Learn, earn blocks, then dig, make, cook, build, sail and beat bad habits.
0Answered
0First try
0Streak
8:00 am
⚡
Storm warning
Play
Friends
Make
Learn
Maths
Science and technology
Mother tongue
Humanities
Singapore and the world
Fire safety
Everyday life (Post-Sec)
Know your Singlish (Post-Sec)
English and the arts
Collection
Go places
Me and view
(c) OpenStreetMap contributors (ODbL)
Paused
Your game is paused. Take a break. Your play time is not counting.
You are out of power. Answer questions to power up, then you can build again.
Click to play
Look around with the mouse, then build.
W A S DmoveSpacejumpLeft clickplaceRight clickbreak or digBBuild or Break mode1–8pick blockIbag, then press the letter or number shown on a blockTabmore blocksEearn blocksLeft clickswing batSpacehop off boatCmake
Left-click to place a block. Right-click a block or the ground to break or dig it (or press B for Break mode). Click the workbench to make things. Power up with timed questions. Make a bat to fight pests. Keep answering or a storm may wreck your build.
Build goals
Blocks in world: 0
Explorer quests
Terrain
Choosing a terrain makes a new landscape and puts every block you placed back in your bag. Dug earth goes on the Earth page of your hotbar.
Power up
Storm warning
Settings
Level
Difficulty
Topic (choose what to practise)
Answer by
Timer speed
Question timer
Auto turns the timer off for P1 and P2, and on for P3 and above.
Grass
Choose Light or Off if the game feels slow.
Questions per round (subject practice)
My grade (questions only use what your grade has learned)
Play plan
Skipped topics
Did you know?
Notes
Worked method
Workbench
Which class are you in?
We will start with questions that fit you. You can change this any time.
Welcome back!
Finishing for today?
What helped you today? Tap one.
Free questions used up
Your free practice questions come back tomorrow. Questions in a class session your teacher starts never use them up. Premium has no daily limit, and you earn full blocks.
Which year are you in?
Pick your Primary year so the game gives you the right questions. You can change it later in the game settings.
🎁 Question crate
Practise until you get it
Getting it
🎯 Goals and quests
Explorer quests
Build goals
Parent panel
Premium
Trainee
Trainee (free): 10 questions per subject each day and basic progress.
Executive, Director and CXO
More subjects, unlimited questions, rare blocks, pets, weekly growth, skill map and a parent summary. Paid plans are not open yet.
For parents: no ads and no chat with strangers. Your child types a first name only. We keep maths progress so you can see how your child is doing.
Tap the ground to walk. Tap objects inside to learn their words.
Hawker Stall
Computing practice
For Secondary 3 and 4 Computing (the G1 modules of the MOE 2026 syllabus). Pick a module. Questions about computers, networks, safe use, documents and media. Right answers earn blocks as usual.
Additional Maths (starter)
Starter set. For Secondary 3 and 4 Additional Mathematics (MOE 2020 G2 and G3 syllabuses). Early topics only, with more to come. Pick a topic. Right answers earn blocks as usual.
Aura skills
Skills for life, not just maths. You grow them by thinking, building, sharing, cooking, visiting friends and reflecting. Earn Seed, Sprout, Tree and Star badges.
Elements
Music studio
Primary Maths: exam question (P5-6)
Topic:
Working:
Think and decide
Short real-life situations. Choose the best answer. Right answers earn blocks and Aura skill points.
What do you want to practise?
1. My level
2. Choose a subject
My mistakes
G1 Maths practice
Questions that follow the G1 Mathematics syllabus for Secondary 1 to 4: number, algebra, geometry, statistics and real-world problems. Pick a level or a strand.
Music practice
Foundations for Sec 3 and 4 music: reading notes, rhythm, scales and keys, terms, and listening. Tunes are old public-domain melodies played by the game itself. Turn Sound on for the listening questions.
Science practice
Quick recall for Primary 3 to 6 Science: living things, cycles, systems, energy and interactions. Pick a level or mix them.
Science and D&T lab
Bahasa Melayu: Peribahasa
Exit ticket
华文 Chinese characters
My classes
Your teacher gives you a class code for each subject. Join with the code. Pick the class you are working on now.
Welcome to Aura Builders
First time here? Do these 3 steps
Type your first name or a nickname. Do not type your full name.
Make a secret code with at least 6 letters or numbers, with no spaces. Tap Suggest a code if you need an idea.
Remember your code. Write it down somewhere safe, then tap Start playing.
Back again? Type the same name and the same secret code.
Play time is over
Teacher controls
Your bag
Type a name to find a block, or tap one to hold it. Press I to open or close the bag. Numbers 1 to 8 pick from the hotbar and Tab changes its page.
Break time!
My character
Make your own look. Earn new hair, hats, accessories and costumes by playing. Your friends see your look when they visit your world.
Lobby
Aura Builders · Aura Life
Beta
Are you sure?
RARE FIND!
Invite friends
Ask a grown-up to share this link. When a friend joins with it, creates a character and answers 20 questions, you earn rewards.
Beta version: premium is free to try
Aura Builders is in beta. We are not collecting any payment now. Premium may have a fee later, and we will tell you first.
Three ways to get premium: 1. Start your one-time 3-day trial. 2. Invite 3 friends: they join with your link, then you get 7 days. 3. Use a premium code from your teacher.
My pets
Adopt a pet
Reading
Choose a reading level, then a passage. Each passage has the same ideas at four levels. Sentences and words get longer as the level goes up.
Where this comes from
Power up
What do you want to practise? Pick a subject. You will choose a topic next.
Zoom
Maths build challenge
What are you using today?
Pick one so the game shows the right instructions. You can change it any time with the Device button.
Family game night
Pick a game. Family members join a table on their own phones with the table code.
Game room
🏆
View:
Drag to turn the board. Pinch or scroll to zoom. Tap a square or column to play. Keyboard: arrow keys to choose, Enter to play, Shift and arrows to turn, plus and minus to zoom, 0 to reset.
How do you want to start?
You can change your mind any time. Tap My house later to get the apartment.
Pests are small creatures that can visit while you build. They are off unless you turn them on. You can change this any time in the Menu.
Maths link
Our block models are small and simple. The real ones are much bigger.
Visit a friend
Saved world found
My progress
Class hub
Free tools for the teacher: timer, picker, groups, points, noise level, Do Now and a lesson log. Everything is saved on this device only.
Class list
Add a class, then paste first names, one per line or separated by commas. Use first names or nicknames only.
Timer
5:00
Random picker
Tap pick
Group maker
Points
Use + custom or − custom for any amount, for example 50 or 100.
Noise level
Uses the microphone only to measure loudness. Nothing is recorded or sent anywhere.
Off
Do Now
Show recall questions on the projector at the start of the lesson. Learners answer on paper or mini whiteboards.
Pick what you have already taught. Questions come only from these.
Exit ticket
Pick a level, a subject and an objective or topic. Show up to 5 questions on the projector. Learners answer on paper or mini whiteboards, then you reveal each answer. Nothing is sent to Learners' devices.
Lesson log and catch-up
Who is absent today? (for your notes only, no reasons)
Catch-up sheet for a Learner
Recent lessons
5:00
5:00
Teacher view
Visit Singapore: the true-size maps of Fort Canning, the Botanic Gardens, Sultan Mosque, Chinatown, Haw Par Villa and Sentosa use map data (c) OpenStreetMap contributors, available under the Open Database Licence (ODbL), openstreetmap.org/copyright. Building heights that were not mapped are estimates, and the ground is flat.
Learner forgot their secret code
Choose the Learner and tap Reset. The Learner then signs in again with the same name and class and types a new secret code, at school or at home. Their saved world moves to the new code.
Start here: 6 steps for a new teacher
Run a live class
Open class controls to choose what Learners do, then tap Start class. Learners join with your class code or QR, and the top 5 for the session shows on the teacher QR panel and the projector view. Tap End class when you finish. The first time, you will make a 4-digit teacher PIN.
Live class board
See who is answering right now, the question each Learner is on, and how they are doing. It updates about every 5 seconds. Learners appear when your class is live and they have a question open. This shows only first name or nickname, the question, right or wrong for the last answer, and the score for this session. It is not a copy of their screen.
Class link by name (older way)
To let Learners join by scanning, use Show QR on your class in Classes above. This older box makes a link from a class name only, without the class code. Type your class name. Send the link to your class. Learners open it, their class fills in, and P1 to P6 classes start at the right level.
Teacher account
Which one am I? Site owner: open the box called Site owner: sign in, below. Other teachers: sign up.
Site owner: sign in
Owner sign-in. Type your owner password. It is not shown to anyone and is not kept on this device.
Forgot the password?
There is no email reset. You set a new password yourself, once, on your computer: open the Terminal in your game folder and run npx wrangler secret put OWNER_PASSWORD, type the new password when asked (nothing shows as you type), then press Enter. It works straight away, and everyone who was signed in as owner has to sign in again. Full steps are in MIGRATION.txt.
I already have an account: sign in
Type your Teacher ID (it starts with T). Lost it? Type your name instead.
Type your secret.
Tap Sign in.
I am new: sign up
Fill in the five boxes below. Use your school email if you have one: school and education emails are approved at once, other emails wait for the site owner.
Choose your own secret (at least 8 letters or numbers).
Tap Sign up. You get a Teacher ID. Write it down with your secret: nobody can read the secret back to you.
We keep your name, organisation and work email only to run your account.
Teachers (owner only)
Make an account for each teacher. Give them the ID and secret. The secret is shown once. Each teacher sees only the classes they make.
Make Premium codes
One code unlocks one child profile. Extra children each need their own code. Each code works once.
Prices: Primary or Secondary $83.16, Both $126.00. Normal 12 months: $118.80 and $180.00.
Classes
Make a class for each subject. Learners join with its code and can be in many classes. Each class has its own plan and roster.
Exit tickets and score tracker
Pick a level, a subject, then an objective or topic (Maths uses the MOE syllabus objectives, other subjects use their topic lists), and set it as the exit ticket for the class in the Class box, or use Add to this week's list to give several. Learners see them first, with a tick when done. Learners get 5 questions (a few objectives have only 3 or 4 different questions so far). 80% or more right is Got it. Objectives and topics without enough questions yet are not listed.
Chinese units for a class
Choose the class in the Class box below. Type a unit name and the characters from the lesson. Learners in that class find it under Class units in Chinese characters. A character can be in many units.
Children who sign up alone get unlimited quizzes and 10 minutes of play a day. Give a parent a code to remove the limit. Each code works once.
Suggest questions
Tell us what topics or kinds of questions to add. A sample question with its answer helps most. We review everything before it goes in.
Ideas box
Suggestions from Learners. Mark one Planned or Done to show it on the Idea wall. Feature + prize puts the idea on the wall and gives its author a rare block.
Class wall
Set a question for your class. Choose whether cards show right away or wait for you.
Session export
What each Learner did in one practice session (one day). Includes free Learners who played online or shared their progress code. Pick the date, then download.
Learner explanations
What Learners in the class chose when asked how they got a wrong answer. Follows the class picked above.
How to play
>16)&255)*f)), g=Math.min(255,Math.round(((n>>8)&255)*f)), b=Math.min(255,Math.round((n&255)*f)); return '#'+((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1);}
if(/^#[0-9a-f]{6}$/i.test(sh)){pal.out=sh; pal.outS=mix(sh,.78); pal.outL=mix(sh,1.2);} pal.leg=cc.pn;
if(/^#[0-9a-f]{6}$/i.test(sk)){pal.skin=sk; pal.skinS=mix(sk,.88);}
}catch(e){}
return pal;
}
function j3Player(){
/* the Learner's own saved Aura look (My character), built like the third-person view, scaled to a 140 cm Aura */
try{
var av=sanitiseAv(S.av||{}), cu=null; try{ if(typeof crW!=='undefined'&&crW){var cc0=crS(); cu=CR_UNI[cc0.path]||CR_UNI.food; av.cs='none'; av.sh=cu[0]; av.pn=cu[1]; if(av.ou==='dress'||av.ou==='skirt')av.ou='pants';} }catch(e0){cu=null;} var g=mkAvatar(av,''); if(cu){ var ex0=cu[2]; function UB(w,h,d,cc,px,py,pz){var b=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),new THREE.MeshLambertMaterial({color:col(cc)})); b.position.set(px,py,pz); g.add(b);} if(ex0==='apron')UB(.5,.62,.04,'#c0463a',0,.98,.16); else if(ex0==='blazer')UB(.57,.72,.3,'#1f3a5f',0,1.05,0); else if(ex0==='coat')UB(.6,.9,.32,'#f2c230',0,.95,0); } g.children.slice().forEach(function(m){if(m.isSprite){g.remove(m); if(m.material){if(m.material.map)m.material.map.dispose(); m.material.dispose();}}});
var legs=[null,null], arms=[null,null];
function pivot(x,y){var p=new THREE.Group(); p.position.set(x,y,0); g.add(p); return p;}
function near(a,b){return Math.abs(a-b)<.004;}
arms[0]=pivot(-.38,1.35); arms[1]=pivot(.38,1.35);
g.children.slice().forEach(function(m){
if(!m.isMesh||!m.geometry||!m.geometry.parameters)return; var q=m.geometry.parameters, w=q.width, h=q.height, p=m.position, i=p.x<0?0:1;
if(near(w,.25)&&near(h,.7)&&near(p.y,.35)){var pv=pivot(p.x,.7); g.remove(m); m.position.set(0,-.35,p.z); pv.add(m); legs[i]=pv;}
else if(near(w,.2)&&near(h,.65)&&near(p.y,1.02)&&near(Math.abs(p.x),.38)){g.remove(m); m.position.set(0,p.y-1.35,p.z); arms[i].add(m);}
else if(near(w,.2)&&near(h,.18)&&near(p.y,.66)&&near(Math.abs(p.x),.38)){g.remove(m); m.position.set(0,p.y-1.35,p.z); arms[i].add(m);}
});
if(!legs[0]||!legs[1])return null;
var root=new THREE.Group(); root.add(g); root.scale.setScalar(.87);
root.userData={legs:legs,arms:arms,aura:[]}; return root;
}catch(e){return null;}
}
function j3Open(only){
if(typeof THREE==='undefined')return false;
var place=only&&J3PLACE[only.id]; if(only&&only.id&&!place)return false;
pauseGame(); JB=null;
var m=document.getElementById('jobModal'); m.hidden=false; m.scrollTop=0;
try{
if(!J3){
var st=document.getElementById('j3stage'), old=st.querySelector('canvas'); if(old)old.parentNode.removeChild(old);
var cv=document.createElement('canvas'); cv.id='j3cv'; cv.setAttribute('aria-label','3D map of Town Centre'); st.insertBefore(cv,st.firstChild);
document.getElementById('j3').hidden=false;
J3=j3Make({makePlayer:function(){return null;},pal:j3Pal,jobName:function(k){var j=j3JobById(J3JOB[k]); return j?j.name:'';},onShift:j3Shift,onList:j3List,onWords:j3Words,onHome:function(){closeModals();}},J3KEEP);
}
j3Mode('town'); if(place)J3.goTo(place);
try{document.getElementById('jobPanel').focus({preventScroll:true});}catch(_){}
return true;
}catch(err){
j3Free(); m.hidden=true; try{console.warn('Town Centre 3D unavailable, using the job list',err);}catch(_){}
return false;
}
}
(function(){
var a=document.getElementById('jobList'); if(a)a.addEventListener('click',j3List);
var b=document.getElementById('jobTown'); if(b)b.addEventListener('click',function(){JB=null; j3Mode('town'); document.getElementById('jobModal').scrollTop=0;});
var c=document.getElementById('j3words'); if(c)c.addEventListener('click',j3Words);
})();
function j3Make(OPT,KEEP){
var SMALL=(window.innerWidth<700)||('ontouchstart' in window), stage=document.getElementById('j3stage'), cv=document.getElementById('j3cv'), panel=document.getElementById('j3p'), dead=false, raf=0, paused=false, LIS=[];
function on(t,e,f,o){t.addEventListener(e,f,o||false); LIS.push([t,e,f,o||false]);}
var hud=document.getElementById('j3hud'); if(hud&&!('ontouchstart' in window))hud.textContent='Click the ground or use the arrow keys or W A S D to walk. Click objects inside to learn their words.';
var renderer=new THREE.WebGLRenderer({canvas:cv,antialias:true});
renderer.setPixelRatio(Math.min(window.devicePixelRatio||1,2)); renderer.shadowMap.enabled=true; renderer.shadowMap.type=THREE.PCFSoftShadowMap;
var scene=new THREE.Scene(); scene.background=new THREE.Color(0xbfe3f7); scene.fog=new THREE.Fog(0xbfe3f7,70,170);
var cam=new THREE.PerspectiveCamera(50,1,0.1,300);
scene.add(new THREE.HemisphereLight(0xffffff,0x88aa77,.85));
var sun=new THREE.DirectionalLight(0xffffff,.7); sun.position.set(40,70,45); sun.castShadow=true; sun.shadow.mapSize.set(SMALL?2048:4096,SMALL?1024:2048); var sc=sun.shadow.camera; sc.left=-135; sc.right=135; sc.top=60; sc.bottom=-60; sc.near=10; sc.far=200; sun.shadow.bias=-.0006; scene.add(sun);
function mat(c){return new THREE.MeshLambertMaterial({color:c});}
function box(w,h,d,c,x,y,z,parent){var m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),mat(c)); m.position.set(x,y,z); (parent||scene).add(m); return m;}
function sign(text,w,h,bg,fg,x,y,z,ry){
var c=document.createElement('canvas'); c.width=512; c.height=Math.round(512*h/w); var g=c.getContext('2d');
g.fillStyle=bg; g.fillRect(0,0,c.width,c.height); g.fillStyle=fg; g.font='700 '+Math.round(c.height*.5)+'px Rubik,Arial,sans-serif'; g.textAlign='center'; g.textBaseline='middle'; g.fillText(text,c.width/2,c.height/2+2);
var t=new THREE.CanvasTexture(c); var m=new THREE.Mesh(new THREE.PlaneGeometry(w,h),new THREE.MeshBasicMaterial({map:t})); m.position.set(x,y,z); m.rotation.y=ry||0; scene.add(m); return m;
}
function tree(x,z,s){var t=new THREE.Group(); box(.6*s,2*s,.6*s,0x7a5230,0,1*s,0,t); var l=new THREE.Mesh(new THREE.ConeGeometry(1.8*s,4*s,8),mat(0x3f8f4f)); l.position.y=4*s; t.add(l); t.position.set(x,0,z); scene.add(t);}
// ground, road, pavement
var ground=new THREE.Mesh(new THREE.PlaneGeometry(320,260),mat(0x8fcf86)); ground.rotation.x=-Math.PI/2; ground.receiveShadow=true; scene.add(ground);
box(260,.05,10,0x6b7078,0,.03,12); // road
for(var i=-126;i<126;i+=8)box(4,.06,.4,0xf2e6b0,i,.07,12);
box(260,.1,4,0xcfc9b8,0,.05,6); // pavement
box(70,.1,22,0xd8d2c0,0,.04,-4); // plaza in front of buildings
box(80,.1,22,0xd8d2c0,73,.04,-4);
// ---------- realism helpers ----------
var fans=[];
function ctex(w,h,fn,rx,ry){var c=document.createElement('canvas'); c.width=w; c.height=h; fn(c.getContext('2d'),w,h); var t=new THREE.CanvasTexture(c); t.wrapS=t.wrapT=THREE.RepeatWrapping; t.repeat.set(rx||1,ry||1); t.anisotropy=4; return t;}
function tm(t){return new THREE.MeshLambertMaterial({map:t});}
function sbox(w,h,d,m,x,y,z,parent,noShadow){var o=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),m); o.position.set(x,y,z); o.castShadow=!noShadow; o.receiveShadow=true; (parent||scene).add(o); return o;}
function cyl(rt,rb,h,c,x,y,z,parent,seg){var o=new THREE.Mesh(new THREE.CylinderGeometry(rt,rb,h,seg||10),mat(c)); o.position.set(x,y,z); o.castShadow=true; o.receiveShadow=true; (parent||scene).add(o); return o;}
function facade(bays,floors,base,glass,frame){return ctex(128,128,function(g,w,h){
g.fillStyle=base; g.fillRect(0,0,w,h);
g.fillStyle=frame; g.fillRect(18,22,92,84);
var gr=g.createLinearGradient(0,24,0,104); gr.addColorStop(0,glass[0]); gr.addColorStop(1,glass[1]); g.fillStyle=gr; g.fillRect(22,26,84,76);
g.fillStyle=frame; g.fillRect(63,26,3,76); g.fillRect(22,62,84,3);
g.fillStyle='rgba(255,255,255,.35)'; g.beginPath(); g.moveTo(26,98); g.lineTo(50,28); g.lineTo(60,28); g.lineTo(36,98); g.fill();
g.fillStyle='rgba(0,0,0,.12)'; g.fillRect(0,h-6,w,6); g.fillRect(14,106,100,5);
},bays,floors);}
function curtain(cols,rows){return ctex(64,64,function(g,w,h){var gr=g.createLinearGradient(0,0,w,h); gr.addColorStop(0,'#6fb0d4'); gr.addColorStop(1,'#3f7fa8'); g.fillStyle=gr; g.fillRect(0,0,w,h); g.fillStyle='#e8eef3'; g.fillRect(0,0,w,3); g.fillRect(0,0,3,h); g.fillStyle='rgba(255,255,255,.25)'; g.fillRect(8,6,10,52);},cols,rows);}
function terrazzo(rx,ry){return ctex(128,128,function(g,w,h){g.fillStyle='#d9bd92'; g.fillRect(0,0,w,h); for(var i=0;i<220;i++){g.fillStyle=['#fff3dc','#b98d60','#9a8f86','#e9d3ae'][i%4]; g.globalAlpha=.55; g.fillRect(Math.random()*w,Math.random()*h,2+Math.random()*3,2+Math.random()*3);} g.globalAlpha=1; g.strokeStyle='rgba(90,70,50,.35)'; g.lineWidth=2; g.strokeRect(0,0,w,h);},rx,ry);}
function corrug(rx,ry,c1,c2){return ctex(64,64,function(g,w,h){for(var i=0;i<8;i++){g.fillStyle=i%2?c1:c2; g.fillRect(i*8,0,8,h);}},rx,ry);}
function hdbTex(bays,floors,base,band){return ctex(64,64,function(g,w,h){g.fillStyle=base; g.fillRect(0,0,w,h); g.fillStyle=band; g.fillRect(0,0,w,10); g.fillStyle='#41576b'; g.fillRect(10,20,16,20); g.fillRect(38,20,16,20); g.fillStyle='rgba(255,255,255,.4)'; g.fillRect(10,20,16,3); g.fillRect(38,20,16,3); g.fillStyle='#cfc9b8'; g.fillRect(0,46,w,4);},bays,floors);}
var PEND=[];
function person(x,y,z,c,parent,seated,face){
var p=new THREE.Group(); p.position.set(x,y,z); (parent||scene).add(p);
var job=function(){
var col=new THREE.Color(c), hx=function(k){return '#'+new THREE.Color(Math.min(1,col.r*k),Math.min(1,col.g*k),Math.min(1,col.b*k)).getHexString();};
var f=mkAura({noPack:1,noAura:1,plain:1,noHat:1,pal:{out:hx(1),outS:hx(.75),outL:hx(1.25),acc:hx(.9),accS:hx(.7),accL:hx(1.1)}}); f.scale.setScalar(.574);
if(seated){f.userData.legs.forEach(function(l){l.rotation.x=-Math.PI/2;}); f.position.y=-.356;}
if(face)p.rotation.y=Math.atan2(face[0]-x,face[1]-z); p.add(f);
};
if(typeof A_HEAD!=='undefined'&&A_HEAD)job(); else PEND.push(job);
return p;
}
var solids=[]; // [x,z,hw,hd]
function solid(x,z,w,d){solids.push([x,z,w/2,d/2]);}
// ---------- Hawker centre (open hall, pillars, pitched metal roof, stalls, tables) ----------
(function(){
var X=-30,Z=-6, g=new THREE.Group(); scene.add(g); g.position.set(X,0,Z);
var tz=terrazzo(7,5); sbox(28,.25,20,tm(tz),0,.12,0,g,true);
// back wall (tiles) and side walls (low)
sbox(28,6.4,.6,mat(0xe9e2cf),0,3.2,-9.7,g); sbox(.6,2.2,19,mat(0xe9e2cf),-13.7,1.1,0,g); sbox(.6,2.2,19,mat(0xe9e2cf),13.7,1.1,0,g);
// pillars
[-12,-6,0,6,12].forEach(function(px){[-8,0,8].forEach(function(pz){sbox(.7,6.2,.7,mat(0xcfd3d6),px,3.1,pz,g);});});
// roof: two pitched halves of corrugated metal, ridge fascia, red edge beams
var th=Math.atan2(2.8,10), rl=Math.sqrt(100+2.8*2.8);
var rm=tm(corrug(14,3,'#b8c6d0','#9fb0bc'));
var rf=sbox(29,.3,rl,rm,0,7.6,5,g); rf.rotation.x=th; var rb=sbox(29,.3,rl,rm,0,7.6,-5,g); rb.rotation.x=-th;
sbox(29,.5,.4,mat(0xb83a2c),0,6.15,10.2,g); sbox(29,.5,.4,mat(0xb83a2c),0,6.15,-10.2,g); sbox(29.2,.4,.5,mat(0x8a8f94),0,9.05,0,g);
// lights and ceiling fans
[-9,-3,3,9].forEach(function(fx){[-3,4].forEach(function(fz){
var f=new THREE.Group(); f.position.set(fx,5.6,fz); g.add(f);
cyl(.08,.08,.7,0x555555,0,.35,0,f,6); var bl=new THREE.Group(); f.add(bl); fans.push(bl);
for(var b=0;b<4;b++){var bd=sbox(2.2,.06,.35,mat(0xeeeeee),0,0,0,bl,true); bd.rotation.y=b*Math.PI/2; bd.position.x=Math.cos(b*Math.PI/2)*1.1; bd.position.z=-Math.sin(b*Math.PI/2)*1.1;}
var lp=sbox(.5,.25,.5,new THREE.MeshBasicMaterial({color:0xfff2c0}),0,-.4,0,f,true);
});});
// stalls along the back wall
var stalls=[['Chicken Rice','#01-12','#d9892b'],['Laksa','#01-13','#c8402f'],['Satay','#01-14','#7a5230'],['Mixed Rice','#01-15','#3f8f4f'],['Drinks','#01-16','#2f7fb5'],['Kopi & Toast','#01-17','#8a4fa0']];
stalls.forEach(function(s,i){
var sx=-11.6+i*4.64;
sbox(4.3,1.3,1.1,mat(0xc9cdd1),sx,.95,-7.8,g); // steel counter
sbox(4.3,.12,1.3,mat(0xe9edf0),sx,1.66,-7.8,g);
sbox(4.3,3.4,.25,mat(s[2]),sx,3.3,-8.9,g); // stall back panel
sbox(4.3,1.1,.1,mat(0x2b2a26),sx,1.5,-8.7,g,true); // shutter / kitchen opening
sbox(.15,3.4,.15,mat(0x888888),sx-2.15,3.3,-7.4,g); sbox(.15,3.4,.15,mat(0x888888),sx+2.15,3.3,-7.4,g);
sbox(4.3,.2,1.9,mat(s[2]),sx,5.05,-8.0,g,true); // small awning
sign(s[0],4.1,.9,s[2],'#ffffff',X+sx,4.5,Z-7.1);
sign(s[1],1.5,.5,'#fff6d6','#2b2a26',X+sx+1.2,2.2,Z-7.2);
var o=sbox(.5,.35,.5,mat(0xffffff),sx-1,1.95,-7.8,g,true); sbox(.35,.1,.35,mat(0xb5651d),sx-1,2.15,-7.8,g,true);
});
// tables, stools, bowls and diners
var tc=[0x2f6fb0,0xc8402f]; var dn=[0x2f7d4f,0xd9892b,0x7b56c4,0xe0707f,0x3f7fd0,0x555555];
var ti=0;
[-9,-3,3,9].forEach(function(tx,ci){[-2,4].forEach(function(tzz,ri){
cyl(1.05,1.05,.12,0xe6e0d0,tx,1.15,tzz,g,18); cyl(.12,.12,1.1,0x666666,tx,.6,tzz,g,6);
[[1.5,0],[-1.5,0],[0,1.5],[0,-1.5]].forEach(function(o,k){
cyl(.38,.38,.15,tc[(ci+ri+k)%2],tx+o[0],.75,tzz+o[1],g,10); cyl(.07,.07,.7,0x777777,tx+o[0],.38,tzz+o[1],g,5);
if((ti*4+k)%3===0){person(tx+o[0]*1.12,.86,tzz+o[1]*1.12,dn[(ti+k)%dn.length],g,true,[tx,tzz]);}
});
cyl(.28,.22,.18,0xffffff,tx-.3,1.3,tzz,g,10); cyl(.28,.1,.04,0xb5651d,tx-.3,1.4,tzz,g,10);
cyl(.18,.14,.14,0xffffff,tx+.4,1.28,tzz+.2,g,8);
solid(X+tx,Z+tzz,2.4,2.4); ti++;
});});
// tray return and bins
sbox(1.8,1.3,.9,mat(0x9aa4ad),14.2,.65,-4,g); sbox(.7,1.1,.7,mat(0x3f8f4f),14.2,.55,-1.5,g); sbox(.7,1.1,.7,mat(0x2f6fb0),14.2,.55,-.4,g);
// entrance sign on the front fascia and planters
sign('Aura Hawker Centre',13,1.8,'#c8402f','#ffffff',X,7.0,Z+10.45);
[-13,13].forEach(function(px){sbox(2,.7,1,mat(0x8a6508),px,.35,10.8,g); var b=new THREE.Mesh(new THREE.SphereGeometry(.8,10,8),mat(0x3f8f4f)); b.position.set(px,1.1,10.8); g.add(b);});
solid(X,Z-8.6,28,3.2);
})();
// ---------- Hospital (multi-storey block, glass podium, wing, helipad, ambulance bay) ----------
(function(){
var X=4,Z=-6, g=new THREE.Group(); scene.add(g); g.position.set(X,0,Z);
var roofM=mat(0xb9bec4), sideT=tm(facade(4,6,'#eef1f5',['#88b6d8','#5f93bd'],'#6d7b88')), frontT=tm(facade(8,6,'#eef1f5',['#88b6d8','#5f93bd'],'#6d7b88'));
// main tower
sbox(22,20,9,[sideT,sideT,roofM,roofM,frontT,frontT],0,10,-1.5,g);
sbox(22.4,.7,9.4,mat(0x2f5f9e),0,20.2,-1.5,g); sbox(22.2,.5,9.2,mat(0x2f5f9e),0,6.8,-1.5,g);
// glass podium and entrance
var cw=tm(curtain(7,1)); sbox(24,4.6,11,[mat(0xdfe5ea),mat(0xdfe5ea),mat(0xcfd5da),roofM,cw,cw],0,2.3,-.5,g);
sbox(24.4,.4,11.4,mat(0xffffff),0,4.8,-.5,g);
sbox(5.4,3.3,.25,mat(0x1c3550),0,1.65,5.1,g,true); sbox(.15,3.3,.3,mat(0xdddddd),0,1.65,5.15,g,true);
sbox(9,.4,4,mat(0xffffff),0,4.2,6.8,g); sbox(.4,4.2,.4,mat(0xcfd3d6),-4.2,2.1,8.5,g); sbox(.4,4.2,.4,mat(0xcfd3d6),4.2,2.1,8.5,g);
sbox(9,.1,4.6,mat(0xc9c3b0),0,.06,7.6,g,true); // steps/landing
sign('AURA GENERAL HOSPITAL',13,1.5,'#1f4a80','#ffffff',X,18.6,Z+3.05);
sbox(3.2,1.0,.3,mat(0xd03a3a),7.6,13.2,3.1,g); sbox(1.0,3.2,.3,mat(0xd03a3a),7.6,13.2,3.1,g);
// side wing with helipad
var wingT=tm(facade(4,4,'#e9edf2',['#88b6d8','#5f93bd'],'#6d7b88')); sbox(12,13,8,[wingT,wingT,roofM,roofM,wingT,wingT],19,6.5,-1.5,g);
sbox(12.4,.5,8.4,mat(0x2f5f9e),19,13.2,-1.5,g);
cyl(2.8,2.8,.15,0x4a4f55,19,13.55,-1.5,g,24);
var hp=new THREE.Mesh(new THREE.PlaneGeometry(3.6,3.6),new THREE.MeshBasicMaterial({map:ctex(128,128,function(c,w,h){c.fillStyle='#4a4f55'; c.fillRect(0,0,w,h); c.fillStyle='#ffffff'; c.font='700 90px Rubik,Arial'; c.textAlign='center'; c.textBaseline='middle'; c.fillText('H',w/2,h/2+4); c.strokeStyle='#e3b53a'; c.lineWidth=6; c.beginPath(); c.arc(w/2,h/2,56,0,6.3); c.stroke();})})); hp.rotation.x=-Math.PI/2; hp.position.set(19,13.66,-1.5); g.add(hp);
// ambulance bay and ambulance
sbox(9,.35,4.4,mat(0xd03a3a),19,4.6,4.6,g); sbox(.3,4.6,.3,mat(0xcfd3d6),14.8,2.3,6.4,g); sbox(.3,4.6,.3,mat(0xcfd3d6),23.2,2.3,6.4,g);
sbox(3.6,2.6,.25,mat(0x1c3550),19,1.3,2.6,g,true); sign('EMERGENCY',5.6,.9,'#d03a3a','#ffffff',X+19,5.4,Z+6.85);
var amb=new THREE.Group(); sbox(5,2.4,2.2,mat(0xffffff),0,1.6,0,amb); sbox(1.6,1.7,2.2,mat(0xffffff),-2.7,1.35,0,amb); sbox(5.05,.35,2.25,mat(0xd03a3a),0,1.5,0,amb,true); sbox(.8,.25,.5,new THREE.MeshBasicMaterial({color:0x3f7fff}),-.8,2.9,0,amb,true);
[-1.6,1.8].forEach(function(wx){cyl(.5,.5,.3,0x222222,wx,.5,1.05,amb,10).rotation.x=Math.PI/2; cyl(.5,.5,.3,0x222222,wx,.5,-1.05,amb,10).rotation.x=Math.PI/2;});
amb.position.set(19,0,4.2); g.add(amb);
// flagpole and planters
cyl(.1,.1,9,0xcccccc,-13,4.5,6,g,6); var fl=new THREE.Mesh(new THREE.PlaneGeometry(2.4,1.4),new THREE.MeshBasicMaterial({color:0xd03a3a,side:THREE.DoubleSide})); fl.position.set(-11.7,8.1,6); g.add(fl);
[-9.5,9.5].forEach(function(px){sbox(3.2,.7,1.4,mat(0x8a6508),px,.35,7.6,g); for(var b=0;b<3;b++){var s=new THREE.Mesh(new THREE.SphereGeometry(.7,10,8),mat(0x3f8f4f)); s.position.set(px-1+b,1.1,7.6); g.add(s);}});
solid(X,Z-.5,24,11); solid(X+19,Z-1.5,12,8);
})();
// ---------- New buildings (outside): Polyclinic, Bakery, School canteen, MRT station ----------
(function(){
var Z=-6;
function grp(X){var g=new THREE.Group(); g.position.set(X,0,Z); scene.add(g); return g;}
// --- Polyclinic and pharmacy ---
(function(){
var X=46, g=grp(X), roofM=mat(0xb9bec4), ft=tm(facade(5,3,'#f1f6f4',['#9fd3cc','#6fb3aa'],'#5d8a84')), st=tm(facade(3,3,'#f1f6f4',['#9fd3cc','#6fb3aa'],'#5d8a84'));
sbox(16,10.5,10,[st,st,roofM,roofM,ft,ft],0,5.25,-2,g);
sbox(16.4,.6,10.4,mat(0x2a8f86),0,10.8,-2,g); sbox(16.2,.5,10.2,mat(0x2a8f86),0,4.0,-2,g);
sbox(4,3.4,.25,mat(0x1c3550),-2.2,1.7,3.05,g,true); sbox(.15,3.4,.3,mat(0xdddddd),-2.2,1.7,3.1,g,true);
sbox(5,3.4,.25,mat(0x1c3550),4.2,1.7,3.05,g,true); sbox(.15,3.4,.3,mat(0xdddddd),4.2,1.7,3.1,g,true);
sbox(10.4,.35,2.8,mat(0xffffff),1,3.9,4.4,g); sbox(.35,3.9,.35,mat(0xcfd3d6),-4.1,1.95,5.6,g); sbox(.35,3.9,.35,mat(0xcfd3d6),6.1,1.95,5.6,g);
sign('AURA POLYCLINIC',12,1.3,'#1f6f68','#ffffff',X,8.2,Z+3.08);
sign('Doctors',4,.8,'#ffffff','#1f6f68',X-2.2,5.0,Z+3.08); sign('Pharmacy',4,.8,'#2f8f5b','#ffffff',X+4.2,5.0,Z+3.08);
sbox(2.4,.7,.3,mat(0x2f8f5b),7.2,7.0,3.1,g); sbox(.7,2.4,.3,mat(0x2f8f5b),7.2,7.0,3.1,g);
sbox(9,.1,3.6,mat(0xc9c3b0),1,.06,5,g,true);
[-7.2,8.6].forEach(function(px){sbox(2.4,.7,1,mat(0x8a6508),px,.35,4.6,g); var b=new THREE.Mesh(new THREE.SphereGeometry(.7,10,8),mat(0x3f8f4f)); b.position.set(px,1.05,4.6); g.add(b);});
solid(X,Z-2,16,10);
})();
// --- Bakery ---
(function(){
var X=64, g=grp(X);
sbox(14,6.8,9,mat(0xf1e2c4),0,3.4,-2,g); sbox(14.4,.5,9.4,mat(0x8a4b2a),0,7.0,-2,g);
sbox(1.2,2.4,1.2,mat(0xb86f4a),4.8,8.4,-4,g);
sbox(8.6,2.8,.2,mat(0x9cc4e8),-1.2,2.5,2.55,g,true); sbox(8.8,.25,.3,mat(0x6b4c05),-1.2,3.95,2.6,g,true); sbox(8.8,.25,.3,mat(0x6b4c05),-1.2,1.05,2.6,g,true);
[-4,-2,0,2].forEach(function(bx2,i){sbox(1.2,.6,.9,mat(i%2?0xd9a05a:0xc88a3f),bx2-.6,1.45,2.3,g,true); var r=new THREE.Mesh(new THREE.SphereGeometry(.45,10,8),mat(0xe3a24c)); r.scale.set(1,.6,1); r.position.set(bx2+.4,1.5,2.3); g.add(r);});
sbox(2.6,3.4,.25,mat(0x6b3f23),5.0,1.7,2.55,g,true); sbox(.4,.4,.2,mat(0xf6c744),5.6,1.7,2.7,g,true);
var aw=tm(corrug(6,1,'#f2a1b3','#ffffff')); var a=sbox(13,.2,3.2,aw,0,4.4,4.0,g); a.rotation.x=.28;
sbox(13,.5,.2,mat(0xf2a1b3),0,3.4,5.45,g,true);
sign('AURA BAKERY',9.5,1.3,'#7a4528','#fff1d6',X,6.0,Z+2.58);
sbox(.8,.8,.8,mat(0xcfd3d6),-6,.4,3.8,g); sbox(1,.7,.8,mat(0x8a6508),6.2,.35,4.3,g);
var bsh=new THREE.Mesh(new THREE.SphereGeometry(.65,10,8),mat(0x3f8f4f)); bsh.position.set(6.2,1.0,4.3); g.add(bsh);
solid(X,Z-2,14,9);
})();
// --- School canteen ---
(function(){
var X=82, g=grp(X);
sbox(17,6.2,9,mat(0xf3e9b8),0,3.1,-2,g); sbox(18.4,.5,11.4,mat(0x3f8f6a),0,6.5,-1.2,g);
sbox(13,3.6,.25,mat(0x2b2a26),0,1.9,2.55,g,true);
[-6.5,-2.2,2.2,6.5].forEach(function(px){sbox(.5,5.8,.5,mat(0xcfd3d6),px,2.9,3.6,g);});
sbox(14,.4,1.6,mat(0xc9cdd1),0,1.3,2.2,g,true);
for(var i=0;i<6;i++){sbox(1.4,.8,.1,mat([0xd9892b,0xc8402f,0x3f8f4f,0x2f7fb5,0xe3b53a,0x8a4fa0][i]),-5.5+i*2.2,2.4,2.4,g,true);}
sign('SCHOOL CANTEEN',11,1.3,'#2c6f52','#ffffff',X,5.4,Z+3.95);
[-5,5].forEach(function(tx){cyl(.9,.9,.1,0xe6e0d0,tx,1.15,6.6,g,16); cyl(.1,.1,1.1,0x666666,tx,.6,6.6,g,6); [[-1.3,0],[1.3,0]].forEach(function(o){cyl(.35,.35,.14,0x2f6fb0,tx+o[0],.75,6.6,g,10); cyl(.07,.07,.7,0x777777,tx+o[0],.38,6.6,g,5);});});
sbox(.15,8,.15,mat(0xcccccc),-8.2,4,5.2,g);
solid(X,Z-2,17,9); solid(X-5,Z+6.6,3.4,2.4); solid(X+5,Z+6.6,3.4,2.4);
})();
// --- MRT station ---
(function(){
var X=100, g=grp(X), cw=tm(curtain(5,2)), conc=mat(0xd2d5d9);
sbox(16,9.5,10,[conc,conc,mat(0xb9bec4),mat(0xb9bec4),cw,cw],0,4.75,-2,g);
sbox(16.4,.9,10.4,mat(0xd9372c),0,9.9,-2,g); sbox(16.3,.4,10.3,mat(0xd9372c),0,3.9,-2,g);
sbox(5.2,3.5,.25,mat(0x1c3550),0,1.75,3.05,g,true); sbox(.15,3.5,.3,mat(0xdddddd),0,1.75,3.1,g,true);
sbox(9,.4,3,mat(0x4a4f55),0,4.2,4.4,g); [-4.2,4.2].forEach(function(px){sbox(.4,4.2,.4,mat(0x6b7078),px,2.1,5.6,g);});
sign('AURA MRT STATION',11.5,1.4,'#222a33','#ffffff',X,7.0,Z+3.08);
var rd=new THREE.Mesh(new THREE.CircleGeometry(1.1,24),new THREE.MeshBasicMaterial({color:0xd9372c})); rd.position.set(-6.6,7,3.1); g.add(rd);
var rw=new THREE.Mesh(new THREE.RingGeometry(.55,.75,24),new THREE.MeshBasicMaterial({color:0xffffff})); rw.position.set(-6.6,7,3.12); g.add(rw);
sbox(9,.1,3.4,mat(0xc9c3b0),0,.06,5,g,true);
[-7.5,7.5].forEach(function(px){sbox(2,.7,1,mat(0x8a6508),px,.35,4.8,g); var b=new THREE.Mesh(new THREE.SphereGeometry(.7,10,8),mat(0x3f8f4f)); b.position.set(px,1.05,4.8); g.add(b);});
solid(X,Z-2,16,10);
})();
})();
// --- School (outside) ---
(function(){
var X=118,Z=-6,g=new THREE.Group(); g.position.set(X,0,Z); scene.add(g);
var wl=mat(0xf4f1e6), rf=mat(0x2f5f9e);
sbox(18,8.4,10,wl,0,4.2,-2,g); sbox(18.6,.6,10.6,rf,0,8.7,-2,g); sbox(19,.4,11,mat(0xcfd3d6),0,.2,-2,g);
[-6.5,-2.2,2.2,6.5].forEach(function(px){[2.3,5.6].forEach(function(py){sbox(2.2,1.9,.12,mat(0x9fd3ea),px,py,3.06,g,true); sbox(2.5,.18,.2,mat(0xffffff),px,py-1.05,3.1,g);});});
sbox(3.2,3.6,.2,mat(0x2f5f9e),0,1.8,3.1,g,true); sbox(.1,3.4,.25,mat(0xffffff),0,1.8,3.2,g,true);
sbox(5,.4,2.4,mat(0xcfd3d6),0,4,4.2,g); [-2.2,2.2].forEach(function(px){sbox(.35,4,.35,mat(0xcfd3d6),px,2,5.2,g);});
sign('AURA PRIMARY SCHOOL',12,1.5,'#2f5f9e','#ffffff',X,7.3,Z+3.15);
sbox(18,.5,1.6,mat(0x3f8f4f),0,.25,4.8,g); [-8,8].forEach(function(px){var b=new THREE.Mesh(new THREE.SphereGeometry(.8,10,8),mat(0x3f8f4f)); b.position.set(px,1.1,4.8); g.add(b);});
solid(X,Z-2,18,10);
})();
// bus stop and bus
(function(){
var X=-52,Z=5; box(.3,3.4,.3,0x555555,X-3,1.7,Z); box(.3,3.4,.3,0x555555,X+3,1.7,Z); box(7.5,.4,3,0xe0a81c,X,3.5,Z); box(7,.2,1,0x8a6508,X,1.2,Z-.6);
sign('Bus to Home',6,1.4,'#2f5f9e','#fff',X,4.6,Z+1.6);
var bus=new THREE.Group(); box(11,3.4,3.4,0xe0a81c,0,2.2,0,bus); box(10,1.2,3.5,0x9cc4e8,0,2.8,0,bus); [-3.5,3.5].forEach(function(w){var wh=new THREE.Mesh(new THREE.CylinderGeometry(.7,.7,.4,12),mat(0x222222)); wh.rotation.x=Math.PI/2; wh.position.set(w,.7,1.5); bus.add(wh); var wh2=wh.clone(); wh2.position.z=-1.5; bus.add(wh2);}); bus.position.set(X-14,0,13.5); scene.add(bus);
solid(X-14,13.5,11,3.4);
// yellow boarding line, bus stop pole with stop code and service plates
box(9,.03,.28,0xf2c200,X,.12,7.55);
box(.18,4.6,.18,0x666b72,X+4.6,2.3,Z+.5);
sign('BUS STOP',2.6,.6,'#2f5f9e','#ffffff',X+4.6,5.0,Z+.62);
sign('Stop A-01',2.6,.8,'#ffffff','#2b2a26',X+4.6,4.3,Z+.62);
sign('Aura Route A1',2.6,.55,'#e0a81c','#2b2a26',X+4.6,3.6,Z+.62);
})();
// trees and lamps
[[-60,-12],[-48,-14],[-40,-16],[10,-16],[18,-18],[28,-18],[50,-20],[66,-20],[84,-20],[102,-20],[116,-16],[-58,32],[-30,32],[0,32],[30,32],[56,32],[86,32],[112,30],[-14,-18]].forEach(function(t){tree(t[0],t[1],1+((t[0]*7)%3)*.1); solid(t[0],t[1],1.2,1.2);});
var pl=(OPT.makePlayer&&OPT.makePlayer())||null; if(!pl){pl=mkAura({pal:(OPT.pal&&OPT.pal())||{}}); pl.scale.setScalar(.574);} scene.add(pl); PEND.forEach(function(j){j();}); PEND.length=0;
var legL=pl.userData.legs[0], legR=pl.userData.legs[1], armL=pl.userData.arms[0], armR=pl.userData.arms[1];
pl.position.set(0,0,10); var target=null, camAng=0;
var keys={};
var spots={hawker:{x:-30,z:5.5,name:'Hawker Centre',path:'Food path'},hospital:{x:4,z:5.5,name:'Hospital',path:'Healthcare path'},bus:{x:-52,z:8.4,name:'Bus stop',path:''}};
function resize(){var w=stage.clientWidth,h=stage.clientHeight; if(!w||!h)return; renderer.setSize(w,h,false); cam.aspect=w/h; cam.updateProjectionMatrix();}
on(window,'resize',resize); var ro=null; try{ro=new ResizeObserver(resize); ro.observe(stage);}catch(e){} resize();
var ray=new THREE.Raycaster(), v2=new THREE.Vector2();
on(cv,'pointerdown',function(e){
var r=cv.getBoundingClientRect(); v2.set(((e.clientX-r.left)/r.width)*2-1,-((e.clientY-r.top)/r.height)*2+1); ray.setFromCamera(v2,cam);
if(inside){
var ih=ray.intersectObjects(inItems,true)[0];
if(ih){var o=ih.object; while(o&&!(o.userData&&o.userData.word))o=o.parent; if(o){selItem=o; seen[inside]=seen[inside]||{}; var wasNew=!seen[inside][o.userData.word]; seen[inside][o.userData.word]=1; if(wasNew&&!bonus&&Object.keys(seen[inside]).length%3===0){var bk=BANK[inside]; bonus=bk[bq[inside]%bk.length]; bq[inside]++; bonusPick=-1;} setInPanel(); var wp=new THREE.Vector3(); o.getWorldPosition(wp); target=new THREE.Vector3(Math.max(-IN_BOUNDS.x,Math.min(IN_BOUNDS.x,wp.x)),0,Math.max(IN_BOUNDS.zMin,Math.min(IN_BOUNDS.zMax,wp.z+3.2))); return;}}
var fh=ray.intersectObject(inFloor)[0]; if(fh){target=new THREE.Vector3(fh.point.x,0,fh.point.z);} return;
}
var hit=ray.intersectObject(ground)[0]; if(hit){autoEnter=null; lockKey=null; target=new THREE.Vector3(hit.point.x,0,hit.point.z); tapGoal={x:hit.point.x,z:hit.point.z};}
});
on(window,'keydown',function(e){if(paused||e.ctrlKey||e.metaKey||e.altKey)return; var k=(e.key||'').toLowerCase(); if(['arrowup','arrowdown','arrowleft','arrowright','w','a','s','d'].indexOf(k)<0)return; var t=e.target&&e.target.tagName; if(t==='INPUT'||t==='TEXTAREA'||t==='SELECT')return; keys[k]=true; tapGoal=null; if(k.length>1)e.preventDefault();});
on(window,'keyup',function(e){keys[(e.key||'').toLowerCase()]=false;});
Array.prototype.forEach.call(document.querySelectorAll('#j3 [data-go]'),function(b){on(b,'click',function(){if(inside)return; var k=b.getAttribute('data-go'), s=spots[k]; if(k!=='bus'&&near===k){enterBuilding(k);return;} autoEnter=(k==='bus')?null:k; target=new THREE.Vector3(s.x,0,s.z);});});
function collide(x,z){for(var i=0;i290&&fs>16){fs-=2; g.font='700 '+fs+'px Rubik,Arial,sans-serif';} g.textAlign='center'; g.textBaseline='middle'; g.fillText(text,160,43); var t=new THREE.CanvasTexture(c); var sp=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false,transparent:true})); sp.scale.set(3.1,.82,1); sp.renderOrder=10; return sp;}
function item(word,sentence,x,z,ly,build,root){
var g=new THREE.Group(); g.position.set(x,0,z); (root||inScene).add(g); build(g);
var sp=lblSprite(word); sp.position.set(0,ly,0); g.add(sp); sp.visible=labelsOn; g.userData={word:word,sentence:sentence,label:sp}; inItems.push(g); return g;
}
function canvasMat(w,h,fn){var c=document.createElement('canvas'); c.width=w; c.height=h; fn(c.getContext('2d'),w,h); return new THREE.MeshBasicMaterial({map:new THREE.CanvasTexture(c)});}
function plane(w,h,m,x,y,z,g,ry){var p=new THREE.Mesh(new THREE.PlaneGeometry(w,h),m); p.position.set(x,y,z); if(ry)p.rotation.y=ry; g.add(p); return p;}
function clearRoom(){while(inScene.children.length>3){var o=inScene.children[inScene.children.length-1]; inScene.remove(o); freeTree(o);}}
function newRoom(w,d){inItems=[]; inSolids=[]; station=null; clearRoom();}
function room(key,floorTex,wallC,stripeC){
floorTex.repeat.set(floorTex.repeat.x,floorTex.repeat.y*44/24); var f=new THREE.Mesh(new THREE.PlaneGeometry(34,44),new THREE.MeshLambertMaterial({map:floorTex})); f.rotation.x=-Math.PI/2; f.position.z=10; inScene.add(f); inFloor=f;
bx(34,16,.5,wallC,0,8,-11.2,inScene); bx(.5,16,44,wallC,-17,8,10,inScene); bx(.5,16,44,wallC,17,8,10,inScene);
bx(34,.5,.6,stripeC,0,2.2,-10.9,inScene); bx(.6,.5,44,stripeC,-16.7,2.2,10,inScene); bx(.6,.5,44,stripeC,16.7,2.2,10,inScene);
bx(34,.4,.6,0xcfc9b8,0,.2,-10.9,inScene);
}
function solidIn(x,z,w,d){inSolids.push([x,z,w/2,d/2]);}
function buildHawkerIn(){
newRoom(); fans.length=fans.length;
room('hawker',tile('#b9644c','#c2705a',9,7),0xece5d0,0xb83a2c);
[-8,-2,4,10,16,22,28].forEach(function(z){bx(34,.35,.35,0xe07a2b,0,12.5,z,inScene);}); [-16,-8,0,8,16].forEach(function(x){bx(.3,.3,44,0xe07a2b,x,12.3,10,inScene);});
// kitchen counter line (front of stall) and floor mark
bx(34,1.15,1.4,0xc9cdd1,0,.58,2.2,inScene); bx(34,.12,1.7,0xe9edf0,0,1.2,2.2,inScene); solidIn(0,2.2,34,1.6);
for(var dx=-14;dx<=14;dx+=4){bx(3.6,.95,.06,0xb9bfc5,dx,.62,1.47,inScene); bx(.5,.1,.06,0x555a60,dx+1,.8,1.42,inScene);}
dCase(-11,1.32,2.2,5,1.3,true,[0xf4e3b5,0xc8402f,0xe9c28a,0x7ac143]); dCase(11,1.32,2.2,5,1.3,true,[0xb5651d,0xf2c14e,0xc8402f,0x7ac143]);
// order counter / start shift point
station={x:0,z:4.6,name:'the order counter'}; var ring=new THREE.Mesh(new THREE.RingGeometry(1.2,1.6,28),new THREE.MeshBasicMaterial({color:0xe3b53a,side:THREE.DoubleSide})); ring.rotation.x=-Math.PI/2; ring.position.set(0,.06,4.6); inScene.add(ring); station.ring=ring;
item('wok','A wok is a big round pan for stir-frying.',-11,-7,3.4,function(g){bx(2.4,1.1,2.4,0x9aa0a6,0,.55,0,g); var bowl=new THREE.Mesh(new THREE.SphereGeometry(1,16,10,0,Math.PI*2,Math.PI/2,Math.PI/2),new THREE.MeshLambertMaterial({color:0x2b2a26,side:THREE.DoubleSide})); bowl.position.y=1.9; bowl.scale.set(1,.7,1); g.add(bowl); var h=bx(1.6,.15,.25,0x6b4c05,1.8,2.0,0,g); h.rotation.z=.2; bx(.5,.35,.5,0xf08a2a,0,1.2,0,g);}); solidIn(-11,-7,2.6,2.6);
item('exhaust hood','An exhaust hood takes away smoke and steam.',-11,-7.2,7,function(g){bx(3.4,.9,3,0xb8bec4,0,5.4,0,g); bx(1,2.6,1,0xaab0b6,0,7.2,0,g);});
item('ladle','A ladle is a deep spoon for scooping soup.',-6,-10.4,3.6,function(g){bx(4,.14,.14,0x888888,0,3.2,0,g); [-1.4,0,1.4].forEach(function(x){cy(.07,.07,1.4,0xcfd3d6,x,2.4,0,g,6); var b=new THREE.Mesh(new THREE.SphereGeometry(.3,10,8),mat(0xcfd3d6)); b.position.set(x,1.6,0); g.add(b);});});
item('chopping board','A chopping board is where we cut food safely.',-3,-7.5,3.0,function(g){bx(3.6,1.15,2,0xc9cdd1,0,.58,0,g); bx(2.4,.2,1.4,0xd9a860,0,1.25,0,g); bx(1,.05,.5,0xcfd3d6,.3,1.4,.2,g); bx(.5,.1,.15,0x6b4c05,-.5,1.4,.2,g); [.9,-.3].forEach(function(x){bx(.4,.12,.3,0xe86b3a,x,1.4,-.3,g);});}); solidIn(-3,-7.5,3.6,2);
item('rice cooker','A rice cooker cooks rice for many people.',2,-7.5,3.7,function(g){bx(3.6,1.15,2,0xc9cdd1,0,.58,0,g); cy(.95,1,1.4,0xf2f2f2,0,1.85,0,g,16); cy(.9,.9,.2,0xd0d0d0,0,2.6,0,g,16); bx(.3,.2,.1,0xd03a3a,0,1.9,1.02,g);}); solidIn(2,-7.5,3.6,2);
item('bamboo steamer','A steamer cooks food with hot steam.',7,-7.5,4.1,function(g){bx(3.6,1.15,2,0xc9cdd1,0,.58,0,g); [0,.45,.9].forEach(function(y){cy(.95,.95,.4,0xc9a36a,0,1.5+y,0,g,16);}); cy(1,.95,.25,0xb08a50,0,2.95,0,g,16);}); solidIn(7,-7.5,3.6,2);
item('fridge','A fridge keeps food fresh and cold.',13,-8.5,5,function(g){bx(2.4,4.4,1.8,0xe8edf0,0,2.2,0,g); bx(.12,1.6,.15,0x8a8f94,1.0,3,.95,g); bx(.12,.8,.15,0x8a8f94,1.0,1.2,.95,g); bx(2.4,.06,1.8,0xcfd3d6,0,2.9,0,g);}); solidIn(13,-8.5,2.6,2);
item('sink','We wash our hands and tools at the sink.',14.5,-2,2.8,function(g){bx(2.6,1.2,2,0xc9cdd1,0,.6,0,g); bx(2,.1,1.4,0x6b7078,0,1.22,0,g); cy(.1,.1,.9,0xcfd3d6,0,1.7,-.7,g,8); bx(.1,.1,.6,0xcfd3d6,0,2.15,-.45,g);}); solidIn(14.5,-2,2.8,2.2);
item('apron','An apron keeps your clothes clean.',-15.5,-6,4,function(g){bx(.2,.2,.2,0x777777,0,3.9,0,g); bx(.1,2,1.3,0x2f6fb0,.1,2.7,0,g); bx(.1,.2,1.4,0x1f4f88,.1,3.6,0,g);});
item('menu board','A menu lists the food and the prices.',-7,-10.8,6.2,function(g){plane(5,3,canvasMat(512,300,function(c,w,h){c.fillStyle='#2b2a26'; c.fillRect(0,0,w,h); c.fillStyle='#ffe28a'; c.font='700 50px Rubik,Arial'; c.textAlign='center'; c.fillText('MENU',w/2,60); c.font='500 36px Rubik,Arial'; c.fillStyle='#fff'; c.textAlign='left'; c.fillText('Chicken rice',30,125); c.fillText('Fish ball noodles',30,185); c.fillText('Iced barley',30,245); c.textAlign='right'; c.fillText('4.00',w-30,125); c.fillText('4.50',w-30,185); c.fillText('1.50',w-30,245);}),0,4.6,.2,g);});
item('hygiene grade','A hygiene grade shows how clean a stall is.',4,-10.8,5.4,function(g){bx(1.8,1.8,.15,0x1f7a4d,0,4,.1,g); plane(1.4,1.4,canvasMat(128,128,function(c,w,h){c.fillStyle='#1f7a4d'; c.fillRect(0,0,w,h); c.fillStyle='#fff'; c.font='700 110px Rubik,Arial'; c.textAlign='center'; c.textBaseline='middle'; c.fillText('A',w/2,h/2+6);}),0,4,.2,g);});
item('QR code','Customers scan a QR code to pay.',-4,2.2,3.6,function(g){bx(1.5,.1,.9,0x6b7078,0,1.3,0,g); var q=plane(1.2,1.2,canvasMat(64,64,function(c,w,h){c.fillStyle='#fff'; c.fillRect(0,0,w,h); c.fillStyle='#111'; var s=7; for(var y=0;y<9;y++)for(var x=0;x<9;x++){var on=((x*7+y*13+x*y)%3===0)||(x<3&&y<3)||(x>5&&y<3)||(x<3&&y>5); if(on)c.fillRect(2+x*s,2+y*s,s-1,s-1);}}),0,1.95,0,g); q.rotation.x=-.5;});
item('tray','A tray carries food and drinks.',5,2.2,3.2,function(g){bx(2.4,.12,1.5,0x2f6fb0,0,1.3,0,g); [-.6,.6].forEach(function(x){cy(.4,.25,.3,0xffffff,x,1.55,0,g,10);}); cy(.25,.25,.5,0xb5651d,.1,1.7,.4,g,8);});
item('chicken rice','Chicken rice is rice with tender chicken.',-1.4,2.2,3.2,function(g){cy(.8,.8,.1,0xffffff,0,1.3,0,g,16); var r=new THREE.Mesh(new THREE.SphereGeometry(.55,10,8,0,Math.PI*2,0,Math.PI/2),mat(0xfafafa)); r.position.set(-.2,1.35,0); g.add(r); [0,.2,.4].forEach(function(o){bx(.5,.1,.35,0xe9c28a,.35,1.4+o*.3,.1+o*.2,g);}); bx(.3,.06,.5,0x6fb06f,.4,1.37,-.5,g);});
item('tray return point','After eating, we return our tray and dishes here to keep the hawker centre clean.',13,6,3.9,function(g){bx(2.6,.15,1.5,0x9aa3ab,0,.2,0,g); bx(.15,3,.15,0x9aa3ab,-1.2,1.5,-.6,g); bx(.15,3,.15,0x9aa3ab,1.2,1.5,-.6,g); bx(.15,2.9,.15,0x9aa3ab,-1.2,1.45,.4,g); bx(.15,2.9,.15,0x9aa3ab,1.2,1.45,.4,g); bx(.12,.12,1.2,0x9aa3ab,-1.2,.5,-.1,g); bx(.12,.12,1.2,0x9aa3ab,1.2,.5,-.1,g); [-1.2,1.2].forEach(function(x){[-.6,.4].forEach(function(z){bx(.3,.08,.3,0x333,x,.04,z,g);});}); [1.0,1.9,2.8].forEach(function(y,i){bx(2.5,.1,1.3,0xc9cdd1,0,y,-.1,g); bx(2.1,.12,1.0,i===1?0x2f6fb0:0xe0572f,0,y+.1,-.1,g); cy(.3,.2,.2,0xffffff,-.5,y+.22,-.1,g,10);}); bx(2.7,.9,.12,0x1f7a4d,0,3.6,-.6,g); plane(2.5,.8,canvasMat(256,80,function(c,w,h){c.fillStyle='#1f7a4d'; c.fillRect(0,0,w,h); c.fillStyle='#fff'; c.font='bold 30px sans-serif'; c.textAlign='center'; c.fillText('RETURN TRAY',w/2,52);}),0,3.6,-.52,g);}); solidIn(13,6,2.8,1.8);
// ceiling fan
(function(){var f=new THREE.Group(); f.position.set(0,6.4,-1); inScene.add(f); cy(.08,.08,1,0x555555,0,.5,0,f,6); var bl=new THREE.Group(); f.add(bl); fans.push(bl); for(var b=0;b<4;b++){var bd=bx(2.6,.06,.4,0xeeeeee,0,0,0,bl); bd.rotation.y=b*Math.PI/2; bd.position.x=Math.cos(b*Math.PI/2)*1.3; bd.position.z=-Math.sin(b*Math.PI/2)*1.3;}
var gi=new THREE.Group(); gi.position.set(0,0,-1); inScene.add(gi); var sp=lblSprite('ceiling fan'); sp.position.set(0,7.6,-1); inScene.add(sp); sp.visible=labelsOn; var hit=new THREE.Mesh(new THREE.BoxGeometry(3,1.5,3),new THREE.MeshBasicMaterial({visible:false})); hit.position.set(0,6.4,-1); inScene.add(hit); hit.userData={word:'ceiling fan',sentence:'A ceiling fan keeps the hawker centre cool.',label:sp}; inItems.push(hit);})();
// customer side: tables, stools, diners
function cyl(a,b,h,c,x,y,z,g,s){return cy(a,b,h,c,x,y,z,g,s);}
item('stool','A stool is a small seat with no back.',-5.5,7,1.9,function(g){cy(.5,.5,.18,0x2f6fb0,0,1.05,0,g,12); cy(.09,.09,1,0x777777,0,.5,0,g,6);});
item('stall owner','The stall owner cooks and sells the food.',-1,-3,2.5,function(g){var f=mkAura({noPack:1,noAura:1,plain:1,pal:{out:'#F2F2F2',outS:'#D5D5D5',outL:'#FFFFFF',acc:'#FFFFFF',accS:'#E4E4E4',accL:'#FFFFFF'}}); f.scale.setScalar(.574); g.add(f);});
// eating area: tables with four stools each
function table4(x,z,i,asItem){
var cols=[0x2f6fb0,0xc8402f,0x2f7d4f,0xd9892b], dc=[0x2f7d4f,0xd9892b,0x7b56c4,0xe0707f,0x3a8fb7];
var seats=[[1.7,0],[-1.7,0],[0,1.7],[0,-1.7]], tops=[0xe8892b,0x2f8f5a,0xd9703a,0x2f8f8f], st=[0xf0a830,0xe8503a,0xf2c14e,0xe8892b];
var build=function(g){cy(1.35,1.35,.14,tops[i%4],0,1.5,0,g,20); cy(.14,.14,1.5,0x2b2a26,0,.75,0,g,6); cy(.5,.5,.1,0xffffff,.35,1.62,.25,g,12); if(i%2)cy(.3,.3,.4,0xb5651d,-.4,1.8,-.3,g,8); seats.forEach(function(q,j){cy(.48,.48,.16,st[(i+j)%4],q[0],1.0,q[1],g,12); cy(.08,.08,1,0x2b2a26,q[0],.5,q[1],g,6);});};
if(asItem)item('table','A table is where customers eat their food.',x,z,3.6,build); else{var g=new THREE.Group(); g.position.set(x,0,z); inScene.add(g); build(g);}
seats.forEach(function(q,j){if((i+j)%3!==0)person(x+q[0],1.02,z+q[1],dc[(i+j)%5],inScene,true,[x,z]);});
solidIn(x,z,4.6,4.6);
}
[[-10,12],[-4,12],[4,12],[10,12],[-10,19],[-4,19],[4,19],[10,19],[-10,26],[-4,26],[4,26],[10,26]].forEach(function(t,i){table4(t[0],t[1],i,i===0);});
var FOODS={'Chicken Rice':[['#f4e3b5','#c8402f','#7ac143'],['#e9c9a0','#b5651d','#fff'],['#f4e3b5','#7ac143','#c8402f']],'Laksa':[['#e8892b','#c8402f','#f4e3b5'],['#d9892b','#fff','#7ac143'],['#e8892b','#c8402f','#fff']],'Satay':[['#8a5a2b','#6b3a1c','#f4e3b5'],['#a0662f','#6b3a1c','#d9372c'],['#8a5a2b','#e3b53a','#fff']],'Drinks':[['#3ab0e0','#fff','#e3b53a'],['#d9372c','#fff','#7ac143'],['#8a5a2b','#f4e3b5','#fff']],'Fresh Fruit':[['#e8503a','#7ac143','#f2c14e'],['#f2c14e','#e8503a','#7ac143'],['#7ac143','#f2c14e','#e8503a']],'Desserts':[['#f7c6d4','#cfe8f0','#f7e3a8'],['#cfe8f0','#f7c6d4','#fff'],['#f7e3a8','#7ac143','#f7c6d4']]};
// many stalls along both side walls, each with its own sign
function stall(side,z,name,hex,css,plates){
var x=side*14.6, ry=side<0?Math.PI/2:-Math.PI/2;
bx(1.2,4.4,6.6,0xe4dfcf,side*16.2,2.2,z,inScene); [3.6,2.6].forEach(function(y){bx(.6,.12,6,0x8a5a2b,side*15.7,y,z,inScene);});
[-2.4,-1.2,0,1.2,2.4].forEach(function(o,q){cy(.25,.25,.7,[0xd9372c,0xe3b53a,0x2f7d4f,0x2f6fb0,0xd9892b][(q+(side>0?2:0))%5],side*15.7,3.97,z+o,inScene,8);});
bx(1.7,1.2,6.2,0xc9cdd1,x,.6,z,inScene); bx(2,.12,6.4,0xf0f2f4,x,1.26,z,inScene);
dFood(name,'#01-'+(10+Math.round(z)),side*16.55,6.2,z,6.4,3.4,ry,css,FOODS[name]);
bx(.2,.4,6.6,0xffe9a8,side*16.5,4.55,z,inScene);
[-2.1,0,2.1].forEach(function(o){bx(.06,.95,1.9,0xb9bfc5,side*13.74,.62,z+o,inScene); bx(.06,.1,.5,0x555a60,side*13.68,.8,z+o+.6,inScene);}); dCase(x,1.32,z-.1,4.2,1.5,false,plates,[].concat.apply([],FOODS[name]).map(function(c){return parseInt(c.slice(1),16);}));
plates.forEach(function(c,q){cy(.42,.34,.2,c,x,1.42,z-2+q*1.3,inScene,12);});
cy(.45,.45,.6,0xb8bec4,x,1.65,z+2.4,inScene,12); bx(1.4,3.4,1.6,0xdfe6ea,side*15.4,1.7,z-3.1+0,inScene); bx(.1,3,1.3,0x9fd0e8,side*14.65,1.7,z-3.1,inScene); [0xd9372c,0x2f7d4f,0xe3b53a,0x3a8fb7].forEach(function(c,q){bx(.12,.3,1.1,c,side*14.58,.8+q*.7,z-3.1,inScene);});
var f=mkAura({noPack:1,noAura:1,plain:1,pal:{out:'#F2F2F2',outS:'#D5D5D5',outL:'#FFFFFF',acc:'#FFFFFF',accS:'#E4E4E4',accL:'#FFFFFF'}}); f.scale.setScalar(.574); f.position.set(side*15.7,0,z+.4); f.rotation.y=ry; inScene.add(f);
solidIn(side*14.6,z,2.4,6.4);
}
dFood('Mixed Rice','#01-01',-12.5,7.2,-10.8,7,3.4,0,'#2f7d4f',[['#fff','#7ac143','#c8402f'],['#fff','#e3b53a','#8a5a2b'],['#fff','#c8402f','#7ac143']]);
dFood('Noodles','#01-02',12.5,7.2,-10.8,7,3.4,0,'#b83a2c',[['#f4e3b5','#c8402f','#7ac143'],['#e9c9a0','#8a5a2b','#fff'],['#f4e3b5','#e3b53a','#c8402f']]);
stall(-1,11,'Chicken Rice','#b83a2c','#b83a2c',[0xffffff,0xf4e3b5,0xffffff,0xf4e3b5]);
stall(-1,19,'Laksa','#d9892b','#d9892b',[0xc8402f,0xc8402f,0xe9c28a,0xc8402f]);
stall(-1,27,'Satay','#7a4a2a','#7a4a2a',[0x6b3a1c,0x8a5a2b,0x6b3a1c,0x8a5a2b]);
stall(1,11,'Drinks','#2f6fb0','#2f6fb0',[0x3ab0e0,0xe3b53a,0xd9372c,0x3ab0e0]);
stall(1,19,'Fresh Fruit','#2f7d4f','#2f7d4f',[0xe8503a,0xf2c14e,0x7ac143,0xe8503a]);
stall(1,27,'Desserts','#c4607a','#c4607a',[0xf7c6d4,0xcfe8f0,0xf7e3a8,0xf7c6d4]);
// look: windows, lanterns, signs, plants, floor line, shelf
dWin(-1,-4,5.2); dWin(-1,5,5.2); dWin(1,-4,5.2); dWin(1,5,5.2);
dSign('HAWKER CENTRE',0,7.4,-10.9,9,2.1,'#7a1f17','#ffe28a',0,'Fresh food every day');
[-12,-6,0,6,12].forEach(function(x){dLantern(x,6.2,2);});
bx(34,.02,.28,0xf6c744,0,.03,4.3,inScene);
dPlant(-15,-9,1.1); dPlant(15,2,1);
bx(5,.2,.8,0x8a5a2b,-14,4.6,-10.6,inScene); [-1.8,-.9,0,.9,1.8].forEach(function(o,i){cy(.28,.28,.9,[0xd9372c,0x2f7d4f,0xe3b53a,0x2f6fb0,0xd9372c][i],-14+o,5.2,-10.6,inScene,8);});
dSign('Return trays',-16.4,6.4,.5,2.2,1.1,'#1f7a4d','#ffffff',Math.PI/2);
built.hawker=true;
}
function buildHospitalIn(){
newRoom();
var ft=ctex(128,128,function(g,w,h){g.fillStyle='#cfdde3'; g.fillRect(0,0,w,h); g.strokeStyle='#aebfc7'; g.lineWidth=2; g.strokeRect(0,0,w,h);},8,6);
room('hospital',ft,0xf4f7fa,0x2f5f9e);
station={x:0,z:-1,name:'the nurses\' station'}; var ring=new THREE.Mesh(new THREE.RingGeometry(1.4,1.8,28),new THREE.MeshBasicMaterial({color:0xe3b53a,side:THREE.DoubleSide})); ring.rotation.x=-Math.PI/2; ring.position.set(0,.06,1.6); inScene.add(ring); station.ring=ring; station.z=1.6;
item('nurses\' station','The nurses\' station is where nurses plan their work.',0,-3,4.6,function(g){bx(8,1.5,2,0xe9edf2,0,.75,0,g); bx(8.4,.2,2.3,0x2f5f9e,0,1.6,0,g); bx(1.6,1,.1,0x2b2a26,2,2.3,-.3,g); bx(.2,.5,.2,0x555,2,1.85,-.3,g); bx(.9,.06,.35,0xcfd3d6,2,1.75,.3,g);}); solidIn(0,-3,8.4,2.4);
item('stethoscope','A stethoscope lets a nurse listen to your heart.',-2.5,-3,3.2,function(g){cy(.1,.1,.9,0x2b2a26,0,1.95,0,g,6); var t=new THREE.Mesh(new THREE.TorusGeometry(.35,.05,6,14),mat(0x2b2a26)); t.position.y=1.5; g.add(t); cy(.14,.14,.08,0xcfd3d6,.35,1.35,0,g,10);});
item('thermometer','A thermometer measures body temperature.',-1,-3,3,function(g){bx(.9,.08,.1,0xf0f0f0,0,1.7,0,g); bx(.3,.1,.12,0xd03a3a,.35,1.7,0,g);});
item('face mask','A face mask covers your nose and mouth.',1.2,-3,3,function(g){bx(.9,.5,.9,0x7fc4e8,0,1.85,0,g); bx(.5,.15,.02,0xffffff,0,1.85,.46,g);});
item('reception counter','At the reception counter, patients say who they are.',-9,5.5,3.6,function(g){bx(5,1.4,1.6,0xe9edf2,0,.7,0,g); bx(5.3,.15,1.9,0x2f5f9e,0,1.5,0,g); bx(1,.8,.1,0x2b2a26,-1,2,-.4,g);}); solidIn(-9,5.5,5.4,1.9);
item('queue number','The queue number shows whose turn it is.',-16.2,6.5,6.2,function(g){plane(3.6,1.6,canvasMat(256,112,function(c,w,h){c.fillStyle='#111'; c.fillRect(0,0,w,h); c.fillStyle='#ffcf3a'; c.font='700 30px Rubik,Arial'; c.textAlign='center'; c.fillText('NOW SERVING',w/2,38); c.font='700 54px Rubik,Arial'; c.fillText('A 025',w/2,96);}),0,4.7,.2,g,Math.PI/2);});
item('waiting chair','Patients sit on waiting chairs until their turn.',-14,9,3,function(g){[-1.2,0,1.2].forEach(function(z){bx(1,.2,1,0x2f5f9e,0,.9,z,g); bx(.15,1,1,0x2f5f9e,-.45,1.5,z,g); bx(.1,.9,.1,0x666,.3,.45,z+.3,g); bx(.1,.9,.1,0x666,.3,.45,z-.3,g);});});
item('hand sanitiser','Hand sanitiser cleans germs from your hands.',-5,7,3.4,function(g){cy(.09,.09,2,0x888888,0,1,0,g,6); bx(.55,.8,.4,0xf2f2f2,0,2.2,0,g); bx(.5,.3,.1,0x3f8fd0,0,2.1,.25,g);});
item('wheelchair','A wheelchair helps people who cannot walk far.',-14,-1,3.6,function(g){bx(1.2,.18,1.3,0x2f5f9e,0,1.3,0,g); bx(1,.08,1.1,0x7fa6d6,0,1.42,0,g); bx(.16,1.4,1.3,0x2f5f9e,-.6,2,0,g); [-.72,.72].forEach(function(z){bx(1.3,.1,.1,0x8a9099,0,1.05,z,g); bx(.1,.1,.1,0x8a9099,0,1.4,z,g); bx(.7,.1,.1,0x333333,-.85,2.75,z*.8,g); var w=cy(.85,.85,.1,0x2b2d31,-.15,.9,z*1.12,g,20); w.rotation.x=Math.PI/2; var h=cy(.22,.22,.14,0xbfc5cc,-.15,.9,z*1.12,g,10); h.rotation.x=Math.PI/2; var r=cy(.7,.7,.05,0xd5d9de,-.15,.9,z*1.18,g,20); r.rotation.x=Math.PI/2;}); [-.55,.55].forEach(function(z){var w=cy(.27,.27,.1,0x2b2d31,.7,.28,z,g,10); w.rotation.x=Math.PI/2; bx(.08,.9,.08,0x8a9099,.7,.8,z*.8,g);}); bx(.5,.08,1,0x8a9099,.85,.4,0,g);});
item('hospital bed','A patient rests in a hospital bed.',8,-6,3.4,function(g){bx(2.6,.6,5,0xcfd3d6,0,.7,0,g); bx(2.4,.4,3.4,0x7fc4e8,0,1.1,.5,g); bx(2.2,.35,1,0xffffff,0,1.2,-1.8,g); bx(2.6,1.2,.15,0x9aa4ad,0,1.4,-2.55,g); [[-1.2,-2.3],[1.2,-2.3],[-1.2,2.3],[1.2,2.3]].forEach(function(p){cy(.1,.1,.5,0x555,p[0],.25,p[1],g,6);});}); solidIn(8,-6,3,5.4);
item('drip stand','A drip stand holds a bag of fluid for a patient.',11,-5,5.2,function(g){cy(.06,.06,4,0xcfd3d6,0,2,0,g,6); bx(1.4,.08,.08,0xcfd3d6,0,4,0,g); bx(.5,.8,.2,0xe8f6ff,0,3.5,0,g); cy(.5,.5,.1,0x777,0,.08,0,g,10);});
item('heart monitor','A heart monitor shows how fast the heart is beating.',5.5,-9,4.4,function(g){bx(1.8,1.3,.3,0x2b2a26,0,3,0,g); plane(1.5,1,canvasMat(128,80,function(c,w,h){c.fillStyle='#04180c'; c.fillRect(0,0,w,h); c.strokeStyle='#3df07a'; c.lineWidth=3; c.beginPath(); c.moveTo(0,50); c.lineTo(30,50); c.lineTo(38,20); c.lineTo(48,70); c.lineTo(58,50); c.lineTo(128,50); c.stroke();}),0,3,.2,g); cy(.07,.07,2,0x555,0,1.8,-.1,g,6);});
item('privacy curtain','A curtain gives a patient some privacy.',8,-9,5.4,function(g){bx(7,.1,.1,0x888,0,5,0,g); for(var i=0;i<7;i++)bx(.95,3.6,.08,i%2?0xb7dff5:0xa0d0ea,-3+i,3.1,0,g);});
item('medicine trolley','A medicine trolley carries medicine around the ward.',-6,-7,3.6,function(g){bx(2.4,1.8,1.2,0xe9edf2,0,1.4,0,g); bx(2.4,.1,1.2,0x2f5f9e,0,2.35,0,g); [0,.5].forEach(function(y){bx(2.2,.35,.05,0xcfd3d6,0,1.0+y,.62,g);}); cy(.12,.12,.2,0x333,-1,.12,.4,g,8); cy(.12,.12,.2,0x333,1,.12,.4,g,8); [-.5,.2,.7].forEach(function(x){cy(.12,.12,.35,0xd9892b,x,2.55,0,g,8);});}); solidIn(-6,-7,2.6,1.4);
item('first aid kit','A first aid kit has bandages for small injuries.',-14.5,-8,3.6,function(g){bx(1.8,1.4,.5,0xffffff,0,3,0,g); bx(.9,.3,.55,0xd03a3a,0,3,0,g); bx(.3,.9,.55,0xd03a3a,0,3,0,g);});
item('X-ray','An X-ray picture shows the bones inside the body.',13,-10.7,5.2,function(g){bx(2.6,2,.2,0xdfe8f0,0,3.6,0,g); plane(2.3,1.7,canvasMat(128,96,function(c,w,h){c.fillStyle='#071a2a'; c.fillRect(0,0,w,h); c.strokeStyle='#cfe8ff'; c.lineWidth=4; for(var i=0;i<5;i++){c.beginPath(); c.arc(w/2,h/2+10,22+i*7,Math.PI*1.05,Math.PI*1.95); c.stroke();} c.beginPath(); c.moveTo(w/2,12); c.lineTo(w/2,h-6); c.stroke();}),0,3.6,.15,g);});
item('crutches','Crutches help a person walk with an injured leg.',14.5,2,3.4,function(g){[0,.8].forEach(function(x){var c=cy(.06,.06,3.2,0x888,x,1.6,0,g,6); c.rotation.z=.12; bx(.5,.1,.1,0x6b4c05,x+.1,3.1,0,g);});});
item('nurse','A nurse takes care of patients.',2.5,-6,2.5,function(g){var f=mkAura({noPack:1,noAura:1,plain:1,pal:{out:'#7FC4E8',outS:'#5AA5CC',outL:'#A6D8F0',acc:'#FFFFFF',accS:'#E4E4E4',accL:'#FFFFFF'}}); f.scale.setScalar(.574); g.add(f);});
// look: windows, signs, guide lines, plants, rug, water cooler
dWin(-1,-8,5.2); dWin(-1,0,5.2); dWin(-1,15,5.2); dWin(1,-6,5.2); dWin(1,5,5.2); dWin(1,16,5.2);
dSign('HOSPITAL',0,7.4,-10.9,9,2.1,'#ffffff','#2f5f9e',0,'Caring for you');
bx(1.2,.3,.1,0xd03a3a,-6,7.4,-10.85,inScene); bx(.3,1.2,.1,0xd03a3a,-6,7.4,-10.85,inScene);
dSign('Registration',-16.55,6.2,-4.1,2.7,1.3,'#2f5f9e','#ffffff',Math.PI/2);
dSign('Wards',16.55,6.2,-0.6,2.6,1.3,'#2f5f9e','#ffffff',-Math.PI/2);
bx(.7,.03,24,0x3f8fd0,-3,.03,10,inScene); bx(.7,.03,24,0x3fb26b,-1.8,.03,10,inScene);
dRug(-12,8.5,9,7,0xc3d8e4,0xe1eef5);
bx(1.6,.9,1.6,0xd8e2e8,-12,.45,8.5,inScene); bx(1.2,.06,1,0xf2c14e,-12,.95,8.5,inScene);
dPlant(15,10,1.2); dPlant(15,-3,1.1); dPlant(-15,17,1.1);
bx(1,3,1,0xf0f4f7,-1,1.5,10.5,inScene); bx(.8,.8,.8,0x8fd0ee,-1,3.4,10.5,inScene); bx(.25,.2,.1,0x3f8fd0,-1,2.4,11.05,inScene);
bx(2.6,1.4,.2,0x2b2a26,13,5,-10.8,inScene).visible=false;
built.hospital=true;
}
/* ---------- interior decoration helpers (look only, not tappable) ---------- */
function dWin(side,z,y){var x=side*16.6; bx(.3,3.6,5.2,0xf4f1e8,x,y,z,inScene); var gl=bx(.2,3,4.6,0xcfeaf9,x-side*.12,y,z,inScene); gl.material=new THREE.MeshBasicMaterial({color:0xcfeaf9}); bx(.12,.12,4.6,0xf4f1e8,x-side*.2,y,z,inScene); bx(.12,3,.12,0xf4f1e8,x-side*.2,y,z,inScene); bx(.5,.22,5.6,0xdad5c6,x-side*.2,y-1.9,z,inScene);}
function dPlant(x,z,sc){sc=sc||1; cy(.7*sc,.55*sc,1*sc,0xb9683f,x,.5*sc,z,inScene,10); [[0,1.5,0,.85],[.45,1.2,.2,.6],[-.45,1.25,-.15,.62],[.1,1.9,.1,.5]].forEach(function(q){var m=new THREE.Mesh(new THREE.SphereGeometry(q[3]*sc,8,6),mat(0x3f9a54)); m.position.set(x+q[0]*sc,q[1]*sc,z+q[2]*sc); inScene.add(m);});}
function dSign(text,x,y,z,w,h,bg,fg,ry,sub){plane(w,h,canvasMat(512,Math.round(512*h/w),function(c,cw,ch){c.fillStyle=bg; c.fillRect(0,0,cw,ch); c.strokeStyle=fg; c.lineWidth=8; c.strokeRect(10,10,cw-20,ch-20); c.fillStyle=fg; c.textAlign='center'; c.textBaseline='middle'; var fs=Math.round(ch*(sub?.34:.42)); c.font='700 '+fs+'px Rubik,Arial'; while(fs>14&&c.measureText(text).width>cw-70){fs-=2; c.font='700 '+fs+'px Rubik,Arial';} c.fillText(text,cw/2,sub?ch*.4:ch/2); if(sub){var f2=Math.round(ch*.2); c.font='500 '+f2+'px Rubik,Arial'; while(f2>10&&c.measureText(sub).width>cw-70){f2-=2; c.font='500 '+f2+'px Rubik,Arial';} c.fillText(sub,cw/2,ch*.76);}}),x,y,z,inScene,ry||0);}
function dFood(name,no,x,y,z,w,h,ry,col,foods){plane(w,h,canvasMat(768,Math.round(768*h/w),function(c,cw,ch){c.fillStyle='#fffdf5'; c.fillRect(0,0,cw,ch); c.fillStyle=col; c.fillRect(8,8,cw-16,ch*.34); c.fillStyle='#fff'; c.textAlign='center'; c.textBaseline='middle'; var fs=Math.round(ch*.24); c.font='800 '+fs+'px Rubik,Arial'; while(fs>16&&c.measureText(name).width>cw-150){fs-=2; c.font='800 '+fs+'px Rubik,Arial';} c.fillText(name,cw/2,8+ch*.17); c.font='600 '+Math.round(ch*.09)+'px Rubik,Arial'; c.textAlign='left'; c.fillText(no,24,ch*.5+10); var n=foods.length,r=Math.min((cw-40)/n/2.1,ch*.26); foods.forEach(function(f,i){var cx=20+(cw-40)*(i+.5)/n,cy2=ch*.7; c.fillStyle='#e9e6dd'; c.beginPath(); c.arc(cx,cy2,r,0,7); c.fill(); c.fillStyle='#fff'; c.beginPath(); c.arc(cx,cy2,r*.86,0,7); c.fill(); f.forEach(function(d,k){c.fillStyle=d; c.beginPath(); c.arc(cx+(k%2?1:-1)*r*.28*(k>0),cy2+(k>1?1:-1)*r*.22*(k>0),r*(k?0.28:.5),0,7); c.fill();});}); c.strokeStyle=col; c.lineWidth=8; c.strokeRect(4,4,cw-8,ch-8);}),x,y,z,inScene,ry||0);}
function dCase(x,y0,z,len,dep,alongX,cols,food){food=food||cols; var nf=food.length;var st=0xcfd4d9,n=cols.length; var w=alongX?len:dep, d=alongX?dep:len; [[-1,-1],[1,-1],[-1,1],[1,1]].forEach(function(q){bx(.12,2.3,.12,st,x+q[0]*w/2,y0+1.15,z+q[1]*d/2,inScene);}); [0,1.1,2.2].forEach(function(dy,k){bx(w,.1,d,st,x,y0+dy+.05,z,inScene); if(k<2){for(var q=0;q<5;q++){var t=(q-2)*(len/5.4); var c=cols[(q+k)%n]; var px=alongX?x+t:x, pz=alongX?z:z+t; cy(.44,.38,.16,0xeeeeee,px,y0+dy+.2,pz,inScene,10); for(var m=0;m<4;m++){var fc=food[(q+k+m)%nf],a=m*1.57+q,rr=m?.2:0,sp=new THREE.Mesh(new THREE.SphereGeometry(m?.12:.2,8,6),mat(fc)); sp.scale.y=.7; sp.position.set(px+Math.cos(a)*rr,y0+dy+.34+(m?0:.04),pz+Math.sin(a)*rr); inScene.add(sp);}}}}); bx(w,.08,.08,st,x,y0+2.25,z+(alongX?d/2:0),inScene); var hd=alongX?bx(w+.2,.12,d+.2,0xe9ecef,x,y0+2.35,z,inScene):bx(w+.2,.12,d+.2,0xe9ecef,x,y0+2.35,z,inScene);}
function dBag(x,y,z,len,rz,ry){var g=new THREE.Group(); g.position.set(x,y,z); g.rotation.set(0,ry||0,rz||0); var b=new THREE.Mesh(new THREE.SphereGeometry(1,12,8),mat(0xd9a05a)); b.scale.set(.2,len/2,.2); g.add(b); var t=new THREE.Mesh(new THREE.SphereGeometry(1,10,6),mat(0xc98a3f)); t.scale.set(.15,len/2*.9,.12); t.position.set(0,0,.11); g.add(t); for(var i=-2;i<=2;i++){var c=bx(.12,.34,.05,0xf3e0b0,0,i*len*.17,.19,g); c.rotation.z=.9;} inScene.add(g); return g;}
function dRug(x,z,w,d,c1,c2){bx(w,.03,d,c1,x,.03,z,inScene); bx(w-.8,.04,d-.8,c2,x,.05,z,inScene);}
function dLantern(x,y,z){var l=new THREE.Mesh(new THREE.SphereGeometry(.6,10,8),mat(0xd9372c)); l.scale.set(1,1.25,1); l.position.set(x,y,z); inScene.add(l); cy(.3,.3,.15,0xe3b53a,x,y+.8,z,inScene,8); cy(.3,.3,.15,0xe3b53a,x,y-.8,z,inScene,8); cy(.03,.03,2,0x555555,x,y+1.8,z,inScene,4);}
// ---------- New interiors: polyclinic, bakery, school canteen, MRT station ----------
function npc(out,outS,outL,acc,hat){return function(g){var f=mkAura({noPack:1,noAura:1,plain:1,noHat:!hat,pal:{out:out,outS:outS,outL:outL,acc:acc,accS:acc,accL:acc}}); f.scale.setScalar(.574); g.add(f);};}
function ringAt(x,z){var ring=new THREE.Mesh(new THREE.RingGeometry(1.2,1.6,28),new THREE.MeshBasicMaterial({color:0xe3b53a,side:THREE.DoubleSide})); ring.rotation.x=-Math.PI/2; ring.position.set(x,.06,z); inScene.add(ring); return ring;}
function setStation(x,z,name){station={x:x,z:z,name:name}; station.ring=ringAt(x,z);}
function tile(c1,c2,rx,ry){return ctex(128,128,function(g,w,h){g.fillStyle=c1; g.fillRect(0,0,w,h); g.fillStyle=c2; g.fillRect(0,0,64,64); g.fillRect(64,64,64,64);},rx,ry);}
function buildClinicIn(){
newRoom();
room('clinic',ctex(128,128,function(g,w,h){g.fillStyle='#d6e6e3'; g.fillRect(0,0,w,h); g.strokeStyle='#b4cbc7'; g.lineWidth=2; g.strokeRect(0,0,w,h);},8,6),0xf3f8f7,0x2a8f86);
setStation(0,4.6,'the registration kiosk');
item('registration kiosk','At the registration kiosk, patients tell the clinic they have arrived.',-11,3,4.4,function(g){bx(1.4,3,1,0xe9edf2,0,1.5,0,g); bx(1.2,.9,.1,0x2b2a26,0,2.5,.52,g); bx(1.0,.6,.05,0x7fc4e8,0,2.5,.58,g);}); solidIn(-11,3,1.6,1.2);
item('queue ticket','A queue ticket has a number that tells you when it is your turn.',-7,3,3.6,function(g){bx(.7,2,.7,0x2a8f86,0,1,0,g); bx(.5,.35,.06,0xffffff,0,2.2,.36,g); bx(.35,.5,.05,0xfff6d6,.1,1.4,.38,g);});
item('waiting chair','Patients sit on waiting chairs until their turn.',-14,8,3,function(g){[-1.2,0,1.2].forEach(function(z){bx(1,.2,1,0x2a8f86,0,.9,z,g); bx(.15,1,1,0x2a8f86,-.45,1.5,z,g); bx(.1,.9,.1,0x666,.3,.45,z+.3,g); bx(.1,.9,.1,0x666,.3,.45,z-.3,g);});});
item('weighing scale','A weighing scale tells us how heavy we are.',-13,-7,2.6,function(g){bx(1.6,.2,1.6,0xcfd3d6,0,.1,0,g); bx(.4,2.4,.4,0xcfd3d6,0,1.4,-.5,g); bx(1,.5,.2,0x2b2a26,0,2.6,-.4,g);});
item('blood pressure monitor','A blood pressure monitor checks how hard the blood pushes in your arm.',-9.5,-7,4,function(g){bx(1.6,1.1,1,0xe9edf2,0,.55,0,g); bx(1.2,.8,.1,0x2b2a26,0,2.2,0,g); bx(1.0,.5,.05,0x3df07a,0,2.2,.08,g); cy(.2,.2,.9,0x2a6fb0,.9,1.1,.3,g,10);}); solidIn(-9.5,-7,1.8,1.4);
item('height chart','A height chart on the wall shows how tall you are.',-6,-10.7,5,function(g){bx(1.1,5,.1,0xffffff,0,3.2,0,g); for(var i=0;i<9;i++)bx(i%2?.5:.8,.08,.05,0x2a8f86,-.1,1.2+i*.5,.08,g);});
item('examination couch','A patient lies on the examination couch for a check-up.',-2,-7.5,3.2,function(g){bx(2,.9,4.4,0x2a8f86,0,.5,0,g); bx(1.8,.2,4.2,0xffffff,0,1.05,0,g); bx(1.8,.4,1,0xffffff,0,1.2,-1.6,g); [[-.8,-1.8],[.8,-1.8],[-.8,1.8],[.8,1.8]].forEach(function(p){cy(.1,.1,.4,0x555,p[0],.2,p[1],g,6);});}); solidIn(-2,-7.5,2.4,4.8);
item('consultation desk','The doctor talks to patients at the consultation desk.',4,-7,3.8,function(g){bx(4,1.4,1.6,0xd9bd92,0,.7,0,g); bx(4.1,.1,1.7,0xc2a070,0,1.45,0,g); bx(1.1,.8,.1,0x2b2a26,.8,2.0,-.3,g); bx(.2,.5,.2,0x555,.8,1.7,-.3,g); bx(.8,.08,.5,0xffffff,-.9,1.52,.2,g);}); solidIn(4,-7,4.2,1.8);
item('doctor','A doctor finds out why a patient is unwell and how to help.',7,-7.4,2.9,npc('#F2F2F2','#D5D5D5','#FFFFFF','#7FC4E8',false));
item('pharmacy counter','At the pharmacy counter, patients collect their medicine.',11,1,3.8,function(g){bx(8,1.5,1.5,0xe9edf2,0,.75,0,g); bx(8.3,.15,1.8,0x2f8f5b,0,1.55,0,g); bx(2.4,.7,.1,0x2f8f5b,0,3.2,-.6,g); bx(.5,.5,.12,0xffffff,0,3.2,-.52,g);}); solidIn(11,1,8.4,1.9);
item('pharmacist','A pharmacist checks medicine and explains how to take it safely.',5.6,-.4,2.9,npc('#F2F2F2','#D5D5D5','#FFFFFF','#2F8F5B',false));
item('medicine shelf','Medicines are kept neatly on a medicine shelf.',11,-5.2,5.2,function(g){bx(8,4.4,.9,0xe9edf2,0,2.2,0,g); [1.1,2.2,3.3].forEach(function(y,r){bx(7.8,.1,1,0xcfd3d6,0,y,0,g); for(var i=0;i<9;i++){cy(.2,.2,.7,[0xd9892b,0xffffff,0x3f8fd0][(i+r)%3],-3.4+i*.85,y+.42,.1,g,8);}}); bx(8,.15,1,0xcfd3d6,0,.2,0,g);}); solidIn(11,-5.2,8.2,1.2);
item('prescription','A prescription says which medicine the doctor wants you to take.',7.8,1,2.6,function(g){bx(.9,.04,1.2,0xffffff,0,1.65,0,g); bx(.6,.02,.08,0x888,0,1.68,-.3,g); bx(.6,.02,.08,0x888,0,1.68,0,g);});
item('medicine bottle','Only take medicine that a doctor or pharmacist gives you.',13.8,1,2.8,function(g){cy(.3,.3,.8,0xd9892b,0,1.95,0,g,10); cy(.32,.32,.2,0xffffff,0,2.45,0,g,10); bx(.45,.35,.05,0xffffff,0,2,.3,g);});
// decoration: windows, signs, posters, waiting area
dWin(-1,-4,5.2); dWin(-1,10,5.2); dWin(1,-4,5.2); dWin(1,10,5.2);
dSign('POLYCLINIC',0,7.6,-10.9,8,2.1,'#1f6f68','#ffffff',0,'Family health care');
bx(1.5,.5,.2,0xd9372c,-5.6,7.6,-10.75,inScene); bx(.5,1.5,.2,0xd9372c,-5.6,7.6,-10.75,inScene);
dSign('Consultation',-3,6.2,-10.9,3.4,1,'#2a8f86','#ffffff',0); dSign('Pharmacy',11,6.2,-10.9,3,1,'#2f8f5b','#ffffff',0);
dSign('Registration',-16.4,6.2,3,3.4,1,'#2a8f86','#ffffff',Math.PI/2);
[[3,'#2a8f86'],[-2,'#2f6fb0']].forEach(function(l,i){bx(.35,.03,16,parseInt(l[1].slice(1),16),i?-3.5:3.5,.04,0,inScene);});
dRug(-12,12,7,5.4,0x2a8f86,0xdcebe9);
bx(2.4,.9,1.4,0xdcebe9,-12,.45,12,inScene);
[[-6,12],[-6,15]].forEach(function(w){bx(1,.2,1.4,0x2a8f86,w[0],.9,w[1],inScene); bx(.15,1,1.4,0x2a8f86,w[0]-.5,1.5,w[1],inScene); bx(.1,.9,.1,0x666,w[0]+.3,.45,w[1],inScene);});
dSign('Wash your hands',-16.4,5.6,14,2.6,3.2,'#e8f3f1','#1f6f68',Math.PI/2,'20 seconds with soap');
dSign('Eat well',-16.4,5.6,18,2.6,3.2,'#fff3d6','#8a4a2a',Math.PI/2,'Fruit and vegetables');
dSign('Stay active',16.4,5.6,10,2.6,3.2,'#e1eef9','#2f6fb0',-Math.PI/2,'Move every day');
cy(.6,.6,2.2,0xeef2f5,14,1.1,12,inScene,12); cy(.5,.5,.8,0x7fc4e8,14,2.6,12,inScene,12);
dPlant(-15,-9,1.3); dPlant(15,-8,1.2); dPlant(-4,22,1.3); dPlant(15,20,1.3);
// more: queue screen, room doors, curtains, seating rows, patients, sanitiser, wheelchair, TV
bx(5,2.4,.2,0x15191c,-14,5.2,-10.7,inScene); dSign('NOW SERVING',-14,5.8,-10.55,4.6,.8,'#15191c','#7fe3a8',0); dSign('023',-14,4.7,-10.55,4.6,1,'#15191c','#ffd25a',0,'Counter 2');
[-6.8,1.2,8.8].forEach(function(x,i){bx(2.4,.2,.2,0x777,x,7.2,-10.7,inScene); dSign('Room '+(i+1),x,6.4,-10.9,2.2,.8,'#2a8f86','#ffffff',0);});
[-4.6,0.6].forEach(function(x){bx(.12,3.8,3.4,0xbfe3f2,x,2.2,-7.3,inScene); bx(.14,.1,3.6,0x777,x,4.15,-7.3,inScene);});
[[-14,11],[-14,14],[-14,17]].forEach(function(w,i){[-1,0,1].forEach(function(o,j){var z=w[1]+o*0; bx(1,.2,1,0x2a8f86,w[0]+j*1.3-1.3,.9,w[1],inScene); bx(1,1,.15,0x2a8f86,w[0]+j*1.3-1.3,1.5,w[1]+.5,inScene); bx(.1,.9,.1,0x666,w[0]+j*1.3-1.3,.45,w[1],inScene); if((i+j)%2===0)person(w[0]+j*1.3-1.3,.98,w[1],[0xd9892b,0x2f6fb0,0xe0707f,0x7b56c4][(i+j)%4],inScene,true,[w[0]+j*1.3-1.3,w[1]-5]);}); solidIn(w[0],w[1],4.2,1.4);});
[[13,10],[13,14],[13,18]].forEach(function(w,i){[-1,0,1].forEach(function(o,j){var x=w[0]+(j-1)*1.3; bx(1,.2,1,0x2f6fb0,x,.9,w[1],inScene); bx(1,1,.15,0x2f6fb0,x,1.5,w[1]+.5,inScene); bx(.1,.9,.1,0x666,x,.45,w[1],inScene); if((i+j)%3!==1)person(x,.98,w[1],[0xe0707f,0x2f7d4f,0xd9892b][(i+j)%3],inScene,true,[x,w[1]-5]);}); solidIn(w[0],w[1],4.2,1.4);});
[[-3.5,7],[3.5,7]].forEach(function(q){cy(.5,.5,.2,0x666,q[0],.1,q[1],inScene,10); cy(.09,.09,2.2,0xcfd3d6,q[0],1.2,q[1],inScene,6); cy(.3,.3,.6,0x7fc4e8,q[0],2.5,q[1],inScene,8); bx(.3,.2,.3,0xffffff,q[0],2.9,q[1],inScene);});
(function(){var g=new THREE.Group(); g.position.set(8,0,3.5); g.rotation.y=-.5; inScene.add(g); bx(1.1,.15,1,0x555,0,1.3,0,g); bx(1.1,1.1,.12,0x555,0,1.9,-.5,g); [-.62,.62].forEach(function(x){var w=cy(.7,.7,.1,0x333,x,.7,0,g,14); w.rotation.z=Math.PI/2;}); bx(.9,.1,.5,0x888,0,.55,.5,g); solidIn(8,3.5,1.6,1.6);})();
bx(.3,3,5,0x15191c,16.7,5.6,-4,inScene); dSign('Stay healthy',16.35,5.7,-4,4.6,2.6,'#e8f3f1','#1f6f68',-Math.PI/2,'Sleep well, drink water');
bx(2.6,.15,1.6,0xd9bd92,-12,.85,12,inScene); [[-.9,.5],[.9,.5],[-.9,-.5],[.9,-.5]].forEach(function(q){bx(.1,.8,.1,0x666,-12+q[0],.4,12+q[1],inScene);}); [0xd9372c,0x2f6fb0,0xe3b53a].forEach(function(c,i){bx(.8,.04,1,c,-12.8+i*.8,.97,12,inScene);});
dPlant(-4,-9,1.2); dPlant(15,3,1.2);
built.clinic=true;
}
function buildBakeryIn(){
newRoom();
room('bakery',ctex(128,128,function(g,w,h){g.fillStyle='#f3f3f0'; g.fillRect(0,0,w,h); g.fillStyle='#cfd1d4'; [[0,0],[64,64],[128,0],[0,128],[128,128]].forEach(function(c){g.beginPath(); g.moveTo(c[0],c[1]-26); g.lineTo(c[0]+26,c[1]); g.lineTo(c[0],c[1]+26); g.lineTo(c[0]-26,c[1]); g.fill();}); g.fillStyle='#e2e3e5'; g.beginPath(); g.arc(64,0,14,0,7); g.arc(0,64,14,0,7); g.arc(128,64,14,0,7); g.arc(64,128,14,0,7); g.fill(); g.strokeStyle='#d7d9db'; g.lineWidth=2; g.strokeRect(0,0,w,h);},10,7),0xf6ecd6,0xc07a54);
setStation(0,5,'the bakery counter');
// kitchen table
bx(5.6,1.3,2.2,0xc9cdd1,-4.2,.65,-7,inScene); bx(5.7,.12,2.3,0xe9edf0,-4.2,1.32,-7,inScene); solidIn(-4.2,-7,5.8,2.4);
item('oven','A baker uses an oven to bake bread and cakes.',-13,-8,5.8,function(g){bx(4,3.6,2.4,0x9aa0a6,0,1.8,0,g); bx(3,1.8,.1,0x2b2a26,0,1.9,1.22,g); bx(2.6,1.4,.08,0xffb347,0,1.9,1.27,g); bx(3.2,.2,.2,0x555,0,3.2,1.25,g); [-1.2,0,1.2].forEach(function(x){cy(.2,.2,.2,0xd03a3a,x,3.7,1.1,g,8).rotation.x=Math.PI/2;});}); solidIn(-13,-8,4.2,2.6);
item('baking tray','Dough is baked on a baking tray.',-8.4,-7,3.2,function(g){bx(2,.08,1.4,0x555,0,1.42,0,g); for(var i=0;i<6;i++){var b=new THREE.Mesh(new THREE.SphereGeometry(.28,10,8),mat(0xd9a05a)); b.scale.set(1,.7,1); b.position.set(-.65+(i%3)*.65,1.6,-.3+Math.floor(i/3)*.6); g.add(b);}});
item('rolling pin','A rolling pin flattens the dough.',-5.4,-7,3.2,function(g){cy(.14,.14,1.8,0xd9a860,0,1.55,0,g,10).rotation.z=Math.PI/2; bx(.4,.12,.12,0x6b4c05,-1.1,1.55,0,g); bx(.4,.12,.12,0x6b4c05,1.1,1.55,0,g); bx(1.4,.04,1,0xffffff,0,1.4,0,g);});
item('mixing bowl','Flour, eggs and sugar are mixed in a mixing bowl.',-2.6,-7,3,function(g){var b=new THREE.Mesh(new THREE.SphereGeometry(.7,14,8,0,Math.PI*2,Math.PI/2,Math.PI/2),new THREE.MeshLambertMaterial({color:0xcfd3d6,side:THREE.DoubleSide})); b.position.y=1.9; g.add(b); cy(.55,.55,.05,0xf6efe0,0,1.9,0,g,14); bx(.12,.9,.12,0x6b4c05,.2,2.1,0,g).rotation.z=.4;});
item('flour sack','Flour comes in a big sack.',-15,-3.5,3,function(g){bx(1.8,2.2,1.2,0xe8d9b0,0,1.1,0,g); bx(1.2,.5,.05,0xc07a54,0,1.4,.63,g);}); solidIn(-15,-3.5,2,1.5);
item('baker','A baker bakes fresh bread, buns and cakes.',-8.4,-4.4,2.9,npc('#FFFFFF','#E4E4E4','#FFFFFF','#FFFFFF',true));
item('bread rack','Warm loaves sit on the bread rack.',9,-8.5,5,function(g){bx(5,4.4,1.1,0xb07a45,0,2.2,0,g); [.9,1.9,2.9].forEach(function(y,r){bx(4.8,.1,1.2,0x8a5a2b,0,y,0,g); for(var i=0;i<4;i++){var l=new THREE.Mesh(new THREE.SphereGeometry(.45,10,8),mat(r%2?0xe3a24c:0xc88a3f)); l.scale.set(1.3,.7,.8); l.position.set(-1.7+i*1.15,y+.35,.1); g.add(l);}});}); solidIn(9,-8.5,5.2,1.5);
// customer side
bx(8.5,1.5,1.6,0xa8743f,2.5,.75,2.2,inScene); [0,1,2,3].forEach(function(q){bx(.06,1.3,.04,0x6b4524,-1.1+q*2.4,.75,3.02,inScene);}); bx(8.2,.08,.04,0x2b2a26,2.5,.9,3.03,inScene); bx(8.4,1.4,1.4,0xbfe3f7,2.5,2.2,2.2,inScene).material.transparent=true; inScene.children[inScene.children.length-1].material.opacity=.45; bx(8.7,.12,1.7,0x2b2a26,2.5,2.95,2.2,inScene); solidIn(2.5,2.2,8.7,1.9);
item('display case','Bread and cakes are shown in the glass display case.',-1,2.2,3.9,function(g){bx(1.2,.1,1.1,0xfff6d6,0,1.55,0,g);});
item('pandan cake','Pandan cake is a soft, light green cake with a sweet smell.',1.6,2.2,3.5,function(g){cy(.65,.65,.55,0xb7d98a,0,1.85,0,g,16); cy(.67,.67,.1,0xf6efe0,0,2.15,0,g,16);});
item('curry puff','A curry puff is a crispy pastry with a spicy filling.',4,2.2,3.3,function(g){var p=new THREE.Mesh(new THREE.SphereGeometry(.5,12,8),mat(0xd9a05a)); p.scale.set(1.2,.7,.9); p.position.y=1.8; g.add(p); bx(.7,.08,.12,0xb07a45,0,2.1,0,g);});
item('cash register','The cashier uses the cash register to add up the bill.',6.4,2.2,3.5,function(g){bx(1.3,.5,.9,0x555,0,1.8,0,g); bx(1,.6,.1,0x2b2a26,0,2.3,-.3,g).rotation.x=-.3; bx(1.1,.1,.6,0x777,0,2.1,.2,g);});
item('cashier','A cashier takes payment and gives the change.',8.6,1.2,2.9,npc('#C07A54','#9A5F3F','#D89472','#FFFFFF',false));
item('paper bag','We put our bread in a paper bag.',7.6,2.2,3.2,function(g){bx(.9,1.1,.5,0xc9a36a,0,2.0,0,g); bx(.7,.15,.05,0xb08a50,0,2.5,.27,g);});
item('price tag','A price tag tells you how much something costs.',-3.8,2.4,3,function(g){bx(.08,.9,.08,0x666,0,.95,0,g); bx(1.1,.7,.08,0xfff6d6,0,1.8,0,g);});
item('table','A table is where customers can sit and enjoy their cake.',11,7,3,function(g){cy(1.1,1.1,.12,0xe6e0d0,0,1.5,0,g,18); cy(.12,.12,1.5,0x666,0,.75,0,g,6); cy(.4,.4,.15,0xffffff,.2,1.65,.1,g,10);}); solidIn(11,7,2.6,2.6);
item('chair','A chair has a back and four legs.',8.6,7,2.6,function(g){bx(.9,.15,.9,0xc07a54,0,.95,0,g); bx(.12,1,.9,0xc07a54,-.4,1.5,0,g); [[-.35,-.35],[.35,-.35],[-.35,.35],[.35,.35]].forEach(function(p){bx(.1,.9,.1,0x666,p[0],.45,p[1],g);});});
// striped valance, filled display case, shelves, chalkboard, baskets
for(var vi=0;vi<9;vi++){bx(.95,1.4,.06,vi%2?0xa8743f:0x9a6a3a,-1.5+vi*.95,.75,3.03,inScene);}
for(var di=0;di<9;di++){var dx=-1.6+di*.95,k2=di%4,fz=2.75,fy=1.74; var fm;
if(k2===0){fm=new THREE.Mesh(new THREE.TorusGeometry(.2,.1,6,12),mat(0xe68ab0)); fm.rotation.x=Math.PI/2; fm.position.set(dx,fy,fz); inScene.add(fm); fm=new THREE.Mesh(new THREE.TorusGeometry(.2,.1,6,12),mat(0x6b3a1c)); fm.rotation.x=Math.PI/2; fm.position.set(dx+.3,fy,fz-.45); inScene.add(fm);}
else if(k2===1){cy(.22,.17,.25,0xf3e0b0,dx,fy,fz,inScene,10); fm=new THREE.Mesh(new THREE.SphereGeometry(.25,8,6),mat(0xf7a8c4)); fm.scale.y=.7; fm.position.set(dx,fy+.25,fz); inScene.add(fm);}
else if(k2===2){fm=new THREE.Mesh(new THREE.SphereGeometry(.3,8,6),mat(0xd9a05a)); fm.scale.set(1.1,.7,1); fm.position.set(dx,fy,fz); inScene.add(fm); fm=new THREE.Mesh(new THREE.SphereGeometry(.3,8,6),mat(0xc98a3f)); fm.scale.set(1.1,.7,1); fm.position.set(dx+.2,fy,fz-.5); inScene.add(fm);}
else{cy(.34,.34,.2,0xc8402f,dx,fy,fz,inScene,10); cy(.3,.3,.22,0xf2c14e,dx,fy+.02,fz,inScene,10); fm=new THREE.Mesh(new THREE.SphereGeometry(.08,6,4),mat(0xc8402f)); fm.position.set(dx,fy+.2,fz); inScene.add(fm);}}
bx(8.3,.06,1.2,0xffffff,2.5,2.25,2.2,inScene);
for(var si=0;si<7;si++){var sc=[0xf7c6d4,0xf3e0b0,0xcfe8f0,0xb7d98a,0xf2c14e,0xe68ab0,0xffffff][si]; cy(.3,.3,.3,sc,-1.2+si*1.2,2.45,2.25,inScene,10); fm=new THREE.Mesh(new THREE.SphereGeometry(.18,6,4),mat(0xc8402f)); fm.position.set(-1.2+si*1.2,2.7,2.25); inScene.add(fm);}
[-10,-2,6,14].forEach(function(sx,i2){bx(7,.15,1,0x8a5a2b,sx,4.6,-10.5,inScene); bx(7,.15,1,0x8a5a2b,sx,6.2,-10.5,inScene); for(var j=0;j<6;j++){var lf=new THREE.Mesh(new THREE.SphereGeometry(.4,8,6),mat([0xd9a05a,0xc98a3f,0xe3b672][(i2+j)%3])); lf.scale.set(1,.7,1.3); lf.position.set(sx-3+j*1.2,4.95,-10.4); inScene.add(lf); cy(.35,.35,.5,[0xf7c6d4,0xcfe8f0,0xf3e0b0][(i2+j)%3],sx-3+j*1.2,6.55,-10.4,inScene,8);}});
dSign('Today',-12.5,2.8,-10.9,3,2.6,'#2b2f2c','#ffffff',0,'Curry puff $1.50');
[[-14,-4],[14,-3]].forEach(function(b){cy(.8,.65,1.2,0xb98a52,b[0],.6,b[1],inScene,12); for(var j=0;j<5;j++){dBag(b[0]-.4+j*.2,1.9,b[1],2.4,(j-2)*.12,0);}});
(function(){var g=new THREE.Group(); g.position.set(13.4,0,7); inScene.add(g); bx(.9,.15,.9,0xc07a54,0,.95,0,g); bx(.12,1,.9,0xc07a54,.4,1.5,0,g); [[-.35,-.35],[.35,-.35],[-.35,.35],[.35,.35]].forEach(function(q){bx(.1,.9,.1,0x666,q[0],.45,q[1],g);}); person(13.4,1.02,7,0x7b56c4,inScene,true,[11,7]); solidIn(13.4,7,1.2,1.2);})();
(function(){var x=-10.5,z=-9; [[-.6,-.4],[.6,-.4],[-.6,.4],[.6,.4]].forEach(function(q){bx(.08,3,.08,0xcfd4d9,x+q[0],1.5,z+q[1],inScene);}); [.6,1.1,1.6,2.1,2.6].forEach(function(y){bx(1.4,.06,1,0xcfd4d9,x,y,z,inScene); cy(.3,.3,.08,0xd9a05a,x-.3,y+.07,z,inScene,8); cy(.3,.3,.08,0xc98a3f,x+.3,y+.07,z,inScene,8);}); solidIn(x,z,1.6,1.2);})();
// more display cases, cake stands, French loaves
var CK=[0xf7c6d4,0xe68ab0,0x6b3a1c,0xf3e0b0,0xc8402f,0xb7d98a,0xffffff];
[[-10.5,5],[13.5,4]].forEach(function(c){bx(c[1],1.3,1.5,0xa8743f,c[0],.65,2.2,inScene); bx(c[1]+.2,.12,1.7,0x2b2a26,c[0],1.36,2.2,inScene); for(var vv=0;vv0?-Math.PI/2:0), px=side*16.55; if(!side){px=x;} var cx=side?px:x, cz=side?z:-10.8; bx(side?.2:w+.4,h+.4,side?w+.4:.2,0x8a5a2b,cx,y,cz,inScene); plane(w,h,canvasMat(512,Math.round(512*h/w),function(c,cw,ch){c.fillStyle='#c9a06a'; c.fillRect(0,0,cw,ch); c.fillStyle='#2f5f9e'; c.fillRect(0,0,cw,ch*.2); c.fillStyle='#fff'; c.font='700 '+Math.round(ch*.14)+'px Rubik,Arial'; c.textAlign='center'; c.textBaseline='middle'; c.fillText(title,cw/2,ch*.1); var cols=['#fff3a8','#cfe8f0','#f7c6d4','#b7d98a','#ffffff','#f4b183']; for(var r=0;r<2;r++)for(var k=0;k<4;k++){c.fillStyle=cols[(r*4+k)%6]; c.save(); c.translate(cw*(.14+k*.24),ch*(.42+r*.32)); c.rotate(((k+r)%3-1)*.06); c.fillRect(-cw*.09,-ch*.12,cw*.18,ch*.24); c.fillStyle='rgba(0,0,0,.35)'; for(var l=0;l<3;l++)c.fillRect(-cw*.07,-ch*.07+l*ch*.07,cw*.14*(l==2?.6:1),ch*.02); c.fillStyle='#d9372c'; c.beginPath(); c.arc(0,-ch*.11,ch*.02,0,7); c.fill(); c.restore();}}),side?cx-side*.12:cx,y,side?cz:-10.65,inScene,side?(side<0?Math.PI/2:-Math.PI/2):0);}
nBoard(0,5.2,12,-1,'Our class work',5.4,3.4); nBoard(0,5.2,19,-1,'Class rules',5.4,3.4); nBoard(0,5.2,12,1,'Reading corner',5.4,3.4); nBoard(0,5.2,19,1,'Birthdays',5.4,3.4);
nBoard(-14.4,5,0,0,'Notices',4.2,3.4); nBoard(0,2.6,3,1,'Our art',4.4,2.2);
// pupil desks: two blocks, with pupils
function pdesk(x,z,i,asItem){
var STEEL=0x8d949b, WOOD=0xd9b27a, EDGE=0xb98a4d;
var build=function(g){
/* laminate top with a darker edge band and a slightly raised front lip */
bx(1.8,.1,1.1,WOOD,0,1.45,0,g); bx(1.84,.06,1.14,EDGE,0,1.4,0,g); bx(1.8,.05,.06,EDGE,0,1.5,.52,g);
/* four round steel legs, a cross bar front and back, and rubber feet */
[[-.78,-.46],[.78,-.46],[-.78,.46],[.78,.46]].forEach(function(q){cy(.045,.045,1.38,STEEL,q[0],.72,q[1],g,8); cy(.07,.07,.05,0x2a2d31,q[0],.03,q[1],g,8);});
bx(1.56,.04,.04,STEEL,0,.35,-.46,g); bx(1.56,.04,.04,STEEL,0,.35,.46,g); bx(.04,.04,.92,STEEL,-.78,.35,0,g); bx(.04,.04,.92,STEEL,.78,.35,0,g);
/* book tray under the top, with a book and a water bottle bag shape */
bx(1.5,.05,.8,0x7f8890,0,1.12,.02,g); bx(1.5,.2,.04,0x7f8890,0,1.22,-.38,g); bx(.5,.08,.34,0x2f6fb0,-.4,1.18,.02,g); bx(.4,.06,.3,0xd9372c,.1,1.17,.02,g);
/* on the desk: open exercise book, pencil, rubber */
bx(.62,.025,.44,0xffffff,-.3,1.515,.1,g); bx(.012,.03,.44,0xc9ced4,-.3,1.52,.1,g);
for(var l=0;l<4;l++)bx(.22,.004,.012,0x6e8fc4,-.46,1.53,.0+l*.07,g);
cy(.018,.018,.34,[0xe3b53a,0x2f8f5a,0xd9372c,0x7b56c4][i%4],.2,1.52,.18,g,6).rotation.z=Math.PI/2+.25;
bx(.1,.05,.06,0xf4b6c2,.38,1.53,.0,g);
if(i%3===0){cy(.09,.09,.28,0x4aa3df,.62,1.64,-.28,g,10); cy(.06,.06,.06,0xffffff,.62,1.81,-.28,g,10);}
};
if(asItem)item('pupil desk','A pupil writes and reads at a pupil desk.',x,z,2.9,build); else{var g=new THREE.Group(); g.position.set(x,0,z); inScene.add(g); build(g);}
/* school chair: moulded seat and back on a tube frame with a cross bar */
var cz=z+1.25, CC=[0x2f5f9e,0x2f8f5a,0xd9892b,0x7b56c4][i%4];
bx(.74,.08,.7,CC,x,.95,cz,inScene); bx(.7,.05,.66,CC,x,.9,cz,inScene);
bx(.7,.55,.07,CC,x,1.4,cz+.36,inScene); bx(.6,.06,.08,CC,x,1.7,cz+.36,inScene); bx(.74,.04,.1,0x1d3f6e,x,1.12,cz+.34,inScene);
[[-.3,-.28],[.3,-.28],[-.3,.3],[.3,.3]].forEach(function(q){cy(.035,.035,.9,0x70767d,x+q[0],.45,cz+q[1],inScene,8); cy(.05,.05,.04,0x222,x+q[0],.02,cz+q[1],inScene,8);});
bx(.6,.03,.03,0x70767d,x,.35,cz-.28,inScene); bx(.6,.03,.03,0x70767d,x,.35,cz+.3,inScene);
if((i%5)!==4)person(x,1.0,cz,[0xffffff,0x2f6fb0,0xe0707f,0xd9892b][i%4],inScene,true,[x,z-4]);
solidIn(x,z+.6,2,2.6);}
[-13,-9,-5,5,9,13].forEach(function(x,ci){[-1,3,7,11,15,19].forEach(function(z,ri){pdesk(x,z,ci+ri*6,ci===1&&ri===0);});});
/* pupil lockers along the left wall at the back */
item('lockers','Pupils keep their bags and books in lockers.',-16.2,26,4.2,function(g){
for(var i=0;i<5;i++){var c=[0x2f5f9e,0x2f7d4f,0xd9892b,0x7b56c4,0xd9372c][i]; bx(.5,3.4,1.5,c,0,1.7,(i-2)*1.55,g); bx(.06,2.4,1.3,0x1d2733,.27,2.2,(i-2)*1.55,g); for(var v=0;v<3;v++)bx(.05,.08,.9,0x111,.3,2.9-v*.18,(i-2)*1.55,g); cy(.06,.06,.06,0xe9ecef,.3,1.7,(i-2)*1.55+.5,g,8).rotation.z=Math.PI/2; bx(.05,.3,.5,0xffffff,.3,1.2,(i-2)*1.55,g);} bx(.6,.12,7.9,0x333,0,3.45,0,g);
}); solidIn(-16.2,26,1,8);
/* reading corner: rug, bean bags and a low book trolley */
item('reading corner','Pupils sit in the reading corner to read quietly.',11,27,3.4,function(g){
cy(3.2,3.2,.06,0xd9892b,0,.03,0,g,32); cy(2.5,2.5,.07,0xf4d9a0,0,.04,0,g,32); cy(1.7,1.7,.08,0x2f8f8f,0,.05,0,g,32);
[[-1.3,-.9,0xd9372c],[1.3,-.6,0x2f5f9e],[0,1.4,0x7b56c4]].forEach(function(b){var o=new THREE.Mesh(new THREE.SphereGeometry(.85,14,10),mat(b[2])); o.scale.set(1,.62,1); o.position.set(b[0],.55,b[1]); g.add(o); var t=new THREE.Mesh(new THREE.SphereGeometry(.4,10,8),mat(b[2])); t.scale.set(1.2,.5,.7); t.position.set(b[0],.95,b[1]-.3); g.add(t);});
bx(.2,.9,2.6,0xb98a52,3.6,.5,0,g); bx(.9,.1,2.6,0xb98a52,3.2,.12,0,g); [0xd9372c,0x2f7d4f,0xe3b53a,0x2f5f9e].forEach(function(c,i){bx(.5,.7,.3,c,3.2,.5,-1+i*.65,g);});
}); solidIn(11,27,6.4,6.4);
/* water cooler and a bin near the door side */
item('water cooler','Pupils fill their water bottles at the water cooler.',-16,18.5,3.4,function(g){bx(1,1.8,1,0xe9ecef,0,.9,0,g); bx(.9,.04,.9,0x9aa1a8,0,1.82,0,g); cy(.35,.35,.9,0x4aa3df,0,2.3,0,g,14); bx(.04,.2,.3,0xd9372c,.5,1.2,-.2,g); bx(.04,.2,.3,0x2f5f9e,.5,1.2,.2,g); bx(.5,.04,.9,0x333,.5,.7,0,g);}); solidIn(-16,18.5,1.2,1.2);
item('bin','Rubbish goes into the bin, and paper into the recycling bin.',-15.6,22.5,2.8,function(g){cy(.45,.35,1,0x2f7d4f,0,.5,0,g,12); cy(.5,.5,.08,0x1f5f3a,0,1.02,0,g,12); cy(.45,.35,1,0x2f5f9e,1.1,.5,0,g,12); cy(.5,.5,.08,0x1d3f6e,1.1,1.02,0,g,12);});
item('school bag','Pupils carry their books in a school bag.',-15,11,2.8,function(g){bx(1,1.3,.6,0x2f6fb0,0,.65,0,g); bx(.8,.5,.1,0xe3b53a,0,.5,.35,g); bx(.15,.9,.1,0x444,-.3,.8,-.3,g);});
item('exercise book','Pupils write their work in an exercise book.',-9,3,2.6,function(g){bx(.7,.06,.9,0xd9372c,0,1.6,0,g); bx(.5,.02,.7,0xffffff,0,1.65,0,g);});
item('pencil case','A pencil case keeps pens and pencils together.',9,3,2.6,function(g){bx(.9,.25,.4,0x7b56c4,0,1.6,0,g); cy(.04,.04,.7,0xe3b53a,.2,1.75,0,g,5).rotation.z=1.2;});
item('fire extinguisher','A fire extinguisher is used by adults to put out small fires.',15.9,8,3,function(g){cy(.3,.3,1.5,0xd9372c,0,0,0,g,10); cy(.1,.1,.3,0x333,0,.9,0,g,6);});
dPlant(-15,-9,1.3); dPlant(15,10,1.3);
built.school=true;
}
var BUILD_IN={hawker:function(){buildHawkerIn();},hospital:function(){buildHospitalIn();},clinic:buildClinicIn,bakery:buildBakeryIn,canteen:buildCanteenIn,mrt:buildMrtIn,school:buildSchoolIn};
var VOC={hawker:['What can you see in the stall? Describe three things.','What is the stall owner doing? What do you think is being cooked?','Which tools does a cook use? What does each one do?','How does the stall keep the food safe and clean?','What would you order and why? Say it in a full sentence.','Where do we put our tray after eating? Why is this good for the cleaners and the next customer?'],
hospital:['Who works in a hospital? What does each person do?','What is the nurse doing? Which tools can you see?','What should you do when you feel unwell?','How does a hospital keep patients safe and clean?','Describe the ward in three sentences.']};
var INNAME={hawker:'Inside the hawker stall',hospital:'Inside the hospital ward'};
BANK.clinic=[
{q:'The pharmacist is giving medicine to a patient. What should be checked first?',o:['The colour of the bottle','That the name and the instructions match the patient','Nothing, hand it over quickly'],a:1,w:'Checking the name and the instructions helps make sure the right person gets the right medicine.'},
{q:'An older patient finds the registration kiosk hard to use. What do you do?',o:['Walk away','Offer to help, explain the steps slowly and let them try','Tell them to hurry up'],a:1,w:'Patient, friendly help makes people feel respected and welcome.'},
{q:'A child is nervous about a check-up. What can you say?',o:['Do not be silly','The doctor will check you gently, and I am right here','Stop worrying'],a:1,w:'Calm, kind words help a nervous child feel safe.'},
{q:'The waiting area is full and a patient asks how long they must wait. What do you say?',o:['I do not know, stop asking','Look at the queue number, tell them honestly, and thank them for waiting','Say five minutes even if it is not true'],a:1,w:'Being honest and polite shows respect for every patient.'}];
BANK.bakery=[
{q:'A customer says they are allergic to nuts. What do you do?',o:['Say all the cakes are fine','Tell them which items have nuts, and check with the baker if you are not sure','Give them any cake'],a:1,w:'With allergies, never guess. Checking first protects the customer.'},
{q:'You have just put rubbish in the bin. What do you do before you touch the bread?',o:['Wash your hands with soap','Wipe your hands on your apron','Carry on quickly'],a:0,w:'Washing hands stops germs from spreading to food.'},
{q:'A customer pays too much by mistake. What do you do?',o:['Keep it quietly','Count again and give the correct change','Say nothing'],a:1,w:'Giving the correct change is honest and builds trust.'},
{q:'A hot tray comes out of the oven. How do you carry it?',o:['With oven gloves','With bare hands, quickly','Ask a customer to hold it'],a:0,w:'Oven gloves protect your hands from burns.'}];
BANK.canteen=[
{q:'A younger pupil drops a tray of food. What do you do?',o:['Laugh','Ask if they are okay and help pick it up','Walk away'],a:1,w:'Helping someone who is upset is a kind thing to do.'},
{q:'The queue is long and someone pushes in. What is a polite way to respond?',o:['Push them back','Politely tell them the queue starts at the back','Shout at them'],a:1,w:'Speaking politely keeps everyone calm and the queue fair.'},
{q:'You spill water on the floor. What do you do?',o:['Leave it and walk away','Tell the cleaner, or wipe it up safely and put up a wet floor sign','Wait for someone to slip'],a:1,w:'A wet floor can make people slip, so clean it or warn others.'},
{q:'What should pupils do before they eat?',o:['Wash their hands with soap','Wipe their hands on their shirt','Nothing'],a:0,w:'Clean hands help keep germs out of our food.'}];
BANK.school=[
{q:'A pupil says they do not understand the question. What does a good teacher do?',o:['Say it again louder','Explain it a different way with an example','Tell the pupil to guess','Move on to the next page'],a:1,w:'Showing the idea in a new way helps the pupil understand.'},
{q:'The class is very noisy. What is a calm way to get attention?',o:['Shout over everyone','Use a quiet signal and wait until all are ready','Give everyone extra work','Leave the room'],a:1,w:'A calm signal teaches pupils to listen without anyone shouting.'},
{q:'A pupil finishes the work early. What is a good next step?',o:['Let them wander around','Give a harder task to think about','Tell them to sleep','Take away their book'],a:1,w:'A harder task keeps a fast learner thinking and growing.'},
{q:'The fire alarm rings during a lesson. What do you do?',o:['Finish the lesson first','Line up calmly and walk out by the exit route','Run to the canteen','Hide under the desks'],a:1,w:'Leaving calmly by the planned route keeps everyone safe.'}];
BANK.mrt=[
{q:'A train arrives and people inside want to get off. Where do you stand?',o:['Right in front of the doors','To the side of the doors, so passengers can get off first','On the platform edge'],a:1,w:'Letting passengers get off first makes boarding safer and quicker for everyone.'},
{q:'You are in a priority seat and an older person is standing nearby. What do you do?',o:['Pretend to sleep','Kindly offer your seat','Look at your phone'],a:1,w:'Priority seats are for people who need them more, so offering your seat is the kind choice.'},
{q:'A passenger looks lost in the station. What do you do?',o:['Ignore them','Offer to help, and show them the station map or the station staff','Tell them to go away'],a:1,w:'A kind offer of help can make a lost passenger feel safe.'},
{q:'You drop your card near the gantry and a queue builds up behind you. What do you do?',o:['Stand still in the gate','Step aside to pick it up so others can go through','Push past people'],a:1,w:'Stepping aside is considerate and keeps the line moving.'}];
Object.assign(VOC,{
clinic:['Who works in a polyclinic? What does each person do?','What does the doctor use to check a patient?','What does the pharmacist do? Why must we only take medicine given by a doctor or pharmacist?','What can you see in the waiting area?','Describe how a patient moves from the kiosk to the doctor to the pharmacy.'],
bakery:['What do you see in the kitchen? Which tools does the baker use?','What is the baker making? Describe the steps in order.','What do you see in the display case? What would you buy?','Why must bakers wash their hands and wear a clean apron?','How does the cashier give the right change?'],
canteen:['What can you see at the food stall?','What would you choose to eat and drink? Say it in a full sentence.','Why do we queue and wash our hands before eating?','Where do we put rubbish and recyclables?','How can you keep the canteen clean and safe?'],
school:['What can you see on the whiteboard?','What does the teacher do at the start of the lesson?','Why do we take attendance every day?','How do pupils show they are ready to learn?','How can you help keep the classroom tidy and calm?'],
mrt:['What do you do at the fare gantry? Why do we tap in and tap out?','Where should you stand when the train doors open?','Who are priority seats for?','What does the station staff do?','Describe how a passenger travels from the gantry to the train.']});
Object.assign(INNAME,{clinic:'Inside the polyclinic',bakery:'Inside the bakery',canteen:'Inside the school canteen',mrt:'Inside the MRT station',school:'Inside the classroom'});
Object.assign(spots,{
clinic:{x:46,z:5.5,name:'Polyclinic and Pharmacy',path:'Healthcare path'},
bakery:{x:64,z:5.5,name:'Bakery',path:'Food path'},
canteen:{x:82,z:5.5,name:'School Canteen',path:'Food path'},
mrt:{x:100,z:5.5,name:'MRT Station',path:'Transport path'},
school:{x:118,z:5.5,name:'School',path:'Teacher path'}});
var IN_BOUNDS={x:15.5,zMin:-9.5,zMax:10.5};
function collideIn(x,z){for(var i=0;i'+spots[key].path+'';
{
h+='';
h+='
'+(nearSt?'You are at '+station.name+'.':'Tap any object to learn its word. The ring is at '+station.name+'.')+'
';
h+='For the class Conversation starters for the teacher'+VOC[key].map(function(q){return '
'+q+'
';}).join('')+'';
}
if(panel.getAttribute('data-k')!==h){panel.innerHTML=h; panel.setAttribute('data-k',h);
var e;
(e=document.getElementById('j3start'))&&e.addEventListener('click',function(){OPT.onShift(key);});
(e=document.getElementById('j3wl2'))&&e.addEventListener('click',function(){OPT.onWords();});
(e=document.getElementById('j3leave'))&&e.addEventListener('click',leaveBuilding);
Array.prototype.forEach.call(panel.querySelectorAll('[data-bo]'),function(bt){bt.addEventListener('click',function(){var v=bt.getAttribute('data-bo'); if(v==='skip'||v==='done'){bonus=null; bonusPick=-1;} else {bonusPick=+v; if(bonusPick===bonus.a){ppl++; KEEP.ppl=ppl;}} setInPanel();});});
(e=document.getElementById('j3lbl'))&&e.addEventListener('click',function(){labelsOn=!labelsOn; inItems.forEach(function(it){it.userData.label.visible=labelsOn;}); setInPanel();});
}
}
var NOTES={
hawker:{t:'Why we have hawker centres',f:['Long ago, many hawkers sold food on the roadside. They had little clean water and nowhere to put their rubbish, which attracted pests and was bad for public health.','From 1971 to 1986, over 18,000 hawkers were moved into markets and hawker centres.','In December 2020, Singapore\'s hawker culture was added to UNESCO\'s list of the world\'s intangible cultural heritage.','Newer hawker centres have high ceilings and big ceiling fans, so they feel airier than older ones.','Since 1 September 2021, diners at hawker centres must return their trays and dishes and clear their litter. This keeps tables clean for the next person and helps the cleaners.'],src:[['SG101: Our Hawker Centres','https://www.sg101.gov.sg/resources/archives/heritage-our-hawker-centres/'],['NEA: Hawker culture','https://www.nea.gov.sg/our-services/hawker-management/programmes-and-grants/hawker-culture'],['NEA: Mandatory tray return','https://www.nea.gov.sg/media/news/news/index/mandatory-for-diners-to-return-dirty-trays-crockery-and-clean-table-litter-at-hawker-centres-coffeeshops-and-food-courts']]},
hospital:{t:'Where Singapore\'s first general hospital began',f:['Singapore General Hospital is Singapore\'s first general hospital and its oldest medical institution.','It began as a wooden shed in the army area (the cantonment) near Bras Basah Road and Stamford Road, to treat soldiers and local people. The National Library dates the shed to 1819. In 1821 proper buildings replaced it.','The hospital moved several times and was rebuilt. The name Singapore General Hospital dates from 1926, and today it stands on Outram Road.'],src:[['National Library Board, Infopedia: Singapore General Hospital','https://eresources.nlb.gov.sg/infopedia/articles/SIP_383_2005-01-03.html'],['SGH: The early years','https://www.sgh.com.sg/about-sgh/who-we-are/our-heritage/the-early-years']]}
,bus:{t:'Bus stop skills',open:1,f:['Tap in, tap out: tap your card on the card reader when you board, and tap it again when you get off. Use the same card both times so you pay the right fare. The reader lights up and beeps when the tap works.','Stand behind the yellow line at the boarding berth, and wait for the bus to stop fully before you board.','Hold on to the handrails or poles when the bus is moving. Buses can brake suddenly.','Priority seats are at the front of the bus. They are for people who need them more, such as the elderly and passengers with young children. Offer your seat if you are sitting in one and they need it.','Every real bus stop has its own 5-digit bus stop code, shown on the bus stop sign. The sign also shows the service numbers of the buses that stop there. The sign in the Town Centre uses made-up names (stop A-01 and Aura Route A1), so it cannot be mistaken for a real bus stop.'],src:[['LTA: Plan Your Journey (same card for tap in and out)','https://www.lta.gov.sg/content/ltagov/en/getting_around/public_transport/plan_your_journey.html'],['Ask.gov.sg: tapping on buses','https://ask.gov.sg/ptc/questions/cmmmvv9oj00145p01ntc023e8'],['SBS Transit: Safety','https://sbstransit.com.sg/safety'],['SBS Transit: Special assistance (priority seats)','https://sbstransit.com.sg/special-assistance'],['MOT: Reserved seats are for those who need them more','https://www.mot.gov.sg/news-resources/newsroom/written-reply-by-minister-for-transport-khaw-boon-wan-to-parliamentary-question-on-tighter-enforcement-for-reserved-seats-on-mrt-trains/'],['Land Transport Guru: Bus stops (5-digit code)','https://landtransportguru.net/bus-stops/']]},
mrt:{t:'MRT manners',f:['Stand to the side of the doors and let passengers get off first.','Tap in at the gantry and tap out at the end of your trip. Use the same card or device for both taps.','Hold on to the grab poles and hand-straps.','There are more than 140 stations across six MRT lines in Singapore.'],src:[['SBS Transit: Safety','https://sbstransit.com.sg/safety'],['LTA: Plan Your Journey','https://www.lta.gov.sg/content/ltagov/en/getting_around/public_transport/plan_your_journey.html'],['LTA: Rail network','https://www.lta.gov.sg/content/ltagov/en/getting_around/public_transport/rail_network.html']]}
};
var CAMO={zd:11,yd:5.2,zp:14,yp:6.6}, CAMI={zd:12,yd:7.5,zp:16.5,yp:10};
var near=null, entered=null, autoEnter=null;
// walking into a door enters: zone is 2.6 m either side of the door spot and anywhere from the building front to z 7.2; leaving locks that door until Aura is 4.6 m away or a new tap
var lockKey=null, tapGoal=null, DOOR_W=2.6, DOOR_Z=7.2, REARM=4.6;
var goBtn=document.getElementById('j3go'); if(goBtn)on(goBtn,'click',function(){if(near&&BUILD_IN[near])enterBuilding(near); else if(near==='bus')OPT.onHome();});
function setPanel(){
var h;
if(near){var s=spots[near]; h='
'+s.name+' '+(s.path?''+s.path+'':'')+'
'+(near==='bus'?'Take the bus back to your home. Read the bus stop skills below first.':'Your workplace for this path. Go inside to look around, then start your shift.')+'
'+(NOTES[near]?''+(NOTES[near].open?'Learn':'Did you know?')+' '+NOTES[near].t+'
Walk to a door to go in, or tap a place in the bar.
';}
if(goBtn){goBtn.hidden=!near||!!inside; goBtn.textContent=(near==='bus')?'Take the bus home':'Enter the building';}
Array.prototype.forEach.call(document.querySelectorAll('#j3 .j3dock [data-go]'),function(b){var on2=b.getAttribute('data-go')===near; b.classList.toggle('here',on2); if(on2){var dk=b.parentNode; var tl=b.offsetLeft-(dk.clientWidth-b.offsetWidth)/2; try{dk.scrollTo({left:Math.max(0,tl),behavior:'smooth'});}catch(e){dk.scrollLeft=Math.max(0,tl);}}});
if(panel.getAttribute('data-k')!==h){panel.innerHTML=h; panel.setAttribute('data-k',h);
var e=document.getElementById('j3enter'); if(e)e.addEventListener('click',function(){if(BUILD_IN[near]){enterBuilding(near);}else{OPT.onHome();}});}
}
setPanel();
var last=performance.now(), t0=0;
function loop(now){
if(dead||paused){raf=0; return;}
var dt=Math.min(.05,(now-last)/1000); last=now; t0+=dt;
fans.forEach(function(f){f.rotation.y+=dt*7;}); var mv=new THREE.Vector3();
if(keys.arrowup||keys.w)mv.z-=1; if(keys.arrowdown||keys.s)mv.z+=1; if(keys.arrowleft||keys.a)mv.x-=1; if(keys.arrowright||keys.d)mv.x+=1;
if(mv.lengthSq()>0){target=null; mv.normalize();}
else if(target){var d=target.clone().sub(pl.position); d.y=0; if(d.length()<.4)target=null; else mv=d.normalize();}
var moving=mv.lengthSq()>0;
if(moving&&!entered){var sp=(inside?4.4:6.2)*dt, nx=pl.position.x+mv.x*sp, nz=pl.position.z+mv.z*sp;
if(inside){nx=Math.max(-IN_BOUNDS.x,Math.min(IN_BOUNDS.x,nx)); nz=Math.max(IN_BOUNDS.zMin,Math.min(IN_BOUNDS.zMax,nz)); if(!collideIn(nx,pl.position.z))pl.position.x=nx; if(!collideIn(pl.position.x,nz))pl.position.z=nz;}
else{nx=Math.max(-80,Math.min(122,nx)); nz=Math.max(-24,Math.min(26,nz));
if(!collide(nx,pl.position.z))pl.position.x=nx; if(!collide(pl.position.x,nz))pl.position.z=nz;}
pl.rotation.y=Math.atan2(mv.x,mv.z); legL.rotation.x=Math.sin(t0*14)*.7; legR.rotation.x=-Math.sin(t0*14)*.7; armL.rotation.x=-Math.sin(t0*14)*.6; armR.rotation.x=Math.sin(t0*14)*.6; pl.position.y=Math.abs(Math.sin(t0*14))*.08;}
else{legL.rotation.x=legR.rotation.x=armL.rotation.x=armR.rotation.x=0; pl.position.y=0;}
pl.userData.aura.forEach(function(m,i){m.position.y+=Math.sin(t0*2+i)*.0006;});
var nn=null; if(!inside)Object.keys(spots).forEach(function(k){var s=spots[k]; if(Math.hypot(pl.position.x-s.x,pl.position.z-s.z)<5)nn=k;});
if(!inside&&lockKey){var ls=spots[lockKey]; if(Math.hypot(pl.position.x-ls.x,pl.position.z-ls.z)>REARM)lockKey=null;}
if(!inside&&!lockKey){var ek=null; Object.keys(BUILD_IN).forEach(function(k){var s=spots[k]; if(!s||ek)return; if(Math.abs(pl.position.x-s.x)=N||sz>=N)return false; var top=surfaceY(sx,sz); if(player.y0)n+=inv[k];}); return n;}
function homeSpend(n){var ks=Object.keys(inv).filter(function(k){return +k!==AXE&&+k!==BOAT&&!dugId(+k)&&inv[k]>0;}).sort(function(a,b){return inv[b]-inv[a];}); while(n>0&&ks.length){var k=ks[0]; inv[k]--; n--; if(inv[k]<=0)ks.shift();}}
function homeGo(){
if(visiting&&!visiting.tour){goHome(); hint('Back at the start.'); return;}
if(visiting){ hint('Tap Go home to leave Singapore first.',true); return; }
var stuck=homeStuck();
if(stuck){ goHome(); sfx('done'); changed(); hint('You were stuck, so Home was free. Welcome back.',true); return; }
if(blockTotal()=0;i--)if(ST[i][0]===a&&ST[i][1]===b&&ST[i][2]===c){ST.splice(i,1);break;} if(v===WH_||v===GL||v===DR)ST.push([a,b,c,d]);}
for(x=X0;x<=X1;x++)for(z=Z0;z<=cz;z++){ /* floor: the ground cell is replaced (not covered) so the wood does not flicker against the paving */
if(H-1>=-D)tv[tvi(x,H-1,z)]=0;
if(z===cz)fill(x,H-1,z,GR); else set(x,H-1,z,BY.plank);
}
terrDirty=true;
for(x=X0;x<=X1;x++)for(z=Z0;z<=Z1;z++){
var edge=(x===X0||x===X1||z===Z0||z===Z1);
for(y=H;y<=H+2;y++){ if(edge)wl(x,y,z,WH_,side(x,z)); else set(x,y,z,0); }
if(edge)set(x,H+3,z,GR); else wl(x,H+3,z,WH_,'U'); /* grey band at the edge, thin white ceiling inside */
}
for(x=X0;x<=X1;x++){ fill(x,H+3,cz,GR); fill(x,H+3,pz,GR); if(x!==12&&!world[wi(x,H,pz)])wl(x,H,pz,WH_,'Z'); } /* corridor: slab above, 1 m parapet, one 1 m gap at x 12 */
for(z=Z0+1;z=HH){hint('The ground is too high here for a house. Try a flatter world.',true); return;}
var TOP=(H+9=9&&x<=13&&z>=5&&z<=9){back[v]=(back[v]||0)+1; continue;} if(plotW){back[v]=(back[v]||0)+1; plotMv++; continue;} hint('Something you built is on the house plot. Move it or break it, then tap My house again.',true); return;}
Object.keys(back).forEach(function(k){inv[k]=(inv[k]||0)+back[k];});
for(x=HOUSE.x0;x<=HOUSE.x1;x++)for(z=HOUSE.z0;z<=HOUSE.z1;z++){
for(y=H;y<=H+TOP;y++)world[wi(x,y,z)]=0;
for(y=surfaceY(x,z);y.lvldesc'); if(sd)sd.textContent='Start on a clear plot of land and build with blocks.'; var sq=md.querySelector('#startPanel>h2'); if(sq)sq.textContent='Ready to build?';
}
function done(){try{localStorage.setItem('mbStartDone','1');}catch(e){} md.hidden=true; FRESH=false;}
(function(){ var off=document.getElementById('spOff'), on=document.getElementById('spOn'); if(!off||!on)return;
function show(){ var z=!!S.zom; off.setAttribute('aria-pressed',String(!z)); on.setAttribute('aria-pressed',String(z)); }
off.addEventListener('click',function(){ setZom('off'); show(); }); on.addEventListener('click',function(){ setZom(S.bm||'normal'); show(); }); show();
})();
/* Empty land: a flat grass field with a 12 x 12 concrete plot (x 6..17, z 2..13) that holds the whole My house footprint (9 x 9, x 7..15, z 3..11) with a margin, a few trees at the edges, the workbench beside the spawn. */
function plotLand(){
var x,y,z;
clearZombies(); clearBoats();
world=new Uint8Array(N*N*WH); terr='flat'; tseed=rnd(1,9999); genTerrain();
for(z=2;z<=13;z++)for(x=6;x<=17;x++)tv[tvi(x,-1,z)]=T_CONC; /* contains the whole apartment footprint (x 7..15, z 3..11) plus a 1 block margin */
[[2,2],[21,2],[1,9],[22,9],[2,20],[21,20]].forEach(function(t){
for(y=0;y<4;y++)world[wi(t[0],y,t[1])]=BY.log;
for(var a=-2;a<=2;a++)for(var c=-2;c<=2;c++)for(var h=3;h<=5;h++){
if(Math.abs(a)+Math.abs(c)+(h-3)*1.2>3.4||(a===0&&c===0&&h<4))continue; var lx=t[0]+a,lz=t[1]+c; if(lx<0||lz<0||lx>=LAND||lz>=LAND)continue; if(!world[wi(lx,h,lz)])world[wi(lx,h,lz)]=BY.leaves;
}
});
world[wi(14,0,16)]=CT;
S.st.tr=[]; S.st.pr=[]; S.st.plot=1;
setGround(); terrDirty=true; dirty=true; goHome(); changed();
}
document.getElementById('startLand').addEventListener('click',function(){var fresh=FRESH; done(); var ok=fresh&&SEED0&&terr==='city'&&!S.house&&SEED0.w.every(function(v,i){return world[i]===v;})&&SEED0.t.every(function(v,i){return tv[i]===v;}); if(ok){try{plotLand();}catch(e){} hint('This is your plot. Build here.',true);} else hint('Empty land it is. Everything you built is kept. Tap the blocks to start building.',true);});
document.getElementById('startHome').addEventListener('click',function(){done(); claimHouse();});
function tryShow(n){
if(!FRESH)return;
if(n>40)return;
if(modalOpen()||terr==='ocean'||S.house||S.houseDone){ if(!modalOpen()&&(S.house||S.houseDone||terr==='ocean')){FRESH=false;} else setTimeout(function(){tryShow(n+1);},1500); return; }
md.hidden=false; try{md.scrollTop=0; document.getElementById('startPanel').focus();}catch(e){}
}
setTimeout(function(){tryShow(0);},2500);
})();
(function(){var b=document.getElementById('houseBtn'); if(b)b.addEventListener('click',function(){claimHouse();});})();
(function(){var b=document.getElementById('jobBtn'); if(b)b.addEventListener('click',function(){openJobs();}); var bk=document.getElementById('jobBack'); if(bk)bk.addEventListener('click',closeModals);})();
var ELS=[["H", "Hydrogen", 1, "Gas", "The lightest element. Hydrogen and oxygen join to make water (H2O). Some rockets burn hydrogen as fuel.", "Science P4-P5: matter, water"], ["He", "Helium", 2, "Gas", "A gas lighter than air, so balloons filled with helium float. It does not burn.", "Science P3-P4: gases"], ["Li", "Lithium", 3, "Metal", "A very light metal. Rechargeable batteries in phones and tablets use lithium.", "Science P5-P6: energy, batteries"], ["C", "Carbon", 6, "Non-metal", "All living things contain carbon. Diamond and pencil lead are both carbon. We breathe out carbon dioxide gas.", "Science P3-P6: living things, gases"], ["N", "Nitrogen", 7, "Gas", "About 78 out of every 100 parts of air is nitrogen. Plants need it to grow, so farmers use fertiliser.", "Science P4-P5: air, plants"], ["O", "Oxygen", 8, "Gas", "About 21 out of every 100 parts of air is oxygen. We need it to breathe and for things to burn.", "Science P4-P5: air, breathing, burning"], ["Ne", "Neon", 10, "Gas", "A glowing gas used in bright signs. It hardly reacts with anything.", "Sec Science: noble gases"], ["Na", "Sodium", 11, "Metal", "A soft metal. With chlorine it makes table salt (NaCl). It reacts strongly with water.", "Sec Science: metals, salts"], ["Mg", "Magnesium", 12, "Metal", "Burns with a very bright white light. Plants need magnesium to make chlorophyll, the green part of leaves.", "Science P4: plants; Sec Science: burning"], ["Al", "Aluminium", 13, "Metal", "A light metal that does not rust. Drink cans and kitchen foil are made of aluminium.", "Science P3: materials"], ["Si", "Silicon", 14, "Non-metal", "Sand and glass contain silicon. Computer chips are made from silicon.", "Science P3: materials; Computing"], ["P", "Phosphorus", 15, "Non-metal", "Found in bones and teeth. It is also in fertiliser to help plants grow.", "Science P5: body, plants"], ["S", "Sulfur", 16, "Non-metal", "A yellow solid. Rotten eggs smell of sulfur compounds.", "Sec Science: elements"], ["Cl", "Chlorine", 17, "Gas", "Kills germs in swimming pools and tap water. It is also half of table salt.", "Science P3-P4: keeping water clean"], ["K", "Potassium", 19, "Metal", "Bananas are a good source of potassium. Your muscles and plants need it.", "Science P5: body, plants"], ["Ca", "Calcium", 20, "Metal", "Makes bones and teeth strong. Milk, chalk and limestone contain calcium.", "Science P5: body systems"], ["Fe", "Iron", 26, "Metal", "Magnets attract iron. It rusts when wet. Steel is mostly iron. Your blood needs a little iron to carry oxygen.", "Science P3: magnets; P5: blood"], ["Cu", "Copper", 29, "Metal", "A shiny reddish metal that carries electricity well. Wires and some pipes are made of copper.", "Science P5: electrical circuits"], ["Zn", "Zinc", 30, "Metal", "Coats iron to stop it rusting. Mixed with copper it makes brass.", "Sec Science: alloys, rusting"], ["Ag", "Silver", 47, "Metal", "Carries electricity better than any other metal. Used in jewellery and coins.", "Science P5: conductors"], ["Sn", "Tin", 50, "Metal", "Food cans are made of steel coated with a thin layer of tin to stop rusting.", "Science P3: materials"], ["I", "Iodine", 53, "Non-metal", "A few drops of iodine turn blue-black when starch is present. It is also needed by your body in tiny amounts.", "Science P4-P5: testing for starch"], ["Au", "Gold", 79, "Metal", "Does not rust or tarnish. It is soft, shiny and rare, so it is used for jewellery.", "Science P3: materials"], ["Hg", "Mercury", 80, "Metal", "The only metal that is liquid at room temperature. Old thermometers used it, but it is poisonous.", "Science P4: heat, thermometers"], ["Pb", "Lead", 82, "Metal", "A heavy, soft metal. It is poisonous, so it is no longer used in paint or pipes.", "Sec Science: metals, health"]];
var ELMAP={copper:'Cu',iron:'Fe',silver:'Ag',gold:'Au',diamond:'C'};
function elHave(){S.st.el=S.st.el||{}; return S.st.el;}
var ELICON={"H":"🎈","He":"🎈","Li":"🔋","Be":"💎","B":"🧪","C":"💎","N":"🌬️","O":"🫧","F":"🦷","Ne":"💡","Na":"🧂","Mg":"🎆","Al":"🥫","Si":"💻","P":"🔥","S":"🌋","Cl":"🏊","Ar":"💡","K":"🍌","Ca":"🥛","Sc":"✈️","Ti":"🚲","V":"🔧","Cr":"🚗","Mn":"🔩","Fe":"🔩","Co":"🧲","Ni":"🪙","Cu":"🪙","Zn":"🔋","Ga":"🌡️","Ge":"📡","As":"☠️","Se":"📷","Br":"🧪","Kr":"💡","Rb":"⏱️","Sr":"🎇","Y":"📺","Zr":"💍","Nb":"🧲","Mo":"🔧","Tc":"🏥","Ru":"🖊️","Rh":"💍","Pd":"🚗","Ag":"🥈","Cd":"🔋","In":"📱","Sn":"🥫","Sb":"🔥","Te":"☀️","I":"🩹","Xe":"💡","Cs":"⏱️","Ba":"🩻","La":"🎬","Ce":"🔥","Pr":"🕶️","Nd":"🧲","Pm":"☢️","Sm":"🧲","Eu":"💶","Gd":"🩻","Tb":"📺","Dy":"🧲","Ho":"🧪","Er":"📡","Tm":"🩻","Yb":"⏱️","Lu":"🧪","Hf":"🚀","Ta":"📱","W":"💡","Re":"✈️","Os":"🖊️","Ir":"🛰️","Pt":"💍","Au":"🥇","Hg":"🌡️","Tl":"☠️","Pb":"⚖️","Bi":"💊","Po":"☢️","At":"☢️","Rn":"☢️","Fr":"☢️","Ra":"☢️","Ac":"☢️","Th":"⚛️","Pa":"☢️","U":"⚛️","Np":"☢️","Pu":"⚛️","Am":"🚨"}, ELFAM={alk:'🧈',ake:'🪨',trn:'⚙️',ptm:'🔩',mtl:'💻',non:'🌿',hal:'🧴',nob:'💡',lan:'🧲',act:'☢️'};
function elIcon(a){return ELICON[a[0]]||ELFAM[a[3]]||'⚗️';}
var ELG={}; ELS.forEach(function(e){ELG[e[0]]=1;});
function elTier(a){return ELG[a[0]]?0:(a[2]<=54&&a[3]!=='lan')?1:2;}
function elNeedLv(t){var m=Math.max(1,LEVELS.length-1); return t===0?0:t===1?Math.ceil(m*.4):Math.ceil(m*.7);}
function elOpen(a){return (S.level|0)>=elNeedLv(elTier(a));}
function elUnlock(sym){
var h=elHave(), left=ELALL.filter(function(a){return !h[a[0]]&&elOpen(a);}); if(!left.length)return null;
var e=sym?left.filter(function(x){return x[0]===sym;})[0]:null; if(!e){var low=left.filter(function(a){return elTier(a)===0;}); e=(low.length&&Math.random()<.5)?pick(low):pick(left);} e=[e[0],e[1]];
h[e[0]]=1; S.st.eln=(S.st.eln||0)+1; try{updateGoals();}catch(_){} sfx('done'); try{hint('New element: '+elIcon(ELALL.filter(function(x){return x[0]===e[0];})[0])+' '+e[1]+' ('+e[0]+')! See Menu, Elements.',true);}catch(_){} return e;
}
var elSel=null;
function openElements(){
if(modalOpen())return; pauseGame(); renderElements(); ob('elModal').hidden=false; try{ob('elPanel').focus({preventScroll:true});}catch(_){}
}
var ELALL=[["H","Hydrogen",1,"non",1,1],["He","Helium",2,"nob",1,18],["Li","Lithium",3,"alk",2,1],["Be","Beryllium",4,"ake",2,2],["B","Boron",5,"mtl",2,13],["C","Carbon",6,"non",2,14],["N","Nitrogen",7,"non",2,15],["O","Oxygen",8,"non",2,16],["F","Fluorine",9,"hal",2,17],["Ne","Neon",10,"nob",2,18],["Na","Sodium",11,"alk",3,1],["Mg","Magnesium",12,"ake",3,2],["Al","Aluminium",13,"ptm",3,13],["Si","Silicon",14,"mtl",3,14],["P","Phosphorus",15,"non",3,15],["S","Sulfur",16,"non",3,16],["Cl","Chlorine",17,"hal",3,17],["Ar","Argon",18,"nob",3,18],["K","Potassium",19,"alk",4,1],["Ca","Calcium",20,"ake",4,2],["Sc","Scandium",21,"trn",4,3],["Ti","Titanium",22,"trn",4,4],["V","Vanadium",23,"trn",4,5],["Cr","Chromium",24,"trn",4,6],["Mn","Manganese",25,"trn",4,7],["Fe","Iron",26,"trn",4,8],["Co","Cobalt",27,"trn",4,9],["Ni","Nickel",28,"trn",4,10],["Cu","Copper",29,"trn",4,11],["Zn","Zinc",30,"trn",4,12],["Ga","Gallium",31,"ptm",4,13],["Ge","Germanium",32,"mtl",4,14],["As","Arsenic",33,"mtl",4,15],["Se","Selenium",34,"non",4,16],["Br","Bromine",35,"hal",4,17],["Kr","Krypton",36,"nob",4,18],["Rb","Rubidium",37,"alk",5,1],["Sr","Strontium",38,"ake",5,2],["Y","Yttrium",39,"trn",5,3],["Zr","Zirconium",40,"trn",5,4],["Nb","Niobium",41,"trn",5,5],["Mo","Molybdenum",42,"trn",5,6],["Tc","Technetium",43,"trn",5,7],["Ru","Ruthenium",44,"trn",5,8],["Rh","Rhodium",45,"trn",5,9],["Pd","Palladium",46,"trn",5,10],["Ag","Silver",47,"trn",5,11],["Cd","Cadmium",48,"trn",5,12],["In","Indium",49,"ptm",5,13],["Sn","Tin",50,"ptm",5,14],["Sb","Antimony",51,"mtl",5,15],["Te","Tellurium",52,"mtl",5,16],["I","Iodine",53,"hal",5,17],["Xe","Xenon",54,"nob",5,18],["Cs","Caesium",55,"alk",6,1],["Ba","Barium",56,"ake",6,2],["La","Lanthanum",57,"lan",9,3],["Ce","Cerium",58,"lan",9,4],["Pr","Praseodymium",59,"lan",9,5],["Nd","Neodymium",60,"lan",9,6],["Pm","Promethium",61,"lan",9,7],["Sm","Samarium",62,"lan",9,8],["Eu","Europium",63,"lan",9,9],["Gd","Gadolinium",64,"lan",9,10],["Tb","Terbium",65,"lan",9,11],["Dy","Dysprosium",66,"lan",9,12],["Ho","Holmium",67,"lan",9,13],["Er","Erbium",68,"lan",9,14],["Tm","Thulium",69,"lan",9,15],["Yb","Ytterbium",70,"lan",9,16],["Lu","Lutetium",71,"lan",9,17],["Hf","Hafnium",72,"trn",6,4],["Ta","Tantalum",73,"trn",6,5],["W","Tungsten",74,"trn",6,6],["Re","Rhenium",75,"trn",6,7],["Os","Osmium",76,"trn",6,8],["Ir","Iridium",77,"trn",6,9],["Pt","Platinum",78,"trn",6,10],["Au","Gold",79,"trn",6,11],["Hg","Mercury",80,"trn",6,12],["Tl","Thallium",81,"ptm",6,13],["Pb","Lead",82,"ptm",6,14],["Bi","Bismuth",83,"ptm",6,15],["Po","Polonium",84,"ptm",6,16],["At","Astatine",85,"hal",6,17],["Rn","Radon",86,"nob",6,18],["Fr","Francium",87,"alk",7,1],["Ra","Radium",88,"ake",7,2],["Ac","Actinium",89,"act",10,3],["Th","Thorium",90,"act",10,4],["Pa","Protactinium",91,"act",10,5],["U","Uranium",92,"act",10,6],["Np","Neptunium",93,"act",10,7],["Pu","Plutonium",94,"act",10,8],["Am","Americium",95,"act",10,9],["Cm","Curium",96,"act",10,10],["Bk","Berkelium",97,"act",10,11],["Cf","Californium",98,"act",10,12],["Es","Einsteinium",99,"act",10,13],["Fm","Fermium",100,"act",10,14],["Md","Mendelevium",101,"act",10,15],["No","Nobelium",102,"act",10,16],["Lr","Lawrencium",103,"act",10,17],["Rf","Rutherfordium",104,"trn",7,4],["Db","Dubnium",105,"trn",7,5],["Sg","Seaborgium",106,"trn",7,6],["Bh","Bohrium",107,"trn",7,7],["Hs","Hassium",108,"trn",7,8],["Mt","Meitnerium",109,"trn",7,9],["Ds","Darmstadtium",110,"trn",7,10],["Rg","Roentgenium",111,"trn",7,11],["Cn","Copernicium",112,"trn",7,12],["Nh","Nihonium",113,"ptm",7,13],["Fl","Flerovium",114,"ptm",7,14],["Mc","Moscovium",115,"ptm",7,15],["Lv","Livermorium",116,"ptm",7,16],["Ts","Tennessine",117,"hal",7,17],["Og","Oganesson",118,"nob",7,18]];
var ELCOL={alk:['#ffb4a2','#7a1f10'],ake:['#ffd88a','#6b4a00'],trn:['#ffb8d9','#7a1250'],ptm:['#9fe0c8','#0b4f3a'],mtl:['#7fd6dd','#0a4a52'],non:['#b5e68a','#2f5a0a'],hal:['#d6b8f5','#4a1f7a'],nob:['#a9c6ff','#17327a'],lan:['#ffe4a8','#6b4a00'],act:['#f2c7e6','#6b1a52']};
var ELLEG=[['alk','Alkali metals'],['lan','Lanthanides'],['act','Actinides'],['ake','Alkaline earth'],['trn','Transition metals'],['ptm','Other metals'],['mtl','Metalloid'],['non','Non-metals'],['hal','Halogens'],['nob','Noble gases']];
function renderElements(){
var h=elHave(), n=Object.keys(h).length, g=ob('elGrid'); g.innerHTML=''; ob('elCount').textContent=n+' of '+ELALL.length+' collected. Higher levels unlock rarer elements (🔒). Tap any element to read about it.';
g.style.cssText='display:grid;grid-template-columns:repeat(18,36px);grid-auto-rows:42px;gap:3px;min-width:690px';
var wrap=g.parentNode; if(wrap&&wrap.id!=='elScroll'){var w=document.createElement('div'); w.id='elScroll'; w.style.cssText='overflow-x:auto;padding-bottom:6px;margin-bottom:6px'; wrap.insertBefore(w,g); w.appendChild(g);}
var game={}; ELS.forEach(function(e){game[e[0]]=e;});
ELALL.forEach(function(a){var sym=a[0], col=ELCOL[a[3]], got=!!h[sym], op=elOpen(a), b=document.createElement('button'); b.type='button';
b.style.cssText='grid-row:'+a[4]+';grid-column:'+a[5]+';display:grid;place-items:center;padding:0;line-height:1.05;border-radius:6px;border:2px solid '+col[1]+';background:'+col[0]+';color:'+col[1]+';cursor:pointer;font:inherit;'+(got?'box-shadow:0 0 0 2px #fff,0 0 0 4px '+col[1]+';':op?'opacity:.8;':'opacity:.4;');
b.setAttribute('aria-label',got?a[1]+', atomic number '+a[2]:op?'Element to find':'Locked until a higher level');
b.innerHTML=got?''+a[2]+' '+elIcon(a)+''+sym+'':''+(op?'?':'🔒')+'';
b.setAttribute('data-s',sym); if(elSel===sym)b.style.outline='3px solid #222';
b.addEventListener('click',function(){elSel=sym; renderElements();}); g.appendChild(b);});
var gap=document.createElement('div'); gap.style.cssText='grid-row:8;grid-column:1/19;height:10px'; g.appendChild(gap);
var lg=ob('elLegend'); if(!lg){lg=document.createElement('div'); lg.id='elLegend'; lg.style.cssText='display:flex;flex-wrap:wrap;gap:6px 10px;margin:4px 0 10px;font-size:.78rem;font-weight:700'; ob('elScroll').parentNode.insertBefore(lg,ob('elScroll').nextSibling);}
lg.innerHTML=''; ELLEG.forEach(function(x){var sp=document.createElement('span'); sp.style.cssText='display:inline-flex;align-items:center;gap:4px'; sp.innerHTML=''+x[1]; lg.appendChild(sp);});
showElement();
}
function showElement(){
var d=ob('elInfo'), h=elHave(), e=ELS.filter(function(x){return x[0]===elSel;})[0]; var al=ELALL.filter(function(x){return x[0]===elSel;})[0]; if(!e&&al){var c0=ELCOL[al[3]]; d.style.borderLeftColor=c0[1]; d.style.background=c0[0]+'55';
if(!h[al[0]]){var op0=elOpen(al); d.innerHTML=op0?'Not found yet. Keep answering Science questions and finding rare blocks. Hint: it is in the '+(ELLEG.filter(function(x){return x[0]===al[3];})[0]||[0,'periodic table'])[1].toLowerCase()+' family.':'Locked. Reach a higher maths level to find this element. Use Power up, Settings to change level, or keep getting 5 correct in a row to be offered Level up.'; return;}
var fam=(ELLEG.filter(function(x){return x[0]===al[3];})[0]||[0,''])[1], per=al[4]>=9?(al[4]===9?6:7):al[4];
d.innerHTML=''+elIcon(al)+''+al[1]+' ('+al[0]+'), atomic number '+al[2]+'. Family: '+fam+'. It is in period '+per+' of the periodic table. Atomic number = number of protons in each atom.'; return;} if(!e){d.textContent='Tap an element to read about it. Find elements by answering Science questions and finding rare blocks.'; return;}
if(!h[e[0]]){d.innerHTML='Locked. Keep answering Science questions and finding rare blocks to discover this element. Hint: it is a '+e[3].toLowerCase()+'.'; return;}
var cc=ELCOL[al?al[3]:'non']; d.style.borderLeftColor=cc[1]; d.style.background=cc[0]+'55'; d.innerHTML=''+elIcon(al)+''+e[1]+' ('+e[0]+'), atomic number '+e[2]+', '+e[3].toLowerCase()+'. '+e[4]+' Links to: '+e[5]+'';
}
/* ---------- Music studio: chords, lyrics, melody ---------- */
var MSCH=[['C','I',[60,64,67]],['Dm','ii',[62,65,69]],['Em','iii',[64,67,71]],['F','IV',[65,69,72]],['G','V',[67,71,74]],['Am','vi',[69,72,76]]];
var MSQ=[
['Build the classic I - IV - V - I.',function(p){return p.join()==='C,F,G,C';},'I-IV-V-I is the most famous chord pattern. It leaves home, travels away and comes back.'],
['Make a progression that ends on the home chord (I) so it sounds finished.',function(p){return p.length===4&&p[3]==='C'&&p[0]!=='C';},'Ending on chord I (the home chord, C) feels settled and finished.'],
['Use the popular pattern I - V - vi - IV.',function(p){return p.join()==='C,G,Am,F';},'I-V-vi-IV is used in hundreds of pop songs.'],
['Start on I and end on V so it sounds like a question that is not finished.',function(p){return p[0]==='C'&&p[3]==='G';},'Chord V wants to go back home to I, so ending on V sounds unfinished.'],
['Use at least three different chords and start and end on C.',function(p){var u={}; p.forEach(function(c){u[c]=1;}); return p[0]==='C'&&p[3]==='C'&&Object.keys(u).length>=3;},'Different chords add colour. Starting and ending on C keeps it grounded.']
];
var MSPT=[['C',60],['D',62],['E',64],['G',67],['A',69]];
var MSM=[
['Make a melody that rises: the last note must be higher than the first note.',function(m){var f=m.filter(function(x){return x>=0;}); return f.length>=4&&m[m.length-1]>=0&&m[0]>=0&&MSPT[m[m.length-1]][1]>MSPT[m[0]][1];},'A rising melody goes up in pitch from start to end.'],
['Use at least 6 of the 8 steps and end on C (the home note).',function(m){var f=m.filter(function(x){return x>=0;}); return f.length>=6&&m[7]===0;},'Ending on the home note C sounds finished.'],
['Make a repeating pattern: steps 1 to 4 must match steps 5 to 8.',function(m){var ok=true,any=false; for(var i=0;i<4;i++){if(m[i]!==m[i+4])ok=false; if(m[i]>=0)any=true;} return ok&&any;},'Repeating a short pattern makes a tune easy to remember.'],
['Make a melody that goes up and then comes back down: the middle is higher than both ends.',function(m){var a=m[0],z=m[7]; if(a<0||z<0)return false; var mx=-1; for(var i=1;i<7;i++)if(m[i]>mx)mx=m[i]; return mx>a&&mx>z;},'A melody that rises then falls makes an arch shape.']
];
var MSRH=[['light','night'],['tree','bee'],['rain','train'],['moon','spoon'],['star','car'],['sky','fly'],['bell','well'],['cake','lake'],['sing','ring'],['day','play'],['bear','hair'],['boat','coat'],['fish','dish'],['snow','glow'],['book','cook']];
var MSSY=[['cat',1],['rain',1],['apple',2],['rainbow',2],['banana',3],['elephant',3],['butterfly',3],['umbrella',3],['computer',3],['pineapple',3],['watermelon',4],['caterpillar',4],['helicopter',4],['hippopotamus',5]];
var MSLN=[['I look up at the silver moon','I will see you very soon','I put my toys in the box'],['We sing a happy song all day','Let us go outside and play','The cat sat on the mat'],['The rain falls down on the tree','Come and sing along with me','The sun is hot today'],['A twinkle in the sky so high','Birds are flying up to fly','I eat rice for lunch']];
var MSTAB='chord', MSST=null;
var MSCOLN={C:'#e5484d',Dm:'#e07a00',Em:'#b88a00',F:'#2e9e5b',G:'#2f7fd1',Am:'#8e5bd1'}, MSPC=['#e5484d','#e07a00','#2e9e5b','#2f7fd1','#8e5bd1'], MSSIC=['\u25AC','\u2669\u2669\u2669\u2669','\u2669 \u2669 \u266A','\u301C'];
var MSIC={'Play':'\u25B6','Undo':'\u21B6','Check':'\u2714','New goal':'\uD83C\uDFB2','Clear':'\uD83D\uDDD1','Slower/Faster':'\uD83D\uDC22\uD83D\uDC07'};
function msIcon(sym,label,cls,fn){var b=document.createElement('button'); b.type='button'; b.className=cls||'btn'; b.innerHTML=''+sym+''+(label==='Slower/Faster'?'Speed':label)+''; b.setAttribute('aria-label',label); b.title=label; b.addEventListener('click',fn); return b;}
function noteSvg(i,h){var P=['','','','']; return '';}
function msAward(msg){S.st.msn=(S.st.msn||0)+1; var got=roll(8); Object.keys(got).forEach(function(id){inv[id]=(inv[id]||0)+1*got[id];}); sfx('done'); ob('msMsg').textContent=msg+' +8 blocks!'; changed();}
function openStudio(){ if(modalOpen())return; pauseGame(); MSST=null; msRender(); ob('msModal').hidden=false; try{ob('msPanel').focus({preventScroll:true});}catch(_){} }
function msTabs(){var t=ob('msTabs'); t.innerHTML=''; [['chord','Chords','\uD83C\uDFB9'],['rhy','Rhythm','\uD83E\uDD41'],['lyric','Lyrics','\uD83C\uDFA4'],['mel','Melody','\uD83C\uDFB5']].forEach(function(x){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.innerHTML=''+x[2]+''+x[1]+''; b.setAttribute('aria-label',x[1]); b.setAttribute('aria-pressed',String(MSTAB===x[0])); b.addEventListener('click',function(){MSTAB=x[0]; MSST=null; msRender();}); t.appendChild(b);});}
function msRender(){ msTabs(); ob('msMsg').textContent=''; var a=ob('msArea'); a.innerHTML=''; if(MSTAB==='chord')msChord(a); else if(MSTAB==='rhy')msRhy(a); else if(MSTAB==='lyric')msLyric(a); else msMel(a); }
var MSSTR=[['Hold','One long chord each bar'],['Pulse','4 beats: 1 2 3 4'],['Pop','Beats 1 and 3, with a lift on 4'],['Harp','Notes one by one (arpeggio)']], MSSTI=0;
function chordPlay(c,when,dur){c[2].forEach(function(m){msKey(hz(m),dur,.045,when,'ep');}); msKey(hz(c[2][0]-12),dur,.07,when,'ep');}
function chordBar(c,t0,beat,mode){
if(mode===0){chordPlay(c,t0,beat*3.6); return;}
if(mode===1){for(var i=0;i<4;i++)chordPlay(c,t0+i*beat,beat*.8); return;}
if(mode===2){chordPlay(c,t0,beat*1.8); chordPlay(c,t0+2*beat,beat*1.2); chordPlay(c,t0+3.5*beat,beat*.5); return;}
msKey(hz(c[2][0]-12),beat*3,.06,t0,'ep'); for(var j=0;j<8;j++)msKey(hz(c[2][[0,1,2,1][j%4]]+(j>=4?12:0)),beat*.9,.06,t0+j*beat*.5,'ep');
}
function msChord(a){
if(!MSST||MSST.k!=='chord'){MSST={k:'chord',q:pick(MSQ),p:[],done:false};}
var q=MSST.q; var d=document.createElement('p'); d.className='lvldesc'; d.textContent='Key of C major. '+q[0]+' Tap chords to fill the 4 bars.'; a.appendChild(d);
var bars=document.createElement('div'); bars.style.cssText='display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin:8px 0'; for(var i=0;i<4;i++){var b=document.createElement('div'); b.className='labslot'; b.style.cssText='min-height:56px;display:grid;place-items:center;font-weight:800;border:2px dashed var(--line);border-radius:8px'; var c=MSST.p[i]; b.innerHTML=c?(''+c+''+MSCH.filter(function(x){return x[0]===c;})[0][1]+''):''+(i+1)+''; if(c){b.style.background=MSCOLN[c]; b.style.color='#fff'; b.style.border='0';} bars.appendChild(b);} a.appendChild(bars);
var row=document.createElement('div'); row.className='labrow'; MSCH.forEach(function(c){var b=document.createElement('button'); b.type='button'; b.className='labtile'; b.style.cssText='background:'+MSCOLN[c[0]]+';color:#fff;min-width:66px;min-height:66px;font-size:1.3rem;border:0;border-radius:14px;text-align:center'; b.setAttribute('aria-label','Chord '+c[0]); b.innerHTML=''+c[0]+' '+c[1]+''; b.addEventListener('click',function(){ if(MSST.done||MSST.p.length>=4)return; MSST.p.push(c[0]); chordPlay(c,0,.5); msRender2(); }); row.appendChild(b);}); a.appendChild(row);
var sr=document.createElement('div'); sr.className='seg'; sr.style.margin='6px 0'; var sl=document.createElement('p'); sl.className='lvldesc'; sl.textContent='Rhythm pattern: '+MSSTR[MSSTI][1]; MSSTR.forEach(function(x,i){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.innerHTML=''+MSSIC[i]+''+x[0]+''; b.setAttribute('aria-label',x[1]); b.title=x[1]; b.setAttribute('aria-pressed',String(MSSTI===i)); b.addEventListener('click',function(){MSSTI=i; msRender2(); if(S.sound&&MSST.p.length){MSST.p.slice(0,1).forEach(function(n){chordBar(MSCH.filter(function(y){return y[0]===n;})[0],0,.5,MSSTI);});}}); sr.appendChild(b);}); a.appendChild(sl); a.appendChild(sr);
var r2=document.createElement('div'); r2.className='row2'; [['Play',function(){ if(!S.sound){hint('Turn Sound on to listen.',true); return;} MSST.p.forEach(function(n,i){var c=MSCH.filter(function(x){return x[0]===n;})[0]; chordBar(c,i*2,.5,MSSTI);}); }],['Undo',function(){MSST.p.pop(); msRender2();}],['Check',function(){ if(MSST.done)return; if(MSST.p.length<4){ob('msMsg').textContent='Fill all 4 bars first.'; return;} if(q[1](MSST.p)){MSST.done=true; msAward('Great! '+q[2]);} else {ob('msMsg').textContent='Not quite. Read the goal again and try other chords.'; sfx('wrong');}}],['New goal',function(){MSST=null; msRender();}]].forEach(function(x){r2.appendChild(msIcon(MSIC[x[0]],x[0],x[0]==='Check'?'btn primary':'btn',x[1]));}); a.appendChild(r2);
}
function msRender2(){var m=ob('msMsg').textContent; var keep=MSST; msTabs(); var a=ob('msArea'); a.innerHTML=''; if(MSTAB==='chord')msChord(a); else if(MSTAB==='rhy')msRhy(a); else if(MSTAB==='mel')msMel(a); ob('msMsg').textContent=m;}
function msLyric(a){
if(!MSST||MSST.k!=='lyric'){var t=pick(['rhyme','syl','line']); MSST={k:'lyric',t:t,done:false};}
var t=MSST.t, d=document.createElement('p'), ans, opts, why; d.className='lvldesc';
if(t==='rhyme'){var pr=pick(MSRH), flip=Math.random()<.5, w=flip?pr[1]:pr[0]; ans=flip?pr[0]:pr[1]; var wr=shuffle(MSRH.filter(function(x){return x!==pr;}).map(function(x){return x[Math.floor(Math.random()*2)];})).slice(0,3); opts=[ans].concat(wr); d.textContent='Songs often end lines with rhyming words. Which word rhymes with "'+w+'"?'; why='"'+ans+'" and "'+w+'" end with the same sound, so they rhyme.';}
else if(t==='syl'){var sw=pick(MSSY); ans=String(sw[1]); opts=['1','2','3','4','5'].filter(function(x){return x!==ans;}); opts=shuffle(opts).slice(0,3).concat([ans]); d.textContent='In a song each syllable usually gets one note. How many syllables (beats) are in "'+sw[0]+'"? Clap it to check.'; why='"'+sw[0]+'" has '+ans+' syllable'+(ans==='1'?'':'s')+', so it needs '+ans+' note'+(ans==='1'?'':'s')+'.';}
else {var L=pick(MSLN); ans=L[1]; opts=[L[1],L[2]]; var oth=pick(MSLN.filter(function(x){return x!==L;})); opts.push(oth[2]); opts.push(oth[0]); d.textContent='Pick the line that rhymes with: "'+L[0]+'"'; why='The last words rhyme, which makes the verse fit together.';}
a.appendChild(d);
var row=document.createElement('div'); row.className='labrow'; shuffle(opts.slice()).forEach(function(o){var b=document.createElement('button'); b.type='button'; b.className='labtile'; b.textContent=o; b.addEventListener('click',function(){ if(MSST.done)return; if(o===ans){MSST.done=true; b.style.borderColor='#2e9e5b'; msAward('Correct! '+why);} else {b.disabled=true; ob('msMsg').textContent='Not quite. Try another.'; sfx('wrong');}}); row.appendChild(b);}); a.appendChild(row);
var w=document.createElement('p'); w.className='lvldesc'; w.textContent='Write your own two lines for a song:'; a.appendChild(w);
var ta=document.createElement('textarea'); ta.maxLength=120; ta.rows=2; ta.style.cssText='width:100%;font:inherit;padding:6px;border:2px solid var(--line);border-radius:8px'; ta.value=S.ms&&S.ms.lyric||''; ta.setAttribute('aria-label','Your song lines'); ta.addEventListener('input',function(){S.ms=S.ms||{}; S.ms.lyric=ta.value.slice(0,120);}); a.appendChild(ta);
var r2=document.createElement('div'); r2.className='row2'; var nb=document.createElement('button'); nb.type='button'; nb.className='btn'; nb.innerHTML='\u27A1Next'; nb.setAttribute('aria-label','Next question'); nb.addEventListener('click',function(){MSST=null; msRender();}); r2.appendChild(nb); a.appendChild(r2);
}
function msMel(a){
if(!MSST||MSST.k!=='mel'){MSST={k:'mel',q:pick(MSM),m:(S.ms&&S.ms.tune&&S.ms.tune.length===8)?S.ms.tune.slice():[-1,-1,-1,-1,-1,-1,-1,-1],done:false,bpm:100};}
var q=MSST.q, d=document.createElement('p'); d.className='lvldesc'; d.textContent='Tap squares to place notes (one per step). Notes: C D E G A sound good together. Goal: '+q[0]; a.appendChild(d);
var g=document.createElement('div'); g.style.cssText='display:grid;grid-template-columns:34px repeat(8,1fr);gap:4px;margin:8px 0';
for(var r=4;r>=0;r--){var lb=document.createElement('div'); lb.textContent=MSPT[r][0]; lb.style.cssText='display:grid;place-items:center;font-weight:800;color:#fff;border-radius:6px;background:'+MSPC[r]; g.appendChild(lb); for(var st=0;st<8;st++){(function(r,st){var c=document.createElement('button'); c.type='button'; var on=MSST.m[st]===r; c.style.cssText='min-height:38px;border-radius:6px;border:2px solid var(--line);background:'+(on?MSPC[r]:'var(--panel)')+';color:#fff;font-size:1.3rem;font-weight:800'; c.textContent=on?'\u266A':''; c.setAttribute('aria-label','Step '+(st+1)+' note '+MSPT[r][0]+(on?' on':' off')); c.addEventListener('click',function(){ if(MSST.done)return; MSST.m[st]=(MSST.m[st]===r)?-1:r; if(MSST.m[st]>=0)msKey(hz(MSPT[r][1]),.7,.14,0,'pn'); S.ms=S.ms||{}; S.ms.tune=MSST.m.slice(); msRender2(); }); g.appendChild(c);})(r,st);}}
a.appendChild(g);
var r2=document.createElement('div'); r2.className='row2'; [['Play',function(){ if(!S.sound){hint('Turn Sound on to listen.',true); return;} playMelody(MSST.m.map(function(x){return [x>=0?MSPT[x][1]:0,1];}),MSST.bpm); }],['Slower/Faster',function(){MSST.bpm=MSST.bpm===100?150:MSST.bpm===150?70:100; ob('msMsg').textContent='Tempo: '+(MSST.bpm===70?'slow':MSST.bpm===100?'medium':'fast');}],['Clear',function(){MSST.m=[-1,-1,-1,-1,-1,-1,-1,-1]; S.ms=S.ms||{}; S.ms.tune=MSST.m.slice(); msRender2();}],['Check',function(){ if(MSST.done)return; if(q[1](MSST.m)){MSST.done=true; msAward('Lovely tune! '+q[2]);} else {ob('msMsg').textContent='Not quite. Re-read the goal and change some notes.'; sfx('wrong');}}],['New goal',function(){var tn=MSST.m; MSST=null; S.ms=S.ms||{}; S.ms.tune=tn; msRender();}]].forEach(function(x){r2.appendChild(msIcon(MSIC[x[0]],x[0],x[0]==='Check'?'btn primary':'btn',x[1]));}); a.appendChild(r2);
}
/* ---------- Exam question: type your own answers, see working after ---------- */
var EXQ=null, EXTOPIC='all', EXMODE='step';
function exS(q,ans,unit,why,part,m){return {q:q,ans:ans,unit:unit||'',why:why,part:part||'',m:m||0};}
function exMoney(n){return '$'+(Math.round(n*100)/100).toFixed(2).replace(/\.00$/,'');}
function gcd2(a,b){return b?gcd2(b,a%b):a;}
var EXT={
'Ratio':function(){
var P=[[3,2,2,1],[5,3,3,2],[4,3,2,1],[5,2,3,2],[7,3,3,2],[2,1,3,4]];var p,a,b,c,d,L,u,t1,t2;
do{p=pick(P);a=p[0];b=p[1];c=p[2];d=p[3];t1=a+b;t2=c+d;L=t1*t2/gcd2(t1,t2);}while(a*L/t1<=c*L/t2);
var A=pick(WPN),B=pick(WPN.filter(function(x){return x!==A;})),it=pick(WPI),u2=rnd(2,6),a1=a*L/t1,a2=c*L/t2,diff=a1-a2,g=diff*u2,tot=L*u2;
return {topic:'Ratio',fig:'',intro:A+' and '+B+' had '+it+' in the ratio '+a+' : '+b+'. After '+A+' gave '+g+' '+it+' to '+B+', the ratio of '+A+'’s '+it+' to '+B+'’s '+it+' became '+c+' : '+d+'.',
steps:[exS('The total number of '+it+' does not change. Before, the total is '+t1+' units. After, how many units is the total?',t2,'units','After the ratio is '+c+' : '+d+', the total is '+c+' + '+d+' = '+t2+' units.'),
exS('Make the total the same. What is the smallest common number of units for '+t1+' and '+t2+'?',L,'units','Lowest common multiple of '+t1+' and '+t2+' is '+L+'. Total = '+L+' new units.'),
exS('Before, '+A+' had '+a+' out of '+t1+' parts. How many of the '+L+' new units is that?',a1,'units',a+' × '+(L/t1)+' = '+a1+' units.'),
exS('After, '+A+' had '+c+' out of '+t2+' parts. How many of the '+L+' new units is that?',a2,'units',c+' × '+(L/t2)+' = '+a2+' units.'),
exS(A+' gave away '+g+' '+it+'. How many units did '+A+' give away?',diff,'units',a1+' − '+a2+' = '+diff+' units.'),
exS(diff+' units = '+g+' '+it+'. What is 1 unit?',u2,it,g+' ÷ '+diff+' = '+u2+'.'),
exS('(a) How many '+it+' did they have altogether?',tot,it,L+' units × '+u2+' = '+tot+'.','(a)',2),
exS('(b) How many '+it+' did '+A+' have at first?',a1*u2,it,a1+' units × '+u2+' = '+(a1*u2)+'.','(b)',2)]};},
'Percentage':function(){
var pr=rnd(4,30)*10,dp=pick([10,20,25,30]),gst=9,after=pr*(100-dp)/100,fin=Math.round(after*109)/100,it=pick(['bag','jacket','watch','pair of shoes','bicycle']);
if(Math.round(after*109)%1)return EXT.Percentage();
return {topic:'Percentage',fig:'',intro:'A '+it+' was sold for $'+pr+'. In a sale, the shop gave a discount of '+dp+'%. A GST of '+gst+'% is then added to the sale price.',
steps:[exS('How much is the discount in dollars?',pr*dp/100,'$',dp+'% of $'+pr+' = '+dp+' ÷ 100 × '+pr+' = $'+(pr*dp/100)+'.'),
exS('What is the sale price before GST?',after,'$','$'+pr+' − $'+(pr*dp/100)+' = $'+after+'.','(a)',2),
exS('What is the GST in dollars? GST is '+gst+'% of the sale price.',after*gst/100,'$',gst+'% of $'+after+' = $'+(after*gst/100)+'.'),
exS('(b) What is the final price including GST?',fin,'$','$'+after+' + $'+(after*gst/100)+' = $'+fin+'. (Or 109% × $'+after+'.)','(b)',2)]};},
'Percentage ':function(){
var T=rnd(5,24)*100,p1=pick([20,25,30,40]),p2=pick([20,25,50]);var r1=T*(100-p1)/100,s2=r1*p2/100,left=r1-s2;var N=pick(WPN);
return {topic:'Percentage',fig:'',intro:N+' had $'+T+'. '+N+' spent '+p1+'% of it on rent. Then '+N+' spent '+p2+'% of the remainder on food. The rest was saved.',
steps:[exS('How much was spent on rent?',T*p1/100,'$',p1+'% of $'+T+' = $'+(T*p1/100)+'.'),
exS('How much was left after paying rent?',r1,'$','$'+T+' − $'+(T*p1/100)+' = $'+r1+'.'),
exS('How much was spent on food? ('+p2+'% of what was left)',s2,'$',p2+'% of $'+r1+' = $'+s2+'.','(a)',2),
exS('(b) How much did '+N+' save?',left,'$','$'+r1+' − $'+s2+' = $'+left+'.','(b)',2)]};},
'Fractions':function(){
var F=[[2,5,1,3],[1,4,2,3],[3,8,1,5],[1,3,3,4],[2,7,1,5]];var f=pick(F),n1=f[0],d1=f[1],n2=f[2],d2=f[3];
var unit=rnd(2,9)*pick([2,3,5]); var T=d1*d2*unit; var s1=T*n1/d1, r1=T-s1, s2=r1*n2/d2, L=r1-s2; if(T>3000)return EXT.Fractions(); var N=pick(WPN);
return {topic:'Fractions',fig:'',intro:N+' spent '+n1+'/'+d1+' of her money on a book. She then spent '+n2+'/'+d2+' of the remainder on lunch. She had $'+L+' left.',
steps:[exS('What fraction of her money was left after buying the book? Write the top number over '+d1+'. (Type the top number.)',d1-n1,'/'+d1,'1 − '+n1+'/'+d1+' = '+(d1-n1)+'/'+d1+'.'),
exS('Of the remainder, she spent '+n2+'/'+d2+'. What fraction of the remainder was left after lunch? (Type the top number over '+d2+'.)',d2-n2,'/'+d2,'1 − '+n2+'/'+d2+' = '+(d2-n2)+'/'+d2+'.'),
exS('Think of the money left after the book as 1 whole of '+d2+' units. Lunch took '+n2+' units. How many units are left?',d2-n2,'units',d2+' − '+n2+' = '+(d2-n2)+' units.'),
exS('$'+L+' = '+(d2-n2)+' units. How much is 1 unit?',L/(d2-n2),'$','$'+L+' ÷ '+(d2-n2)+' = $'+(L/(d2-n2))+'.'),
exS('(a) How much money was left after buying the book?',r1,'$',d2+' units × $'+(L/(d2-n2))+' = $'+r1+'.','(a)',2),
exS('(b) How much money did she have at first?',T,'$','After the book, '+(d1-n1)+'/'+d1+' was left = $'+r1+'. 1/'+d1+' = $'+(r1/(d1-n1))+'. '+d1+'/'+d1+' = $'+T+'.','(b)',2)]};},
'Average':function(){
var n=rnd(4,8),av=rnd(40,90),x=rnd(1,15)*5,sum=n*av,newav=(sum+x)/(n+1); if(newav!==Math.round(newav*10)/10)return EXT.Average(); var N=pick(WPN);
return {topic:'Average',fig:'',intro:'The average mark of '+n+' pupils in a quiz was '+av+'. When '+N+' joined the group, the average of all '+(n+1)+' pupils became '+newav+'.',
steps:[exS('What was the total mark of the first '+n+' pupils?',sum,'marks',n+' × '+av+' = '+sum+'.'),
exS('What is the total mark of all '+(n+1)+' pupils?',newav*(n+1),'marks',(n+1)+' × '+newav+' = '+(newav*(n+1))+'.'),
exS('(a) What mark did '+N+' score?',x,'marks',(newav*(n+1))+' − '+sum+' = '+x+'.','(a)',2),
exS('(b) By how many marks is '+N+'’s mark above the old average of '+av+'? (Type a negative number if below.)',x-av,'marks',x+' − '+av+' = '+(x-av)+'.','(b)',2)]};},
'Speed':function(){
var s1=pick([40,50,60]),s2=pick([30,40,45,60]),t1=pick([1,2,3]),catch_=rnd(1,4),sp2=s1+pick([10,20]),head=s1*t1;
var gap=sp2-s1,tt=head/gap; if(tt!==Math.round(tt))return EXT.Speed(); var N1=pick(WPN),N2=pick(WPN.filter(function(x){return x!==N1;})),dist=sp2*tt;
return {topic:'Speed',fig:'',intro:N1+' left town A and cycled along a road at '+s1+' km/h. '+t1+' hour'+(t1>1?'s':'')+' later, '+N2+' left town A and rode the same way at '+sp2+' km/h.',
steps:[exS('How far ahead was '+N1+' when '+N2+' started?',head,'km',s1+' × '+t1+' = '+head+' km.'),
exS('Each hour '+N2+' gains on '+N1+'. How many km does '+N2+' gain in 1 hour?',gap,'km',sp2+' − '+s1+' = '+gap+' km.'),
exS('(a) How many hours after leaving did '+N2+' catch up with '+N1+'?',tt,'h',head+' ÷ '+gap+' = '+tt+' h.','(a)',2),
exS('(b) How far from town A did they meet?',dist,'km',sp2+' × '+tt+' = '+dist+' km.','(b)',2)]};},
'Algebra':function(){
var x=rnd(3,15),m=rnd(2,8),k=rnd(2,4),A=pick(WPN),B=pick(WPN.filter(function(y){return y!==A;})),C=pick(WPN.filter(function(y){return y!==A&&y!==B;})),it=pick(WPI);
var a=x,b=x+m,c=k*b,tot=a+b+c;
return {topic:'Algebra',fig:'',intro:A+' had x '+it+'. '+B+' had '+m+' more '+it+' than '+A+'. '+C+' had '+k+' times as many as '+B+'. Altogether they had '+tot+' '+it+'.',
steps:[exS('Write '+B+'’s '+it+' in terms of x. How many x does '+B+' have? (Type the number of x.)',1,'x','x + '+m+' means 1 x and '+m+' more.'),
exS(C+' has '+k+' times '+B+'’s. How many x does '+C+' have?',k,'x',k+' × (x + '+m+') = '+k+'x + '+(k*m)+'.'),
exS('Altogether the x terms: x + (x + '+m+') + ('+k+'x + '+(k*m)+'). How many x in all?',k+2,'x','1 + 1 + '+k+' = '+(k+2)+' x.'),
exS('The plain numbers add up to '+m+' + '+(k*m)+'. What is that?',m+k*m,'',m+' + '+(k*m)+' = '+(m+k*m)+'.'),
exS('(a) '+(k+2)+'x + '+(m+k*m)+' = '+tot+'. What is x?',x,'','('+tot+' − '+(m+k*m)+') ÷ '+(k+2)+' = '+x+'.','(a)',2),
exS('(b) How many '+it+' did '+C+' have?',c,it,k+' × ('+x+' + '+m+') = '+c+'.','(b)',2)]};},
'Area':function(){
var W=rnd(8,16),H=rnd(8,14),cw=rnd(3,W-4),ch=rnd(3,H-4),pw=W*H-cw*ch;
var inner='';
var X0=48,Y0=20,fw=130,fh=88,cx=Math.round(fw*cw/W),cy=Math.round(fh*ch/H);
inner='';
inner+=''+W+' cm'+H+' cm'+cw+' cm'+ch+' cm';
return {topic:'Area',fig:svgWrap(inner),intro:'The figure is made of one big rectangle with a small rectangle cut from a corner. All corners are right angles. All lengths are in cm.',
steps:[exS('What is the area of the big rectangle, '+W+' cm by '+H+' cm, in cm²?',W*H,'cm²',W+' × '+H+' = '+(W*H)+'.'),
exS('What is the area of the small rectangle cut away, '+cw+' cm by '+ch+' cm?',cw*ch,'cm²',cw+' × '+ch+' = '+(cw*ch)+'.'),
exS('(a) What is the area of the figure in cm²?',pw,'cm²',(W*H)+' − '+(cw*ch)+' = '+pw+'.','(a)',2),
exS('(b) What is the perimeter of the figure in cm? (It is the same as the big rectangle’s perimeter.)',2*(W+H),'cm','Cutting a corner does not change the perimeter. 2 × ('+W+' + '+H+') = '+(2*(W+H))+'.','(b)',2)]};},
'Rate':function(){
var r=pick([8,10,12,15]),dr=pick([2,3,4,5]),T=pick([10,12,15,20]),t0=rnd(2,5),C=r*T*1+0; var cap=(r-dr)*T+r*t0; if(cap%r)return EXT.Rate();
return {topic:'Rate',fig:'',intro:'An empty tank is filled by a tap at '+r+' litres per minute. After '+t0+' minutes, a drain at the bottom is opened. It lets out '+dr+' litres per minute. The tap stays on. The tank is full '+T+' minutes after the drain was opened.',
steps:[exS('How much water was in the tank when the drain was opened?',r*t0,'litres',r+' × '+t0+' = '+(r*t0)+' litres.'),
exS('After the drain opened, how many litres does the tank gain each minute?',r-dr,'litres',r+' − '+dr+' = '+(r-dr)+' litres.'),
exS('How much water was added in the '+T+' minutes?',(r-dr)*T,'litres',(r-dr)+' × '+T+' = '+((r-dr)*T)+' litres.'),
exS('(a) What is the capacity of the tank in litres?',cap,'litres',(r*t0)+' + '+((r-dr)*T)+' = '+cap+' litres.','(a)',2),
exS('(b) How long, in minutes, would the tap alone need to fill the empty tank?',cap/r,'min',cap+' ÷ '+r+' = '+(cap/r)+' min.','(b)',2)]};},
'Angles':function(){
var y=pick([20,30,40,50,60,70,80]), side=52, X0=90, Y0=58, D=[X0,Y0], C=[X0+side,Y0], A=[X0,Y0+side], B=[X0+side,Y0+side], r=y*Math.PI/180, E=[X0+side*Math.cos(r),Y0-side*Math.sin(r)];
function f(p){return fN(p[0])+' '+fN(p[1]);}
function T(p,t,dx,dy,q){return ''+t+'';}
var inner=''+ra(X0,Y0+side,1,-1)+T(A,'A',-10,6)+T(B,'B',10,6)+T(C,'C',12,4)+T(D,'D',-12,4)+T(E,'E',6,-9)+T(D,y+'°',24,-12)+T([E[0],E[1]],'',0,0);
var ang1=(180-y)/2, adeA=90+y, dae=(180-adeA)/2;
return {topic:'Angles',fig:svgWrap(inner),figAlt:'Square ABCD with an isosceles triangle DCE drawn on side DC. DE equals DC and angle CDE is '+y+' degrees.',intro:'ABCD is a square. DE = DC, and angle CDE is '+y+'°. Triangle ADE is also isosceles.',
steps:[exS('Triangle DCE has DE = DC. The angles DCE and DEC are equal. What is their sum?',180-y,'degrees','Angles in a triangle add up to 180°. 180 − '+y+' = '+(180-y)+'°.'),
exS('(a) What is angle DCE?',ang1,'degrees',(180-y)+' ÷ 2 = '+ang1+'°.','(a)',2),
exS('What is angle ADE? (Angle ADC is a right angle, 90°, and angle CDE is '+y+'°.)',adeA,'degrees','90 + '+y+' = '+adeA+'°.'),
exS('In triangle ADE, AD = DE. What is the sum of angles DAE and DEA?',180-adeA,'degrees','180 − '+adeA+' = '+(180-adeA)+'°.'),
exS('(b) What is angle DAE?',dae,'degrees',(180-adeA)+' ÷ 2 = '+dae+'°.','(b)',2)]};},
'Circles':function(){
var r=pick([7,14,21]), side=2*r, sq=side*side, semi=11*r*r/7, arc=22*r/7, area=sq+semi, per=3*side+arc;
var inner=''+side+' cm'+side+' cm';
return {topic:'Circles',figAlt:'A square of side '+side+' centimetres with a semicircle on its top side. The diameter of the semicircle is that side.',fig:svgWrap(inner.replace('M60 48 H180 V118 H60 Z','M60 48 H180 V118 H60 Z')),intro:'The figure is a square of side '+side+' cm with a semicircle on one side. The diameter of the semicircle is the side of the square. Use π = 22/7.',
steps:[exS('What is the area of the square in cm²?',sq,'cm²',side+' × '+side+' = '+sq+'.'),
exS('The radius of the semicircle is '+r+' cm. What is the area of the semicircle in cm²? (Half of π × r × r.)',semi,'cm²','1/2 × 22/7 × '+r+' × '+r+' = '+semi+'.'),
exS('(a) What is the total area of the figure in cm²?',area,'cm²',sq+' + '+semi+' = '+area+'.','(a)',2),
exS('What is the length of the curved edge of the semicircle in cm? (Half of π × diameter.)',arc,'cm','1/2 × 22/7 × '+side+' = '+arc+'.'),
exS('(b) What is the perimeter of the whole figure in cm? (Count 3 sides of the square and the curve. Do not count the line inside.)',per,'cm','3 × '+side+' + '+arc+' = '+per+'.','(b)',2)]};},
'Coins':function(){
var n=rnd(6,30),k=rnd(4,15),c1=20,c2=50,V=c1*n+c2*(n+k),N=pick(WPN);
return {topic:'Whole numbers',fig:'',intro:N+' has only 20-cent and 50-cent coins. There are '+k+' more 50-cent coins than 20-cent coins. The coins are worth $'+(V/100).toFixed(2)+' altogether.',
steps:[exS('Imagine taking away the extra '+k+' fifty-cent coins. What are they worth, in cents?',c2*k,'cents',k+' × 50 = '+(c2*k)+' cents.'),
exS('What are the remaining coins worth, in cents? (There are now equal numbers of each kind.)',V-c2*k,'cents',V+' − '+(c2*k)+' = '+(V-c2*k)+' cents.'),
exS('One 20-cent coin and one 50-cent coin make a pair. How many cents is one pair worth?',c1+c2,'cents','20 + 50 = 70 cents.'),
exS('(a) How many 20-cent coins are there?',n,'coins',(V-c2*k)+' ÷ 70 = '+n+'.','(a)',2),
exS('(b) How many coins are there altogether?',2*n+k,'coins',n+' + ('+n+' + '+k+') = '+(2*n+k)+'.','(b)',2)]};},
'Volume':function(){
var orig=Math.random()<.3, tl=orig?25:pick([20,25,30,40]), tw=orig?9:pick([8,9,10,12]), te=orig?8:pick([4,5,6,8,10]), th=orig?15:pick([10,12,15,18]), tk=orig?4:pick([3,4,5]), tpl=orig?20:pick([20,25,30]), tpw=orig?10:pick([10,12,15]);
var A=pick(WPN), pour=tl*tw*te, left=(tk-1)*pour, back=pour+(orig?600:rnd(3,10)*100), capQ=tl*tw*(te+th), lq=capQ-back, capP=left+back, capL=Math.round(capP)/1000;
return {topic:'Volume',fig:tankFig(tpl,tpw,tl,tw,te,th),intro:'Two rectangular tanks, P and Q, had some water at first. Tank P has a base '+tpl+' cm by '+tpw+' cm. Tank Q has a base '+tl+' cm by '+tw+' cm; its water is '+th+' cm deep and the water is '+te+' cm below the brim. '+A+' poured 1/'+tk+' of the water from Tank P into Tank Q to fill it to the brim. Then '+A+' poured some water back from Tank Q into Tank P to fill it to the brim. '+lq+' cm³ of water was left in Tank Q.',
steps:[exS('How much water, in cm³, is needed to fill Tank Q to the brim? (The empty part is '+tl+' cm by '+tw+' cm by '+te+' cm.)',pour,'cm³',tl+' × '+tw+' × '+te+' = '+pour+' cm³ = '+pour+' ml.'),
exS('That water was 1/'+tk+' of the water in P. How much water was in P at first, in ml?',pour*tk,'ml','1 unit = '+pour+' ml. '+tk+' units = '+(pour*tk)+' ml.'),
exS('(a) How much water was left in Tank P, in ml?',left,'ml',(pour*tk)+' − '+pour+' = '+left+' ml.','(a)',2),
exS('What is the capacity of Tank Q in cm³? (Water '+th+' cm deep plus '+te+' cm empty = '+(te+th)+' cm tall.)',capQ,'cm³',tl+' × '+tw+' × '+(te+th)+' = '+capQ+' cm³.'),
exS('Tank Q was full, then '+lq+' cm³ was left. How much water was poured back into P, in ml?',back,'ml',capQ+' − '+lq+' = '+back+' ml.'),
exS('(b) What is the capacity of Tank P in litres?',capL,'litres',left+' + '+back+' = '+capP+' ml = '+capL+' litres.','(b)',2)]};}
};
/* ---------- more exam questions: Angles, Circles and Shaded area, each with its own diagram ---------- */
var GXD=Math.PI/180;
function gxP(p){return fN(p[0])+' '+fN(p[1]);}
function gxV(c,r,d){return [c[0]+r*Math.cos(d*GXD),c[1]-r*Math.sin(d*GXD)];}
function gxDir(p,q){return Math.atan2(-(q[1]-p[1]),q[0]-p[0])/GXD;}
function gxNorm(d){d=d%360; return d<0?d+360:d;}
var GX={pts:[],boxes:[],reqs:[],n:0};
function gxReset(){GX={pts:[],boxes:[],reqs:[],n:0};}
function gxSamp(a,b,own){var d=Math.sqrt((b[0]-a[0])*(b[0]-a[0])+(b[1]-a[1])*(b[1]-a[1])),n=Math.max(1,Math.ceil(d/3)),i; for(i=0;i<=n;i++)GX.pts.push([a[0]+(b[0]-a[0])*i/n,a[1]+(b[1]-a[1])*i/n,own||0]);}
function gxLn(a,b,c,ex){if(!c||c==='fl')gxSamp(a,b); return '';}
function gxPg(pts,c,ex){if(!c||c==='fl'){var i; for(i=0;i';}
function gxTx(p,s,q,sz){var fs=sz||15,hw=Math.max(1,String(s).replace(/&[a-z]+;/g,'x').length)*fs*.29+1,hh=fs*.5; GX.boxes.push([p[0]-hw,p[1]-hh,p[0]+hw,p[1]+hh]); return gxTxR(p,s,q,sz);}
function gxTxR(p,s,q,sz){return ''+s+'';}
function gxLb(p,s){return gxTx(p,s,0,12);}
function gxArc(c,r,a,b,cls,own){var p1=gxV(c,r,a),p2=gxV(c,r,b),i,n=Math.max(2,Math.ceil((b-a)/6)); for(i=0;i<=n;i++){var q=gxV(c,r,a+(b-a)*i/n); GX.pts.push([q[0],q[1],own||0]);} var rr=Math.abs((b-a)-180)<.5?r-.2:r; return '';}
function gxAng(P,a,b,txt,known,r,lr){r=r||22; lr=lr||r+17; var id=++GX.n; GX.reqs.push({id:id,P:P,a:a,b:b,txt:txt,known:known,r:r,lr:lr}); return gxArc(P,r,a,b,known?'fk':'fq',id)+'';}
function gxPlace(fig){
GX.reqs.slice().sort(function(p,q){return (p.b-p.a)-(q.b-q.a);}).forEach(function(q){
var fs=q.known?14:16,hw=String(q.txt).length*fs*.29+1,hh=fs*.5,w=q.b-q.a,m=(q.a+q.b)/2,best=null,bc=1e9;
var rads=[-8,-4,0,4,8,14,20,28].map(function(d){return q.lr+d;}).filter(function(v){return v>=q.r+7;}),dl=w>40?[0,-.25,.25,-.38,.38]:[0];
rads.forEach(function(rr){dl.forEach(function(dd){
var c=gxV(q.P,rr,m+dd*w),bx=[c[0]-hw,c[1]-hh,c[0]+hw,c[1]+hh],cost=Math.abs(rr-q.lr)*.3+Math.abs(dd)*8;
if(bx[0]<3||bx[2]>237||bx[1]<6||bx[3]>126)cost+=500;
GX.pts.forEach(function(p){if(p[2]!==q.id&&p[0]>bx[0]-1.5&&p[0]bx[1]-1.5&&p[1]o[2]||bx[3]o[3]))cost+=300;});
if(cost',gxTxR(best[0],q.txt,!q.known,q.known?14:16));
});
return fig;}
function gxAngAt(P,Q,R,txt,known,r,lr){var a=gxDir(P,Q),b=gxDir(P,R),d=gxNorm(b-a); if(d<=180)return gxAng(P,a,a+d,txt,known,r,lr); return gxAng(P,b,b+360-d,txt,known,r,lr);}
function gxAngVal(P,Q,R){var d=gxNorm(gxDir(P,R)-gxDir(P,Q)); return d>180?360-d:d;}
function gxRA(P,a,b){var s=8,u=[Math.cos(a*GXD),-Math.sin(a*GXD)],v=[Math.cos(b*GXD),-Math.sin(b*GXD)]; return '';}
function gxRAt(P,Q,R){return gxRA(P,gxDir(P,Q),gxDir(P,R));}
function gxTk(a,b,n){var m=[(a[0]+b[0])/2,(a[1]+b[1])/2],dx=b[0]-a[0],dy=b[1]-a[1],l=Math.sqrt(dx*dx+dy*dy)||1,ux=dx/l,uy=dy/l,nx=-uy*4.5,ny=ux*4.5,o='',i; n=n||1;
for(i=0;i';} return o;}
function gxPr(a,b,f){f=f||.5; var m=[a[0]+(b[0]-a[0])*f,a[1]+(b[1]-a[1])*f],dx=b[0]-a[0],dy=b[1]-a[1],l=Math.sqrt(dx*dx+dy*dy)||1,ux=dx/l,uy=dy/l,nx=-uy*4,ny=ux*4;
return '';}
/* fit points (y up) into a box [x0,y0,x1,y1] on screen (y down); returns {s,P} */
function gxFit(pts,b,cap){var xs=pts.map(function(p){return p[0];}),ys=pts.map(function(p){return p[1];}),mnx=Math.min.apply(null,xs),mxx=Math.max.apply(null,xs),mny=Math.min.apply(null,ys),mxy=Math.max.apply(null,ys),w=(mxx-mnx)||1,h=(mxy-mny)||1,s=Math.min((b[2]-b[0])/w,(b[3]-b[1])/h,cap||1e9),ox=(b[0]+b[2])/2-s*(mnx+mxx)/2,oy=(b[1]+b[3])/2+s*(mny+mxy)/2;
return {s:s,P:pts.map(function(p){return [ox+s*p[0],oy-s*p[1]];})};}
function gxCen(pts){var x=0,y=0; pts.forEach(function(p){x+=p[0];y+=p[1];}); return [x/pts.length,y/pts.length];}
function gxVL(P,C,s,d){d=d||11; var dx=P[0]-C[0],dy=P[1]-C[1],l=Math.sqrt(dx*dx+dy*dy)||1; return gxTx([P[0]+d*dx/l,P[1]+d*dy/l],s,0,14);}
function gxR2(v){return Math.round(v*100)/100;}
function gxN(v){return String(gxR2(v));}
function gxReg(k,f){EXT[k]=function(){for(var i=0;i<120;i++){gxReset(); var q=f();if(q){q.fig=svgWrap(gxPlace(q.fig0)); delete q.fig0; return q;}} return EXT.Circles();};}
function gxQ(topic,fig,alt,intro,steps,meta){return {topic:topic,fig0:fig,figAlt:alt,intro:intro,steps:steps,meta:meta};}
function gxS(q,ans,why,part){return exS(q,ans,'degrees',why,part,part?2:0);}
function gxA(q,ans,unit,why,part){return exS(q,ans,unit,why,part,part?2:0);}
var GXNS='';
/* triangle (y up) with base BC = 1 from the angles at B and C */
function gxTri(b,c){var a=180-b-c,AB=Math.sin(c*GXD)/Math.sin(a*GXD); return [[AB*Math.cos(b*GXD),AB*Math.sin(b*GXD)],[0,0],[1,0]];}
/* ===== ANGLES ===== */
gxReg('Angles#1',function(){
var a=rnd(7,18)*5,b=rnd(7,18)*5,x=180-a-b; if(x<35||x>115)return null;
var P=[120,100],f=gxLn([18,100],[222,100])+gxLn(P,gxV(P,82,a))+gxLn(P,gxV(P,82,a+b));
f+=gxAng(P,0,a,a+'°',1,20,38)+gxAng(P,a,a+b,b+'°',1,29,54)+gxAng(P,a+b,180,'x',0,20,38);
return gxQ('Angles',f,'A straight line with two rays from one point. The angles on the line are '+a+' degrees, '+b+' degrees and x.',
'Three angles meet on a straight line. Two of them are '+a+'° and '+b+'°.'+GXNS+' Find angle x.',
[gxS('What is the sum of the two angles shown, '+a+'° and '+b+'°?',a+b,a+' + '+b+' = '+(a+b)+'°.'),
gxS('(a) Angles on a straight line add up to 180°. What is angle x?',x,'180 − '+(a+b)+' = '+x+'°.','(a)')],
{pat:'A1',p:{a:a,b:b},lab:[a+'°',b+'°','x'],ans:[a+b,x]});});
gxReg('Angles#2',function(){
var v=[rnd(10,26)*5,rnd(10,26)*5,rnd(10,26)*5],x=360-v[0]-v[1]-v[2]; if(x<50||x>140)return null;
var u=rnd(0,3),arr=v.slice(); arr.splice(u,0,x); var s=rnd(0,35)*10+15,P=[120,65],f='',d=s,i;
for(i=0;i<4;i++){f+=gxLn(P,gxV(P,54,d)); d+=arr[i];}
d=s; for(i=0;i<4;i++){f+=gxAng(P,d,d+arr[i],i===u?'x':arr[i]+'°',i!==u,19,38); d+=arr[i];}
var kn=v.map(function(q){return q+'°';}).join(', ');
return gxQ('Angles',f,'Four angles meeting at a point. Three are '+v.join(', ')+' degrees. The fourth is marked x.',
'Four angles meet at a point. Three of them are '+v[0]+'°, '+v[1]+'° and '+v[2]+'°.'+GXNS+' Find angle x.',
[gxS('What is the sum of the three angles shown?',v[0]+v[1]+v[2],v[0]+' + '+v[1]+' + '+v[2]+' = '+(v[0]+v[1]+v[2])+'°.'),
gxS('(a) Angles at a point add up to 360°. What is angle x?',x,'360 − '+(v[0]+v[1]+v[2])+' = '+x+'°.','(a)')],
{pat:'A2',p:{v:v},lab:v.map(function(q){return q+'°';}).concat(['x']),ans:[v[0]+v[1]+v[2],x]});});
gxReg('Angles#3',function(){
var a=pick([rnd(9,15)*5,rnd(21,27)*5]),t=rnd(3,12)*5,k=rnd(0,3),P=[120,65],bd=[t,t+a,t+180,t+180+a,t+360],f=gxLn(gxV(P,60,t),gxV(P,60,t+180))+gxLn(gxV(P,60,t+a),gxV(P,60,t+a+180)),i;
var val=function(i){return i%2===0?a:180-a;},v=val(k),x=180-v,y=v;
for(i=0;i<4;i++){var lab=i===k?v+'°':(i===(k+1)%4?'x':(i===(k+2)%4?'y':'')); if(lab)f+=gxAng(P,bd[i],bd[i+1],lab,i===k,18,36);}
return gxQ('Angles',f,'Two straight lines crossing. One angle is '+v+' degrees. The angle next to it is marked x and the angle opposite it is marked y.',
'Two straight lines cross. One of the angles is '+v+'°. Angle x is next to it and angle y is opposite it.'+GXNS,
[gxS('(a) Angles on a straight line add up to 180°. What is angle x?',x,'180 − '+v+' = '+x+'°.','(a)'),
gxS('(b) Angle y is opposite the '+v+'° angle (vertically opposite angles are equal). What is angle y?',y,'Vertically opposite angles are equal, so y = '+v+'°.','(b)')],
{pat:'A3',p:{v:v},lab:[v+'°','x','y'],ans:[x,y]});});
gxReg('Angles#4',function(){
var mode=rnd(0,1),al=pick([40,50,60,70,80,100,110,120,140]),be=(180-al)/2,T=gxTri(be,be),F=gxFit(T,[28,22,212,110]),A=F.P[0],B=F.P[1],C=F.P[2],cen=gxCen(F.P);
var f=gxPg(F.P)+gxTk(A,B)+gxTk(A,C)+gxVL(A,cen,'A')+gxVL(B,cen,'B')+gxVL(C,cen,'C');
var steps,intro,alt;
if(mode===0){f+=gxAngAt(A,B,C,al+'°',1,20,36)+gxAngAt(B,C,A,'x',0,20,36);
intro='ABC is an isosceles triangle with AB = AC. Angle BAC is '+al+'°.'+GXNS+' Find angle x, which is angle ABC.'; alt='Isosceles triangle ABC with AB equal to AC. The angle at A is '+al+' degrees. The angle at B is marked x.';
steps=[gxS('Angles B and C are equal. What is their sum?',180-al,'180 − '+al+' = '+(180-al)+'°.'),gxS('(a) What is angle x?',be,(180-al)+' ÷ 2 = '+be+'°.','(a)')];}
else{f+=gxAngAt(B,C,A,be+'°',1,20,36)+gxAngAt(A,B,C,'x',0,20,36);
intro='ABC is an isosceles triangle with AB = AC. Angle ABC is '+be+'°.'+GXNS+' Find angle x, which is angle BAC.'; alt='Isosceles triangle ABC with AB equal to AC. The angle at B is '+be+' degrees. The angle at A is marked x.';
steps=[gxS('Angle ACB is equal to angle ABC. What is angle ACB?',be,'The base angles are equal, so angle ACB = '+be+'°.'),gxS('(a) What is angle x?',al,'180 − '+be+' − '+be+' = '+al+'°.','(a)')];}
return gxQ('Angles',f,alt,intro,steps,{pat:'A4',p:{mode:mode,al:al,be:be},lab:[mode?be+'°':al+'°','x'],ans:[mode?be:180-al,mode?al:be]});});
gxReg('Angles#5',function(){
var al=rnd(7,17)*5,be=rnd(7,17)*5,c=180-al-be; if(c<30)return null;
var T=gxTri(be,c),D=[T[2][0]+.45,0],F=gxFit([T[0],T[1],T[2],D],[26,16,214,100]),A=F.P[0],B=F.P[1],C=F.P[2],Dd=F.P[3],cen=gxCen([A,B,C]);
var f=gxPg([A,B,C])+gxLn(C,Dd)+gxVL(A,cen,'A')+gxVL(B,cen,'B',12)+gxVL(C,cen,'C',12)+gxTx([Dd[0],Dd[1]+13],'D',0,14);
f+=gxAngAt(A,B,C,al+'°',1,18,32)+gxAngAt(B,C,A,be+'°',1,22,38)+gxAngAt(C,A,Dd,'x',0,18,34);
return gxQ('Angles',f,'Triangle ABC with side BC extended to D. Angle A is '+al+' degrees and angle B is '+be+' degrees. The outside angle ACD is marked x.',
'In the figure, BCD is a straight line. Angle BAC is '+al+'° and angle ABC is '+be+'°.'+GXNS+' Find angle x, which is angle ACD.',
[gxS('What is angle ACB? (Angles in a triangle add up to 180°.)',c,'180 − '+al+' − '+be+' = '+c+'°.'),
gxS('(a) BCD is a straight line. What is angle x?',180-c,'180 − '+c+' = '+(180-c)+'°. (It is also '+al+' + '+be+'.)','(a)')],
{pat:'A5',p:{al:al,be:be},lab:[al+'°',be+'°','x'],ans:[c,180-c]});});
gxReg('Angles#6',function(){
var c=rnd(3,7)*5; var cc=Math.cos(c*GXD),A=[0,Math.tan(c*GXD)],B=[0,0],C=[1,0],D=[1-1/(2*cc*cc),0],F=gxFit([A,B,C,D],[28,22,210,102]),a=F.P[0],b=F.P[1],cP=F.P[2],d=F.P[3];
var f=gxPg([a,b,cP])+gxLn(a,d)+gxRAt(b,a,cP)+gxTk(a,d)+gxTk(d,cP)+gxVL(a,gxCen([a,b,cP]),'A')+gxVL(b,gxCen([a,b,cP]),'B')+gxVL(cP,gxCen([a,b,cP]),'C',12)+gxTx([d[0],d[1]+13],'D',0,14);
f+=gxAngAt(cP,b,a,c+'°',1,24,40)+gxAngAt(d,a,b,'x',0,18,32)+gxAngAt(a,b,d,'y',0,24,40);
return gxQ('Angles',f,'Right-angled triangle ABC with the right angle at B. Point D is on BC and AD equals DC. Angle ACB is '+c+' degrees. Angle ADB is marked x and angle BAD is marked y.',
'ABC is a right-angled triangle with a right angle at B. D is a point on BC and AD = DC. Angle ACB is '+c+'°.'+GXNS+' Find angle x (angle ADB) and angle y (angle BAD).',
[gxS('AD = DC, so triangle ADC is isosceles. What is angle DAC?',c,'The base angles are equal, so angle DAC = '+c+'°.'),
gxS('(a) Angle x is an outside angle of triangle ADC. What is angle x?',2*c,c+' + '+c+' = '+(2*c)+'°.','(a)'),
gxS('What is angle BAC? (Triangle ABC has a right angle at B.)',90-c,'180 − 90 − '+c+' = '+(90-c)+'°.'),
gxS('(b) What is angle y?',90-2*c,(90-c)+' − '+c+' = '+(90-2*c)+'°.','(b)')],
{pat:'A6',p:{c:c},lab:[c+'°','x','y'],ans:[c,2*c,90-c,90-2*c]});});
/* parallel lines cut by a transversal */
gxReg('Angles#7',function(){
var th=pick([rnd(10,16)*5,rnd(20,26)*5]),type=pick(['corresponding','alternate','co-interior']),topKnown=rnd(0,1);
var PR={corresponding:[['UR','UR'],['UL','UL'],['LL','LL'],['LR','LR']],alternate:[['LL','UR'],['LR','UL']],'co-interior':[['LR','UR'],['LL','UL']]};
var pr=pick(PR[type]),kLine=topKnown?'T':'B',xLine=topKnown?'B':'T',kSec=topKnown?pr[0]:pr[1],xSec=topKnown?pr[1]:pr[0];
var VAL={UR:th,UL:180-th,LL:th,LR:180-th},RNG={UR:[0,th],UL:[th,180],LL:[180,180+th],LR:[180+th,360]},ADJ={UR:'UL',UL:'UR',LL:'LR',LR:'LL'};
var ySec=ADJ[xSec],v=VAL[kSec],x=VAL[xSec],y=VAL[ySec];
var yT=24,yB=106,Tx=120+41/Math.tan(th*GXD),T=[Tx,yT],B=[Tx-82/Math.tan(th*GXD),yB];
var f=gxLn([16,yT],[224,yT])+gxLn([16,yB],[224,yB])+gxPr([16,yT],[224,yT],.88)+gxPr([16,yB],[224,yB],.88)+gxLn(gxV(T,26,th),gxV(B,26,th+180));
var Q={T:T,B:B};
f+=gxAng(Q[kLine],RNG[kSec][0],RNG[kSec][1],v+'°',1,14,30)+gxAng(Q[xLine],RNG[xSec][0],RNG[xSec][1],'x',0,14,28)+gxAng(Q[xLine],RNG[ySec][0],RNG[ySec][1],'y',0,14,28);
var why={corresponding:'Corresponding angles on parallel lines are equal, so x = '+v+'°.',alternate:'Alternate angles on parallel lines are equal, so x = '+v+'°.','co-interior':'Co-interior (allied) angles between parallel lines add up to 180°. 180 − '+v+' = '+x+'°.'};
return gxQ('Angles',f,'Two parallel lines cut by a slanting line. One angle is '+v+' degrees. Angle x is on the other parallel line and angle y is next to x.',
'The two horizontal lines are parallel. One angle is '+v+'°.'+GXNS+' Find angle x and angle y.',
[gxS('(a) Use the parallel lines. What is angle x?',x,why[type],'(a)'),
gxS('(b) Angle y is next to angle x on a straight line. What is angle y?',y,'180 − '+x+' = '+y+'°.','(b)')],
{pat:'A7',p:{th:th,type:type,topKnown:topKnown,kSec:kSec,xSec:xSec},lab:[v+'°','x','y'],ans:[x,y]});});
gxReg('Angles#8',function(){
var mode=rnd(0,1),a,p,f,steps,intro,alt,meta;
if(mode===0){a=pick([rnd(5,8)*10,rnd(10,13)*10]); var A=[0,0],B=[1.5,0],D=[.8*Math.cos(a*GXD),.8*Math.sin(a*GXD)],C=[B[0]+D[0],D[1]],F=gxFit([A,B,C,D],[34,20,206,104]),P=F.P,cen=gxCen(P);
f=gxPg(P)+gxPr(P[0],P[1],.5)+gxPr(P[3],P[2],.5)+gxPr(P[0],P[3],.5)+gxPr(P[1],P[2],.5)+gxVL(P[0],cen,'A')+gxVL(P[1],cen,'B')+gxVL(P[2],cen,'C')+gxVL(P[3],cen,'D');
f+=gxAngAt(P[0],P[1],P[3],a+'°',1,18,34)+gxAngAt(P[1],P[2],P[0],'x',0,18,34)+gxAngAt(P[2],P[3],P[1],'y',0,18,34);
intro='ABCD is a parallelogram. Angle DAB is '+a+'°.'+GXNS+' Find angle x (angle ABC) and angle y (angle BCD).'; alt='Parallelogram ABCD. The angle at A is '+a+' degrees. The angles at B and at C are marked x and y.';
steps=[gxS('(a) Angles next to each other in a parallelogram add up to 180°. What is angle x?',180-a,'180 − '+a+' = '+(180-a)+'°.','(a)'),gxS('(b) Opposite angles of a parallelogram are equal. What is angle y?',a,'Angle BCD is opposite angle DAB, so y = '+a+'°.','(b)')];
meta={pat:'A8a',p:{a:a},lab:[a+'°','x','y'],ans:[180-a,a]};}
else{a=rnd(6,12)*5; p=rnd(6,12)*5; var xx=180-a-p; if(xx<30||xx>100)return null;
var AD=Math.sin(p*GXD)/Math.sin(xx*GXD),A2=[0,0],B2=[1,0],D2=[AD*Math.cos(a*GXD),AD*Math.sin(a*GXD)],C2=[B2[0]+D2[0],D2[1]],F2=gxFit([A2,B2,C2,D2],[30,20,210,104]),Q=F2.P,cen2=gxCen(Q);
f=gxPg(Q)+gxLn(Q[1],Q[3])+gxPr(Q[0],Q[1],.5)+gxPr(Q[3],Q[2],.5)+gxVL(Q[0],cen2,'A')+gxVL(Q[1],cen2,'B')+gxVL(Q[2],cen2,'C')+gxVL(Q[3],cen2,'D');
f+=gxAngAt(Q[0],Q[1],Q[3],a+'°',1,18,32)+gxAngAt(Q[1],Q[0],Q[3],p+'°',1,22,38)+gxAngAt(Q[3],Q[0],Q[1],'x',0,16,30)+gxAngAt(Q[3],Q[0],Q[2],'y',0,27,44);
intro='ABCD is a parallelogram and BD is a straight line joining B and D. Angle DAB is '+a+'° and angle ABD is '+p+'°.'+GXNS+' Find angle x (angle ADB) and angle y (angle ADC).'; alt='Parallelogram ABCD with the diagonal BD. Angle A is '+a+' degrees and angle ABD is '+p+' degrees. Angle ADB is marked x and angle ADC is marked y.';
steps=[gxS('(a) What is angle x? (Angles in triangle ABD add up to 180°.)',xx,'180 − '+a+' − '+p+' = '+xx+'°.','(a)'),gxS('AB is parallel to DC, so angle BDC = angle ABD (alternate angles). What is angle BDC?',p,'Alternate angles are equal, so angle BDC = '+p+'°.'),gxS('(b) What is angle y?',xx+p,xx+' + '+p+' = '+(xx+p)+'°. (Check: it equals 180 − '+a+'.)','(b)')];
meta={pat:'A8b',p:{a:a,p:p},lab:[a+'°',p+'°','x','y'],ans:[xx,p,xx+p]};}
return gxQ('Angles',f,alt,intro,steps,meta);});
gxReg('Angles#9',function(){
var b=pick([40,50,60,70,80,100,110]),x=(180-b)/2,cb=Math.cos(b*GXD),sb=Math.sin(b*GXD),
A=[0,0],Bp=[1,0],C=[1-cb,sb],D=[-cb,sb],F=gxFit([A,Bp,C,D],[34,24,206,100]),P=F.P,cen=gxCen(P);
var f=gxPg(P)+gxLn(P[0],P[2])+gxTk(P[0],P[1])+gxTk(P[1],P[2])+gxTk(P[2],P[3])+gxTk(P[3],P[0])+gxVL(P[0],cen,'A')+gxVL(P[1],cen,'B')+gxVL(P[2],cen,'C')+gxVL(P[3],cen,'D');
f+=gxAngAt(P[1],P[0],P[2],b+'°',1,18,34)+gxAngAt(P[0],P[1],P[2],'x',0,18,36)+gxAngAt(P[0],P[1],P[3],'y',0,30,52);
return gxQ('Angles',f,'Rhombus ABCD with the diagonal AC. Angle ABC is '+b+' degrees. Angle BAC is marked x and angle BAD is marked y.',
'ABCD is a rhombus. Angle ABC is '+b+'°.'+GXNS+' Find angle x (angle BAC) and angle y (angle BAD).',
[gxS('All sides of a rhombus are equal, so triangle ABC is isosceles with AB = BC. What is angle BAC + angle BCA?',180-b,'180 − '+b+' = '+(180-b)+'°.'),
gxS('(a) What is angle x?',x,(180-b)+' ÷ 2 = '+x+'°.','(a)'),
gxS('(b) The diagonal AC cuts angle BAD into two equal parts. What is angle y?',2*x,x+' × 2 = '+(2*x)+'°. (Also 180 − '+b+'.)','(b)')],
{pat:'A9',p:{b:b},lab:[b+'°','x','y'],ans:[180-b,x,2*x]});});
gxReg('Angles#10',function(){
var a=rnd(11,24)*5,b=rnd(11,24)*5; if(a===90&&b===90)return null; var h=.85,A=[0,0],B=[1.7,0],D=[h/Math.tan(a*GXD),h],C=[B[0]-h/Math.tan(b*GXD),h];
if(C[0]-D[0]<.35)return null; var F=gxFit([A,B,C,D],[26,16,214,108]),P=F.P,cen=gxCen(P);
var f=gxPg(P)+gxPr(P[0],P[1],.5)+gxPr(P[3],P[2],.5)+gxVL(P[0],cen,'A')+gxVL(P[1],cen,'B')+gxVL(P[2],cen,'C')+gxVL(P[3],cen,'D');
f+=(a===90?gxRAt(P[0],P[1],P[3]):gxAngAt(P[0],P[1],P[3],a+'°',1,14,30))+(b===90?gxRAt(P[1],P[0],P[2]):gxAngAt(P[1],P[0],P[2],b+'°',1,14,30))+gxAngAt(P[3],P[0],P[2],'x',0,14,28)+gxAngAt(P[2],P[1],P[3],'y',0,14,28);
var kn=(a===90?'Angle DAB is a right angle (90°)':'Angle DAB is '+a+'°')+(b===90?' and angle ABC is a right angle (90°)':' and angle ABC is '+b+'°');
return gxQ('Angles',f,'Trapezium ABCD with AB parallel to DC. Angle A is '+a+' degrees and angle B is '+b+' degrees. Angle ADC is marked x and angle BCD is marked y.',
'ABCD is a trapezium. AB is parallel to DC. '+kn+'.'+GXNS+' Find angle x (angle ADC) and angle y (angle BCD).',
[gxS('(a) AB is parallel to DC, so angles A and D add up to 180°. What is angle x?',180-a,'180 − '+a+' = '+(180-a)+'°.','(a)'),
gxS('(b) Angles B and C also add up to 180°. What is angle y?',180-b,'180 − '+b+' = '+(180-b)+'°.','(b)')],
{pat:'A10',p:{a:a,b:b},lab:[a===90?'':a+'°',b===90?'':b+'°','x','y'],ans:[180-a,180-b]});});
gxReg('Angles#11',function(){
var inside=rnd(0,1),A=[0,0],B=[1,0],C=[1,1],D=[0,1],s3=Math.sqrt(3)/2,E=inside?[1-s3,.5]:[1+s3,.5],F=gxFit([A,B,C,D,E],[34,18,206,108]),P=F.P,cen=gxCen([P[0],P[1],P[2],P[3]]);
var xx=inside?30:150,yy=(180-xx)/2;
var f=gxPg([P[0],P[1],P[2],P[3]])+gxLn(P[1],P[4])+gxLn(P[2],P[4])+gxLn(P[0],P[4])+gxTk(P[0],P[1])+gxTk(P[1],P[2])+gxTk(P[1],P[4])+gxTk(P[2],P[4])+gxRAt(P[3],P[0],P[2])+gxVL(P[0],cen,'A')+gxVL(P[1],cen,'B')+gxVL(P[2],cen,'C')+gxVL(P[3],cen,'D');
f+=(inside?gxTx([P[4][0]-3,P[4][1]-11],'E',0,14):gxTx([P[4][0]+11,P[4][1]],'E',0,14));
f+=gxAngAt(P[1],P[0],P[4],'x',0,inside?22:14,inside?44:28)+gxAngAt(P[4],P[0],P[1],'y',0,inside?14:18,inside?30:30);
return gxQ('Angles',f,'Square ABCD with an equilateral triangle BCE drawn '+(inside?'inside':'outside')+' the square on side BC. Angle ABE is marked x and angle AEB is marked y.',
'ABCD is a square and BCE is an equilateral triangle '+(inside?'inside':'outside')+' the square, as shown.'+GXNS+' Find angle x (angle ABE) and angle y (angle AEB).',
[gxS('(a) Angle ABC is 90° and angle CBE is 60° (equilateral triangle). What is angle x (angle ABE)?',xx,inside?'The triangle is inside the square, so x = 90 − 60 = 30°.':'The triangle is outside the square, so x = 90 + 60 = 150°.','(a)'),
gxS('AB = BC = BE, so triangle ABE is isosceles. What is the sum of angles BAE and BEA?',180-xx,'180 − '+xx+' = '+(180-xx)+'°.'),
gxS('(b) What is angle y?',yy,(180-xx)+' ÷ 2 = '+yy+'°.','(b)')],
{pat:'A11',p:{inside:inside},lab:['x','y'],ans:[xx,180-xx,yy]});});
gxReg('Angles#12',function(){
var ph=rnd(5,13)*5,a=180-2*ph,H=Math.tan(ph*GXD),A=[0,H],B=[1,H],C=[1,0],D=[0,0],O=[.5,H/2],F=gxFit([A,B,C,D],[26,18,214,112]),P=F.P,Op=[(P[0][0]+P[2][0])/2,(P[0][1]+P[2][1])/2],cen=Op;
var x=ph,y=90-ph;
var f=gxPg(P)+gxLn(P[0],P[2])+gxLn(P[1],P[3])+gxTk(P[0],Op)+gxTk(P[1],Op)+gxRAt(P[3],P[0],P[2])+gxVL(P[0],cen,'A')+gxVL(P[1],cen,'B')+gxVL(P[2],cen,'C')+gxVL(P[3],cen,'D')+gxTx([Op[0]+2,Op[1]+9],'O',0,13);
f+=gxAngAt(Op,P[0],P[1],a+'°',1,10,22)+gxAngAt(P[0],P[1],Op,'x',0,16,32)+gxAngAt(P[0],P[3],Op,'y',0,22,40);
return gxQ('Angles',f,'Rectangle ABCD with both diagonals meeting at O. Angle AOB is '+a+' degrees. Angle OAB is marked x and angle OAD is marked y.',
'ABCD is a rectangle. The diagonals AC and BD meet at O. Angle AOB is '+a+'°.'+GXNS+' Find angle x (angle OAB) and angle y (angle OAD).',
[gxS('The diagonals of a rectangle are equal and cross at O, so OA = OB. What is angle OAB + angle OBA?',180-a,'180 − '+a+' = '+(180-a)+'°.'),
gxS('(a) What is angle x?',x,(180-a)+' ÷ 2 = '+x+'°.','(a)'),
gxS('(b) Angle DAB is a right angle. What is angle y?',y,'90 − '+x+' = '+y+'°.','(b)')],
{pat:'A12',p:{a:a},lab:[a+'°','x','y'],ans:[180-a,x,y]});});
gxReg('Angles#13',function(){
var e=rnd(21,30)*5,be=180-e,ap=2*e-180,T=[[.5,.5*Math.tan(be*GXD)],[0,0],[1,0]],D=[1.45,0],F=gxFit([T[0],T[1],T[2],D],[26,16,214,100]),A=F.P[0],B=F.P[1],C=F.P[2],Dd=F.P[3],cen=gxCen([A,B,C]);
var f=gxPg([A,B,C])+gxLn(C,Dd)+gxTk(A,B)+gxTk(A,C)+gxVL(A,cen,'A')+gxVL(B,cen,'B',12)+gxVL(C,cen,'C',12)+gxTx([Dd[0],Dd[1]+13],'D',0,14);
f+=gxAngAt(C,A,Dd,e+'°',1,20,36)+gxAngAt(C,A,B,'x',0,20,34)+gxAngAt(A,B,C,'y',0,16,32);
return gxQ('Angles',f,'Isosceles triangle ABC with AB equal to AC and side BC extended to D. Angle ACD is '+e+' degrees. Angle ACB is marked x and angle BAC is marked y.',
'ABC is an isosceles triangle with AB = AC. BCD is a straight line. Angle ACD is '+e+'°.'+GXNS+' Find angle x (angle ACB) and angle y (angle BAC).',
[gxS('(a) BCD is a straight line. What is angle x?',be,'180 − '+e+' = '+be+'°.','(a)'),
gxS('Angle ABC is equal to angle ACB. What is angle ABC?',be,'AB = AC, so the base angles are equal: '+be+'°.'),
gxS('(b) What is angle y?',ap,'180 − '+be+' − '+be+' = '+ap+'°.','(b)')],
{pat:'A13',p:{e:e},lab:[e+'°','x','y'],ans:[be,be,ap]});});
gxReg('Angles#14',function(){
var d=rnd(8,17)*5,c=rnd(8,17)*5; if(d+c>120||d+c<80)return null; var T=gxTri(d,c),A=T[0],B=T[1],C=T[2],t=.4,Dp=[A[0]*t,A[1]*t],Ep=[C[0]+(A[0]-C[0])*t,A[1]*t],F=gxFit([A,B,C],[26,18,214,106]),sc=F.s,S=F.P;
var ptr=function(p){return [S[1][0]+(p[0]-B[0])*sc*1,S[1][1]-(p[1]-B[1])*sc];},D=ptr(Dp),E=ptr(Ep),cen=gxCen(S);
var f=gxPg(S)+gxLn(D,E)+gxPr(S[1],S[2],.5)+gxPr(D,E,.5)+gxVL(S[0],cen,'A')+gxVL(S[1],cen,'B',12)+gxVL(S[2],cen,'C',12)+gxVL(D,[D[0]+30,D[1]],'D',11)+gxVL(E,[E[0]-30,E[1]],'E',11);
f+=gxAngAt(D,S[0],E,d+'°',1,14,28)+gxAngAt(S[2],S[1],S[0],c+'°',1,20,36)+gxAngAt(S[0],D,E,'x',0,18,34)+gxAngAt(D,S[1],E,'y',0,14,26);
var x=180-d-c;
return gxQ('Angles',f,'Triangle ABC with a line DE parallel to BC, D on AB and E on AC. Angle ADE is '+d+' degrees and angle ACB is '+c+' degrees. Angle DAE is marked x and angle BDE is marked y.',
'In triangle ABC, DE is parallel to BC. Angle ADE is '+d+'° and angle ACB is '+c+'°.'+GXNS+' Find angle x (angle DAE) and angle y (angle BDE).',
[gxS('DE is parallel to BC, so angle AED = angle ACB (corresponding angles). What is angle AED?',c,'Corresponding angles are equal, so angle AED = '+c+'°.'),
gxS('(a) What is angle x?',x,'180 − '+d+' − '+c+' = '+x+'°.','(a)'),
gxS('(b) ADB is a straight line. What is angle y?',180-d,'180 − '+d+' = '+(180-d)+'°.','(b)')],
{pat:'A14',p:{d:d,c:c},lab:[d+'°',c+'°','x','y'],ans:[c,x,180-d]});});
gxReg('Angles#15',function(){
var kn=[rnd(11,25)*5,rnd(11,25)*5,rnd(11,25)*5],r90=rnd(0,2)===0; if(r90)kn[rnd(0,2)]=90;
var s3=kn[0]+kn[1]+kn[2],x=360-s3; if(x<55||x>130)return null;
var ang=[kn[0],kn[1],kn[2],x]; var u=rnd(0,3); ang=kn.slice(); ang.splice(u,0,x);
var th=[0],i; for(i=1;i<4;i++)th.push(th[i-1]+180-ang[i]);
var L1=.8+Math.random()*.5,L2=.8+Math.random()*.5,u1=[Math.cos(th[0]*GXD),Math.sin(th[0]*GXD)],u2=[Math.cos(th[1]*GXD),Math.sin(th[1]*GXD)],u3=[Math.cos(th[2]*GXD),Math.sin(th[2]*GXD)],u4=[Math.cos(th[3]*GXD),Math.sin(th[3]*GXD)];
var rx=-(L1*u1[0]+L2*u2[0]),ry=-(L1*u1[1]+L2*u2[1]),det=u3[0]*u4[1]-u3[1]*u4[0]; if(Math.abs(det)<.15)return null;
var L3=(rx*u4[1]-ry*u4[0])/det,L4=(u3[0]*ry-u3[1]*rx)/det; if(L3<.35||L4<.35)return null;
var V=[[0,0]]; V.push([L1*u1[0],L1*u1[1]]); V.push([V[1][0]+L2*u2[0],V[1][1]+L2*u2[1]]); V.push([V[2][0]+L3*u3[0],V[2][1]+L3*u3[1]]);
var k,F=gxFit(V,[34,22,206,104]),P=F.P,cen=gxCen(P),nm=['A','B','C','D']; if(Math.max.apply(null,P.map(function(p){return p[0];}))-Math.min.apply(null,P.map(function(p){return p[0];}))<120)return null; for(k=0;k<4;k++){var dd=Math.hypot(P[k][0]-P[(k+1)%4][0],P[k][1]-P[(k+1)%4][1]); if(dd<58)return null;}
var f=gxPg(P); for(k=0;k<4;k++){f+=gxVL(P[k],cen,nm[k],12);}
for(k=0;k<4;k++){var Q=P[(k+1)%4],R=P[(k+3)%4]; if(k===u)f+=gxAngAt(P[k],Q,R,'x',0,14,28); else if(ang[k]===90)f+=gxRAt(P[k],Q,R); else f+=gxAngAt(P[k],Q,R,ang[k]+'°',1,14,30);}
var parts=[],k2; for(k2=0;k2<4;k2++){if(k2!==u)parts.push('angle '+nm[k2]+(ang[k2]===90?' is a right angle (90°)':' is '+ang[k2]+'°'));}
return gxQ('Angles',f,'Quadrilateral ABCD. '+parts.join(', ')+'. Angle '+nm[u]+' is marked x.',
'ABCD is a quadrilateral. '+parts.slice(0,2).join(', ')+' and '+parts[2]+'.'+GXNS+' Find angle x, which is angle '+nm[u]+'.',
[gxS('What is the sum of the three angles you know?',s3,ang.filter(function(q,j){return j!==u;}).join(' + ')+' = '+s3+'°.'),
gxS('(a) Angles in a quadrilateral add up to 360°. What is angle x?',x,'360 − '+s3+' = '+x+'°.','(a)')],
{pat:'A15',p:{ang:ang,u:u},lab:ang.filter(function(q,j){return j!==u&&q!==90;}).map(function(q){return q+'°';}).concat(['x']),ans:[s3,x]});});
/* ===== CIRCLES ===== */
function gxPi(){return Math.random()<.5?{t:'22/7',m:function(x){return x*22/7;},say:'Use π = 22/7.',rs:[7,7,14,14,21,28]}:{t:'3.14',m:function(x){return x*3.14;},say:'Use π = 3.14.',rs:[2,3,4,5,6,7,8,9,10,12,15,20]};}
function gxOk(pi,v){if(!(v>0)||!isFinite(v))return false; return pi.t==='3.14'?Math.abs(v*100-Math.round(v*100))<1e-6:Math.abs(v*2-Math.round(v*2))<1e-9;}
function gxAllOk(pi,arr){return arr.every(function(v){return gxOk(pi,v);});}
function gxC(c,r,cls,ex){return '';}
function gxDot(c){return '';}
function gxDash(a,b){return gxLn(a,b,'fl',' stroke-dasharray="5 4"');}
/* 1 circle: radius or diameter given, find circumference and area */
gxReg('Circles#1',function(){
var pi=gxPi(),r=pick(pi.rs),useD=rnd(0,1),d=2*r,C=pi.m(d),A=pi.m(r*r); if(!gxAllOk(pi,[C,A]))return null;
var O=[120,65],R=50,f=gxC(O,R)+gxDot(O)+(useD?gxLn([O[0]-R,O[1]],[O[0]+R,O[1]])+gxLb([O[0],O[1]-11],'d = '+d+' cm'):gxLn(O,[O[0]+R,O[1]])+gxLb([O[0]+R/2,O[1]-11],'r = '+r+' cm'));
var st=[]; if(useD)st.push(gxA('What is the radius of the circle in cm? (Half of the diameter.)',r,'cm',d+' ÷ 2 = '+r+' cm.'));
st.push(gxA('(a) What is the circumference of the circle in cm? (π × diameter)',C,'cm',pi.t+' × '+d+' = '+gxN(C)+' cm.','(a)'));
st.push(gxA('(b) What is the area of the circle in cm²? (π × radius × radius)',A,'cm²',pi.t+' × '+r+' × '+r+' = '+gxN(A)+' cm².','(b)'));
return gxQ('Circles',f,'A circle with its '+(useD?'diameter marked as '+d:'radius marked as '+r)+' centimetres.',
'A circle has a '+(useD?'diameter of '+d:'radius of '+r)+' cm. '+pi.say,st,{pat:'C1',pi:pi.t,p:{r:r,useD:useD},lab:[useD?'d = '+d+' cm':'r = '+r+' cm'],ans:(useD?[r]:[]).concat([C,A])});});
/* 2 circumference given, find radius or diameter, then area */
gxReg('Circles#2',function(){
var pi=gxPi(),r=pick(pi.rs),C=pi.m(2*r),A=pi.m(r*r),askD=rnd(0,1); if(!gxAllOk(pi,[C,A]))return null;
var O=[120,54],R=44,f=gxC(O,R)+gxDot(O)+gxDash(O,[O[0]+R,O[1]])+gxTx([O[0]+R/2,O[1]-10],'?',1,17)+gxTx([O[0],O[1]+R+13],'C = '+gxN(C)+' cm',0,12);
var d=2*r;
return gxQ('Circles',f,'A circle with circumference '+gxN(C)+' centimetres. The '+(askD?'diameter':'radius')+' is not given.',
'The circumference of a circle is '+gxN(C)+' cm. '+pi.say,
[gxA('The circumference is π × diameter. What is the diameter in cm?',d,'cm',gxN(C)+' ÷ '+pi.t+' = '+d+' cm.'),
askD?gxA('(a) What is the diameter of the circle in cm?',d,'cm','The diameter is '+d+' cm.','(a)'):gxA('(a) What is the radius of the circle in cm?',r,'cm',d+' ÷ 2 = '+r+' cm.','(a)'),
gxA('(b) What is the area of the circle in cm²?',A,'cm²',pi.t+' × '+r+' × '+r+' = '+gxN(A)+' cm².','(b)')],
{pat:'C2',pi:pi.t,p:{C:C,r:r},lab:['C = '+gxN(C)+' cm'],ans:[d,askD?d:r,A]});});
/* 3 semicircle: perimeter and area */
gxReg('Circles#3',function(){
var pi=gxPi(),r=pick(pi.rs),d=2*r,arc=pi.m(d)/2,P=arc+d,A=pi.m(r*r)/2; if(!gxAllOk(pi,[arc,P,A]))return null;
var O=[120,100],R=84,f=gxLn([O[0]-R,O[1]],[O[0]+R,O[1]])+gxArc(O,R,0,180)+gxLb([O[0],O[1]+14],d+' cm');
return gxQ('Circles',f,'A semicircle whose diameter is '+d+' centimetres.','The figure is a semicircle. Its diameter is '+d+' cm. '+pi.say,
[gxA('What is the length of the curved edge in cm? (Half of π × diameter.)',arc,'cm','1/2 × '+pi.t+' × '+d+' = '+gxN(arc)+' cm.'),
gxA('(a) What is the perimeter of the semicircle in cm? (Add the diameter.)',P,'cm',gxN(arc)+' + '+d+' = '+gxN(P)+' cm.','(a)'),
gxA('(b) What is the area of the semicircle in cm²? (Half of π × radius × radius.)',A,'cm²','1/2 × '+pi.t+' × '+r+' × '+r+' = '+gxN(A)+' cm².','(b)')],
{pat:'C3',pi:pi.t,p:{r:r},lab:[d+' cm'],ans:[arc,P,A]});});
/* 4 quarter circle */
gxReg('Circles#4',function(){
var pi=gxPi(),r=pick(pi.rs),arc=pi.m(r)/2,P=arc+2*r,A=pi.m(r*r)/4; if(!gxAllOk(pi,[arc,P,A]))return null;
var O=[58,108],R=88,f=gxLn(O,[O[0]+R,O[1]])+gxLn(O,[O[0],O[1]-R])+gxArc(O,R,0,90)+gxRA(O,0,90)+gxLb([O[0]+R/2,O[1]+11],r+' cm')+gxLb([O[0]-20,O[1]-R/2],r+' cm');
return gxQ('Circles',f,'A quarter circle with both straight edges '+r+' centimetres long.','The figure is a quarter circle. The radius is '+r+' cm. '+pi.say,
[gxA('What is the length of the curved edge in cm? (A quarter of π × 2 × radius.)',arc,'cm','1/4 × 2 × '+pi.t+' × '+r+' = '+gxN(arc)+' cm.'),
gxA('(a) What is the perimeter of the quarter circle in cm? (Add the two straight edges.)',P,'cm',gxN(arc)+' + '+r+' + '+r+' = '+gxN(P)+' cm.','(a)'),
gxA('(b) What is the area of the quarter circle in cm²?',A,'cm²','1/4 × '+pi.t+' × '+r+' × '+r+' = '+gxN(A)+' cm².','(b)')],
{pat:'C4',pi:pi.t,p:{r:r},lab:[r+' cm',r+' cm'],ans:[arc,P,A]});});
/* 5 rectangle with a semicircle on one side */
gxReg('Circles#5',function(){
var pi=gxPi(),r=pick(pi.rs),w=2*r,h=rnd(3,Math.max(4,Math.round(1.3*w))),orient=rnd(0,1),arc=pi.m(w)/2,P=w+2*h+arc,A=w*h+pi.m(r*r)/2; if(!gxAllOk(pi,[arc,P,A]))return null;
var f,alt;
if(orient===0){var sc=Math.min(104/w,50/h),wp=w*sc,hp=h*sc,x0=120-wp/2,y1=112,y0=y1-hp,R=wp/2;
f=gxLn([x0,y0],[x0,y1])+gxLn([x0,y1],[x0+wp,y1])+gxLn([x0+wp,y1],[x0+wp,y0])+gxArc([120,y0],R,0,180)+gxDash([x0,y0],[x0+wp,y0])+gxLb([120,y1+10],w+' cm')+gxLb([x0+wp+24,(y0+y1)/2],h+' cm');
alt='A rectangle '+w+' centimetres wide and '+h+' centimetres high with a semicircle on top. The diameter of the semicircle is the top side.';}
else{var sc3=Math.min(104/(h+r),100/w),lp=h*sc3,dp=w*sc3; var x00=120-(lp+dp/2)/2,yc=65,yt=yc-dp/2,yb=yc+dp/2;
f=gxLn([x00,yt],[x00+lp,yt])+gxLn([x00,yt],[x00,yb])+gxLn([x00,yb],[x00+lp,yb])+gxArc([x00+lp,yc],dp/2,-90,90)+gxDash([x00+lp,yt],[x00+lp,yb])+gxLb([x00+lp/2,yb+11],h+' cm')+gxLb([x00-16,yc],w+' cm');
alt='A rectangle '+h+' centimetres long and '+w+' centimetres high with a semicircle on its right side. The diameter of the semicircle is the right side.';}
return gxQ('Circles',f,alt,'The figure is made of a rectangle and a semicircle. The semicircle sits on a '+w+' cm side of the rectangle, and that side is its diameter. The other side of the rectangle is '+h+' cm. '+pi.say,
[gxA('What is the length of the curved edge of the semicircle in cm?',arc,'cm','1/2 × '+pi.t+' × '+w+' = '+gxN(arc)+' cm.'),
gxA('(a) What is the perimeter of the whole figure in cm? (Do not count the dotted diameter.)',P,'cm',h+' + '+h+' + '+w+' + '+gxN(arc)+' = '+gxN(P)+' cm.','(a)'),
gxA('(b) What is the area of the whole figure in cm²?',A,'cm²',w+' × '+h+' + 1/2 × '+pi.t+' × '+r+' × '+r+' = '+(w*h)+' + '+gxN(pi.m(r*r)/2)+' = '+gxN(A)+' cm².','(b)')],
{pat:'C5',pi:pi.t,p:{r:r,w:w,h:h,orient:orient},lab:[w+' cm',h+' cm'],ans:[arc,P,A]});});
/* 6 circles in a row inside a rectangle */
gxReg('Circles#6',function(){
var pi=gxPi(),r=pick(pi.rs),d=2*r,n=rnd(2,4),Cc=pi.m(d),tot=n*Cc,Lr=n*d,Pr=2*(Lr+d); if(!gxAllOk(pi,[Cc,tot]))return null;
var sc=Math.min(196/Lr,70/d),dp=d*sc,x0=120-n*dp/2,y0=65-dp/2,f=gxPg([[x0,y0],[x0+n*dp,y0],[x0+n*dp,y0+dp],[x0,y0+dp]]),i;
for(i=0;i.8*Math.min(r1,r2))return null;
var sc=Math.min(190/L,88/(2*big)); if(m*sc<28)return null; var x1=120-L*sc/2+r1*sc,x2=x1+m*sc,yc=50,f=gxC([x1,yc],r1*sc,'fl')+gxC([x2,yc],r2*sc,'fl');
f+=gxLn([x1-r1*sc,yc],[x2+r2*sc,yc])+gxDot([x1,yc])+gxDot([x2,yc])+gxTx([x1,yc-9],'P',0,13)+gxTx([x2,yc-9],'Q',0,13)+gxTx([x1-r1*sc-10,yc],'A',0,13)+gxTx([x2+r2*sc+10,yc],'B',0,13);
f+=gxLb([x1-r1*sc/2,yc+10],r1+' cm')+gxLb([x2+r2*sc/2,yc+10],r2+' cm');
var yb=yc+Math.max(r1,r2)*sc+12;
f+=gxLn([x1-r1*sc,yb],[x2+r2*sc,yb],'fl',' stroke-width="1.6"')+gxLn([x1-r1*sc,yb-4],[x1-r1*sc,yb+4])+gxLn([x2+r2*sc,yb-4],[x2+r2*sc,yb+4])+gxLb([x1-r1*sc+L*sc/2,yb+11],'AB = '+L+' cm');
return gxQ('Circles',f,'Two overlapping circles with centres P and Q on the straight line AB. AB is '+L+' centimetres. The radius of the circle with centre P is '+r1+' centimetres and the radius of the circle with centre Q is '+r2+' centimetres.',
'Two circles overlap. Their centres are P and Q, and A, P, Q, B are on one straight line. The radius of the circle with centre P is '+r1+' cm and the radius of the circle with centre Q is '+r2+' cm. AB = '+L+' cm. '+pi.say,
[gxA('What is the distance PQ between the centres in cm? (AB = AP + PQ + QB.)',m,'cm',L+' − '+r1+' − '+r2+' = '+m+' cm.'),
gxA('(a) How long is the part of AB that is inside both circles, in cm? (Add AP and QB, then take away PQ.)',ov,'cm',r1+' + '+r2+' − '+m+' = '+ov+' cm.','(a)'),
gxA('(b) What is the circumference of the bigger circle in cm?',Cb,'cm',pi.t+' × '+(2*big)+' = '+gxN(Cb)+' cm.','(b)')],
{pat:'C8',pi:pi.t,p:{r1:r1,r2:r2,m:m},lab:[r1+' cm',r2+' cm','AB = '+L+' cm'],ans:[m,ov,Cb]});});
/* 9 two overlapping quarter circles */
gxReg('Circles#9',function(){
var pi=gxPi(),r=pi.t==='3.14'?pick([10,12,14,16,20]):pick([14,28]),l=rnd(4,Math.floor(r*1.25)); if(l/r<.35||l/r>1.25)return null;
var S=l*r/2,Q=pi.m(r*r)/4,aq=pi.m(r)/2,Aw=2*Q-S,Pw=2*aq-l+2*r; if(!gxAllOk(pi,[Aw,Pw,aq,Q])||S!==Math.round(S))return null;
var phi=l/r/GXD,th=90-phi,O=[88,106],R=80,A1=gxV(O,R,0),C1=gxV(O,R,90),B1=gxV(O,R,th),D1=gxV(O,R,th+90);
var sh='';
var f=sh+gxLn(O,A1)+gxArc(O,R,0,90)+gxLn(O,C1)+gxLn(O,B1)+gxArc(O,R,th,th+90)+gxLn(O,D1)+gxDot(O)
+gxTx([O[0]-10,O[1]+9],'O',0,14)+gxTx([A1[0]+10,A1[1]+3],'A',0,14)+gxTx([B1[0]+10,B1[1]-5],'B',0,14)+gxTx([C1[0]-9,C1[1]-6],'C',0,14)+gxTx([D1[0]-9,D1[1]-4],'D',0,14)
+gxLb([O[0]+R/2,O[1]+11],r+' cm');
f+=gxLb(gxV(O,R+22,(th+90)/2),l+' cm');
return gxQ('Circles',f,'Two identical quarter circles OAC and OBD overlap. Each has a radius of '+r+' centimetres. The overlapping part OBC is shaded. Its curved edge BC is '+l+' centimetres long.',
'Two identical quarter circles, OAC and OBD, overlap to make the figure OABCD. Each has a radius of '+r+' cm. The shaded overlapping part OBC has an area of '+gxN(S)+' cm², and its curved edge BC is '+l+' cm long. '+pi.say+'',
[gxA('What is the area of one quarter circle in cm²?',Q,'cm²','1/4 × '+pi.t+' × '+r+' × '+r+' = '+gxN(Q)+' cm².'),
gxA('(a) What is the area of the whole figure OABCD in cm²? (Two quarter circles, but the shaded part was counted twice.)',Aw,'cm²','2 × '+gxN(Q)+' − '+gxN(S)+' = '+gxN(Aw)+' cm².','(a)'),
gxA('What is the length of the curved edge of one quarter circle in cm?',aq,'cm','1/4 × 2 × '+pi.t+' × '+r+' = '+gxN(aq)+' cm.'),
gxA('(b) What is the perimeter of the whole figure OABCD in cm? (Two curved edges, take away the overlapping curve BC once, then add OA and OD.)',Pw,'cm','2 × '+gxN(aq)+' − '+l+' + '+r+' + '+r+' = '+gxN(Pw)+' cm.','(b)')],
{pat:'C9',pi:pi.t,p:{r:r,l:l,S:S},lab:[r+' cm',l+' cm'],ans:[Q,Aw,aq,Pw],shade:{kind:'sector',r:r,l:l,sc:R/r}});});
/* 10 square with four semicircles on its sides, outside */
gxReg('Circles#10',function(){
var pi=gxPi(),r=pick(pi.rs),s=pi.t==='22/7'?2*r:rnd(3,14),per=2*pi.m(s),ar=s*s+pi.m(s*s)/2; if(!gxAllOk(pi,[per,ar]))return null;
var sp=56,x0=120-sp/2,y0=65-sp/2,h=sp/2,f=gxPg([[x0,y0],[x0+sp,y0],[x0+sp,y0+sp],[x0,y0+sp]]);
f+=gxArc([120,y0],h,0,180)+gxArc([120,y0+sp],h,180,360)+gxArc([x0,65],h,90,270)+gxArc([x0+sp,65],h,-90,90)+gxLb([120,65],s+' cm');
return gxQ('Circles',f,'A square of side '+s+' centimetres with a semicircle drawn outwards on each of its four sides.',
'A square has a side of '+s+' cm. A semicircle is drawn outwards on each side of the square, with the side as its diameter, to make a flower shape. '+pi.say,
[gxA('The four semicircles together make two whole circles. What is the length of the whole curved boundary in cm?',per,'cm','2 × '+pi.t+' × '+s+' = '+gxN(per)+' cm.'),
gxA('(a) What is the perimeter of the whole shape in cm?',per,'cm','Only the four curved edges are on the outside, so the perimeter is '+gxN(per)+' cm.','(a)'),
gxA('(b) What is the area of the whole shape in cm²? (Square + two circles of diameter '+s+' cm.)',ar,'cm²',s+' × '+s+' + 2 × '+pi.t+' × '+gxN(s/2)+' × '+gxN(s/2)+' = '+(s*s)+' + '+gxN(pi.m(s*s)/2)+' = '+gxN(ar)+' cm².','(b)')],
{pat:'C10',pi:pi.t,p:{s:s},lab:[s+' cm'],ans:[per,per,ar]});});
/* 11 wheel rolling */
gxReg('Circles#11',function(){
var pi=gxPi(),r=pick(pi.rs),d=2*r,n=rnd(3,12),Cc=pi.m(d),dist=n*Cc; if(!gxAllOk(pi,[Cc,dist]))return null;
var g=100,R=26,x0=50,f=gxLn([14,g],[226,g])+gxC([x0,g-R],R)+gxDot([x0,g-R])+gxLn([x0-R,g-R],[x0+R,g-R])+gxLb([x0,g-R-10],d+' cm');
var xe=x0+R+110;
f+=gxC([xe,g-R],R,'fl',' stroke-dasharray="4 4"')+gxLn([x0,g+8],[xe,g+8],'fk')+gxLn([x0,g+3],[x0,g+13],'fk')+gxLn([xe,g+3],[xe,g+13],'fk')+gxTx([(x0+xe)/2,g+20],'? cm',1,13)+gxLb([(x0+xe)/2,g-R*2-4],n+' complete turns');
return gxQ('Circles',f,'A wheel with a diameter of '+d+' centimetres rolls along flat ground and makes '+n+' complete turns. The distance it rolls is marked with a question mark.',
'A wheel has a diameter of '+d+' cm. It rolls along flat ground and makes '+n+' complete turns. '+pi.say,
[gxA('(a) How far does the wheel move forward in one complete turn? (This is its circumference.) Give your answer in cm.',Cc,'cm',pi.t+' × '+d+' = '+gxN(Cc)+' cm.','(a)'),
gxA('(b) How far does the wheel move in '+n+' complete turns, in cm?',dist,'cm',n+' × '+gxN(Cc)+' = '+gxN(dist)+' cm.','(b)')],
{pat:'C11',pi:pi.t,p:{r:r,n:n},lab:[d+' cm',n+' complete turns'],ans:[Cc,dist]});});
/* 12 circle fits in a square (square perimeter given) */
gxReg('Circles#12',function(){
var pi=gxPi(),r=pick(pi.rs),s=2*r,P=4*s,Cc=pi.m(s),A=pi.m(r*r); if(!gxAllOk(pi,[Cc,A]))return null;
var x0=77,y0=12,sp=86,f=gxPg([[x0,y0],[x0+sp,y0],[x0+sp,y0+sp],[x0,y0+sp]])+gxC([120,y0+sp/2],sp/2)+gxDot([120,y0+sp/2])+gxTx([120,y0+sp+14],'Perimeter of square = '+P+' cm',0,12);
return gxQ('Circles',f,'A circle drawn inside a square so that it touches all four sides. The perimeter of the square is '+P+' centimetres.',
'A circle fits exactly inside a square and touches all four sides. The perimeter of the square is '+P+' cm. '+pi.say,
[gxA('What is the side of the square in cm? (This is also the diameter of the circle.)',s,'cm',P+' ÷ 4 = '+s+' cm.'),
gxA('(a) What is the circumference of the circle in cm?',Cc,'cm',pi.t+' × '+s+' = '+gxN(Cc)+' cm.','(a)'),
gxA('(b) What is the area of the circle in cm²?',A,'cm²',pi.t+' × '+r+' × '+r+' = '+gxN(A)+' cm².','(b)')],
{pat:'C12',pi:pi.t,p:{s:s},lab:['Perimeter of square = '+P+' cm'],ans:[s,Cc,A]});});
/* 13 area given, find radius and circumference */
gxReg('Circles#13',function(){
var pi=gxPi(),r=pick(pi.rs),A=pi.m(r*r),Cc=pi.m(2*r); if(!gxAllOk(pi,[A,Cc])||(pi.t==='22/7'&&A!==Math.round(A)))return null;
var O=[120,54],R=44,f=gxC(O,R)+gxDot(O)+gxDash(O,[O[0]+R,O[1]])+gxTx([O[0]+R/2,O[1]-10],'r',1,17)+gxTx([O[0],O[1]+R+13],'Area = '+gxN(A)+' cm²',0,12);
return gxQ('Circles',f,'A circle with an area of '+gxN(A)+' square centimetres. The radius is not given.',
'The area of a circle is '+gxN(A)+' cm². '+pi.say,
[gxA('Area = π × radius × radius. What is radius × radius?',r*r,'',gxN(A)+' ÷ '+pi.t+' = '+(r*r)+'.'),
gxA('(a) What is the radius of the circle in cm?',r,'cm','The number that multiplied by itself gives '+(r*r)+' is '+r+'. So r = '+r+' cm.','(a)'),
gxA('(b) What is the circumference of the circle in cm?',Cc,'cm',pi.t+' × '+(2*r)+' = '+gxN(Cc)+' cm.','(b)')],
{pat:'C13',pi:pi.t,p:{r:r},lab:['Area = '+gxN(A)+' cm²'],ans:[r*r,r,Cc]});});
/* 14 clock hand */
gxReg('Circles#14',function(){
var pi=gxPi(),r=pick(pi.rs),t=pick([15,30,45]),Cc=pi.m(2*r),dist=Cc*t/60; if(!gxAllOk(pi,[Cc,dist]))return null;
var O=[120,65],R=50,f=gxC(O,R),i; for(i=0;i<12;i++){f+=gxLn(gxV(O,R,90-30*i),gxV(O,R-5,90-30*i),'fl',' stroke-width="1.6"');}
var ang=90-t*6,E=gxV(O,R,ang),T0=gxV(O,R,90);
f+=gxArc(O,R+5,ang,90,'fq')+gxLn(O,T0,'fk')+gxLn(O,E,'fk',' stroke-dasharray="4 3"')+gxDot(O)+gxLb([O[0]-22,O[1]-24],r+' cm');
var said=t===15?'quarter':t===30?'half':'three-quarters';
return gxQ('Circles',f,'A clock face. The minute hand is '+r+' centimetres long and starts at 12. The red curve shows the path of its tip in '+t+' minutes.',
'The minute hand of a clock is '+r+' cm long. At 12 o’clock it points straight up. '+pi.say,
[gxA('(a) The tip of the minute hand goes round a circle in 60 minutes. What is the circumference of this circle in cm?',Cc,'cm',pi.t+' × '+(2*r)+' = '+gxN(Cc)+' cm.','(a)'),
gxA('(b) How far does the tip move in '+t+' minutes, in cm? ('+t+' minutes is '+(t/15)+'/4 of an hour.)',dist,'cm',(t/15)+'/4 × '+gxN(Cc)+' = '+gxN(dist)+' cm.','(b)')],
{pat:'C14',pi:pi.t,p:{r:r,t:t},lab:[r+' cm'],ans:[Cc,dist]});});
/* ===== SHADED AREA AND PERIMETER ===== */
function gxRP(x,y,w,h){return 'M'+fN(x)+' '+fN(y)+' h'+fN(w)+' v'+fN(h)+' h'+fN(-w)+' Z';}
function gxCP(c,r){var q=fN(r-.2); return 'M'+fN(c[0]+r)+' '+fN(c[1])+' A'+q+' '+q+' 0 1 0 '+fN(c[0]-r)+' '+fN(c[1])+' A'+q+' '+q+' 0 1 0 '+fN(c[0]+r)+' '+fN(c[1])+' Z';}
function gxSh(d,ev){return '';}
function gxAt(c,r,a1,a2){var rr=Math.abs(Math.abs(a2-a1)-180)<.5?r-.2:r; return 'A'+fN(rr)+' '+fN(rr)+' 0 '+(Math.abs(a2-a1)>180?1:0)+' '+(a2=R)return null; var A1=pi.m(R*R),A2=pi.m(r*r),A=A1-A2,P=pi.m(2*(R+r)); if(!gxAllOk(pi,[A1,A2,A,P]))return null;
var sc=Math.min(48/R,48/R),O=[120,65],Rp=R*sc,rp=r*sc;
var f=gxSh(gxCP(O,Rp)+' '+gxCP(O,rp),1)+gxC(O,Rp)+gxC(O,rp)+gxDot(O)+gxLn(O,gxV(O,rp,135))+gxLn(O,gxV(O,Rp,-45))+gxLb(gxV(O,rp*.55,135+(rp<14?0:0)).map(function(v,i){return v+(i?-9:-14);}),'r = '+r+' cm')+gxLb(gxV(O,Rp+4,-45).map(function(v,i){return v+(i?8:24);}),'R = '+R+' cm');
return gxQ('Shaded area',f,'Two circles with the same centre. The inner circle has radius '+r+' centimetres and the outer circle has radius '+R+' centimetres. The ring between them is shaded.',
'Two circles have the same centre. The radius of the small circle is '+r+' cm and the radius of the big circle is '+R+' cm. The ring between the two circles is shaded. '+pi.say,
[gxA('What is the area of the big circle in cm²?',A1,'cm²',pi.t+' × '+R+' × '+R+' = '+gxN(A1)+' cm².'),
gxA('What is the area of the small circle in cm²?',A2,'cm²',pi.t+' × '+r+' × '+r+' = '+gxN(A2)+' cm².'),
gxA('(a) What is the area of the shaded ring in cm²?',A,'cm²',gxN(A1)+' − '+gxN(A2)+' = '+gxN(A)+' cm².','(a)'),
gxA('(b) What is the total length of the two edges of the ring (both circumferences) in cm?',P,'cm',pi.t+' × '+(2*R)+' + '+pi.t+' × '+(2*r)+' = '+gxN(P)+' cm.','(b)')],
{pat:'S4',pi:pi.t,p:{R:R,r:r},lab:['r = '+r+' cm','R = '+R+' cm'],ans:[A1,A2,A,P],sa:A,sc:sc});});
/* 5 four quarter circles in the corners of a square, centre part shaded */
gxReg('Shaded area#5',function(){
var pi=gxPi(),r=pick(pi.rs),s=2*r,A=s*s-pi.m(r*r),P=pi.m(2*r); if(!gxAllOk(pi,[A,P]))return null;
var sp=88,x0=120-sp/2,y0=21,h=sp/2,corner={tl:[x0,y0],tr:[x0+sp,y0],br:[x0+sp,y0+sp],bl:[x0,y0+sp]};
var d='M'+gxP([x0+h,y0+sp])+' '+gxAt(corner.br,h,180,90)+' '+gxAt(corner.tr,h,270,180)+' '+gxAt(corner.tl,h,0,-90)+' '+gxAt(corner.bl,h,90,0)+' Z';
d=d.replace(/A(\S+) (\S+) 0 0 1/g,'A$1 $2 0 0 1');
var f=gxSh(d)+gxSq(x0,y0,sp)+gxArc(corner.bl,h,0,90)+gxArc(corner.br,h,90,180)+gxArc(corner.tr,h,180,270)+gxArc(corner.tl,h,270,360)+gxLb([120,y0+sp+10],s+' cm');
return gxQ('Shaded area',f,'A square of side '+s+' centimetres. A quarter circle of radius '+r+' centimetres is drawn at each corner. The middle part that is outside all four quarter circles is shaded.',
'The figure shows a square of side '+s+' cm. A quarter circle of radius '+r+' cm is drawn at each of the four corners. The middle part outside the quarter circles is shaded. '+pi.say,
[gxA('The four quarter circles together make one whole circle of radius '+r+' cm. What is its area in cm²?',pi.m(r*r),'cm²',pi.t+' × '+r+' × '+r+' = '+gxN(pi.m(r*r))+' cm².'),
gxA('(a) What is the area of the shaded part in cm²?',A,'cm²',(s*s)+' − '+gxN(pi.m(r*r))+' = '+gxN(A)+' cm².','(a)'),
gxA('(b) What is the perimeter of the shaded part in cm? (It is made of the four curved edges.)',P,'cm','The four quarter circles’ curves make one circumference: '+pi.t+' × '+(2*r)+' = '+gxN(P)+' cm.','(b)')],
{pat:'S5',pi:pi.t,p:{s:s},lab:[s+' cm'],ans:[pi.m(r*r),A,P],sa:A,sc:sp/s});});
/* 6 four semicircles inside a square, petals shaded */
gxReg('Shaded area#6',function(){
var pi=gxPi(),r=pick(pi.rs),s=2*r,A=2*pi.m(r*r)-s*s,P=4*pi.m(r); if(!gxAllOk(pi,[A,P])||pi.m(r*r)*2<=s*s)return null;
var sp=88,x0=120-sp/2,y0=21,h=sp/2,ctr=[120,y0+h],d='',k;
for(k=0;k<4;k++){var rot=function(p){var dx=p[0]-ctr[0],dy=-(p[1]-ctr[1]),a=k*90*GXD,nx=dx*Math.cos(a)-dy*Math.sin(a),ny=dx*Math.sin(a)+dy*Math.cos(a); return [ctr[0]+nx,ctr[1]-ny];};
var P0=rot([x0,y0+sp]),P1=rot(ctr),c1=rot([x0+h,y0+sp]),c2=rot([x0,y0+h]);
d+='M'+gxP(P0)+' A'+fN(h)+' '+fN(h)+' 0 0 1 '+gxP(P1)+' A'+fN(h)+' '+fN(h)+' 0 0 1 '+gxP(P0)+' Z ';}
var f=gxSh(d)+gxSq(x0,y0,sp)+gxArc([120,y0+sp],h,0,180)+gxArc([120,y0],h,180,360)+gxArc([x0,y0+h],h,-90,90)+gxArc([x0+sp,y0+h],h,90,270)+gxLb([120,y0+sp+10],s+' cm');
return gxQ('Shaded area',f,'A square of side '+s+' centimetres. A semicircle is drawn inside the square on each side, using the side as its diameter. The four petal-shaped overlaps are shaded.',
'A semicircle is drawn inside a square of side '+s+' cm on each of its four sides, using each side as a diameter. The four petal-shaped parts where the semicircles overlap are shaded. '+pi.say,
[gxA('What is the total area of the four semicircles in cm²? (They make two whole circles of radius '+r+' cm.)',2*pi.m(r*r),'cm²','2 × '+pi.t+' × '+r+' × '+r+' = '+gxN(2*pi.m(r*r))+' cm².'),
gxA('(a) The four semicircles cover the square exactly, and the petals are counted twice. What is the area of the shaded petals in cm²?',A,'cm²',gxN(2*pi.m(r*r))+' − '+(s*s)+' = '+gxN(A)+' cm².','(a)'),
gxA('(b) What is the perimeter of the shaded petals in cm? (Eight quarter-circle curves, the same as 4 × π × '+r+'.)',P,'cm','4 × '+pi.t+' × '+r+' = '+gxN(P)+' cm.','(b)')],
{pat:'S6',pi:pi.t,p:{s:s},lab:[s+' cm'],ans:[2*pi.m(r*r),A,P],sa:A,sc:sp/s});});
/* 7 picture frame: shaded border */
gxReg('Shaded area#7',function(){
var l=rnd(10,26),w=rnd(7,18),t=rnd(1,3); if(w>=l||2*t+2>=w)return null; var il=l-2*t,iw=w-2*t,Ai=il*iw,A=l*w-Ai,Pi=2*(il+iw);
var sc=Math.min(150/l,86/w),lp=l*sc,wp=w*sc,x0=120-lp/2,y0=65-wp/2-4,tp=t*sc;
var f=gxSh(gxRP(x0,y0,lp,wp)+' '+gxRP(x0+tp,y0+tp,lp-2*tp,wp-2*tp),1)+gxPg([[x0,y0],[x0+lp,y0],[x0+lp,y0+wp],[x0,y0+wp]])+gxPg([[x0+tp,y0+tp],[x0+lp-tp,y0+tp],[x0+lp-tp,y0+wp-tp],[x0+tp,y0+wp-tp]])+gxLb([120,y0+wp+10],l+' cm')+gxLb([x0+lp+24,65-4],w+' cm')+gxLb([x0-22,65-4+wp/4],t+' cm')+gxLn([x0,65-4+wp/4],[x0+tp,65-4+wp/4],'fk');
return gxQ('Shaded area',f,'A rectangular photo frame '+l+' centimetres long and '+w+' centimetres wide. The border is '+t+' centimetres wide all the way round and is shaded.',
'A picture frame is a rectangle '+l+' cm long and '+w+' cm wide. The shaded border is '+t+' cm wide all the way round. The picture inside is also a rectangle.',
[gxA('What is the length of the picture in cm? (Take away the border from both ends.)',il,'cm',l+' − 2 × '+t+' = '+il+' cm.'),
gxA('What is the width of the picture in cm?',iw,'cm',w+' − 2 × '+t+' = '+iw+' cm.'),
gxA('(a) What is the area of the picture (the white part) in cm²?',Ai,'cm²',il+' × '+iw+' = '+Ai+' cm².','(a)'),
gxA('(b) What is the area of the shaded border in cm²?',A,'cm²',l+' × '+w+' − '+Ai+' = '+A+' cm².','(b)')],
{pat:'S7',p:{l:l,w:w,t:t},lab:[l+' cm',w+' cm',t+' cm'],ans:[il,iw,Ai,A],sa:A,sc:sc});});
/* 8 rectangle with a triangle: shade the two outer triangles */
gxReg('Shaded area#8',function(){
var l=rnd(8,24),w=rnd(5,14),a=rnd(2,l-2),T=l*w/2,A=l*w-T; if(T!==Math.round(T))return null;
var sc=Math.min(150/l,66/w),lp=l*sc,wp=w*sc,x0=120-lp/2,y0=24,E=[x0+a*sc,y0+wp],Aa=[x0,y0+wp],B=[x0+lp,y0+wp],C=[x0+lp,y0],D=[x0,y0];
var f=gxSh('M'+gxP(Aa)+' L'+gxP(E)+' L'+gxP(D)+' Z M'+gxP(E)+' L'+gxP(B)+' L'+gxP(C)+' Z')+gxPg([D,C,B,Aa])+gxLn(D,E)+gxLn(C,E)+gxVL(Aa,gxCen([D,C,B,Aa]),'A')+gxVL(B,gxCen([D,C,B,Aa]),'B')+gxVL(C,gxCen([D,C,B,Aa]),'C')+gxVL(D,gxCen([D,C,B,Aa]),'D')+gxTx([E[0],E[1]+10],'E',0,14)+gxLb([120,y0-9],l+' cm')+gxLb([x0+lp+24,y0+wp/2],w+' cm')+gxLn([x0,y0+wp+22],[E[0],y0+wp+22],'fl',' stroke-width="1.6"')+gxLn([x0,y0+wp+18],[x0,y0+wp+26])+gxLn([E[0],y0+wp+18],[E[0],y0+wp+26])+gxLb([(x0+E[0])/2,y0+wp+33],a+' cm');
return gxQ('Shaded area',f,'Rectangle ABCD is '+l+' centimetres long and '+w+' centimetres wide. E is a point on AB with AE equal to '+a+' centimetres. The two triangles AED and EBC are shaded.',
'ABCD is a rectangle, '+l+' cm long and '+w+' cm wide. E is a point on AB, and AE = '+a+' cm. Triangles AED and EBC are shaded.'+GXNS,
[gxA('What is the area of the rectangle in cm²?',l*w,'cm²',l+' × '+w+' = '+(l*w)+' cm².'),
gxA('(a) What is the area of the unshaded triangle DEC in cm²? (Its base is DC = '+l+' cm and its height is '+w+' cm.)',T,'cm²','1/2 × '+l+' × '+w+' = '+gxN(T)+' cm².','(a)'),
gxA('(b) What is the total area of the shaded triangles in cm²?',A,'cm²',(l*w)+' − '+gxN(T)+' = '+gxN(A)+' cm².','(b)')],
{pat:'S8',p:{l:l,w:w,a:a},lab:[l+' cm',w+' cm',a+' cm'],ans:[l*w,T,A],sa:A,sc:sc});});
/* 9 rectangle, M and N midpoints, shade triangle DMN */
gxReg('Shaded area#9',function(){
var l=2*rnd(3,10),w=2*rnd(2,7),R=l*w,U=R*5/8,A=R*3/8; if(A!==Math.round(A))return null;
var sc=Math.min(160/l,82/w),lp=l*sc,wp=w*sc,x0=120-lp/2,y0=62-wp/2,Aa=[x0,y0+wp],B=[x0+lp,y0+wp],C=[x0+lp,y0],D=[x0,y0],M=[x0+lp/2,y0+wp],N=[x0+lp,y0+wp/2],cen=gxCen([Aa,B,C,D]);
var f=gxSh('M'+gxP(D)+' L'+gxP(M)+' L'+gxP(N)+' Z')+gxPg([D,C,B,Aa])+gxLn(D,M)+gxLn(M,N)+gxLn(N,D)+gxTk(Aa,M)+gxTk(M,B)+gxTk(B,N)+gxTk(N,C)+gxVL(Aa,cen,'A')+gxVL(B,cen,'B')+gxVL(C,cen,'C')+gxVL(D,cen,'D')+gxTx([M[0],M[1]+11],'M',0,14)+gxTx([N[0]+11,N[1]],'N',0,14)+gxLb([120,y0-9],l+' cm')+gxLb([x0-26,62],w+' cm');
return gxQ('Shaded area',f,'Rectangle ABCD is '+l+' centimetres long and '+w+' centimetres wide. M is the midpoint of AB and N is the midpoint of BC. Triangle DMN is shaded.',
'ABCD is a rectangle, '+l+' cm long and '+w+' cm wide. M is the midpoint of AB and N is the midpoint of BC. Triangle DMN is shaded.'+GXNS,
[gxA('What is the area of the rectangle in cm²?',R,'cm²',l+' × '+w+' = '+R+' cm².'),
gxA('What is the total area of the three unshaded triangles AMD, MBN and NCD in cm²? (AMD = 1/4 of the rectangle, MBN = 1/8, NCD = 1/4.)',U,'cm²','5/8 × '+R+' = '+gxN(U)+' cm².'),
gxA('(a) What fraction of the rectangle is shaded? Type the number of eighths.',3,'eighths','1 − 5/8 = 3/8, so 3 eighths are shaded.','(a)'),
gxA('(b) What is the area of the shaded triangle in cm²?',A,'cm²',R+' − '+gxN(U)+' = '+gxN(A)+' cm².','(b)')],
{pat:'S9',p:{l:l,w:w},lab:[l+' cm',w+' cm'],ans:[R,U,3,A],sa:A,sc:sc});});
/* 10 two overlapping squares */
gxReg('Shaded area#10',function(){
var a=rnd(6,14),b=rnd(6,14),c=rnd(2,Math.min(a,b)-2),U=a*a+b*b-c*c,Pm=4*a+4*b-4*c;
var sc=Math.min(92/(a+b-c),92/(a+b-c)),ext=(a+b-c)*sc,x0=120-ext/2,yb=65+ext/2,s1=a*sc,s2=b*sc,cc=c*sc;
var S1=[x0,yb-s1],S2=[x0+(a-c)*sc,yb-(a-c)*sc-s2];
var f=gxSh(gxRP(x0+(a-c)*sc,yb-a*sc,cc,cc))+gxSq(S1[0],S1[1],s1)+gxSq(S2[0],S2[1],s2)+gxLb([x0+s1/2-6,yb+10],a+' cm')+gxLb([S2[0]+s2/2+6,S2[1]-9],b+' cm')+gxLb([x0+(a-c)*sc+cc/2,yb-a*sc+cc/2],c+'');
return gxQ('Shaded area',f,'Two squares overlap at a corner. The first square has side '+a+' centimetres and the second has side '+b+' centimetres. The small square where they overlap has side '+c+' centimetres and is shaded.',
'Two squares overlap. One has a side of '+a+' cm and the other has a side of '+b+' cm. The shaded overlapping part is a small square of side '+c+' cm.'+GXNS,
[gxA('(a) What is the area of the shaded part in cm²?',c*c,'cm²',c+' × '+c+' = '+(c*c)+' cm².','(a)'),
gxA('What is the total of the two big squares’ areas in cm²?',a*a+b*b,'cm²',a+' × '+a+' + '+b+' × '+b+' = '+(a*a+b*b)+' cm².'),
gxA('(b) What is the area of the whole figure in cm²? (The shaded part was counted twice.)',U,'cm²',(a*a+b*b)+' − '+(c*c)+' = '+U+' cm².','(b)')],
{pat:'S10',p:{a:a,b:b,c:c},lab:[a+' cm',b+' cm',c+''],ans:[c*c,a*a+b*b,U],sa:c*c,sc:sc});});
/* 11 circles in a row inside a rectangle, shade the rest */
gxReg('Shaded area#11',function(){
var pi=gxPi(),r=pick(pi.rs),d=2*r,n=rnd(2,4),Rr=n*d*d,Cs=n*pi.m(r*r),A=Rr-Cs; if(!gxAllOk(pi,[Cs,A]))return null;
var sc=Math.min(150/(n*d),70/d),dp=d*sc,x0=120-n*dp/2,y0=65-dp/2,p=gxRP(x0,y0,n*dp,dp),i;
for(i=0;i=R)return null; var D=2*R,d=2*r,Ab=pi.m(R*R)/2,As=pi.m(r*r)/2,A=Ab-As,P=pi.m(D)/2+pi.m(d)/2+(D-d); if(!gxAllOk(pi,[Ab,As,A,P]))return null;
var sc=Math.min(190/D,60/R),Dp=D*sc,dp=d*sc,x0=120-Dp/2,yb=76,Rp=Dp/2,rp=dp/2;
var path='M'+gxP([x0+dp,yb])+' L'+gxP([x0+Dp,yb])+' A'+fN(Rp-.2)+' '+fN(Rp-.2)+' 0 0 0 '+gxP([x0,yb])+' A'+fN(rp-.2)+' '+fN(rp-.2)+' 0 0 1 '+gxP([x0+dp,yb])+' Z';
var f=gxSh(path)+gxLn([x0,yb],[x0+Dp,yb])+gxArc([x0+Rp,yb],Rp,0,180)+gxArc([x0+rp,yb],rp,0,180)+gxLn([x0,yb+9],[x0+dp,yb+9],'fk')+gxLn([x0,yb+5],[x0,yb+13],'fk')+gxLn([x0+dp,yb+5],[x0+dp,yb+13],'fk')+gxLb([x0+rp,yb+20],d+' cm')+gxLn([x0,yb+29],[x0+Dp,yb+29],'fl',' stroke-width="1.6"')+gxLn([x0,yb+25],[x0,yb+33])+gxLn([x0+Dp,yb+25],[x0+Dp,yb+33])+gxLb([x0+Dp/2,yb+39],'D = '+D+' cm');
return gxQ('Shaded area',f,'A big semicircle of diameter '+D+' centimetres. A small semicircle of diameter '+d+' centimetres is cut out from one end of the same diameter line. The remaining part is shaded.',
'A semicircle of diameter '+D+' cm has a smaller semicircle of diameter '+d+' cm cut out of it, as shown, with both diameters on the same line. The remaining part is shaded. '+pi.say,
[gxA('What is the area of the big semicircle in cm²? (Its radius is '+R+' cm.)',Ab,'cm²','1/2 × '+pi.t+' × '+R+' × '+R+' = '+gxN(Ab)+' cm².'),
gxA('What is the area of the small semicircle in cm²? (Its radius is '+r+' cm.)',As,'cm²','1/2 × '+pi.t+' × '+r+' × '+r+' = '+gxN(As)+' cm².'),
gxA('(a) What is the area of the shaded part in cm²?',A,'cm²',gxN(Ab)+' − '+gxN(As)+' = '+gxN(A)+' cm².','(a)'),
gxA('(b) What is the perimeter of the shaded part in cm? (Big curve + small curve + the straight part of length '+(D-d)+' cm.)',P,'cm','1/2 × '+pi.t+' × '+D+' + 1/2 × '+pi.t+' × '+d+' + '+(D-d)+' = '+gxN(P)+' cm.','(b)')],
{pat:'S13',pi:pi.t,p:{R:R,r:r},lab:['D = '+D+' cm',d+' cm'],ans:[Ab,As,A,P],sa:A,sc:sc});});
var EXTOP=[['all','Any topic'],['Ratio','Ratio'],['Percentage','Percentage'],['Fractions','Fractions'],['Average','Average'],['Speed','Speed'],['Algebra','Algebra'],['Area','Area'],['Rate','Rate'],['Angles','Angles'],['Circles','Circles'],['Shaded area','Shaded area and perimeter'],['Whole numbers','Coins and whole numbers'],['Volume','Volume and tanks']];
function exBuild(){
var tpOf=function(k){return EXT[k]===EXT.Coins?'Whole numbers':k.replace(/#.*$/,'').trim();};
var topics={}; Object.keys(EXT).forEach(function(k){var tp=tpOf(k); (topics[tp]=topics[tp]||[]).push(k);});
var tp=EXTOPIC==='all'||!topics[EXTOPIC]?pick(Object.keys(topics)):EXTOPIC;
var q=EXT[pick(topics[tp])](); q.step=0; q.res=[]; q.done=false; return q;
}
function openExam(){ if(modalOpen())return; pauseGame(); exTopics(); exNew(); ob('exModal').hidden=false; try{ob('exPanel').focus({preventScroll:true});}catch(_){} }
function exTopics(){
var box=ob('exTopics'); box.innerHTML='';
EXTOP.forEach(function(t){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=t[1]; b.setAttribute('aria-pressed',String(EXTOPIC===t[0])); b.addEventListener('click',function(){EXTOPIC=t[0]; exTopics(); exNew();}); box.appendChild(b);});
var m=document.createElement('button'); m.type='button'; m.className='btn small'; m.textContent=EXMODE==='step'?'Mode: step by step (tap for exam style)':'Mode: exam style (tap for step by step)'; m.addEventListener('click',function(){EXMODE=EXMODE==='step'?'exam':'step'; exTopics(); exNew();}); box.appendChild(m);
}
function exNew(){
EXQ=exBuild(); var q=EXQ; var ef=ob('exFig'); ef.innerHTML=q.fig||''; if(q.fig){ef.setAttribute('role','img'); ef.setAttribute('aria-label',q.figAlt||'Diagram');} else {ef.removeAttribute('role'); ef.removeAttribute('aria-label');} ob('exIntro').textContent=q.intro; ob('exRes').hidden=true; ob('exWork').innerHTML=''; ob('exMsg').textContent=''; ob('exTopicTag').textContent=q.topic;
exShow();
}
function exParts(){return EXQ.steps.filter(function(s){return s.part;});}
function exShow(){
var q=EXQ, box=ob('exSteps'); box.innerHTML=''; ob('exCheck').hidden=false; ob('exCheck').textContent=EXMODE==='step'?'Check this step':'Check my answers';
var list=EXMODE==='step'?[q.steps[q.step]]:exParts();
if(EXMODE==='step'){var tot=q.steps.length; ob('exMsg').textContent='Step '+(q.step+1)+' of '+tot+'.';}
else ob('exMsg').textContent='Answer every part, then check. No hints until you check.';
list.forEach(function(s,i){var wrap=document.createElement('div'); wrap.style.margin='10px 0';
var p=document.createElement('p'); var b=document.createElement('b'); b.textContent=(EXMODE==='exam'?exExamText(s):s.q); p.appendChild(b); wrap.appendChild(p);
var lab=document.createElement('label'); lab.textContent='Answer'+(s.unit?' ('+s.unit+')':'')+': '; var inp=document.createElement('input'); inp.type='text'; inp.setAttribute('inputmode','decimal'); inp.className='exin'; inp.style.cssText='width:8em;font:inherit;padding:6px;border:2px solid var(--line);border-radius:8px'; inp.setAttribute('aria-label','Answer'); lab.appendChild(inp); wrap.appendChild(lab);
var fb=document.createElement('p'); fb.className='lvldesc exfb'; fb.setAttribute('aria-live','polite'); wrap.appendChild(fb); box.appendChild(wrap); inp._s=s; inp._fb=fb;
inp.addEventListener('keydown',function(e){if(e.key==='Enter')exCheck();});});
var f=box.querySelector('input'); if(f)try{f.focus({preventScroll:true});}catch(_){}
}
function exExamText(s){return s.q.replace(/^\((a|b)\) /,'($1) ');}
function exNum(v){var n=parseFloat(String(v).replace(/[,$\s]/g,'')); return isNaN(n)?null:n;}
function exFinish(){
var q=EXQ; q.done=true; var parts=exParts(), tot=0, got=0; parts.forEach(function(s){tot+=s.m; if(s.ok)got+=s.m;});
ob('exRes').hidden=false; ob('exCheck').hidden=true; ob('exSteps').querySelectorAll('input').forEach(function(i){i.disabled=true;});
ob('exScore').textContent='Score: '+got+' / '+tot+'.';
try{q.steps.forEach(function(s){if(typeof s.ok==='boolean'){recordSkill({kicker:'Exam: '+(q.topic||'practice'),kickShow:'Exam practice',gi:0,lv:S.level,rec:false},s.ok?1:0,1500); S.st.a=(S.st.a||0)+1; if(s.ok)S.st.f=(S.st.f||0)+1;}});}catch(e){}
var ol=ob('exWork'); ol.innerHTML=''; q.steps.forEach(function(s){var li=document.createElement('li'); li.innerHTML=' '; li.firstChild.textContent=s.q.replace(/^\([ab]\) /,''); li.lastChild.textContent=s.why; ol.appendChild(li);});
if(got>=tot*.75){S.st.exm=(S.st.exm||0)+1; var n=got===tot?10:6, g=roll(n); Object.keys(g).forEach(function(id){inv[id]=(inv[id]||0)+g[id];}); sfx('done'); ob('exMsg').textContent='Well done!'; try{var xr=ob('exRw'); if(!xr){xr=document.createElement('div'); xr.id='exRw'; xr.style.cssText='display:flex;flex-wrap:wrap;gap:6px;margin:6px 0'; ob('exMsg').parentNode.insertBefore(xr,ob('exMsg').nextSibling);} xr.innerHTML=''; Object.keys(g).forEach(function(id){var t=typeById(+id); if(t)xr.appendChild(chip(t,'+'+g[id]+' '+t.name));});}catch(e){}} else {sfx('wrong'); ob('exMsg').textContent='Read the working below, then try a new question.';}
changed();
}
function exCheck(){
var q=EXQ; if(!q||q.done)return; if(q.next){var nf=q.next; q.next=null; nf(); return;} var ins=[].slice.call(ob('exSteps').querySelectorAll('input'));
if(EXMODE==='step'){
var inp=ins[0], s=inp._s, v=exNum(inp.value); if(v===null){inp._fb.textContent='Type a number first.'; return;}
var ok=Math.abs(v-s.ans)<1e-6; s.tries=(s.tries||0)+1;
if(ok){s.ok=s.tries===1; inp._fb.textContent='Correct. '+s.why; inp.disabled=true; sfx('right');
var last=q.step>=q.steps.length-1; ob('exCheck').textContent=last?'See my score':'Next step'; q.next=function(){if(last){exFinish();} else {q.step++; exShow();}}; return;}
sfx('wrong'); if(s.tries>=2){inp._fb.textContent='Not yet. The answer is '+s.ans+'. '+s.why; inp.disabled=true; s.ok=false; var last2=q.step>=q.steps.length-1; ob('exCheck').textContent=last2?'See my score':'Next step'; q.next=function(){if(last2){exFinish();} else {q.step++; exShow();}};}
else inp._fb.textContent='Not quite. Read the step again and try once more.';
return;
}
var any=false; ins.forEach(function(i){var v=exNum(i.value); if(v===null){any=true;}}); if(any){ob('exMsg').textContent='Type an answer for every part first.'; return;}
ins.forEach(function(i){var v=exNum(i.value); i._s.ok=Math.abs(v-i._s.ans)<1e-6;}); exFinish();
}
var MSRN=[['Crotchet',1,'1 beat','♩'],['Quavers',1,'2 quavers = 1 beat','♫'],['Minim',2,'2 beats','𝅗𝅥'],['Rest',1,'1 beat of silence','𝄽']];
var MSRQ=[
['Fill both bars with exactly 4 beats each and use at least one minim.',function(r){return r[0].beats===4&&r[1].beats===4&&r.some(function(b){return b.n.indexOf(2)>=0;});}],
['Make bar 2 busy: use quavers in bar 2 and make both bars exactly 4 beats.',function(r){return r[0].beats===4&&r[1].beats===4&&r[1].n.indexOf(1)>=0;}],
['Make a rhythm with at least one rest. Both bars must be exactly 4 beats.',function(r){return r[0].beats===4&&r[1].beats===4&&r.some(function(b){return b.n.indexOf(3)>=0;});}],
['Make bar 2 copy bar 1. Both bars must be exactly 4 beats.',function(r){return r[0].beats===4&&r[1].beats===4&&r[0].n.join()===r[1].n.join();}],
['Use all four kinds of note or rest at least once. Both bars must be exactly 4 beats.',function(r){var u={}; r.forEach(function(b){b.n.forEach(function(x){u[x]=1;});}); return r[0].beats===4&&r[1].beats===4&&Object.keys(u).length===4;}]
];
function rhyBeats(b){var t=0; b.n.forEach(function(x){t+=MSRN[x][1];}); return t;}
function msRhy(a){
if(!MSST||MSST.k!=='rhy'){MSST={k:'rhy',q:pick(MSRQ),r:[{n:[],beats:0},{n:[],beats:0}],done:false,bpm:100};}
var q=MSST.q, d=document.createElement('p'); d.className='lvldesc'; d.textContent='4/4 time: each bar has 4 beats. Tap a note to add it to a bar. Goal: '+q[0]; a.appendChild(d);
MSST.r.forEach(function(bar,bi){bar.beats=rhyBeats(bar); var row=document.createElement('div'); row.style.cssText='display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:6px 0;padding:6px;border:2px solid '+(bar.beats===4?'#2e9e5b':bar.beats>4?'#c0392b':'var(--line)')+';border-radius:8px;min-height:50px';
var lb=document.createElement('b'); lb.textContent=(bi+1)+' '+'\u25CF'.repeat(Math.min(4,bar.beats))+'\u25CB'.repeat(Math.max(0,4-bar.beats)); lb.setAttribute('aria-label','Bar '+(bi+1)+', '+bar.beats+' of 4 beats'); row.appendChild(lb);
bar.n.forEach(function(x,i){var c=document.createElement('span'); c.style.cssText='padding:2px 6px;border-radius:8px;background:#fff3d6;color:#222;display:inline-flex'; c.innerHTML=noteSvg(x,34); row.appendChild(c);});
a.appendChild(row);});
var pal=document.createElement('div'); pal.className='labrow'; [0,1].forEach(function(bi){});
var which=MSST.bar||0; var sel=document.createElement('div'); sel.className='seg'; [0,1].forEach(function(bi){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.innerHTML=''+(bi+1)+'Bar'; b.setAttribute('aria-label','Add to bar '+(bi+1)); b.setAttribute('aria-pressed',String(which===bi)); b.addEventListener('click',function(){MSST.bar=bi; msRender2();}); sel.appendChild(b);}); a.appendChild(sel);
MSRN.forEach(function(nv,i){var b=document.createElement('button'); b.type='button'; b.className='labtile'; b.style.cssText='min-width:76px;min-height:76px;text-align:center'; b.innerHTML=noteSvg(i,40)+''+nv[2]+''; b.setAttribute('aria-label',nv[0]+', '+nv[2]); b.addEventListener('click',function(){ if(MSST.done)return; var bar=MSST.r[which]; if(bar.beats+nv[1]>4){ob('msMsg').textContent='That bar is full. A bar holds exactly 4 beats.'; sfx('wrong'); return;} bar.n.push(i); bar.beats=rhyBeats(bar); if(S.sound&&i!==3)msDrum(i===1?'hat':'snare',.4,0); if(bar.beats===4&&which===0){MSST.bar=1;} msRender2(); }); pal.appendChild(b);}); a.appendChild(pal);
var r2=document.createElement('div'); r2.className='row2'; [['Play',function(){ if(!S.sound){hint('Turn Sound on to listen.',true); return;} var beat=60/MSST.bpm, t=0; MSST.r.forEach(function(bar){var first=true; bar.n.forEach(function(x){ var dur=MSRN[x][1]*beat; if(x===0){msDrum(first?'kick':'snare',.45,t); msDrum('hat',.2,t);} else if(x===1){msDrum('hat',.35,t); msDrum('hat',.28,t+beat/2); if(first)msDrum('kick',.45,t);} else if(x===2){msDrum(first?'kick':'snare',.45,t); msDrum('crash',.16,t);} first=false; t+=dur;}); }); }],['Slower/Faster',function(){MSST.bpm=MSST.bpm===100?150:MSST.bpm===150?70:100; ob('msMsg').textContent='Tempo: '+(MSST.bpm===70?'slow':MSST.bpm===100?'medium':'fast');}],['Undo',function(){var bar=MSST.r[which]; if(!bar.n.length&&which===1){bar=MSST.r[0];} bar.n.pop(); msRender2();}],['Clear',function(){MSST.r=[{n:[],beats:0},{n:[],beats:0}]; MSST.bar=0; msRender2();}],['Check',function(){ if(MSST.done)return; MSST.r.forEach(function(b){b.beats=rhyBeats(b);}); if(q[1](MSST.r)){MSST.done=true; msAward('Great rhythm! Bars with exactly 4 beats keep the music steady.');} else {ob('msMsg').textContent='Not quite. Check each bar has exactly 4 beats and re-read the goal.'; sfx('wrong');}}],['New goal',function(){MSST=null; msRender();}]].forEach(function(x){r2.appendChild(msIcon(MSIC[x[0]],x[0],x[0]==='Check'?'btn primary':'btn',x[1]));}); a.appendChild(r2);
}
var starBtn=null, starTgt=null, camDirV=null, starAim={c:null,t:0,l:0};
function starCheck(){
if(!starBtn){starBtn=document.createElement('button'); starBtn.type='button'; starBtn.className='btn primary'; starBtn.style.cssText='position:absolute;left:50%;transform:translateX(-50%);bottom:150px;z-index:6'; starBtn.hidden=true; starBtn.addEventListener('click',function(){if(starTgt)openStar(starTgt);}); view.appendChild(starBtn);}
var show=false; if(stars&&stars.visible&&!paused&&!modalOpen()&&conList.length&&(!visiting||visiting.room)){
if(!camDirV)camDirV=new THREE.Vector3(); camera.getWorldDirection(camDirV); var best=null,bd=Math.cos(6*Math.PI/180);
conList.forEach(function(c){var d=camDirV.dot(c.dir); if(d>bd){bd=d; best=c;}}); var nowT=Date.now(); if(best){ if(starAim.c!==best){starAim.c=best; starAim.t=nowT;} starAim.l=nowT; if(nowT-starAim.t>=900){starTgt=best; starBtn.textContent='Read about '+best.name; show=true;} } else if(starAim.c&&nowT-starAim.l<500&&!starBtn.hidden){show=true;} else {starAim.c=null;}
}
if(starBtn.hidden===show)starBtn.hidden=!show;
}
var FORMS=[{id:'mosquito',name:'Mosquito'},{id:'lizard',name:'Lizard'},{id:'cockroach',name:'Cockroach'},{id:'housefly',name:'Housefly'}], TIERS=['','Tough ','Strong ','Giant ','Boss '];
/* ---------- pest notes: why each one matters in nature ---------- */
var PESTINFO={
mosquito:{role:'Nature needs mosquitoes: fish, frogs, dragonflies and geckos eat them.',sub:'Annoying at home, useful in nature',
f:['Only female mosquitoes bite. They need the blood to make their eggs. Males drink flower nectar.','Mosquito larvae live in water and are food for fish and other small animals.','Adults are food for dragonflies, frogs, geckos, bats and birds.','Some mosquitoes carry germs, like the Aedes mosquito that spreads dengue. Tip out stagnant water every week so they cannot breed.'],
q:'Which mosquitoes bite people: males or females?',a:'Females. They need blood to make their eggs.'},
lizard:{role:'Nature needs geckos: they eat mosquitoes, flies and moths, and snakes and birds eat them.',sub:'The house gecko, a helpful neighbour',
f:['House geckos eat mosquitoes, flies, moths and even small cockroaches.','They climb walls and ceilings because their toes have tiny hairs that grip surfaces.','If a predator grabs the tail, it can drop off. The gecko escapes and grows a new tail.','Geckos are food for snakes, birds and cats, so they are part of the food chain.'],
q:'How does a gecko escape when a predator grabs its tail?',a:'The tail drops off, and the gecko grows a new one.'},
housefly:{role:'Nature needs flies: geckos, spiders, frogs and birds eat them, and fly maggots help break down rotting waste.',sub:'Small, noisy and good at spreading germs',
f:['A housefly tastes with its feet, so it can tell what it lands on.','Flies lay eggs on rotting food and rubbish. The young, called maggots, help break down waste.','Flies are food for geckos, spiders, frogs and birds.','A fly that lands on rubbish can carry germs on its feet to your food. Cover food, close bin lids and wash your hands before eating.'],
q:'Why should we cover our food at home?',a:'Flies can carry germs on their feet from rubbish to food.'},
cockroach:{role:'Nature needs cockroaches: they break down dead leaves and animals, and birds and lizards eat them.',sub:'Most cockroaches never come indoors',
f:['There are thousands of kinds of cockroaches. Only a few live in homes.','Outdoors, cockroaches eat dead plants and animals and turn them into goodness for the soil.','Birds, lizards and frogs eat them.','At home they look for food and water. Keep food covered, wipe up crumbs and close the bin to keep them out.'],
q:'What do cockroaches do for nature outdoors?',a:'They break down dead plants and animals, which helps the soil.'}
};
var pestBtn=null, pestTgt=null;
function pestCheck(){
if(!pestBtn){pestBtn=document.createElement('button'); pestBtn.type='button'; pestBtn.className='btn primary'; pestBtn.style.cssText='position:absolute;left:50%;transform:translateX(-50%);bottom:150px;z-index:6'; pestBtn.hidden=true; pestBtn.addEventListener('click',function(){if(pestTgt)openPest(pestTgt);}); view.appendChild(pestBtn);}
var show=false;
if(!paused&&!modalOpen()&&zombies.length&&(!visiting||visiting.room)){
if(!camDirV)camDirV=new THREE.Vector3(); camera.getWorldDirection(camDirV); var best=null,bd=Math.cos(14*Math.PI/180);
zombies.forEach(function(z){var dx=z.x-player.x,dy=z.y+.5-(player.y+EYE),dz=z.z-player.z,L=Math.hypot(dx,dy,dz); if(L>1&&L<10){var d=(dx*camDirV.x+dy*camDirV.y+dz*camDirV.z)/L; if(d>bd){bd=d;best=z;}}});
if(best&&PESTINFO[best.form]){pestTgt=best.form; pestBtn.textContent='Read about the '+best.fname; show=true;}
}
if(pestBtn.hidden===show)pestBtn.hidden=!show;
}
function openPest(f){
var d=PESTINFO[f]; if(!d||modalOpen())return; pauseGame(); var nm=FORMS.filter(function(x){return x.id===f;})[0].name;
try{document.querySelector('#tourPanel .tmaths b').textContent='Quick question'; var nn=document.querySelectorAll('#tourPanel .lvldesc'); nn[nn.length-1].textContent='Pests can be a nuisance, but every one has a job in nature.';}catch(e){}
document.getElementById('tourT').textContent=nm; document.getElementById('tourSub').textContent=d.sub;
document.getElementById('tourF').innerHTML=d.f.map(function(x){return '
'+x+'
';}).join('')+photoLink(nm);
document.getElementById('tourQ').textContent=d.q; var a=document.getElementById('tourA'); a.textContent=d.a; a.hidden=true; document.getElementById('tourShow').hidden=false;
var md=document.getElementById('tourModal'); md.hidden=false; md.scrollTop=0;
S.st.ps=S.st.ps||{}; if(!S.st.ps[nm]){S.st.ps[nm]=1; var got=roll(5); Object.keys(got).forEach(function(id){inv[id]=(inv[id]||0)+got[id];}); sfx('done'); document.getElementById('tourSub').textContent=d.sub+' · First time: +5 blocks! ('+Object.keys(S.st.ps).length+'/4 pests read)'; changed();}
}
var BMK={easy:{t:-1,cap:2,sp:1.6,v:.7,n:-1,h:3},normal:{t:0,cap:5,sp:1,v:1,n:0,h:2},hard:{t:1,cap:6,sp:.7,v:1.25,n:1,h:1.5}};
function bmk(){return BMK[S.bm]||BMK.normal;}
function zTier(){return Math.min(4,Math.max(0,Math.floor((S.st.z||0)/4)+(S.diff==='hard'?1:0)+bmk().t));}
function mkZombie(x,y,z,kd,form,tier){
form=form||'zombie'; tier=tier|0;
var H=HABITS[kd], g=new THREE.Group(), mats=[];
function M(c){var m=new THREE.MeshLambertMaterial({color:col(c)}); mats.push(m); return m;}
function B(w,h,d,m,px,py,pz){var b=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),m); b.position.set(px,py,pz); g.add(b); return b;}
var PAL={zombie:[H.skin,H.shirt,H.pants],ghost:['#f1f4ff','#e6eaff','#dfe4ff'],witch:['#6fa84a','#4b2a7a','#2a1a46'],skeleton:['#e9e5d6','#d8d3c0','#c4bfab']}[form]||[H.skin,H.shirt,H.pants];
var l1,l2, eyeR=new THREE.MeshBasicMaterial({color:0xd8232a}), eye=new THREE.MeshBasicMaterial({color:0x111111}), ly=2.25;
if(form==='mosquito'){
var mb=M('#3b3b42'), ms=M('#d8d8dc'), mw=M('#cfe8ff'), ml=M('#26262b'); mw.transparent=true; mw.opacity=.55;
B(.22,.22,.8,mb,0,1.1,0); B(.235,.235,.1,ms,0,1.1,-.12); B(.235,.235,.1,ms,0,1.1,.1); B(.27,.27,.27,mb,0,1.14,.52); B(.05,.05,.55,ml,0,1.08,.95);
B(.75,.03,.3,mw,-.55,1.32,0); B(.75,.03,.3,mw,.55,1.32,0);
B(.03,.03,.4,ml,-.07,1.18,.8).rotation.y=.25; B(.03,.03,.4,ml,.07,1.18,.8).rotation.y=-.25;
var lg=[]; [.2,0,-.2].forEach(function(zz,n){ [-1,1].forEach(function(sd){ var up=B(.04,.5,.04,ml,sd*.2,.82,zz); up.rotation.z=sd*.7; var lo=B(.035,.5,.035,ml,sd*.38,.45,zz); lo.rotation.z=-sd*.25; lg.push(up);}); });
l1=lg[0]; l2=lg[1];
B(.08,.08,.02,eyeR,-.09,1.2,.66); B(.08,.08,.02,eyeR,.09,1.2,.66); ly=1.95;
} else if(form==='housefly'){
var hb=M('#4a4d52'), hs=M('#8d9096'), hw=M('#dfeeff'), hl=M('#2a2b2e'), he=new THREE.MeshBasicMaterial({color:0xb3262e}); hw.transparent=true; hw.opacity=.5;
B(.3,.28,.36,hb,0,1.05,.08); B(.26,.24,.4,hs,0,1.0,-.28); B(.28,.24,.2,hb,0,1.1,.36);
B(.09,.13,.1,he,-.15,1.15,.42); B(.09,.13,.1,he,.15,1.15,.42);
B(.7,.02,.4,hw,-.5,1.22,-.1); B(.7,.02,.4,hw,.5,1.22,-.1);
var hg=[]; [.14,0,-.14].forEach(function(zz){ [-1,1].forEach(function(sd){ var up=B(.035,.4,.035,hl,sd*.16,.8,zz); up.rotation.z=sd*.6; hg.push(up); }); });
l1=hg[0]; l2=hg[1]; ly=1.75;
} else if(form==='lizard'){
var lk=M('#a9b27a'), ld=M('#7b845a'), lb=M('#efe6c8');
B(.44,.22,.9,lk,0,.5,0); B(.3,.2,.34,lk,0,.54,.62); B(.3,.14,.5,ld,0,.46,-.66); B(.2,.1,.5,ld,0,.43,-1.08); B(.1,.08,.42,ld,0,.41,-1.45);
B(.14,.02,.14,ld,-.08,.62,.1); B(.14,.02,.14,ld,.1,.62,-.2); B(.3,.04,.4,lb,0,.38,.0);
l1=B(.14,.32,.14,ld,-.3,.2,.3); l2=B(.14,.32,.14,ld,.3,.2,.3); B(.14,.32,.14,ld,-.3,.2,-.25); B(.14,.32,.14,ld,.3,.2,-.25);
B(.07,.07,.02,eye,-.1,.62,.8); B(.07,.07,.02,eye,.1,.62,.8); ly=1.35;
} else {
var cb=M('#5a3216'), cl=M('#8a5527'), cd=M('#2e1a0b');
B(.5,.16,1.0,cb,0,.3,-.05); B(.44,.03,.78,cl,0,.4,-.12); B(.03,.04,.78,cd,0,.42,-.12); B(.4,.17,.26,cl,0,.31,.5); B(.28,.13,.2,cb,0,.29,.7);
B(.03,.03,.9,cd,-.1,.34,1.15).rotation.y=.22; B(.03,.03,.9,cd,.1,.34,1.15).rotation.y=-.22; B(.04,.05,.18,cd,-.1,.26,-.62); B(.04,.05,.18,cd,.1,.26,-.62);
var cl6=[]; [.35,0,-.35].forEach(function(zz){ [-1,1].forEach(function(sd){ var u=B(.3,.05,.05,cd,sd*.3,.24,zz); u.rotation.z=-sd*.3; var d2=B(.05,.2,.05,cd,sd*.45,.11,zz); cl6.push(u);}); });
l1=cl6[0]; l2=cl6[1];
B(.07,.07,.02,eyeR,-.1,.31,.8); B(.07,.07,.02,eyeR,.1,.31,.8); ly=1.1;
}
var lab=labelSprite((tier>=3?TIERS[tier]:'')+FORMS.filter(function(f){return f.id===form;})[0].name+' \u00b7 '+H.name.replace('Too Much Screen Time','Screen Time')); lab.scale.set(2,.32,1); lab.position.y=ly; g.add(lab);
g.scale.setScalar(1.3+.1*tier);
g.position.set(x,y,z); scene.add(g);
return {g:g,mats:mats,l1:l1,l2:l2,x:x,y:y,z:z,vy:0,onG:false,hp:6+2*tier,hit:0,ph:Math.random()*6,kx:0,kz:0,kind:kd,form:form,tier:tier,fname:FORMS.filter(function(f){return f.id===form;})[0].name};
}
function zWet(x,z){return terr==='ocean'&&!solidAt(Math.floor(x),-1,Math.floor(z));}
function zSolid(x,y,z){var cx=Math.floor(x),cz=Math.floor(z),y0=Math.floor(y+.05),y1=Math.floor(y+1.05); return solidAt(cx,y0,cz)||solidAt(cx,y1,cz)||(cx>=0&&cz>=0&&cx=0;i--)killZ(i);}
function killZ(i){
var k=HABITS[zombies[i].kind], zt=zombies[i].tier|0, nb=3+zt, pr=(PESTINFO[zombies[i].form]||{}).role||''; removeZ(i); S.st.z=(S.st.z||0)+1; var got=roll(nb);
Object.keys(got).forEach(function(id){inv[id]=(inv[id]||0)+got[id];});
hint('Cleared '+k.name+'! +'+nb+' blocks. '+k.tip,true); sfx('done'); changed(); habitToast('You cleared '+k.name+'! +'+nb+' blocks',k.tip+(pr?' '+pr:''));
}
var htTimer=null, htPending=null;
function habitShow(t,p){var e=document.getElementById('htoast'); if(!e)return; document.getElementById('htT').textContent=t; document.getElementById('htP').textContent=p; e.hidden=false; clearTimeout(htTimer); htTimer=setTimeout(function(){e.hidden=true;},9000);}
function habitToast(t,p){if(modalOpen()||modal&&!modal.hidden){htPending=[t,p]; return;} habitShow(t,p);}
document.getElementById('htoast').addEventListener('click',function(){this.hidden=true;});
function spawnZombie(){
if(!S.zom||visiting)return;
for(var t=0;t<14;t++){
var x=rnd(1,LAND-2)+.5, z=rnd(1,LAND-2)+.5; if(Math.hypot(x-player.x,z-player.z)<12)continue;
var cx=Math.floor(x),cz=Math.floor(z), y=surfaceY(cx,cz), g=0; while(solidAt(cx,y,cz)&&g0?1:0)):0;
zTimer-=dt; if(zTimer<=0){zTimer=Math.max(9,22-3*zTier())*bmk().sp; if(zombies.length0)hurtCd-=dt;
if(hearts<5){regenT+=dt; if(regenT>=20){regenT=0; hearts++; drawHearts();}}
for(var i=zombies.length-1;i>=0;i--){
var z=zombies[i];
if(!S.zom){removeZ(i); continue;}
var dx=player.x-z.x, dz=player.z-z.z, d=Math.sqrt(dx*dx+dz*dz)||1;
var sp=(z.hit>0?0:(d<2.5?.6:.8))*bmk().v, vx=dx/d*sp+z.kx, vz=dz/d*sp+z.kz, k=Math.pow(.02,dt); z.kx*=k; z.kz*=k;
var sc=null; try{sc=petScare(z.x,z.z);}catch(e){} if(sc){var fs=1.5*bmk().v; vx=sc.ax*fs; vz=sc.az*fs; try{petSay(sc.o,PABIL[sc.o.pt.k][2],sc.o.pt.k); if(!S.st.psc){S.st.psc=1; hint(sc.o.pt.n+' scared the pest away. Grown-up pets that are fed and watered protect you.',true);} S.st.psn=(S.st.psn||0)+(Math.random()
0)z.hit-=dt;
var em=z.hit>0?0x990000:0; z.mats.forEach(function(m){m.emissive.setHex(em);});
z.g.position.set(z.x,z.y+((z.form==='mosquito'||z.form==='housefly')?.45+Math.sin(z.ph*.5)*.12:0),z.z); z.g.rotation.y=Math.atan2(dx,dz);
if(d<1&&!z.sc&&Math.abs(player.y-z.y)<1.5&&hurtCd<=0&&z.hit<=0){hurtPlayer(dx/d,dz/d); if(hearts>=5&&zombies.length===0)break;}
}
}
var duel=null;
function startDuel(z){
if(modalOpen()||!z)return false;
pauseGame(); mode='duel'; diag=null; duel={z:z,need:Math.max(2,3+(z.tier|0)+bmk().n),got:0,done:false,faint:false}; setupOpen=false; setSetupVisible(false); lastKey='';
modal.hidden=false; modal.scrollTop=0; showQuestion();
try{document.getElementById('quizPanel').focus({preventScroll:true});}catch(_){}
return true;
}
function duelMiss(why){
var k=HABITS[duel.z.kind]; stopTimer(); locked2=true; entryLocked=true; setEntryEnabled(false);
hearts--; drawHearts(); sfx('hurt'); playFx('hurt'); hurtCd=2; regenT=0;
if(hearts<=0){duel.faint=true; el.fb.textContent=(why==='time'?'Time is up. ':'Not quite. ')+'The answer was '+cq.answer+'. '+k.name+' knocked you out!'; el.next.textContent='Wake up at home';}
else{el.fb.textContent=(why==='time'?'Time is up. ':'Not quite. ')+'The answer was '+cq.answer+'. '+k.name+' hit you. '+hearts+' heart'+(hearts===1?'':'s')+' left. Try again.'; el.next.textContent='Next flash question';}
el.fb.className='fb bad'; el.next.hidden=false; if(S.ans!=='mc')document.getElementById('entry').hidden=true; save(); updateStats();
try{el.next.focus({preventScroll:true});}catch(_){}
}
function duelHit(){
var k=HABITS[duel.z.kind]; duel.got++; duel.z.hit=.25; sfx('hit');
if(duel.got>=duel.need){
duel.done=true; if(hearts<5){hearts++; drawHearts();} var i=zombies.indexOf(duel.z);
if(i>=0)killZ(i);
var rr=maybeRare(.8,'hard'); el.fb.textContent='Correct! You cleared '+k.name+'! +'+(3+(duel.z.tier|0))+' blocks and +1 heart. '+(rr?rr+' ':'')+k.tip; el.fb.className='fb good'; el.next.textContent='Back to the world';
} else {
el.fb.textContent='Correct! Hit! '+(duel.need-duel.got)+' more to clear '+k.name+'.'; el.fb.className='fb good'; el.next.textContent='Next flash question';
}
el.next.hidden=false; if(S.ans!=='mc')document.getElementById('entry').hidden=true; updateHotbar(); updateStats(); save(); updateGoals();
try{el.next.focus({preventScroll:true});}catch(_){}
}
function attackZ(){
if(!zombies.length||paused||!camera)return false;
var o=rayO(), d=new THREE.Vector3(); camera.getWorldDirection(d);
var best=-1,bt=1e9;
zombies.forEach(function(z,i){
var vx=z.x-o.x,vy=z.y+.9-o.y,vz=z.z-o.z, t=vx*d.x+vy*d.y+vz*d.z; if(t<0||t>3.6)return;
var px=vx-d.x*t,py=vy-d.y*t,pz=vz-d.z*t; if(Math.sqrt(px*px+py*py+pz*pz)>.8)return;
if(t0)){
if(gated()||classBlocked()||visiting){hint('Do your learning first.',true); return true;}
z.kx=ex/ed*2; z.kz=ez/ed*2; startDuel(z); return true;
}
z.hp-=2; z.hit=.25; z.kx=ex/ed*6; z.kz=ez/ed*6; sfx('hit');
if(z.hp<=0)killZ(best); else hint('Hit! '+Math.ceil(z.hp/2)+' more swing'+(z.hp<=2?'':'s')+'.');
return true;
}
/* ---------- game loop ---------- */
var lastT=0, fxT=0, flashEl=document.getElementById('flash');
var reduceMotion=false; try{reduceMotion=window.matchMedia('(prefers-reduced-motion: reduce)').matches;}catch(_){}
function update(dt){
try{accTick(dt);}catch(e){}
var fwd=0,str=0;
if(keys.KeyW)fwd+=1; if(keys.KeyS)fwd-=1; if(keys.KeyD)str+=1; if(keys.KeyA)str-=1;
fwd+=-joy.y; str+=joy.x;
var len=Math.sqrt(fwd*fwd+str*str); if(len>1){fwd/=len; str/=len;}
if(keys.ArrowLeft)yaw+=1.9*dt; if(keys.ArrowRight)yaw-=1.9*dt;
if(keys.ArrowUp)pitch=clamp(pitch+1.4*dt,-1.5,1.5); if(keys.ArrowDown)pitch=clamp(pitch-1.4*dt,-1.5,1.5);
if(boarded){sail(fwd,str,dt); zUpdate(dt); return;}
if(TZ.cur){ if(TZ.busy)return; if(TZ.cur.fly){TZ.flyMove(dt,fwd,str); return;} }
var wet=(terr==='ocean'||(TZ.cur&&TZ.cur.water))&&player.y<-.2&&!solidAt(Math.floor(player.x),Math.floor(player.y+.3),Math.floor(player.z));
var s=Math.sin(yaw),c=Math.cos(yaw), spd=wet?SPEED*.6:SPEED; if(TZ.cur&&(TZ.run||keys.ShiftLeft||keys.ShiftRight))spd*=2.4; if(tired())spd*=.35; if(enOn()&&(fwd||str)&&!paused&&!modalOpen()){enDrain(EN_MOVE[enMode()]*dt);}
var vx=(-s*fwd+c*str)*spd, vz=(-c*fwd-s*str)*spd;
var nx=player.x+vx*dt; if(!collides(nx,player.y,player.z))player.x=nx;
var nz=player.z+vz*dt; if(!collides(player.x,player.y,nz))player.z=nz;
if(keys.Space&&player.onGround){player.vy=JUMP; player.onGround=false; sfx('jump');}
if(wet){ if(keys.Space)player.vy=player.y>-.95?JUMP:Math.min(6,player.vy+34*dt); else player.vy+=((-.75-player.y)*16-player.vy*8)*dt; }
else{ player.vy-=GRAV*dt; if(player.vy<-30)player.vy=-30; }
var ny=player.y+player.vy*dt; player.onGround=false;
if(collides(player.x,ny,player.z)){
if(player.vy<0){player.y=Math.floor(ny+1e-6)+1; player.onGround=true;}
else{player.y=Math.floor(ny+PH)-PH-1e-3;}
player.vy=0;
}else player.y=ny;
if(player.y<-D-4)goHome();
zUpdate(dt);
}
function frame(now){
if(STARM&&stars&&stars.visible&&!REDUCED_TWK()){var _t=now/1000; STARM[0].opacity=.55+.4*Math.sin(_t*1.7); STARM[1].opacity=.55+.4*Math.sin(_t*1.3+2.1); STARM[2].opacity=.55+.4*Math.sin(_t*2.1+4.2);}
requestAnimationFrame(frame);
var dt=Math.min(.05,(now-lastT)/1000); lastT=now; if(!(dt>0))return; planTick(dt); visitTick(dt); petTick(dt); skyTick(dt); brkTick(dt,now); bonTick(dt); sessTick(dt); try{jcTick(dt);}catch(e){}
if(!paused)update(dt);
if(dirty){rebuild(); dirty=false;}
if(terrDirty){buildTerrain(); terrDirty=false;}
try{if(tuftC&&Math.hypot(player.x-tuftC.x,player.z-tuftC.z)>3.5)buildTufts();}catch(e){}
EYEV.set(player.x,player.y+EYE,player.z); camera.position.copy(EYEV); camera.rotation.set(pitch,yaw,0); if(TP)tpUpdate(dt); try{starCheck();}catch(e){} try{pestCheck();}catch(e){} try{buildProps();}catch(e){} try{doorKnobs();}catch(e){} try{treeCheck();}catch(e){} try{rainTick(dt);}catch(e){} try{rbTick(dt);}catch(e){} try{dzTick(dt);}catch(e){} try{qwTick(dt);}catch(e){} try{halTick(dt); try{dropTick(dt);}catch(e){} try{xqTick(dt);}catch(e){}}catch(e){}
if(fxT>0){
fxT-=dt;
if(!reduceMotion){var a=Math.min(fxT,1)*.14; camera.position.x+=(Math.random()-.5)*a; camera.position.y+=(Math.random()-.5)*a; camera.position.z+=(Math.random()-.5)*a;}
}
skyG.position.copy(camera.position);
underEl.hidden=!((terr==='ocean'||(TZ.cur&&TZ.cur.water))&&camera.position.y<-.12);
if(!paused)updateTarget();
clouds.forEach(function(g,i){g.position.x+=(.35+i*.04)*dt; if(g.position.x>N+45)g.position.x=-45;});
var gmM=document.getElementById('gmModal'); if(!(gmM&&!gmM.hidden))renderer.render(scene,camera); /* the Game room covers the world: do not draw it behind the board */
}
/* ---------- maths questions ---------- */
function rnd(a,b){return a+Math.floor(Math.random()*(b-a+1));}
function pick(a){return a[rnd(0,a.length-1)];}
function shuffle(a){for(var i=a.length-1;i>0;i--){var j=rnd(0,i),t=a[i];a[i]=a[j];a[j]=t;} return a;}
function Q(kicker,text,answer,extra){var q={kicker:kicker,text:text,answer:answer}; if(extra)for(var k in extra)q[k]=extra[k]; return q;}
/* ---------- angle diagrams ---------- */
function fN(v){return v.toFixed(1);}
function arcP(cx,cy,r,a1,a2){
var d=a2-a1; while(d>Math.PI)d-=2*Math.PI; while(d<-Math.PI)d+=2*Math.PI;
return 'M'+fN(cx+r*Math.cos(a1))+' '+fN(cy+r*Math.sin(a1))+' A'+r+' '+r+' 0 0 '+(d>0?1:0)+' '+fN(cx+r*Math.cos(a2))+' '+fN(cy+r*Math.sin(a2));
}
function angMid(a1,a2){var d=a2-a1; while(d>Math.PI)d-=2*Math.PI; while(d<-Math.PI)d+=2*Math.PI; return a1+d/2;}
function angMark(cx,cy,a1,a2,txt,known,small){
var r=small?22:28, m=angMid(a1,a2), lr=r+16, d=Math.abs(a2-a1); while(d>Math.PI)d=Math.abs(d-2*Math.PI);
if(d<.7)lr=r+24;
return ''+txt+'';
}
function svgWrap(inner){return '';}
function straightFig(known,R,knownRight){
var Px=120,Py=108,L=84,rad=-R*Math.PI/180, ex=Px+L*Math.cos(rad), ey=Py+L*Math.sin(rad);
var inner='';
inner+=angMark(Px,Py,0,rad,knownRight?known+'°':'?',knownRight);
inner+=angMark(Px,Py,rad,-Math.PI,knownRight?'?':known+'°',!knownRight);
return svgWrap(inner);
}
function triFig(x,y){
var z=180-x-y, rx=x*Math.PI/180, ry=y*Math.PI/180, rz=z*Math.PI/180;
var L=Math.min(190,92*Math.sin(rz)/(Math.sin(rx)*Math.sin(ry)));
var A={x:0,y:0}, B={x:L,y:0}, AC=L*Math.sin(ry)/Math.sin(rz), C={x:AC*Math.cos(rx),y:-AC*Math.sin(rx)};
var minX=Math.min(0,C.x), maxX=Math.max(L,C.x), tx=(240-(maxX-minX))/2-minX, ty=118;
[A,B,C].forEach(function(v){v.x+=tx; v.y+=ty;});
var inner='';
inner+=angMark(A.x,A.y,0,-rx,x+'°',true,true);
inner+=angMark(B.x,B.y,Math.PI,-Math.PI+ry,y+'°',true,true);
inner+=angMark(C.x,C.y,Math.atan2(A.y-C.y,A.x-C.x),Math.atan2(B.y-C.y,B.x-C.x),'?',false,true);
return svgWrap(inner);
}
function pointFig(a,b,c){
var Px=120,Py=65,L=56,s0=-Math.PI/2+rnd(-20,20)*Math.PI/180, r1=s0, r2=s0+a*Math.PI/180, r3=r2+b*Math.PI/180;
var inner='';
[r1,r2,r3].forEach(function(r){inner+='';});
inner+=angMark(Px,Py,r1,r2,a+'°',true,true)+angMark(Px,Py,r2,r3,b+'°',true,true)+angMark(Px,Py,r3,r1+2*Math.PI,'?',false,true);
return svgWrap(inner);
}
function anglesQ(){
var t=rnd(0,2);
if(t===0){var a=rnd(35,140),right=rnd(0,1)===1; return Q('Angles','The angles on a straight line are shown. One angle is '+a+'°. Find the angle marked ?',180-a,{fig:straightFig(a,right?a:180-a,right),figAlt:'A straight line with a ray from it. One angle is '+a+' degrees and the other is marked with a question mark.'});}
if(t===1){var x=rnd(30,80),y=rnd(30,80); return Q('Angles','Two angles of a triangle are '+x+'° and '+y+'°. Find the angle marked ?',180-x-y,{fig:triFig(x,y),figAlt:'A triangle with angles '+x+' degrees and '+y+' degrees. The third angle is marked with a question mark.'});}
var a2,b2,c2; do{a2=rnd(7,14)*10; b2=rnd(7,14)*10; c2=360-a2-b2;}while(c2<60||c2>170);
return Q('Angles','Three angles meet at a point. Two of them are '+a2+'° and '+b2+'°. Find the angle marked ?',c2,{fig:pointFig(a2,b2,c2),figAlt:'Three angles meeting at a point. Two are '+a2+' degrees and '+b2+' degrees. The third is marked with a question mark.'});
}
function unitOpts(ans,u){var out=[ans*10,ans/10,ans+u,ans-u,ans+u/10].filter(function(v){return v>0&&v!==ans&&v===Math.round(v);}); return strOpts(ans,out);}
function unitsQ(level){
var U=[['m','cm',100],['kg','g',1000],['ℓ','mℓ',1000],['km','m',1000]];
if(level===0){var u=pick(U.slice(0,3)),k=rnd(2,9); return Q('Units',k+' '+u[0]+' = ? '+u[1],k*u[2],{options:unitOpts(k*u[2],u[2])});}
if(level===1){
if(rnd(0,1)){var h=rnd(1,6),m=pick([15,30,45]); return Q('Units',h+' h '+m+' min = ? min',h*60+m,{options:strOpts(h*60+m,[h*100+m,h*60+m+10,h*60+m-10,(h+1)*60+m])});}
var u2=pick([['km','m',1000],['kg','g',1000],['ℓ','mℓ',1000]]),k2=rnd(1,9),r=rnd(1,19)*50; return Q('Units',k2+' '+u2[0]+' '+r+' '+u2[1]+' = ? '+u2[1],k2*1000+r,{options:strOpts(k2*1000+r,[k2*100+r,k2*1000-r,k2*1000+r+100,k2+r])});
}
if(level===2){
var u3=pick(U),t=rnd(11,99);
if(rnd(0,1))return Q('Units',tenths(t)+' '+u3[0]+' = ? '+u3[1],t*u3[2]/10,{options:unitOpts(t*u3[2]/10,u3[2])});
return Q('Units',(t*u3[2]/10)+' '+u3[1]+' = ? '+u3[0],tenths(t),{options:strOpts(tenths(t),[String(t),tenths(t+10),tenths(Math.max(1,t-10)),String(t*10)])});
}
var u4=pick([U[3],U[1],U[2]]),h2=rnd(105,995); return Q('Units',(h2/100).toFixed(2)+' '+u4[0]+' = ? '+u4[1],h2*10,{options:unitOpts(h2*10,1000)});
}
function strOpts(ans,wr){
var s=[String(ans)]; wr.forEach(function(w){w=String(w); if(s.indexOf(w)<0&&s.length<4)s.push(w);});
var f=1; while(s.length<4){var w2=String(parseFloat(ans)+f*7); if(s.indexOf(w2)<0)s.push(w2); f++;}
return shuffle(s);
}
/* ---------- clock face ---------- */
function clockFig(h,m){
var cx=120,cy=65,r=58,i,inner='';
for(i=0;i<60;i++){var a=i*6*Math.PI/180-Math.PI/2,big=i%5===0,r1=r-(big?8:4);
inner+='';}
for(i=1;i<=12;i++){var b=i*30*Math.PI/180-Math.PI/2; inner+=''+i+'';}
var ha=((h%12)+m/60)*30*Math.PI/180-Math.PI/2, ma=m*6*Math.PI/180-Math.PI/2;
inner+='';
inner+='';
inner+='';
return svgWrap(inner);
}
function tm(h,m){return h+':'+(m<10?'0':'')+m;}
function clockQ(five){
var h=rnd(1,12), m=five?pick([0,0,15,30,45,5,10,20,25,35,40,50,55]):rnd(0,59), ans=tm(h,m);
var h2=h%12+1, h0=(h+10)%12+1, sw=m%5===0?tm(m/5||12,h*5%60):tm(h2,m);
var wr=[tm(h2,m),tm(h0,m),sw,tm(h,(m+5)%60),tm(h,(m+55)%60),tm(h,(m+1)%60)];
return Q('Telling time','What time does the clock show? (short hand = hours, red long hand = minutes)',ans,{fig:clockFig(h,m),figAlt:'A clock face showing '+h+' hours and '+m+' minutes.',options:strOpts(ans,wr)});
}
/* ---------- syllabus gap questions (graphs, pie, time, shapes, factors, fractions, percentage, volume, circle) ---------- */
function gcd2(a,b){return b?gcd2(b,a%b):a;}
var COLS=['#e8a33d','#4aa3b8','#d96f6f','#7aa95c','#9a7cc4'];
function picFig(names,counts,scale){
var inner='',i,j;
for(i=0;i'+names[i]+'';
for(j=0;j';
}
inner+='= '+scale+'';
return svgWrap(inner);
}
function picQ(scale){
var names=shuffle(['Cats','Dogs','Fish','Birds']).slice(0,3), counts=[rnd(2,6),rnd(2,6),rnd(2,6)];
while(counts[0]===counts[1])counts[1]=rnd(2,6);
var t=rnd(0,1), fig=picFig(names,counts,scale), alt='A picture graph of pets. Each circle stands for '+scale+'.';
if(t===0)return Q('Picture graph','Each circle stands for '+scale+'. How many '+names[0].toLowerCase()+' are there?',counts[0]*scale,{fig:fig,figAlt:alt});
var a=Math.max(counts[0],counts[1]), n1=counts[0]>counts[1]?names[0]:names[1], n2=counts[0]>counts[1]?names[1]:names[0];
return Q('Picture graph','Each circle stands for '+scale+'. How many more '+n1.toLowerCase()+' than '+n2.toLowerCase()+'?',Math.abs(counts[0]-counts[1])*scale,{fig:fig,figAlt:alt});
}
function barFig(names,vals,step){
var max=Math.ceil(Math.max.apply(null,vals)/step)*step, X0=36,B=108,T=14,inner='',k,n=names.length,bw=Math.min(36,150/n);
for(k=0;k<=max;k+=step){var y=B-(k/max)*(B-T); inner+=''+k+'';}
inner+='';
var gap=(226-X0)/n;
for(k=0;k'+names[k]+'';}
return svgWrap(inner);
}
function barQ(){
var step=pick([2,5,10]), names=shuffle(['Red','Blue','Green','Pink','Gold']).slice(0,4), vals=names.map(function(){return rnd(1,6)*step;});
var t=rnd(0,2), fig=barFig(names,vals,step), alt='A bar graph of favourite colours.';
if(t===0){var i=rnd(0,3); return Q('Bar graph','How many children chose '+names[i]+'?',vals[i],{fig:fig,figAlt:alt});}
if(t===1){var tot=vals.reduce(function(a,b){return a+b;},0); return Q('Bar graph','How many children are there altogether?',tot,{fig:fig,figAlt:alt});}
var i2=rnd(0,3),j2=(i2+1+rnd(0,2))%4; return Q('Bar graph','How many more children chose '+names[i2]+' than '+names[j2]+'? (Answer 0 if fewer.)',Math.max(0,vals[i2]-vals[j2]),{fig:fig,figAlt:alt});
}
function tableFig(names,vals){
var n=names.length,w=200/n,inner='',i;
for(i=0;i'+names[i]+''+vals[i]+''; if(i)inner+='';}
return svgWrap(inner);
}
function tableQ(){
var names=['Mon','Tue','Wed','Thu'], vals=names.map(function(){return rnd(5,40);}), t=rnd(0,1), fig=tableFig(names,vals), alt='A table of books borrowed each day.';
if(t===0)return Q('Table','The table shows books borrowed each day. How many books were borrowed on all 4 days?',vals.reduce(function(a,b){return a+b;},0),{fig:fig,figAlt:alt});
var i=rnd(0,1),j=rnd(2,3); return Q('Table','The table shows books borrowed each day. How many more books were borrowed on '+names[i]+' than on '+names[j]+'? (Answer 0 if fewer.)',Math.max(0,vals[i]-vals[j]),{fig:fig,figAlt:alt});
}
function pieFig(parts){
var cx=120,cy=65,r=56,a=-Math.PI/2,inner='';
parts.forEach(function(p,i){
var a2=a+p.f*2*Math.PI, large=p.f>.5?1:0;
inner+='';
var m=(a+a2)/2, lr=p.f>.3?r*.55:r*.68;
inner+=''+p.n+''+p.l+'';
a=a2;});
return svgWrap(inner);
}
function pieQ(pct){
var sets=[[['1/2',.5],['1/4',.25],['1/4',.25]],[['1/2',.5],['1/4',.25],['1/8',.125],['1/8',.125]],[['1/5',.2],['3/10',.3],['1/2',.5]],[['1/4',.25],['1/2',.5],['1/8',.125],['1/8',.125]]];
if(pct)sets=sets.concat([[['50%',.5],['25%',.25],['25%',.25]],[['40%',.4],['30%',.3],['20%',.2],['10%',.1]],[['45%',.45],['35%',.35],['20%',.2]]]);
var st=pick(sets), names=shuffle(['Bus','Walk','Car','MRT']).slice(0,st.length), tot=pick([40,80,120,160]);
var parts=st.map(function(x,i){return {n:names[i],l:x[0],f:x[1]};}), i=rnd(0,st.length-1);
return Q('Pie chart','The pie chart shows how '+tot+' pupils come to school. '+(names[i]==='Walk'?'How many pupils walk to school?':'How many pupils go by '+(names[i]==='MRT'?'MRT':names[i].toLowerCase())+'?'),tot*st[i][1],{fig:pieFig(parts),figAlt:'A pie chart of how pupils travel to school with each part labelled.'});
}
function fmt12(mins){mins=((mins%1440)+1440)%1440; var h=Math.floor(mins/60),m=mins%60,ap=h>=12?'pm':'am',h12=h%12||12; return h12+':'+(m<10?'0':'')+m+' '+ap;}
function durQ(){
var h=rnd(1,3),m=pick([0,15,30,45]),st=rnd(7,11)*60+pick([0,15,30,45])+(rnd(0,1)?720:0), t=rnd(0,1), dur=h*60+m, ds=h+' h'+(m?' '+m+' min':'');
if(t===0){var ans=fmt12(st+dur); return Q('Time','A film starts at '+fmt12(st)+' and lasts '+ds+'. What time does it end?',ans,{options:strOpts(ans,[fmt12(st+dur+30),fmt12(st+dur-30),fmt12(st+dur+60),fmt12(st+dur-15)])});}
var ans2=ds.replace(' h',' h'); var out=function(x){var hh=Math.floor(x/60),mm=x%60; return hh+' h'+(mm?' '+mm+' min':'');};
return Q('Time','A lesson starts at '+fmt12(st)+' and ends at '+fmt12(st+dur)+'. How long is the lesson?',out(dur),{options:strOpts(out(dur),[out(dur+30),out(dur+60),out(Math.max(15,dur-30)),out(dur+15)])});
}
function h24Q(){var h=rnd(1,11),m=pick([0,15,30,45]); return Q('24-hour time',h+':'+(m<10?'0':'')+m+' pm in 24-hour time is? (for example 3:30 pm is 1530)',(h+12)*100+m,{min:1300,max:2359});}
function shapeQ(solid){
var A=solid?[
['Which solid has 6 square faces?','Cube',['Cuboid','Cone','Cylinder']],
['Which solid is shaped like a ball?','Sphere',['Cube','Cone','Cylinder']],
['Which solid has 2 circles as flat faces and one curved surface?','Cylinder',['Cone','Sphere','Cuboid']],
['Which solid has a point at the top and one circle at the bottom?','Cone',['Cylinder','Sphere','Cube']],
['Which solid has 6 rectangular faces and is not a cube?','Cuboid',['Sphere','Cone','Cylinder']],
['How many faces does a cube have?','6',['4','8','12']],
['How many edges does a cube have?','12',['6','8','10']],
['How many corners does a cuboid have?','8',['6','10','12']]
]:[
['Which shape has 4 equal sides and 4 square corners?','Square',['Rectangle','Triangle','Circle']],
['Which shape has 3 sides and 3 corners?','Triangle',['Square','Circle','Rectangle']],
['Which shape has 4 sides, 2 long and 2 short?','Rectangle',['Square','Triangle','Circle']],
['Which shape is round with no corners?','Circle',['Square','Triangle','Rectangle']],
['How many sides does a rectangle have?','4',['3','5','6']],
['Two half circles are put together. Which shape do they make?','Circle',['Square','Triangle','Rectangle']],
['Two identical triangles are put together to make a square. How many sides does the new shape have?','4',['3','5','6']]
];
var q=pick(A); return Q('Shapes',q[0],q[1],{options:strOpts(q[1],q[2])});
}
function factorQ(){
var t=rnd(0,2);
if(t===0){var N=pick([24,30,36,42,48,56,60,72,84,90]),ds=[2,3,4,5,6,7,8,9].filter(function(d){return N%d===0;}),nd=[2,3,4,5,6,7,8,9].filter(function(d){return N%d!==0;}),f=pick(ds); return Q('Factors','Which of these is a factor of '+N+'?',f,{options:shuffle([f].concat(shuffle(nd).slice(0,3)))});}
if(t===1){var M=pick([12,16,18,20,24,28,30,36,40,42]),c=0; for(var i=1;i<=M;i++)if(M%i===0)c++; return Q('Factors','How many factors does '+M+' have?',c,{min:2,max:12});}
var g=rnd(2,9),pq=pick([[2,3],[3,4],[2,5],[3,5],[4,5],[5,6],[2,7],[3,7]]); return Q('Factors','What is the biggest number that is a factor of both '+(g*pq[0])+' and '+(g*pq[1])+'?',g,{min:1,max:12});
}
function mixedQ(){
var d=pick([3,4,5,6,8]),q=rnd(1,4),r=rnd(1,d-1),n=q*d+r;
if(rnd(0,1)){var ans=q+' '+r+'/'+d; return Q('Mixed numbers',n+'/'+d+' as a mixed number is?',ans,{options:strOpts(ans,[(q+1)+' '+r+'/'+d,q+' '+(d-r)+'/'+d,r+' '+q+'/'+d])});}
return Q('Improper fractions',q+' '+r+'/'+d+' = ?/'+d,n,{min:2});
}
function fracMulQ(){
var t=rnd(0,3),d=pick([3,4,5,6,8]),a=rnd(1,d-1);
if(t===0){var k=rnd(2,9)*d; return Q('Fraction ×',a+'/'+d+' × '+k+' = ?',a*k/d,{min:1});}
if(t===1){var b=pick([2,3,4,5]),c=pick([2,3,4,5,6]),e=rnd(1,c-1),num=a*e,den=d*c,g=gcd2(num,den),ans=(num/g)+'/'+(den/g); if(den/g===1)return fracMulQ();
return Q('Fraction ×',a+'/'+d+' × '+e+'/'+c+' = ?',ans,{options:strOpts(ans,[(a+e)+'/'+(d+c),num+'/'+(d+c),(num/g)+'/'+(den/g+1),(num/g+1)+'/'+(den/g)].filter(function(x){return x!==ans;}))});}
if(t===2){var k2=pick([2,3,4,5]),dd=pick([2,3,4,5]); return Q('Fraction ÷','1/'+dd+' ÷ '+k2+' = 1/?',dd*k2);}
var k3=rnd(2,9),dv=pick([2,3,4,5]); return Q('Fraction ÷',k3+' ÷ 1/'+dv+' = ?',k3*dv);
}
function moneyPctQ(){
var t=rnd(0,4);
if(t===0){var P=rnd(1,10)*20,p=pick([10,20,25,50]); return Q('Discount','A bag costs $'+P+'. It is on sale at '+p+'% discount. What is the sale price in dollars?',P*(100-p)/100,{min:1});}
if(t===1){var B=rnd(1,10)*100; return Q('GST','A TV costs $'+B+' before GST. GST is 9%. What is the price with GST in dollars?',B*109/100,{min:1});}
if(t===2){var Pr=rnd(1,10)*100,r=rnd(1,5),y=rnd(1,3); return Q('Interest','Ben puts $'+Pr+' in a bank. The interest is '+r+'% per year. How much interest does he earn in '+y+(y===1?' year':' years')+'?',Pr*r*y/100,{min:1});}
if(t===3){var o=rnd(1,5)*20,pc=pick([10,20,25,50]),inc=rnd(0,1),nw=inc?o*(100+pc)/100:o*(100-pc)/100; return Q('Percentage change','A price goes from $'+o+' to $'+nw+'. What is the percentage '+(inc?'increase':'decrease')+'?',pc,{min:1,max:100});}
var pc2=pick([10,20,25,50]),N=rnd(1,10)*20; return Q('Find the whole',pc2+'% of a number is '+(N*pc2/100)+'. What is the number?',N,{min:1});
}
function algebraQ(){
var t=rnd(0,3);
if(t===0){var x=rnd(2,15),a=rnd(3,20); return Q('Algebra','x + '+a+' = '+(x+a)+'. Find x.',x,{min:1});}
if(t===1){var x2=rnd(2,12),k=rnd(2,9); return Q('Algebra',k+'x = '+(k*x2)+'. Find x.',x2,{min:1});}
if(t===2){var a3=rnd(2,9),k2=rnd(2,6),c=rnd(1,9); return Q('Algebra','If a = '+a3+', find '+k2+'a + '+c+'.',k2*a3+c,{min:1});}
var p=rnd(2,9),q=rnd(2,9); return Q('Algebra',p+'a + '+q+'a = ?a',p+q,{min:2});
}
function rateQ(){
var t=rnd(0,2),r=rnd(2,9),n=rnd(3,12);
if(t===0)return Q('Rate','A tap fills '+r+' ℓ of water every minute. How many litres does it fill in '+n+' minutes?',r*n,{min:1});
if(t===1)return Q('Rate','Apples cost $'+r+' per kg. How much do '+n+' kg cost in dollars?',r*n,{min:1});
return Q('Rate','A machine makes '+r+' toys every minute. How many minutes does it need to make '+(r*n)+' toys?',n,{min:1});
}
function volQ(){
var t=rnd(0,3);
if(t===0){var l=rnd(2,12),w=rnd(2,10),h=rnd(2,9); return Q('Volume','A cuboid is '+l+' cm long, '+w+' cm wide and '+h+' cm high. What is its volume in cm³?',l*w*h,{min:1,fig:cubFig(l+' cm',w+' cm',h+' cm'),figAlt:'A cuboid '+l+' cm long, '+w+' cm wide and '+h+' cm high.'});}
if(t===1){var e=rnd(2,10); return Q('Volume','A cube has edges of '+e+' cm. What is its volume in cm³?',e*e*e,{min:1});}
if(t===2){var e2=pick([2,3,4,5,6,10]); return Q('Volume','The volume of a cube is '+(e2*e2*e2)+' cm³. How long is one edge in cm?',e2,{min:1});}
var a=rnd(1,5),b=rnd(1,5),c=rnd(1,5); return Q('Volume','A tank is '+(10*a)+' cm long, '+(10*b)+' cm wide and '+(10*c)+' cm high. How many litres of water can it hold when full? (1 ℓ = 1000 cm³)',a*b*c,{min:1});
}
function circleQ(){
var r=pick([7,14,21,28,35,42,49]),t=rnd(0,5);
if(t===0)return Q('Circle','A circle has a radius of '+r+' cm. Use π = 22/7. What is its circumference in cm?',44*r/7,{min:1,fig:cirFig('r = '+r+' cm'),figAlt:'A circle with radius '+r+' cm marked.'});
if(t===1)return Q('Circle','A circle has a radius of '+r+' cm. Use π = 22/7. What is its area in cm²?',22*r*r/7,{min:1,fig:cirFig('r = '+r+' cm'),figAlt:'A circle with radius '+r+' cm marked.'});
if(t===2)return Q('Circle','A circle has a diameter of '+(2*r)+' cm. Use π = 22/7. What is its circumference in cm?',44*r/7,{min:1,fig:cirFig((2*r)+' cm',1),figAlt:'A circle with diameter '+(2*r)+' cm marked.'});
if(t===3)return Q('Circle','A circle has a diameter of '+(2*r)+' cm. Use π = 22/7. What is its area in cm²?',22*r*r/7,{min:1,fig:cirFig((2*r)+' cm',1),figAlt:'A circle with diameter '+(2*r)+' cm marked.'});
if(t===4)return Q('Circle','The circumference of a circle is '+(44*r/7)+' cm. Use π = 22/7. What is its radius in cm?',r,{min:1});
return Q('Circle','A semicircle has a diameter of '+(2*r)+' cm. Use π = 22/7. What is the area of the semicircle in cm²?',11*r*r/7,{min:1});
}
function money(c){return '$'+(c/100).toFixed(2);}
function tenths(n){return (n/10).toFixed(1);}
function seqQ(step,start,len){
var arr=[],i; for(i=0;i=10?undefined:99});}
/* ---------- Secondary & post-secondary question helpers ---------- */
function ord(n){var r=n%100,s=(r>=11&&r<=13)?'th':({1:'st',2:'nd',3:'rd'}[n%10]||'th'); return n+s;}
function pn(n){return n<0?'(−'+(-n)+')':String(n);}
function sg(n){return n<0?'−'+(-n):String(n);}
function fr2(n,d){var g=gcd2(Math.abs(n),d); n/=g; d/=g; return d===1?String(n):n+'/'+d;}
function ncr(n,r){var v=1; for(var i=1;i<=r;i++)v=v*(n-r+i)/i; return Math.round(v);}
var NEG={min:-9999};
function sNeg(){
var t=rnd(0,3),a=rnd(2,15),b=rnd(2,15);
if(t===0){return Q('Negative numbers',pn(-a)+' + '+b+' = ?',b-a,NEG);}
if(t===1){return Q('Negative numbers',a+' − '+pn(-b)+' = ?',a+b,NEG);}
if(t===2){var c=rnd(2,9),d=rnd(2,9); return Q('Negative numbers',pn(-c)+' × '+d+' = ?',-c*d,NEG);}
var e=rnd(2,9),f=rnd(2,9); return Q('Negative numbers',pn(-e*f)+' ÷ '+pn(-e)+' = ?',f,NEG);
}
function sEq(){
var t=rnd(0,2),x=rnd(-8,10);
if(t===0){var a=rnd(2,7),b=rnd(1,12); return Q('Equation',a+'x + '+b+' = '+sg(a*x+b)+'. Find x.',x,NEG);}
if(t===1){var a2=rnd(4,9),c=rnd(2,a2-1),b2=rnd(1,10),x1=rnd(1,10); return Q('Equation',a2+'x + '+b2+' = '+c+'x + '+(b2+(a2-c)*x1)+'. Find x.',x1,NEG);}
var k=rnd(2,5),m=rnd(1,6); return Q('Equation',k+'(x + '+m+') = '+sg(k*(x+m))+'. Find x.',x,NEG);
}
function sSimp(){
var t=rnd(0,2);
if(t===0){var a=rnd(2,6),b=rnd(1,9),c=rnd(2,6); return Q('Simplify',a+'(x + '+b+') + '+c+'x = ?x + '+a*b+'. Find the missing number.',a+c,{min:1});}
if(t===1){var x=rnd(-4,5),p=rnd(2,5),q=rnd(1,9); return Q('Simplify','If x = '+sg(x)+', find '+p+'x² + '+q+'.',p*x*x+q,NEG);}
var u=rnd(5,9),v=rnd(2,u-2),w=rnd(2,9); return Q('Simplify',u+'a + '+w+'b − '+v+'a = ?a + '+w+'b. Find the missing number.',u-v,{min:1});
}
function sRatioShare(){
var a=rnd(1,5),b=rnd(a+1,8),u=rnd(3,12),tot=(a+b)*u,big=Math.random()<.5;
return Q('Ratio','Share $'+tot+' in the ratio '+a+' : '+b+'. How much is the '+(big?'larger':'smaller')+' share in dollars?',big?b*u:a*u,{min:1});
}
function sSpeed(){
var t=rnd(0,2),s=rnd(4,15)*5,h=rnd(2,6);
if(t===0)return Q('Speed','A car travels '+(s*h)+' km in '+h+' hours. What is its average speed in km/h?',s,{min:1});
if(t===1)return Q('Speed','A bus travels at '+s+' km/h for '+h+' hours. How far does it go in km?',s*h,{min:1});
return Q('Speed','A train goes '+(s*h)+' km at '+s+' km/h. How many hours does it take?',h,{min:1});
}
function sPoly(){
var t=rnd(0,2);
if(t===0){var n=rnd(5,10); return Q('Polygons','What is the sum of the interior angles of a polygon with '+n+' sides, in degrees?',(n-2)*180,{min:100});}
if(t===1){var m=pick([5,6,8,9,10,12,15,18,20,24]); return Q('Polygons','A regular polygon has '+m+' sides. What is the size of each exterior angle in degrees?',360/m,{min:1});}
var e=pick([30,36,40,45,60,72,90]); return Q('Polygons','Each exterior angle of a regular polygon is '+e+'°. How many sides does it have?',360/e,{min:3});
}
function sArea2(){
var t=rnd(0,2);
if(t===0){var a=rnd(3,12),b=rnd(a+1,18),h=rnd(2,10)*2; return Q('Trapezium','A trapezium has parallel sides '+a+' cm and '+b+' cm, and height '+h+' cm. What is its area in cm²?',(a+b)*h/2,{fig:trapFig(a+' cm',b+' cm',h+' cm'),figAlt:'A trapezium with parallel sides '+a+' cm and '+b+' cm and height '+h+' cm.',min:1});}
if(t===1){var bs=rnd(4,15),hh=rnd(3,12); return Q('Parallelogram','A parallelogram has a base of '+bs+' cm and a height of '+hh+' cm. What is its area in cm²?',bs*hh,{min:1,fig:paraFig(bs+' cm',hh+' cm'),figAlt:'A parallelogram with base '+bs+' cm and height '+hh+' cm.'});}
var r=rnd(1,5)*7,h2=rnd(2,10); return Q('Cylinder','A cylinder has radius '+r+' cm and height '+h2+' cm. Use π = 22/7. What is its volume in cm³?',22*r*r*h2/7,{min:1,fig:cylFig('r = '+r,h2+' cm'),figAlt:'A cylinder with radius '+r+' cm and height '+h2+' cm.'});
}
function sStats(){
var t=rnd(0,3),n=pick([5,7]),arr=[],i; for(i=0;i0?'+ '+(s1===1?'':s1)+'x ':''))+(c<0?'− '+(-c):'+ '+c)+' = 0';
return Q('Quadratic','Solve '+txt+'. What is the '+(lg?'larger':'smaller')+' root?',lg?Math.max(p,q):Math.min(p,q),NEG);
}
/*MFBEGIN*/
function tx(x,y,s,q){return ''+s+'';}
function dsh(x1,y1,x2,y2){return '';}
function ra(x,y,dx,dy){var s=9;return '';}
function rtFig(base,vert,hyp,ang){
var A=[40,108],B=[196,108],C=[196,26],inner=''+ra(196,108,-1,-1);
inner+=tx(118,124,base,base==='?')+tx(214,67,vert,vert==='?')+tx(104,56,hyp,hyp==='?');
if(ang)inner+=tx(66,100,ang,ang==='?');
return svgWrap(inner);
}
function rectFig(l,w){var inner=''+tx(105,112,l)+tx(208,60,w);return svgWrap(inner);}
function triHFig(b,h){var inner=''+dsh(90,20,90,108)+ra(90,108,1,-1)+tx(120,123,b)+tx(66,64,h);return svgWrap(inner);}
function trapFig(a,b,h){var inner=''+dsh(80,26,80,108)+ra(80,108,1,-1)+tx(125,14,a)+tx(120,123,b)+tx(56,68,h);return svgWrap(inner);}
function paraFig(b,h){var inner=''+dsh(70,26,70,108)+ra(70,108,1,-1)+tx(95,123,b)+tx(46,68,h);return svgWrap(inner);}
function cylFig(r,h){var inner=''+dsh(120,30,168,30)+tx(146,50,r)+tx(192,64,h);return svgWrap(inner);}
function cubFig(l,w,h){var inner=''+tx(90,124,l)+tx(213,70,h)+tx(188,106,w);return svgWrap(inner);}
function cirFig(lab,dia){var inner='';
if(dia)inner+=''+tx(120,52,lab);else inner+=''+tx(148,52,lab);return svgWrap(inner);}
function pt(a,r){return [120+r*Math.cos(a*Math.PI/180),65-r*Math.sin(a*Math.PI/180)];}
function cirThFig(c2){var A=pt(215,52),B=pt(325,52),P=pt(90,52),O=[120,65],inner='';
inner+='';
inner+=tx(120,84,c2)+tx(P[0],P[1]+14,'?',true)+tx(A[0]-10,A[1]+8,'A')+tx(B[0]+10,B[1]+8,'B')+tx(120,48,'O');return svgWrap(inner);}
function sectFig(r,th){var e=pt(th,52),inner=''+tx(150,76,r)+tx(150,50,th+'°');return svgWrap(inner);}
function cycFig(a){var P=[pt(130,52),pt(230,52),pt(320,52),pt(50,52)],inner='';
inner+=tx(P[0][0]-12,P[0][1]-4,'A')+tx(P[1][0]-12,P[1][1]+8,'B')+tx(P[2][0]+12,P[2][1]+8,'C')+tx(P[3][0]+12,P[3][1]-4,'D');
inner+=tx(P[0][0]+4,P[0][1]+16,a+'°')+tx(P[2][0]-6,P[2][1]-14,'?',true);return svgWrap(inner);}
function gradFig(x1,y1,x2,y2){var mnx=Math.min(x1,x2),mxx=Math.max(x1,x2),mny=Math.min(y1,y2),mxy=Math.max(y1,y2);
var sx=120/Math.max(1,mxx-mnx),sy=70/Math.max(1,mxy-mny);
function X(x){return 60+(x-mnx)*sx;}function Y(y){return 92-(y-mny)*sy;}
var inner=''+tx(222,108,'x')+tx(42,12,'y');
var dx=x2-x1,dy=y2-y1;
inner+='';
[[x1,y1],[x2,y2]].forEach(function(p){var px=X(p[0]),py=Y(p[1]);inner+=''+tx(px>150?px-38:px+38,py,'('+p[0]+', '+p[1]+')');});
return svgWrap(inner);}
/*MFEND*/
function sPyth(){
var tr=pick([[3,4,5],[5,12,13],[8,15,17],[7,24,25],[6,8,10],[9,12,15],[20,21,29]]),k=pick([1,1,2]),a=tr[0]*k,b=tr[1]*k,c=tr[2]*k,t=rnd(0,1);
if(t===0)return Q('Pythagoras','A right-angled triangle has two shorter sides '+a+' cm and '+b+' cm. How long is the hypotenuse in cm?',c,{min:1,fig:rtFig(a,b,'?'),figAlt:'A right-angled triangle with shorter sides '+a+' cm and '+b+' cm. The hypotenuse is marked with a question mark.'});
return Q('Pythagoras','A right-angled triangle has hypotenuse '+c+' cm and one side '+a+' cm. How long is the other side in cm?',b,{min:1,fig:rtFig(a,'?',c),figAlt:'A right-angled triangle with hypotenuse '+c+' cm and one side '+a+' cm. The other side is marked with a question mark.'});
}
function sTrig(){
var t=rnd(0,2);
if(t===0){var tr=pick([[3,4,5],[5,12,13],[8,15,17]]),w=rnd(0,2),nm=['sin A','cos A','tan A'][w],nu=[tr[0],tr[1],tr[0]][w],de=[tr[2],tr[2],tr[1]][w];
var ans=fr2(nu,de); return Q('Trigonometry','In a right-angled triangle, the side opposite A is '+tr[0]+', the side next to A is '+tr[1]+' and the hypotenuse is '+tr[2]+'. Find '+nm+' as a fraction.',ans,{fig:rtFig(tr[1],tr[0],tr[2],'A'),figAlt:'A right-angled triangle with angle A. Opposite '+tr[0]+', next to A '+tr[1]+', hypotenuse '+tr[2]+'.',options:strOpts(ans,[fr2(de,nu),fr2(tr[1],tr[2]),fr2(tr[0],tr[2]),fr2(tr[1],tr[0]),fr2(tr[2],tr[0])])});}
if(t===1){var h=rnd(2,12)*2; return Q('Trigonometry','In a right-angled triangle, the angle is 30° and the hypotenuse is '+h+' cm. How long is the side opposite the 30° angle in cm?',h/2,{min:1,fig:rtFig('', '?',h,'30°'),figAlt:'A right-angled triangle with a 30 degree angle and hypotenuse '+h+' cm. The side opposite is marked with a question mark.'});}
var s=rnd(3,15); return Q('Trigonometry','A right-angled triangle has a 45° angle. The side next to it is '+s+' cm. How long is the opposite side in cm?',s,{min:1,fig:rtFig(s,'?','','45°'),figAlt:'A right-angled triangle with a 45 degree angle. The side next to it is '+s+' cm and the opposite side is marked with a question mark.'});
}
function sIndices2(){
var t=rnd(0,3);
if(t===0){var a=rnd(2,5),b=rnd(2,4); return Q('Indices','(x^'+a+')^'+b+' = x^?',a*b,{min:2});}
if(t===1){var n=rnd(2,5),e=rnd(2,6); return Q('Indices','Write 2^−'+n+' as a fraction.','1/'+Math.pow(2,n),{options:strOpts('1/'+Math.pow(2,n),['-'+Math.pow(2,n),'1/'+(2*n),'-1/'+Math.pow(2,n),'1/'+Math.pow(2,n+1)])});}
if(t===2){var m=rnd(2,9); return Q('Indices',m+'^0 + '+m+'^1 = ?',1+m,{min:2});}
var r=pick([4,9,16,25,36,49,64,81]),v=Math.sqrt(r); return Q('Indices',r+'^(1/2) = ?',v,{min:1});
}
function sProb(){
var t=rnd(0,2);
if(t===0){var r=rnd(1,6),b=rnd(1,6),g=rnd(1,5),tot=r+b+g,ans=fr2(r,tot); return Q('Probability','A bag has '+r+' red, '+b+' blue and '+g+' green balls. One ball is picked at random. What is the probability it is red?',ans,{options:strOpts(ans,[fr2(b,tot),fr2(g,tot),fr2(r+b,tot),fr2(1,r+b+g),fr2(r,r+b)])});}
if(t===1){var n=rnd(2,4),d=Math.pow(2,n),ans2='1/'+d; return Q('Probability','A fair coin is tossed '+n+' times. What is the probability of getting '+n+' heads?',ans2,{options:strOpts(ans2,['1/'+(2*n),'1/'+(d*2),'1/'+(d/2),'1/'+(n+2),'1/'+(d+2)])});}
var k=rnd(2,5),p=fr2(k,10),q=fr2(10-k,10); return Q('Probability','The probability it rains tomorrow is '+p+'. What is the probability it does not rain?',q,{options:strOpts(q,[p,fr2(1,10),fr2(k+1,10),fr2(10-k+1,10),fr2(1,k)])});
}
function sSim(){
var x=rnd(1,12),y=rnd(1,12),t=rnd(0,1);
if(t===0)return Q('Simultaneous equations','x + y = '+(x+y)+' and x − y = '+sg(x-y)+'. Find x.',x,NEG);
return Q('Simultaneous equations','x + y = '+(x+y)+' and x − y = '+sg(x-y)+'. Find y.',y,NEG);
}
function sCircle(){
var t=rnd(0,2);
if(t===0){var a=rnd(20,80); return Q('Circle theorem','The angle at the centre of a circle is '+(2*a)+'°. What is the angle at the circumference on the same arc in degrees?',a,{min:1,fig:cirThFig((2*a)+'°'),figAlt:'A circle with centre O. The angle at the centre is '+(2*a)+' degrees. The angle at the circumference on the same arc is marked with a question mark.'});}
if(t===1){var r=rnd(1,4)*7,th=pick([60,90,120,180]),al=Math.round(th/360*2*22*r/7*100)/100; return Q('Circle theorem','A sector has radius '+r+' cm and angle '+th+'°. Use π = 22/7. What is its arc length in cm?',al,{min:1,fig:sectFig('r = '+r,th),figAlt:'A sector with radius '+r+' cm and angle '+th+' degrees.'});}
var a2=rnd(40,120); return Q('Circle theorem','ABCD is a cyclic quadrilateral. Angle A is '+a2+'°. What is the opposite angle C in degrees?',180-a2,{min:1,fig:cycFig(a2),figAlt:'A cyclic quadrilateral ABCD in a circle. Angle A is '+a2+' degrees and angle C is marked with a question mark.'});
}
function sGrad(){
var m=pick([-4,-3,-2,-1,1,2,3,4,5]),c=rnd(-5,6),x1=rnd(-3,2),x2=x1+rnd(1,4),y1=m*x1+c,y2=m*x2+c;
if(Math.random()<.5)return Q('Gradient','Find the gradient of the line through ('+sg(x1)+', '+sg(y1)+') and ('+sg(x2)+', '+sg(y2)+').',m,{min:NEG.min,max:NEG.max,fig:gradFig(x1,y1,x2,y2),figAlt:'A straight line through the points ('+x1+', '+y1+') and ('+x2+', '+y2+') on a grid.'});
return Q('Gradient','A line has gradient '+sg(m)+' and passes through (0, '+sg(c)+'). What is y when x = '+x2+'?',m*x2+c,NEG);
}
function sSeq(){
var a=rnd(2,9),d=rnd(2,7),n=rnd(10,25),t=rnd(0,1);
if(t===0)return Q('Sequences','The sequence is '+a+', '+(a+d)+', '+(a+2*d)+', '+(a+3*d)+', … What is the '+ord(n)+' term?',a+(n-1)*d,{min:1});
var r=rnd(2,3),k=rnd(3,5); return Q('Sequences','A geometric sequence starts '+a+', '+a*r+', '+a*r*r+', … What is term number '+k+'?',a*Math.pow(r,k-1),{min:1});
}
function sStdForm(){
var m=rnd(11,99)/10,e=rnd(2,6),val=Math.round(m*Math.pow(10,e));
return Q('Standard form',val+' = '+m+' × 10^?',e,{min:1});
}
function sCompound(){
var P=pick([500,800,1000,1500,2000,2500,4000,5000]),r=pick([5,10,15,20]),y=pick([2,3]),A=P*Math.pow(1+r/100,y);
return Q('Compound interest','$'+P+' is invested at '+r+'% compound interest per year. How much is it worth after '+y+' years in dollars?',Math.round(A*100)/100,{min:1});
}
/* ---- Post-sec: everyday maths ---- */
function sLoan(){
var t=rnd(0,2),P=rnd(2,20)*500;
if(t===0){var r=rnd(2,8),y=rnd(2,5); return Q('Loan','You borrow $'+P+' at '+r+'% simple interest per year for '+y+' years. How much interest do you pay in dollars?',P*r*y/100,{min:1});}
if(t===1){var m=pick([6,10,12,20,24]),inst=rnd(2,12)*10,tot=inst*m; return Q('Loan','A $'+tot+' laptop is paid in '+m+' equal monthly instalments with no interest. How much is each instalment in dollars?',inst,{min:1});}
var m2=pick([12,24,36]),mp=rnd(5,20)*10; var cost=mp*m2,extra=rnd(1,9)*10; return Q('Loan','A phone costs $'+cost+' cash. On a plan you pay $'+(cost+extra)+' in total. How much extra do you pay for the plan in dollars?',extra,{min:1});
}
function sTax(){
var inc=rnd(9,28)*2500,bands=[[20000,0],[10000,2],[10000,3.5],[20000,7]],tax=0,left=inc,i;
for(i=0;i1?'s are':' is')+' eaten. How many parts are left?',4-Math.min(d/4,3),{min:1,max:4});}
return Q('Half & quarter','Double of '+(n/2)+' is ?',n,{min:0,max:mx});
}
function sShare1(mx){
var g=rnd(2,5),e=rnd(1,Math.floor(mx/g)),t=rnd(0,1);
if(t===0)return Q('Sharing',g*e+' sweets are shared equally among '+g+' children. How many sweets does each child get?',e,{min:1,max:10});
return Q('Sharing',(e===1?'1 bag has '+g+' apples':e+' bags each have '+g+' apples')+'. How many apples are there in all?',g*e,{min:1,max:Math.max(10,mx)});
}
function sCoins(lv){
var t=rnd(0,1);
if(lv===1){var a=rnd(1,4),b=rnd(0,2); if(t===0)return Q('Coins','You have '+a+' ten-cent coin'+(a>1?'s':'')+(b>0?' and '+b+' five-cent coin'+(b===1?'':'s'):'')+'. How many cents do you have?',a*10+b*5,{min:5,max:60}); var k=rnd(2,9); return Q('Coins','One sweet costs 10 cents. How many cents do '+k+' sweets cost?',k*10,{min:10,max:100});}
var d=rnd(1,3),c=pick([10,20,30,40,50]); if(t===0)return Q('Coins','You have '+d+' one-dollar coin'+(d>1?'s':'')+' and a '+c+'-cent coin. How many cents do you have?',d*100+c,{min:50});
var p=pick([50,60,70,80,90]),q=rnd(1,4)*10; return Q('Coins','A pen costs '+p+' cents. You pay with a $1 coin. How many cents change do you get?',100-p,{min:1,max:99});
}
function sLen1(lv){
var a=rnd(3,lv===1?10:30),b=rnd(2,lv===1?9:20),t=rnd(0,2);
if(t===0)return Q('Length','A pencil is '+a+' cm long. A pen is '+b+' cm longer. How long is the pen in cm?',a+b,{min:1,max:lv===1?20:60});
if(t===1){var c=a+b; return Q('Length','A rope is '+c+' cm long. A piece of '+a+' cm is cut off. How long is the rest in cm?',b,{min:1,max:lv===1?20:60});}
var n=rnd(2,6),u=rnd(2,6); return Q('Length','A strip is '+u+' cm long. Put '+n+' strips end to end. How long are they in all, in cm?',n*u,{min:1,max:lv===1?60:99});
}
/* ---------- syllabus gap questions: symmetry, lines, nets, triangles, composite figures, tanks, line graphs, number sense ---------- */
var DASH='stroke:#c77d00;stroke-width:3;stroke-dasharray:6 5;fill:none';
function symFig(shape,line){
var p={rect:'M55 28 H185 V102 H55 Z',tri:'M120 18 L185 108 L55 108 Z',rtri:'M60 108 L60 22 L185 108 Z',para:'M80 28 H190 L160 102 H50 Z',kite:'M120 14 L170 60 L120 116 L70 60 Z'}[shape];
var ln={v:'',h:'',d:'',o:''}[line];
return svgWrap(''+ln);
}
function sSymm(){
var t=rnd(0,2);
if(t<2){
var C=[['rect','v','Yes'],['rect','h','Yes'],['rect','d','No'],['tri','v','Yes'],['tri','o','No'],['rtri','v','No'],['para','v','No'],['kite','v','Yes'],['kite','h','No'],['tri','h','No'],['para','h','No'],['rtri','d','No'],['kite','d','No']],c=pick(C);
return Q('Symmetry','Is the dashed line a line of symmetry for this shape?',c[2],{fig:symFig(c[0],c[1]),figAlt:'A '+({rect:'rectangle',tri:'triangle with two equal sides',rtri:'right-angled triangle',para:'parallelogram',kite:'kite'}[c[0]])+' with a dashed line drawn through it.',options:['Yes','No']});
}
var L=[['a square',4],['a rectangle that is not a square',2],['an equilateral triangle',3],['a regular pentagon',5],['a regular hexagon',6],['an isosceles triangle that is not equilateral',1],['a kite that is not a rhombus',1],['a rhombus that is not a square',2],['a parallelogram that is not a rectangle',0],['the letter H',2],['the letter A',1]].slice(0,13),x=pick(L);
return Q('Symmetry','How many lines of symmetry does '+x[0]+' have?',x[1],{min:0,max:8});
}
/* ---------- P4 syllabus gap questions: number words, decimals, angle names and measuring, rectangle and square ---------- */
var NW1=['','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve','thirteen','fourteen','fifteen','sixteen','seventeen','eighteen','nineteen'],NW2=['','','twenty','thirty','forty','fifty','sixty','seventy','eighty','ninety'];
function nw99(n){return n<20?NW1[n]:NW2[Math.floor(n/10)]+(n%10?'-'+NW1[n%10]:'');}
function numWords(n){var t=Math.floor(n/1000),r=n%1000,h=Math.floor(r/100),u=r%100,s=nw99(t)+' thousand'; if(h)s+=' '+NW1[h]+' hundred'; if(u)s+=' and '+nw99(u); return s;}
function spN(n){return String(n).replace(/\B(?=(\d{3})+(?!\d))/g,' ');}
function fmtDec(t){var s=(t/1000).toFixed(3).replace(/0+$/,'').replace(/\.$/,''); return s;}
function uniqStr(a,n){var o=[]; a.forEach(function(x){x=String(x); if(o.indexOf(x)<0&&o.length=10000&&v<100000;}), o=uniqStr(w.map(spN),3); while(o.length<3){var fv=n+1000*(o.length+1); if(fv>99999)fv=n-1000*(o.length+1); o.push(spN(fv));}
return Q('Number words','Which number is "'+numWords(n)+'"?',spN(n),{options:strOpts(spN(n),o)});
}
var w2=[sw(0,1),sw(3,4),sw(1,2),n+1000,n-100].filter(function(v){return v!==n&&v>=10000&&v<100000&&String(v).slice(-3)!==String(n).slice(-3)||v!==n&&v>=10000&&v<100000;}), ow=uniqStr(w2.map(numWords),3); while(ow.length<3){var fw=n+1000*(ow.length+2); if(fw>99999)fw=n-1000*(ow.length+2); ow.push(numWords(fw));}
return Q('Number words','How do we write '+spN(n)+' in words?',numWords(n),{options:strOpts(numWords(n),ow)});
}
function pCompare(){
var base=rnd(10,90)*1000, s={}, a=[]; while(a.length<4){var v=base+rnd(1,9)*100+pick([0,rnd(1,9)*10,rnd(1,99)]); if(!s[v]){s[v]=1; a.push(v);}}
var big=rnd(0,1)===1, ans=big?Math.max.apply(null,a):Math.min.apply(null,a);
return Q('Compare numbers','Which is the '+(big?'greatest':'smallest')+' number?',spN(ans),{options:a.map(spN)});
}
function pSeq(){
var st=pick([100,200,250,500,1000,1250,2000,2500]), up=rnd(0,1)===1, a0=up?rnd(1,9)*1000+rnd(0,3)*st:rnd(40,98)*1000-rnd(0,3)*st; if(!up&&a0-st*5<1000)a0+=st*5;
var t=[],i; for(i=0;i<5;i++)t.push(a0+(up?1:-1)*st*i); var h=rnd(1,3), ans=t[h]; t[h]='?';
return Q('Number sequences','Fill in the missing number: '+t.map(function(x){return x==='?'?'?':spN(x);}).join(', '),ans,{options:[ans,ans+st,ans-st,ans+(up?-1:1)*st*2].filter(function(v,k,arr){return arr.indexOf(v)===k;}).concat([ans+st*3]).slice(0,4)});
}
function pCommonMult(){
var P=pick([[2,3],[3,4],[2,5],[3,5],[4,6],[4,5],[3,6],[2,7],[5,6],[3,8]]), a=P[0], b=P[1], l=a*b/gcd2(a,b), c=l*rnd(1,3), pool=[],m;
for(m=2;m<=80;m++){if((m%a===0)!==(m%b===0)&&Math.abs(m-c)<=20)pool.push(m);}
shuffle(pool); var w=pool.slice(0,3); while(w.length<3)w.push(c+7+w.length*3);
return Q('Common multiples','Which of these is a common multiple of '+a+' and '+b+'?',c,{options:[c].concat(w)});
}
function pDecPlace(){
var d=shuffle([1,2,3,4,5,6,7,8,9]).slice(0,4), pos=rnd(1,3), num=d[0]+'.'+d[1]+d[2]+d[3], dg=d[pos], nm=['','tenths','hundredths','thousandths'][pos];
return Q('Decimal place','In '+num+', what does the digit '+dg+' stand for?',dg+' '+nm,{options:[dg+' ones',dg+' tenths',dg+' hundredths',dg+' thousandths']});
}
function pDecOrder(){
var ip=rnd(0,5), s={}, a=[]; while(a.length<4){var v=ip*1000+pick([rnd(1,9)*100,rnd(1,9)*10,rnd(101,999)]); if(v%1000!==0&&!s[v]){s[v]=1; a.push(v);}}
var big=rnd(0,1)===1, ans=big?Math.max.apply(null,a):Math.min.apply(null,a);
return Q('Order decimals','Which is the '+(big?'greatest':'smallest')+' decimal?',fmtDec(ans),{options:a.map(fmtDec)});
}
function pDecFrac(){
var h=pick([5,10,15,20,25,30,35,40,45,50,60,65,70,75,80,85,90,4,6,8,12,16,24,36]), g=gcd2(h,100), ans=(h/g)+'/'+(100/g), dec=fmtDec(h*10);
var w=uniqStr([g>1?h+'/100':null,h+'/10',h+'/1000',((h/g)+1)+'/'+(100/g),(h/g)+'/'+(100/g+10)].filter(function(x){return x&&x!==ans;}),3);
return Q('Decimal to fraction','Express '+dec+' as a fraction in its simplest form.',ans,{options:strOpts(ans,w)});
}
function pFracDec(){
var den=pick([2,4,5,10,20,25,50,100]), num=rnd(1,den-1); if(gcd2(num,den)>1&&den>10&&rnd(0,1))num=1;
var k=num*100/den, ans=fmtDec(k*10), w=uniqStr([fmtDec(k*1),fmtDec(k*100),fmtDec(Math.round(k/10)*10*10+(k%10?k%10*1:0)*0+10),fmtDec((k+10)*10)].filter(function(x){return x!==ans&&parseFloat(x)>0;}),3);
while(w.length<3)w.push(fmtDec((k+w.length*3+1)*10));
return Q('Fraction to decimal','Express '+num+'/'+den+' as a decimal.',ans,{options:strOpts(ans,w)});
}
function pRoundDec(){
var t=rnd(1001,9999); while(t%1000===0)t=rnd(1001,9999);
var m=pick([0,1,2]), unit=[1000,100,10][m], r=Math.round(t/unit)*unit, fl=Math.floor(t/unit)*unit, nm=['the nearest whole number','1 decimal place','2 decimal places'][m];
var fx=function(v){return m===0?String(v/1000):(v/1000).toFixed(m);};
var ans=fx(r), w=uniqStr([fx(fl),fx(fl+unit),fx(r+unit),fx(r-unit),fx(fl-unit)].filter(function(x){return x!==ans&&parseFloat(x)>=0;}),3);
return Q('Round decimals','Round '+(t/1000).toFixed(3)+' to '+nm+'.',ans,{options:strOpts(ans,w)});
}
function pDecMulDiv(){
var b=rnd(2,9), v;
if(rnd(0,1)){var h=rnd(105,995); while(h%100===0)h++; v=h*b*10; return Q('Decimal ×÷',fmtDec(h*10)+' × '+b+' = ?',fmtDec(v),{options:strOpts(fmtDec(v),[fmtDec(v*10),fmtDec(v/10),fmtDec(v+10*b),fmtDec(v-10*b)])});}
var q=rnd(15,400); while(q%100===0)q++; var a=q*b*10;
return Q('Decimal ×÷',fmtDec(a)+' ÷ '+b+' = ?',fmtDec(q*10),{options:strOpts(fmtDec(q*10),[fmtDec(q*100),fmtDec(q),fmtDec(q*10+10),fmtDec(q*10-10)])});
}
function pDivDec(){
var d=pick([2,4,5]), a=rnd(3,79); while(a%d===0)a++;
var ans=fmtDec(a*1000/d), q=Math.floor(a/d);
return Q('Divide to decimal',a+' ÷ '+d+' = ? Give your answer as a decimal.',ans,{options:strOpts(ans,[q+'.'+(a%d)*(d===4?25:d===5?2:5)/(d===2?1:1),fmtDec(a*100/d),fmtDec(a*10000/d),String(q)])});
}
function pRoundAns(){
var b=pick([3,6,7,9,11]), a=rnd(10,99); while(a%b===0)a++;
var dp=rnd(1,2), p=Math.pow(10,dp), k=Math.round(a*p/b), ans=(k/p).toFixed(dp), fl=(Math.floor(a*p/b)/p).toFixed(dp);
var w=uniqStr([fl,((k+1)/p).toFixed(dp),((k-1)/p).toFixed(dp),(Math.floor(a/b)).toFixed(dp)].filter(function(x){return x!==ans;}),3);
return Q('Round answer','Divide '+a+' by '+b+'. Give your answer correct to '+dp+' decimal place'+(dp>1?'s':'')+'.',ans,{options:strOpts(ans,w)});
}
function pAngName(){
var P={A:[40,108],B:[200,108],C:[118,22]}, L=['A','B','C'], v=pick(L), o=L.filter(function(x){return x!==v;}), X=o[0], Y=o[1];
var ang=function(f,t){return Math.atan2(P[t][1]-P[f][1],P[t][0]-P[f][0]);};
var lab={A:[22,114],B:[220,114],C:[118,9]};
var inner=''+L.map(function(k){return ''+k+'';}).join('')+'';
var s1=rnd(0,1), nm='∠'+(s1?X+v+Y:Y+v+X), w=uniqStr(['∠'+v+X+Y,'∠'+X+Y+v,'∠'+v+Y+X],3);
return Q('Angle names','Which is the correct name for the marked angle?',nm,{fig:svgWrap(inner),figAlt:'Triangle ABC with the angle at '+v+' marked.',options:strOpts(nm,w)});
}
function protFig(th,ray,dot){
var cx=120,cy=112,R=80,i,s='';
for(i=0;i<=180;i+=10){var a=i*Math.PI/180,c=Math.cos(a),n=Math.sin(a),tl=(i%30===0?10:6); s+=''+i+'';}
s+='';
var a2=th*Math.PI/180; s+='';
if(ray)s+='';
if(dot)s+='';
return svgWrap(s+'');
}
function pAngMeasure(){
var th=rnd(3,33)*5; while(th===90)th=rnd(3,33)*5;
var w=uniqStr([180-th,th+10,th-10,th+5,th-5].filter(function(v){return v>0&&v<180&&v!==th;}),3);
return Q('Measure angle','Use the protractor to read the size of the marked angle, in degrees. The bottom arm is at 0°.',th,{fig:protFig(th,true,false),figAlt:'A protractor with an angle of '+th+' degrees marked from the right-hand baseline.',options:[th].concat(w)});
}
function pDrawAng(){
var th=rnd(2,34)*5, nm=th<90?'Acute':th>90?'Obtuse':'Right', o=['Acute','Obtuse','Right','Reflex'];
return Q('Draw angle','Ali draws an angle of '+th+'°. He puts a dot at '+th+' on the protractor and joins it to the centre. What kind of angle does he draw?',nm,{fig:protFig(th,false,true),figAlt:'A protractor with a dot at '+th+' degrees.',options:o});
}
function pRect(){
var t=rnd(0,2), w=rnd(4,12), h=rnd(2,w-1);
function box(W,H,names,labs){var x0=120-W/2,y0=65-H/2;
var s='';
[[x0,y0],[x0+W,y0],[x0+W,y0+H],[x0,y0+H]].forEach(function(p){s+='';});
s=s.replace(/]*\/>/g,'');
var cx=[x0-12,x0+W+12,x0+W+12,x0-12],cy=[y0-8,y0-8,y0+H+9,y0+H+9]; names.forEach(function(n,i){s+=''+n+'';});
s+=labs; return s;}
if(t===0){
var RW=Math.min(140,w*12),RH=Math.max(30,Math.min(70,h*12)),ry0=65-RH/2,inner=box(RW,RH,['A','B','C','D'],''+w+' cm'+h+' cm');
var side=pick([['CD',w],['AD',h]]);
return Q('Rectangle & square','ABCD is a rectangle. AB = '+w+' cm and BC = '+h+' cm. What is the length of '+side[0]+', in cm?',side[1],{fig:svgWrap(inner),figAlt:'Rectangle ABCD.',options:[side[1],side[1]===w?h:w,w+h,2*(w+h)]});
}
if(t===1){
var s2=rnd(3,12), sd=pick(['RS','SP','QR']), inner2=box(86,86,['P','Q','R','S'],''+s2+' cm');
return Q('Rectangle & square','PQRS is a square. PQ = '+s2+' cm. What is the length of '+sd+', in cm?',s2,{fig:svgWrap(inner2),figAlt:'Square PQRS.',options:[s2,2*s2,3*s2,4*s2]});
}
var inner3=box(130,70,['A','B','C','D'],'');
var qs=[['How many pairs of parallel sides does a rectangle have?',2,[1,3,4]],['How many right angles does a rectangle have?',4,[2,3,1]],['How many sides does a rectangle have?',4,[2,3,5]]], z=pick(qs);
return Q('Rectangle & square',z[0],z[1],{fig:svgWrap(inner3),figAlt:'A rectangle.',options:[z[1]].concat(z[2])});
}
var P4GAP=[pWords,pCompare,pSeq,pCommonMult,pDecPlace,pDecOrder,pDecFrac,pFracDec,pRoundDec,pDecMulDiv,pDivDec,pRoundAns,pAngName,pAngMeasure,pDrawAng,pRect];
function sLines(){
var t=rnd(0,2);
if(t===0){
var k=pick(['Parallel','Perpendicular','Neither']),inner,y1=rnd(25,60),sl=rnd(-25,25),gap=rnd(35,60);
if(k==='Parallel')inner='';
else if(k==='Perpendicular'){var cx=rnd(80,150),cy=rnd(60,95); inner='';}
else{var x0=rnd(50,100),dx=pick([-70,-55,-40,40,55,70]); inner='';}
return Q('Lines','Look at the two lines. Are they parallel, perpendicular, or neither?',k,{fig:svgWrap(inner),figAlt:'Two lines.',options:['Parallel','Perpendicular','Neither']});
}
var S=[['M55 28 H185 V102 H55 Z',4,'rectangle'],['M60 105 L60 25 L185 105 Z',1,'right-angled triangle'],['M50 105 L50 30 L125 30 L190 105 Z',2,'trapezium with two right angles'],['M60 30 L185 30 L150 105 L25 105 Z',0,'parallelogram'],['M120 20 L190 65 L120 110 L50 65 Z',0,'rhombus'],['M55 100 H170 L120 35 Z',0,'triangle with no right angles'],['M185 105 L185 25 L60 105 Z',1,'right-angled triangle'],['M70 25 H170 V105 H70 Z',4,'rectangle']],s=pick(S);
return Q('Lines','How many right angles does this shape have?',s[1],{fig:svgWrap(''),figAlt:'A '+s[2]+'.',min:0,max:5});
}
var NETF='rgba(46,125,90,.18)', CUBEOK=[[[1,0],[0,1],[1,1],[2,1],[3,1],[1,2]],[[0,0],[0,1],[1,1],[2,1],[3,1],[3,2]],[[0,0],[1,0],[1,1],[2,1],[3,1],[1,2]],[[0,0],[1,0],[1,1],[2,1],[2,2],[3,2]],[[0,0],[0,1],[1,1],[1,2],[2,2],[2,3]],[[0,0],[1,0],[2,0],[2,1],[3,1],[4,1]],[[0,0],[1,0],[1,1],[2,1],[1,2],[1,3]]],
CUBEBAD=[[[0,0],[1,0],[0,1],[1,1],[2,1],[3,1]],[[0,0],[1,0],[2,0],[3,0],[4,0],[1,1]],[[1,0],[1,1],[0,1],[2,1],[3,1],[3,0]],[[0,0],[1,0],[2,0],[3,0],[0,1],[3,1]],[[0,0],[1,0],[2,0],[3,0],[1,1],[2,1]],[[0,0],[1,0],[1,1],[2,1],[2,2],[2,3]],[[0,0],[1,0],[2,0],[3,0],[4,0],[2,1]]];
function nPoly(p){return '';}
function nRect(x,y,w,h){return '';}
function nSquares(cells){var u=cells.some(function(c){return c[0]>=4||c[1]>=3;})?24:30, mx=0,my=0; cells.forEach(function(c){mx=Math.max(mx,c[0]);my=Math.max(my,c[1]);}); var ox=(240-(mx+1)*u)/2, oy=(130-(my+1)*u)/2, o=''; cells.forEach(function(c){o+=nRect(ox+c[0]*u,oy+c[1]*u,u,u);}); return o;}
function netInner(kind){
var o='',i;
if(kind==='Cube')return nSquares(pick(CUBEOK));
if(kind==='Cuboid'){var L=40,W=26,H=30,ox=54,oy=24; o=nRect(ox,oy,L,W)+nRect(ox,oy+W+H,L,W)+nRect(ox,oy+W,L,H)+nRect(ox+L,oy+W,W,H)+nRect(ox+L+W,oy+W,L,H)+nRect(ox+2*L+W,oy+W,W,H); return o;}
if(kind==='Triangular prism'){var w=34,h=44,t=w*.866,ox=69,oy=(130-(2*t+h))/2; o=nRect(ox,oy+t,w,h)+nRect(ox+w,oy+t,w,h)+nRect(ox+2*w,oy+t,w,h)+nPoly([[ox+w,oy+t],[ox+2*w,oy+t],[ox+1.5*w,oy]])+nPoly([[ox+w,oy+t+h],[ox+2*w,oy+t+h],[ox+1.5*w,oy+2*t+h]]); return o;}
if(kind==='Square-based pyramid'){var s=40,t=36,ox=(240-s-2*t)/2,oy=(130-s-2*t)/2; o=nRect(ox+t,oy+t,s,s)+nPoly([[ox+t,oy+t],[ox+t+s,oy+t],[ox+t+s/2,oy]])+nPoly([[ox+t,oy+t+s],[ox+t+s,oy+t+s],[ox+t+s/2,oy+2*t+s]])+nPoly([[ox+t,oy+t],[ox+t,oy+t+s],[ox,oy+t+s/2]])+nPoly([[ox+t+s,oy+t],[ox+t+s,oy+t+s],[ox+2*t+s,oy+t+s/2]]); return o;}
if(kind==='Cylinder'){var w=96,h=36,ox=72,oy=17; o=nRect(ox,oy+30,w,h)+''; return o;}
if(kind==='Cone'){var ax=120,ay=116,R=52,x1=ax-R*.866,x2=ax+R*.866,y1=ay-R*.5; o=''; return o;}
return '';
}
function solidInner(kind){
var H='stroke-dasharray:5 4;opacity:.65', S=function(d){return '';}, D=function(d){return '';};
if(kind==='Cube'||kind==='Cuboid'){var w=kind==='Cube'?52:74,h=kind==='Cube'?52:40,dx=26,dy=-22,x=(240-w-dx)/2,y=(130+h+dy)/2+ (kind==='Cube'?0:0);
var fx=x,fy=y-h; return S('M'+fx+','+fy+' h'+w+' v'+h+' h-'+w+' Z M'+fx+','+fy+' l'+dx+','+dy+' h'+w+' l-'+dx+','+(-dy)+' M'+(fx+w)+','+fy+' l'+dx+','+dy+' v'+h+' l-'+dx+','+(-dy))+D('M'+(fx+dx)+','+(fy+dy)+' v'+h+' h'+w+' M'+(fx+dx)+','+(fy+dy+h)+' L'+fx+','+(fy+h));}
if(kind==='Triangular prism')return S('M40,108 L110,108 L75,62 Z M75,62 L109,38 M110,108 L144,84 L109,38')+D('M40,108 L74,84 L144,84 M74,84 L109,38');
if(kind==='Square-based pyramid')return S('M45,104 H115 L140,84 M92,34 L45,104 M92,34 L115,104 M92,34 L140,84')+D('M45,104 L70,84 H140 M92,34 L70,84');
if(kind==='Triangular pyramid')return S('M50,104 H120 L92,80 L50,104 M85,32 L50,104 M85,32 L120,104 M85,32 L92,80')+D('M50,104 L92,80');
return '';
}
var SOLIDS=[['Cube',6,12,8],['Cuboid',6,12,8],['Triangular prism',5,9,6],['Square-based pyramid',5,8,5],['Triangular pyramid',4,6,4]], NETNAMES=['Cube','Cuboid','Triangular prism','Square-based pyramid','Cylinder','Cone'];
function sNets(){
var t=rnd(0,5);
if(t<=2){var k=pick(NETNAMES), ws=NETNAMES.filter(function(x){return x!==k;}); if(k==='Cube')ws=ws.filter(function(x){return x!=='Cuboid';}).concat(['Cuboid']); ws=shuffle(ws).slice(0,3);
return Q('Nets','This net folds up to make a solid. Which solid is it?',k,{fig:svgWrap(netInner(k)),figAlt:'A flat net of a '+k.toLowerCase()+'.',options:strOpts(k,ws)});}
if(t===3){var ok=rnd(0,1)===1, c=pick(ok?CUBEOK:CUBEBAD);
return Q('Nets','Can this net be folded to make a cube?',ok?'Yes':'No',{fig:svgWrap(nSquares(c)),figAlt:'A net made of six squares.',options:['Yes','No']});}
var s=pick(SOLIDS), q=rnd(0,2), nm=['faces','edges','vertices'][q];
return Q('Nets','How many '+nm+' does this solid have?',s[1+q],{fig:svgWrap(solidInner(s[0])),figAlt:'A drawing of a '+s[0].toLowerCase()+'. Hidden edges are dashed.',min:2,max:14});
}
function sTriProps(){
var t=rnd(0,3);
if(t===0){var a=rnd(20,80); return Q('Triangles','An isosceles triangle has two equal angles. The different angle at the top is '+a*2+'°. What is each equal angle in degrees?',90-a,{min:1,max:89});}
if(t===1){var b=rnd(25,75); return Q('Triangles','An isosceles triangle has two equal angles of '+b+'° each. What is the third angle in degrees?',180-2*b,{min:1,max:179});}
if(t===2){var k=pick([['an equilateral triangle',60],['a right-angled isosceles triangle (the two equal angles)',45]]); return Q('Triangles','What is each angle of '+k[0]+', in degrees?',k[1],{min:1,max:90});}
var x=rnd(25,60); return Q('Triangles','A right-angled triangle has one angle of '+x+'° besides the right angle. What is the third angle in degrees?',90-x,{min:1,max:89});
}
function qArc(P,a1,a2,label,ask){
var r=17, x1=P.x+r*Math.cos(a1), y1=P.y+r*Math.sin(a1), x2=P.x+r*Math.cos(a2), y2=P.y+r*Math.sin(a2), m=(a1+a2)/2, lx=P.x+30*Math.cos(m), ly=P.y+30*Math.sin(m)+4;
return ''+label+'';
}
function quadFig(kind,a,ask){
var ar=a*Math.PI/180, w=kind==='trap'?112:104, sd=kind==='rhombus'?w*.62:60, top=kind==='trap'?62:0;
var A={x:0,y:0}, B={x:w,y:0}, D={x:sd*Math.cos(ar),y:-sd*Math.sin(ar)}, C=kind==='trap'?{x:D.x+top,y:D.y}:{x:B.x+D.x,y:D.y};
var minX=Math.min(A.x,D.x), maxX=Math.max(B.x,C.x), tx=(240-(maxX-minX))/2-minX, ty=112; [A,B,C,D].forEach(function(v){v.x+=tx; v.y+=ty;});
var P=function(v){return fN(v.x)+' '+fN(v.y);};
var inner='';
var AV=-ar, pi=Math.PI;
inner+=qArc(A,-ar,0,a+'°',false);
if(kind==='trap'){
var bc=Math.atan2(C.y-B.y,C.x-B.x);
inner+=qArc(D,0,pi-ar,'?',true);
['A','B','C','D'].forEach(function(n,i){var v=[A,B,C,D][i]; inner+=''+n+'';});
var chev=function(x,y){return '';};
inner+=chev((A.x+B.x)/2,A.y)+chev((D.x+C.x)/2,D.y);
} else {
if(ask==='next')inner+=qArc(B,-pi,-ar,'?',true);
else inner+=qArc(C,pi-ar,pi,'?',true);
if(kind==='rhombus'){var tk=function(u,v){var mx=(u.x+v.x)/2,my=(u.y+v.y)/2,dx=v.x-u.x,dy=v.y-u.y,l=Math.sqrt(dx*dx+dy*dy)||1,nx=-dy/l*4,ny=dx/l*4; return '';}; inner+=tk(A,B)+tk(B,C)+tk(C,D)+tk(D,A);}
}
return svgWrap(inner);
}
function sQuadAng(){
var a=rnd(5,13)*10,t=rnd(0,3); if(a===90)a=70;
if(t===0)return Q('Quadrilaterals','In a parallelogram, one angle is '+a+'°. What is the angle next to it, in degrees?',180-a,{min:1,max:179,fig:quadFig('para',a,'next'),figAlt:'A parallelogram with one angle marked '+a+' degrees and the angle next to it marked with a question mark.'});
if(t===1)return Q('Quadrilaterals','In a parallelogram, one angle is '+a+'°. What is the opposite angle, in degrees?',a,{min:1,max:179,fig:quadFig('para',a,'opp'),figAlt:'A parallelogram with one angle marked '+a+' degrees and the opposite angle marked with a question mark.'});
if(t===2)return Q('Quadrilaterals','In a rhombus, one angle is '+a+'°. What is the angle next to it, in degrees?',180-a,{min:1,max:179,fig:quadFig('rhombus',a,'next'),figAlt:'A rhombus with all four sides marked equal. One angle is '+a+' degrees and the angle next to it is marked with a question mark.'});
return Q('Quadrilaterals','In a trapezium ABCD, AB is parallel to DC. Angle A is '+a+'°. What is angle D, in degrees?',180-a,{min:1,max:179,fig:quadFig('trap',a,'d'),figAlt:'A trapezium ABCD with AB parallel to DC. Angle A is '+a+' degrees and angle D is marked with a question mark.'});
}
function sComposite(sem){
if(!sem){
var W=rnd(8,16),H=rnd(7,14),cw=rnd(3,W-4),ch=rnd(2,H-3),s=Math.min(115/W,78/H),ox=50,oy=24,X=function(v){return Math.round((ox+v*s)*10)/10;},Y=function(v){return Math.round((oy+v*s)*10)/10;};
var d='M'+X(0)+' '+Y(H)+' H'+X(W)+' V'+Y(ch)+' H'+X(W-cw)+' V'+Y(0)+' H'+X(0)+' Z';
var tx=function(x,y,t){return ''+t+'';};
var inner=''+tx(X(W/2),Y(H)+12,W)+tx(X(W)+14,Y((ch+H)/2),H-ch)+tx(X(W-cw/2),Y(ch)-11,cw)+tx(X(W-cw)-11,Y(ch/2),ch)+tx(X((W-cw)/2),Y(0)-10,W-cw)+tx(X(0)-12,Y(H/2),H);
var per=Math.random()<.5;
return Q('Composite figures','The figure is made of straight sides (all lengths in cm, all corners are right angles). '+(per?'What is its perimeter in cm?':'What is its area in cm²?'),per?2*(W+H):W*H-cw*ch,{fig:svgWrap(inner),figAlt:'An L-shaped figure with all sides labelled in centimetres.',min:1});
}
var w=pick([14,28]),h=rnd(2,12),r=w/2,per2=Math.random()<.5;
var inner2=''+w+' cm'+h+' cm';
return Q('Composite figures','A rectangle '+w+' cm wide and '+h+' cm high has a semicircle on top (the diameter is the top side of the rectangle). Use π = 22/7. '+(per2?'What is the perimeter of the whole figure in cm? (Do not count the diameter inside.)':'What is the area of the whole figure in cm²?'),per2?w+2*h+22*w/14:w*h+22*r*r/14,{fig:svgWrap(inner2),figAlt:'A rectangle with a semicircle on top.',min:1});
}
function sMissing(cub){
var t=rnd(0,cub?1:3);
var T=function(x,y,tx,an,fs){return ''+tx+'';};
var MF=function(inner,alt){return {min:1,fig:svgWrap(inner),figAlt:alt};};
var rectF=function(len,mid,alt){return MF(''+T(120,112,len+' cm')+T(202,62,'? cm','start')+T(120,62,mid),alt);};
var sqF=function(mid,alt){return MF(''+T(120,62,mid)+T(120,122,'? cm'),alt);};
var cubF=function(lenT,hT,topT,alt){return MF(''+T(90,122,lenT)+T(34,80,hT,'end')+(topT?T(105,38,topT,'middle',11):''),alt);};
if(cub){
if(t===0){var l=rnd(3,9),w=rnd(2,8),h=rnd(2,9); var f=cubF(l+' cm','? cm',null,'A cuboid with length '+l+' cm, width '+w+' cm and an unknown height'); f.fig=f.fig.replace('',T(186,56,w+' cm','start')+''); return Q('Missing side','A cuboid has volume '+l*w*h+' cm³, length '+l+' cm and width '+w+' cm. What is its height in cm?',h,f);}
var b=rnd(4,12)*5,h2=rnd(2,9); return Q('Missing side','A cuboid has volume '+b*h2+' cm³ and the area of its base is '+b+' cm². What is its height in cm?',h2,cubF('','? cm','Base '+b+' cm²','A cuboid with base area '+b+' square centimetres and an unknown height'));
}
if(t===0){var l2=rnd(4,12),w2=rnd(2,9); while(w2===l2)w2=rnd(2,9); return Q('Missing side','A rectangle has area '+l2*w2+' cm² and length '+l2+' cm. What is its breadth in cm?',w2,rectF(l2,'Area '+l2*w2+' cm²','A rectangle with length '+l2+' cm, area '+l2*w2+' square centimetres and an unknown breadth'));}
if(t===1){var l3=rnd(5,14),w3=rnd(2,l3-1); return Q('Missing side','A rectangle has perimeter '+2*(l3+w3)+' cm and length '+l3+' cm. What is its breadth in cm?',w3,rectF(l3,'Perimeter '+2*(l3+w3)+' cm','A rectangle with length '+l3+' cm, perimeter '+2*(l3+w3)+' cm and an unknown breadth'));}
var s=rnd(3,12); return t===2?Q('Missing side','A square has area '+s*s+' cm². What is the length of one side in cm?',s,sqF('Area '+s*s+' cm²','A square with area '+s*s+' square centimetres and an unknown side')):Q('Missing side','A square has perimeter '+4*s+' cm. What is the length of one side in cm?',s,sqF('Perimeter '+4*s+' cm','A square with perimeter '+4*s+' cm and an unknown side'));
}
function sTank(){
var base=pick([[50,20],[40,25],[25,40],[20,50],[50,40],[40,50],[25,80],[80,25]]),f=base[0]*base[1]/1000,d1=rnd(2,6),d2=d1+rnd(2,6),t=rnd(0,2);
if(t===0)return Q('Tank','A tank has a base '+base[0]+' cm by '+base[1]+' cm. It has water '+d2+' cm deep. How many litres of water are in it? (1 litre = 1000 cm³)',d2*f,{min:1});
if(t===1)return Q('Tank','A tank has a base '+base[0]+' cm by '+base[1]+' cm. The water rises from '+d1+' cm to '+d2+' cm deep. How many litres were poured in? (1 litre = 1000 cm³)',(d2-d1)*f,{min:1});
return Q('Tank','A tank has a base '+base[0]+' cm by '+base[1]+' cm. It holds '+(d2*f)+' litres of water. How deep is the water in cm? (1 litre = 1000 cm³)',d2,{min:1});
}
function lineFig(days,vals,top){
var x0=40,x1=220,y0=104,y1=14,i,inner='',n=days.length,py=function(v){return y0-(y0-y1)*v/top;},px=function(i){return x0+8+(x1-x0-16)*i/(n-1);};
for(var g=0;g<=top;g+=top/5){inner+=''+g+'';}
var pts=vals.map(function(v,i){return px(i).toFixed(1)+','+py(v).toFixed(1);}).join(' ');
inner+='';
vals.forEach(function(v,i){inner+=''+days[i]+'';});
return svgWrap(inner);
}
function sLineGraph(){
var days=['Mon','Tue','Wed','Thu','Fri'],top=pick([50,100]),st=top/5,vals=days.map(function(){return rnd(1,5)*st;}),i=rnd(0,4),j=rnd(0,4);
while(j===i)j=rnd(0,4);
var fig=lineFig(days,vals,top),alt='A line graph with values for Monday to Friday.',t=rnd(0,2),unit=top===50?'cm of rain':'visitors';
if(t===0)return Q('Line graph','The line graph shows the number of '+(top===50?'mm of rain':'visitors')+' each day. What is the value on '+days[i]+'?',vals[i],{fig:fig,figAlt:alt,min:0});
if(t===1){var hi=Math.max.apply(null,vals); return Q('Line graph','The line graph shows the number of '+(top===50?'mm of rain':'visitors')+' each day. What is the highest value on the graph?',hi,{fig:fig,figAlt:alt,min:0});}
var a=Math.max(vals[i],vals[j]),b=Math.min(vals[i],vals[j]),hiD=vals[i]>=vals[j]?i:j,loD=hiD===i?j:i;
return Q('Line graph','The line graph shows the number of '+(top===50?'mm of rain':'visitors')+' each day. How much more was there on '+days[hiD]+' than on '+days[loD]+'?',a-b,{fig:fig,figAlt:alt,min:0});
}
function sOddEven(){
var t=rnd(0,2),n=rnd(11,99);
if(t===0)return Q('Odd & even','Is '+n+' an odd number or an even number?',n%2?'Odd':'Even',{options:['Odd','Even']});
if(t===1){var m=rnd(10,60); return Q('Odd & even','What is the next even number after '+m+'?',m%2?m+1:m+2,{min:1});}
var k=rnd(10,40); return Q('Odd & even','Which odd number comes right after '+k+'?',k%2?k+2:k+1,{min:1});
}
function sPow10(dec){
var t=dec?rnd(0,3):rnd(0,1),f=pick([10,100,1000]);
if(t===0){var a=rnd(2,99); return Q('Powers of 10',a+' × '+f+' = ?',a*f,{min:1});}
if(t===1){var b=rnd(2,99); return Q('Powers of 10',b*f+' ÷ '+f+' = ?',b,{min:1});}
if(t===2){var c=rnd(11,99)/10; return Q('Powers of 10',c+' × '+f+' = ?',Math.round(c*f*10)/10,{min:1});}
var d=rnd(11,99); return Q('Powers of 10',d*10+' ÷ '+f+' = ?'+(((d*10)%f)?' (give a decimal)':''),Math.round(d*10/f*1000)/1000,{options:strOpts(Math.round(d*10/f*1000)/1000,[d*10/f*10,d*10/f/10,d*10*f,d*10/f+1].map(function(v){return Math.round(v*1000)/1000;}))});
}
function sBigNum(){
var t=rnd(0,2),n=rnd(1000000,9999999),s=String(n),pos=rnd(0,6),dg=+s.charAt(6-pos);
while(dg===0){pos=rnd(0,6); dg=+s.charAt(6-pos);}
var fmtn=function(x){return String(x).replace(/\B(?=(\d{3})+(?!\d))/g,' ');};
if(t<2)return Q('Big numbers','In '+fmtn(n)+', what is the value of the digit '+dg+' in the '+['ones','tens','hundreds','thousands','ten thousands','hundred thousands','millions'][pos]+' place?',dg*Math.pow(10,pos),{min:1});
var m=rnd(2,9),k=rnd(1,9); return Q('Big numbers',m+' million and '+k+' hundred thousand is written as a number. What is it?',m*1000000+k*100000,{min:1});
}
function sLikeFr(){
var d=rnd(5,12),a=rnd(1,d-3),b=rnd(1,d-1-a),add=Math.random()<.5,n=add?a+b:Math.max(a,b)-Math.min(a,b);
if(n<=0||gcd2(n,d)>1)return sLikeFr();
return Q('Like fractions',add?a+'/'+d+' + '+b+'/'+d+' = ?':Math.max(a,b)+'/'+d+' − '+Math.min(a,b)+'/'+d+' = ?',n+'/'+d,{options:strOpts(n+'/'+d,[(a+b)+'/'+(2*d),(n+1)+'/'+d,(n-1>0?n-1:n+2)+'/'+d,n+'/'+(2*d),n+'/'+(d+1)].filter(function(z){var pp=z.split('/'); return pp[0]*d!==n*pp[1];}))});
}
function sCmpFr(){
var a=rnd(2,9),b=rnd(2,9); while(a===b)b=rnd(2,9); var big=a0;}).map(function(x){return x[0]+' '+x[1]+' note'+(x[0]>1?'s':'');}); var ntx=np.length>1?np.slice(0,-1).join(', ')+' and '+np[np.length-1]:np[0];
return Q('Notes','You have '+ntx+'. How many dollars do you have?',a*10+b*5+c*2,{min:2,max:100});
}
function sLenOrder(){
var v=shuffle([rnd(2,5),rnd(6,9),rnd(10,14),rnd(15,18)]).slice(0,3),big=Math.random()<.5,L=['A','B','C'],m=big?Math.max.apply(null,v):Math.min.apply(null,v);
return Q('Length','Ribbon A is '+v[0]+' cm, ribbon B is '+v[1]+' cm and ribbon C is '+v[2]+' cm. Which ribbon is the '+(big?'longest':'shortest')+'?',L[v.indexOf(m)],{options:['A','B','C']});
}
var LEVELS=[
{name:'P1 · to 10',desc:'Count dots, add, take away and make 10 within 10, plus positions (1st, 2nd), halves, sharing and shapes.',gen:[
function(){var n=rnd(1,10); return Q('Count','How many dots?',n,{dots:n,min:1,max:10});},
function(){var a=rnd(1,9),b=rnd(1,10-a); return Q('Add',a+' + '+b+' = ?',a+b,{max:10});},
function(){var a=rnd(2,10),b=rnd(1,a-1); return Q('Take away',a+' − '+b+' = ?',a-b,{max:10});},
function(){var a=rnd(1,9); return Q('Make 10',a+' + ? = 10',10-a,{max:10});},
function(){var s=rnd(1,2); return seqQ(s,rnd(0,10-s*4),5);},
function(){return compareQ(0,10);},
function(){return sOrd();},function(){return sHalf(10);},function(){return sShare1(10);},function(){return shapeQ(false);}
]},
{name:'P1 · to 20',desc:'Add and take away within 20, 1 more and 10 more, time on a clock, picture graphs, coins and cents, length in cm, halves and shapes.',gen:[
function(){var n=rnd(11,20); return Q('Count','How many dots?',n,{dots:n,min:1,max:20});},
function(){var a=rnd(2,18),b=rnd(1,20-a); return Q('Add',a+' + '+b+' = ?',a+b,{max:20});},
function(){var a=rnd(5,20),b=rnd(1,a-1); return Q('Take away',a+' − '+b+' = ?',a-b,{max:20});},
function(){var a=rnd(1,19); return Q('Make 20',a+' + ? = 20',20-a,{max:20});},
function(){var n=rnd(2,18),t=rnd(0,2); if(t===0)return Q('More or less','What is 1 more than '+n+'?',n+1,{max:20}); if(t===1)return Q('More or less','What is 1 less than '+n+'?',n-1,{max:20}); n=rnd(1,10); return Q('More or less','What is 10 more than '+n+'?',n+10,{max:20});},
function(){return placeQ(1,rnd(0,9));},
function(){var s=pick([2,5]); return seqQ(s,s*rnd(0,2),5);},
function(){return compareQ(0,20);},
function(){return clockQ(true);},
function(){return picQ(1);},
function(){return sCoins(1);},function(){return sLen1(1);},function(){return sHalf(20);},function(){return shapeQ(false);},function(){return sOrd();},
function(){return sLenOrder();},function(){return sAmPm(true);}
]},
{name:'P1–2 · to 100',desc:'Add and take away within 100, tens and ones, 10 more or less, skip counting and telling time on a clock.',gen:[
function(){var a=rnd(11,89),b=pick([rnd(1,9),rnd(10,50)]); if(a+b>100)b=100-a; if(b<1)b=1; return Q('Add',a+' + '+b+' = ?',a+b,{max:100});},
function(){var a=rnd(20,99),b=pick([rnd(1,9),rnd(10,Math.min(a-1,60))]); if(b>=a)b=a-1; return Q('Take away',a+' − '+b+' = ?',a-b,{max:100});},
function(){return placeQ(rnd(2,9),rnd(0,9));},
function(){var n=rnd(11,89); return rnd(0,1)?Q('More or less','What is 10 more than '+n+'?',n+10,{max:100}):Q('More or less','What is 10 less than '+n+'?',n-10,{max:100});},
function(){var s=pick([2,5,10]); return seqQ(s,s*rnd(0,5),5);},
function(){return compareQ(10,99);},
function(){return clockQ(true);},
function(){return picQ(pick([2,5,10]));},
function(){return shapeQ(false);},
function(){return sCoins(2);},function(){return sLen1(2);},function(){return sHalf(20);},function(){return sShare1(20);},
function(){return sOddEven();},function(){return sNotes();},function(){return sAmPm();}
]},
{name:'P2–3 · × and ÷',desc:'Times tables, sharing equally, missing numbers, bigger sums and simple unit conversion (m and cm, kg and g, ℓ and mℓ).',gen:[
function(){var a=pick([2,3,4,5,10]),b=rnd(1,10); return Q('Multiply',a+' × '+b+' = ?',a*b);},
function(){var a=pick([2,3,4,5,10]),b=rnd(1,10); return Q('Divide',(a*b)+' ÷ '+a+' = ?',b,{max:12});},
function(){var a=pick([2,3,4,5,10]),b=rnd(2,10); return Q('Missing number','? × '+a+' = '+(a*b),b,{max:12});},
function(){var a=rnd(11,70)*10,b=rnd(1,Math.floor((1000-a)/10))*10; return Q('Add',a+' + '+b+' = ?',a+b);},
function(){var a=rnd(20,90)*10,b=rnd(1,a/10-1)*10; return Q('Take away',a+' − '+b+' = ?',a-b);},
function(){return unitsQ(0);},
function(){return shapeQ(true);},
function(){return picQ(pick([2,5,10]));},
function(){return sLikeFr();},function(){return sCmpFr();},function(){return sLines();},
function(){return clockQ(false);}
]},
{name:'P3–4 · Big numbers & word problems',desc:'Times tables of 6 to 9, remainders, 3- and 4-digit sums, 2-digit × 1-digit, long division, rounding, word problems, money and conversion of units (km and m, h and min).',gen:[
function(){var a=rnd(120,880),b=rnd(120,999-a); return Q('Add',a+' + '+b+' = ?',a+b);},
function(){var a=rnd(300,999),b=rnd(100,a-20); return Q('Take away',a+' − '+b+' = ?',a-b);},
function(){var a=rnd(12,49),b=rnd(3,9); return Q('Multiply',a+' × '+b+' = ?',a*b);},
function(){var d=rnd(3,9),q=rnd(12,40); return Q('Divide',(d*q)+' ÷ '+d+' = ?',q);},
function(){var n=rnd(1100,9899), h=Math.round(n/100)*100, t=Math.round(n/10)*10; if(h===t||n%100===50){n+=3; h=Math.round(n/100)*100; t=Math.round(n/10)*10;} return Q('Rounding','Round '+n+' to the nearest hundred.',h,{options:strOpts(h,[h+100,h-100,t,Math.floor(n/1000)*1000+1000])});},
function(){var b=rnd(2,6),c=rnd(2,9),a=b*c+rnd(5,40); return Q('Word problem','Mei has $'+a+'. She buys '+b+' pens at $'+c+' each. How much money does she have left?',a-b*c,{min:0});},
function(){var k=rnd(3,9),q=rnd(6,25); return Q('Word problem',(k*q)+' stickers are shared equally among '+k+' children. How many stickers does each child get?',q);},
function(){var b=rnd(6,15),c=rnd(12,48); return Q('Word problem','There are '+b+' boxes with '+c+' apples in each box. How many apples are there altogether?',b*c);},
function(){var d=rnd(3,9),c=pick([10,20,25,30,40,50,60,70,75,80,90]),m=rnd(1,d-1),price=d*100+c,ans=price-m*100; return Q('Money',money(price)+' \u2212 $'+m+' = ?',money(ans),{options:strOpts(money(ans),[money(price-m),money(price+m*100),money(ans+100),money(ans-100),money(ans+c)])});},
function(){return unitsQ(1);},
function(){var a=pick([6,7,8,9]),b=rnd(2,10); return Q('Times tables',a+' × '+b+' = ?',a*b);},
function(){var a=pick([6,7,8,9]),b=rnd(2,10); return Q('Times tables',(a*b)+' ÷ '+a+' = ?',b,{max:12});},
function(){var d=rnd(3,9),q=rnd(3,12),r=rnd(1,d-1); return Q('Remainder',(d*q+r)+' sweets are shared equally among '+d+' children. How many sweets are left over?',r,{max:d-1});},
function(){var a=rnd(1200,6800),b=rnd(1100,9999-a); return Q('Add',a+' + '+b+' = ?',a+b);},
function(){var a=rnd(3000,9999),b=rnd(1100,a-500); return Q('Take away',a+' − '+b+' = ?',a-b);},
function(){return barQ();},
function(){return tableQ();},
function(){return durQ();},
function(){return h24Q();},
function(){return sNets();},function(){return sMissing(false);},function(){return sLineGraph();},function(){return sPow10(false);},function(){return sLines();}
]},
{name:'P4–5 · Fractions, decimals & area',desc:'Add fractions, fraction of a set, equivalent fractions, decimals, perimeter, area, multiples, money, 3-digit × 2-digit, numbers up to 100 000 and decimal unit conversion.',gen:[
function(){var d=pick([5,6,7,8,9,10,12]),a=rnd(1,d-2),b=rnd(1,d-1-a),s=a+b; return Q('Fractions',a+'/'+d+' + '+b+'/'+d+' = ?',s+'/'+d,{options:strOpts(s+'/'+d,[s+'/'+(2*d),(s+1)+'/'+d,(s-1)+'/'+d,(a*b)+'/'+d,s+'/'+(d+1)])});},
function(){var d=pick([2,3,4,5,6,8,10]),n=rnd(1,d-1),base=d*rnd(2,12); return Q('Fraction of a set',n+'/'+d+' of '+base+' = ?',base/d*n);},
function(){var b=pick([3,4,5,6,8]),a=rnd(1,b-1),k=rnd(2,6); return Q('Equivalent fractions',a+'/'+b+' = ?/'+(b*k),a*k);},
function(){var a=rnd(12,79),b=rnd(12,79),s=a+b; return Q('Decimals',tenths(a)+' + '+tenths(b)+' = ?',tenths(s),{options:strOpts(tenths(s),[tenths(s+1),tenths(s-1),tenths(s+10),tenths(s-10),tenths(s+9)])});},
function(){var a=rnd(40,99),b=rnd(11,a-5),s=a-b; return Q('Decimals',tenths(a)+' − '+tenths(b)+' = ?',tenths(s),{options:strOpts(tenths(s),[tenths(s+1),tenths(s-1),tenths(s+10),tenths(Math.abs(s-10)),tenths(s+9)])});},
function(){var l=rnd(8,25),w=rnd(4,l-1); return Q('Perimeter','A rectangle is '+l+' cm long and '+w+' cm wide. What is its perimeter in cm?',2*(l+w),{fig:rectFig(l+' cm',w+' cm'),figAlt:'A rectangle '+l+' cm long and '+w+' cm wide.',options:strOpts(2*(l+w),[l*w,l+w,2*l+w,2*(l+w)+2,2*(l+w)-2])});},
function(){var l=rnd(6,19),w=rnd(4,15); return Q('Area','A rectangle is '+l+' cm long and '+w+' cm wide. What is its area in cm²?',l*w,{fig:rectFig(l+' cm',w+' cm'),figAlt:'A rectangle '+l+' cm long and '+w+' cm wide.',options:strOpts(l*w,[2*(l+w),l*w+l,l*w-w,l+w,l*w+10])});},
function(){var m=rnd(3,9),k=rnd(3,12),right=m*k,set={},arr=[right]; set[right]=1; while(arr.length<4){var v=right+rnd(-14,14); if(v>1&&!set[v]&&v%m!==0){set[v]=1; arr.push(v);}} return Q('Multiples','Which of these is a multiple of '+m+'?',right,{options:arr});},
function(){var pay=pick([5,10,20]),cost=rnd(21,pay*20-4)*5,ans=pay*100-cost; return Q('Money','You pay $'+pay+' for something that costs '+money(cost)+'. How much change do you get?',money(ans),{options:strOpts(money(ans),[money(cost),money(ans+100),money(Math.abs(ans-100)),money(pay*100+cost)])});},
function(){return unitsQ(2);},
function(){var k=pick([10,100,1000]),t=Math.random()<.5, n=t?rnd(2,99):rnd(11,99); return t?Q('Multiply',n+' × '+k+' = ?',n*k):Q('Multiply',tenths(n)+' × '+k+' = ?',n*k/10);},
function(){var k=pick([10,100,1000]),a=rnd(2,99); return Q('Divide',(a*k)+' ÷ '+k+' = ?',a);},
function(){var n=rnd(10000,99999),k=rnd(0,4),names=['ones','tens','hundreds','thousands','ten thousands'],d=Math.floor(n/Math.pow(10,k))%10; if(d===0){n+=Math.pow(10,k);d=Math.floor(n/Math.pow(10,k))%10;} var ans=d*Math.pow(10,k); var ns=String(n).replace(/(\d)(\d{3})$/,'$1 $2'); return Q('Place value','In '+ns+', what is the value of the digit '+d+' in the '+names[k]+' place?',ans,{options:strOpts(ans,[d*Math.pow(10,Math.min(4,k+1)),d*Math.pow(10,Math.max(0,k-1)),d,d*Math.pow(10,k)*(k<4?1:1)+Math.pow(10,k)])});},
function(){var n=rnd(10001,98999),to=pick([10,100,1000]),r=Math.round(n/to)*to; if(n%to===to/2)n+=1,r=Math.round(n/to)*to; var nm={10:'ten',100:'hundred',1000:'thousand'}[to]; return Q('Rounding','Round '+n+' to the nearest '+nm+'.',r,{options:strOpts(r,[r+to,r-to,Math.floor(n/to)*to===r?Math.floor(n/to)*to+to:Math.floor(n/to)*to,Math.round(n/(to*10))*to*10])});},
function(){return factorQ();},
function(){return mixedQ();},
function(){return pieQ(false);},
function(){return barQ();},
function(){return sTriProps();},function(){return sQuadAng();},function(){return sComposite(false);},function(){return sBigNum();},function(){return sTank();},function(){return sSymm();},
function(){return sPow10(true);}
]},
{name:'P5–6 · Percentage, ratio & more',desc:'Percentage of a quantity, ratio, average, order of operations, triangle area, angles with diagrams, unit conversion and two-step problems.',gen:[
function(){var p=pick([5,10,20,25,30,40,50]),b=rnd(2,20)*20; return Q('Percentage',p+'% of '+b+' = ?',b*p/100);},
function(){var a=rnd(1,5),b=rnd(a+1,7),k=rnd(3,9),tot=k*(a+b); return Q('Ratio','The ratio of red to blue marbles is '+a+' : '+b+'. There are '+tot+' marbles. How many are red?',k*a,{options:strOpts(k*a,[k*b,tot-a,a*b,k*(a+b)-k,tot/2])});},
function(){var av=rnd(14,45),x=rnd(2,9),y=rnd(10,12),nums=shuffle([av-x,av+x,av-y,av+y]); return Q('Average','What is the average of '+nums.join(', ')+'?',av);},
function(){var a=rnd(2,12),b=rnd(2,9),c=rnd(2,9),d=rnd(1,Math.min(10,a+b*c-1)),ans=a+b*c-d; return Q('Order of operations',a+' + '+b+' × '+c+' − '+d+' = ?',ans,{options:strOpts(ans,[(a+b)*c-d,ans+1,ans-1,ans+10,a+b*(c-d)])});},
function(){var b=rnd(4,20)*2,h=rnd(3,15); return Q('Area of a triangle','A triangle has a base of '+b+' cm and a height of '+h+' cm. What is its area in cm²?',b*h/2,{fig:triHFig(b+' cm',h+' cm'),figAlt:'A triangle with base '+b+' cm and height '+h+' cm shown by a dashed line.',options:strOpts(b*h/2,[b*h,b+h,b*h/2+h,(b+h)/2,b*h/4])});},
function(){return anglesQ();},
function(){return unitsQ(3);},
function(){var base=rnd(2,10)*12,pr=pick([[2,3],[2,4],[3,4],[3,6],[4,6],[2,6]]); return Q('Two-step problem','Sam had $'+base+'. He spent 1/'+pr[0]+' of it on a book and 1/'+pr[1]+' of it on lunch. How much money does he have left?',base-base/pr[0]-base/pr[1]);},
function(){return fracMulQ();},
function(){return moneyPctQ();},
function(){return algebraQ();},
function(){return rateQ();},
function(){return volQ();},
function(){return circleQ();},
function(){return pieQ(true);},
function(){return sComposite(true);},function(){return sWholeDivFr();},function(){return sMissing(true);},function(){return sTank();},function(){return sQuadAng();},function(){return sTriProps();}
]},
{name:'Sec 1–2 · Algebra, ratio & data',desc:'Negative numbers, equations, simplifying, ratio sharing, speed, polygons, trapezium and cylinder, mean/median/mode, reverse percentages, indices, LCM and HCF.',gen:[
function(){return sNeg();},function(){return sEq();},function(){return sSimp();},function(){return sRatioShare();},
function(){return sSpeed();},function(){return sPoly();},function(){return sArea2();},function(){return sStats();},
function(){return sPctRev();},function(){return sIndices1();},function(){return sLcm();}
]},
{name:'Sec 3–4 · Quadratics, trig & probability',desc:'Quadratic equations, Pythagoras, trigonometry, indices laws, probability, simultaneous equations, circle theorems, gradient, sequences, standard form and compound interest.',gen:[
function(){return sQuad();},function(){return sPyth();},function(){return sTrig();},function(){return sIndices2();},
function(){return sProb();},function(){return sSim();},function(){return sCircle();},function(){return sGrad();},
function(){return sSeq();},function(){return sStdForm();},function(){return sCompound();}
]},
{name:'Post-sec · Everyday money & work',desc:'Real-life maths for work and adult life: loans, tax, wages and overtime, discounts, GST and service charge, profit, unit conversion, exchange rates, saving and budgeting.',gen:[
function(){return sLoan();},function(){return sTax();},function(){return sWage();},function(){return sShop();},
function(){return sConvert();},function(){return sAvgSpeed();},function(){return sBudget();},function(){return sPctRev();},
function(){return sSpeed();},function(){return sCompound();}
]},
{name:'Post-sec · Functions, logs & calculus',desc:'Functions, logarithms, radians and special angles, differentiation, definite integrals, counting, series, growth, quadratics, z-scores and vectors.',gen:[
function(){return sFunc();},function(){return sLog();},function(){return sRad();},function(){return sCalc();},
function(){return sCount();},function(){return sSeries();},function(){return sGrowth();},function(){return sVertex();},
function(){return sStat2();},function(){return sVec();},function(){return sProb();}
]}
];
P4GAP.forEach(function(f){LEVELS[5].gen.push(function(){return f();});});
function options(q){
if(q.options)return q.keep?q.options.slice():shuffle(q.options.slice());
var a=q.answer, min=q.min!=null?q.min:0, max=q.max!=null?q.max:Infinity, set={}, out=[a]; set[a]=1;
var sp=a>=200?[10,100,20,50]:a>=40?[1,2,5,10]:a>=12?[1,2,3,10]:[1,2,3], c=[];
var r9=function(v){return typeof v==='number'?+v.toFixed(9):v;}; sp.forEach(function(s){c.push(r9(a+s),r9(a-s));}); shuffle(c);
c.forEach(function(v){ if(out.length<4&&!set[v]&&v>=min&&v<=max){set[v]=1; out.push(v);} });
for(var d=1;out.length<4&&d<300;d++){[a+d,a-d].forEach(function(v){ if(out.length<4&&!set[v]&&v>=min&&v<=max){set[v]=1; out.push(v);} });}
return shuffle(out);
}
/* ---------- timed quiz, storms and checkpoints ---------- */
var mode='play', diag=null, cq=null, tries=0, locked2=false, counted=false, lastKey='';
var qTimer=null, qStart=0, qLimit=0, qLimit0=0, xtUsed=false, qActive=false, pendingFx=null, lastKind='', lastAlert=null;
var el={kick:document.getElementById('kicker'),vis:document.getElementById('vis'),qt:document.getElementById('qtext'),ans:document.getElementById('answers'),fb:document.getElementById('fb'),rw:document.getElementById('rewards'),next:document.getElementById('next'),up:document.getElementById('up'),cpUse:document.getElementById('cpUse'),cpAgain:document.getElementById('cpAgain'),timer:document.getElementById('timer'),tbar:document.getElementById('tbar'),tfill:document.getElementById('tfill'),tnum:document.getElementById('tnum'),alert:document.getElementById('alert'),setup:document.getElementById('setup'),title:document.getElementById('qTitle')};
function curLevel(){return mode==='check'&&diag?diag.lvl:S.level;}
/* ---- My mistakes: every wrong or timed-out question is saved. Premium shows the analysis and lets the Learner practise them. ---- */
var PM=null;
function mxKey(q){return String(q.kicker)+'|'+String(q.text).replace(/<[^>]*>/g,'').slice(0,80)+'|'+String(q.answer);}
function mxSub(){var id=S.pwLast||'mathBtn', nm=null; try{LRNSUBS.forEach(function(r){if(!nm&&r[0]===id)nm=r[1];});}catch(e){} return nm||'Maths';}
function mxLog(q,why){
try{
if(!q||!q.text||q.bridge)return; var L=S.mx=S.mx||[], key=mxKey(q), it=L.filter(function(x){return x.key===key;})[0];
if(it){it.n=(it.n||1)+1; it.d=dkey(0); it.why=why; return;}
var c={}; for(var k in q){if(k==='rec'||k==='rec0'||k==='xt'||k==='dx'||k==='bridge'||k==='mxid')continue; if(typeof q[k]==='function')continue; c[k]=q[k];}
if(JSON.stringify(c).length>2600)return;
L.push({key:key,id:Date.now().toString(36)+Math.random().toString(36).slice(2,5),sub:mxSub(),k:q.kickShow||q.kicker,why:why,n:1,d:dkey(0),q:c});
while(L.length>80)L.shift();
}catch(e){}
}
function mxFixKey(key){
try{var L=S.mx||[], n=L.length; S.mx=L.filter(function(x){return x.key!==key;}); if(S.mx.length=PM.ids.length)return null; var id=PM.ids[PM.i++], it=(S.mx||[]).filter(function(x){return x.id===id;})[0]; if(!it)return pmNext();
var q=JSON.parse(JSON.stringify(it.q)); q.rec=false; q.mxid=id; q.kickShow=(it.k||q.kicker)+' · from My mistakes'; return q;
}
function startMx(ids){
if(!ids||!ids.length)return; closeModals(); PM={ids:ids.slice(0,10),i:0,ok:0}; openQuiz(); if(modal.hidden)PM=null;
}
function mxPlain(t){return String(t||'').replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim();}
function openMx(){
if(modalOpen())return; pauseGame(); renderMx(); ob('mxModal').hidden=false; ob('mxModal').scrollTop=0; try{ob('mxPanel').focus({preventScroll:true});}catch(_){}
}
function renderMx(){
var L=(S.mx||[]).slice(), box=ob('mxBody'); box.innerHTML='';
if(!L.length){box.appendChild(mk('p','lvldesc','No mistakes saved yet. When a question goes wrong or time runs out, it is saved here so you can fix it later.')); return;}
box.appendChild(mk('p','lvldesc',L.length+' question'+(L.length===1?'':'s')+' waiting to be fixed. A question leaves this list when you answer it correctly.'));
if(!premium()){
box.appendChild(mk('p','lvldesc','Premium shows which topics and kinds of mistakes come up most, and lets you practise exactly these questions.'));
var pb=mk('button','btn primary','See Premium'); pb.type='button'; pb.addEventListener('click',function(){closeModals(); setTimeout(openPrem,30);}); box.appendChild(pb); return;
}
function sect(t){var h=mk('div','label',t); h.style.margin='14px 0 4px'; box.appendChild(h);}
var order=L.slice().sort(function(a,b){return (b.n||1)-(a.n||1)||(b.d>a.d?1:-1);});
var go=mk('button','btn primary','Practise my mistakes ('+Math.min(10,order.length)+')'); go.type='button'; go.addEventListener('click',function(){startMx(order.map(function(x){return x.id;}));}); box.appendChild(go);
var wy={wrong:0,rushed:0,time:0}; L.forEach(function(x){wy[x.why]=(wy[x.why]||0)+1;});
sect('What kind of mistakes');
[['wrong','Wrong answer','Read the explanation, then practise the same kind of question.'],['rushed','Answered too fast','Slow down. Check your answer before you tap.'],['time','Ran out of time','Use the Working space and practise the steps until they feel quick.']].forEach(function(r){
if(!wy[r[0]])return; var row=mk('div','lvldesc'); row.innerHTML=' '; row.firstChild.textContent=r[1]+': '+wy[r[0]]; row.appendChild(document.createTextNode(' · '+r[2])); box.appendChild(row);});
var bySub={}, byTop={}; L.forEach(function(x){bySub[x.sub]=(bySub[x.sub]||0)+1; var t=byTop[x.k]||(byTop[x.k]={n:0,ids:[]}); t.n+=(x.n||1); t.ids.push(x.id);});
var sk=Object.keys(bySub).sort(function(a,b){return bySub[b]-bySub[a];}), mxs=bySub[sk[0]];
sect('By subject'); sk.forEach(function(k){var row=mk('div','lvldesc',k+': '+bySub[k]); box.appendChild(row); box.appendChild(miniBar(bySub[k],mxs));});
var tk=Object.keys(byTop).sort(function(a,b){return byTop[b].n-byTop[a].n;}).slice(0,6);
sect('Topics to work on');
tk.forEach(function(k){var row=mk('div','row2'); var lab=mk('span','lvldesc',k+' · missed '+byTop[k].n+(byTop[k].n===1?' time':' times')); lab.style.flex='1'; row.appendChild(lab);
var b=mk('button','btn small','Practise'); b.type='button'; b.addEventListener('click',function(){startMx(byTop[k].ids);}); row.appendChild(b); box.appendChild(row);});
var rep=L.filter(function(x){return (x.n||1)>=2;}).length; if(rep){box.appendChild(mk('p','lvldesc',rep+' question'+(rep===1?' has':'s have')+' been missed more than once. These come first in your practice.'));}
sect('Latest mistakes');
order.slice().sort(function(a,b){return b.d>a.d?1:b.d110?t.slice(0,107)+'...':t)+' Answer: '+x.q.answer; box.appendChild(d);});
}
ob('mxBack').addEventListener('click',closeModals);
/* Adaptive time: if a learner keeps running out of time, questions get longer (up to 1.8x); when they answer comfortably it eases back. */
function taNote(timedOut,fracLeft){
if(UNT||mode==='duel'||mode==='check')return;
var h=S.tarun=S.tarun||[]; h.push(timedOut?'T':(fracLeft>.4?'E':'O')); if(h.length>8)h.shift();
var t=h.filter(function(x){return x==='T';}).length, e=h.filter(function(x){return x==='E';}).length, a=S.ta||1;
if(t>=3&&a<1.8){S.ta=Math.min(1.8,Math.round((a+.2)*100)/100); S.tarun=[]; try{hint('You are getting more time on each question now ('+Math.round((S.ta-1)*100)+'% more). Take your time. It will come back down as you get quicker.',true);}catch(_){}}
else if(e>=6&&h.length>=8&&a>1){S.ta=Math.max(1,Math.round((a-.1)*100)/100); S.tarun=[]; try{hint('You are answering with time to spare, so the timer is getting a little shorter again.',true);}catch(_){}}
}
function timeLimit(){
if(S.pl&&S.pl.k!=='off'&&S.pl.qs>0)return Math.max(5,S.pl.qs|0);
var base=((cq&&cq.tb)?cq.tb:[14,16,18,18,30,34,40,48,56,52,64][(cq&&cq.lv!=null)?cq.lv:curLevel()])*(cq&&cq.df==='hard'?.8:cq&&cq.df==='easy'?1.25:1)*(cq&&cq.wp?1.6:1);
var mul=(mode==='check'?Math.max(1,S.tm)*1.3:mode==='duel'?S.tm*.75:S.tm)*(mode==='duel'||mode==='check'?1:(S.ta||1));
var tmul=(cq&&useTyped())?(S.ans==='pix'?2:1.3):1;
return Math.max(6,Math.round(base*mul*tmul));
}
var TOPICS=[
{id:'num',name:'Numbers',k:['Number words','Compare numbers','Number sequences','Count','Add','Take away','Make 10','Make 20','Pattern','Compare','More or less','Place value','Rounding','Missing number','Word problem','Remainder','Order of operations','Two-step problem','Negative numbers','Indices','LCM & HCF','Sequences','Standard form','Counting','Odd & even','Powers of 10','Big numbers']},
{id:'mul',name:'Times tables & factors',k:['Common multiples','Multiply','Divide','Times tables','Multiples','Factors','Sharing']},
{id:'frac',name:'Fractions & decimals',k:['Decimal place','Order decimals','Decimal to fraction','Fraction to decimal','Round decimals','Decimal ×÷','Divide to decimal','Round answer','Fractions','Fraction of a set','Equivalent fractions','Half & quarter','Like fractions','Fraction \u00f7','Decimals','Mixed numbers','Improper fractions','Fraction ×','Fraction ÷']},
{id:'pct',name:'Money & percentage',k:['Money','Percentage','Discount','GST','Interest','Percentage change','Find the whole','Loan','Tax','Wage','Profit','Exchange','Coins','Notes','Saving','Budget','Compound interest','Growth']},
{id:'time',name:'Time',k:['Telling time','Time','24-hour time','am/pm','Duration']},
{id:'meas',name:'Measures',k:['Units','Perimeter','Area','Area of a triangle','Volume','Circle','Speed','Length','Composite figures','Missing side','Tank','Trapezium','Parallelogram','Cylinder','Convert']},
{id:'geo',name:'Shapes & angles',k:['Angle names','Measure angle','Draw angle','Rectangle & square','Shapes','Position','Symmetry','Lines','Nets','Triangles','Quadrilaterals','Angles','Polygons','Pythagoras','Trigonometry','Circle theorem','Gradient','Radians','Vectors']},
{id:'data',name:'Graphs & data',k:['Picture graph','Bar graph','Table','Pie chart','Line graph','Average','Statistics','Probability']},
{id:'alg',name:'Ratio, rate & algebra',k:['Ratio','Rate','Algebra','Equation','Simplify','Quadratic','Simultaneous equations','Function','Log','Calculus','Series']}
];
var COMPT={id:'comp',name:'Computing (Sec 3-4)',k:['Computing: Computing fundamentals','Computing: Networking and cloud','Computing: Impact and safe use','Computing: Document processing','Computing: Media software','Computing: Spreadsheets','Computing: Programming']};
var SCT={id:'sci',name:'Science (P3-P6)',k:['Science: P3','Science: P4','Science: P5','Science: P6']};
var DTT={id:'dt',name:'Design & Technology',k:['D&T']};
var MUT={id:'mus',name:'Music',k:['Music: Reading notes','Music: Rhythm and time','Music: Scales, keys and intervals','Music: Terms and signs','Music: Listening','Music: Western Classical','Music: Asian music','Music: Jazz','Music: Popular music','Music: Film and TV music']};
var E21T={id:'e21',name:'Aura skills (Think and decide)',k:['Aura skills: Think and create','Aura skills: Connect and share','Aura skills: Care for our world','Aura skills: Grow']};
var COOKT={id:'cook',name:'Cooking & portions',k:['Cooking: scale','Cooking: measure','Cooking: portions','Cooking: nutrition']};
var WPT={id:'wp',name:'Word problems, step by step',k:['Word problem: find','Word problem: plan','Word problem: work out']};
/* ---------- lesson objectives (teacher picks what the class practises) ---------- */
var OBJ=[
[0,'Count objects up to 10','Count'],[0,'Add within 10','Add'],[0,'Take away within 10','Take away'],[0,'Make 10 (number bonds)','Make 10'],[0,'Number patterns and comparing numbers','Pattern|Compare'],[0,'Ordinal positions: 1st, 2nd, 3rd','Position'],[0,'Halves and sharing equally','Half & quarter|Sharing'],[0,'Recognise 2D shapes','Shapes'],
[1,'Count objects up to 20','Count'],[1,'Add and take away within 20','Add|Take away|Make 20'],[1,'1 more, 10 more, tens and ones','More or less|Place value'],[1,'Number patterns and comparing numbers','Pattern|Compare'],[1,'Tell time, am and pm, half an hour and 1 hour','Telling time|am/pm|Duration'],[1,'Read picture graphs','Picture graph'],[1,'Count coins in cents','Coins'],[1,'Measure and compare length in cm','Length'],[1,'Halves, shapes and positions','Half & quarter|Shapes|Position'],
[2,'Add and take away within 100','Add|Take away'],[2,'Place value and 10 more or less','Place value|More or less'],[2,'Skip counting patterns and comparing numbers','Pattern|Compare'],[2,'Tell time on a clock, am and pm','Telling time|am/pm'],[2,'Read picture graphs with a scale','Picture graph'],[2,'Money: coins and dollar notes','Coins|Notes'],[2,'Odd and even numbers','Odd & even'],[2,'Length, halves and sharing','Length|Half & quarter|Sharing'],[2,'Recognise 2D shapes','Shapes'],
[3,'Multiplication and division facts','Multiply|Divide|Missing number'],[3,'Add and take away bigger numbers','Add|Take away'],[3,'Convert units: m and cm, kg and g, ℓ and mℓ','Units'],[3,'Add and take away like fractions','Like fractions'],[3,'Shapes, parallel and perpendicular lines','Shapes|Lines'],[3,'Line symmetry','Symmetry'],[3,'Read picture graphs','Picture graph'],[3,'Tell time on a clock','Telling time'],
[4,'Add, take away, multiply and divide big numbers','Add|Take away|Multiply|Divide|Times tables|Remainder'],[4,'Rounding and powers of 10','Rounding|Powers of 10'],[4,'Word problems','Word problem'],[4,'Money problems','Money'],[4,'Convert units','Units'],[4,'Read bar graphs, tables and line graphs','Bar graph|Table|Line graph'],[4,'Time, duration and the 24-hour clock','Time|24-hour time'],[4,'Nets and solids','Nets'],[4,'Missing sides of rectangles and squares','Missing side'],[4,'Parallel and perpendicular lines','Lines'],
[5,'Fractions: equivalent, mixed numbers, improper','Fractions|Equivalent fractions|Mixed numbers|Improper fractions'],[5,'Fraction of a set','Fraction of a set'],[5,'Decimals','Decimals'],[5,'Perimeter and area','Perimeter|Area'],[5,'Composite figures and missing sides','Composite figures|Missing side'],[5,'Factors and multiples','Factors|Multiples'],[5,'Place value, rounding and big-number operations','Place value|Rounding|Big numbers|Multiply|Divide|Powers of 10'],[5,'Money and unit conversion','Money|Units'],[5,'Pie charts and bar graphs','Pie chart|Bar graph'],[5,'Triangles and quadrilaterals: unknown angles','Triangles|Quadrilaterals'],[5,'Water in a rectangular tank','Tank'],[5,'Line symmetry','Symmetry'],
[6,'Percentage, discount, GST and interest','Percentage|Discount|GST|Interest|Percentage change|Find the whole'],[6,'Ratio','Ratio'],[6,'Units of measurement','Units'],[6,'Average','Average'],[6,'Order of operations and two-step problems','Order of operations|Two-step problem'],[6,'Area of a triangle','Area of a triangle'],[6,'Angles on a line, at a point and in a triangle','Angles'],[6,'Multiply and divide fractions','Fraction ×|Fraction ÷'],[6,'Algebra: simple expressions and equations','Algebra'],[6,'Rate','Rate'],[6,'Volume, missing edges and tanks','Volume|Missing side|Tank'],[6,'Circles: area and circumference','Circle'],[6,'Composite figures with semicircles','Composite figures'],[6,'Pie charts','Pie chart'],[6,'Triangles and quadrilaterals: unknown angles','Triangles|Quadrilaterals'],
[7,'Negative numbers','Negative numbers'],[7,'Linear equations and simplifying','Equation|Simplify'],[7,'Ratio and speed','Ratio|Speed'],[7,'Polygons and areas','Polygons|Parallelogram|Cylinder|Trapezium'],[7,'Mean, median, mode and range','Statistics'],[7,'Reverse percentages, indices, LCM and HCF','Percentage change|Indices|LCM & HCF'],
[8,'Quadratic equations','Quadratic'],[8,'Pythagoras and trigonometry','Pythagoras|Trigonometry'],[8,'Indices and standard form','Indices|Standard form'],[8,'Probability','Probability'],[8,'Simultaneous equations','Simultaneous equations'],[8,'Circle theorems and gradient','Circle theorem|Gradient'],[8,'Sequences and compound interest','Sequences|Compound interest'],
[9,'Loans, tax and wages','Loan|Tax|Wage'],[9,'Discounts, GST, profit and budgeting','Discount|GST|Profit|Budget|Saving'],[9,'Unit conversion, exchange and speed','Convert|Exchange|Speed'],[9,'Percentage change and compound interest','Percentage change|Compound interest'],
[10,'Functions and quadratics','Function|Quadratic'],[10,'Logarithms and growth','Log|Growth'],[10,'Radians and trigonometry','Radians|Trigonometry'],[10,'Differentiation and integration','Calculus'],[10,'Counting, series and probability','Counting|Series|Probability'],[10,'Statistics and vectors','Statistics|Vectors']
];
var OBJC={};
var OBJT=OBJ.map(function(o,i){return {id:'o'+i,lv:o[0],name:o[1],k:o[2].split('|')};});
function objKs(ids){var ks={}; ids.forEach(function(i){var o=OBJ[i]; if(o)o[2].split('|').forEach(function(k){ks[k]=1;});}); return ks;}
function objGens(lu,ks){
var key=lu+'|'+Object.keys(ks).join(','); if(OBJC[key])return OBJC[key];
var L=LEVELS[lu],out=[]; L.gen.forEach(function(g,gi){for(var n=0;n<40;n++){if(ks[g().kicker]){out.push(gi); break;}}});
return OBJC[key]=out;
}
function objSample(lu,ks){
var gs=objGens(lu,ks); if(!gs.length)return null;
for(var n=0;n<40;n++){var q=LEVELS[lu].gen[pick(gs)](); if(ks[q.kicker])return q;} return null;
}
function objFrom(d){
if(!d||!Array.isArray(d.obj)||!d.obj.length)return null;
var ids=d.obj.filter(function(i){return OBJ[i]&&OBJ[i][0]===(d.ol|0);}); if(!ids.length)return null;
return {lv:Math.min(Math.max(d.ol|0,0),LEVELS.length-1),ids:ids,ks:objKs(ids)};
}
function objActive(){return (typeof sess!=='undefined'&&sess&&sessLive(sess))?objFrom(sess):null;}
function genObj(ob){
var gs=objGens(ob.lv,ob.ks),L=LEVELS[ob.lv],q,key,t=0,gi;
if(!gs.length){gi=rnd(0,L.gen.length-1); q=L.gen[gi]();}
else do{gi=pick(gs); q=L.gen[gi](); key=q.text+'|'+(q.dots||''); t++;}while((!ob.ks[q.kicker]||key===lastKey)&&t<40);
lastKey=q.text+'|'+(q.dots||''); q.opts=options(q); q.gi=gi; q.lv=ob.lv; q.df='normal'; q.rv=false; q.rec=false; return q;
}
var TGEN={};
function topicMap(li){
if(TGEN[li])return TGEN[li]; var m={}, L=LEVELS[li];
L.gen.forEach(function(g,gi){TOPICS.forEach(function(){}); for(var n=0;n<30;n++){var k=g().kicker; TOPICS.concat(OBJT.filter(function(t){return t.lv===li;})).forEach(function(t){if(t.k.indexOf(k)>=0){(m[t.id]=m[t.id]||{})[gi]=1;}});}});
return TGEN[li]=m;
}
function effTopic(){if(BONT&&mode==='play'&&!objActive())return BONT; return (typeof sess!=='undefined'&&sess&&sessLive(sess)&&sess.topic&&sess.topic!=='all')?sess.topic:(S.topic||'all');}
function topicLocked(){return !!objActive()||effTopic()!==(S.topic||'all')||(typeof sess!=='undefined'&&sess&&sessLive(sess)&&!!sess.topic&&sess.topic!=='all');}
function topicOk(li){var t=effTopic(); if(t==='all'||mode==='check')return null; var m=topicMap(li)[t]; return m||null;}
function pickGi(li){var ok=topicOk(li),n=LEVELS[li].gen.length; if(!ok)return rnd(0,n-1); var a=Object.keys(ok); return +pick(a);}
function gen(){
if(PM)return pmNext();
var ob=(mode==='play'||mode==='duel')?objActive():null; if(ob)return genObj(ob);
if(CK)return ckNext();
if(CP)return cpNext();
if(EP)return epNext();
if(SC)return scNext();
if(MU)return muNext();
if(G1S)return g1Next();
var wq=wpGen(); if(wq&&mode==='play'&&wq.lv!=null&&skTopic(wq.lv,wq.gi))wq=null; if(wq)return wq;
var lu=curLevel(), L=LEVELS[lu], q, t=0, key, gi, rv, df=(mode==='duel'&&(S.bm==='easy'||S.bm==='hard'))?S.bm:(mode==='play'||mode==='duel')?S.diff:'normal';
do{
lu=curLevel(); if(df==='hard'&&lu0&&Math.random()<.5)lu--;
if(effTopic()!=='all'&&mode!=='check'&&!topicMap(lu)[effTopic()])lu=curLevel();
L=LEVELS[lu]; gi=pickGi(lu); rv=false;
var brg=null;
if(mode==='play'&&S.rev.length&&Math.random()<(S.rev.some(function(x){return x.k==='gap';})?.5:.35)){
var okT=topicOk(curLevel()); var cand=S.rev.filter(function(x){return x.l===curLevel()&&x.g=2){var bb=remBridge(lu,gi); if(bb){brg={l:lu,g:gi}; lu=bb.l; gi=bb.g; L=LEVELS[lu];}}}
}
q=L.gen[gi](); if(brg){q.bridge=brg; q.bridgeNote='Warm-up step';} key=q.text+'|'+(q.dots||''); t++;
}while((key===lastKey&&t<12)||(tooHard(q,lu)&&t<60)||(mode==='play'&&skTopic(lu,gi)&&t<80));
lastKey=key; q.opts=options(q); q.gi=gi; q.lv=lu; q.df=df; q.rv=rv; q.rec=false; return q;
}
/* ---------- skill tracking ---------- */
function dkey(off){var d=new Date(); d.setDate(d.getDate()-(off||0)); return d.getFullYear()+'-'+('0'+(d.getMonth()+1)).slice(-2)+'-'+('0'+d.getDate()).slice(-2);}
var unlockNote='';
function checkUnlock(lv){
var st=S.lr[lv]||'', c=(st.match(/1/g)||[]).length;
if(!S.du[lv]&&st.length>=15&&c>=13){S.du[lv]=1; unlockNote='CHALLENGE UNLOCKED for '+LEVELS[lv].name+'! Turn it on in Settings under Difficulty.'; try{updateLevels();}catch(e){}}
}
function classAct(){return !!(S.me&&S.me.cls&&typeof sess!=='undefined'&&sessLive(sess));}
function csLog(k,ok,ms,xt){
var c=S.cs=S.cs||{a:0,f:0,sk:{},h:{}}; c.a++; if(ok)c.f++;
var sk=c.sk[k]||(c.sk[k]={n:0,c:0,r:[],lim:0}); sk.n++; if(ok){sk.c++; sk.tm=(sk.tm||0)+Math.round(ms); sk.tl=(sk.tl||0)+Math.round(qLimit0||qLimit||0);}
sk.r.push([ok?1:0,ok?Math.round(ms):0]); if(sk.r.length>8)sk.r.shift(); sk.lim=qLimit0||qLimit||sk.lim; if(xt)sk.xt=(sk.xt||0)+1;
var d=dkey(0), h=c.h[d]||(c.h[d]={a:0,c:0,t:0}); h.a++; if(ok){h.c++; h.t+=Math.round(ms);}
var keep={}; for(var i=0;i<56;i++)keep[dkey(i)]=1; Object.keys(c.h).forEach(function(x){if(!keep[x])delete c.h[x];});
}
function ssLog(k,ok,ms){
var now=Date.now(), a=S.ss=S.ss||[], cur=a[a.length-1];
if(!cur||now-(cur.e||0)>30*60000){cur={s:now,e:now,a:0,c:0,tm:0,k:{}}; a.push(cur); if(a.length>15)a.shift();}
cur.e=now; cur.a++; if(ok){cur.c++; cur.tm+=Math.round(ms);}
var key=String(k).slice(0,48), e=cur.k[key]; if(!e){if(Object.keys(cur.k).length>=14)return; e=cur.k[key]=[0,0];} e[0]++; if(ok)e[1]++;
}
function recordSkill(q,ok,ms){
try{lbAns(q,ok);}catch(e){}
try{var _t=trk(); if(_t&&q&&!q.rec0){q.rec0=1; if(ok)_t.ok=(_t.ok||0)+1; sobRec(q,ok);}}catch(e){}
if(q.xt&&ok&&ms8)sk.r.shift(); sk.lim=qLimit0||qLimit||sk.lim; try{remLog(sk,q,ok);}catch(e){}
if(q.df==='normal'&&q.lv!=null&&mode==='play'){S.lr[q.lv]=((S.lr[q.lv]||'')+(ok?'1':'0')).slice(-15); checkUnlock(q.lv);}
var d=dkey(0), h=S.hist[d]||(S.hist[d]={a:0,c:0,t:0}); h.a++; if(ok){h.c++; h.t+=Math.round(ms);}
try{if(classAct()){ssLog(q.kickShow||k,ok,ms); csLog(k,ok,ms,q.xt);}}catch(e){}
var keep={}; for(var i=0;i<30;i++)keep[dkey(i)]=1; Object.keys(S.hist).forEach(function(x){if(!keep[x])delete S.hist[x];});
}
/* ===== Make Aura remediation and mastery engine =====
Diagnose each miss (slip / rushed / gap), bridge a gap down to a simpler skill,
and call a skill mastered only after repeated full marks on different days and in different formats. */
var REM_NEED=8, REM_DAYS=3, REM_VARS=2;
var PREREQ={'Take away':'Add','Multiply':'Add','Divide':'Multiply','Missing number':'Multiply','Times tables':'Multiply','Remainder':'Divide','Rounding':'Place value','Like fractions':'Half & quarter','Fractions':'Equivalent fractions','Equivalent fractions':'Multiply','Fraction of a set':'Divide','Mixed numbers':'Fractions','Improper fractions':'Mixed numbers','Decimals':'Place value','Decimal \u00d7\u00f7':'Multiply','Perimeter':'Add','Area':'Multiply','Composite figures':'Area','Area of a triangle':'Area','Percentage':'Fraction of a set','Ratio':'Divide','Average':'Divide','Fraction \u00d7':'Fraction of a set','Fraction \u00f7':'Fraction \u00d7','Discount':'Percentage','GST':'Percentage','Interest':'Percentage','Order of operations':'Multiply','Algebra':'Missing number','Equation':'Algebra','Simplify':'Algebra','Rate':'Divide','Speed':'Rate','Volume':'Area','Circle':'Area','Negative numbers':'Take away','Indices':'Multiply','Quadratic':'Simplify','Trigonometry':'Ratio','Pythagoras':'Area'};
/* ===== THIN-CELL FILL (primary) BEGIN ===== */
/* ===== THIN-CELL FILL (primary): P1 depth, story problems and spot-the-mistake =====
Each group below is ONE wrapper generator per level and kicker, so the old questions keep a fair share of every round.
Every template carries a short explanation (why). Registry THR is used by the checker scripts. */
var THR=[];
var TH_NM=[['Mei Ling','f'],['Aisha','f'],['Siti','f'],['Priya','f'],['Hui Min','f'],['Farah','f'],['Kai Xin','f'],['Arjun','m'],['Wei Jie','m'],['Daniel','m'],['Ben','m'],['Raj','m'],['Jun Hao','m'],['Zhi Hao','m']];
function thName(ex){var n,t=0; do{n=pick(TH_NM); t++;}while(ex&&n[0]===ex[0]&&t<30); return n;}
function thHe(n,cap){var h=n[1]==='f'?'she':'he'; return cap?h.charAt(0).toUpperCase()+h.slice(1):h;}
function thHis(n){return n[1]==='f'?'her':'his';}
var TH_EAT=[{s:'curry puff',p:'curry puffs'},{s:'egg tart',p:'egg tarts'},{s:'red bean bun',p:'red bean buns'},{s:'slice of kaya toast',p:'slices of kaya toast'},{s:'plate of chicken rice',p:'plates of chicken rice'},{s:'mandarin orange',p:'mandarin oranges'},{s:'rambutan',p:'rambutans'},{s:'ang ku kueh',p:'ang ku kueh'}];
var TH_PIECE=[TH_EAT[0],TH_EAT[1],TH_EAT[2],TH_EAT[3],TH_EAT[5],TH_EAT[6],TH_EAT[7]];
var TH_THG=[{s:'pencil',p:'pencils'},{s:'eraser',p:'erasers'},{s:'sticker',p:'stickers'},{s:'storybook',p:'storybooks'},{s:'marble',p:'marbles'},{s:'stamp',p:'stamps'},{s:'paper crane',p:'paper cranes'},{s:'bookmark',p:'bookmarks'}];
function thN(n,it){return n+' '+(n===1?it.s:it.p);}
/* four distinct string options inside [lo,hi]: the answer, the given wrong answers first, then near misses */
function thOpt(ans,wr,lo,hi){
var out=[String(ans)];
wr.concat([ans+1,ans-1,ans+2,ans-2,ans+3,ans-3,ans+10,ans-10,ans+4,ans-4,ans+5,ans-5]).forEach(function(w){
if(w!==ans&&w>=lo&&(hi==null||w<=hi)&&out.indexOf(String(w))<0&&out.length<4)out.push(String(w));});
return shuffle(out);
}
var TH_TB={'Story problem':[30,34,38,44],'Word problem':[0,0,0,0,55,65,75],'Spot the mistake':[40,45,50,55,60,70,80]};
function thWrap(lv,kick,fns){
var f=function(){var q=pick(fns)(); if(q.tb==null&&TH_TB[kick]&&TH_TB[kick][lv])q.tb=TH_TB[kick][lv]; return q;};
THR.push({lv:lv,kick:kick,fns:fns,w:f}); LEVELS[lv].gen.push(f); return f;
}
function thTopic(id,kicks){TOPICS.forEach(function(t){if(t.id===id)kicks.forEach(function(k){if(t.k.indexOf(k)<0)t.k.push(k);});});}
function thObj(lv,name,kicks){OBJ.push([lv,name,kicks]); OBJT.push({id:'o'+(OBJ.length-1),lv:lv,name:name,k:kicks.split('|')});}
/* ---------- pictures for P1 ---------- */
var TH_COL={red:'#d33a2c',blue:'#2f6fb0',green:'#3f8f4f',yellow:'#e0a82e'};
function thCountFig(items){
var inner='',n=items.length,big=n<=10,per=big?5:10,dx=big?40:21,r=big?14:9,x0=big?(240-(per-1)*dx)/2:(240-(per-1)*dx)/2,i;
for(i=0;i':'';
}
return svgWrap(inner);
}
function thShapeFig(nm){
var st='fill:#cfe3f1;stroke:#16222f;stroke-width:3;stroke-linejoin:round', d;
if(nm==='square')d='';
else if(nm==='rectangle')d='';
else if(nm==='triangle')d='';
else if(nm==='circle')d='';
else if(nm==='half circle')d='';
else d='';
return svgWrap(d);
}
function thShapeRow(list){ /* list of 'square','triangle','circle' */
var inner='',n=list.length,dx=Math.min(44,210/n),x0=(240-(n-1)*dx)/2,st='fill:#cfe3f1;stroke:#16222f;stroke-width:2.5;stroke-linejoin:round';
list.forEach(function(s,i){var x=x0+i*dx,y=65,h=Math.min(16,dx*.38);
inner+=s==='square'?'':s==='circle'?'':'';});
return svgWrap(inner);
}
function thPartFig(kind,parts,shaded){ /* kind 'circle' or 'rect'; parts 2 or 4; `shaded` consecutive parts from a random start are shaded */
var inner='',i,sh='fill:#e8a33d;stroke:#16222f;stroke-width:2.5',un='fill:#fff;stroke:#16222f;stroke-width:2.5',st0=rnd(0,parts-1);
function on(i){return ((i-st0)%parts+parts)%parts';}
else{var cx=120,cy=65,r=52;
for(i=0;i';}}
return svgWrap(inner);
}
function thRuler(bars,max){ /* bars: [{len,colour}] drawn above a ruler marked 0..max cm */
var u=14,x0=15,inner='',i;
inner+='';
for(i=0;i<=max;i++){inner+=''+i+'';}
bars.forEach(function(b,k){var y=bars.length===1?36:(18+k*28); inner+='';});
return svgWrap(inner);
}
/* ---------- Count ---------- */
function thCount(mx){
var cs=shuffle(['red','blue','green','yellow']),c1=cs[0],c2=cs[1],n1=rnd(1,mx-1),n2=rnd(1,mx-n1),t=rnd(0,2),items=[],i;
if(t<2){
for(i=0;i=20?4:3),n;
if(t===0){n=rnd(1,mx-1); return Q('Pattern','What number comes just after '+n+'?',n+1,{min:0,max:mx,why:'Count on 1 from '+n+'. The next number is '+(n+1)+'.'});}
if(t===1){n=rnd(2,mx); return Q('Pattern','What number comes just before '+n+'?',n-1,{min:0,max:mx,why:'Count back 1 from '+n+'. The number before is '+(n-1)+'.'});}
if(t===2){n=rnd(1,mx-2); return Q('Pattern','Which number is between '+n+' and '+(n+2)+'?',n+1,{min:0,max:mx,why:n+', '+(n+1)+', '+(n+2)+'. The number in the middle is '+(n+1)+'.'});}
if(t===3){var s=rnd(Math.min(mx,6),mx),a=[s,s-1,s-2,s-3]; return Q('Pattern','Count back: '+a[0]+', '+a[1]+', '+a[2]+', ?',a[3],{min:0,max:mx,why:'Each number is 1 less than the one before. After '+a[2]+' comes '+a[3]+'.'});}
var st=pick(mx>=100?[2,5,10]:[2,5]),top=st*rnd(4,Math.floor(mx/st)),b=[top,top-st,top-2*st,top-3*st];
return Q('Pattern','Count back in '+st+'s: '+b[0]+', '+b[1]+', '+b[2]+', ?',b[3],{min:0,max:mx,why:'Each number is '+st+' less than the one before. '+b[2]+' − '+st+' = '+b[3]+'.'});
}
/* ---------- Make 10 and Make 20 ---------- */
function thBond(n){
var K=n===10?'Make 10':'Make 20',a=rnd(1,n-1),b=n-a,t=rnd(0,3),w=[a,n,b+1,b-1,a+b+1];
if(t===0)return Q(K,'? + '+a+' = '+n,b,{min:0,max:n,options:thOpt(b,[a,n-a+1,a+1],0,n),why:a+' + '+b+' = '+n+', so the missing number is '+b+'.'});
if(t===1)return Q(K,n+' = '+a+' + ?',b,{min:0,max:n,options:thOpt(b,[a,b+2],0,n),why:n+' − '+a+' = '+b+'.'});
if(t===2)return Q(K,a+' + '+b+' = '+n+'. So '+n+' − '+a+' = ?',b,{min:0,max:n,options:thOpt(b,[a,n,n+a],0,null),why:'Adding and taking away undo each other. Since '+a+' + '+b+' = '+n+', we know '+n+' − '+a+' = '+b+'.'});
var good=a+' and '+b,bad=[], x=0; while(bad.length<3){x=rnd(1,n-1); var y=rnd(1,n-1); if(x+y!==n){var s=x+' and '+y; if(bad.indexOf(s)<0&&s!==good&&(y+' and '+x)!==good)bad.push(s);}}
return Q(K,'Which two numbers make '+n+'?',good,{options:shuffle([good].concat(bad)),why:a+' + '+b+' = '+n+'. Check each pair by adding.'});
}
/* ---------- Compare ---------- */
function thCmp(lo,hi){
var t=rnd(0,2),a=rnd(lo,hi),b=rnd(lo,hi);
if(t===0){while(b===a)b=rnd(lo,hi); var big=Math.random()<.5,ans=big?Math.max(a,b):Math.min(a,b); return Q('Compare','Which number is '+(big?'greater':'smaller')+', '+a+' or '+b+'?',ans,{options:[String(a),String(b)],why:(big?Math.max(a,b)+' is greater than '+Math.min(a,b):Math.min(a,b)+' is smaller than '+Math.max(a,b))+'.'});}
if(t===1){if(Math.random()<.2)b=a; var sg=a>b?'>':a for greater than, < for smaller than and = for the same as.\n'+a+' □ '+b,sg,{options:['>','<','='],why:a===b?a+' and '+b+' are the same, so we use =.':a+(a>b?' is greater than ':' is smaller than ')+b+', so we use '+sg+'.'});}
var s=shuffle([rnd(lo,hi),rnd(lo,hi),rnd(lo,hi)]);
while(s[0]===s[1]||s[1]===s[2]||s[0]===s[2])s=shuffle([rnd(lo,hi),rnd(lo,hi),rnd(lo,hi)]);
var up=Math.random()<.5,sorted=s.slice().sort(function(x,y){return up?x-y:y-x;}),right=sorted.join(', '),opts=[right];
[[1,0,2],[0,2,1],[2,1,0],[1,2,0],[2,0,1]].forEach(function(p){var o=[sorted[p[0]],sorted[p[1]],sorted[p[2]]].join(', '); if(opts.indexOf(o)<0&&opts.length<4)opts.push(o);});
return Q('Compare','Which list is in order, from '+(up?'smallest to biggest':'biggest to smallest')+'?',right,{options:shuffle(opts),why:'Start with the '+(up?'smallest':'biggest')+' number, '+sorted[0]+'. Then '+sorted[1]+'. Then '+sorted[2]+'.'});
}
/* ---------- Place value ---------- */
/* base-ten picture: each ten is a rod of 10 cubes, each one is a single cube */
function tensFig(T,O){
var u=11, g=3, w=0, h=10*u+4, x=4, svg='', i, j;
for(i=0;i'; x+=u+g; }
if(T&&O)x+=6;
for(i=0;i'; }
w=x+(O?Math.ceil(O/5)*(u+g):0)+4;
return '';
}
function tensFigAlt(T,O){return (T?T+' ten rod'+(T===1?'':'s'):'')+(T&&O?' and ':'')+(O?O+' one cube'+(O===1?'':'s'):'');}
function thPlace(lo,hi){
var n=rnd(lo,hi),t=rnd(0,3),T=Math.floor(n/10),O=n%10;
if(t===0){var cand=[],z; for(z=Math.max(lo,12);z<=hi;z++){if(Math.floor(z/10)!==z%10&&z%10!==0&&z>=10)cand.push(z);} n=pick(cand); T=Math.floor(n/10); O=n%10; var tens=rnd(0,1);
return Q('Place value','What is the value of the digit '+(tens?T:O)+' in '+n+'?',tens?T*10:O,{options:thOpt(tens?T*10:O,[tens?T:O*10,tens?O:T,tens?O*10:T*10],0,null),min:0,why:'In '+n+', '+T+' is in the tens place and '+O+' is in the ones place. '+(tens?T+' tens is '+T*10+'.':O+' ones is '+O+'.')});}
if(t===1){return Q('Place value',n+' = '+T+' '+(T===1?'ten':'tens')+' and ? ones',O,{fig:(n<100&&T<=9)?tensFig(T,O):undefined,figAlt:tensFigAlt(T,O),min:0,max:9,why:n+' is '+T+' '+(T===1?'ten':'tens')+' and '+O+' '+(O===1?'one':'ones')+'.'});}
if(t===2){var rev=O*10+T,wr=[rev,T+O,T*10,n+10]; if(rev===n)wr[0]=n+1;
return Q('Place value','Which number is '+T+' '+(T===1?'ten':'tens')+' and '+O+' '+(O===1?'one':'ones')+'?',n,{fig:(n<100&&T<=9)?tensFig(T,O):undefined,figAlt:tensFigAlt(T,O),options:thOpt(n,wr,1,null),why:T+' tens is '+T*10+'. '+T*10+' and '+O+' make '+n+'.'});}
return Q('Place value',T*10+' + '+O+' = ?',n,{min:1,why:T*10+' is '+T+' tens and '+O+' is '+O+' ones. Together they make '+n+'.'});
}
/* ---------- Add and Take away ---------- */
function thAdd(mx){
var t=rnd(0,mx>=100?4:3),a,b,c;
if(t===0){if(mx>=100){a=rnd(2,6)*10;b=rnd(1,Math.floor((100-a)/10))*10; return Q('Add',a+' + '+b+' = ?',a+b,{max:100,why:a/10+' tens + '+b/10+' tens = '+(a+b)/10+' tens. That is '+(a+b)+'.'});}
a=rnd(1,Math.floor(mx/3)); b=rnd(1,Math.floor(mx/3)); c=rnd(1,Math.floor(mx/3)); return Q('Add',a+' + '+b+' + '+c+' = ?',a+b+c,{max:mx,why:'Add the first two: '+a+' + '+b+' = '+(a+b)+'. Then add '+c+': '+(a+b)+' + '+c+' = '+(a+b+c)+'.'});}
if(t===1){a=mx>=100?rnd(12,70):rnd(1,mx-1); b=mx>=100?rnd(2,Math.min(9,100-a)):rnd(1,mx-a); var n=a+b; return Q('Add','? + '+b+' = '+n,a,{min:0,max:mx,options:thOpt(a,[n+b,b,a+1],0,mx),why:n+' − '+b+' = '+a+', so '+a+' + '+b+' = '+n+'.'});}
if(t===2){a=rnd(1,mx>=100?40:Math.floor(mx/2)); b=rnd(1,mx>=100?40:Math.floor(mx/2)); return Q('Add',a+' + '+b+' = '+b+' + ?',a,{min:0,max:mx,options:thOpt(a,[b,a+b,a+1],0,null),why:'We can add in any order. '+a+' + '+b+' = '+b+' + '+a+'.'});}
if(t===3){a=rnd(1,mx>=100?30:Math.floor(mx/2)); b=rnd(1,mx>=100?30:Math.floor(mx/2)); var c2=a+b,sgn=['+','−'],good=a+' + '+b+' = '+c2,bad=[a+' + '+b+' = '+(c2+1),a+' + '+b+' = '+(c2-1),a+' + '+b+' = '+(c2+10)];
return Q('Add','Which number sentence is correct?',good,{options:shuffle([good].concat(bad)),why:'Add '+a+' and '+b+' to get '+c2+'. The other sentences give a different total.'});}
a=rnd(1,6)*10+rnd(1,9); b=rnd(1,Math.floor((99-a)/10))*10; return Q('Add',a+' + '+b+' = ?',a+b,{max:100,why:'Add the tens: '+Math.floor(a/10)*10+' + '+b+' = '+(Math.floor(a/10)*10+b)+'. The ones stay '+a%10+'. The answer is '+(a+b)+'.'});
}
function thSub(mx){
var t=rnd(0,mx>=100?3:2),a,b,c;
if(t===0){a=rnd(Math.min(4,mx-1),mx>=100?90:mx); b=rnd(1,Math.max(1,Math.floor(a/3))); c=rnd(1,Math.max(1,Math.floor((a-b)/2))); return Q('Take away',a+' − '+b+' − '+c+' = ?',a-b-c,{min:0,max:mx,why:'Take away '+b+' first: '+a+' − '+b+' = '+(a-b)+'. Then take away '+c+': '+(a-b)+' − '+c+' = '+(a-b-c)+'.'});}
if(t===1){a=rnd(3,mx>=100?90:mx); b=rnd(1,a-1); return Q('Take away',a+' − ? = '+(a-b),b,{min:0,max:mx,options:thOpt(b,[a-b,a,a+b],0,mx),why:a+' − '+b+' = '+(a-b)+', so the missing number is '+b+'.'});}
if(t===2){a=rnd(3,mx>=100?60:mx); b=rnd(1,a-1); var sg=['+','−','='],ans='−'; return Q('Take away',a+' □ '+b+' = '+(a-b)+'\nWhich sign goes in the box?','−',{options:['+','−','='],why:'The answer '+(a-b)+' is smaller than '+a+', so we take away. We use −.'});}
a=rnd(2,9)*10+rnd(0,9); b=rnd(1,Math.floor(a/10)-1)*10; return Q('Take away',a+' − '+b+' = ?',a-b,{max:100,why:'Take away '+b/10+' tens from '+Math.floor(a/10)+' tens. That leaves '+(Math.floor(a/10)-b/10)+' tens. The ones stay '+a%10+'. The answer is '+(a-b)+'.'});
}
/* ---------- Shapes ---------- */
var TH_SH=['square','rectangle','triangle','circle','half circle','quarter circle'];
function thShape(){
var t=rnd(0,5),nm,o;
if(t===0){nm=pick(TH_SH); var wr=TH_SH.filter(function(x){return x!==nm;}); return Q('Shapes','What is the name of this shape?',nm,{fig:thShapeFig(nm),figAlt:'A shape drawn on the screen.',options:shuffle([nm].concat(shuffle(wr).slice(0,3))),why:{square:'A square has 4 equal sides and 4 square corners.',rectangle:'A rectangle has 4 sides. Two are long and two are short.',triangle:'A triangle has 3 sides and 3 corners.',circle:'A circle is round and has no corners.','half circle':'A half circle is half of a circle. It has one straight side and one curved side.','quarter circle':'A quarter circle is one quarter of a circle. It has two straight sides and one curved side.'}[nm]});}
if(t===1){var S3=[['square',4],['rectangle',4],['triangle',3]],s=pick(S3); return Q('Shapes','How many sides does a '+s[0]+' have?',s[1],{options:thOpt(s[1],[3,4,5,6],2,7),why:'A '+s[0]+' has '+s[1]+' sides.'});}
if(t===2){var S4=[['triangle',3],['square',4],['rectangle',4]],s2=pick(S4); return Q('Shapes','How many corners does a '+s2[0]+' have?',s2[1],{options:thOpt(s2[1],[3,4,5,6],2,7),why:'A '+s2[0]+' has '+s2[1]+' corners.'});}
if(t===3){var Z=[['Which shape has no corners?','circle',['square','triangle','rectangle']],['Which shape has 3 sides?','triangle',['square','circle','rectangle']],['Which shape has 4 sides that are all the same length?','square',['rectangle','triangle','circle']],['Which shape has 4 sides, with 2 long sides and 2 short sides?','rectangle',['square','triangle','circle']]],z=pick(Z);
return Q('Shapes',z[0],z[1],{options:shuffle([z[1]].concat(z[2])),why:'The '+z[1]+' is the shape that fits.'});}
if(t===4){var O=[['a 50-cent coin','circle'],['a round clock face','circle'],['the cover of an exercise book','rectangle'],['an MRT card','rectangle'],['a square floor tile','square'],['a slice of bread cut from corner to corner','triangle']],ob=pick(O),wr2=TH_SH.slice(0,4).filter(function(x){return x!==ob[1];});
return Q('Shapes','Which shape is '+ob[0]+'?',ob[1],{options:shuffle([ob[1]].concat(shuffle(wr2).slice(0,3))),why:ob[0].charAt(0).toUpperCase()+ob[0].slice(1)+' has the shape of a '+ob[1]+'.'});}
var pool=['square','triangle','circle'],target=pick(pool),k=rnd(3,8),row=[],tc=rnd(1,k-1),i;
for(i=0;imx){k1=1;k2=1;tot=c1[0]+c2[0];}
return Q('Coins',n[0]+' has '+['','one','two','three'][k1]+' '+c1[1]+' coin'+(k1>1?'s':'')+' and '+['','one','two','three'][k2]+' '+c2[1]+' coin'+(k2>1?'s':'')+'. How many cents does '+thHe(n)+' have?',tot,{min:5,max:100,options:thOpt(tot,[tot+5,tot-5,tot+10,tot-10],5,100),why:k1+' × '+c1[0]+' = '+c1[0]*k1+' and '+k2+' × '+c2[0]+' = '+c2[0]*k2+'. '+c1[0]*k1+' + '+c2[0]*k2+' = '+tot+' cents.'});}
if(t===1){var v=pick([10,20,50]),k=pick([2,3,4,5]); while(v*k>100)k--; return Q('Coins','How many '+v+'-cent coins make '+v*k+' cents?',k,{min:1,max:10,options:thOpt(k,[k+1,k-1,k+2],1,10),why:'Count in '+v+'s: '+Array.apply(null,Array(k)).map(function(_,i){return v*(i+1);}).join(', ')+'. That takes '+k+' coins.'});}
if(t===2){var a=pick([20,30,40,50,60,70]),good,opts=[]; var sets={20:['10-cent coin + 10-cent coin','5-cent coin + 5-cent coin'],30:['20-cent coin + 10-cent coin','20-cent coin + 5-cent coin'],40:['20-cent coin + 20-cent coin','20-cent coin + 10-cent coin'],50:['50-cent coin','20-cent coin + 20-cent coin'],60:['50-cent coin + 10-cent coin','50-cent coin + 5-cent coin'],70:['50-cent coin + 20-cent coin','50-cent coin + 10-cent coin']};
good=sets[a][0]; var wr3=sets[a][1]; var pool=['10-cent coin + 5-cent coin','20-cent coin + 5-cent coin','50-cent coin + 5-cent coin','10-cent coin + 10-cent coin','20-cent coin + 10-cent coin','50-cent coin + 20-cent coin','50-cent coin + 10-cent coin','20-cent coin + 20-cent coin','5-cent coin + 5-cent coin'];
function val(s){return s.split(' + ').reduce(function(x,y){return x+parseInt(y,10);},0);}
var bad=pool.filter(function(p){return val(p)!==a;}); opts=[good].concat(shuffle(bad).slice(0,3));
return Q('Coins','Which coins make '+a+' cents?',good,{options:shuffle(opts),why:good.split(' + ').map(function(s){return parseInt(s,10);}).join(' + ')+' = '+a+' cents.'});}
var m1=pick([20,30,40,50,60]),m2=pick([20,30,40,50,60]); while(m1===m2)m2=pick([20,30,40,50,60]); var n2=thName(n),big=m1>m2?n[0]:n2[0];
return Q('Coins',n[0]+' has '+m1+' cents. '+n2[0]+' has '+m2+' cents. Who has more money?',big,{options:[n[0],n2[0]],why:Math.max(m1,m2)+' cents is more than '+Math.min(m1,m2)+' cents.'});
}
/* ---------- Equal groups and sharing (multiplication and division ideas, MOE P1) ---------- */
function thGroups(mx){
var t=rnd(0,3),g,e,it=pick(TH_EAT),n=thName();
if(t===0){e=pick([2,3,4,5]); g=rnd(2,Math.floor(mx/e)); if(g>5)g=5; it=pick(TH_PIECE); return Q('Equal groups',g+' plates. '+thN(e,it)+' on each plate. How many '+it.p+' altogether?',g*e,{min:1,max:mx,options:thOpt(g*e,[g+e,g*e+e,g*e-e],1,null),why:g+' groups of '+e+': '+Array.apply(null,Array(g)).map(function(){return e;}).join(' + ')+' = '+g*e+'.'});}
if(t===1){e=pick([2,3,4,5]); g=rnd(2,Math.min(5,Math.floor(mx/e))); return Q('Equal groups',Array.apply(null,Array(g)).map(function(){return e;}).join(' + ')+' = ?',g*e,{min:2,max:mx,options:thOpt(g*e,[g+e,g*e+e,g*e-1],2,null),why:'Add '+e+' again and again, '+g+' times. The total is '+g*e+'.'});}
if(t===2){if(mx<30){e=pick([2,5,10]); g=rnd(2,Math.floor(mx/e));} else {e=pick([2,3,4,5,10]); g=rnd(2,Math.min(8,Math.floor(40/e)));} return Q('Equal groups',g+' × '+e+' = ?',g*e,{min:2,max:40,options:thOpt(g*e,[g+e,g*e+e,g*e-e],2,null),why:g+' × '+e+' means '+g+' groups of '+e+'. '+Array.apply(null,Array(g)).map(function(){return e;}).join(' + ')+' = '+g*e+'.'});}
e=pick([2,5,10]); g=rnd(2,Math.floor(mx/e)); return Q('Equal groups','How many groups of '+e+' make '+g*e+'?',g,{min:1,max:10,options:thOpt(g,[g+1,g-1,e],1,20),why:'Count in '+e+'s up to '+g*e+'. You say '+g+' numbers, so there are '+g+' groups.'});
}
function thShare(mx){
var t=rnd(0,2),k=pick([2,3,4,5]),e=rnd(2,Math.floor(mx/k)),it=pick(TH_EAT),n=thName();
if(t===0)return Q('Sharing',thN(k*e,it)+' are shared equally among '+k+' children. How many does each child get?',e,{min:1,max:20,options:thOpt(e,[k,k*e,e+1],1,null),why:'Share equally into '+k+' groups. Each group gets '+e+', because '+k+' × '+e+' = '+k*e+'.'});
if(t===1)return Q('Sharing',k*e+' ÷ '+k+' = ?',e,{min:1,max:20,options:thOpt(e,[k,e+1,e-1],1,null),why:k+' × '+e+' = '+k*e+', so '+k*e+' ÷ '+k+' = '+e+'.'});
var tk=pick(TH_THG); return Q('Sharing',n[0]+' has '+thN(k*e,tk)+'. '+thHe(n,true)+' puts '+e+' in each box. How many boxes does '+thHe(n)+' need?',k,{min:1,max:10,options:thOpt(k,[e,k+1,k*e],1,null),why:k*e+' ÷ '+e+' = '+k+'. '+n[0]+' needs '+k+' boxes.'});
}
/* ---------- Story problems (P1 to P3): short sentences, digits for numbers ---------- */
function thPair(mx){ /* a and b with a + b <= mx, both at least 1; tens when mx is 100 */
if(mx>=100){var a=rnd(11,69),b=rnd(5,Math.min(40,99-a)); return [a,b];}
var a2=rnd(1,mx-1),b2=rnd(1,mx-a2); return [a2,b2];
}
function thStoryOpts(ans,wr,mx){return thOpt(ans,wr,0,mx>=100?100:Math.max(mx,ans));}
var TH_STORY={
join:function(mx){var p=thPair(mx),a=p[0],b=p[1],n=thName(),eat=rnd(0,1),it=pick(eat?TH_EAT:TH_THG);
return Q('Story problem',n[0]+' has '+thN(a,it)+'. '+thHe(n,true)+(eat?' buys ':' gets ')+b+' more. How many '+it.p+' does '+n[0]+' have now?',a+b,{min:0,max:mx,options:thStoryOpts(a+b,[Math.abs(a-b),a+b+1,a+b-1],mx),why:'Join the two groups. '+a+' + '+b+' = '+(a+b)+'.'});},
together:function(mx){var p=thPair(mx),a=p[0],b=p[1],n=thName(),m=thName(n),it=pick(TH_THG);
return Q('Story problem',n[0]+' has '+thN(a,it)+'. '+m[0]+' has '+thN(b,it)+'. How many '+it.p+' do they have altogether?',a+b,{min:0,max:mx,options:thStoryOpts(a+b,[Math.abs(a-b),a+b+1,a+b-1],mx),why:'Put the two groups together. '+a+' + '+b+' = '+(a+b)+'.'});},
takeaway:function(mx){var a=mx>=100?rnd(21,95):rnd(3,mx),b=rnd(1,a-1),n=thName(),eat=rnd(0,1),it=pick(eat?TH_EAT:TH_THG),m=thName(n);
return Q('Story problem',n[0]+' has '+thN(a,it)+'. '+thHe(n,true)+(eat?' eats ':' gives ')+b+(eat?'':' to '+m[0])+'. How many '+it.p+' does '+n[0]+' have left?',a-b,{min:0,max:mx,options:thStoryOpts(a-b,[a+b,a-b+1,a-b-1],mx),why:'Take away the '+b+'. '+a+' − '+b+' = '+(a-b)+'.'});},
moreThan:function(mx){var a=mx>=100?rnd(21,95):rnd(3,mx),b=rnd(1,a-1),n=thName(),m=thName(n),it=pick(TH_THG);
return Q('Story problem',n[0]+' has '+thN(a,it)+'. '+m[0]+' has '+thN(b,it)+'. How many more '+it.p+' does '+n[0]+' have than '+m[0]+'?',a-b,{min:1,max:mx,options:thStoryOpts(a-b,[a+b,a-b+1,a-b-1],mx),why:'Find the difference. '+a+' − '+b+' = '+(a-b)+'.'});},
fewer:function(mx){var a=mx>=100?rnd(21,95):rnd(3,mx),b=rnd(1,a-1),n=thName(),m=thName(n),it=pick(TH_THG);
return Q('Story problem',n[0]+' has '+thN(a,it)+'. '+m[0]+' has '+thN(b,it)+'. How many fewer '+it.p+' does '+m[0]+' have than '+n[0]+'?',a-b,{min:1,max:mx,options:thStoryOpts(a-b,[a+b,a-b+1,a-b-1],mx),why:'Find the difference. '+a+' − '+b+' = '+(a-b)+'.'});},
partWhole:function(mx){var tot=mx>=100?rnd(20,100):rnd(4,mx),a=rnd(1,tot-1),it=pick([['marbles','red','blue'],['balloons','red','yellow'],['flags','red','white'],['beads','green','blue']]),
where=pick(['in a bag','in a box']);
return Q('Story problem','There are '+tot+' '+it[0]+' '+where+'. '+a+' are '+it[1]+'. The rest are '+it[2]+'. How many are '+it[2]+'?',tot-a,{min:0,max:mx,options:thStoryOpts(tot-a,[a,tot,tot-a+1],mx),why:'Take the '+it[1]+' ones away from all of them. '+tot+' − '+a+' = '+(tot-a)+'.'});},
changeUnknown:function(mx){var tot=mx>=100?rnd(30,100):rnd(5,mx),a=rnd(1,tot-1),n=thName(),it=pick(TH_THG);
return Q('Story problem',n[0]+' has '+thN(a,it)+'. '+thHe(n,true)+' gets some more. Now '+thHe(n)+' has '+tot+'. How many did '+n[0]+' get?',tot-a,{min:1,max:mx,options:thStoryOpts(tot-a,[tot+a,a,tot-a+1],mx),why:'Find how many more are needed to go from '+a+' to '+tot+'. '+tot+' − '+a+' = '+(tot-a)+'.'});},
needMore:function(mx){var tot=mx>=100?rnd(30,100):rnd(5,mx),a=rnd(1,tot-1),n=thName(),it=pick(TH_THG);
return Q('Story problem',n[0]+' wants '+thN(tot,it)+'. '+thHe(n,true)+' has '+a+'. How many more does '+n[0]+' need?',tot-a,{min:1,max:mx,options:thStoryOpts(tot-a,[tot+a,a,tot-a+1],mx),why:tot+' − '+a+' = '+(tot-a)+'. '+n[0]+' needs '+(tot-a)+' more.'});},
three:function(mx){var a=rnd(10,30),b=rnd(10,30),c=rnd(10,30),pl=pick(['hawker centre','school canteen','library']),
tb=pick([['table A','table B','table C'],['Row 1','Row 2','Row 3']]);
if(a+b+c>100){c=100-a-b; if(c<5){a=10;b=10;c=10;}}
var nm=tb[0].charAt(0)==='R'?'people sit in':'people sit at';
return Q('Story problem','At the '+pl+', '+a+' '+nm+' '+tb[0]+'. '+b+' '+nm+' '+tb[1]+'. '+c+' '+nm+' '+tb[2]+'. How many people is that in all?',a+b+c,{min:3,max:100,options:thStoryOpts(a+b+c,[a+b+c+10,a+b+c-10,a+b],100),why:'Add the three groups. '+a+' + '+b+' = '+(a+b)+'. '+(a+b)+' + '+c+' = '+(a+b+c)+'.'});},
groups:function(mx){var e=pick([2,3,4,5,10]),g=rnd(2,Math.max(2,Math.min(5,Math.floor(Math.min(mx,40)/e)))),n=thName(),it=pick(TH_PIECE);
return Q('Story problem',n[0]+' puts '+thN(e,it)+' on each plate. '+thHe(n,true)+' has '+g+' plates. How many '+it.p+' is that in all?',g*e,{min:1,max:40,options:thOpt(g*e,[g+e,g*e+e,g*e-e],1,null),why:g+' groups of '+e+' is '+g+' × '+e+' = '+g*e+'.'});},
share:function(mx){var k=pick([2,3,4,5]),e=rnd(2,Math.floor(Math.min(mx,20)/k)),n=thName(),it=pick(TH_PIECE);
return Q('Story problem',n[0]+' has '+thN(k*e,it)+'. '+thHe(n,true)+' shares them equally among '+k+' friends. How many does each friend get?',e,{min:1,max:20,options:thOpt(e,[k,k*e,e+1],1,null),why:k*e+' ÷ '+k+' = '+e+'. Each friend gets '+e+'.'});}
};
function thStoryGen(mx,kinds){return kinds.map(function(k){return function(){return TH_STORY[k](mx);};});}
/* ---------- Spot the mistake: a worked solution with exactly one wrong step ----------
Later steps follow from the wrong step, so only ONE step is wrong on its own. Learners choose the wrong step or the correct answer.
o = {head, steps[], bad (0-based), right, wrong, distract[], why (names the mistake), fix, mode} */
function thSpare(r){ /* spare wrong choices so string options never run short */
var m=/^\$(\d+)$/.exec(String(r)); if(!m)return []; var v=+m[1],a=[]; a.push('$'+(v+10)); if(v>10)a.push('$'+(v-10)); a.push('$'+(v+5)); a.push('$'+v*2); return a;
}
function thSpot(o){
var lines=[o.head]; o.steps.forEach(function(s,i){lines.push('Step '+(i+1)+': '+s);});
var mode=o.mode||(Math.random()<.5?'step':'ans'), body=lines.join('\n'), num=typeof o.right==='number';
var why='The mistake is in Step '+(o.bad+1)+'. '+o.why+' The correct answer is '+o.right+'.';
if(mode==='step'){
var os=o.steps.map(function(_,i){return 'Step '+(i+1);});
return Q('Spot the mistake',body+'\nOne step is wrong. Which step?','Step '+(o.bad+1),{options:os,keep:true,why:why,sp:{bad:o.bad,mode:'step'}});
}
var opts=num?thOpt(o.right,[o.wrong].concat(o.distract||[]),Math.min(0,o.right),null):strOpts(o.right,[o.wrong].concat(o.distract||[]).concat(thSpare(o.right)));
return Q('Spot the mistake',body+'\nOne step is wrong. What is the correct answer?',o.right,{options:opts,why:why,sp:{bad:o.bad,mode:'ans'}});
}
function thList(from,len){var a=[],i; for(i=0;imx||a===b)a=rnd(2,5); var n=thName();
return thSpot({head:n[0]+' works out '+a+' × '+b+'.',steps:[a+' × '+b+' means '+a+' groups of '+b+'.','Add the two numbers: '+a+' + '+b+' = '+(a+b)+'.','So '+a+' × '+b+' = '+(a+b)+'.'],bad:1,right:a*b,wrong:a+b,distract:[a*b+b,a*b-1],
why:n[0]+' added the two numbers once. '+a+' groups of '+b+' means adding '+b+' a total of '+a+' times: '+Array.apply(null,Array(a)).map(function(){return b;}).join(' + ')+' = '+a*b+'.'});},
compareOnes:function(mx){var ta=rnd(1,8),tb=rnd(ta+1,9),oa=rnd(5,9),ob=rnd(0,oa-1),a=ta*10+oa,b=tb*10+ob,n=thName();
return thSpot({head:n[0]+' wants to know which is greater, '+a+' or '+b+'.',steps:['Compare the ones digits first: '+oa+' and '+ob+'.',oa+' is greater than '+ob+'.','So '+a+' is greater than '+b+'.'],bad:0,right:b,wrong:a,distract:[ta*10+ob,tb*10+oa],
why:'We must compare the tens first. '+tb+' tens is more than '+ta+' tens, so '+b+' is greater than '+a+'. The ones only matter when the tens are the same.'});},
subSmaller:function(mx){var tb=rnd(1,5),ta=rnd(tb+1,9),oa=rnd(0,6),ob=rnd(oa+1,9),a=ta*10+oa,b=tb*10+ob,w=10*(ta-tb)+(ob-oa),n=thName();
return thSpot({head:n[0]+' works out '+a+' − '+b+'.',steps:['Ones: '+oa+' < '+ob+', so '+ob+' − '+oa+' = '+(ob-oa)+'.','Tens: '+ta+' − '+tb+' = '+(ta-tb)+'.','The answer is '+w+'.'],bad:0,right:a-b,wrong:w,distract:[a-b+10,a-b-1],
why:n[0]+' took the smaller digit from the bigger digit. '+oa+' ones cannot lose '+ob+' ones, so regroup 1 ten as 10 ones: '+(10+oa)+' − '+ob+' = '+(10+oa-ob)+', and the tens are '+(ta-1)+' − '+tb+' = '+(ta-1-tb)+'.'});},
carry:function(mx){var oa=rnd(2,9),ob=rnd(10-oa,9),ta=rnd(1,4),tb=rnd(1,4),a=ta*10+oa,b=tb*10+ob,n=thName();
return thSpot({head:n[0]+' works out '+a+' + '+b+' by adding the ones first.',steps:['Add the ones: '+oa+' + '+ob+' = '+(oa+ob)+'.','Write '+(oa+ob-10)+' in the ones place. Carry 1 ten.','Add the tens: '+ta+' + '+tb+' = '+(ta+tb)+'.','The answer is '+((ta+tb)*10+oa+ob-10)+'.'],bad:2,right:a+b,wrong:(ta+tb)*10+oa+ob-10,distract:[a+b+10,a+b-1],
why:n[0]+' forgot the carried ten. Step 3 should be '+ta+' + '+tb+' + 1 = '+(ta+tb+1)+' tens, so the answer is '+(a+b)+'.'});},
faceValue:function(mx){var t=rnd(2,9),o=rnd(1,9); while(o===t)o=rnd(1,9); var n=thName();
return thSpot({head:n[0]+' finds the value of the digit '+t+' in '+(t*10+o)+'.',steps:['The digit '+t+' is in the tens place.','The digit is '+t+', so its value is '+t+'.','The value of the digit is '+t+'.'],bad:1,right:t*10,wrong:t,distract:[o,t*10+o],
why:n[0]+' gave the digit itself. A digit in the tens place is worth that many tens: '+t+' tens is '+t*10+'.'});}
};
function thSpotGen(mx,kinds){return kinds.map(function(k){return function(){return TH_SP1[k](mx);};});}
/* ---------- register the P1 groups (one wrapper per level and kicker) ---------- */
function thT(tid,f){f.tid=tid; return f;}
(function(){
function reg(lv,kick,list){var fns=list.map(function(p){var g=p[1]; return thT(p[0]+'@'+lv,function(){return g();});}); thWrap(lv,kick,fns);}
function st(lv,mx,kinds){return kinds.map(function(k){return ['story.'+k,function(){return TH_STORY[k](mx);}];});}
function sp(lv,mx,kinds){return kinds.map(function(k){return ['spot.'+k,function(){return TH_SP1[k](mx);}];});}
/* level 0: P1 to 10 */
reg(0,'Count',[['count',function(){return thCount(10);}]]);
reg(0,'Pattern',[['seq',function(){return thSeq(10);}]]);
reg(0,'Make 10',[['bond',function(){return thBond(10);}]]);
reg(0,'Compare',[['cmp',function(){return thCmp(1,10);}]]);
reg(0,'Add',[['add',function(){return thAdd(10);}]]);
reg(0,'Take away',[['sub',function(){return thSub(10);}]]);
reg(0,'Shapes',[['shape',thShape]]);
reg(0,'Half & quarter',[['half',thHalf]]);
reg(0,'Sharing',[['share',function(){return thShare(10);}]]);
reg(0,'Story problem',st(0,10,['join','together','takeaway','moreThan','partWhole']));
reg(0,'Spot the mistake',sp(0,10,['counton','countback','ordinal','three']));
/* level 1: P1 to 20 */
reg(1,'Count',[['count',function(){return thCount(20);}]]);
reg(1,'Pattern',[['seq',function(){return thSeq(20);}]]);
reg(1,'Make 20',[['bond',function(){return thBond(20);}]]);
reg(1,'Compare',[['cmp',function(){return thCmp(1,20);}]]);
reg(1,'Place value',[['place',function(){return thPlace(11,20);}]]);
reg(1,'Add',[['add',function(){return thAdd(20);}]]);
reg(1,'Take away',[['sub',function(){return thSub(20);}]]);
reg(1,'Telling time',[['time',thTime]]);
reg(1,'am/pm',[['ampm',thAmPm]]);
reg(1,'Coins',[['coins',function(){return thCoins(60);}]]);
reg(1,'Length',[['len',thLen]]);
reg(1,'Shapes',[['shape',thShape]]);
reg(1,'Half & quarter',[['half',thHalf]]);
reg(1,'Equal groups',[['groups',function(){return thGroups(20);}]]);
reg(1,'Sharing',[['share',function(){return thShare(20);}]]);
reg(1,'Story problem',st(1,20,['join','together','takeaway','moreThan','fewer','partWhole','changeUnknown','needMore','groups','share']));
reg(1,'Spot the mistake',sp(1,20,['counton','countback','makeTen','teens','ordinal','three','groups']));
/* level 2: P1-2 to 100 */
reg(2,'Pattern',[['seq',function(){return thSeq(100);}]]);
reg(2,'Compare',[['cmp',function(){return thCmp(10,99);}]]);
reg(2,'Place value',[['place',function(){return thPlace(11,99);}]]);
reg(2,'Add',[['add',function(){return thAdd(100);}]]);
reg(2,'Take away',[['sub',function(){return thSub(100);}]]);
reg(2,'Telling time',[['time',thTime]]);
reg(2,'am/pm',[['ampm',thAmPm]]);
reg(2,'Coins',[['coins',function(){return thCoins(100);}]]);
reg(2,'Length',[['len',thLen]]);
reg(2,'Shapes',[['shape',thShape]]);
reg(2,'Half & quarter',[['half',thHalf]]);
reg(2,'Equal groups',[['groups',function(){return thGroups(40);}]]);
reg(2,'Sharing',[['share',function(){return thShare(20);}]]);
reg(2,'Story problem',st(2,100,['join','together','takeaway','moreThan','fewer','partWhole','changeUnknown','needMore','three','groups','share']));
reg(2,'Spot the mistake',sp(2,100,['compareOnes','groups','subSmaller','carry','faceValue']));
})();
/* ---------- topics, lesson objectives and prerequisites for the new kickers ---------- */
(function(){
thTopic('num',['Story problem']);
thTopic('mul',['Equal groups']);
TOPICS.push({id:'spot',name:'Spot the mistake',k:['Spot the mistake']});
PREREQ['Story problem']='Add'; PREREQ['Equal groups']='Add';
thObj(0,'Story problems within 10','Story problem');
thObj(0,'Spot the mistake: counting on and back, adding','Spot the mistake');
thObj(0,'Sharing equally','Sharing');
thObj(1,'Story problems within 20','Story problem');
thObj(1,'Equal groups and sharing (the idea of × and ÷)','Equal groups|Sharing');
thObj(1,'Spot the mistake: adding, counting and place value','Spot the mistake');
thObj(2,'Story problems within 100','Story problem');
thObj(2,'Equal groups and sharing (the idea of × and ÷)','Equal groups|Sharing');
thObj(2,'Spot the mistake: adding, taking away and place value','Spot the mistake');
})();
/* ---------- Story and word problems for P2 to P6 ---------- */
var TH_FOOD=[['chicken rice','plates of chicken rice','plate'],['laksa','bowls of laksa','bowl'],['kaya toast','sets of kaya toast','set'],['curry puffs','curry puffs','curry puff'],['milo','cups of milo','cup'],['nasi lemak','packets of nasi lemak','packet']];
var TH_PLACE=['the school canteen','the hawker centre','a bakery in Tampines','a food court near the MRT station'];
function thAmt(c){return '$'+(c/100).toFixed(2);}
function thLink(a,b){return a+b;}
var TH_ST3={
add3:function(){var a=rnd(120,480),b=rnd(120,480),f=pick(TH_FOOD),pl=pick(TH_PLACE);
return Q('Story problem','On Monday, '+pl+' sold '+a+' '+f[1]+'. On Tuesday, it sold '+b+' '+f[1]+'. How many '+f[1]+' did it sell in all?',a+b,{min:0,options:thOpt(a+b,[a+b+10,a+b-10,a+b+100,a+b-100],0,null),why:'Add the two days. '+a+' + '+b+' = '+(a+b)+'.'});},
sub3:function(){var a=rnd(300,900),b=rnd(110,a-60),n=thName();
return Q('Story problem','A school library has '+a+' books. '+b+' books are borrowed. How many books are left in the library?',a-b,{min:0,options:thOpt(a-b,[a+b,a-b+10,a-b-10,a-b+100],0,null),why:'Take away the books that are borrowed. '+a+' − '+b+' = '+(a-b)+'.'});},
mult:function(){var e=pick([2,3,4,5,10]),g=rnd(3,9),n=thName(),f=pick(['mandarin oranges','red bean buns','egg tarts','curry puffs']);
return Q('Story problem',n[0]+' buys '+g+' bags of '+f+'. There are '+e+' '+f+' in each bag. How many '+f+' is that in all?',g*e,{min:1,options:thOpt(g*e,[g+e,g*e+e,g*e-e],1,null),why:g+' groups of '+e+'. '+g+' × '+e+' = '+g*e+'.'});},
div:function(){var k=pick([2,3,4,5,10]),e=rnd(3,9),t=k*e,n=thName();
return Q('Story problem',k+' children share '+t+' stickers equally. How many stickers does each child get?',e,{min:1,options:thOpt(e,[k,t,e+1],1,null),why:'Share equally into '+k+' groups. '+t+' ÷ '+k+' = '+e+'.'});},
money:function(){var pay=pick([5,10,20])*100,cost=rnd(11,pay/10-6)*10,n=thName(),f=pick(TH_FOOD.slice(0,3));
return Q('Story problem',n[0]+' buys '+['a plate of chicken rice','a bowl of laksa','a set of kaya toast'][TH_FOOD.indexOf(f)]+' for '+thAmt(cost)+'. '+thHe(n,true)+' pays with '+thAmt(pay)+'. How much change does '+thHe(n)+' get?',thAmt(pay-cost),{options:strOpts(thAmt(pay-cost),[thAmt(pay+cost),thAmt(pay-cost+100),thAmt(pay-cost-100),thAmt(pay-cost+10)]),why:'Change = money paid − cost. '+thAmt(pay)+' − '+thAmt(cost)+' = '+thAmt(pay-cost)+'.'});},
twoStep:function(){var g=rnd(2,5),p=rnd(1,4),pay=pick([10,20]),n=thName(); while(g*p>=pay)p--; if(p<1)p=1;
return Q('Story problem',n[0]+' buys '+g+' packets of tissue at $'+p+' each. '+thHe(n,true)+' pays with $'+pay+'. How much change does '+thHe(n)+' get?','$'+(pay-g*p),{options:strOpts('$'+(pay-g*p),['$'+(pay-p),'$'+(pay+g*p),'$'+(g*p),'$'+(pay-g*p+1)]),why:g+' × $'+p+' = $'+g*p+'. $'+pay+' − $'+g*p+' = $'+(pay-g*p)+'.'});},
time:function(){var h=rnd(3,8),m=pick([0,15,30,45]),d=pick([15,30,45]),n=thName(),st=h*60+m+720,en=st+d;
return Q('Story problem',n[0]+' starts reading at '+fmt12(st)+'. '+thHe(n,true)+' reads for '+d+' minutes. At what time does '+thHe(n)+' stop?',fmt12(en),{options:strOpts(fmt12(en),[fmt12(en+15),fmt12(en-15),fmt12(en+30),fmt12(en-30)]),why:fmt12(st)+' and '+d+' minutes later is '+fmt12(en)+'.'});},
fraction:function(){var d=pick([5,6,8,10,12]),a=rnd(1,d-3),b=rnd(1,d-1-a),n1=thName(),n2=thName(n1),s=a+b;
return Q('Story problem',n1[0]+' ate '+a+'/'+d+' of a pizza. '+n2[0]+' ate '+b+'/'+d+' of the same pizza. What fraction of the pizza did they eat in all?',s+'/'+d,{options:strOpts(s+'/'+d,[s+'/'+(2*d),(a*b)+'/'+d,(s+1)+'/'+d,(s-1)+'/'+d]),why:'The parts are the same size, so add the tops. '+a+' + '+b+' = '+s+'. They ate '+s+'/'+d+'.'});}
};
var TH_ST4={
timesAsMany:function(){var a=rnd(12,40),k=rnd(2,5),n=thName(),m=thName(n);
return Q('Word problem',n[0]+' has '+a+' marbles. '+m[0]+' has '+k+' times as many marbles as '+n[0]+'. How many marbles do they have altogether?',a+k*a,{min:0,options:thOpt(a+k*a,[k*a,a+k,a*k*k,a+k*a+a],0,null),why:m[0]+' has '+a+' × '+k+' = '+k*a+' marbles. Altogether: '+a+' + '+k*a+' = '+(a+k*a)+'.'});},
twoStepMul:function(){var b=pick([6,10,12]),g=rnd(3,9),u=rnd(5,b*g-8),n=thName();
return Q('Word problem','A tray holds '+b+' eggs. '+n[0]+' buys '+g+' trays and uses '+u+' eggs to bake. How many eggs are left?',b*g-u,{min:0,options:thOpt(b*g-u,[b*g+u,b+g-u>0?b+g-u:b*g-u+3,b*g-u+10,b*g-u-10],0,null),why:g+' × '+b+' = '+b*g+' eggs at first. '+b*g+' − '+u+' = '+(b*g-u)+'.'});},
buses:function(){var c=pick([30,40,45,50]),k=rnd(2,6),t=c*k+rnd(3,c-5);
return Q('Word problem','A school bus carries '+c+' pupils. '+t+' pupils go on a trip. How many buses are needed?',k+1,{min:1,options:thOpt(k+1,[k,k+2,Math.round(t/c)===k+1?k+2:Math.round(t/c)],1,null),why:t+' ÷ '+c+' = '+k+' remainder '+(t-c*k)+'. The '+(t-c*k)+' pupils left over need 1 more bus. So '+(k+1)+' buses are needed.'});},
mass:function(){var kg=rnd(2,5),g=rnd(1,9)*100+pick([0,50]),n=thName(),a=kg*1000-g;
return Q('Word problem','A bag of rice has a mass of '+kg+' kg. '+n[0]+' uses '+g+' g of the rice. How many grams of rice are left?',a,{min:1,options:thOpt(a,[kg*1000+g,kg-g>0?kg-g:a+100,a+100,a-100],1,null),why:kg+' kg = '+kg*1000+' g. '+kg*1000+' − '+g+' = '+a+' g.'});},
money2:function(){var p=rnd(2,6)*10+rnd(0,1)*50,k=rnd(2,4),d=rnd(10,30)*10,pay=pick([10,20])*100,n=thName(); var cost=p*k+d; while(cost>=pay){p-=10; cost=p*k+d; if(p<20){k=2;d=100;p=60;cost=220;}}
return Q('Word problem',n[0]+' buys '+k+' curry puffs at '+thAmt(p)+' each and a drink for '+thAmt(d)+'. '+thHe(n,true)+' pays with '+thAmt(pay)+'. How much change does '+thHe(n)+' get?',thAmt(pay-cost),{options:strOpts(thAmt(pay-cost),[pay-p-d,cost,pay-cost+100,pay-cost-100,pay-cost+50].filter(function(c){return c>0&&c!==pay-cost;}).map(thAmt)),why:k+' × '+thAmt(p)+' = '+thAmt(p*k)+'. Add the drink: '+thAmt(p*k)+' + '+thAmt(d)+' = '+thAmt(cost)+'. Change: '+thAmt(pay)+' − '+thAmt(cost)+' = '+thAmt(pay-cost)+'.'});},
time2:function(){var h=rnd(6,9),m=pick([10,20,30,40,50]),dh=rnd(1,3),dm=pick([15,25,35,45]),n=thName(),st=h*60+m,en=st+dh*60+dm;
return Q('Word problem','A bus leaves the interchange at '+fmt12(st)+'. The trip takes '+dh+' h '+dm+' min. At what time does the bus arrive?',fmt12(en),{options:strOpts(fmt12(en),[fmt12(en+10),fmt12(en-10),fmt12(en+60),fmt12(st+dh*60+dm+30)]),why:fmt12(st)+' plus '+dh+' h is '+fmt12(st+dh*60)+'. Then plus '+dm+' min is '+fmt12(en)+'.'});},
track:function(){var len=pick([200,250,400,500]),k=rnd(3,8),n=thName();
return Q('Word problem',n[0]+' jogs '+k+' rounds around a track that is '+len+' m long. How many metres does '+thHe(n)+' jog?',len*k,{min:1,options:thOpt(len*k,[len+k,len*k+len,len*k-len,len*k*10],1,null),why:k+' rounds of '+len+' m. '+k+' × '+len+' = '+len*k+' m.'});},
perimeter:function(){var l=rnd(8,25),w=rnd(4,l-1),n=thName();
return Q('Word problem','A rectangular garden is '+l+' m long and '+w+' m wide. '+n[0]+' puts a fence all round the garden. How long is the fence in metres?',2*(l+w),{min:1,options:thOpt(2*(l+w),[l*w,l+w,2*l+w,l+w+l],1,null),why:'The fence goes all round: 2 × ('+l+' + '+w+') = 2 × '+(l+w)+' = '+2*(l+w)+' m.'});}
};
var TH_ST5={
fracOfSet:function(){var d=pick([4,5,8,10]),k=rnd(1,d-2),u=rnd(2,9),n=d*u,a=thName(),b=thName(a),it=pick(['stickers','marbles','stamps']);
return Q('Word problem',a[0]+' has '+n+' '+it+'. '+thHe(a,true)+' gives '+k+'/'+d+' of them to '+b[0]+'. How many '+it+' does '+a[0]+' have left?',n-k*u,{min:1,options:thOpt(n-k*u,[k*u,n-u,n+k*u,n-k*u+u],1,null),why:'1/'+d+' of '+n+' = '+u+'. '+k+'/'+d+' of '+n+' = '+k*u+'. Left: '+n+' − '+k*u+' = '+(n-k*u)+'.'});},
walk:function(){var l=rnd(20,45),w=rnd(12,l-3),r=rnd(2,4),n=thName();
return Q('Word problem','A rectangular playground is '+l+' m long and '+w+' m wide. '+n[0]+' walks '+r+' times round the edge. How many metres does '+thHe(n)+' walk?',r*2*(l+w),{min:1,options:thOpt(r*2*(l+w),[2*(l+w),r*l*w,r*(l+w),2*r*l+w],1,null),why:'One round is 2 × ('+l+' + '+w+') = '+2*(l+w)+' m. '+r+' rounds: '+r+' × '+2*(l+w)+' = '+r*2*(l+w)+' m.'});},
tiles:function(){var l=rnd(6,15),w=rnd(4,l-1),n=thName();
return Q('Word problem','A rectangular classroom floor is '+l+' m long and '+w+' m wide. Square tiles with sides of 1 m cover the floor. How many tiles are needed?',l*w,{min:1,options:thOpt(l*w,[2*(l+w),l+w,l*w+l,l*w-w],1,null),why:'Area = length × width = '+l+' × '+w+' = '+l*w+' m². Each tile covers 1 m², so '+l*w+' tiles are needed.'});},
decMoney:function(){var a=rnd(40,95)*10+5*rnd(0,1)*10,b=rnd(15,40)*10+50,p=rnd(2,3),q=rnd(2,4),t=a*p+b*q,n=thName();
return Q('Word problem','A tin of Milo costs '+thAmt(a)+'. A packet of biscuits costs '+thAmt(b)+'. '+n[0]+' buys '+p+' tins and '+q+' packets. How much does '+thHe(n)+' pay in all?',thAmt(t),{options:strOpts(thAmt(t),[thAmt(a+b),thAmt(t+100),thAmt(t-100),thAmt(a*q+b*p)]),why:p+' × '+thAmt(a)+' = '+thAmt(a*p)+'. '+q+' × '+thAmt(b)+' = '+thAmt(b*q)+'. Add: '+thAmt(a*p)+' + '+thAmt(b*q)+' = '+thAmt(t)+'.'});},
decMeasure:function(){var a=rnd(31,59),b=rnd(11,a-8),n=thName();
return Q('Word problem','A jug has '+tenths(a)+' ℓ of water. '+n[0]+' pours out '+tenths(b)+' ℓ. How much water is left in the jug in ℓ?',tenths(a-b),{options:strOpts(tenths(a-b),[tenths(a+b),tenths(a-b+1),tenths(a-b-1),tenths(a-b+10)]),why:tenths(a)+' − '+tenths(b)+' = '+tenths(a-b)+' ℓ.'});},
mult3x2:function(){var a=rnd(120,480),d=rnd(12,29),pl=pick(TH_PLACE),f=pick(TH_FOOD.slice(0,5));
return Q('Word problem','Every day, '+pl+' sells '+a+' '+f[1]+'. How many '+f[1]+' does it sell in '+d+' days?',a*d,{min:1,options:thOpt(a*d,[a*d+a,a*d-a,a*d+d*10,a*d-10*d],1,null),why:a+' × '+d+' = '+a*d+'. ('+a+' × 10 = '+a*10+' and '+a+' × '+(d-10)+' = '+a*(d-10)+'. Add them.)'});},
divDec:function(){var k=pick([2,4,5]),u=rnd(11,39),t=k*u,n=thName();
return Q('Word problem',n[0]+' cuts a ribbon that is '+tenths(t)+' m long into '+k+' equal pieces. How long is each piece in metres?',tenths(u),{options:strOpts(tenths(u),[tenths(u*10),tenths(u+10),tenths(Math.max(1,u-10)),String(u)]),why:tenths(t)+' ÷ '+k+' = '+tenths(u)+' m.'});},
fracLeft:function(){var P=[[1,2,1,4],[1,2,1,8],[1,4,1,2],[1,3,1,6],[1,2,1,3],[2,5,1,10],[1,5,1,2]],p=pick(P),n=thName(),it=pick(['a bottle of water','a packet of rice','a cake']);
var A=p[0]/p[1],B=p[2]/p[3],left=1-A-B; var d=p[1]*p[3]/gcd2(p[1],p[3]),ln=Math.round(left*d),L=fr2(ln,d);
return Q('Word problem',n[0]+' used '+p[0]+'/'+p[1]+' of '+it+' on Monday and '+p[2]+'/'+p[3]+' of it on Tuesday. What fraction of it is left?',L,{options:strOpts(L,[fr2(Math.round((A+B)*d),d),fr2(Math.round((1-A)*d),d),fr2(Math.round((1-B)*d),d),(p[0]+p[2])+'/'+(p[1]+p[3])]),why:p[0]+'/'+p[1]+' + '+p[2]+'/'+p[3]+' = '+fr2(Math.round((A+B)*d),d)+' used. 1 − '+fr2(Math.round((A+B)*d),d)+' = '+L+' is left.'});}
};
var TH_ST6={
ratioShare:function(){var a=rnd(1,5),b=rnd(a+1,7),u=rnd(4,15),t=(a+b)*u,n=thName(),m=thName(n),big=rnd(0,1);
return Q('Word problem',n[0]+' and '+m[0]+' share $'+t+' in the ratio '+a+' : '+b+'. How much money does '+(big?m[0]:n[0])+' get?','$'+(big?b:a)*u,{options:strOpts('$'+(big?b:a)*u,['$'+(big?a:b)*u,'$'+Math.round(t/2),'$'+(big?b:a)*u*2,'$'+(t-(big?b:a))]),why:(a+b)+' parts in all. 1 part = $'+t+' ÷ '+(a+b)+' = $'+u+'. '+(big?m[0]+' gets '+b:n[0]+' gets '+a)+' parts: '+(big?b:a)+' × $'+u+' = $'+(big?b:a)*u+'.'});},
ratioTotal:function(){var a=rnd(2,5),b=rnd(2,7),u=rnd(3,9),n=a*u; while(a===b)b=rnd(2,7);
return Q('Word problem','The ratio of boys to girls in a choir is '+a+' : '+b+'. There are '+n+' boys. How many pupils are in the choir altogether?',(a+b)*u,{min:1,options:thOpt((a+b)*u,[b*u,n+b,(a+b)*u+u,a*b*u],1,null),why:a+' parts = '+n+', so 1 part = '+u+'. Total parts = '+(a+b)+'. '+(a+b)+' × '+u+' = '+(a+b)*u+'.'});},
discount:function(){var pr=pick([40,60,80,120,200]),d=pick([10,20,25,50]),n=thName(),sale=pr*(100-d)/100;
return Q('Word problem','A pair of shoes costs $'+pr+'. The shop gives a '+d+'% discount. What is the sale price in dollars?',sale,{min:1,options:thOpt(sale,[pr*d/100,pr-d,pr+pr*d/100,pr-2*pr*d/100],1,null),why:d+'% of $'+pr+' = $'+pr*d/100+'. $'+pr+' − $'+pr*d/100+' = $'+sale+'.'});},
gst:function(){var pr=pick([20,40,50,60,80,100,120,200]),tot=String(+(pr*109/100).toFixed(2)),g=String(+(pr*9/100).toFixed(2));
return Q('Word problem','A school bag costs $'+pr+' before GST. GST is 9%. What is the price after GST in dollars?',tot,{options:strOpts(tot,[g,String(+(pr*91/100).toFixed(2)),String(pr+9),String(+(pr*118/100).toFixed(2))]),why:'9% of $'+pr+' = $'+g+'. $'+pr+' + $'+g+' = $'+tot+'.'});},
nextScore:function(){var a=rnd(60,90),b=rnd(60,95),c=rnd(60,95),k=4,avg=rnd(70,90),need=avg*k-a-b-c,n=thName(); if(need<40||need>100){a=70;b=80;c=90;avg=80;need=80;}
return Q('Word problem',n[0]+' scored '+a+', '+b+' and '+c+' marks in three tests. What score must '+thHe(n)+' get in the fourth test to have an average of '+avg+' marks?',need,{min:0,max:100,options:thOpt(need,[avg,need+4,need-4,Math.round((a+b+c)/3)],0,null),why:'Total needed = '+avg+' × 4 = '+avg*4+'. Marks so far: '+(a+b+c)+'. '+avg*4+' − '+(a+b+c)+' = '+need+'.'});},
speed:function(){var v=pick([40,50,60,75,80,90]),t1=rnd(2,4),t2=t1+rnd(1,3),n=thName();
return Q('Word problem','A train travels '+v*t1+' km in '+t1+' hours at a steady speed. How far does it travel in '+t2+' hours at the same speed?',v*t2,{min:1,options:thOpt(v*t2,[v,v*t1+t2,v*t2+v,v*(t2-t1)],1,null),why:'Speed = '+v*t1+' ÷ '+t1+' = '+v+' km/h. Distance in '+t2+' hours = '+v+' × '+t2+' = '+v*t2+' km.'});},
volume:function(){var l=pick([40,50,60,80]),w=pick([20,25,30,40]),h=pick([20,25,30,40]),v=l*w*h; if(v%1000!==0){l=50;w=40;h=30;v=60000;}
return Q('Word problem','A fish tank is '+l+' cm long, '+w+' cm wide and '+h+' cm high. It is filled with water to the top. How many litres of water does it hold? (1 ℓ = 1000 cm³)',v/1000,{min:1,options:thOpt(v/1000,[v,v/100,v/10000,l+w+h],1,null),why:'Volume = '+l+' × '+w+' × '+h+' = '+v+' cm³. '+v+' ÷ 1000 = '+v/1000+' ℓ.'});},
fracRem:function(){var base=pick([60,120,180,240]),a=pick([3,4,5]),b=pick([2,3,4]),n=thName(),s1=base/a,rem=base-s1,s2=rem/b; if(rem%b!==0){return TH_ST6.fracRem();}
return Q('Word problem',n[0]+' had $'+base+'. '+thHe(n,true)+' spent 1/'+a+' of it on a shirt. Then '+thHe(n)+' spent 1/'+b+' of the remainder on lunch. How much money does '+thHe(n)+' have left in dollars?',rem-s2,{min:1,options:thOpt(rem-s2,[base-s1-base/b,rem,s2,base-s2],1,null),why:'Shirt: $'+base+' ÷ '+a+' = $'+s1+'. Remainder: $'+base+' − $'+s1+' = $'+rem+'. Lunch: $'+rem+' ÷ '+b+' = $'+s2+'. Left: $'+rem+' − $'+s2+' = $'+(rem-s2)+'.'});}
};
/* ---------- Spot the mistake for P2 to P6 ---------- */
function thD(n){return [Math.floor(n/100),Math.floor(n/10)%10,n%10];}
function thCar(s,u){return s>=10?'Write '+(s%10)+', carry '+Math.floor(s/10)+' '+u+'.':'Write '+s+'.';}
var TH_SP2={
carry3:function(){var oa,ob,ta,tb,ha,hb,n=thName(),t=0;
do{oa=rnd(2,9);ob=rnd(10-oa,9);ta=rnd(1,9);tb=rnd(1,9);ha=rnd(1,4);hb=rnd(1,4);t++;}while(false);
var a=ha*100+ta*10+oa,b=hb*100+tb*10+ob,s1=oa+ob,s2=ta+tb,c2=Math.floor(s2/10),h3=ha+hb+c2,right=a+b,w=h3*100+(s2%10)*10+(s1%10);
var hr=ha+hb+Math.floor((ta+tb+1)/10); if(hr>9)return TH_SP2.carry3();
return thSpot({head:n[0]+' works out '+a+' + '+b+' by adding the columns.',steps:['Ones: '+oa+' + '+ob+' = '+s1+'. '+thCar(s1,'ten'),'Tens: '+ta+' + '+tb+' = '+s2+'. '+thCar(s2,'hundred'),'Hundreds: '+ha+' + '+hb+(c2?' + '+c2:'')+' = '+h3+'.','The answer is '+w+'.'],bad:1,right:right,wrong:w,distract:[right+10,right-10,right+100],
why:n[0]+' forgot to add the carried ten in the tens column. Step 2 should be '+ta+' + '+tb+' + 1 = '+(ta+tb+1)+'.'});},
subSmaller3:function(){var oa=rnd(0,6),ob=rnd(oa+1,9),tb=rnd(1,4),ta=rnd(tb+1,8),hb=rnd(1,4),ha=rnd(hb+1,9),a=ha*100+ta*10+oa,b=hb*100+tb*10+ob,n=thName(),w=(ha-hb)*100+(ta-tb)*10+(ob-oa);
return thSpot({head:n[0]+' works out '+a+' − '+b+'.',steps:['Ones: '+oa+' < '+ob+', so '+ob+' − '+oa+' = '+(ob-oa)+'.','Tens: '+ta+' − '+tb+' = '+(ta-tb)+'.','Hundreds: '+ha+' − '+hb+' = '+(ha-hb)+'.','The answer is '+w+'.'],bad:0,right:a-b,wrong:w,distract:[a-b+10,a-b-10,a-b+100],
why:n[0]+' took the smaller digit from the bigger digit in the ones column. We cannot take '+ob+' ones from '+oa+' ones, so regroup 1 ten as 10 ones: '+(10+oa)+' − '+ob+' = '+(10+oa-ob)+'. Then the tens become '+(ta-1)+' − '+tb+'.'});},
subForget:function(){var oa=rnd(0,6),ob=rnd(oa+1,9),tb=rnd(1,4),ta=rnd(tb+2,9),hb=rnd(1,4),ha=rnd(hb+1,9),a=ha*100+ta*10+oa,b=hb*100+tb*10+ob,n=thName(),w=(ha-hb)*100+(ta-tb)*10+(10+oa-ob);
return thSpot({head:n[0]+' works out '+a+' − '+b+'.',steps:['Ones: regroup 1 ten as 10 ones. '+(10+oa)+' − '+ob+' = '+(10+oa-ob)+'.','Tens: '+ta+' − '+tb+' = '+(ta-tb)+'.','Hundreds: '+ha+' − '+hb+' = '+(ha-hb)+'.','The answer is '+w+'.'],bad:1,right:a-b,wrong:w,distract:[a-b+10,a-b-1,a-b+100],
why:n[0]+' forgot that 1 ten was regrouped. The tens digit is now '+(ta-1)+', so Step 2 should be '+(ta-1)+' − '+tb+' = '+(ta-1-tb)+'.'});},
fracAddLike:function(){var d=pick([5,6,8,10,12]),a=rnd(1,d-3),b=rnd(1,d-1-a),s=a+b,n=thName();
return thSpot({head:n[0]+' works out '+a+'/'+d+' + '+b+'/'+d+'.',steps:['Add the top numbers: '+a+' + '+b+' = '+s+'.','Add the bottom numbers: '+d+' + '+d+' = '+2*d+'.','The answer is '+s+'/'+2*d+'.'],bad:1,right:s+'/'+d,wrong:s+'/'+2*d,distract:[(s+1)+'/'+d,a+'/'+d],
why:n[0]+' added the bottom numbers. The parts are the same size, so the bottom number stays '+d+'. The answer is '+s+'/'+d+'.'});},
fracCmp:function(){var D=[2,3,4,5,6,8,10,12],a=pick(D.slice(0,7)),b=pick(D.filter(function(x){return x>a;})),n=thName();
return thSpot({head:n[0]+' wants to know which is bigger, 1/'+a+' or 1/'+b+'.',steps:['Look at the bottom numbers: '+a+' and '+b+'.',b+' is bigger than '+a+', so 1/'+b+' is bigger.','So 1/'+b+' is bigger than 1/'+a+'.'],bad:1,right:'1/'+a,wrong:'1/'+b,distract:['1/'+(a+b),'1/'+Math.max(2,a-1)],
why:'A bigger bottom number means the whole is cut into more parts, so each part is smaller. 1/'+a+' is bigger than 1/'+b+'.'});},
hourMin:function(){var h=rnd(1,4),m=pick([10,15,20,30,45]),n=thName();
return thSpot({head:n[0]+' changes '+h+' h '+m+' min to minutes.',steps:['1 h = 100 min.',h+' h = '+h+' × 100 = '+h*100+' min.',h*100+' + '+m+' = '+(h*100+m)+' min.'],bad:0,right:h*60+m,wrong:h*100+m,distract:[h*60,h*60+m+10],
why:'1 hour is 60 minutes, not 100 minutes. '+h+' h = '+h+' × 60 = '+h*60+' min. Then '+h*60+' + '+m+' = '+(h*60+m)+' min.'});}
};
var TH_SP4={
carryMul:function(){var oa=rnd(3,9),d=rnd(3,9); while(oa*d<10)d=rnd(3,9); var ta=rnd(2,9),a=ta*10+oa,p1=oa*d,c1=Math.floor(p1/10),p2=ta*d,n=thName(),w=p2*10+p1%10;
return thSpot({head:n[0]+' works out '+a+' × '+d+'.',steps:['Ones: '+oa+' × '+d+' = '+p1+'. Write '+p1%10+', carry '+c1+'.','Tens: '+ta+' × '+d+' = '+p2+'.','Write '+p2+' next to the '+p1%10+'. The answer is '+w+'.'],bad:1,right:a*d,wrong:w,distract:[a*d+10,a*d-10,a*d+100],
why:n[0]+' forgot to add the carried '+c1+'. Step 2 should be '+ta+' × '+d+' + '+c1+' = '+(p2+c1)+'. The answer is '+a*d+'.'});},
remBoxes:function(){var c=pick([30,40,45,50]),k=rnd(2,6),r=rnd(3,c-5),t=c*k+r,n=thName(),pl=pick(['the zoo','the Science Centre','the museum']);
return thSpot({head:t+' pupils go to '+pl+'. Each bus carries '+c+' pupils. How many buses are needed?',steps:[t+' ÷ '+c+' = '+k+' remainder '+r+'.','There are '+k+' full buses.','So '+k+' buses are needed.'],bad:2,right:k+1,wrong:k,distract:[k+2,t-c*k],
why:'The remainder is '+r+', so '+r+' pupils still need a bus. We must add 1 bus: '+(k+1)+' buses are needed.'});},
roundHundred:function(){var h=rnd(11,98),t=rnd(5,9),o=rnd(1,4),N=h*100+t*10+o,n=thName();
return thSpot({head:n[0]+' rounds '+N+' to the nearest hundred.',steps:['Look at the ones digit: '+o+'.',o+' is less than 5, so round down.','The answer is '+h*100+'.'],bad:0,right:(h+1)*100,wrong:h*100,distract:[(h+1)*100+100,N-o],
why:'To round to the nearest hundred, look at the tens digit, not the ones digit. The tens digit is '+t+', which is 5 or more, so round up to '+(h+1)*100+'.'});},
timeAdd:function(){var h1=rnd(1,4),h2=rnd(1,3),m1=pick([30,35,40,45,50]),m2=pick([25,30,35,40,45]),s=m1+m2,n=thName(); while(s%60===0||s<60){m2=pick([25,30,35,40,45]); s=m1+m2;}
var H=h1+h2+1,M=s-60;
return thSpot({head:n[0]+' works out '+h1+' h '+m1+' min + '+h2+' h '+m2+' min.',steps:['Add the hours: '+h1+' + '+h2+' = '+(h1+h2)+' h.','Add the minutes: '+m1+' + '+m2+' = '+s+' min.','The answer is '+(h1+h2)+' h '+s+' min.'],bad:2,right:H+' h '+M+' min',wrong:(h1+h2)+' h '+s+' min',distract:[(H-1)+' h '+(M+10)+' min',H+' h '+(M+10)+' min',(H+1)+' h '+M+' min'],
why:s+' min is more than 60 min. 60 min = 1 h, so '+s+' min = 1 h '+M+' min. The answer is '+(h1+h2)+' h + 1 h '+M+' min = '+H+' h '+M+' min.'});},
areaPerim:function(){var l=rnd(6,14),w=rnd(3,l-1),n=thName(); while(l*w===2*(l+w)||l*w===l+w||l*w===l*w+l){l=rnd(6,14);w=rnd(3,l-1);}
return thSpot({head:n[0]+' finds the area of a rectangle that is '+l+' cm long and '+w+' cm wide.',steps:['The rectangle is '+l+' cm by '+w+' cm.','Area = 2 × ('+l+' + '+w+') = '+2*(l+w)+' cm².','The area is '+2*(l+w)+' cm².'],bad:1,right:l*w,wrong:2*(l+w),distract:[l+w,l*w+l],
why:n[0]+' used the rule for the perimeter. Area is length × width: '+l+' × '+w+' = '+l*w+' cm².'});},
divZero:function(){var d=pick([2,3,4]),mxh=Math.floor(9/d),h=rnd(1,mxh),u=rnd(1,mxh),N=d*h*100+d*u,n=thName();
return thSpot({head:n[0]+' works out '+N+' ÷ '+d+'.',steps:['Hundreds: '+d*h+' ÷ '+d+' = '+h+'.','Tens: 0 ÷ '+d+' = 0. We do not need to write this 0.','Ones: '+d*u+' ÷ '+d+' = '+u+'.','The answer is '+(h*10+u)+'.'],bad:1,right:h*100+u,wrong:h*10+u,distract:[h*1000+u,h*100+u*10],
why:'The 0 in the tens place must be written, because it keeps the digits in the right places. The answer is '+h+'0'+u+' = '+(h*100+u)+'.'});}
};
function thFrAdd(){var p=pick([[2,3],[2,5],[3,4],[3,5],[4,5],[2,7],[3,7],[5,6],[2,9],[3,8]]),b=p[0],d=p[1],a=rnd(1,b-1),c=rnd(1,d-1),num=a*d+c*b,den=b*d; return {a:a,b:b,c:c,d:d,num:num,den:den};}
var TH_SP5={
fracAdd:function(){var f=thFrAdd(),n=thName(),R=fr2(f.num,f.den),wr=(f.a+f.c)+'/'+(f.b+f.d); while(R===wr){f=thFrAdd(); R=fr2(f.num,f.den); wr=(f.a+f.c)+'/'+(f.b+f.d);}
return thSpot({head:n[0]+' works out '+f.a+'/'+f.b+' + '+f.c+'/'+f.d+'.',steps:['Add the top numbers: '+f.a+' + '+f.c+' = '+(f.a+f.c)+'.','Add the bottom numbers: '+f.b+' + '+f.d+' = '+(f.b+f.d)+'.','The answer is '+wr+'.'],bad:1,right:R,wrong:wr,distract:[fr2(f.a*f.d+f.c,f.den),fr2(Math.abs(f.a*f.d-f.c*f.b)||1,f.den)],
why:n[0]+' added the bottom numbers. Fractions with different bottom numbers must be changed to the same bottom number first: '+f.a*f.d+'/'+f.den+' + '+f.c*f.b+'/'+f.den+' = '+R+'.'});},
mixedConcat:function(){var w=rnd(1,6),d=rnd(3,9),nu=rnd(1,d-1),n=thName(),R=(w*d+nu)+'/'+d,W=(w*10+nu)+'/'+d;
return thSpot({head:n[0]+' changes '+w+' '+nu+'/'+d+' to an improper fraction.',steps:['Write the whole number and the top number together: '+w+nu+'.','Keep the bottom number: '+W+'.','The answer is '+W+'.'],bad:0,right:R,wrong:W,distract:[(w+nu)+'/'+d,(w*d)+'/'+d],
why:n[0]+' joined the digits '+w+' and '+nu+'. The correct way is whole number × bottom number + top number: '+w+' × '+d+' + '+nu+' = '+(w*d+nu)+'. The answer is '+R+'.'});},
decCompare:function(){var p=rnd(1,8),q=rnd(1,9),c=rnd(p+1,9),n=thName(),x='0.'+p+q,y='0.'+c,flip=rnd(0,1),A=flip?y:x,B=flip?x:y;
return thSpot({head:n[0]+' wants to know which is greater, '+A+' or '+B+'.',steps:['Ignore the decimal points. Compare '+(p*10+q)+' and '+c+'.',(p*10+q)+' is greater than '+c+'.','So '+x+' is greater than '+y+'.'],bad:0,right:y,wrong:x,distract:['0.0'+c,'0.'+q],
why:n[0]+' ignored the decimal points. Compare the tenths first: '+c+' tenths is more than '+p+' tenths, so '+y+' is greater than '+x+'.'});},
decMulTen:function(){var v=rnd(11,99),x=tenths(v),k=pick([10,100,1000]),z=String(k).length-1,n=thName(),R=v*k/10,W=x+Array(z+1).join('0');
return thSpot({head:n[0]+' works out '+x+' × '+k+'.',steps:[k+' has '+z+' zero'+(z>1?'s':'')+'.','Add '+z+' zero'+(z>1?'s':'')+' to the end of '+x+': '+W+'.','The answer is '+W+'.'],bad:1,right:R,wrong:W,distract:[R*10,R/10],
why:n[0]+' only added zeros, so the value did not change. Multiplying by '+k+' moves each digit '+z+' place'+(z>1?'s':'')+' to the left: '+x+' × '+k+' = '+R+'.'});},
zeroPlace:function(){var a=rnd(12,49),tb=rnd(1,3),ub=rnd(2,9),b=tb*10+ub,n=thName(),p1=a*ub,p2=a*tb,w=p1+p2;
return thSpot({head:n[0]+' works out '+a+' × '+b+'.',steps:[a+' × '+ub+' = '+p1+'.',a+' × '+tb+' = '+p2+'.','Add: '+p1+' + '+p2+' = '+w+'.'],bad:1,right:a*b,wrong:w,distract:[a*b+10,a*b-a,p1*tb],
why:'The '+tb+' in '+b+' is '+tb+' tens, so Step 2 should be '+a+' × '+tb+'0 = '+a*tb*10+'. Then '+p1+' + '+a*tb*10+' = '+a*b+'.'});},
fracOfSet:function(){var d=pick([4,5,8]),k=rnd(2,d-1),m=rnd(1,2),N=k*d*m,n=thName(),R=N/d*k,W=N/k*d;
return thSpot({head:n[0]+' works out '+k+'/'+d+' of '+N+'.',steps:['Divide '+N+' by the top number '+k+': '+N+' ÷ '+k+' = '+N/k+'.','Multiply by the bottom number '+d+': '+N/k+' × '+d+' = '+W+'.','So '+k+'/'+d+' of '+N+' = '+W+'.'],bad:0,right:R,wrong:W,distract:[N/d,R+N/d],
why:n[0]+' divided by the top number. To find '+k+'/'+d+' of '+N+', divide by the bottom number '+d+' first: '+N+' ÷ '+d+' = '+N/d+'. Then multiply by '+k+': '+N/d+' × '+k+' = '+R+'.'});}
};
var TH_SP6={
pctOf:function(){var p=pick([20,25,40,50]),b=pick([40,60,80,100,120,200]),n=thName(),R=b*p/100,W=+(b/p).toFixed(2);
return thSpot({head:n[0]+' finds '+p+'% of '+b+'.',steps:[p+'% means '+p+' out of 100.','Divide '+b+' by '+p+': '+b+' ÷ '+p+' = '+W+'.','So '+p+'% of '+b+' = '+W+'.'],bad:1,right:String(R),wrong:String(W),distract:[String(b*p),String(R*2)],
why:n[0]+' divided by '+p+'. '+p+'% means '+p+'/100, so we find '+p+'/100 × '+b+' = '+R+'.'});},
pctIncrease:function(){var pr=pick([40,60,80,120,200]),p=pick([10,20,25,50]),n=thName(),inc=pr*p/100,it=pick(['A pair of shoes','A school bag','A bicycle']);
return thSpot({head:it+' costs $'+pr+'. The price goes up by '+p+'%. '+n[0]+' works out the new price.',steps:['The increase is $'+p+'.','New price = $'+pr+' + $'+p+' = $'+(pr+p)+'.','The new price is $'+(pr+p)+'.'],bad:0,right:'$'+(pr+inc),wrong:'$'+(pr+p),distract:['$'+(pr-inc),'$'+inc],
why:'The increase is '+p+'% of $'+pr+', not $'+p+'. '+p+'% of $'+pr+' = $'+inc+'. New price = $'+pr+' + $'+inc+' = $'+(pr+inc)+'.'});},
ratioShare:function(){var a=rnd(1,4),b=rnd(a+1,6),u=rnd(2,6),t=(a+b)*a*u,n=thName(),R=b*t/(a+b),W=b*t/a;
return thSpot({head:n[0]+' shares $'+t+' in the ratio '+a+' : '+b+'. How much is the bigger share?',steps:['Add the parts: '+a+' + '+b+' = '+(a+b)+'.','One part = $'+t+' ÷ '+a+' = $'+t/a+'.','The bigger share = '+b+' × $'+t/a+' = $'+W+'.'],bad:1,right:'$'+R,wrong:'$'+W,distract:['$'+t/(a+b)*a,'$'+t/2],
why:'One part is the total divided by all the parts, '+(a+b)+', not by '+a+'. $'+t+' ÷ '+(a+b)+' = $'+t/(a+b)+'. The bigger share = '+b+' × $'+t/(a+b)+' = $'+R+'.'});},
average0:function(){var R=pick([3,6,9]),S=4*R,p1=rnd(1,S-2),p2=rnd(1,S-p1-1),p3=S-p1-p2,arr=shuffle([p1,p2,p3,0]),n=thName();
return thSpot({head:n[0]+' scored '+arr.join(', ')+' in 4 quizzes. '+thHe(n,true)+' works out the average score.',steps:['Add the scores: '+arr.join(' + ')+' = '+S+'.','Count the scores that are not 0: 3.',S+' ÷ 3 = '+S/3+'.'],bad:1,right:String(R),wrong:String(S/3),distract:[String(S),String(R+1)],
why:'A score of 0 still counts as a quiz. There are 4 quizzes, so divide by 4: '+S+' ÷ 4 = '+R+'.'});},
triArea:function(){var b=rnd(3,10)*2,h=rnd(3,12),n=thName();
return thSpot({head:n[0]+' finds the area of a triangle with base '+b+' cm and height '+h+' cm.',steps:['The base is '+b+' cm and the height is '+h+' cm.','Area = base × height = '+b+' × '+h+' = '+b*h+' cm².','The area is '+b*h+' cm².'],bad:1,right:b*h/2,wrong:b*h,distract:[b+h,b*h/2+b],
why:n[0]+' forgot the half. The area of a triangle is 1/2 × base × height = 1/2 × '+b+' × '+h+' = '+b*h/2+' cm².'});},
orderOps:function(){var t=rnd(0,2),n=thName(),a,b,c,R,W,txt,s1,s2;
if(t===0){a=rnd(2,9);b=rnd(2,9);c=rnd(2,6); R=a+b*c; W=(a+b)*c; txt=a+' + '+b+' × '+c; s1=a+' + '+b+' = '+(a+b)+'.'; s2=(a+b)+' × '+c+' = '+W+'.';}
else if(t===1){b=rnd(2,5);c=rnd(2,5);a=b*c+rnd(3,12); R=a-b*c; W=(a-b)*c; txt=a+' − '+b+' × '+c; s1=a+' − '+b+' = '+(a-b)+'.'; s2=(a-b)+' × '+c+' = '+W+'.';}
else {c=pick([2,3,4]);b=c*rnd(2,6);a=c*rnd(1,5); R=a+b/c; W=(a+b)/c; txt=a+' + '+b+' ÷ '+c; s1=a+' + '+b+' = '+(a+b)+'.'; s2=(a+b)+' ÷ '+c+' = '+W+'.';}
return thSpot({head:n[0]+' works out '+txt+'.',steps:['Work from left to right: '+s1,s2,'The answer is '+W+'.'],bad:0,right:R,wrong:W,distract:[R+1,R-1,a+b+c],
why:n[0]+' worked from left to right. Multiplication and division come before addition and subtraction. '+(t===0?b+' × '+c+' = '+b*c+' first, then '+a+' + '+b*c+' = '+R:t===1?b+' × '+c+' = '+b*c+' first, then '+a+' − '+b*c+' = '+R:b+' ÷ '+c+' = '+b/c+' first, then '+a+' + '+b/c+' = '+R)+'.'});},
pctChange:function(){var k=rnd(2,8),up=rnd(0,1),o=up?4*k*5:5*k*5,nw=up?5*k*5:4*k*5,diff=Math.abs(nw-o),n=thName(),R=up?25:20,W=up?20:25,it=pick(['A bag','A pair of sandals','A toy']);
return thSpot({head:it+' costs $'+o+' on Monday and $'+nw+' on Friday. '+n[0]+' works out the percentage '+(up?'increase':'decrease')+'.',steps:['Change = $'+Math.max(o,nw)+' − $'+Math.min(o,nw)+' = $'+diff+'.','Divide by the new price: '+diff+' ÷ '+nw+' × 100 = '+W+'%.','The percentage '+(up?'increase':'decrease')+' is '+W+'%.'],bad:1,right:R+'%',wrong:W+'%',distract:['10%','50%'],
why:'A percentage change is found by dividing by the old price, not the new price. '+diff+' ÷ '+o+' × 100 = '+R+'%.'});}
};
/* ---------- register P2 to P6 groups ---------- */
(function(){
function reg(lv,kick,list){var fns=list.map(function(p){var g=p[1]; return thT(p[0]+'@'+lv,function(){return g();});}); thWrap(lv,kick,fns);}
function fam(T,pre){return Object.keys(T).map(function(k){return [pre+'.'+k,T[k]];});}
reg(3,'Story problem',fam(TH_ST3,'story'));
reg(3,'Spot the mistake',fam(TH_SP2,'spot'));
reg(4,'Word problem',fam(TH_ST4,'word'));
reg(4,'Spot the mistake',fam(TH_SP4,'spot'));
reg(5,'Word problem',fam(TH_ST5,'word'));
reg(5,'Spot the mistake',fam(TH_SP5,'spot'));
reg(6,'Word problem',fam(TH_ST6,'word'));
reg(6,'Spot the mistake',fam(TH_SP6,'spot'));
thObj(3,'Story problems (add, take away, × and ÷, money, time, fractions)','Story problem');
thObj(3,'Spot the mistake: columns, fractions and time','Spot the mistake');
thObj(4,'Spot the mistake: carrying, remainders, rounding, time and area','Spot the mistake');
thObj(5,'Word problems','Word problem');
thObj(5,'Spot the mistake: fractions, decimals and multiplying','Spot the mistake');
thObj(6,'Word problems','Word problem');
thObj(6,'Spot the mistake: percentage, ratio, average and order of operations','Spot the mistake');
thTopic('num',['Word problem']);
})();
/* ===== THIN-CELL FILL (primary) END ===== */
function remDay(){return Math.floor((Date.now()-new Date().getTimezoneOffset()*6e4)/864e5);}
function remFmt(q){
if(q.wp)return 'word'; if(q.opts&&q.opts.length===2&&/^(true|false)$/i.test(String(q.opts[0])))return 'tf';
return (ansMode()||'mc')+(q.xt?'+':'');
}
function remLog(sk,q,ok){
var ev=sk.ev||(sk.ev=[]); ev.push([ok?1:0,remDay(),remFmt(q)+':'+(q.gi==null?'':q.gi)]); if(ev.length>24)ev.shift();
}
function remStrict(sk){
var ev=(sk&&sk.ev)||[]; if(ev.length=REM_DAYS&&Object.keys(v).length>=REM_VARS;
}
function remProgress(sk){
var ev=(sk&&sk.ev)||[], last=ev.slice(-REM_NEED), run=0, d={}, v={};
for(var i=ev.length-1;i>=0&&ev[i][0]&&run=0&&ev[i][0];i--)run++;
var win=ev.slice(ev.length-Math.min(run,REM_NEED)), d={}, v={}; win.forEach(function(e){d[e[1]]=1; v[e[2]]=1;});
var days=Object.keys(d).length, vars=Object.keys(v).length, need=[];
if(run0,run:run,days:days,vars:vars,need:need,strict:remStrict(sk)};
}
function remReadoutHtml(sk){
var o=remReadout(sk); if(!o.has)return 'No detail yet. Answer this skill again to start your count.';
var t=''+o.run+' correct in a row \u00B7 '+o.days+(o.days===1?' day':' days')+' \u00B7 '+o.vars+(o.vars===1?' format':' formats');
return t+(o.need.length?' Needs '+o.need.join(', ')+' to be mastered.':' All three are met.');
}
function remDiagnose(q,wrong,usedFrac){
var sk=S.sk[q.kicker]; if(!sk)return 'gap';
if(!wrong)return 'time';
var prior=sk.r.slice(-7,-1), okc=prior.filter(function(r){return r[0];}).length;
if(usedFrac<.2)return 'rushed';
if(prior.length>=4&&okc/prior.length>=.8)return 'slip';
return 'gap';
}
var REM_MSG={slip:'This looks like a slip. You usually get this right, so read it once more and try again.',
rushed:'You answered very fast. Slow down and check each step before you choose.',
gap:'This step is still new for you. We will practise it with a simpler one first, then come back.',
time:'Take your time. Showing your steps helps.'};
function remNote(q){return q&&q.dx?' '+REM_MSG[q.dx]:'';}
var BRG={};
function remBridge(lv,gi){
var key=lv+'|'+gi; if(key in BRG)return BRG[key];
var res=null; try{
var k=LEVELS[lv].gen[gi]().kicker, pre=PREREQ[k];
if(pre)for(var l=lv;l>=0&&!res;l--){var G=LEVELS[l].gen; for(var g=0;g(ex.need||1)){ex.need=need; ex.k=kind;} return;}
S.rev.push({l:q.lv,g:q.gi,k:kind||'slip',need:need}); if(S.rev.length>6)S.rev.shift();
}
/* ===== Step-by-step breakdowns for the gap path (additive block) =====
Hand-written 2 to 4 step teaching with a tiny worked example. Key = question kicker (the same key as S.sk and PREREQ).
remSteps(key) returns {title,steps} or null. If null, the gap path behaves exactly as before. */
var REM_STEPS={
"Add":[
"Adding numbers",
[
"Small numbers: start from the bigger number and count on. For 3 + 5, start at 5 and count on 3: 6, 7, 8. So 3 + 5 = 8.",
"Bigger numbers: line up the ones under the ones and the tens under the tens. Add the ones first.",
"If the ones make 10 or more, regroup: 10 ones are 1 ten. Write the leftover ones and add the new ten to the tens.",
"Example: 38 + 27. Ones: 8 + 7 = 15, which is 1 ten and 5 ones. Tens: 3 + 2 + 1 = 6 tens. So 38 + 27 = 65."
]
],
"Take away":[
"Taking away",
[
"Small numbers: start at the bigger number and count back. For 9 \u2212 4, count back 4 from 9: 8, 7, 6, 5. So 9 \u2212 4 = 5.",
"Bigger numbers: take away the ones first, then the tens.",
"If the ones on top are too few, regroup: take 1 ten and change it into 10 ones.",
"Example: 52 \u2212 27. 2 ones cannot take away 7 ones, so change 5 tens 2 ones into 4 tens 12 ones. Ones: 12 \u2212 7 = 5. Tens: 4 \u2212 2 = 2. So 52 \u2212 27 = 25. Check: 25 + 27 = 52."
]
],
"Make 10":[
"Number bonds to 10",
[
"A number bond splits a number into two parts. Here the whole is 10, and the two parts must add up to 10.",
"To find the missing part, count on from the part you know up to 10.",
"Example: 6 + ? = 10. Count on from 6: 7, 8, 9, 10. That is 4 steps, so the missing part is 4. Check: 10 \u2212 6 = 4."
]
],
"Make 20":[
"Number bonds to 20",
[
"The whole is 20. Find the missing part by counting on from the part you know, or by taking the part you know away from 20.",
"Example: 14 + ? = 20. Think of 20 as 10 + 10. 14 is 10 and 4 ones, so you still need 10 \u2212 4 = 6 more.",
"Count on to check: 15, 16, 17, 18, 19, 20. That is 6 steps. So 14 + 6 = 20."
]
],
"Place value":[
"Place value",
[
"Every digit has a value that depends on its place. In 47, the 4 is in the tens place, so it is worth 40. The 7 is in the ones place, so it is worth 7.",
"To build a number, write each part and add. 6 tens and 2 ones is 60 + 2 = 62.",
"Bigger numbers work the same way. 5 hundreds, 0 tens and 8 ones is 500 + 0 + 8 = 508. The 0 holds the empty tens place."
]
],
"Rounding":[
"Rounding",
[
"Find the digit in the place you are rounding to. Then look at the digit just to its right.",
"If that digit is 5 or more, round up. If it is 4 or less, keep the digit the same. Then change every digit to the right into 0.",
"Example: round 3462 to the nearest hundred. The hundreds digit is 4. The digit to its right is 6, so round up: 4 hundreds becomes 5 hundreds. The answer is 3500."
]
],
"Times tables":[
"Times tables",
[
"A times table fact is groups of the same size. 4 \u00d7 6 means 4 groups of 6. Skip count to check: 6, 12, 18, 24. So 4 \u00d7 6 = 24.",
"Build a hard fact from easy ones. Example: 7 \u00d7 6. You know 5 \u00d7 6 = 30 and 2 \u00d7 6 = 12. So 7 \u00d7 6 = 30 + 12 = 42.",
"For a division fact, think of the times table backwards. 56 \u00f7 8: what number times 8 makes 56? 7 \u00d7 8 = 56, so 56 \u00f7 8 = 7."
]
],
"Multiply":[
"Multiplying",
[
"Multiplying means adding equal groups. 3 \u00d7 4 is 4 + 4 + 4 = 12.",
"To multiply by 10, move every digit one place to the left. 7 \u00d7 10 = 70.",
"For a 2-digit number, split it into tens and ones, multiply each part, then add.",
"Example: 23 \u00d7 4. Tens: 20 \u00d7 4 = 80. Ones: 3 \u00d7 4 = 12. Add: 80 + 12 = 92."
]
],
"Divide":[
"Dividing",
[
"Dividing means sharing equally or making equal groups. It is the opposite of multiplying.",
"Think of the times table backwards. 45 \u00f7 5: what number times 5 makes 45? 9 \u00d7 5 = 45, so the answer is 9.",
"For a bigger number, take out a chunk you know first. Example: 78 \u00f7 6. 6 \u00d7 10 = 60, and 78 \u2212 60 = 18 is left. 18 \u00f7 6 = 3. So 78 \u00f7 6 = 10 + 3 = 13. Check: 13 \u00d7 6 = 78."
]
],
"Remainder":[
"Remainders",
[
"When things cannot be shared out equally, the leftover is called the remainder.",
"Find the biggest number in the times table that is not more than the number you are dividing. Then subtract to find what is left.",
"Example: 29 sweets are shared among 4 children. 4 \u00d7 7 = 28, and 4 \u00d7 8 = 32 is too big. Each child gets 7 and 29 \u2212 28 = 1 sweet is left over.",
"The remainder must always be smaller than the number you divide by."
]
],
"Missing number":[
"Finding the missing number",
[
"A missing number in a times sentence can be found by dividing. If ? \u00d7 6 = 42, then 42 \u00f7 6 gives the missing number.",
"Example: ? \u00d7 6 = 42. 42 \u00f7 6 = 7, so the missing number is 7.",
"Always check by multiplying: 7 \u00d7 6 = 42. It works."
]
],
"Half & quarter":[
"Halves, quarters and doubles",
[
"A half is 1 of 2 equal parts. A quarter is 1 of 4 equal parts.",
"Half of a number: share it into 2 equal groups. Half of 14 is 7, because 7 + 7 = 14.",
"A quarter is half of a half. Example: a quarter of 12. Half of 12 is 6, and half of 6 is 3. So a quarter of 12 is 3, because 4 \u00d7 3 = 12.",
"Double means 2 times a number. Double 6 is 6 + 6 = 12."
]
],
"Like fractions":[
"Fractions with the same denominator",
[
"The bottom number (denominator) tells how many equal parts the whole is cut into. The top number (numerator) tells how many parts you have.",
"When the denominators are the same, add only the numerators and keep the denominator. Example: 2/9 + 4/9 = 6/9.",
"To compare two fractions with 1 on top, look at the denominator. More parts means each part is smaller. Example: 1/8 is smaller than 1/5, so 1/5 is bigger."
]
],
"Fractions":[
"Adding and subtracting fractions",
[
"If the denominators are the same, add or subtract only the numerators. The denominator stays the same.",
"Example: 3/10 + 4/10 = 7/10, because 3 + 4 = 7. And 9/10 \u2212 4/10 = 5/10, because 9 \u2212 4 = 5.",
"To write a fraction in simplest form, divide the top and bottom by the same number. 5/10 \u00f7 5 on both = 1/2."
]
],
"Equivalent fractions":[
"Equivalent fractions",
[
"Equivalent fractions show the same amount. To make one, multiply the top and the bottom by the same number.",
"Find what the bottom was multiplied by: divide the new bottom by the old bottom.",
"Example: 3/4 = ?/20. 20 \u00f7 4 = 5, so multiply the top by 5 too: 3 \u00d7 5 = 15. So 3/4 = 15/20.",
"To simplify, divide the top and bottom by the same number instead. 12/16 \u00f7 4 on both = 3/4."
]
],
"Fraction of a set":[
"Fraction of a set",
[
"Draw a bar model. Cut the bar into as many equal parts as the denominator. The whole bar is the total.",
"Divide the total by the denominator to find the value of 1 part. Then multiply by the numerator to find the parts you need.",
"Example: 3/4 of 24. 24 \u00f7 4 = 6 for 1 part. 3 parts is 3 \u00d7 6 = 18. So 3/4 of 24 = 18."
]
],
"Improper fractions":[
"Improper fractions and mixed numbers",
[
"To change a mixed number into an improper fraction: whole number \u00d7 denominator, then add the numerator. Keep the same denominator.",
"Example: 3 2/5. 3 \u00d7 5 = 15, and 15 + 2 = 17. So 3 2/5 = 17/5.",
"To go back, divide the top by the bottom. 17 \u00f7 5 = 3 remainder 2, so 17/5 = 3 2/5."
]
],
"Mixed numbers":[
"Improper fractions to mixed numbers",
[
"An improper fraction has a top that is bigger than or equal to the bottom. A mixed number has a whole number and a fraction.",
"Divide the top by the bottom. The answer is the whole number. The remainder is the new top. The bottom stays the same.",
"Example: 11/4. 11 \u00f7 4 = 2 remainder 3. So 11/4 = 2 3/4. Check: 2 \u00d7 4 + 3 = 11."
]
],
"Fraction \u00d7":[
"Multiplying fractions",
[
"To multiply a fraction by a whole number, multiply the numerator only. Example: 3/5 \u00d7 4 = 12/5, which is 2 2/5.",
"To multiply two fractions, multiply the tops together and the bottoms together. Example: 2/3 \u00d7 3/4 = 6/12.",
"Then simplify. 6/12 \u00f7 6 on both = 1/2."
]
],
"Fraction \u00f7":[
"Dividing fractions",
[
"Dividing a fraction by a whole number makes each part smaller. Multiply the denominator by that number. Example: 1/3 \u00f7 2 = 1/6, because 3 \u00d7 2 = 6.",
"Dividing a whole number by a unit fraction asks how many of those parts fit in. Example: 3 \u00f7 1/4. Each whole has 4 quarters, so 3 wholes have 3 \u00d7 4 = 12 quarters. The answer is 12.",
"When both fractions have the same denominator, divide the numerators. Example: 8/5 \u00f7 2/5 = 8 \u00f7 2 = 4."
]
],
"Decimals":[
"Adding and subtracting decimals",
[
"Line up the decimal points, so tenths are under tenths and ones are under ones. Add or subtract the tenths first.",
"Example: 4.6 + 2.7. Tenths: 6 + 7 = 13 tenths, which is 1 one and 3 tenths. Ones: 4 + 2 + 1 = 7. So 4.6 + 2.7 = 7.3.",
"Example: 5.2 \u2212 1.8. 2 tenths is too few, so change 5 ones 2 tenths into 4 ones 12 tenths. Tenths: 12 \u2212 8 = 4. Ones: 4 \u2212 1 = 3. So 5.2 \u2212 1.8 = 3.4."
]
],
"Decimal \u00d7\u00f7":[
"Multiplying and dividing decimals",
[
"Multiply or divide as if there were no decimal point. Then put the decimal point back so the answer has the same number of decimal places as the decimal you started with.",
"Example: 2.4 \u00d7 3. 24 \u00d7 3 = 72. 2.4 has 1 decimal place, so the answer has 1 decimal place: 7.2.",
"Example: 9.6 \u00f7 4. 96 \u00f7 4 = 24. Put back 1 decimal place: 2.4. Check: 2.4 \u00d7 4 = 9.6."
]
],
"Fraction to decimal":[
"Fractions to decimals",
[
"Tenths have 1 decimal place and hundredths have 2. So 6/10 = 0.6 and 6/100 = 0.06.",
"If the denominator is not 10 or 100, make an equivalent fraction. Example: 3/5. Multiply top and bottom by 2 to get 6/10, which is 0.6.",
"Example: 7/25. Multiply top and bottom by 4 to get 28/100, which is 0.28. Or divide: the top divided by the bottom, like 3 \u00f7 4 = 0.75, so 3/4 = 0.75."
]
],
"Percentage":[
"Percentage of a number",
[
"Percent means out of 100. Remember 50% is half, 25% is a quarter and 10% is one tenth.",
"Find 10% by dividing by 10. Then build the percentage you need from 10%.",
"Example: 30% of 140. 10% of 140 = 14. 30% is 3 times that: 3 \u00d7 14 = 42.",
"Example: 25% of 80 = a quarter of 80 = 20."
]
],
"Ratio":[
"Ratio with a bar model",
[
"Draw one box for each part of the ratio. Add the parts to find how many equal parts the whole has.",
"Divide the total by the number of parts to find the value of 1 part. Then multiply by the parts you need.",
"Example: boys to girls is 2 : 3 and there are 35 children. 2 + 3 = 5 parts. 35 \u00f7 5 = 7 for 1 part. Boys: 2 \u00d7 7 = 14. Girls: 3 \u00d7 7 = 21. Check: 14 + 21 = 35."
]
],
"Average":[
"Average",
[
"Add up all the numbers. Then divide by how many numbers there are.",
"Example: the average of 4, 7, 9 and 12. Total: 4 + 7 + 9 + 12 = 32. There are 4 numbers. 32 \u00f7 4 = 8. The average is 8."
]
],
"Perimeter":[
"Perimeter",
[
"Perimeter is the distance all the way around a shape. Add the lengths of all the sides. The unit is cm or m, not square units.",
"A rectangle has 2 long sides and 2 short sides. Perimeter = 2 \u00d7 (length + width).",
"Example: a rectangle 12 cm long and 5 cm wide. 12 + 5 = 17. 2 \u00d7 17 = 34. The perimeter is 34 cm. Check: 12 + 5 + 12 + 5 = 34."
]
],
"Area":[
"Area of a rectangle",
[
"Area is the amount of flat space inside a shape. We count it in square units, like cm\u00b2.",
"For a rectangle, Area = length \u00d7 width.",
"Example: a rectangle 9 cm long and 6 cm wide. Area = 9 \u00d7 6 = 54 cm\u00b2. A square with side 7 cm has area 7 \u00d7 7 = 49 cm\u00b2."
]
],
"Composite figures":[
"Composite figures",
[
"A composite figure is made of simple shapes joined together. Split it into rectangles (or triangles) you know.",
"Find the area of each part, then add them. If part of the figure is cut out, subtract it.",
"Example: an L-shape made of a rectangle 8 cm by 3 cm and a rectangle 4 cm by 2 cm. Areas: 8 \u00d7 3 = 24 and 4 \u00d7 2 = 8. Total: 24 + 8 = 32 cm\u00b2.",
"A semicircle is half a circle. With \u03c0 = 22/7 and radius 7 cm: half of 22/7 \u00d7 7 \u00d7 7 = half of 154 = 77 cm\u00b2."
]
],
"Area of a triangle":[
"Area of a triangle",
[
"The height is the straight line from the top corner down to the base, at a right angle to the base.",
"A triangle is half of a rectangle with the same base and height. Area = \u00bd \u00d7 base \u00d7 height.",
"Example: base 10 cm and height 6 cm. 10 \u00d7 6 = 60. Half of 60 = 30. The area is 30 cm\u00b2."
]
],
"Angles":[
"Finding unknown angles",
[
"Know the three facts: angles on a straight line add up to 180\u00b0, angles at a point add up to 360\u00b0, and angles in a triangle add up to 180\u00b0.",
"Add the angles you know, then subtract the total from 180\u00b0 or 360\u00b0.",
"Example (straight line): one angle is 65\u00b0. 180 \u2212 65 = 115\u00b0.",
"Example (triangle): two angles are 50\u00b0 and 60\u00b0. 50 + 60 = 110. 180 \u2212 110 = 70\u00b0."
]
],
"Money":[
"Money and change",
[
"100 cents make $1. A price like $6.40 means 6 dollars and 40 cents.",
"Change = money you pay \u2212 cost. A simple way is to count up from the cost to the money you paid.",
"Example: you pay $10 for something that costs $6.40. From $6.40 to $7 is 60 cents. From $7 to $10 is $3. Change = $3 + 60 cents = $3.60. Check: 10 \u2212 6.40 = 3.60."
]
],
"Coins":[
"Counting coins",
[
"Know the coins: 5 cents, 10 cents, 20 cents, 50 cents and $1 (which is 100 cents).",
"Start with the biggest coin and count on. Example: 50 cents, 20 cents, 10 cents and 5 cents. 50, 70, 80, 85. The total is 85 cents.",
"To change dollars to cents, multiply by 100. Example: $2 and 30 cents is 200 + 30 = 230 cents."
]
],
"Telling time":[
"Telling time on a clock",
[
"The short hand shows the hour. The long hand shows the minutes.",
"The long hand counts in fives: at 1 it is 5 minutes, at 2 it is 10, at 3 it is 15, at 6 it is 30, at 9 it is 45.",
"Example: the short hand is just past 4, and the long hand points at 7. 7 \u00d7 5 = 35, so the time is 4:35. That is also 25 minutes to 5, because 60 \u2212 35 = 25."
]
],
"Time":[
"Time: duration and finishing time",
[
"To find the finishing time, add the hours first, then the minutes. Remember that 60 minutes make 1 hour. Passing 12 noon changes am to pm.",
"Example: a film starts at 10:20 am and lasts 2 h 50 min. Add 2 h: 12:20 pm. Add 40 min: 1:00 pm. Add the last 10 min: 1:10 pm.",
"To find how long, count up in jumps. Example: 9:40 am to 12:10 pm. 9:40 to 10:00 is 20 min. 10:00 to 12:00 is 2 h. 12:00 to 12:10 is 10 min. Total: 2 h 30 min."
]
],
"Units":[
"Changing units",
[
"Know the links: 1 m = 100 cm, 1 km = 1000 m, 1 kg = 1000 g, 1 \u2113 = 1000 m\u2113, 1 h = 60 min.",
"Changing to a smaller unit: multiply. 6 m = 6 \u00d7 100 = 600 cm.",
"Changing to a bigger unit: divide. 4500 g = 4500 \u00f7 1000 = 4.5 kg.",
"Mixed units: change each part. 2 h 20 min = 2 \u00d7 60 + 20 = 120 + 20 = 140 min."
]
],
"Order of operations":[
"Order of operations",
[
"Work in this order: brackets first, then \u00d7 and \u00f7, then + and \u2212. Go from left to right when the signs are at the same level.",
"Example: 20 \u2212 3 \u00d7 4 + 5. Multiply first: 3 \u00d7 4 = 12. Then 20 \u2212 12 = 8. Then 8 + 5 = 13."
]
],
"Rate":[
"Rate",
[
"A rate tells how much for 1 unit, like $4 per kg or 8 toys every minute.",
"To find the total, multiply the rate by the number of units. $4 per kg for 6 kg: 6 \u00d7 4 = $24.",
"To find how many units, divide the total by the rate. 72 toys at 8 toys per minute: 72 \u00f7 8 = 9 minutes."
]
],
"Discount":[
"Discount",
[
"A discount is the amount taken off the price. Sale price = original price \u2212 discount.",
"Find the discount with percentages. Example: $90 with 20% off. 10% of 90 = 9, so 20% = 18.",
"Sale price = 90 \u2212 18 = $72."
]
],
"GST":[
"GST",
[
"GST is a tax added to the price. In Singapore it is 9%. Price with GST = price + GST.",
"Example: $200 before GST. 1% of 200 = 2, so 9% = 9 \u00d7 2 = 18.",
"Price with GST = 200 + 18 = $218."
]
],
"Interest":[
"Simple interest",
[
"Simple interest is the same amount every year. Interest for 1 year = rate % of the money in the bank.",
"Example: $300 at 2% per year for 3 years. 1% of 300 = 3, so 2% = 6 each year.",
"For 3 years: 3 \u00d7 6 = $18."
]
],
"Word problem":[
"Word problems with a bar model",
[
"Read the question twice. Underline what you know, and circle what you must find.",
"Draw a bar model for the story. Put the numbers you know on the bars and a ? where you must find the answer.",
"Work out one step at a time. Example: Ali has 52 stickers. He gives 3 friends 6 stickers each. First, 3 \u00d7 6 = 18 stickers given away. Then 52 \u2212 18 = 34 stickers left."
]
],
"Two-step problem":[
"Two-step problems",
[
"A two-step problem needs two calculations. Find the first part, then use it to find the second.",
"Draw a bar model for the whole. Mark each part that is spent or used.",
"Example: Sam had $36. He spent 1/4 on a toy and 1/3 on food. Toy: 36 \u00f7 4 = $9. Food: 36 \u00f7 3 = $12. Spent: 9 + 12 = $21. Left: 36 \u2212 21 = $15."
]
],
"Story problem":["Solving a story problem", ["Read the story twice. Find the question and underline what you must find.", "Circle the numbers and the key words. 'In all' or 'altogether' usually means add. 'Left' or 'fewer' usually means take away. 'Each' group means multiply. 'Share equally' means divide.", "Draw a quick picture or bar, then write a number sentence.", "Example: Mei Ling has 8 stickers and gets 5 more. 'Gets more' means add: 8 + 5 = 13. She has 13 stickers. Check that the answer makes sense and write the unit."]],
"Equal groups":["Equal groups", ["Equal groups means every group has the same number.", "Count how many groups there are and how many are in each group.", "To find the total, multiply: groups × number in each group.", "Example: 3 plates with 4 curry puffs on each plate. 3 × 4 = 12 curry puffs. To share 12 equally among 3 plates, divide: 12 ÷ 3 = 4 on each plate."]],
"Algebra":["Letters for numbers", ["A letter stands for a number we do not know yet.", "Write words as maths. 'A number plus 5' is n + 5. '3 times a number' is 3n. We write 3n, not 3 × n.", "To find the value, put the number in for the letter and follow the order of operations: multiply first, then add.", "Example: if n = 4, then 3n + 5 = 3 × 4 + 5 = 12 + 5 = 17."]],
"Equation":["Solving an equation", ["An equation is like a balance. Whatever you do to one side, do to the other side.", "Undo adding or subtracting first. Then undo multiplying or dividing.", "Example: 3x + 5 = 20. Take away 5 from both sides: 3x = 15. Divide both sides by 3: x = 5.", "Check: 3 × 5 + 5 = 20. It matches, so x = 5."]],
"Simplify":["Simplifying expressions", ["Like terms have the same letter part. 3x and 5x are like terms. 3x and 5 are not.", "Add or subtract only the numbers in front of like terms.", "Example: 3x + 2y + 5x − y. The x terms: 3x + 5x = 8x. The y terms: 2y − y = y. The answer is 8x + y.", "To expand a bracket, multiply what is outside by every term inside: 2(x + 3) = 2x + 6."]],
"Speed":["Speed, distance and time", ["speed = distance ÷ time. distance = speed × time. time = distance ÷ speed.", "Check the units match. Use km with hours, or m with seconds. Change minutes to hours first: 30 min = 0.5 h.", "Example: 150 km in 2 hours. Speed = 150 ÷ 2 = 75 km/h.", "Example: 60 km/h for 3 hours. Distance = 60 × 3 = 180 km."]],
"Volume":["Volume of a cuboid", ["Volume is the space inside a solid. We measure it in cubic units, such as cm³.", "For a cuboid: volume = length × width × height.", "Example: 5 cm by 3 cm by 2 cm. The base is 5 × 3 = 15 cm². Then 15 × 2 = 30 cm³.", "For any prism, volume = area of the base × height."]],
"Circle":["Circles", ["The diameter is twice the radius: d = 2r.", "Circumference = π × d, or 2 × π × r. Area = π × r × r. Use the value of π that the question gives, such as 3.14 or 22/7.", "Example: r = 7 cm and π = 22/7. Circumference = 2 × 22/7 × 7 = 44 cm. Area = 22/7 × 7 × 7 = 154 cm².", "Area uses square units. Circumference uses plain length units."]],
"Negative numbers":["Negative numbers", ["On a number line, numbers to the left of 0 are negative. The further left, the smaller the number.", "Adding moves right. Subtracting moves left.", "Example: −3 + 5. Start at −3 and move 5 right. The answer is 2. Example: −2 − 4. Start at −2 and move 4 left. The answer is −6.", "Subtracting a negative is the same as adding: 5 − (−2) = 5 + 2 = 7."]],
"Indices":["Powers (indices)", ["A power tells us how many times to multiply a number by itself. 2³ = 2 × 2 × 2 = 8.", "Same base, multiply: add the powers. 2³ × 2⁴ = 2⁷.", "Same base, divide: subtract the powers. 5⁶ ÷ 5² = 5⁴.", "Any number to the power 0 is 1. A power of a power: multiply the powers, (2³)² = 2⁶."]],
"Quadratic":["Factorising a quadratic", ["Write it as x² + bx + c.", "Find two numbers that multiply to c and add to b.", "Example: x² + 5x + 6. The numbers 2 and 3 multiply to 6 and add to 5. So x² + 5x + 6 = (x + 2)(x + 3).", "Check by expanding: x² + 3x + 2x + 6 = x² + 5x + 6. To solve (x + 2)(x + 3) = 0, set each bracket to 0: x = −2 or x = −3."]],
"Quadratic (rearranged)":["Rearrange first", ["Move every term to one side so the other side is 0.", "Example: x² = 5x − 6 becomes x² − 5x + 6 = 0.", "Factorise. Two numbers multiply to 6 and add to −5: −2 and −3. So (x − 2)(x − 3) = 0.", "Set each bracket to 0: x = 2 or x = 3. Check in the original: 2² = 4 and 5 × 2 − 6 = 4."]],
"Quadratic formula":["The quadratic formula", ["Write the equation as ax² + bx + c = 0. Note a, b and c with their signs.", "The formula is x = (−b ± √(b² − 4ac)) ÷ 2a.", "Example: x² + 3x − 2 = 0, so a = 1, b = 3, c = −2. b² − 4ac = 9 + 8 = 17. So x = (−3 ± √17) ÷ 2, which is about 0.56 or −3.56.", "If b² − 4ac is negative, there are no real solutions."]],
"Completing the square":["Completing the square", ["For x² + bx, take half of b and square it.", "Example: x² + 6x + 1. Half of 6 is 3, and (x + 3)² = x² + 6x + 9.", "So x² + 6x + 1 = (x + 3)² − 9 + 1 = (x + 3)² − 8.", "Check by expanding: x² + 6x + 9 − 8 = x² + 6x + 1. To solve it = 0: (x + 3)² = 8, so x = −3 ± √8."]],
"Quadratic graphs":["Quadratic graphs", ["The graph is a smooth U shape. If the x² term is positive, it opens upward and has a lowest point. If negative, it opens downward and has a highest point.", "The roots are where the graph crosses the x-axis, where y = 0. The y-intercept is the constant term.", "The turning point is halfway between the two roots.", "Example: y = x² − 4x + 3 = (x − 1)(x − 3). Roots 1 and 3. Halfway is x = 2, and y = 4 − 8 + 3 = −1. The turning point is (2, −1)."]],
"Trigonometry":["Trigonometry in a right-angled triangle", ["Label the sides from the angle you are using. The hypotenuse is opposite the right angle. Then name the opposite side and the adjacent side.", "Remember SOH CAH TOA: sin = opposite ÷ hypotenuse, cos = adjacent ÷ hypotenuse, tan = opposite ÷ adjacent.", "Choose the ratio that uses the side you know and the side you want.", "Example: angle 30°, hypotenuse 10 cm, find the opposite side. sin 30° = opposite ÷ 10, so opposite = 10 × 0.5 = 5 cm. Keep the calculator in degree mode."]],
"Trig: sides and angles":["Finding a side or an angle", ["To find a side, write the ratio and solve. Example: tan 40° = x ÷ 8, so x = 8 × tan 40°, which is about 6.71.", "To find an angle, use the inverse key. Example: opposite 3 and hypotenuse 5. sin θ = 3 ÷ 5 = 0.6, so θ = sin⁻¹(0.6), which is about 36.9°.", "Check that your answer is sensible: the hypotenuse is always the longest side."]],
"Pythagoras":["Pythagoras' theorem", ["It works only in right-angled triangles. The hypotenuse c is opposite the right angle: a² + b² = c².", "To find the hypotenuse, add the squares and take the square root. Example: sides 3 and 4. 9 + 16 = 25, so c = 5.", "To find a shorter side, subtract. Example: hypotenuse 13 and one side 5. 169 − 25 = 144, so the other side is 12.", "The hypotenuse must always be the longest side."]],
"Symmetry":["Lines of symmetry", ["A line of symmetry cuts a shape into two halves that match exactly. One half is a mirror picture of the other.", "Imagine folding the shape along the line. If the two halves sit exactly on top of each other, it is a line of symmetry.", "Example: a rectangle has 2 lines of symmetry, one across the middle and one down the middle. A square has 4.", "A line that cuts the shape into two different-looking parts is not a line of symmetry."]],
"Bar graph":["Reading a bar graph", ["Read the label under each bar to see what it shows. Then look at the numbers on the side to see how many.", "Find the top of the bar. Look straight across to the number on the side. That is the value.", "To find the total, add up the value of every bar. To find how many more, subtract the smaller value from the bigger one.", "Example: 8 children chose red and 5 chose blue. Altogether is 8 + 5 = 13. How many more chose red is 8 − 5 = 3."]],
"Statistics":["Mean, median, mode and range", ["Mode is the number that appears most often. Range is the biggest number minus the smallest number.", "Median is the middle number when the numbers are in order, smallest to biggest. With two middle numbers, find the number halfway between them.", "Mean is the total of all the numbers divided by how many numbers there are.", "Example: 2, 4, 4, 6, 9. Mode 4. Range 9 − 2 = 7. Median 4. Mean (2 + 4 + 4 + 6 + 9) ÷ 5 = 25 ÷ 5 = 5."]],
"Symmetry (line and rotation)":["Line and rotational symmetry", ["Line symmetry: if you fold the shape along a line, the two halves match exactly. That line is a line of symmetry.", "Rotational symmetry: turn the shape about its centre. The order is how many times it looks the same in one full turn.", "Examples: a rectangle (not a square) has 2 lines of symmetry and rotational order 2. An equilateral triangle has 3 lines and order 3. A square has 4 lines and order 4.", "Use tracing paper to check if you are not sure."]],
"Constructions":["Compass and ruler constructions", ["Perpendicular bisector of AB: open the compasses to more than half of AB. Draw arcs above and below from A. With the same setting, draw arcs from B. Join the two crossing points.", "Angle bisector: with the compass point at the vertex, draw an arc across both arms. From each crossing point draw an arc of the same size so they meet. Join the vertex to where they meet.", "Keep the compass setting the same for each pair of arcs, and keep your construction lines."]],
"Data displays":["Choosing and reading data displays", ["Bar graph: compare categories. Pie chart: parts of a whole. Line graph: change over time. Pictogram: read the key first.", "Always read the title, the axis labels and the scale before you read any value.", "Example: a bar ends halfway between 20 and 30, so its value is 25.", "Check your reading: the parts of a pie chart add up to 360°, and the parts of a total add up to the total."]],
"Histograms":["Histograms", ["A histogram has bars that touch. The bottom axis is a number scale, split into class intervals.", "If every class is the same width, the bar height is the frequency.", "If the classes have different widths, use frequency density = frequency ÷ class width. The area of each bar is the frequency.", "Example: class 20 to 40 has frequency 30. Class width 20, so frequency density = 30 ÷ 20 = 1.5."]],
"Stem-and-leaf":["Stem-and-leaf diagrams", ["The stem is the tens digit. The leaf is the ones digit. Always write a key.", "Put the leaves in order from smallest to largest in every row.", "Example: 12, 15, 21, 27, 27, 34 gives 1 | 2 5, then 2 | 1 7 7, then 3 | 4. Key: 2 | 1 means 21.", "The mode is the most common value, here 27. The median is the middle: with 6 values, take the average of the 3rd and 4th, (21 + 27) ÷ 2 = 24."]],
"Box plots":["Box plots", ["A box plot uses five numbers: minimum, lower quartile, median, upper quartile and maximum.", "Put the data in order. The median is the middle value. The lower quartile is the median of the lower half. The upper quartile is the median of the upper half.", "Example: 2, 4, 6, 8, 10, 12, 14. The median is 8. The lower half 2, 4, 6 has median 4. The upper half 10, 12, 14 has median 12.", "Draw a box from 4 to 12 with a line at 8, and lines out to 2 and 14. The interquartile range is 12 − 4 = 8."]],
"Cumulative frequency":["Cumulative frequency", ["Cumulative frequency is a running total of the frequencies.", "Plot each total at the upper end of its class, then join the points with a smooth curve.", "Example: frequencies 5, 8, 7 give cumulative totals 5, 13, 20.", "To find the median, go to half of the total (20 ÷ 2 = 10), read across to the curve, then down to the axis. The quartiles are at one quarter and three quarters of the total."]],
"Standard deviation":["Standard deviation", ["Standard deviation tells us how spread out the data is around the mean. A bigger number means more spread.", "Steps: find the mean. Subtract the mean from each value and square the results. Find the mean of those squares. Take the square root.", "Example: 2, 4, 6. The mean is 4. The differences are −2, 0, 2 and their squares are 4, 0, 4. The mean of the squares is 8 ÷ 3, about 2.67. The square root is about 1.63.", "Check your result with the calculator's standard deviation key."]]
};
function remSteps(skillKey){var s=Object.prototype.hasOwnProperty.call(REM_STEPS,skillKey)?REM_STEPS[skillKey]:null; return s?{title:s[0],steps:s[1]}:null;}
(function(){
/* remember which skill was missed, so the steps teach that skill even when the warm-up is a simpler one */
var _addRev=addRev;
addRev=function(q,kind){
_addRev(q,kind);
try{if(kind==='gap'&&q&&!q.wp){S.rev.forEach(function(x){if(x.l===q.lv&&x.g===q.gi){x.kk=q.kicker; x.stp=0;}});}}catch(e){}
};
/* mark a question that is the first of a gap review so the panel shows before it */
var _gen=gen;
gen=function(){
var q=_gen();
try{
if(q&&q.rv&&mode==='play'&&!q.stepKey){
var o=q.bridge||{l:q.lv,g:q.gi}, e=null; S.rev.forEach(function(x){if(x.l===o.l&&x.g===o.g)e=x;});
if(e&&e.k==='gap'&&!e.stp){
var key=(e.kk&&remSteps(e.kk))?e.kk:(remSteps(q.kicker)?q.kicker:null);
if(key){q.stepKey=key; q.stepEntry=e;}
}
}
}catch(err){}
return q;
};
function stepsClose(){var p=document.getElementById('remStepPanel'); if(p&&p.parentNode)p.parentNode.removeChild(p); try{document.getElementById('quizPanel').classList.remove('remstep');}catch(e){}}
function stepsShow(q){
var info=remSteps(q.stepKey); if(!info)return;
var quiz=document.getElementById('quizPanel'), anchor=document.getElementById('timer'); if(!quiz||!anchor||!anchor.parentNode)return;
var i=0, n=info.steps.length, p=document.createElement('section'); p.id='remStepPanel'; p.setAttribute('aria-label','Step by step help');
p.innerHTML='
Let’s learn it step by step
';
anchor.parentNode.insertBefore(p,anchor); quiz.classList.add('remstep');
stopTimer(); locked2=true;
var last=q.bridge?'Try the warm-up':'Try the question';
function draw(){
p.querySelector('#rsTitle').textContent=info.title;
p.querySelector('#rsCount').textContent='Step '+(i+1)+' of '+n;
p.querySelector('#rsText').textContent=info.steps[i];
p.querySelector('#rsBack').hidden=(i===0);
p.querySelector('#rsNext').textContent=(i===n-1)?last:'Next';
}
p.querySelector('#rsBack').addEventListener('click',function(){if(i>0){i--; draw();}});
p.querySelector('#rsNext').addEventListener('click',function(){
if(i=6&&c>=5&&(!lim||avgMs<=lim*.6))return 'mastered';
if((last.length>=6&&c>=5)||(c===last.length&&last.length>=3))return 'secure';
return 'learning';
}
function skillEntry(k,src){
var sk=(src||S.sk)[k], last=sk.r.slice(-6).filter(function(r){return r[0];});
var avg=last.length?last.reduce(function(a,r){return a+r[1];},0)/last.length:0;
return [k,sk.n,sk.c,sk.r.map(function(r){return r[0];}).join(''),Math.round(avg),sk.lim||0,sk.xt||0,(src?-1:(remStrict(sk)?1:0))];
}
/* masteredCount keeps the quicker 'Secure to Mastered' rule: it still unlocks land and crafting. Stars use the strict rule. */
function starCount(){return Object.keys(S.st.stars||{}).length;}
function masteredCount(){
return Object.keys(S.sk).filter(function(k){var e=skillEntry(k); return statusOf(e[1],e[3],e[4],e[5])==='mastered';}).length;
}
function buildReport(classOnly){
var HS=classOnly?(S.cs&&S.cs.h||{}):S.hist, SK=classOnly?(S.cs&&S.cs.sk||{}):S.sk, h={}; for(var i=0;i<(classOnly?56:14);i++){var d=dkey(i); if(HS[d])h[d]=[HS[d].a,HS[d].c,HS[d].t,HS[d].r|0];}
return {v:1,n:S.me.name||'Guest',c:S.me.cls||'',ts:Date.now(),cbt:(S.cb&&S.cb.t)||0,cbn:(S.cb&&S.cb.n)||0,l:S.level,a:classOnly?(S.cs&&S.cs.a||0):S.st.a,f:classOnly?(S.cs&&S.cs.f||0):S.st.f,rf:classOnly?(S.cs&&S.cs.rf||0):0,to:classOnly?(S.cs&&S.cs.to||0):0,fx:classOnly?0:(S.st.fx||0),z:classOnly?0:(S.st.z||0),sk:Object.keys(SK).map(function(k){return skillEntry(k,SK);}),stars:Object.keys(S.st.stars||{}).filter(function(k){return !classOnly||SK[k];}).length,h:h,ss:(S.ss||[]).slice(-6)};
}
function explainText(q){
if(q.why)return q.why;
var t=q.text, n=(t.match(/\d+(\.\d+)?/g)||[]).map(Number), a=q.answer, m;
try{
switch(q.kicker){
case 'Add': if(n[1]<10)return 'Start at '+n[0]+' and count on '+n[1]+'. You land on '+a+'.'; return 'Add the tens, then the ones: '+n[0]+' + '+(n[1]-n[1]%10)+' = '+(n[0]+n[1]-n[1]%10)+', then + '+(n[1]%10)+' = '+a+'.';
case 'Take away': if(n[1]<10)return 'Start at '+n[0]+' and count back '+n[1]+'. You land on '+a+'.'; return 'Take away the tens, then the ones: '+n[0]+' − '+(n[1]-n[1]%10)+' = '+(n[0]-n[1]+n[1]%10)+', then − '+(n[1]%10)+' = '+a+'.';
case 'Multiply': var big=Math.max(n[0],n[1]), sm=Math.min(n[0],n[1]); if(big<=10)return 'Use your times table: '+n[0]+' × '+n[1]+' = '+a+'. Think of '+sm+' equal groups of '+big+'.'; return 'Split the bigger number into tens and ones: ('+(big-big%10)+' × '+sm+') + ('+(big%10)+' × '+sm+') = '+((big-big%10)*sm)+' + '+((big%10)*sm)+' = '+a+'.';
case 'Divide': return 'Think of the times table backwards: '+a+' × '+n[1]+' = '+n[0]+', so '+n[0]+' ÷ '+n[1]+' = '+a+'.';
case 'Missing number': return 'Ask: what times '+n[0]+' makes '+n[1]+'? Divide: '+n[1]+' ÷ '+n[0]+' = '+a+'.';
case 'Place value': return n[0]+' tens is '+(n[0]*10)+'. Add '+n[1]+' ones to get '+a+'.';
case 'Fractions': m=t.match(/(\d+)\/(\d+) \+ (\d+)\/(\d+)/); return 'The bottom numbers are the same, so keep '+m[2]+' on the bottom and add only the tops: '+m[1]+' + '+m[3]+' = '+(+m[1]+ +m[3])+'. The answer is '+a+'.';
case 'Fraction of a set': m=t.match(/(\d+)\/(\d+) of (\d+)/); return 'Divide by the bottom number first: '+m[3]+' ÷ '+m[2]+' = '+(m[3]/m[2])+'. Then multiply by the top number: '+(m[3]/m[2])+' × '+m[1]+' = '+a+'.';
case 'Equivalent fractions': m=t.match(/(\d+)\/(\d+) = \?\/(\d+)/); return 'The bottom went from '+m[2]+' to '+m[3]+', so it was multiplied by '+(m[3]/m[2])+'. Do the same to the top: '+m[1]+' × '+(m[3]/m[2])+' = '+a+'.';
case 'Decimals': return 'Line up the decimal points. Add or subtract the tenths like whole numbers, then put the decimal point back. The answer is '+a+'.';
case 'Perimeter': return 'Perimeter = 2 × (length + width) = 2 × ('+n[0]+' + '+n[1]+') = 2 × '+(n[0]+n[1])+' = '+a+' cm.';
case 'Area': return 'Area = length × width = '+n[0]+' × '+n[1]+' = '+a+' cm².';
case 'Area of a triangle': return 'Area of a triangle = ½ × base × height = ½ × '+n[0]+' × '+n[1]+' = '+a+' cm².';
case 'Multiples': return 'A multiple of '+n[0]+' is in the '+n[0]+' times table. Check each number: '+n[0]+' × '+(a/n[0])+' = '+a+'.';
case 'Percentage': return 'Percent means out of 100. '+n[0]+'% of '+n[1]+' = '+n[0]+' ÷ 100 × '+n[1]+' = '+a+'. Tip: 10% is the number ÷ 10.';
case 'Ratio': return 'Add the parts: '+n[0]+' + '+n[1]+' = '+(n[0]+n[1])+' parts. '+n[2]+' ÷ '+(n[0]+n[1])+' = '+(n[2]/(n[0]+n[1]))+' in each part. Red has '+n[0]+' parts: '+n[0]+' × '+(n[2]/(n[0]+n[1]))+' = '+a+'.';
case 'Average': var tot=n.reduce(function(x,y){return x+y;},0); return 'Add all the numbers, then divide by how many there are: '+tot+' ÷ '+n.length+' = '+a+'.';
case 'Order of operations': return 'Multiply first: '+n[1]+' × '+n[2]+' = '+(n[1]*n[2])+'. Then work left to right: '+n[0]+' + '+(n[1]*n[2])+' − '+n[3]+' = '+a+'.';
case 'Angles': if(t.indexOf('straight')>=0)return 'Angles on a straight line add up to 180°. Take away the angle you know: 180 − '+n[0]+' = '+a+'.'; if(t.indexOf('point')>=0)return 'Angles at a point add up to 360°. Add the two you know: '+n[0]+' + '+n[1]+' = '+(n[0]+n[1])+'. Then 360 − '+(n[0]+n[1])+' = '+a+'.'; return 'Angles in a triangle add up to 180°. Add the two you know: '+n[0]+' + '+n[1]+' = '+(n[0]+n[1])+'. Then 180 − '+(n[0]+n[1])+' = '+a+'.';
case 'Units': return 'Know the links: 1 km = 1000 m, 1 kg = 1000 g, 1 ℓ = 1000 mℓ, 1 m = 100 cm and 1 h = 60 min. Changing to a smaller unit: multiply. Changing to a bigger unit: divide. The answer is '+a+'.';
case 'Two-step problem': m=t.match(/\$(\d+).*1\/(\d+).*1\/(\d+)/); return 'Find each part: '+m[1]+' ÷ '+m[2]+' = '+(m[1]/m[2])+' and '+m[1]+' ÷ '+m[3]+' = '+(m[1]/m[3])+'. Take both away from $'+m[1]+' to get '+a+'.';
case 'Rounding': return 'Look at the digit just to the right of the place you are rounding to. 5 or more: round up. Less than 5: stay the same. The answer is '+a+'.';
case 'Money': if(t.indexOf('change')>=0)return 'Change = money paid − cost. Take away the dollars first, then the cents. The answer is '+a+'.'; return 'Take away the whole dollars only. The cents stay the same: '+t.replace(' = ?','')+' = '+a+'.';
case 'Word problem': return 'Draw a bar model. Write down what you know and what you must find, then decide which operation joins them: add, take away, multiply or divide. The answer is '+a+'.';
}
}catch(e){}
return 'The answer is '+a+'. Slow down and check each step, or draw a picture.';
}
function showExplain(){
document.getElementById('exText').textContent=explainText(cq); document.getElementById('explain').hidden=false;
}
function lvlLow(i){var n=(LEVELS[i]&&LEVELS[i].name)||'', m=/^P(\d)/.exec(n); if(m)return +m[1]; if(/^Sec/.test(n))return 7; return 9;}
function skTopic(l,g){try{return !!(S.st.skt&&S.st.skt['g:'+l+':'+g]);}catch(e){return false;}}
function skInfo(){var n=Object.keys(S.st.skt||{}).length, e=document.getElementById('skInfo'); if(e)e.textContent=n?(n+' skipped topic'+(n>1?'s':'')+' will not be asked again.'):'No skipped topics.';}
function skipQ(){
if(!cq||locked2||!qActive||mode!=='play'||CK)return;
rollDay(); if((S.day.sk||0)>=5){el.fb.textContent='You have skipped 5 questions today. Try this one, or ask your teacher.'; el.fb.className='fb bad'; return;}
S.day.sk=(S.day.sk||0)+1; S.st.sk=(S.st.sk||0)+1;
stopTimer(); locked2=true; entryLocked=true; try{setEntryEnabled(false);}catch(_){}
Array.prototype.forEach.call(el.ans.children,function(x){x.disabled=true; if(x.dataset.v===String(cq.answer))x.classList.add('right'); else x.classList.add('dim');});
try{if(cq.lv!=null&&cq.gi!=null){S.st.skt=S.st.skt||{}; S.st.skt['g:'+cq.lv+':'+cq.gi]=1; skInfo();}}catch(_){} try{showExplain();}catch(_){}
el.fb.textContent='Skipped. No blocks lost. The answer is '+cq.answer+'. This topic will not be asked again until you or your teacher turn it back on.'; el.fb.className='fb';
sfx('click'); updateStats(); save(); finishQ();
}
document.getElementById('skipQ').addEventListener('click',skipQ);
document.getElementById('skReset').addEventListener('click',function(){S.st.skt={}; skInfo(); save();});
function stopTimer(){clearInterval(qTimer); qTimer=null; qActive=false; try{xtShow(false);}catch(_){} try{document.getElementById('modal').classList.remove('tflash');}catch(_){}}
var XT_COST=2;
function xtShow(on){var b=document.getElementById('moreTime'); if(b){ if(on){var free=false; try{free=senG();}catch(e){} b.textContent=free?'More time':'More time ('+XT_COST+' blocks)';} if(b.hidden===on)b.hidden=!on;}}
function xtTotal(){var t=0; Object.keys(inv).forEach(function(k){if(inv[k]>0)t+=inv[k];}); return t;}
function xtSpend(n){while(n>0){var best=null; Object.keys(inv).forEach(function(k){if(inv[k]>0&&(best===null||inv[k]>inv[best]))best=k;}); if(best===null)return false; inv[best]--; n--;} return true;}
function xtAsk(){
if(!qActive||locked2||xtUsed||!cq||mode==='duel')return;
var free=false; try{free=senG();}catch(e){}
if(!free){ if(xtTotal()0)return true; if(S.to===0)return false; if(S.to===1)return true;
var g=effGr(); return g?g>2:S.level>2;
}
function startTimer(sec){
stopTimer(); UNT=(((S.gr|0)>0&&(S.gr|0)<=2)&&mode!=='diag')||(!timerOn()&&(mode==='play'||mode==='check'||accOn('noTimers')))||(mode==='duel'&&((((S.gr|0)>0&&(S.gr|0)<=2))||(((typeof cq!=='undefined'&&cq&&cq.lv!=null)?cq.lv:curLevel())<=2))); /* Jasmine, 30 Oct 2026: no timer on pest (flash) questions for P1 and P2 Learners (grade picked), and for the first three levels */ try{document.getElementById('timer').hidden=UNT;}catch(_){}
if(UNT){qLimit=3600000; qLimit0=qLimit; xtUsed=true; qStart=performance.now(); qActive=true; return;} qLimit=sec*1000; qLimit0=qLimit; xtUsed=false; qStart=performance.now(); qActive=true; timerUI(1,sec); xtShow(false);
qTimer=setInterval(function(){
var left=qLimit-(performance.now()-qStart);
if(left<=0){stopTimer(); onTimeout(); return;}
var secs=Math.ceil(left/1000); timerUI(left/qLimit,secs);
xtShow((left<=Math.max(5000,qLimit*.6)||!document.getElementById('wkBody').hidden)&&!xtUsed&&mode!=='duel'&&!locked2); var fl=left<=Math.min(5000,qLimit*.34); var mm=document.getElementById('modal'); if(mm.classList.contains('tflash')!==fl)mm.classList.toggle('tflash',fl);
},100);
}
function frames(n){
el.vis.innerHTML=''; var f,i,count=n>10?2:1;
for(f=0;f=t0.len){roundEnd(t0); return;}}
cq=genG(); try{senTrim(cq);}catch(e){} try{updateProg();}catch(e){} try{calcSync(cq);}catch(e){} if(!cq&&PM){var pk=PM.ok, pt=PM.ids.length; closeModals(); hint(pk+' of '+pt+' mistakes fixed. '+(pk26);
if(cq.dots){el.vis.hidden=false; frames(cq.dots);} else {el.vis.hidden=true; el.vis.innerHTML='';}
var figEl=document.getElementById('fig'); var mpb=document.getElementById('musPlay'); if(mpb){mpb.hidden=!cq.listen; if(cq.listen)setTimeout(function(){try{playMelody(cq.listen,cq.bpm);}catch(e){}},400);}
if(cq.fig){figEl.innerHTML=cq.fig; figEl.setAttribute('aria-label',cq.figAlt||'Diagram'); figEl.hidden=false;} else {figEl.hidden=true; figEl.innerHTML='';}
el.ans.hidden=false; el.timer.hidden=false; el.ans.innerHTML='';
cq.opts.forEach(function(v,oi){var b=document.createElement('button'); b.type='button'; b.className='ans'+(String(v).length>8?' txt':''); b.textContent=v; b.dataset.k=String(oi+1); b.dataset.v=String(v); el.ans.appendChild(b);});
setupEntry();
el.fb.textContent=''; el.fb.className='fb'; el.rw.innerHTML=''; document.getElementById('explain').hidden=true; try{coachHide();}catch(e){} try{document.getElementById('srcLine').hidden=true;}catch(e){}
el.next.hidden=true; el.up.hidden=true; el.cpUse.hidden=true; el.cpAgain.hidden=true; el.alert.hidden=true;
if(mode==='check')el.title.textContent='Checkpoint '+(diag.n+1)+' of '+diag.total; else if(mode==='duel')el.title.textContent=(duel.z.fname||'Pest')+' of '+HABITS[duel.z.kind].name+': '+(duel.need-duel.got)+' hit'+(duel.need-duel.got===1?'':'s')+' to win'; else el.title.textContent=SC?((SCMOD==='D'?'D&T practice':(SCMOD==='Y'||SCMOD.slice(0,2)==='y:')?'History practice':(SCMOD==='G'||SCMOD.slice(0,2)==='g:')?'Geography practice':(SCMOD==='S'||SCMOD.slice(0,2)==='s:')?'Social Studies practice':(SCMOD==='T'||SCMOD.slice(0,2)==='l:')?'Literature practice':(SCMOD==='W'||SCMOD.slice(0,2)==='w:')?'Singapore and the world':SCMOD.slice(0,2)==='r:'?'Reading questions':'Science practice')+' \u00b7 question '+(SC.n+1)):MU?'Music practice · question '+(MU.n+1):G1S?'Secondary Maths · question '+(G1S.n+1)+(G1S.len?' of '+G1S.len:''):EP?'Think and decide · question '+(EP.n+1):CP?(CP.am?'Additional Maths (starter) · question ':'Computing practice · question ')+(CP.n+1):CK?'Cooking: '+CK.dish.name+' · step '+(CK.i+1)+' of '+CK.steps.length:BON?(BON.t?'Challenge: ':'Bonus x2: ')+'question '+(BON.done+1)+' of 5':'Earn blocks';
startTimer(timeLimit()); try{senAutoRead();}catch(e){} try{lbShow();}catch(e){}
}
function roll(n){
var tot=BASIC.reduce(function(s,t){return s+t.w;},0), got={};
for(var i=0;i>>0;} return h.toString(36).toUpperCase().slice(-2);}
function pmValid(code){var m=/^PM(\d{4})-?([A-Z0-9]{2})$/.exec(String(code||'').toUpperCase().replace(/\s/g,'')); return !!m&&pmChk(m[1])===m[2];}
function secLvl(){try{if(typeof SC!=='undefined'&&SC&&typeof SCMOD==='string'&&(SCMOD==='D'||SCMOD==='Y'||SCMOD==='G'||/^[yg]:/.test(SCMOD)))return true;}catch(e){} return curLevel()>=7;}
var GRACE=7*86400000;
function planOk(){var t=Date.now()-GRACE; return secLvl()?(S.pmS>t):(S.pmP>t);}
function planAny(){var t=Date.now()-GRACE; return S.pmP>t||S.pmS>t;}
function graceTxt(){var t=Date.now(),o=[]; [['Primary',S.pmP],['Secondary',S.pmS]].forEach(function(x){if(x[1]&&x[1]<=t&&x[1]>t-GRACE)o.push(x[0]+' ended '+Math.max(0,Math.floor((t-x[1])/86400000))+' day(s) ago. Premium stays on for '+Math.max(1,Math.ceil((x[1]+GRACE-t)/86400000))+' more day(s). Ask a grown-up to renew.');}); return o.join(' ');}
function planTxt(){var t=Date.now(),a=S.pmP>t-GRACE,b=S.pmS>t-GRACE,f=function(x){return new Date(x).toLocaleDateString('en-SG',{day:'numeric',month:'short',year:'numeric'});}; return (a&&b?'Primary and Secondary (until '+f(Math.min(S.pmP,S.pmS))+(S.pmP!==S.pmS?', '+(S.pmPDate.now())||planOk());}
function rarePop(t,pk,got){
try{
var ov=document.getElementById('rareOv'), cv=document.getElementById('rareCv'), col=pk[3]||'#f5c518';
ov.style.setProperty('--rc',col); paintIcon(cv,t.key); cv.getContext('2d');
document.getElementById('rareK').textContent=got?'RARE FIND!':'A RARE BLOCK!';
document.getElementById('rareT').textContent=t.name; document.getElementById('rareSym').textContent=pk[2]!=='gem'?'Element symbol: '+pk[2]:'A precious gem';
document.getElementById('rareFact').textContent=pk[5];
document.getElementById('rareCt').textContent=got?('You now have '+(inv[t.id]||1)+' in your bag.'):'Premium members can keep rare blocks. This one is added to your element collection!';
var old=ov.querySelectorAll('.rspark'); [].forEach.call(old,function(n){n.remove();});
var card=document.getElementById('rareCard'), cs=['#ffd43b','#ff6b6b','#4dabf7','#69db7c','#da77f2','#fff'];
for(var i=0;i<34;i++){var sp=document.createElement('i'); sp.className='rspark'; var a=Math.random()*Math.PI*2, d=90+Math.random()*150; sp.style.left='50%'; sp.style.top='110px'; sp.style.background=cs[i%cs.length]; sp.style.setProperty('--dx',Math.round(Math.cos(a)*d)+'px'); sp.style.setProperty('--dy',Math.round(Math.sin(a)*d)+'px'); sp.style.animationDelay=(Math.random()*.35)+'s'; card.appendChild(sp);}
ov.hidden=false; tone(523,.12,'triangle',.08); tone(659,.12,'triangle',.08,.12); tone(784,.12,'triangle',.08,.24); tone(1047,.4,'triangle',.09,.36);
setTimeout(function(){try{document.getElementById('rareOk').focus({preventScroll:true});}catch(_){}},60);
}catch(e){}
}
(function(){var ov=document.getElementById('rareOv'); function close(){ov.hidden=true;} document.getElementById('rareOk').addEventListener('click',close); ov.addEventListener('click',function(e){if(e.target===ov)close();}); ov.addEventListener('keydown',function(e){if(e.key==='Escape'||e.key==='Enter'){e.stopPropagation(); close();}});})();
function maybeRare(f,df){
var ch=(.05+.10*f)*(df==='hard'?2:df==='easy'?.5:1); if(Math.random()>ch)return '';
var rr0=RARE[Math.floor(Math.random()*RARE.length)]; try{elUnlock(ELMAP[rr0[0]]||null);}catch(e){}
if(!premium()){setTimeout(function(){rarePop(typeById(BY['r_'+rr0[0]]),rr0,false);},450);}
if(!premium())return 'A rare block was hiding nearby! Premium members can collect rare blocks. You found a new element for your collection!';
var tot=RARE.reduce(function(a,r){return a+r[4];},0), r0=Math.random()*tot, pk=RARE[0], i;
for(i=0;i0)||!classAct())return; if(!S.cb||S.cb.t!==sess.t)S.cb={t:sess.t,n:0}; S.cb.n+=n;}catch(e){}}
function giveRewards(n){
if(typeof SESS0!=='undefined')SESS0.blk+=n; cbAdd(n);
var got=roll(n); el.rw.innerHTML='';
Object.keys(got).forEach(function(id){var t=typeById(+id); inv[id]=(inv[id]||0)+got[id]; el.rw.appendChild(chip(t,'+'+got[id]+' '+t.name));});
try{ckPantryDrop(el.rw);}catch(e){}
}
function loseFromBag(k){
var pool=[]; Object.keys(inv).forEach(function(id){if(+id===AXE||(typeById(+id)||{}).rare||furnOff(+id))return; for(var i=0;i<(inv[id]||0);i++)pool.push(id);});
k=Math.min(k,pool.length); var lost={};
for(var i=0;i=1||(S.st.s||0)>=10); document.getElementById('rdLabel').textContent='Special scenarios: '+tk+' token'+(tk===1?'':'s')+'. Every streak of 20 earns one (your streak is '+(S.st.s||0)+').';
var box=document.getElementById('rbtns'); box.innerHTML='';
REDEEM.forEach(function(r){
var b=document.createElement('button'); b.type='button'; b.className='btn'; b.textContent=r.name+': '+r.desc; b.disabled=tk<1||mode==='check';
b.addEventListener('click',function(){redeem(r.id);}); box.appendChild(b);
});
}
function redeem(id){
if((S.st.tk||0)<1)return; S.st.tk--; var msg='';
if(id==='treasure'){giveRewards(30); msg='Treasure Island! You found 30 blocks.';}
else if(id==='shield'){S.st.sh=(S.st.sh||0)+3; msg='Disaster Shield on. It will block the next '+S.st.sh+' disasters.';}
else if(id==='buster'){banishAll(); zTimer=180; msg='Bad-Habit Buster! Every pest is gone for 3 minutes.';}
else{el.rw.innerHTML=''; for(var k=9;k<=16;k++){if(furnOff(k))continue; inv[k]=(inv[k]||0)+2; el.rw.appendChild(chip(typeById(k),'+2 '+typeById(k).name));} msg="Builder's Kit unlocked!";}
el.fb.textContent=msg; el.fb.className='fb good'; sfx('done'); updateHotbar(); updateGoals(); save(); renderRedeem();
}
function updateStats(){
renderRedeem();
try{var gc=document.getElementById('gclock'), ah=document.getElementById('auraHud'); if(gc&&!ah){ah=document.createElement('div'); ah.id='auraHud'; ah.className='aurahud'; gc.parentNode.insertBefore(ah,gc.nextSibling);} if(ah){var on=(typeof crAuraOn==='function'&&crAuraOn()&&typeof crEnabled==='function'&&crEnabled()); ah.hidden=!on; if(on)ah.textContent='Aura $'+Math.round(crCoins()).toLocaleString('en-SG');}}catch(e){}
var _sn=0; try{Object.keys(S.sk||{}).forEach(function(k){_sn+=(S.sk[k].n|0);});}catch(e){} document.getElementById('stA').textContent=Math.max(S.st.a|0,_sn); document.getElementById('stF').textContent=S.st.f; document.getElementById('stS').textContent=S.st.s;
try{var qk=document.getElementById('qStk'); if(qk){var ss=S.st.s||0; qk.textContent='Streak: '+ss+(ss>=3?' \u2605':'')+' \u00b7 Best: '+(S.st.b||0); qk.classList.toggle('hot',ss>=3); if(ss>(updateStats.p||0)){qk.classList.remove('bump'); void qk.offsetWidth; qk.classList.add('bump');} updateStats.p=ss;}}catch(e){}
}
function updateDanger(){
var m=Math.min(3,S.st.m||0);
Array.prototype.forEach.call(document.querySelectorAll('#pips i'),function(p,i){p.classList.toggle('on',i0);
document.getElementById('meterTxt').textContent=S.st.m>0?'Answer the next question correctly to calm the storm.':'';
}
/* storms */
var FLAMM=[BY.plank,BY.log,BY.leaves,BY.door,BY.bed,BY.bookshelf,BY.chest,BY.ctable];
function blocksIdx(pred){var a=[]; for(var i=0;i=HH)return true; return world[wi(p.x,p.y+1,p.z)]===0;}
function destroy(list){
var types=list.map(function(i){return world[i];}); list.forEach(function(i){world[i]=0;});
var salv=Math.floor(list.length/4), s;
for(s=0;s0){S.st.sh--; return {title:'Shield held!',text:'Your disaster shield blocked the storm. Shields left: '+S.st.sh+'.',fx:null};}
var kinds=['fire','quake','bolt','flood','tornado','tsunami'].filter(function(k){return k!==lastKind;});
var kind=miss===2?'wind':pick(kinds), cand=[], cnt=0, title='', verb='', all, c0, side;
if(kind==='fire'){cand=blocksIdx(function(i,v){return FLAMM.indexOf(v)>=0;}); if(!cand.length)kind='quake';}
if(kind==='flood'){cand=blocksIdx(function(i){return idxXYZ(i).y<=1;}); if(!cand.length)kind='quake';}
if(kind==='tsunami'){
side=rnd(0,1); all=blocksIdx(); c0=idxXYZ(all[rnd(0,all.length-1)]);
cand=blocksIdx(function(i){var p=idxXYZ(i); return Math.abs((side?p.x-c0.x:p.z-c0.z))<=3&&p.y<=3;});
if(!cand.length)kind='quake';
}
if(kind==='wind'){cand=blocksIdx(function(i){return exposedTop(i);}); cnt=Math.max(3,Math.round(total*.1)); title='Strong winds!'; verb='blown away';}
else if(kind==='fire'){cnt=Math.max(4,Math.round(cand.length*.4)); title='Fire!'; verb='burned';}
else if(kind==='flood'){cnt=Math.max(4,Math.round(cand.length*.5)); title='Flood!'; verb='washed away by rising water';}
else if(kind==='tsunami'){cnt=Math.min(60,cand.length); title='Tsunami!'; verb='swept away by a giant wave';}
else if(kind==='tornado'){
all=blocksIdx(); c0=idxXYZ(all[rnd(0,all.length-1)]); title='Tornado!'; verb='ripped apart';
cand=blocksIdx(function(i){var p=idxXYZ(i); return Math.abs(p.x-c0.x)<=2&&Math.abs(p.z-c0.z)<=2;}); cnt=Math.min(40,cand.length);
}
else if(kind==='bolt'){
all=blocksIdx(); c0=idxXYZ(all[rnd(0,all.length-1)]); title='Lightning strike!'; verb='destroyed';
cand=blocksIdx(function(i){var p=idxXYZ(i); return Math.abs(p.x-c0.x)<=1&&Math.abs(p.z-c0.z)<=1;}); cnt=cand.length;
}
else{kind='quake'; cand=blocksIdx(); cnt=Math.max(4,Math.round(total*.25)); title='Earthquake!'; verb='shaken apart';}
lastKind=kind;
try{var fp=(kind==='tornado'||kind==='bolt'||kind==='tsunami')&&c0?c0:(cand.length?idxXYZ(cand[rnd(0,cand.length-1)]):null); fxPos=fp;}catch(e){fxPos=null;}
var res=destroy(shuffle(cand).slice(0,Math.min(cnt,cand.length)));
dirty=true;
return {title:title,text:'Your world lost '+res.total+' block'+(res.total===1?'':'s')+' ('+verb+'). These came from your build, not your bag. You saved '+res.salv+' back into your bag (one in four).',fx:kind};
}
function onTimeout(wrong){
if(locked2)return; locked2=true; entryLocked=true; setEntryEnabled(false); if(!wrong){try{taNote(true);}catch(_){}} if(mode!=='duel'){try{mxLog(cq,wrong?(((performance.now()-qStart)/Math.max(1,qLimit))<.2?'rushed':'wrong'):'time');}catch(_){}}
Array.prototype.forEach.call(el.ans.children,function(x){
x.disabled=true; if(x.dataset.v===String(cq.answer))x.classList.add('right'); else if(!x.classList.contains('wrong'))x.classList.add('dim');
});
typedEl.classList.add('wrong');
if(!counted){counted=true; S.st.a++;}
if(CK){CK.miss=(CK.miss||0)+1; recordSkill(cq,0,0); showExplain(); S.st.s=0; el.fb.textContent=(wrong?'Not quite. ':'Time is up. ')+'The answer was '+cq.answer+'. Keep cooking!'; el.fb.className='fb bad'; sfx('bad'); updateStats(); save(); finishQ(); return;}
if(mode==='duel'){recordSkill(cq,0,0); duelMiss('time'); return;}
if(PM){showExplain(); S.st.s=0; el.fb.textContent=(wrong?'Not quite. ':'Time is up. ')+'The answer was '+cq.answer+'. It stays in My mistakes until you get it right.'; el.fb.className='fb bad'; sfx('bad'); updateStats(); save(); finishQ(); return;}
if(mode==='check'){
recordSkill(cq,0,0); diagRecord(false); el.fb.textContent='Time is up. The answer was '+cq.answer+'.'; el.fb.className='fb bad'; sfx('bad'); finishQ(); updateStats(); save(); return;
}
S.st.s=0; if(!senG())S.st.m=(S.st.m||0)+1; if(!cq.rec){recordSkill(cq,0,0); try{cq.dx=remDiagnose(cq,wrong===true,(performance.now()-qStart)/Math.max(1,qLimit)); var _s=S.sk[cq.kicker]; if(_s){_s.dx=_s.dx||{}; _s.dx[cq.dx]=(_s.dx[cq.dx]||0)+1;}}catch(e){} addRev(cq,cq.dx);} showExplain();
var lost=senG()?{}:loseFromBag(Math.min(6,1+S.st.m)), n=0; el.rw.innerHTML='';
Object.keys(lost).forEach(function(id){n+=lost[id]; var t=typeById(+id); el.rw.appendChild(chip(t,'−'+lost[id]+' '+t.name,true));});
el.fb.textContent=(wrong?'Not quite. ':'Time is up. ')+'The answer was '+cq.answer+'. '+(senG()?'No blocks lost. Mistakes help us learn.':'You lost '+n+' block'+(n===1?'':'s')+' from your bag.'); el.fb.className='fb bad'; try{el.fb.textContent+=remNote(cq);}catch(e){}
var s=senG()?{title:'Keep going',text:'No storm. You are learning, and that is what matters.',fx:null}:strike(S.st.m);
el.alert.hidden=senG(); document.getElementById('alertT').textContent=s.title; document.getElementById('alertP').textContent=s.text;
if(s.fx){pendingFx=s.fx; lastAlert=s.title+' '+s.text; sfx(s.fx);} else sfx('bad');
updateHotbar(); updateGoals(); updateStats(); updateDanger(); save(); finishQ();
if(wrong===true){try{wyStart();}catch(e){} try{coachStart();}catch(e){}}
}
function finishQ(){
try{document.getElementById('skipQ').hidden=true;}catch(_){}
try{var sl=document.getElementById('srcLine'); if(cq&&cq.src){var a=document.createElement('a'); a.href=cq.src; a.target='_blank'; a.rel='noopener noreferrer'; var h=''; try{h=new URL(cq.src).hostname.replace(/^www\./,'');}catch(e){h=cq.src;} a.textContent=h; sl.textContent='Source: '; sl.appendChild(a); sl.hidden=false;} else {sl.hidden=true; sl.textContent='';}}catch(e){}
try{ckProgress();}catch(e){} try{cpProgress();}catch(e){} try{if(EP&&mode==='play'){EP.n++; S.st.epn=(S.st.epn||0)+1;}}catch(e){} try{if(MU&&mode==='play'){MU.n++; S.st.mun=(S.st.mun||0)+1;}}catch(e){} try{if(G1S&&mode==='play'){G1S.n++; S.st.g1n=(S.st.g1n||0)+1;}}catch(e){} try{updateProg();}catch(e){} try{if(SC&&mode==='play'){SC.n++; S.st.scn=(S.st.scn||0)+1; if(S.st.scn%6===0)elUnlock(null);}}catch(e){} try{if(CP&&!CP.am&&mode==='play')S.st.cpn=(S.st.cpn||0)+1;}catch(e){} try{missionCheck();}catch(e){} try{bonusProgress();}catch(e){}
if(S.ans!=='mc')document.getElementById('entry').hidden=true;
el.next.hidden=false;
el.next.textContent=(mode==='check'&&diag.n>=diag.total)?'See my result':(CKDONE?'Back to kitchen':'Next question');
try{el.next.focus({preventScroll:true});}catch(_){}
}
/* ---------- typed and pixel answers ---------- */
var FORCE_MC=['Compare','Multiples'], typedEl=document.getElementById('typed'), ent='', entryLocked=false, padOn=new Array(140).fill(0), padGuessD=null, ENTRY_MAX=8;
var TPL={
'0':[['.###.','#...#','#...#','#...#','#...#','#...#','.###.']],
'1':[['..#..','.##..','..#..','..#..','..#..','..#..','.###.'],['..#..','..#..','..#..','..#..','..#..','..#..','..#..']],
'2':[['.###.','#...#','....#','...#.','..#..','.#...','#####'],['.##..','#..#.','...#.','..#..','.#...','#....','#####']],
'3':[['#####','...#.','..#..','...#.','....#','#...#','.###.'],['.###.','#...#','....#','..##.','....#','#...#','.###.']],
'4':[['...#.','..##.','.#.#.','#..#.','#####','...#.','...#.'],['#..#.','#..#.','#..#.','#####','...#.','...#.','...#.'],['#...#','#...#','#...#','.####','....#','....#','....#']],
'5':[['#####','#....','####.','....#','....#','#...#','.###.'],['#####','#....','#....','####.','....#','#...#','.###.']],
'6':[['..##.','.#...','#....','####.','#...#','#...#','.###.'],['.###.','#....','#....','####.','#...#','#...#','.###.'],['..#..','.#...','#....','####.','#...#','#...#','.###.'],['.##..','#....','#....','#.##.','##..#','#...#','.###.']],
'7':[['#####','....#','...#.','..#..','.#...','.#...','.#...'],['#####','....#','....#','...#.','...#.','..#..','..#..'],['#####','....#','...#.','...#.','..#..','..#..','.#...'],['#####','....#','...#.','..#..','..#..','.#...','.#...'],['####.','...#.','...#.','..#..','..#..','.#...','.#...']],
'8':[['.###.','#...#','#...#','.###.','#...#','#...#','.###.'],['.###.','#...#','.#.#.','..#..','.#.#.','#...#','.###.'],['.##..','#..#.','#..#.','.##..','#..#.','#..#.','.##..']],
'9':[['.###.','#...#','#...#','.####','....#','...#.','.##..'],['.###.','#...#','#...#','.####','....#','....#','.###.'],['.###.','#...#','#...#','.####','....#','...#.','..#..'],['.##..','#..#.','#..#.','.###.','...#.','...#.','...#.']]
};
var GW=10, GH=14;
function up(rows){var o=new Array(GW*GH).fill(0); for(var y=0;y<7;y++)for(var x=0;x<5;x++)if(rows[y][x]==='#'){o[(2*y)*GW+2*x]=o[(2*y)*GW+2*x+1]=o[(2*y+1)*GW+2*x]=o[(2*y+1)*GW+2*x+1]=1;} return o;}
function dil(a){var o=a.slice(); for(var y=0;y=0&&xx=0&&yy=0&&xx=0&&yymaxx)maxx=x;if(ymaxy)maxy=y;}
if(maxx<0)return null;
var w=maxx-minx+1,h=maxy-miny+1,s=Math.min(GH/h,GW/w), ox=(GW-w*s)/2, oy=(GH-h*s)/2, out=new Array(GW*GH).fill(0);
for(var ty=0;ty=minx&&x<=maxx&&y>=miny&&y<=maxy&&pad[y*GW+x])ink+=wx*wy;
}
if(area>0&&ink/area>=.3)out[ty*GW+tx]=1;
}
return out;
}
function recognizePad(pad){
var p=normalize(pad); if(!p)return null; var pd=dil(p), np=p.reduce(function(a,b){return a+b;},0); if(np<6)return null;
var best={};
TB.forEach(function(t){
var miss=0,extra=0; for(var i=0;i14)return null;
return {d:ds[0],dist:best[ds[0]],gap:best[ds[1]]-best[ds[0]]};
}
function useTyped(){return ansMode()!=='mc'&&FORCE_MC.indexOf(cq.kicker)<0&&/^[-$]?[\d.\/]+$/.test(String(cq.answer));}
function norm(x){
x=String(x).replace(/[\s$]/g,'').replace(/\u2212/g,'-'); if(/^\d+\/\d+$/.test(x))return x;
if(/^-?(\d+\.?\d*|\.\d+)$/.test(x))return String(Math.round(parseFloat(x)*1e6)/1e6);
return x;
}
function drawTyped(){typedEl.textContent=ent; typedEl.dataset.empty=ent?'0':'1';}
function setEntryEnabled(on){
Array.prototype.forEach.call(document.querySelectorAll('#keypad .key, #submitAns, #padAdd, #padClear'),function(b){b.disabled=!on;});
}
function clearPad(){
padOn=new Array(140).fill(0); Array.prototype.forEach.call(document.getElementById('padGrid').children,function(c){c.classList.remove('on');}); updateGuess();
}
function updateGuess(){
var r=recognizePad(padOn), g=document.getElementById('padGuess'), add=document.getElementById('padAdd');
padGuessD=r?r.d:null; g.textContent=r?'I see: '+r.d:(padOn.some(function(v){return v;})?'I see: ?':''); add.disabled=entryLocked||padGuessD===null;
}
function pressKey(k){
if(entryLocked||!qActive)return;
if(k==='Del')ent=ent.slice(0,-1); else if(ent.length=0)ex.push('.'); if(a0.indexOf('/')>=0)ex.push('/'); if(a0.charAt(0)==='$')ex.push('$');
var keys=S.ans==='pix'?ex.concat(['Del']):['7','8','9','4','5','6','1','2','3','0'].concat(ex,['Del']);
if(S.ans==='pix'&&ex.length===0)keys=['Del'];
var names={'-':'Minus','.':'Point','/':'Fraction bar','$':'Dollar sign','Del':'Delete'};
keys.forEach(function(k){
var b=document.createElement('button'); b.type='button'; b.className='key'+(k==='Del'?' wide':''); b.textContent=k; b.setAttribute('aria-label',names[k]||k);
b.addEventListener('click',function(){pressKey(k); b.blur();}); kp.appendChild(b);
});
}
function setupEntry(){
var typed=useTyped(), box=document.getElementById('entry'); document.getElementById('quizPanel').classList.toggle('padmode',typed&&S.ans==='pix');
el.ans.hidden=typed; box.hidden=!typed; ent=''; entryLocked=false; typedEl.className='typed'; drawTyped();
if(!typed)return;
buildKeys(); document.getElementById('pix').hidden=S.ans!=='pix'; clearPad(); setEntryEnabled(true);
var a=String(cq.answer), h='';
if(a.indexOf('/')>=0)h='Write it as a fraction, like 3/8.'; else if(a.charAt(0)==='$')h='Write the money with cents, like 1.90.'; else if(a.indexOf('.')>=0)h='Use the point key for the decimal point.'; else if(a.charAt(0)==='-')h='The answer is negative: press the minus key first.';
document.getElementById('entryHint').textContent=h;
updateGuess(); fitPad(); requestAnimationFrame(fitPad);
}
function fitPad(){
var g=document.getElementById('padGrid'); if(!g||S.ans!=='pix'||document.getElementById('entry').hidden)return;
g.style.width=''; var mdl=document.getElementById('modal'), top=g.getBoundingClientRect().top+(mdl.scrollTop||0), nat=g.getBoundingClientRect().width;
var avail=window.innerHeight-top-128, w=Math.max(220,Math.min(nat,avail*10/14)); g.style.width=Math.round(w)+'px';
}
window.addEventListener('resize',fitPad);
function submitTyped(){
if(entryLocked||!qActive||locked2)return;
if(S.ans==='pix'&&padGuessD!==null){pressKey(padGuessD); clearPad();}
if(!ent)return;
resolveAnswer(norm(ent)===norm(cq.answer),null);
}
(function(){
var g=document.getElementById('padGrid'), i, painting=null;
for(i=0;i=GW||y>=GH)?-1:y*GW+x;
}
function setCell(c,v){ if(c<0||padOn[c]===v)return; padOn[c]=v; g.children[c].classList.toggle('on',!!v); updateGuess(); }
var lastC=-1;
function lineTo(c,v){
if(c<0)return; if(lastC<0){setCell(c,v); lastC=c; return;}
var x0=lastC%GW,y0=Math.floor(lastC/GW),x1=c%GW,y1=Math.floor(c/GW),n=Math.max(Math.abs(x1-x0),Math.abs(y1-y0),1);
for(var k=1;k<=n;k++)setCell(Math.round(y0+(y1-y0)*k/n)*GW+Math.round(x0+(x1-x0)*k/n),v);
lastC=c;
}
g.addEventListener('pointerdown',function(e){
if(entryLocked||!qActive)return; var c=cellAt(e); if(c<0)return; e.preventDefault();
painting=padOn[c]?0:1; lastC=-1; lineTo(c,painting); try{g.setPointerCapture(e.pointerId);}catch(_){}
});
g.addEventListener('pointermove',function(e){if(painting===null)return; lineTo(cellAt(e),painting);});
['pointerup','pointercancel'].forEach(function(ev){g.addEventListener(ev,function(){painting=null; lastC=-1;});});
document.getElementById('padClear').addEventListener('click',clearPad);
document.getElementById('padAdd').addEventListener('click',function(){ if(padGuessD!==null){pressKey(padGuessD); clearPad();} });
document.getElementById('submitAns').addEventListener('click',submitTyped);
})();
function markAnswers(b,correct){
Array.prototype.forEach.call(el.ans.children,function(x){
x.disabled=true; if(x.dataset.v===String(cq.answer))x.classList.add('right'); else if(x===b)x.classList.add('wrong'); else x.classList.add('dim');
});
typedEl.classList.add(correct?'right':'wrong'); entryLocked=true; setEntryEnabled(false);
}
function resolveAnswer(correct,b){
if(locked2||!qActive)return;
if(!counted){counted=true; S.st.a++;}
var left=Math.max(0,qLimit-(performance.now()-qStart));
if(mode==='duel'){stopTimer(); locked2=true; markAnswers(b,correct); recordSkill(cq,correct?1:0,qLimit-left); if(correct){S.st.f++; duelHit();} else duelMiss('wrong'); return;}
if(mode==='check'){
stopTimer(); locked2=true; markAnswers(b,correct);
recordSkill(cq,correct?1:0,qLimit-left); diagRecord(correct);
el.fb.textContent=correct?'Correct.':'Not this time. The answer was '+cq.answer+'.'; el.fb.className='fb '+(correct?'good':'bad');
sfx(correct?'good':'bad'); finishQ(); updateStats(); save(); return;
}
if(!correct){stopTimer(); markAnswers(b,false); onTimeout(true); return;}
stopTimer(); locked2=true; markAnswers(b,true); recordSkill(cq,1,qLimit-left); try{taNote(false,left/Math.max(1,qLimit));}catch(_){} try{mxFixKey(mxKey(cq));}catch(_){}
var f=UNT?.5:left/qLimit, b0=6+Math.min(4,Math.max(0,S.level-3))*2, secs=((qLimit-left)/1000).toFixed(1), n, msg;
S.st.f++; S.st.s++; if(S.st.s>(S.st.b||0))S.st.b=S.st.s; enGain(10); try{if(cq&&cq.tg){S.st.tgc=S.st.tgc||{}; var bf=S.st.tgc[cq.tg]||0; S.st.tgc[cq.tg]=bf+1; if(bf=TGN){setTimeout(function(){hint('Grade '+cq.tg+' terms cleared!'+(cq.tg<5?' Grade '+(cq.tg+1)+' terms are next.':' You cleared all five grades!'),true);},900);}}}catch(e){} if(REV){REV.ok++; if(REV.ok>=3){REVDONE=true; hearts=3; drawHearts(); el.next.textContent='Wake up';}}
n=Math.round(b0*(.6+.9*f)); if(S.st.s>=3)n+=2; if(cq.df==='hard')n=Math.round(n*1.5); else if(cq.df==='easy')n=Math.max(2,Math.round(n*.75)); if(BON){n*=2; BON.ok++;} if(CK)CK.ok++; if(cq&&cq.e2)e21('sc_'+cq.e2); if(cq&&cq.e2c)e21('sel_'+cq.e2c);
msg='Correct! '+(f>=.66?'Fast! ':'')+'+'+(premium()?n:Math.max(1,Math.ceil(n/2)))+' blocks.';
if(S.st.s%3===0&&hearts<5){hearts++; drawHearts(); msg+=' +1 HEART for 3 in a row!';}
try{msg+=streakBonus();}catch(e){}
if(S.st.s%5===0&&S.level=3&&_pr.filter(function(r){return r[0];}).length/_pr.length<=.5&&(qLimit-left)/qLimit<.2&&(ansMode()||'mc')==='mc'&&!cq.rv&&_sk.ev&&_sk.ev.length){_sk.ev[_sk.ev.length-1][0]=0; addRev(cq,'confirm'); msg+=' Quick check: one more like this to be sure.';}}catch(e){}
if(S.st.s%5===0&&zombies.length){var zc=zombies.length; banishAll(); msg+=' Streak power banished '+zc+' bad-habit pest'+(zc>1?'s':'')+'!';}
if(S.st.s%(S.pl.vk||10)===0){S.st.vp=(S.st.vp||0)+1; msg+=' VISIT PASS earned! Use it on Visit a friend.';}
if(S.st.s%20===0){S.st.tk=(S.st.tk||0)+1; msg+=' STREAK OF 20! You earned a special scenario token. Redeem it below.';}
var landMsg='';
if(S.st.s%10===0&&expandLand())landMsg=' NEW LAND UNLOCKED for your streak!';
var mcNow=masteredCount(); if(mcNow>(S.st.mc||0)){S.st.mc=mcNow; if(expandLand())landMsg+=' You mastered a skill: NEW LAND UNLOCKED!';}
try{var _sk2=S.sk[cq.kicker]; S.st.stars=S.st.stars||{}; if(_sk2&&remStrict(_sk2)&&!S.st.stars[cq.kicker]){S.st.stars[cq.kicker]=1; var _g=typeById(BY.r_gold); if(_g)inv[_g.id]=(inv[_g.id]||0)+1; msg+=' \u2605 MASTERED '+cq.kicker+'! You earned a golden star and 1 gold block. Stars so far: '+starCount()+'.'; sfx('done');}}catch(e){}
if(landMsg)msg+=landMsg+' The world is now '+LAND+' \u00d7 '+LAND+'.';
if(cq.df==='hard')msg+=' Challenge bonus!'; if(unlockNote){msg+=' '+unlockNote; unlockNote='';}
S.st.m=0; el.fb.textContent=msg; el.fb.className='fb good';
giveRewards(premium()?n:Math.max(1,Math.ceil(n/2))); if(!premium()&&freeLeft()<=3)el.fb.textContent+=(freeLeft()>0?' Free questions left today: '+freeLeft()+'.':' That was your last free question today.'); var rr=maybeRare(f,cq.df); if(rr)el.fb.textContent+=' '+rr; sfx('good'); updateHotbar(); updateStats(); updateDanger(); save(); updateGoals(); finishQ();
}
el.ans.addEventListener('click',function(e){
var b=e.target.closest('.ans'); if(!b||locked2||b.disabled||!qActive)return;
resolveAnswer(b.dataset.v===String(cq.answer),b);
});
el.next.addEventListener('click',function(){
if(REVDONE){REVDONE=false; closeModals(); hint('You are awake with 3 hearts. Be careful!',true); return;}
if(CKDONE){CKDONE=false; closeModals(); openKitchen(); return;}
if(mode==='duel'){var d=duel; if(d&&d.done){closeModals(); return;} if(d&&d.faint){closeModals(); faint(); return;} showQuestion(); return;}
if(mode==='check'&&diag.n>=diag.total){showResult(); return;}
showQuestion();
});
el.up.addEventListener('click',function(){S.level=Math.min(S.level+1,LEVELS.length-1); updateLevels(); lastKey=''; showQuestion(); save();});
/* levels, timer setting */
var lv=document.getElementById('levels'), lvBtns=[];
LEVELS.forEach(function(L,i){
var b=document.createElement('button'); b.type='button'; b.className='btn lvl'; b.textContent=L.name; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){S.level=i; updateLevels(); lastKey=''; showQuestion(); save();});
lv.appendChild(b); lvBtns.push(b);
});
var PSTART=[0,0,2,3,4,5,6], pyBtns=[];
(function(){
var before=lvBtns[7]; lvBtns.slice(0,7).forEach(function(b){b.hidden=true;});
for(var n=1;n<=6;n++)(function(n){
var b=document.createElement('button'); b.type='button'; b.className='btn lvl'; b.textContent='P'+n; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){S.gr=n; S.level=PSTART[n]; updateLevels(); lastKey=''; showQuestion(); save();});
lv.insertBefore(b,before); pyBtns.push([b,n]);
})(n);
})();
function pyYear(){return S.level<7?(S.gr>=1&&S.gr<=6?S.gr:Math.max(1,lvlLow(S.level))):0;}
var anSeg=document.getElementById('anSeg'), anBtns=[];
[['Choose','mc'],['Keypad','key'],['Draw','pix']].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[0]; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){S.ans=o[1]; updateLevels(); showQuestion(); save();});
anSeg.appendChild(b); anBtns.push([b,o[1]]);
});
var gq2=document.getElementById('grSeg2'), gqBtns=[];
[['Full','full'],['Light','light'],['Off','off']].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[0]; b.setAttribute('aria-pressed',String(GRASSQ===o[1]));
b.addEventListener('click',function(){GRASSQ=o[1]; try{localStorage.setItem('mbGrass',GRASSQ);}catch(_){} gqBtns.forEach(function(p){p[0].setAttribute('aria-pressed',String(p[1]===GRASSQ));}); try{buildTufts();}catch(_){}});
gq2.appendChild(b); gqBtns.push([b,o[1]]);
});
var toSeg=document.getElementById('toSeg'), toBtns=[];
[['Auto',undefined],['On',1],['Off',0]].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[0]; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){S.to=o[1]; updateLevels(); showQuestion(); save();});
toSeg.appendChild(b); toBtns.push([b,o[1]]);
});
var tmSeg=document.getElementById('tmSeg'), tmBtns=[];
[['Relaxed',1.6],['Normal',1],['Fast',.7]].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[0]; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){S.tm=o[1]; updateLevels(); showQuestion(); save();});
tmSeg.appendChild(b); tmBtns.push([b,o[1]]);
});
var grSeg=document.getElementById('grSeg'), grBtns=[];
[['Any',0],['P1',1],['P2',2],['P3',3],['P4',4],['P5',5],['P6',6]].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[0]; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){S.gr=o[1]; updateLevels(); showQuestion(); save();});
grSeg.appendChild(b); grBtns.push([b,o[1]]);
});
/* Bonus play time: 1 minute for every 5 correct in a row, only for questions at the Learner's own grade or above. */
function qGrade(q){
var m=grMap(), lv=(q.lv!=null?q.lv:S.level), v=q.kicker?m[lv+'|'+q.kicker]:undefined; if(v===undefined&&q.kicker)v=m['*|'+q.kicker];
return v!==undefined?v:lvlLow(lv);
}
function streakBonus(){
if(!planOn()||mode!=='play'&&mode!=='quiz')return '';
var g=effGr(), ok=!!g&&qGrade(cq)>=g;
if(S.st.s===1)S.st.qs=0;
if(ok)S.st.qs=(S.st.qs||0)+1;
if(S.st.s%5!==0)return '';
rollDay();
if(ok&&S.st.qs%5===0){
if((S.day.bq||0)>=10)return ' Five in a row! You have earned the most bonus play time for today. Great work.';
S.day.bq=(S.day.bq||0)+1; S.day.b=(S.day.b||0)+1; if(S.ph==='play')S.rd.pm-=60; else S.day.bk=(S.day.bk||0)+60; return ' +1 MINUTE of play time for 5 in a row at your level!';
}
if(!g)return ' Five in a row! Pick My grade in Settings, then answer questions at your own level to earn bonus play time.';
return ' Five in a row! Bonus play time comes from questions at your own level (P'+g+'). Try some at your level next.';
}
var GRM=null;
function grMap(){
if(GRM)return GRM; GRM={};
SYL.forEach(function(r){var g=+String(r[1]).slice(1); if(!(g>=1&&g<=6))return;
etPairs(r).forEach(function(p){if(p.grid||p.qg)return; var k=p.lv+'|'+p.k; if(!(k in GRM)||g6||!q||!q.kicker)return false; var m=grMap(), v=m[lu+'|'+q.kicker]; if(v===undefined)v=m['*|'+q.kicker];
return v!==undefined&&v>g;
}
/* ---------- learn first, then play ---------- */
var P=S.pl, lockOv=document.getElementById('lockOv'), clockEl=document.getElementById('sesClock'), chipAcc=0, saveAcc=0, warn2=false, warn1=false, lastRound={q:0,c:0,pm:0};
function kid(){return !!(S.me&&S.me.name)&&!S.fam&&!(S.me.cls&&S.me.cm);}
/* No daily play-time cap for Learners (parents set screen time on the device). Learners still earn each play round by answering questions. */
var KIDPL={k:'class',qo:0,u:'q',uq:5,um:10,pm:10,dm:1440,dmw:1440,dme:1440};
var KIDHIDE=['gmBtn','visitBtn','lobBtn','invBtn'];
function enforceKid(){
if(!kid())return;
S.pl=Object.assign(S.pl,KIDPL); P=S.pl;
var kh=KIDHIDE.filter(function(x){return !(x==='invBtn'&&S.me&&S.me.a13===1);});
S.pl.hide=(Array.isArray(S.pl.hide)?S.pl.hide:[]).filter(function(x){return KIDHIDE.indexOf(x)<0;}).concat(kh);
try{applyHide();}catch(e){}
}
function releaseKid(){
S.pl.k='off'; S.pl.qo=0; P=S.pl; S.pl.hide=(Array.isArray(S.pl.hide)?S.pl.hide:[]).filter(function(x){return KIDHIDE.indexOf(x)<0;});
S.ph='learn'; try{applyHide();}catch(e){}
}
function planOn(){enforceKid(); return S.pl.k!=='off';}
function gateOn(){return LEARN_FIRST_GATE||!!S.pin;}
function gated(){return classBlocked()||(gateOn()&&planOn()&&S.ph!=='play');}
function nowD(){var d=new Date(); try{ if(S&&S.pl&&S.pl.sgt===false)return d; d=new Date(d.getTime()+(480+d.getTimezoneOffset())*60000); }catch(e){} return d;}
function todayKey(){var n=nowD(); return n.getFullYear()+'-'+(n.getMonth()+1)+'-'+n.getDate();}
function rollDay(){if(S.day.d!==todayKey())S.day={d:todayKey(),q:0,c:0,lm:0,pm:0,b:0,u:0,rs:(S.day&&S.day.rs)|0};}
/* Singapore school calendar (MOE, 2026 and 2027): days when school is closed count as rest days, with the weekend limit. Dates are inclusive, year-month-day. */
var SGCAL=[
['2026-01-01','2026-01-01'],['2026-02-17','2026-02-18'],['2026-03-14','2026-03-23'],['2026-04-03','2026-04-03'],['2026-05-01','2026-05-01'],['2026-05-27','2026-05-27'],
['2026-05-30','2026-06-28'],['2026-07-06','2026-07-06'],['2026-08-10','2026-08-10'],['2026-09-04','2026-09-13'],['2026-10-02','2026-10-02'],['2026-11-09','2026-11-09'],['2026-11-21','2027-01-03'],
['2027-02-06','2027-02-09'],['2027-03-10','2027-03-10'],['2027-03-13','2027-03-21'],['2027-03-26','2027-03-26'],['2027-05-03','2027-05-03'],['2027-05-17','2027-05-17'],['2027-05-20','2027-05-20'],
['2027-05-29','2027-06-27'],['2027-07-05','2027-07-05'],['2027-08-09','2027-08-10'],['2027-09-03','2027-09-12'],['2027-10-08','2027-10-08'],['2027-10-28','2027-10-28'],['2027-11-20','2027-12-31']
];
function ymd(d){var m=d.getMonth()+1,x=d.getDate(); return d.getFullYear()+'-'+(m<10?'0':'')+m+'-'+(x<10?'0':'')+x;}
function restDay(d){d=d||nowD(); var w=d.getDay(); if(w===0||w===6)return true; if(S.pl.cal===false||S.pl.place==='any')return false; var k=ymd(d); for(var i=0;i=SGCAL[i][0]&&k<=SGCAL[i][1])return true;} return false;}
function ownLimit(rest){var p=S.pl, v=rest?p.dme:p.dmw; if(typeof v==='number')return v; return rest?p.dm:Math.min(30,p.dm);}
function baseLimit(rest){
var v=ownLimit(rest), pl=S.plat, own=rest?S.pl.dme:S.pl.dmw;
if(pl){var pv=rest?pl.dme:pl.dmw; if(typeof pv==='number'){ if(pl.force)v=Math.min(v,pv); else if(typeof own!=='number')v=pv; }}
return v;
}
function dayCap(){return baseLimit(restDay())+(S.day.b||0);}
function dayRemain(){return dayCap()*60-S.day.pm;}
function playLeft(){return Math.max(0,Math.min(S.pl.pm*60-S.rd.pm,dayRemain()));}
function unlockMet(){return S.pl.u==='q'?S.rd.q>=S.pl.uq:S.rd.lm>=S.pl.um*60;}
function fmt(sec){var t=Math.max(0,Math.ceil(sec)), m=Math.floor(t/60), r=t%60; return m+':'+(r<10?'0':'')+r;}
var _recordSkill=recordSkill; recordSkill=function(q,ok){ rollDay(); S.rd.q++; S.day.q++; if(mode==='play')window.QSIT=(window.QSIT||0)+1; if(!classAct()){S.day.u=(S.day.u||0)+1; quotaPush();} if(ok){S.rd.c++; S.day.c++;} return _recordSkill.apply(this,arguments); };
function planText(){
var q=S.pl; if(q.k==='off')return 'Free play. No limits.';
if(q.qo)return 'Quiz only.';
return (q.k==='class'?'Class plan: ':q.k==='home'?'Home plan: ':'Custom plan: ')+(q.u==='q'?q.uq+' questions':q.um+' minutes of learning')+', then '+q.pm+' minutes of play. Up to '+q.dm+' minutes of play a day.';
}
function updateChip(){
var lg=document.getElementById('lockGo');
if(classBlocked()){
document.getElementById('lockT').textContent='Waiting for your teacher';
document.getElementById('lockP').textContent='The class is not live right now. Your game opens when your teacher starts the class.';
lg.hidden=true; clockEl.hidden=true; var inf=document.getElementById('planInfo'); if(inf)inf.textContent=planText(); return;
}
lg.hidden=false;
var info=document.getElementById('planInfo'); if(info)info.textContent=planText();
if(!gateOn()){clockEl.hidden=true; try{lockOv.hidden=true;}catch(e){} return;}
if(!planOn()){clockEl.hidden=true; return;}
clockEl.hidden=false; clockEl.className='sesclock';
if(S.pl.qo&&S.pl.k!=='off'&&!(S.ph==='play')){clockEl.textContent='\uD83D\uDCDA Quiz time'; var qt=document.getElementById('lockT'), qp=document.getElementById('lockP'), qg=document.getElementById('lockGo'); qt.textContent='Quiz time'; qp.textContent='Your teacher set a quiz. Choose a subject and answer questions.'; qg.textContent='Start a quiz'; return;}
if(S.ph==='play'){var l=playLeft(); clockEl.textContent='🎮 Play '+fmt(l); if(l<=60)clockEl.classList.add('low'); else clockEl.classList.add('go');}
else if(dayRemain()<=0){clockEl.textContent='🌙 Play is done for today';}
else if(S.pl.u==='q'){clockEl.textContent='📚 '+Math.min(S.rd.q,S.pl.uq)+' of '+S.pl.uq+' questions to unlock play';}
else {clockEl.textContent='📚 '+fmt(S.pl.um*60-S.rd.lm)+' of learning to unlock play';}
var t=document.getElementById('lockT'), p=document.getElementById('lockP'), g=document.getElementById('lockGo');
if(dayRemain()<=0){t.textContent='Play is finished for today'; p.textContent='Great learning! You can keep practising any subject if you like. Play opens again tomorrow.'+((S.day.bq||0)<10?' Bonus: answer 5 in a row correctly at your own level'+(effGr()?' (P'+effGr()+' or above)':'')+' to win 1 more minute of play today.':''); g.textContent='Keep practising';}
else {t.textContent='Learn first, then play'; p.textContent=(S.pl.u==='q'?'Answer '+Math.max(0,S.pl.uq-S.rd.q)+' more question'+(S.pl.uq-S.rd.q===1?'':'s'):'Learn for '+fmt(Math.max(0,S.pl.um*60-S.rd.lm))+' more')+' to unlock '+Math.min(S.pl.pm,Math.ceil(dayRemain()/60))+' minutes of play.'; g.textContent='Earn blocks';}
}
var MENUF=[['mathBtn','Primary Maths'],['etBtn','Exit ticket'],['exBtn','Primary Maths: exam question'],['g1Btn','Secondary Maths'],['scBtn','Science'],['dtBtn','D&T'],['hiBtn','History'],['geBtn','Geography'],['ssBtn','Social Studies'],['wBtn','Singapore and the world'],['fireBtn','Fire safety'],['enBtn','English'],['dlBtn','Everyday life (Post-Sec)'],['sgBtn','Know your Singlish (Post-Sec)'],['cnBtn','Chinese characters'],['mlBtn','Malay peribahasa'],['rdBtn','Reading'],['liBtn','Literature'],['muBtn','Music'],['cpBtn','Computing'],['e21Btn','Aura skills'],['cookBtn','Kitchen'],['hkBtn','Hawker Stall'],['msBtn','Music studio'],['labBtn','Science lab'],['tourBtn','Visit Singapore'],['visitBtn','Visit a friend'],['lobBtn','Lobby'],['invBtn','Invite friends'],['petBtn','My pets'],['gmBtn','Game room'],['premBtn','Premium']];
function applyHide(){
var h=(S.pl&&Array.isArray(S.pl.hide))?S.pl.hide:[];
MENUF.forEach(function(f){var e=document.getElementById(f[0]); if(e)e.style.display=(h.indexOf(f[0])>=0)?'none':'';});
try{var dlb=document.getElementById('dlBtn'); if(dlb&&lrnLevel()!=='Q')dlb.style.display='none';}catch(e){}
try{var sgb=document.getElementById('sgBtn'); if(sgb&&lrnLevel()!=='Q')sgb.style.display='none';}catch(e){}
/* the exit ticket only shows when a teacher has set one for one of the Learner's classes (or a teacher is signed in) */
try{var et0=document.getElementById('etBtn'); if(et0)et0.style.display=window.ETSET?'':'none';}catch(e){}
/* hide any heading that has nothing visible under it */
[['tray','traycat'],['subTray','traycat']].forEach(function(p){var box=document.getElementById(p[0]); if(!box)return; var kids=[].slice.call(box.children), i, head=null, any=false;
function done(){if(head){head.style.display=any?'':'none';}}
for(i=0;i30){playPushAcc=0; try{quotaPush();}catch(e){}}}
saveAcc+=dt; if(saveAcc>15){saveAcc=0; save();}
chipAcc+=dt; if(chipAcc<.25)return; chipAcc=0;
if(S.ph==='learn'){
if(dayRemain()>0&&unlockMet())unlockPlay();
} else {
var l=playLeft();
if(l<=120&&!warn2){warn2=true; hint('Two minutes of play left.',true);}
if(l<=60&&!warn1){warn1=true; hint('One minute of play left.',true);}
if(l<=0){endPlay(); return;}
}
updateChip();
}
function unlockPlay(){
S.ph='play'; S.rd.pm=-(S.day.bk||0); S.day.bk=0; warn2=false; warn1=false; save(); sfx('done');
hint('Play unlocked! You have '+Math.round(playLeft()/60)+' minutes. Go back to the world and build.',true);
if(!modal.hidden){var a=document.getElementById('alert'); document.getElementById('alertT').textContent='Play unlocked!'; document.getElementById('alertP').textContent='Finish this question, then press Back to play for '+Math.round(playLeft()/60)+' minutes.'; a.hidden=false;}
applyGate();
}
function endPlay(){
if(visiting)exitVisit();
lastRound={q:S.rd.q,c:S.rd.c,pm:S.rd.pm}; S.ph='learn'; S.rd={q:0,c:0,lm:0,pm:0}; save();
stopTimer(); mode='play'; diag=null; modal.hidden=true; craftModal.hidden=true; ['gradeModal','dropModal','drillModal','goalsModal','whoModal','clsMod','cnMod','mlMod','etMod','progModal','teachModal','planModal'].forEach(function(i){document.getElementById(i).hidden=true;});
pauseGame(); lockOv.hidden=true; sfx('done');
var done=dayRemain()<=0, acc=lastRound.q?Math.round(lastRound.c/lastRound.q*100):0;
document.getElementById('sesT').textContent=done?'That is all the play for today':'Play time is over for now';
document.getElementById('sesMsg').textContent=done?'Well done! You have used all of your play time today. Come back tomorrow, or keep practising any subject.':'Great building! Answer '+(S.pl.u==='q'?S.pl.uq+' questions':S.pl.um+' minutes of learning')+' to earn your next '+Math.min(S.pl.pm,Math.ceil(dayRemain()/60))+' minutes of play.';
document.getElementById('sesStat').innerHTML='
';
document.getElementById('sesAgain').textContent=done?'Keep practising':'Back to learning';
var m=document.getElementById('sesModal'); m.hidden=false; m.scrollTop=0;
try{document.getElementById('sesPanel').focus({preventScroll:true});}catch(_){}
updateChip();
}
document.getElementById('sesAgain').addEventListener('click',function(){document.getElementById('sesModal').hidden=true; openQuiz();});
document.getElementById('lockGo').addEventListener('click',function(){openPower();});
/* teacher controls */
var planModal=document.getElementById('planModal'), planBody=document.getElementById('planBody'), pinOK=false;
function hp(t){var h=5381,i; for(i=0;iDate.now()?g.l-Date.now():0;}
function gpTry(v){ /* returns 'ok', 'bad' (with tries left in gpTry.left) or 'locked' */
var g=gpGet(); if(g.l>Date.now())return 'locked'; if(g.l){g.l=0; g.f=0;}
if(gpMatch(v)){g.f=0; g.l=0; gpPut(g); return 'ok';}
g.f++; if(g.f>=5){g.l=Date.now()+10*60000; g.f=0; gpPut(g); return 'locked';}
gpPut(g); gpTry.left=5-g.f; return 'bad';
}
function gpAddMinutes(n){ /* extra play time for today only: the daily reset clears S.day.b and S.day.bk */
rollDay(); S.day.b=(S.day.b||0)+n; if(S.ph==='play')S.rd.pm-=n*60; else S.day.bk=(S.day.bk||0)+n*60;
gpMode=''; save(); planModal.hidden=true; try{applyGate();}catch(e){} try{updateChip();}catch(e){}
hint('Added '+n+' minutes of play for today.',true);
}
function gpRemove(){var g=gpGet(); if(!S.pin&&g.h)S.pin=g.h; gpMode=''; S.fam=1; releaseKid(); pinOK=true; save(); renderPlan(); applyGate(); updateChip();}
function gpStartSet(){gpMode='set'; renderPlan();}
function gpBlock(parent){
var box=mk('div','gpbox'); box.appendChild(mk('h4','','Grown-up PIN'));
var msg=mk('p','lvldesc'); msg.setAttribute('aria-live','polite');
function pin(lbl){var i=mk('input','pininp'); i.type='password'; i.inputMode='numeric'; i.maxLength=4; i.autocomplete='off'; i.setAttribute('aria-label',lbl); i.setAttribute('data-gp',lbl); i.placeholder='PIN'; return i;}
function btn(t,f,cls){var b=mk('button','btn '+(cls||''),t); b.type='button'; b.addEventListener('click',f); return b;}
function lockTxt(){var m=Math.ceil(gpLockLeft()/60000); return 'Too many tries. The PIN is locked for '+m+' more minute'+(m===1?'':'s')+'. Please wait, then ask a grown-up to try again.';}
if(gpMode==='ok'){
box.appendChild(mk('p','lvldesc','PIN is right. Choose what to do.'));
var ol=mk('div','gpopts');
ol.appendChild(btn('Add 10 minutes today',function(){gpAddMinutes(10);},'primary'));
ol.appendChild(btn('Add 30 minutes today',function(){gpAddMinutes(30);},'primary'));
ol.appendChild(btn('This player is an adult: remove the limits',gpRemove));
try{accPlanBtn(box);}catch(e){}
ol.appendChild(btn('Change the PIN',function(){gpMode='set'; renderPlan();},'small'));
box.appendChild(ol); box.appendChild(mk('p','lvldesc','Extra minutes only last until tomorrow. Choosing "This player is an adult" removes the child limits on this device and turns it into a grown-up device. Only do this for an adult, because anyone who knows the PIN can undo it.'));
} else if(gpMode==='set'){
box.appendChild(mk('p','lvldesc','Choose a 4-digit PIN. Keep it private. You will use it to give extra play time.'));
var a=pin('New PIN'), b=pin('New PIN again'); b.placeholder='Again';
function save2(){var x=a.value.replace(/\D/g,''), y=b.value.replace(/\D/g,''); if(x.length!==4){msg.textContent='Use exactly 4 digits.'; return;} if(x!==y){msg.textContent='The two PINs are not the same. Try again.'; b.value=''; return;}
var g=gpGet(); g.h=hp(x); g.f=0; g.l=0; gpPut(g); gpMode=''; renderPlan();}
var sh2=btn('Show',function(){var on=sh2.getAttribute('aria-pressed')!=='true'; sh2.setAttribute('aria-pressed',String(on)); sh2.textContent=on?'Hide':'Show'; a.type=on?'text':'password'; b.type=on?'text':'password';},'small'); sh2.setAttribute('aria-pressed','false');
box.appendChild(a); box.appendChild(b); box.appendChild(sh2); box.appendChild(btn('Save PIN',save2,'primary')); box.appendChild(msg);
} else if(gpLockLeft()>0){
msg.textContent=lockTxt(); box.appendChild(msg);
} else if(!gpHas()){
box.appendChild(mk('p','lvldesc','There is no grown-up PIN yet. A grown-up can set one. First they must unlock with a grown-up code below.'));
box.appendChild(btn('I am a grown-up: set a PIN',function(){gpWant=true; msg.textContent='Now type the grown-up code in the box below.';},'small')); box.appendChild(msg);
} else {
box.appendChild(mk('p','lvldesc','A grown-up can type the 4-digit PIN to give you more play time.'));
var pi=pin('Grown-up PIN');
function chk(){var v=pi.value.replace(/\D/g,''); if(v.length!==4){msg.textContent='Type the 4 digits.'; return;}
var r=gpTry(v); pi.value='';
if(r==='ok'){gpMode='ok'; renderPlan();}
else if(r==='locked'){renderPlan();}
else msg.textContent='That PIN is not right. You can try '+gpTry.left+' more time'+(gpTry.left===1?'':'s')+'.';}
pi.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault(); chk();}});
box.appendChild(pi); box.appendChild(btn('Check PIN',chk,'primary')); box.appendChild(msg);
}
parent.appendChild(box);
}
function ctlName(){var r='pupil'; try{r=premRole();}catch(e){} return (r==='teacher'||r==='owner')?'Teacher controls':'Parent controls';}
function renderPlan(){
planBody.innerHTML='';
try{var cn=ctlName(), h2=document.querySelector('#planPanel .qhead h2'); if(h2&&h2.firstChild&&h2.firstChild.nodeType===3)h2.firstChild.nodeValue=cn+' '; var pb=document.getElementById('planBtn'); if(pb)pb.textContent=cn; document.getElementById('planPanel').setAttribute('aria-label',cn); document.getElementById('planBack').setAttribute('aria-label','Close '+cn.toLowerCase());}catch(e){}
if(kid()){
if(planModal.hidden){gpMode=''; gpWant=false;}
planBody.appendChild(mk('h3','','Ask a grown-up'));
planBody.appendChild(mk('p','lvldesc','You can do quizzes as much as you like. Play time is 1 hour on school days and 2 hours on weekends and school holidays. A parent or teacher can change the limit. They type their grown-up code here.'));
gpBlock(planBody);
var ki=mk('input','pininp'); ki.type='text'; ki.maxLength=12; ki.autocomplete='off'; ki.setAttribute('aria-label','Grown-up code'); ki.placeholder='Grown-up code';
var km=mk('p','lvldesc'); km.setAttribute('aria-live','polite');
var kg=mk('button','btn primary','Unlock'); kg.type='button';
function kdone(){if(gpWant){gpWant=false; gpStartSet(); return;} S.fam=1; releaseKid(); save(); km.textContent='Unlocked. A grown-up can now set the plan.'; pinOK=false; renderPlan(); applyGate(); updateChip();}
function ksub(){
var c=ki.value.toUpperCase().replace(/\s/g,''); if(!c){km.textContent='Type the grown-up code.'; return;}
if(pmValid(c)){kdone(); return;}
try{ if(window.crypto&&crypto.subtle){ crypto.subtle.digest('SHA-256',new TextEncoder().encode(c)).then(function(h){ var hx=Array.prototype.map.call(new Uint8Array(h),function(x){return ('0'+x.toString(16)).slice(-2);}).join(''); if(hx==='2cdfde167264b8aaee19575f2bc278835138f9695d063a531be6f62e25e88b9a'){kdone();} else ksub2(c); }).catch(function(){ksub2(c);}); return; } }catch(e){}
ksub2(c);
}
function ksub2(c){
if(!dbh){km.textContent='Cannot check this code now. Open the game online and try again.'; return;}
try{dbh.doc('plans/fam_'+c).get().then(function(sn){
if(!sn.exists){km.textContent='That code does not work. Ask your parent or teacher.'; return;}
return dbh.doc('referrals/famc_'+c).get().then(function(cl){
var d=cl.exists?cl.data():null;
if(d&&d.by&&d.by!==uid){km.textContent='That code has already been used.'; return;}
return dbh.doc('referrals/famc_'+c).set({by:uid,t:Date.now()}).then(kdone);
});
}).catch(function(){km.textContent='Could not check the code. Try again.';});}catch(e){km.textContent='Could not check the code.';}
}
kg.addEventListener('click',ksub); ki.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault(); ksub();}});
var eh=mk('p','lvldesc','No code? Get one by email. We send a 6-digit code to the parent. We do not keep the email address.'), ei=mk('input','pininp'); ei.type='email'; ei.autocomplete='email'; ei.maxLength=80; ei.placeholder='Parent email'; ei.setAttribute('aria-label','Parent email');
var es=mk('button','btn small','Email me a code'); es.type='button';
var ec=mk('input','pininp'); ec.type='text'; ec.inputMode='numeric'; ec.maxLength=6; ec.autocomplete='one-time-code'; ec.placeholder='6-digit code'; ec.setAttribute('aria-label','6-digit code from the email'); ec.hidden=true;
var ev=mk('button','btn small primary','Check code'); ev.type='button'; ev.hidden=true;
es.addEventListener('click',function(){
var em=ei.value.trim(); if(!em){km.textContent='Type the parent email.'; return;} es.disabled=true; km.textContent='Sending...';
whoPost('/api/grownup/send',{email:em},function(){es.disabled=false; km.textContent='We sent a 6-digit code to '+em+'. It works for 10 minutes. Check spam if you cannot see it.'; ec.hidden=ev.hidden=false; try{ec.focus();}catch(_){}},function(er){es.disabled=false; km.textContent=er;});
});
function evs(){var c=ec.value.replace(/\D/g,''); if(c.length!==6){km.textContent='Type the 6 digits from the email.'; return;} whoPost('/api/grownup/check',{email:ei.value.trim(),code:c},function(j){ki.value=j.code; ksub();},function(er){km.textContent=er;});}
ev.addEventListener('click',evs); ec.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault(); evs();}});
planBody.appendChild(ki); planBody.appendChild(kg); planBody.appendChild(eh); planBody.appendChild(ei); planBody.appendChild(es); planBody.appendChild(ec); planBody.appendChild(ev); planBody.appendChild(km); return;
}
if(!pinOK){
var has=!!S.pin;
planBody.appendChild(mk('p','lvldesc',has?'Enter the PIN to change the play plan.':'Create a 4 digit PIN. You will need it to change the plan later, so keep it private. Type it twice so we know it is right.'));
function pinBox(lbl,ph){var i=mk('input','pininp'); i.type='password'; i.inputMode='numeric'; i.maxLength=4; i.autocomplete='off'; i.setAttribute('aria-label',lbl); if(ph)i.placeholder=ph; return i;}
var inp=pinBox(has?'PIN':'New PIN','PIN'), inp2=has?null:pinBox('New PIN again','Again');
var msg=mk('p','lvldesc'); msg.setAttribute('aria-live','polite');
var go=mk('button','btn primary',has?'Unlock':'Set PIN'); go.type='button';
var sh=mk('button','btn small','Show'); sh.type='button'; sh.setAttribute('aria-pressed','false');
sh.addEventListener('click',function(){var on=sh.getAttribute('aria-pressed')!=='true'; sh.setAttribute('aria-pressed',String(on)); sh.textContent=on?'Hide':'Show'; inp.type=on?'text':'password'; if(inp2)inp2.type=on?'text':'password';});
function submit(){var v=inp.value.replace(/\D/g,''); if(v.length!==4){msg.textContent='Use exactly 4 digits.'; return;}
if(!has){var v2=inp2.value.replace(/\D/g,''); if(v2.length!==4){msg.textContent='Type the PIN again in the second box.'; inp2.focus(); return;} if(v!==v2){msg.textContent='The two PINs are not the same. Try again.'; inp.value=''; inp2.value=''; inp.focus(); return;}
S.pin=hp(v); pinOK=true; save(); renderPlan(); return;}
if(hp(v)===S.pin){pinOK=true; renderPlan();} else {msg.textContent='That PIN is not right.'; inp.value='';}}
go.addEventListener('click',submit); [inp,inp2].forEach(function(x){if(x)x.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault(); submit();}});});
var row=mk('div','row2'); row.appendChild(inp); if(inp2)row.appendChild(inp2); row.appendChild(sh);
planBody.appendChild(row); planBody.appendChild(msg); planBody.appendChild(go); setTimeout(function(){try{inp.focus();}catch(_){}},50);
return;
}
rollDay(); var g=mk('div','plangrid'), cu=mk('div','plangrid');
function sec(t){g.appendChild(mk('div','label plansec',t));}
sec('1. Choose a mode');
var curMode=S.pl.k==='off'?'free':S.pl.qo?'quiz':S.pl.k==='class'?'class':S.pl.k==='home'?'home':'custom';
var MODES=[['free','Free play','No limits. Learners play and learn as they like.'],['quiz','Quiz only','Learners only answer questions. No building or play.'],['class','Quiz + short play (class)','Answer 10 questions, then 5 minutes of play. Up to 10 minutes of play.'],['home','Quiz + play (home)','Answer 15 questions, then 15 minutes of play. Up to 30 minutes a day.']];
MODES.forEach(function(m){var b=mk('button','btn plmode'); b.type='button'; b.setAttribute('aria-pressed',String(curMode===m[0])); b.innerHTML=''+m[1]+''+m[2]+'';
b.addEventListener('click',function(){
if(m[0]==='free')S.pl.k='off';
else if(m[0]==='quiz')S.pl=Object.assign(S.pl,{k:'class',qo:1,u:'q',uq:10,um:10,pm:5,dm:0,dmw:0,dme:0});
else if(m[0]==='class')S.pl=Object.assign(S.pl,{k:'class',qo:0,u:'q',uq:10,um:10,pm:5,dm:10,dmw:10,dme:10});
else S.pl=Object.assign(S.pl,{k:'home',qo:0,u:'q',uq:15,um:10,pm:15,dm:30,dmw:30,dme:30});
P=S.pl; planChanged(true);}); g.appendChild(b);});
if(curMode==='custom')g.appendChild(mk('p','lvldesc','You have customised the settings below.'));
sec('2. Timing');
g.appendChild(planSeg('Time for each question',[['Standard (by level)',0],['10 sec',10],['15 sec',15],['20 sec',20],['30 sec',30],['45 sec',45],['60 sec',60]],S.pl.qs||0,function(v){S.pl.qs=v; planChanged(false);}));
g.appendChild(mk('p','lvldesc',S.pl.qs?'Every question gets '+S.pl.qs+' seconds for every Learner on this plan. Short times practise speed. Long times give room to think.':'Standard gives each question a time that fits its level.'));
g.appendChild(planSeg('Overall time when you press Start class (minutes)',[['5',5],['10',10],['15',15],['20',20],['30',30],['40',40],['60',60]],S.pl.sl||40,function(v){S.pl.sl=v; planChanged(false);}));
var sc=mk('div','plangrid'); sc.appendChild(mk('div','label plansec','3. Run a class session'));
var sin=mk('input','fldin'); sin.placeholder='Class, e.g. P1A'; sin.maxLength=10; sin.value=S.me.cls||''; sin.setAttribute('aria-label','Class for the session');
var sst=mk('p','lvldesc',''); sst.setAttribute('aria-live','polite');
sc.appendChild(planSeg('Topic for this class',[['Any topic','all']].concat(TOPICS.map(function(t){return [t.name,t.id];})),S.pl.st||'all',function(v){S.pl.st=v; planChanged(false);}));
var od=mk('details','plancust'); od.open=!!(S.pl.ob&&S.pl.ob.length)||!!window.__planObj; od.addEventListener('toggle',function(){window.__planObj=od.open;}); od.appendChild(mk('summary','','Pick lesson objectives (optional)'));
od.appendChild(planSeg('Level',LEVELS.map(function(L,i){return [L.name,i];}),S.pl.ol===undefined?-1:S.pl.ol,function(v){S.pl.ol=v; S.pl.ob=[]; planChanged(false);}));
if(S.pl.ol!==undefined&&S.pl.ol>=0){
var ol=mk('div','vlist'); S.pl.ob=S.pl.ob||[];
OBJ.forEach(function(o,i){ if(o[0]!==S.pl.ol)return; var on=S.pl.ob.indexOf(i)>=0, b=mk('button','btn small',(on?'\u2713 ':'')+'I can: '+o[1].charAt(0).toLowerCase()+o[1].slice(1)); b.type='button'; b.setAttribute('aria-pressed',String(on)); b.style.textAlign='left';
b.addEventListener('click',function(){var k=S.pl.ob.indexOf(i); if(k>=0)S.pl.ob.splice(k,1); else if(S.pl.ob.length<12)S.pl.ob.push(i); planChanged(false);}); ol.appendChild(b);});
od.appendChild(ol);
var smp=mk('p','lvldesc',S.pl.ob.length?'':'Tick one or more objectives. Learners then get only those questions.');
if(S.pl.ob.length){var qs=objSample(S.pl.ol,objKs(S.pl.ob)); smp.textContent=S.pl.ob.length+' selected. Sample question: '+(qs?qs.text:'(none)')+' Learners get only these questions at '+LEVELS[S.pl.ol].name+'.';}
od.appendChild(smp);
var clr=mk('button','btn small','Clear objectives (use topic instead)'); clr.type='button'; clr.addEventListener('click',function(){S.pl.ob=[]; S.pl.ol=undefined; planChanged(false);}); od.appendChild(clr);
}
sc.appendChild(od);
sc.appendChild(planSeg('Learners wait for my Start',[['Yes',true],['No',false]],!!S.pl.sr,function(v){S.pl.sr=v; planChanged(false);}));
var srow=mk('div','row2'), go1=mk('button','btn primary small','Start class'), go2=mk('button','btn small','End class'); go1.type=go2.type='button';
go1.addEventListener('click',function(){sessWrite(sin.value,'live',function(t){sst.textContent=t;});});
go2.addEventListener('click',function(){sessWrite(sin.value,'ended',function(t){sst.textContent=t;});});
srow.appendChild(sin); srow.appendChild(go1); srow.appendChild(go2); sc.appendChild(srow); sc.appendChild(sst); g.appendChild(sc);
var pubRow=mk('div','row2'), pcls=mk('input','fldin'); pcls.placeholder='Class, e.g. P1A'; pcls.maxLength=10; pcls.value=S.me.cls||''; pcls.setAttribute('aria-label','Class to publish the plan to');
var pub=mk('button','btn small','Publish plan to class'); pub.type='button'; var pm=mk('p','lvldesc',''); pm.setAttribute('aria-live','polite');
pub.addEventListener('click',function(){planPublish(pcls.value,function(t){pm.textContent=t;});});
pubRow.appendChild(pcls); pubRow.appendChild(pub); g.appendChild(pubRow); g.appendChild(pm);
g.appendChild(planSeg('Where are you learning?',[['Singapore mode',false],['Global mode',true]],S.pl.place==='any',function(v){S.pl.place=v?'any':'sg'; planChanged(false);}));
g.appendChild(mk('p','lvldesc','Singapore mode adds Singapore topics, Know your Singlish, Visit Singapore and the school holiday calendar. Global mode is for learners anywhere in the world: it hides them and keeps everything else. Number, science, language and reading practice stay the same.'));
g.appendChild(planSeg('Which clock decides the day?',[['Singapore time',true],['This device’s own time',false]],S.pl.sgt!==false,function(v){S.pl.sgt=v; planChanged(false);}));
if(S.pl.k!=='off'){
cu.appendChild(planSeg('Unlock play by',[['Questions','q'],['Learning minutes','m']],S.pl.u,function(v){setCustom(function(p){p.u=v;});}));
if(S.pl.u==='q')cu.appendChild(planSeg('Questions to unlock play',[['5',5],['10',10],['15',15],['20',20],['30',30],['40',40],['50',50],['60',60]],S.pl.uq,function(v){setCustom(function(p){p.uq=v;});}));
else cu.appendChild(planSeg('Learning minutes to unlock play',[['3',3],['5',5],['10',10],['15',15],['20',20],['30',30]],S.pl.um,function(v){setCustom(function(p){p.um=v;});}));
cu.appendChild(planSeg('Play minutes per round',[['5',5],['10',10],['15',15],['20',20]],S.pl.pm,function(v){setCustom(function(p){p.pm=v;});}));
cu.appendChild(planSeg('Play limit on school days (minutes)',[['None',0],['10',10],['15',15],['20',20],['30',30]],ownLimit(false),function(v){setCustom(function(p){p.dmw=v;});}));
cu.appendChild(planSeg('Play limit on weekends and school holidays (minutes)',[['None',0],['15',15],['30',30],['45',45],['60',60],['90',90]],ownLimit(true),function(v){setCustom(function(p){p.dme=v;});}));
cu.appendChild(planSeg('Follow the Singapore school holiday calendar',[['Yes',true],['No',false]],S.pl.cal!==false,function(v){setCustom(function(p){p.cal=v;});}));
cu.appendChild(mk('p','lvldesc','Today counts as '+(restDay()?'a weekend or school holiday':'a school day')+', so the limit today is '+baseLimit(restDay())+' minutes. The holiday calendar covers 2026 and 2027 (Ministry of Education dates). Learning time (questions and courses) is never limited.'));
if(S.plat&&S.plat.force)cu.appendChild(mk('p','lvldesc','The platform owner has set a maximum of '+S.plat.dmw+' minutes on school days and '+S.plat.dme+' minutes on weekends and holidays. Your limits cannot go above this.'));
var gd=mk('p','lvldesc','Singapore guidance (Ministry of Health, Grow Well SG, 21 January 2025): children aged 3 to 6 should have less than 1 hour of screen time a day outside school, and children aged 7 to 12 less than 2 hours a day unless it is for schoolwork. Play time in this game is only part of a child’s total screen time.'); gd.style.fontStyle='italic'; cu.appendChild(gd);
var row=mk('div','row2'), bonus=mk('button','btn small','Add 5 bonus play minutes today'+(S.day.b?' (added '+S.day.b+')':'')); bonus.type='button';
bonus.addEventListener('click',function(){S.day.b=(S.day.b||0)+5; planChanged(false);});
var rs=mk('button','btn small','Reset today’s play time'); rs.type='button'; rs.addEventListener('click',function(){S.day.pm=0; S.day.b=0; S.rd.pm=0; planChanged(false);});
row.appendChild(bonus); row.appendChild(rs); cu.appendChild(row);
cu.appendChild(mk('p','lvldesc','Today: '+(S.day.q||0)+' questions, '+(S.day.c||0)+' correct, '+Math.round(S.day.lm/60)+' min learning, '+Math.round(S.day.pm/60)+' min play.'));
}
cu.appendChild(planSeg('Break monster every (minutes)',[['Off',0],['10',10],['15',15],['20',20],['30',30]],S.pl.bk===undefined?20:S.pl.bk,function(v){S.pl.bk=v; planChanged(false);}));
cu.appendChild(planSeg('Energy (answers refill it)',[['Off',0],['Gentle',1],['Normal',2],['Strict',3]],enMode(),function(v){S.pl.en=v; if(!v){energy=100;} drawEnergy(); planChanged(false);}));
cu.appendChild(planSeg('Reading level (Reading subject)',[['Learner chooses',0],['Emerging',1],['Developing',2],['Fluent',3],['Advanced',4]],S.pl.rd||0,function(v){S.pl.rd=v; planChanged(false);}));
cu.appendChild(mk('div','label','Menu items Learners can use (tap to turn off or on)'));
var mrow=mk('div','vlist'); S.pl.hide=Array.isArray(S.pl.hide)?S.pl.hide:[];
MENUF.forEach(function(f){var off=S.pl.hide.indexOf(f[0])>=0, b=mk('button','btn small',(off?'\u2717 Off: ':'\u2713 On: ')+f[1]); b.type='button'; b.setAttribute('aria-pressed',String(!off));
b.addEventListener('click',function(){var k=S.pl.hide.indexOf(f[0]); if(k>=0)S.pl.hide.splice(k,1); else S.pl.hide.push(f[0]); planChanged(false);}); mrow.appendChild(b);});
cu.appendChild(mrow);
cu.appendChild(planSeg('Rain and weather',[['Rainy days on',1],['Off',0]],S.pl.rn===0?0:1,function(v){S.pl.rn=v; if(!v)rainStop(); planChanged(false);}));
cu.appendChild(planSeg('Break length',[['20 sec (eyes only)',20],['30 sec',30],['1 min',60],['2 min',120],['5 min',300]],S.pl.bs||60,function(v){S.pl.bs=v; planChanged(false);}));
var tb=mk('button','btn small','Try a break now'); tb.type='button'; tb.addEventListener('click',function(){closeModals(); startBreak();}); cu.appendChild(tb);
cu.appendChild(planSeg('Streak needed for a visit pass',[['5',5],['10',10],['15',15],['20',20]],S.pl.vk||10,function(v){S.pl.vk=v; planChanged(false);}));
cu.appendChild(planSeg('Visit length (minutes)',[['2',2],['3',3],['5',5]],S.pl.vm||3,function(v){S.pl.vm=v; planChanged(false);}));
cu.appendChild(planSeg('Sky and clock',[['Slow day (1 hour)',"slow"],['Medium day (30 min)',"mid"],['Fast day (12 min)',"cycle"],['Real time',"real"],['Off',"off"]],S.pl.dn||'slow',function(v){S.pl.dn=v; planChanged(false); skyLast=-1; clockKey=-1; applyTheme();}));
var cd=mk('details','plancust'); cd.open=!!window.__planCust; cd.addEventListener('toggle',function(){window.__planCust=cd.open;}); cd.appendChild(mk('summary','','4. Customise: show every setting')); cd.appendChild(cu);
g.appendChild(cd);
var row2=mk('div','row2'), pc=mk('button','btn small','Change PIN'); pc.type='button'; pc.addEventListener('click',function(){S.pin=''; pinOK=false; save(); renderPlan();});
var dn=mk('button','btn primary','Done'); dn.type='button'; dn.addEventListener('click',function(){planModal.hidden=true; pinOK=false; applyGate();});
var qk=mk('div','plangrid');
qk.appendChild(mk('h3','','Quick setup for parents'));
var onP=S.pl.k!=='off', dmv=(ownLimit(false)===ownLimit(true))?ownLimit(false):-1;
qk.appendChild(planSeg('Play time each day (minutes)',[['10',10],['15',15],['20',20],['30',30],['45',45],['60',60]],onP?dmv:-1,function(v){
if(S.pl.k==='off'||S.pl.qo)S.pl=Object.assign(S.pl,{k:'home',qo:0,u:'q',uq:S.pl.uq||10,um:10});
S.pl.dmw=v; S.pl.dme=v; S.pl.dm=v; S.pl.pm=Math.min(v,15); S.pl.k='custom'; P=S.pl; planChanged(false);}));
qk.appendChild(planSeg('Questions to answer before each play round',[['5',5],['10',10],['15',15],['20',20]],(onP&&S.pl.u==='q')?S.pl.uq:-1,function(v){
if(S.pl.k==='off'||S.pl.qo)S.pl=Object.assign(S.pl,{k:'home',qo:0,dm:30,dmw:30,dme:30,pm:15});
S.pl.u='q'; S.pl.uq=v; S.pl.k='custom'; P=S.pl; planChanged(false);}));
qk.appendChild(planSeg('Question crates in solo play (minutes between drops)',[['Off',0],['5',5],['10',10],['15',15]],dropIv(),function(v){S.pl.dp=v; DR.t=0; planChanged(false);}));
qk.appendChild(planSeg('Explorer quests (goals that show Learners around the game)',[['On',1],['Off',0]],xqOn()?1:0,function(v){S.pl.xq=v; planChanged(false); xqRender();}));
qk.appendChild(planSeg('Help after a wrong answer (why, steps, retest)',[['On',1],['Off',0]],coachOn()?1:0,function(v){S.pl.co=v; planChanged(false);}));
qk.appendChild(mk('p','lvldesc',onP?('Right now: your child answers '+(S.pl.u==='q'?S.pl.uq+' questions':S.pl.um+' minutes of learning')+', then plays up to '+Math.min(S.pl.pm,baseLimit(restDay()))+' minutes. Play stops at '+baseLimit(restDay())+' minutes today. Learning time is never limited. Five correct in a row at their own level adds 1 bonus minute.'):'Right now there are no limits. Tap a number above to set a daily play limit.'));
planBody.appendChild(qk);
try{accPlanBtn(planBody);}catch(e){}
var sn=mk('div','plangrid'); sn.appendChild(mk('h3','','Support for learners (SEN)'));
sn.appendChild(mk('p','lvldesc','Settings for Learners who need extra support, such as dyslexia or slower processing. Set them for the Learner who uses this device.'));
var SEN=[['nt','No timer'],['ra','Read each question aloud automatically'],['big','Larger text and wider spacing'],['cream','Soft cream background'],['gentle','Gentle mode (no blocks lost, no storms)']];
SEN.forEach(function(o){sn.appendChild(planSeg(o[1],[['On',1],['Off',0]],senOn(o[0])?1:0,function(v){senMap()[o[0]]=v; planChanged(false);}));});
sn.appendChild(planSeg('Answer choices',[['4',0],['3',3],['2',2]],(S.pl.sen&&S.pl.sen.fw)||0,function(v){senMap().fw=v; planChanged(false);}));
var allOn=['nt','ra','big','cream','gentle'].every(senOn)&&senMap().fw===3, noneOn=['nt','ra','big','cream','gentle'].every(function(k){return !senOn(k);})&&!senMap().fw;
sn.appendChild(planSeg('Quick preset',[['Off',0],['Recommended support',1]],allOn?1:noneOn?0:-1,function(v){var z=senMap(); ['nt','ra','big','cream','gentle'].forEach(function(k){z[k]=v;}); z.fw=v?3:0; planChanged(false);}));
sn.appendChild(mk('p','lvldesc','Read aloud uses your device voice, and some browsers need one tap on "Read to me" first.'));
planBody.appendChild(sn);
var adv=mk('details','plancust'); adv.open=!!window.__planAdv; adv.addEventListener('toggle',function(){window.__planAdv=adv.open;}); adv.appendChild(mk('summary','','More settings for teachers (modes, class sessions, every option)')); adv.appendChild(g); planBody.appendChild(adv);
row2.appendChild(pc); row2.appendChild(dn); planBody.appendChild(row2);
}
document.getElementById('planBtn').addEventListener('click',openPlan);
document.getElementById('planBack').addEventListener('click',function(){planModal.hidden=true; pinOK=false; applyGate();});
setTimeout(function(){rollDay(); enforceKid(); applyGate(); updateChip();},0);
function renderDiff(){
var seg=document.getElementById('dfSeg'); if(!seg)return; seg.innerHTML='';
var un=!!S.du[S.level]; if(S.diff==='hard'&&!un)S.diff='normal';
[['easy','Easy'],['normal','Normal'],['hard',un?'Challenge':'Challenge (locked)']].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='btn'; b.textContent=o[1]; b.setAttribute('aria-pressed',String(S.diff===o[0]));
if(o[0]==='hard'&&!un){b.setAttribute('aria-disabled','true'); b.style.opacity='.6';}
b.addEventListener('click',function(){ if(o[0]==='hard'&&!un){document.getElementById('dfInfo').textContent=dfLock(); return;} S.diff=o[0]; lastKey=''; renderDiff(); showQuestion(); save(); });
seg.appendChild(b);
});
var info={easy:'Warm-up: some questions from one level down, more time, fewer blocks.',normal:'Standard questions for this level.',hard:'Challenge: some questions from the next level up, less time, more blocks and more rare finds.'};
document.getElementById('dfInfo').textContent=info[S.diff]+(un?'':' '+dfLock());
}
function dfLock(){var st=S.lr[S.level]||'', c=(st.match(/1/g)||[]).length; return 'Challenge unlocks when you get 13 of your last 15 right at '+LEVELS[S.level].name+' on Normal. Now: '+c+' of '+st.length+' right.';}
function renderTopics(){
var box=document.getElementById('topics'); if(!box)return; box.innerHTML='';
var oa=objActive(); if(oa){var p0=document.createElement('p'); p0.className='lvldesc'; p0.innerHTML='Today\u2019s lesson objectives (set by your teacher, '+esc(LEVELS[oa.lv].name.split(' \u00b7')[0])+'): '+oa.ids.map(function(i){return '\u2022 '+esc(OBJ[i][1]);}).join(' '); box.appendChild(p0); return;}
var m=topicMap(S.level), lk=topicLocked(), cur=lk?effTopic():(S.topic||'all'); if(!lk&&cur!=='all'&&!(cur==='wp'&&S.level<=6)&&!m[cur]){S.topic='all'; cur='all';}
var ot=OBJT.filter(function(t){return t.lv===S.level&&m[t.id];});
var list=[{id:'all',name:'All topics'}].concat(ot.length?ot:TOPICS.filter(function(t){return m[t.id];})).concat(S.level<=6?[WPT]:[]);
list.forEach(function(t){
var b=document.createElement('button'); b.type='button'; b.className='btn lvl'; b.textContent=t.name; b.setAttribute('aria-pressed',String(t.id===cur)); if(lk)b.disabled=true;
b.addEventListener('click',function(){if(topicLocked())return; S.topic=t.id; lastKey=''; renderTopics(); updateLevels(); showQuestion(); save();});
box.appendChild(b);
});
if(lk){var nt=document.createElement('p'); nt.className='lvldesc'; nt.textContent='Your teacher picked today\u2019s topic: '+((TOPICS.concat(OBJT).filter(function(t){return t.id===cur;})[0]||{name:'All topics'}).name)+'.'; box.appendChild(nt);}
}
function updateLevels(){
try{mbLevelUI();}catch(e){}
lvBtns.forEach(function(b,i){b.setAttribute('aria-pressed',String(i===S.level));}); try{var py=pyYear(); pyBtns.forEach(function(p){p[0].setAttribute('aria-pressed',String(p[1]===py));});}catch(_){}
tmBtns.forEach(function(p){p[0].setAttribute('aria-pressed',String(p[1]===S.tm));}); toBtns.forEach(function(p){p[0].setAttribute('aria-pressed',String(p[1]===S.to));});
anBtns.forEach(function(p){p[0].setAttribute('aria-pressed',String(p[1]===S.ans));});
grBtns.forEach(function(p){p[0].setAttribute('aria-pressed',String(p[1]===(S.gr||0)));});
document.getElementById('lvldesc').textContent=LEVELS[S.level].desc; if(!updateLevels.busy){updateLevels.busy=1; renderTopics(); renderDiff(); updateLevels.busy=0;}
document.getElementById('lvlTag').textContent=SC?(SCMOD==='D'?'Design and Technology \u00b7 Choose the best answer':(SCMOD==='Y'||SCMOD.slice(0,2)==='y:')?'Lower Secondary History \u00b7 Choose the best answer':(SCMOD==='G'||SCMOD.slice(0,2)==='g:')?'Lower Secondary Geography \u00b7 Choose the best answer':(SCMOD==='S'||SCMOD.slice(0,2)==='s:')?'Social Studies \u00b7 Choose the best answer':(SCMOD==='T'||SCMOD.slice(0,2)==='l:')?'G1 Literature in English \u00b7 Choose the best answer':(SCMOD==='W'||SCMOD.slice(0,2)==='w:')?'Singapore and the world \u00b7 Choose the best answer':/^sg(:|$)/.test(SCMOD)?'Know your Singlish \u00b7 Choose the best answer':/^f(:|$)/.test(SCMOD)?'Fire safety \u00b7 Choose the best answer':/^en(:|$)/.test(SCMOD)?'English \u00b7 Choose the best answer':/^e(:|$)/.test(SCMOD)?'Everyday life \u00b7 Choose the best answer':SCMOD.slice(0,2)==='r:'?'Reading comprehension \u00b7 Choose the best answer':(SCMOD.charAt(0)==='x'?(SCMOD.charAt(1)==='3'?'Upper Secondary Science':'Lower Secondary Science'):'Primary Science')+' \u00b7 Choose the best answer'):MU?'Music \u00b7 Choose the best answer':G1S?'Secondary Maths \u00b7 '+(GL?'G'+GL:'')+' \u00b7 Choose the best answer':EP?'Aura skills \u00b7 Choose the best answer':CP?(CP.am?'Additional Maths (starter) \u00b7 Choose the best answer':'Sec 3 G1 Computing \u00b7 Choose the best answer'):(objActive()?LEVELS[objActive().lv]:LEVELS[S.level]).name+((effTopic()!=='all')?' \u00b7 '+(TOPICS.filter(function(t){return t.id===effTopic();})[0]||{name:''}).name:'')+' \u00b7 '+(S.ans==='mc'?'Choose':S.ans==='key'?'Keypad':'Draw')+' \u00b7 '+(S.tm===1.6?'Relaxed':S.tm===.7?'Fast':'Normal');
try{if(!SC&&S.level<7&&S.gr>=1&&S.gr<=6){var tg=document.getElementById('lvlTag'), bn=LEVELS[S.level].name.split(' \u00b7')[0]; if(tg.textContent.indexOf(bn)===0)tg.textContent='P'+S.gr+tg.textContent.slice(bn.length);}}catch(_){}
var due=(S.st.a-(S.st.cl||0))>=15, cb=document.getElementById('cpLvBtn');
cb.classList.toggle('primary',due); cb.textContent=due?'Level checkpoint (due)':'Level checkpoint';
document.getElementById('cpInfo').textContent=S.cp
? 'Last checkpoint: '+LEVELS[S.cp.lvl].name+' ('+S.cp.score+' of '+S.cp.total+' right first time). A checkpoint is 6 short questions that find the right level.'
: 'A checkpoint is 6 short questions that find the right level. No blocks are lost during a checkpoint.';
}
/* checkpoint */
var setupOpen=false;
function setSetupVisible(v){el.setup.style.display=(v&&setupOpen)?'grid':'none'; document.getElementById('meter').style.display=v?'flex':'none'; document.getElementById('setBtn').hidden=!v;}
function toggleSetup(open){ setTimeout(fitPad,30);setupOpen=open; document.getElementById('setBtn').setAttribute('aria-expanded',String(open)); setSetupVisible(mode!=='check'); if(!open){try{document.getElementById('setBtn').focus({preventScroll:true});}catch(_){}}}
document.getElementById('setBtn').addEventListener('click',function(){toggleSetup(!setupOpen);});
document.getElementById('setClose').addEventListener('click',function(){toggleSetup(false);});
function startCheckpoint(){
mode='check'; diag={n:0,total:6,lvl:S.level,hist:[],firstOk:0}; setupOpen=false; document.getElementById('setBtn').setAttribute('aria-expanded','false'); setSetupVisible(false); lastKey=''; showQuestion();
}
function diagRecord(ok){
diag.hist.push({lvl:diag.lvl,ok:ok}); if(ok)diag.firstOk++;
diag.lvl=ok?Math.min(Math.max(6,S.level),diag.lvl+1):Math.max(0,diag.lvl-1); diag.n++;
}
function showResult(){
stopTimer();
var h=diag.hist.slice(2); if(!h.length)h=diag.hist;
var est=Math.round(h.reduce(function(s,x){return s+x.lvl;},0)/h.length); est=Math.max(0,Math.min(Math.max(6,S.level),est));
diag.est=est; S.cp={lvl:est,score:diag.firstOk,total:diag.total}; S.st.cl=S.st.a;
el.title.textContent='Checkpoint result'; el.timer.hidden=true; el.ans.hidden=true; document.getElementById('entry').hidden=true; el.vis.hidden=true;
el.kick.textContent='Suggested level'; el.qt.textContent=LEVELS[est].name; el.qt.classList.add('long');
el.fb.textContent='You got '+diag.firstOk+' of '+diag.total+' right first time. Starting here should feel challenging but fair. Bonus: 8 blocks for finishing.'; el.fb.className='fb good';
giveRewards(8); sfx('done'); updateHotbar(); updateGoals(); updateStats(); save();
el.next.hidden=true; el.up.hidden=true; el.cpUse.hidden=false; el.cpAgain.hidden=false;
try{el.cpUse.focus({preventScroll:true});}catch(_){}
}
document.getElementById('cpLvBtn').addEventListener('click',startCheckpoint);
el.cpAgain.addEventListener('click',startCheckpoint);
el.cpUse.addEventListener('click',function(){S.level=diag.est; mode='play'; diag=null; setSetupVisible(true); updateLevels(); lastKey=''; showQuestion(); save();});
/* ---------- crafting ---------- */
var RECIPES=[
{out:'ctable',n:1,ing:{plank:4},anywhere:true},
{out:'bed',n:1,ing:{plank:3,leaves:3}},
{out:'bookshelf',n:1,ing:{plank:6,log:2},need:1},
{out:'chest',n:1,ing:{plank:8}},
{out:'lamp',n:2,ing:{glass:2,log:1},need:1},
{out:'stonebrick',n:4,ing:{stone:4}},
{out:'furnace',n:1,ing:{stone:8},need:2},
{out:'terracotta',n:4,ing:{brick:2,plank:1},need:2},
{out:'axe',n:1,ing:{plank:3,stone:2}},
{out:'boat',n:1,ing:{plank:6,log:2},need:1},
{out:'sofa',n:1,ing:{plank:4,c_pink:3}},
{out:'dtable',n:1,ing:{plank:6,log:2}},
{out:'chair',n:2,ing:{plank:3,log:1}},
{out:'rug',n:2,ing:{c_red:2,c_white:2}},
{out:'plant',n:1,ing:{leaves:4,brick:1}},
{out:'curtain',n:2,ing:{c_pink:2,plank:1}},
{out:'fridge',n:1,ing:{c_white:4,glass:1,stone:2}},
{out:'flowers',n:2,ing:{leaves:3,c_pink:1,c_yellow:1}},
{out:'water',n:4,ing:{glass:1},anywhere:true},
{out:'brick',n:4,ing:{dirt:2,water:1}},
{out:'crop_tomato0',n:3,ing:{leaves:2,dirt:1},anywhere:true},
{out:'crop_chilli0',n:3,ing:{leaves:2,dirt:1},anywhere:true},
{out:'crop_kangkong0',n:3,ing:{leaves:2,dirt:1},anywhere:true},
{out:'crop_sunflower0',n:3,ing:{leaves:2,dirt:1},anywhere:true}
];
var SCI={
water:'Water covers most of the Earth. Plants, animals and people all need it. A glass of water is a good drink for the body.',
ctable:'A table is made of wood, and wood comes from trees. Plants give us many useful materials.',
bed:'Sleep gives your body time to rest and grow. Children your age need about 9 to 11 hours each night.',
bookshelf:'Paper is made from wood. Wood comes from trees, so books start out as plants.',
chest:'Wood is a strong material. People choose materials for their properties, such as strength and hardness.',
lamp:'A lamp is a source of light. We see things when light from them, or light bouncing off them, enters our eyes. Glass is transparent, so light passes through it.',
stonebrick:'Rock is hard and strong, so it is a good material for building walls.',
furnace:'An oven gives out heat. Heat always moves from a hotter object to a colder one.',
terracotta:'Clay becomes hard when it is heated in a very hot oven. Heat can change a material so it cannot go back.',
axe:'A bat is a lever. The long handle means a small push from your hand makes the far end swing fast.',
boat:'A wooden boat floats because the wood and the air inside it weigh less than the water they push aside.'
};
RECIPES.push({out:'wardrobe',n:1,ing:{plank:6}},{out:'tvstand',n:1,ing:{plank:4,glass:1}},{out:'desk',n:2,ing:{plank:4,log:1}},{out:'whiteboard',n:1,ing:{c_white:4,plank:2,glass:1}},{out:'schoolbag',n:1,ing:{c_blue:3,plank:1}},{out:'books',n:2,ing:{plank:2,c_red:1,c_blue:1}},{out:'pencilcase',n:2,ing:{c_yellow:2,plank:1}},{out:'abacus',n:1,ing:{plank:4,log:2,c_red:2}},{out:'clock',n:1,ing:{glass:1,plank:2,c_white:1}},{out:'globe',n:1,ing:{glass:2,c_blue:2,leaves:2}},{out:'trophy',n:1,ing:{c_yellow:4,stone:2},need:2},{out:'flag',n:2,ing:{c_red:2,c_white:2,log:1}},{out:'computer',n:1,ing:{glass:2,stone:2,c_black:2},need:2},{out:'bell',n:1,ing:{c_yellow:2,log:2,brick:1}});
Object.assign(SCI,{desk:'A flat desk top holds your book still so you can write neatly. Wood is strong enough to carry heavy books.',whiteboard:'Whiteboard markers use ink that wipes off a smooth, shiny surface but soaks into paper.',schoolbag:'A heavy bag can hurt your back. Pack only what you need and wear both straps.',books:'Paper is made from wood. Reading a little every day helps you learn new words.',pencilcase:'The \u201clead\u201d in a pencil is really graphite, a soft grey form of carbon.',abacus:'An abacus uses beads to count in ones, tens and hundreds. People used it long before calculators.',clock:'A clock face shows 12 hours. The long hand goes round once in 60 minutes, and 60 minutes make 1 hour.',globe:'A globe is a small model of the Earth. About 7 out of every 10 parts of the Earth\u2019s surface are water.',trophy:'A trophy celebrates effort. Practising a little every day makes you better.',flag:'The Singapore flag has red for equality and brotherhood, white for purity, a crescent for a young nation and five stars for democracy, peace, progress, justice and equality.',computer:'A computer follows instructions called a program. People who write programs are called programmers.',bell:'Sound is made when something vibrates. A bell rings because the metal shakes back and forth.'});
CLR.forEach(function(c){RECIPES.push({out:'c_'+c[0],n:4,ing:{plank:1},anywhere:true});});
RECIPES.push(
{out:'keyboard',n:1,ing:{c_white:2,c_grey:2,plank:1}},
{out:'mouse',n:1,ing:{c_white:2,c_grey:1}},
{out:'monitor',n:1,ing:{glass:2,c_black:2,c_blue:1}},
{out:'laptop',n:1,ing:{glass:1,c_grey:3,c_black:1}},
{out:'router',n:1,ing:{c_white:3,c_cyan:1,glass:1}},
{out:'printer',n:1,ing:{c_grey:3,c_white:2,c_black:1}},
{out:'usbdrive',n:2,ing:{c_blue:1,c_grey:1}},
{out:'speaker',n:2,ing:{c_black:2,c_grey:1,plank:1}},
{out:'server',n:1,ing:{c_grey:4,c_black:2,glass:1},need:1},
{out:'cloud',n:1,ing:{c_white:4,c_cyan:2},need:1},
{out:'robot',n:1,ing:{c_grey:4,c_cyan:2,c_red:1,glass:1},need:2}
);
Object.assign(SCI,{
keyboard:'A keyboard is an input device. Input devices send data into a computer. Keyboards, mice and microphones are all input devices.',
mouse:'A mouse is an input device. When you move it, a pointer moves on the screen, and a click selects what the pointer is on.',
monitor:'A monitor is an output device. Output devices show or play the results of what a computer has processed, like screens, speakers and printers.',
laptop:'A laptop is a computer with a screen, keyboard and battery all in one. The processor (CPU) is its brain: it follows instructions and does the calculations.',
router:'A router joins the devices in a network and passes data between them. Wi-Fi sends that data through the air using radio waves, so no cable is needed.',
printer:'A printer is an output device. It puts text and pictures on paper. A printout is called a hard copy.',
usbdrive:'A USB drive is a storage device. Storage keeps your files even when the computer is switched off. Always eject it safely before you pull it out.',
speaker:'A speaker is an output device. It turns the sound data from a computer into sound you can hear.',
server:'A server is a powerful computer that shares files, websites and programs with many other computers over a network.',
cloud:'Cloud storage keeps your files on servers on the internet, so you can open them from many devices. Use a strong password to keep them safe.',
robot:'A robot follows a program, which is a set of instructions. Its sensors are inputs, and its motors are outputs.'
});
var qty=RECIPES.map(function(){return 1;}), craftNear=false, craftTab='build';
var CRCAT={build:['ctable','chest','stonebrick','furnace','terracotta','axe','boat','water','brick'],garden:['crop_tomato0','crop_chilli0','crop_kangkong0','crop_sunflower0'],home:['bed','bookshelf','lamp','sofa','dtable','chair','rug','plant','curtain','fridge','flowers'],school:['desk','whiteboard','schoolbag','books','pencilcase','abacus','clock','globe','trophy','flag','computer','bell'],tech:['keyboard','mouse','monitor','laptop','router','printer','usbdrive','speaker','server','cloud','robot']};
function crCat(o){for(var k in CRCAT)if(CRCAT[k].indexOf(o)>=0)return k; return 'colour';}
function crCan(r,q){var usable=r.anywhere||craftNear, mlock=!S.tu&&(r.need||0)>masteredCount(); if(!usable||mlock)return false; return Object.keys(r.ing).every(function(k){return (inv[BY[k]]||0)>=r.ing[k]*q;});}
function renderCraft(){
var box=document.getElementById('recipes'); box.innerHTML='';
document.getElementById('craftNote').textContent=craftNear?'You are at a workbench. Pick how many to make. The ingredient totals show the multiplication.':'No workbench nearby. Walk up to one, or make a workbench first (4 planks) and place it.';
document.getElementById('craftNote').textContent+=' Skills mastered: '+masteredCount()+'. Mastering skills unlocks special items.';
var tabs=document.createElement('div'); tabs.className='seg'; tabs.style.cssText='margin-bottom:8px;flex-wrap:wrap';
var nowN=RECIPES.filter(function(r,ri){return !furnOff(BY[r.out])&&crCan(r,qty[ri]);}).length;
[['now','Can make now ('+nowN+')'],['build','Build'],['home',CREATIVE_FURNITURE?'Home':'Plants'],['school','School'],['tech','Computing'],['colour','Colours']].forEach(function(t){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=t[1]; b.setAttribute('aria-pressed',String(craftTab===t[0])); b.addEventListener('click',function(){craftTab=t[0]; renderCraft();}); tabs.appendChild(b);});
box.appendChild(tabs);
var shown=0;
RECIPES.forEach(function(r,ri){
if(furnOff(BY[r.out]))return;
if(craftTab==='now'){if(!crCan(r,qty[ri]))return;} else if(crCat(r.out)!==craftTab)return; shown++;
var out=typeById(BY[r.out]), q=qty[ri], can=true, usable=r.anywhere||craftNear, mlock=!S.tu&&(r.need||0)>masteredCount();
var card=document.createElement('div'); card.className='rec'+(usable?'':' locked');
card.appendChild(iconCanvas(r.out));
var info=document.createElement('div'); var h3=document.createElement('h3'); h3.textContent=out.name; info.appendChild(h3);
var mk=document.createElement('div'); mk.className='mk'; mk.textContent=q+' × '+r.n+' = makes '+(q*r.n)+' '+out.name; info.appendChild(mk); card.appendChild(info);
var ings=document.createElement('div'); ings.className='ings';
Object.keys(r.ing).forEach(function(k){
var need=r.ing[k]*q, have=inv[BY[k]]||0, ok=have>=need; if(!ok)can=false;
var s=document.createElement('span'); s.className='ing '+(ok?'ok':'no'); s.textContent=q+' × '+r.ing[k]+' = '+need+' '+typeById(BY[k]).name+' (you have '+have+')'; ings.appendChild(s);
});
card.appendChild(ings);
if(SCI[r.out]){var sc=document.createElement('div'); sc.className='sci'; var sb=document.createElement('b'); sb.textContent='Did you know?'; sc.appendChild(sb); sc.appendChild(document.createTextNode(SCI[r.out])); card.appendChild(sc);}
var row=document.createElement('div'); row.className='row';
var qd=document.createElement('div'); qd.className='qty';
var minus=document.createElement('button'); minus.type='button'; minus.className='btn'; minus.textContent='−'; minus.setAttribute('aria-label','Make fewer '+out.name); minus.disabled=q<=1;
var num=document.createElement('b'); num.textContent=q;
var plus=document.createElement('button'); plus.type='button'; plus.className='btn'; plus.textContent='+'; plus.setAttribute('aria-label','Make more '+out.name); plus.disabled=q>=9;
minus.addEventListener('click',function(){qty[ri]=Math.max(1,qty[ri]-1); renderCraft();});
plus.addEventListener('click',function(){qty[ri]=Math.min(9,qty[ri]+1); renderCraft();});
qd.appendChild(minus); qd.appendChild(num); qd.appendChild(plus); row.appendChild(qd);
var go=document.createElement('button'); go.type='button'; go.className='btn primary go';
go.textContent=mlock?'Master '+r.need+' skill'+(r.need>1?'s':'')+' to unlock':usable?(can?'Make':'Not enough yet'):'Needs a workbench'; go.disabled=!(can&&usable&&!mlock);
go.addEventListener('click',function(){
Object.keys(r.ing).forEach(function(k){inv[BY[k]]-=r.ing[k]*q;});
inv[BY[r.out]]=(inv[BY[r.out]]||0)+r.n*q; sfx('craft');
document.getElementById('craftFb').textContent='Made '+(r.n*q)+' '+out.name+(r.out==='axe'?'. It is selected. Left-click a pest to swing it.':r.out==='boat'?'. Choose the Ocean terrain, then right-click open water to launch it.':'. Find it on the '+PAGES[pageOf(BY[r.out])].name+' page of your hotbar (press Tab or tap the page button).'); if(r.out==='axe')selectType(AXE); if(r.out==='boat')selectType(BOAT); document.getElementById('craftFb').className='fb good';
updateHotbar(); updateGoals(); save(); renderCraft();
});
row.appendChild(go); card.appendChild(row); box.appendChild(card);
});
if(!shown){var em=document.createElement('p'); em.className='lvldesc'; em.textContent=craftTab==='now'?'Nothing to make yet. Earn more blocks, or try another tab.':'Nothing here.'; box.appendChild(em);}
}
/* ---------- overlays ---------- */
var modal=document.getElementById('modal'), craftModal=document.getElementById('craftModal');
function pauseGame(){
paused=true; keys={}; joy.id=null; joy.x=joy.y=0; look=null; drag=null; document.getElementById('joy').hidden=true;
try{if(document.exitPointerLock)document.exitPointerLock();}catch(_){}
promptEl.hidden=true;
}
var FQ=20;
function freeLeft(){rollDay(); return premium()?999:Math.max(0,(S.pl.fq||FQ)-(S.day.u||0));}
(function(){
var tog=document.getElementById('wkTog'), body=document.getElementById('wkBody'), dr=document.getElementById('wkDraw'), cv=document.getElementById('wkCv'); if(!tog||!cv)return;
var cx=cv.getContext('2d'), col='#1d1a3a', er=false, down=false, lx=0, ly=0, penSeen=false, COLS=['#1d1a3a','#d63a3a','#2563eb','#16a34a'];
var cw=document.getElementById('wkCols');
COLS.forEach(function(c,i){var d=document.createElement('i'); d.style.background=c; if(!i)d.className='on'; d.addEventListener('click',function(){col=c; er=false; sync(); [].forEach.call(cw.children,function(k){k.classList.toggle('on',k===d);});}); cw.appendChild(d);});
function sync(){document.getElementById('wkPen').classList.toggle('on',!er); document.getElementById('wkEr').classList.toggle('on',er);}
function fit(){var r=cv.getBoundingClientRect(); if(!r.width)return; var d=window.devicePixelRatio||1, w=Math.round(r.width*d), h=Math.round(r.height*d); if(cv.width===w&&cv.height===h)return; var img=null; try{img=cx.getImageData(0,0,cv.width,cv.height);}catch(e){} cv.width=w; cv.height=h; if(img)cx.putImageData(img,0,0);}
var UND=[];
function snap(){try{UND.push(cx.getImageData(0,0,cv.width,cv.height)); if(UND.length>25)UND.shift();}catch(e){}}
function undo(){var im=UND.pop(); if(!im){try{hint('Nothing to undo.');}catch(e){} return;} if(im.width!==cv.width||im.height!==cv.height){cx.clearRect(0,0,cv.width,cv.height); try{var t=document.createElement('canvas'); t.width=im.width; t.height=im.height; t.getContext('2d').putImageData(im,0,0); cx.drawImage(t,0,0,cv.width,cv.height);}catch(e){}} else {cx.putImageData(im,0,0);} }
function clr(){snap(); cx.clearRect(0,0,cv.width,cv.height);}
function maxH(){if(body.hidden)return; try{var top=cv.getBoundingClientRect().top, h=Math.floor(window.innerHeight-top-14); if(h>170){cv.style.height=h+'px';}}catch(e){}}
function pos(e){var r=cv.getBoundingClientRect(), d=cv.width/r.width; return [(e.clientX-r.left)*d,(e.clientY-r.top)*d];}
function seg(e,a,b){var d=cv.width/cv.getBoundingClientRect().width; cx.lineCap='round'; cx.lineJoin='round'; if(er){cx.globalCompositeOperation='destination-out'; cx.lineWidth=22*d;} else {cx.globalCompositeOperation='source-over'; cx.strokeStyle=col; var pr=(e.pointerType==='pen'&&e.pressure>0)?e.pressure:.5; cx.lineWidth=(1.6+pr*3.2)*d;} cx.beginPath(); cx.moveTo(a[0],a[1]); cx.lineTo(b[0],b[1]); cx.stroke();}
cv.addEventListener('pointerdown',function(e){if(e.pointerType==='pen')penSeen=true; if(e.pointerType==='touch'&&penSeen)return; e.preventDefault(); snap(); down=true; try{cv.setPointerCapture(e.pointerId);}catch(_){} var p=pos(e); lx=p[0]; ly=p[1]; seg(e,[lx,ly],[lx+.01,ly]);});
cv.addEventListener('pointermove',function(e){if(!down)return; e.preventDefault(); var evs=(e.getCoalescedEvents&&e.getCoalescedEvents())||[e]; if(!evs.length)evs=[e]; evs.forEach(function(ev){var p=pos(ev); seg(e,[lx,ly],p); lx=p[0]; ly=p[1];});});
['pointerup','pointercancel','pointerleave'].forEach(function(n){cv.addEventListener(n,function(){down=false;});});
function open(v){dr.classList.toggle('open',v); body.hidden=!v; tog.setAttribute('aria-expanded',String(v)); if(v){fit(); maxH(); fit(); try{body.scrollIntoView({block:'nearest',behavior:'smooth'});}catch(_){}}}
tog.addEventListener('click',function(){open(body.hidden);}); document.getElementById('wkDone').addEventListener('click',function(){open(false);});
document.getElementById('wkPen').addEventListener('click',function(){er=false; sync();});
document.getElementById('wkEr').addEventListener('click',function(){er=true; sync();});
document.getElementById('wkClr').addEventListener('click',clr);
document.getElementById('wkUndo').addEventListener('click',undo);
window.addEventListener('resize',function(){if(!body.hidden){maxH(); fit();}});
window.wkReset=function(){UND.length=0; cx.clearRect(0,0,cv.width,cv.height);}; window.wkOpen=open;
/* The Working space can move into the Town Centre jobs, the Hawker Stall and Career shifts, so any question that needs working out has it. */
var HP=dr.parentNode, HN=dr.nextSibling;
window.wkHome=function(){ if(dr.parentNode!==HP){ try{open(false);}catch(e){} HP.insertBefore(dr,(HN&&HN.parentNode===HP)?HN:null); } };
window.wkNeeds=function(t){var m=String(t||'').match(/\d+(?:[.,]\d+)?/g); return !!m&&m.length>=2;};
window.wkPlace=function(host,text){ if(!host)return; if(!window.wkNeeds(text)){window.wkHome(); return;} try{open(false);}catch(e){} host.appendChild(dr); try{window.wkReset();}catch(e){} };
/* vertical addition / subtraction guide drawn behind the pen layer (pen and eraser only touch the canvas pixels) */
(function(){
var vb=document.getElementById('wkVert'), st=0, NAMES=['Vertical: off','Vertical: add','Vertical: subtract','Vertical: multiply','Vertical: divide'], OPS=['','+','-','x','/'];
var HD=''; return o;
}
if(op==='/'){
var Q=[84,142,200,258]; o=HD.replace('@H@','400');
Q.forEach(function(x,i){ o+=box(x,8,50,50,1)+box(x,70,50,50)+box(x,150,50,50)+box(x,232,50,50)+box(x,310,50,50,1); });
o+=box(10,70,56,50)+'';
o+=txt(14,28,'answer',11)+txt(14,62,'divide by',9)
+txt(60,185,'−',34,'#5b4fd6','end')+''+txt(14,226,'bring down',9)
+txt(60,267,'−',34,'#5b4fd6','end')+''+txt(14,348,'remainder',9)+''; return o;
}
o=HD.replace('@H@','270'); X.forEach(function(x,i){ o+=txt(x+25,16,L[i],14,'','middle')+''+box(x,60,50,56)+box(x,122,50,56)+box(x,196,50,56,1); });
o+=txt(20,46,op==='+'?'carry':'regroup',11)+txt(30,166,op==='+'?'+':'−',46,'#5b4fd6')+'';
return o;
}
function apply(){ cv.style.backgroundImage=st?'url("data:image/svg+xml;utf8,'+encodeURIComponent(svg(OPS[st]))+'")':'none'; cv.style.backgroundRepeat='no-repeat'; cv.style.backgroundPosition='left top'; cv.style.backgroundSize='contain'; vb.textContent=NAMES[st]; vb.classList.toggle('on',!!st); }
vb.addEventListener('click',function(){ st=(st+1)%5; apply(); });
})();
})();
function openQuiz(){
if(modalOpen())return; if(freeLeft()<=0){showLimit(); return;} if(classBlocked()){hint('Wait for your teacher to start the class.',true); return;} if(visiting&&!visiting.tour){hint('Go home first, then you can earn blocks.',true); return;} pauseGame(); mode='play'; diag=null; setSetupVisible(true); updateLevels(); updateDanger();
window.QSIT=0; modal.hidden=false; modal.scrollTop=0; showQuestion();
try{document.getElementById('quizPanel').focus({preventScroll:true});}catch(_){}
setTimeout(function(){guideAuto('earn');},0);
}
function etCheck(){
try{var cl=clsMine(); if(!dbh||!cl.length){if(window.ETSET){window.ETSET=false; applyHide();} return;}
var left=cl.length, any=false;
cl.forEach(function(c){try{dbh.doc('plans/cls_'+c.code).get().then(function(sn){var d=sn.exists?sn.data():null; if(d&&((d.et&&etR(d.et)&&etPupilOk(d.et))||(Array.isArray(d.wk)&&d.wk.some(function(k){return etR(k)&&etPupilOk(k);}))))any=true;}).catch(function(){}).then(function(){left--; if(left===0&&window.ETSET!==any){window.ETSET=any; applyHide(); try{if(!ob('lrnModal').hidden)renderLearn();}catch(e){}}});}catch(e){left--;}});
}catch(e){}
}
setTimeout(function(){try{etCheck();}catch(e){}},3500); setInterval(function(){try{etCheck();}catch(e){}},120000);
var PWSUB=['mathBtn','etBtn','exBtn','g1Btn','scBtn','dtBtn','cpBtn','hiBtn','geBtn','ssBtn','wBtn','fireBtn','dlBtn','sgBtn','enBtn','cnBtn','mlBtn','rdBtn','liBtn','muBtn'];
function openPower(){
if(modalOpen())return; if(freeLeft()<=0){showLimit(); return;} if(classBlocked()){hint('Wait for your teacher to start the class.',true); return;} if(visiting&&!visiting.tour){hint('Go home first, then you can earn blocks.',true); return;}
openLearn(); return;
var list=document.getElementById('pwList'), last=document.getElementById('pwLast'); list.innerHTML=''; last.innerHTML='';
function mk(id,label,primary){var b=document.createElement('button'); b.type='button'; b.className='btn'+(primary?' primary':''); b.textContent=label; b.addEventListener('click',function(){
try{S.pwLast=id; save();}catch(e){}
closeModals(); var t=document.getElementById(id); if(t)t.click();}); return b;}
var lid=S.pwLast, le=lid&&document.getElementById(lid);
if(le&&le.style.display!=='none')last.appendChild(mk(lid,'Keep going: '+le.textContent.trim(),true));
PWSUB.forEach(function(id){var e=document.getElementById(id); if(!e||e.style.display==='none'||e.hidden)return; list.appendChild(mk(id,e.textContent.trim(),false));});
document.getElementById('pwModal').hidden=false; document.getElementById('pwModal').scrollTop=0;
try{document.getElementById('pwPanel').focus({preventScroll:true});}catch(_){}
}
document.getElementById('pwX').addEventListener('click',closeModals);
function openCraft(atTable){
if(modalOpen())return; if(visiting){hint('Go home first, then you can make things.',true); return;} if(gated()){hint('Do your learning first. Then you can make things.',true); return;} pauseGame(); craftNear=!!atTable||nearTable();
document.getElementById('craftFb').textContent=''; document.getElementById('craftFb').className='fb';
renderCraft(); craftModal.hidden=false; craftModal.scrollTop=0;
try{document.getElementById('craftPanel').focus({preventScroll:true});}catch(_){}
}
function playFx(kind){
try{dzStart(kind);}catch(e){}
fxT=1.3; flashEl.className='flash '+kind+' on'; setTimeout(function(){flashEl.className='flash';},1400);
}
function closeModals(){
try{lbLeave();}catch(e){}
try{j3Free();}catch(e){}
window.LRNLV='';
try{window.speechSynthesis&&speechSynthesis.cancel();}catch(e){}
userPaused=false; if(pauseOv)pauseOv.hidden=true;
stopTimer(); mode='play'; diag=null; duel=null; BON=null; BONT=null; WPS=null; CK=null; CKDONE=false; HK=null; CP=null; EP=null; MU=null; PM=null; G1S=null; SC=null; REV=null; if(htPending){var hp=htPending; htPending=null; setTimeout(function(){habitShow(hp[0],hp[1]);},350);} bonusHide(); modal.hidden=true; craftModal.hidden=true; ['gradeModal','dropModal','drillModal','goalsModal','whoModal','clsMod','cnMod','mlMod','etMod','progModal','teachModal','hubModal','sesModal','planModal','syncModal','visitModal','tourModal','petModal','gmModal','pwModal','rdModal','invModal','lobModal','bagModal','avModal','cookModal','hkModal','limModal','cpModal','e21Modal','epModal','muModal','g1Modal','lrnModal','mxModal','scModal','elModal','msModal','exModal','labModal','clsModal','dayModal','endModal','premModal','parModal','jobModal','accModal'].forEach(function(i){document.getElementById(i).hidden=true;}); paused=false; promptSeen=true; updatePrompt(); updateHotbar(); applyGate();
if(pendingFx){playFx(pendingFx); pendingFx=null; hint(lastAlert,true);}
else hint(touchUI?'Back in the world. Tap on the right to place blocks.':'Back in the world. Click to look around and build.');
}
document.getElementById('earn').addEventListener('click',openPower);
document.getElementById('craft').addEventListener('click',function(){openCraft(false);});
document.getElementById('back').addEventListener('click',closeModals);
document.getElementById('craftBack').addEventListener('click',closeModals);
/* ---------- cloud sync (optional), profile, progress, teacher ---------- */
var dbh=null, uid=null, dbState='off', syncT=null;
/* ---------- pause ---------- */
var userPaused=false, pauseOv=document.getElementById('pauseOv');
function setUserPause(v){
if(v){ if(userPaused||modalOpen()||gated())return; userPaused=true; try{lbLeave();}catch(e){} pauseGame(); pauseOv.hidden=false; try{document.getElementById('pauseGo').focus({preventScroll:true});}catch(_){} }
else { if(!userPaused)return; userPaused=false; pauseOv.hidden=true; paused=false; applyGate(); updatePrompt(); }
}
document.getElementById('pauseBtn').addEventListener('click',function(){setUserPause(!userPaused); this.blur();});
(function(){
/* Gentle reminder for players who have no secret code yet. Snoozes for longer each time ("Later"). */
var box=null;
function get(){try{return JSON.parse(localStorage.getItem('mbNag')||'{}');}catch(e){return {};}}
function put(o){try{localStorage.setItem('mbNag',JSON.stringify(o));}catch(e){}}
function hide(){if(box){box.remove(); box=null;}}
function snooze(){var o=get(); o.n=(o.n||0)+1; var days=o.n===1?1:(o.n===2?3:7); o.until=Date.now()+days*86400000; put(o); hide();}
function eligible(){
try{
if(!S||!S.me||S.me.key)return false;
if(S.me.role==='teacher'||S.me.role==='parent')return false;
var o=get(); if(o.until&&o.until>Date.now())return false;
if((S.day&&S.day.q||0)<5)return false;
if(typeof modalOpen==='function'&&modalOpen())return false;
if(typeof paused!=='undefined'&&paused)return false;
return true;
}catch(e){return false;}
}
function show(){
if(box)return;
box=document.createElement('div'); box.className='nagcode'; box.setAttribute('role','region'); box.setAttribute('aria-label','Save your progress');
var t=document.createElement('b'); t.textContent='Keep your progress safe';
var p=document.createElement('p'); p.textContent='Make a secret code. It keeps your name yours and your progress safe. Write the code down!'+(typeof cloudOn==='function'&&cloudOn()?'':(FREE_MODE?' Your progress is saved on this device.':' Saving online on any device is a Premium feature.'));
var r=document.createElement('div'); r.className='row2';
var ok=document.createElement('button'); ok.type='button'; ok.className='btn primary'; ok.textContent='Make a code';
var lt=document.createElement('button'); lt.type='button'; lt.className='btn'; lt.textContent='Later';
ok.addEventListener('click',function(){hide(); try{openWho();}catch(e){} setTimeout(function(){try{document.getElementById('whoMore').open=true; document.getElementById('whoCode').focus({preventScroll:true});}catch(e){}},80);});
lt.addEventListener('click',snooze);
r.appendChild(ok); r.appendChild(lt); box.appendChild(t); box.appendChild(p); box.appendChild(r); document.body.appendChild(box);
}
window.__nagCheck=function(){ if(box&&!eligible()&&!(S&&S.me&&!S.me.key)){hide();} else if(!box&&eligible())show(); return !!box; };
setInterval(window.__nagCheck,15000);
})();
(function(){
/* Landscape phone: once the sign-in box is closed, start with the game filling the screen. */
var done=false, mq=null; try{ mq=window.matchMedia('(orientation:landscape) and (max-height:560px)'); }catch(e){}
function land(){ try{ if(!mq||!mq.matches){done=false;return;} if(done)return; var v=document.getElementById('view'), wm=document.getElementById('whoModal'); if(!v||(wm&&!wm.hidden))return; window.scrollTo(0,Math.max(0,Math.round(v.getBoundingClientRect().top+window.pageYOffset-2))); done=true; }catch(e){} }
window.addEventListener('orientationchange',function(){done=false;});
setInterval(land,700);
})();
(function(){
/* Full screen button, and no accidental page zoom while playing. */
try{ var vm=document.querySelector('meta[name=viewport]'); if(vm&&!/maximum-scale/.test(vm.content))vm.content+=',maximum-scale=1,user-scalable=no'; }catch(e){}
function noZoomKey(e){ if(!(e.ctrlKey||e.metaKey))return; var k=e.key, c=e.keyCode||e.which; if(k==='+'||k==='-'||k==='='||k==='_'||k==='0'||k==='Add'||k==='Subtract'||c===187||c===189||c===48||c===107||c===109||c===96){ e.preventDefault(); e.stopPropagation(); } }
[window,document].forEach(function(t){ t.addEventListener('wheel',function(e){ if(e.ctrlKey||e.metaKey){e.preventDefault();e.stopPropagation();} },{passive:false,capture:true}); t.addEventListener('keydown',noZoomKey,true); t.addEventListener('keyup',noZoomKey,true); });
['gesturestart','gesturechange','gestureend'].forEach(function(ev){document.addEventListener(ev,function(e){e.preventDefault();});});
document.addEventListener('touchmove',function(e){ if(e.touches&&e.touches.length>1)e.preventDefault(); },{passive:false});
var lastT=0; document.addEventListener('touchend',function(e){ var n=Date.now(); if(n-lastT<300&&!/INPUT|TEXTAREA|SELECT/.test((e.target&&e.target.tagName)||''))e.preventDefault(); lastT=n; },{passive:false});
var fb=document.getElementById('fsBtn'); if(!fb)return;
var de=document.documentElement, rq=de.requestFullscreen||de.webkitRequestFullscreen||de.msRequestFullscreen;
var standalone=(window.matchMedia&&matchMedia('(display-mode: standalone)').matches)||window.navigator.standalone;
var focus=true; try{var _f=localStorage.getItem('mbFocus'); if(_f==='0')focus=false;}catch(e){}
function isFs(){return !!(document.fullscreenElement||document.webkitFullscreenElement);}
function lab(){fb.textContent=focus?'Exit full screen':'Full screen';}
function apply(){ lab(); document.body.classList.toggle('fsmode',focus); try{localStorage.setItem('mbFocus',focus?'1':'0');}catch(e){} try{window.scrollTo(0,0);}catch(e){} try{window.dispatchEvent(new Event('resize'));}catch(e){} setTimeout(function(){try{window.scrollTo(0,0);window.dispatchEvent(new Event('resize'));}catch(e){}},250); }
function rs(){ if(!isFs()&&focus&&fsAsked){ focus=false; fsAsked=false; apply(); } }
var fsAsked=false;
document.addEventListener('fullscreenchange',rs); document.addEventListener('webkitfullscreenchange',rs);
var fx=document.getElementById('fsExit'); if(fx)fx.addEventListener('click',function(){fb.click();});
fb.addEventListener('click',function(){
this.blur();
if(focus){ focus=false; fsAsked=false; try{ if(isFs())(document.exitFullscreen||document.webkitExitFullscreen).call(document); }catch(e){} apply(); return; }
focus=true; apply();
if(rq){ try{ var r=rq.call(de); fsAsked=true; if(r&&r.catch)r.catch(function(){fsAsked=false;}); }catch(e){} }
});
if(focus)apply(); else lab();
var ft=document.getElementById('fsTop'); if(ft)ft.addEventListener('click',function(){fb.click();});
/* Corner icon button, like most apps */
var cb=document.createElement('button'); cb.id='fsCorner'; cb.type='button';
var IN='', OUT='';
function cl(){ cb.innerHTML=focus?OUT:IN; var t=focus?'Exit full screen':'Full screen'; cb.title=t; cb.setAttribute('aria-label',t); }
cl(); cb.addEventListener('click',function(){ fb.click(); setTimeout(cl,50); });
document.body.appendChild(cb);
var ib=document.createElement('button'); ib.id='fsInfo'; ib.type='button'; ib.title='Show or hide scores and buttons'; ib.setAttribute('aria-label','Show or hide scores and menu');
ib.innerHTML='';
ib.addEventListener('click',function(){ var h=document.body.classList.toggle('fshide'); try{localStorage.setItem('mbFsHide',h?'1':'0');}catch(e){} });
try{ if(localStorage.getItem('mbFsHide')==='1')document.body.classList.add('fshide'); }catch(e){}
document.body.appendChild(ib);
var gb=document.createElement('button'); gb.id='fsGoal'; gb.type='button'; gb.title='Goals and quests'; gb.setAttribute('aria-label','Goals and quests');
gb.innerHTML='';
gb.addEventListener('click',function(){ var gi=document.getElementById('goalIco'); if(gi)gi.click(); });
document.body.appendChild(gb);
var _ap=apply; apply=function(){ _ap(); cl(); };
})();
document.getElementById('pauseGo').addEventListener('click',function(){setUserPause(false);});
/* ---------- break monster ---------- */
var brk=null, brkMon=null, brkBase=0, brkIv=null;
var BRK_TIPS=[['Look far away','Look out of a window or at something far away for 20 seconds. Your eyes need a rest from the screen.'],['Stretch','Stand up. Reach your arms up high, then touch your toes.'],['Drink water','Have a few sips of water.'],['Bathroom','Do you need the toilet? Go now, then come back.'],['Wiggle','Shake out your hands, wiggle your fingers and roll your shoulders.'],['Breathe','Close your eyes and take 5 slow, deep breaths.'],['Walk','Walk around the room once.']];
function mkBreakMon(){
var g=new THREE.Group(); function M(c){return new THREE.MeshLambertMaterial({color:col(c)});}
function B(w,h,d,c,px,py,pz){var b=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),M(c)); b.position.set(px,py,pz); g.add(b); return b;}
B(1.6,1.5,1.2,'#6c78e0',0,.95,0); B(1.1,.9,.1,'#c9d0ff',0,.8,.62); B(.35,.5,.3,'#6c78e0',-.6,1.95,0); B(.35,.5,.3,'#6c78e0',.6,1.95,0);
B(.3,.8,.3,'#6c78e0',-.95,.9,0); B(.3,.8,.3,'#6c78e0',.95,.9,0); B(.5,.2,.6,'#4b55b8',-.4,.1,.2); B(.5,.2,.6,'#4b55b8',.4,.1,.2);
var d=new THREE.MeshBasicMaterial({color:0x14163a}); [[-.35,1.4],[.35,1.4]].forEach(function(e){var m=new THREE.Mesh(new THREE.BoxGeometry(.32,.07,.05),d); m.position.set(e[0],e[1],.62); g.add(m);});
var mo=new THREE.Mesh(new THREE.BoxGeometry(.3,.26,.05),d); mo.position.set(0,1.0,.62); g.add(mo);
var l1=labelSprite('Break Monster \u00b7 infinite life'); l1.position.y=3.0; g.add(l1); var l2=labelSprite('Z z z'); l2.position.set(.9,2.5,0); l2.scale.set(1.4,.22,1); g.add(l2);
g.scale.setScalar(1.4); return g;
}
function drawBrkArt(){
var c=document.getElementById('brkArt'), g=c.getContext('2d'); g.clearRect(0,0,16,14); function R(col,x,y,w,h){g.fillStyle=col; g.fillRect(x,y,w,h);}
R('#6c78e0',3,3,10,9); R('#6c78e0',3,1,2,3); R('#6c78e0',11,1,2,3); R('#6c78e0',1,6,2,5); R('#6c78e0',13,6,2,5);
R('#c9d0ff',5,8,6,4); R('#4b55b8',4,12,3,2); R('#4b55b8',9,12,3,2);
R('#14163a',5,5,2,1); R('#14163a',9,5,2,1); R('#14163a',7,7,2,2);
R('#fff',12,0,2,1); R('#fff',13,1,1,1); R('#fff',12,2,2,1);
}
function olderUser(){try{return lrnLevel()!=='P';}catch(e){return false;}}
var BRK_TIPS_OLD=[['Look away from the screen','Look at something about 6 metres away for 20 seconds. Let your eyes relax.'],['Stretch','Stand up, roll your shoulders and stretch your arms and neck gently.'],['Drink water','Have a glass of water.'],['Walk','Take a short walk, even just to the window or the next room.'],['Breathe','Take five slow, deep breaths.'],['Posture check','Sit back, relax your shoulders and keep the screen at eye level.']];
function brkOn(){return !accOn('noTimers')&&(S.pl.bk===undefined?20:S.pl.bk)>0;}
function brkTick(dt,now){
if(brkMon)brkMon.position.y=brkBase+Math.sin(now/500)*.12;
if(brk){ if(!paused)pauseGame(); return; }
if(document.hidden||userPaused||visiting||!brkOn())return;
S.brT=(S.brT||0)+dt;
if(S.brT>=(S.pl.bk===undefined?20:S.pl.bk)*60&&mode==='play'&&!classBlocked()){
var blocked=['whoModal','clsMod','cnMod','mlMod','etMod','progModal','teachModal','hubModal','sesModal','planModal','syncModal','visitModal'].some(function(i){return !document.getElementById(i).hidden;});
if(!blocked)startBreak();
}
}
function startBreak(){
if(brk)return; var wasQuiz=!modal.hidden; stopTimer(); closeModals(); pauseGame();
var old=olderUser(), bs=S.pl.bs||60, TT=old?BRK_TIPS_OLD:BRK_TIPS, tips=[TT[0]].concat(shuffle(TT.slice(1)).slice(0,2));
var mx=player.x-Math.sin(yaw)*5, mz=player.z-Math.cos(yaw)*5, cx=Math.max(1,Math.min(LAND-2,Math.floor(mx))), cz=Math.max(1,Math.min(LAND-2,Math.floor(mz))), my=surfaceY(cx,cz), g0=0; while(solidAt(cx,my,cz)&&g0'; li.querySelector('b').textContent=t[0]; li.querySelector('span').textContent=t[1]; ul.appendChild(li);});
var btn=document.getElementById('brkDone'); btn.disabled=true;
function tick(){var left=Math.max(0,Math.ceil((brk.end-Date.now())/1000)); document.getElementById('brkClock').textContent=left>0?(left>=60?(Math.floor(left/60)+':'+(left%60<10?'0':'')+(left%60)):(left+' s')):'Well done!'; if(left<=0){btn.disabled=false; btn.textContent=brk.old?'I am back':'I took my break'; clearInterval(brkIv); brkIv=null;} else btn.textContent=brk.old?'Wait for the timer to finish':'Wait for the monster to doze off';}
clearInterval(brkIv); brkIv=setInterval(tick,250); tick();
drawBrkArt(); document.getElementById('brkArt').style.display=old?'none':''; document.getElementById('brkPanel').querySelector('h2').textContent=old?'Eye break':'Break time!'; var m=document.getElementById('brkModal'); m.hidden=false; m.scrollTop=0;
}
document.getElementById('brkDone').addEventListener('click',function(){
if(!brk)return; var wq=brk.wasQuiz, wo=brk.old; brk=null; clearInterval(brkIv); brkIv=null;
if(brkMon){scene.remove(brkMon); brkMon=null;}
S.brT=0; S.st.bk=(S.st.bk||0)+1; hearts=5; drawHearts(); enGain(30); sfx('done');
document.getElementById('brkModal').hidden=true; closeModals(); updateGoals(); save();
hint(wo?'Welcome back. Hearts refilled.':'Welcome back! The Break Monster went back to sleep. Hearts refilled.',true);
if(wq&&!gated()&&!classBlocked())openQuiz();
});
/* ---------- my character ---------- */
var AVBACK=false;
function redrawAv(){drawAv(document.getElementById('avPrev'),S.av,AVBACK); drawAv(avHudEl,S.av);}
function openAv(){
if(modalOpen())return; pauseGame();
var box=document.getElementById('avOpts'); box.innerHTML=''; document.getElementById('avMsg').textContent='';
var pre=document.createElement('div'), ps=document.createElement('div'); pre.appendChild(ps);
[['Boy',{hs:'short',ou:'pants',hr:'#3b2a1a',sh:'#2f80ed',pn:'#34495e'}],['Girl',{hs:'long',ou:'dress',hr:'#7a4b26',sh:'#f27fb4',pn:'#f27fb4'}],['Girl with ponytail',{hs:'pony',ou:'skirt',hr:'#d9a441',sh:'#8a4fc4',pn:'#2f6fd0'}]].forEach(function(o){
var b=document.createElement('button'); b.type='button'; b.className='txt'; b.textContent=o[0]; b.addEventListener('click',function(){Object.assign(S.av,o[1]); S.av.cs='none'; openAv.keep=1; closeAvList(); openAvRows(); save();}); ps.appendChild(b);
});
function closeAvList(){box.innerHTML='';}
function openAvRows(){
[['sk','Skin'],['hr','Hair colour'],['hs','Hair style'],['sh','Shirt or dress colour'],['pn','Trousers or skirt colour'],['ou','Outfit'],['hat','Hat'],['ac','Accessory'],['cs','Costume']].forEach(function(c){
var wrap=document.createElement('div'), lab=document.createElement('div'); lab.className='label'; lab.textContent=c[1]; wrap.appendChild(lab);
var sw=document.createElement('div'); sw.className='sw'; wrap.appendChild(sw);
AVP[c[0]].forEach(function(o){
var open=avOpen(c[0],o[0]), b=document.createElement('button'); b.type='button'; b.setAttribute('aria-pressed',String(S.av[c[0]]===o[0]));
var isCol=(c[0]==='sk'||c[0]==='hr'||c[0]==='sh'||c[0]==='pn');
if(isCol){b.setAttribute('aria-label',c[1]+': '+o[1]); b.style.background=o[0];}
else {b.className='txt'; b.textContent=(open?'':'🔒 ')+o[1]; if(!open){b.style.opacity='.7'; b.setAttribute('aria-label',o[1]+', locked');}}
b.addEventListener('click',function(){
if(!open){var r=AVREQ[c[0]+':'+o[0]]; document.getElementById('avMsg').textContent=o[1]+' unlocks when you: '+r[2].toLowerCase()+'. You have '+Math.min(avHave(r[0]),r[1])+' of '+r[1]+'.'; return;}
document.getElementById('avMsg').textContent=''; S.av[c[0]]=o[0]; Array.prototype.forEach.call(sw.children,function(x){x.setAttribute('aria-pressed','false');}); b.setAttribute('aria-pressed','true'); redrawAv(); save();
});
sw.appendChild(b);
});
box.appendChild(wrap);
});
redrawAv();
}
openAvRows(); var m=document.getElementById('avModal'); m.hidden=false; m.scrollTop=0;
}
document.getElementById('avBtn').addEventListener('click',openAv);
document.getElementById('avHudBtn').addEventListener('click',function(e){e.stopPropagation(); openAv();});
document.getElementById('avBack').addEventListener('click',closeModals);
document.getElementById('avTurn').addEventListener('click',function(){AVBACK=!AVBACK; this.setAttribute('aria-pressed',String(AVBACK)); redrawAv();});
document.getElementById('avDone').addEventListener('click',closeModals);
drawAv(avHudEl,S.av);
/* ---------- bag ---------- */
var BAGKEYS='1234567890abcdefghjklmnopqrstuvwxyz', bagBtns={};
function openBag(){
if(modalOpen())return; pauseGame(); bagBtns={}; var bi=0;
var body0=document.getElementById('bagBody'); body0.innerHTML=''; var body=body0;
var tg=document.createElement('button'); tg.type='button'; tg.className='btn small'; tg.style.margin='0 0 6px'; tg.textContent=window.__bagAll?'Show only blocks I have':'Show all blocks'; tg.addEventListener('click',function(){window.__bagAll=!window.__bagAll; closeModals(); openBag();}); body.appendChild(tg);
var sq=document.createElement('input'); sq.type='search'; sq.id='bagSearch'; sq.placeholder='Type to search your bag'; sq.setAttribute('aria-label','Search your bag'); sq.autocomplete='off'; sq.style.cssText='display:block;width:100%;box-sizing:border-box;margin:0 0 8px;padding:8px 10px;font-size:15px;border:1px solid var(--line,#b9c2cc);border-radius:8px'; body0.appendChild(sq);
var list=document.createElement('div'); body0.appendChild(list);
function fillBag(){
var q=sq.value.trim().toLowerCase(); bagBtns={}; bi=0; list.innerHTML=''; body=list; var any=false;
PAGES.forEach(function(pg,pi){
var h=document.createElement('div'); h.className='bagpage label'; h.textContent=pg.name; body.appendChild(h);
var g=document.createElement('div'); g.className='baggrid'; body.appendChild(g);
TYPES.forEach(function(t){
if(pageOf(t.id)!==pi||furnOff(t.id))return; var n=inv[t.id]||0; if(q){if(t.name.toLowerCase().indexOf(q)<0)return;}else if(!n&&!window.__bagAll&&S.sel!==t.id)return; any=true;
var b=document.createElement('button'); b.type='button'; b.className='bagitem'+(n?'':' empty'); b.setAttribute('aria-pressed',String(S.sel===t.id&&!S.brk)); b.setAttribute('aria-label',t.name+', '+n+' in your bag');
b.appendChild(iconCanvas(t.key));
var nm=document.createElement('span'); nm.className='bn'; nm.textContent=t.name; b.appendChild(nm);
var ct=document.createElement('span'); ct.className='bc'; ct.textContent=n; b.appendChild(ct);
b.addEventListener('click',function(){selectType(t.id); closeModals(); hint('Holding '+t.name+'.');});
if(bi0?' ('+S.st.vp+')':''); if(t!==vbLast){vbLast=t; document.getElementById('visitBtn').textContent=t;}}
function visitTick(dt){
updateVisitBtn(); if(visiting&&visiting.room){roomTick(dt); return;} if(visiting&&visiting.tour){tourTick(dt); return;} if(!visiting||paused)return;
if(!accOn('noTimers'))visitLeft-=dt; var t=Math.max(0,Math.ceil(visitLeft)), m=Math.floor(t/60), r=t%60;
document.getElementById('visitTxt').textContent='Visiting '+visiting.name+' \u00b7 '+m+':'+(r<10?'0':'')+r+' \u00b7 look only';
if(visitLeft<=0){exitVisit(); hint('Visit time is over. Welcome home. Keep your streak going to earn another pass.',true);}
}
function visitHint(m){hint(m,true);}
var visAv=null;
function enterVisit(doc){
if(!(S.st.vp>0)){return;}
var d; try{d=JSON.parse(doc.blob);}catch(e){return;}
var w=dec(unrle(d.w),N*N*WH,TYPES.length), t=dec(unrle(d.t),N*N*TD,7); if(!w||!t){document.getElementById('visitMsg').textContent='That world could not be opened.'; return;}
if(boarded)leaveBoat();
var snap={world:world,tv:tv,terr:terr,tseed:tseed,LAND:LAND,boats:boats.map(function(b){return [b.x,b.z];}),px:player.x,py:player.y,pz:player.z,yaw:yaw,pitch:pitch};
while(boats.length){scene.remove(boats[0].g); boats.shift();} boarded=null; clearZombies();
S.st.vp--; visitLeft=(S.pl.vm||3)*60; visiting={name:doc.n||'a friend',snap:snap};
world=w; tv=t; terr=d.tr||'meadow'; tseed=d.ts|0||7; LAND=[24,32,40,48,56,64].indexOf(d.r)>=0?d.r:24;
(Array.isArray(d.bt)?d.bt:[]).forEach(function(q){boats.push(mkBoat(q[0],q[1],0));});
document.body.classList.add('visiting'); buildBounds(); setGround(); dirty=true; terrDirty=true; goHome();
if(visAv){scene.remove(visAv); visAv=null;}
try{var fa=sanitiseAv(d.av), ax=14.5, az=12.5, ay=surfaceY(14,12), gg=0; while(solidAt(14,ay,12)&&gg0)){var k=S.pl.vk||10; msg.textContent='Visiting is earned. Answer '+k+' questions correctly in a row to win a visit pass. Your streak is '+(S.st.s||0)+' right now. Each visit lasts '+(S.pl.vm||3)+' minutes.'; return;}
if(!dbh){msg.textContent='Visiting needs the online game. Open it from the link your teacher gave you.'; return;}
if(!S.me.cls){msg.textContent='Tap My progress, then Change, and add your class to see your classmates.'; return;}
msg.textContent='Loading your classmates...';
try{dbh.collection('worlds').where('c','==',S.me.cls).get().then(function(sn){
var docs=sn.docs.map(function(x){return x.data();}).filter(function(x){return x&&x.blob&&x.n;});
var mine=S.me.name; docs=docs.filter(function(x){return !(x.n===mine&&x.c===S.me.cls);}).sort(function(a,b){return String(a.n).localeCompare(String(b.n));});
if(!docs.length){msg.textContent='No classmates have saved a world online yet. Ask them to add their secret code.'; return;}
msg.textContent='You have '+S.st.vp+' visit pass'+(S.st.vp===1?'':'es')+'. A visit uses one and lasts '+(S.pl.vm||3)+' minutes. You can look around but not change anything.';
docs.forEach(function(x){var b=document.createElement('button'); b.type='button'; b.className='btn'; b.textContent=String(x.n).slice(0,20)+'’s world'; b.addEventListener('click',function(){enterVisit(x);}); list.appendChild(b);});
}).catch(function(){msg.textContent='Could not load classmates right now.';});}catch(e){msg.textContent='Could not load classmates right now.';}
}
/* ---------- Singapore tour ---------- */
var LMS=[
{id:'merlion',name:'The Merlion',x:11,z:32,r:13,ly:26,sub:'Merlion Park, Singapore',
f:['The Merlion has the head of a lion and the body of a fish.','The lion head stands for Singapura, the old name of Singapore. It means “lion city”.','The fish body stands for Temasek, the fishing village Singapore began as. Temasek means “sea town”.','It is 8.6 metres tall and weighs 70 tonnes.','Lim Nang Seng made it. Prime Minister Lee Kuan Yew unveiled it on 15 September 1972.','In 2002 it was moved to its spot at Merlion Park.','A smaller Merlion, the Merlion Cub, stands nearby. It is about 2 metres tall.','Zoom in to Marina Bay and stand next to it. One block there is one metre, so count the blocks up to the top!'],
q:'The Merlion was unveiled in 1972 and moved in 2002. How many years later was it moved?',a:'2002 − 1972 = 30 years.'},
{id:'mbs',name:'Marina Bay Sands',x:30,z:8,r:24,ly:30,sub:'Marina Bay, Singapore',
f:['It has three hotel towers, each with 55 storeys.','A long SkyPark joins the tops of the three towers. It is 340 metres long.','The SkyPark has gardens, an observation deck and a 150 metre infinity pool.','The architect, Moshe Safdie, first got the idea from a deck of cards.','The resort opened in 2010.','The SkyPark sticks out 66.5 metres past the north tower. It is the longest cantilevered deck in the world that people can stand on.','The SkyPark is about 200 metres above the ground. Sources give the height of the towers anywhere from 191 m (the pool level) to about 207 m (the top).'],
q:'There are 3 towers and each has 55 storeys. How many storeys are there in all the towers together?',a:'3 × 55 = 165 storeys.'},
{id:'gbb',name:'Gardens by the Bay',x:56,z:10,r:14,ly:30,sub:'Bay South, Singapore',
f:['The Supertrees are 18 tree-like towers. They are 25 to 50 metres tall.','Solar cells on the Supertrees make electricity for lights.','They also collect rainwater, and help to cool the two big glass gardens, the Flower Dome and the Cloud Forest.','A skyway walkway joins two of the big Supertrees.','The gardens cover 105 hectares and opened in 2012.','The OCBC Skyway is 128 metres long and 22 metres above the ground. It joins two Supertrees that are each 42 metres tall.','Inside the Cloud Forest, the Cloud Mountain is 42 metres tall.'],
q:'The tallest Supertree is 50 metres and the shortest is 25 metres. How much taller is the tallest one?',a:'50 − 25 = 25 metres taller.'},
{id:'changi',name:'Changi Control Tower',x:9,z:55,r:12,ly:30,sub:'Changi Airport, Singapore',
f:['Changi Airport opened in 1981. Flights began on 1 July 1981 and the official opening was on 29 December 1981.','Air traffic controllers work in a control tower. From the glass room at the top they watch the runways and tell pilots when it is safe to take off and land.','Changi has four main passenger terminals, and a fifth, Terminal 5, is being built.','Skytrax has named Changi the World\u2019s Best Airport 13 times.','Jewel Changi has the Rain Vortex, the world\u2019s tallest indoor waterfall. It is 40 metres tall and falls down 7 storeys.','The first plan for Changi Airport included a control tower 80 metres tall.'],
q:'Changi has 4 main terminals and is building Terminal 5. How many main terminals will it have when Terminal 5 is finished?',a:'4 + 1 = 5 terminals.'},
{id:'esp',name:'Esplanade',x:47,z:54,r:15,ly:13,sub:'Esplanade – Theatres on the Bay',
f:['People call it “the durian” because its spiky shape looks like the fruit.','It is a centre for music, dance and plays.','The spiky metal shades keep the sun’s heat off the glass roof. Singapore is near the equator, so the sun is strong.','It opened on 12 October 2002.'],
q:'The Esplanade opened in 2002. Gardens by the Bay opened in 2012. How many years later did the gardens open?',a:'2012 − 2002 = 10 years.'},
{id:'fc',name:'Fort Canning Park',x:5,z:19,r:11,ly:13,sub:'Fort Canning Park, Singapore',
f:['Fort Canning Hill stands in the middle of the city. Long ago it was called Bukit Larangan, which means “Forbidden Hill” in Malay.','Raffles built a house on the hill in 1822. Later the hill had a fort, and the old Fort Gate is still there.','Archaeologists dug here and found gold ornaments, coins and pottery from the 1300s. This helps us learn that Singapore was a busy trading place long before 1819.','Under the hill is the Battle Box, a secret underground command centre from the Second World War.','Fort Canning Hill is about 48 metres high.'],
q:'Raffles arrived in Singapore in 1819 and built a house on Fort Canning Hill in 1822. How many years later was that?',a:'1822 − 1819 = 3 years.'},
{id:'bg',name:'Singapore Botanic Gardens',x:5,z:42,r:10,ly:10,sub:'Singapore Botanic Gardens',
f:['The Botanic Gardens began in 1859. It is Singapore’s first UNESCO World Heritage Site, listed in 2015.','The park is about 82 hectares big, with lakes, rainforest and lawns.','The National Orchid Garden has thousands of orchids. The Vanda Miss Joaquim is our national flower.','The Bandstand in the Gardens is a place where bands play and families picnic.'],
q:'The Botanic Gardens began in 1859 and became a World Heritage Site in 2015. How many years later was that?',a:'2015 − 1859 = 156 years.'},
{id:'acm',name:'Asian Civilisations Museum',x:29,z:42,r:13,ly:18,sub:'Empress Place, Singapore River',
f:['The museum looks at the cultures and history of Asia, including Southeast Asia, China, South Asia and West Asia.','It stands in the Empress Place Building by the Singapore River, near where Raffles landed in 1819.','The old building has tall columns and a clock tower. It was built in the 1860s and was once a government office.','The museum opened at Empress Place in 2003. It helps us see how many cultures shaped Singapore.'],
q:'Singapore became independent in 1965. The museum opened at Empress Place in 2003. How many years is that after 1965?',a:'2003 − 1965 = 38 years.'},
{id:'sultan',name:'Sultan Mosque',x:4,z:4,r:9,ly:14,sub:'Kampong Glam, Singapore',
f:['The Sultan Mosque is in Kampong Glam. People also call it Masjid Sultan.','Two gold-coloured domes sit on top of the main building.','The bases of the domes are decorated with the cut-off ends of glass bottles. Poorer families gave the bottles when the mosque was rebuilt.','The first mosque was ordered in 1819 by Sultan Hussein Shah and finished in 1826.','The mosque you see today was finished in 1932. It was made a national monument on 8 March 1975.'],
q:'The first mosque was finished in 1826 and the new one in 1932. How many years later was the new mosque finished?',a:'1932 − 1826 = 106 years.'},
{id:'chinatown',name:'Chinatown',x:27,z:14,r:18,ly:11,sub:'Chinatown and the Buddha Tooth Relic Temple',
f:['Chinatown has rows of shophouses. These are narrow buildings with a shop downstairs and a home upstairs.','Raffles planned the town in 1822 and set aside this area for Chinese immigrants.','The Buddha Tooth Relic Temple was built in 2007 in the style of the Tang dynasty of China.','It keeps a relic of the Buddha, a tooth, in a stupa made with about 320 kilograms of gold.','The temple has a museum of Buddhist art and a roof garden with a prayer wheel.'],
q:'The gold in the stupa weighs about 320 kg. Devotees gave 234 kg of it. How many kg were not given by devotees?',a:'320 − 234 = 86 kg.'},
{id:'hawpar',name:'Haw Par Villa',x:9,z:61,r:10,ly:12,sub:'Pasir Panjang, Singapore',
f:['Haw Par Villa is a park full of colourful statues from Chinese stories and sayings.','The brothers Aw Boon Haw and Aw Boon Par, who made Tiger Balm, built it. The park opened in 1937.','It has more than 1,000 statues and 150 large scenes called dioramas.','The scenes include the story Journey to the West about the Monkey King.','The park was fixed up and reopened on 1 July 2021.'],
q:'Haw Par Villa opened in 1937 and reopened after restoration in 2021. How many years is that?',a:'2021 − 1937 = 84 years.'},
{id:'sentosa',name:'Sentosa',x:28,z:29,r:10,ly:15,sub:'Sentosa Island, Singapore',
f:['Sentosa is an island south of the main island of Singapore. It is about 5 square kilometres.','Sentosa means peace and tranquillity in Malay.','Long ago it was called Pulau Blakang Mati. It was renamed Sentosa after a naming contest.','A cable car has linked Mount Faber to Sentosa since 1974. A causeway opened in 1992.','The Sentosa Express train opened in 2007. Resorts World Sentosa opened in 2010.'],
q:'The causeway to Sentosa opened in 1992 and the Sentosa Express in 2007. How many years apart?',a:'2007 − 1992 = 15 years.'},
{id:'zoo',name:'Singapore Zoo',x:3,z:53,r:8,ly:11,sub:'Mandai, Singapore',
f:['The Singapore Zoo opened on 27 June 1973.','It is about 28 hectares big and has about 315 kinds of animals.','The animals live in open-looking homes. Moats, glass and hidden fences keep animals and visitors safe.','Many of the animals are from the rainforests of Asia, such as orang utans.','The Night Safari is next to the zoo. It opens at night so you can see animals that are awake in the dark.'],
q:'The Singapore Zoo opened in 1973. How old was it in 2023?',a:'2023 − 1973 = 50 years old.'},
{id:'jewel',name:'Jewel Changi',x:59,z:40,r:9,ly:13,sub:'Changi Airport, Singapore',
f:['Jewel Changi Airport is a glass dome with gardens and shops next to the airport.','It opened in 2019.','The Rain Vortex is the world’s tallest indoor waterfall. It is 40 metres high.','The Forest Valley garden has about 3,000 trees and 60,000 shrubs.','The glass roof is 200 metres by 150 metres. The Canopy Bridge is 50 metres long and 23 metres above the ground.','Passengers can walk through Jewel to get to the airport terminals.'],
q:'The Forest Valley has about 3,000 trees and 60,000 shrubs. How many trees and shrubs is that altogether?',a:'3,000 + 60,000 = 63,000.'}
];
/* ===== Singapore tour v2: a zoomable map. Island map (1 block = 250 m) and true-scale areas (1 block = 1 m).
Areas are built when you zoom in and thrown away when you zoom out, so the player's own world is never touched.
The areas do not use the 64 x 64 voxel arrays. They are lists of whole-metre boxes (drawn as a few merged meshes)
with a spatial hash for collisions, answered through solidAt()/getBlock() while TZ.cur is set. ===== */
var TZ=(function(){
var T={cur:null, busy:0, run:false, save:null, texs:{}, mats:{}, SC:{}};
var TILE_PX=64;
/* ---------- textures ---------- */
function cvTex(fn){
var c=document.createElement('canvas'); c.width=c.height=TILE_PX; var g=c.getContext('2d'); fn(g,TILE_PX);
var t=new THREE.CanvasTexture(c); t.encoding=THREE.sRGBEncoding; t.wrapS=t.wrapT=THREE.RepeatWrapping;
t.magFilter=THREE.NearestFilter; t.minFilter=THREE.LinearMipmapLinearFilter; t.generateMipmaps=true; t.anisotropy=4; return t;
}
function rnd32(s){s=(s*1664525+1013904223)>>>0; return s;}
function grid(g,P,tile,a){ g.fillStyle='rgba(0,0,0,'+a+')'; var n=Math.max(1,Math.round(tile)), st=P/n; for(var i=0;i>>8)%P; s=rnd32(s); var y=(s>>>8)%P; g.fillRect(x,y,sz||2,sz||2);} }
/* name: [kind, colour a, colour b, tile metres, extra] */
var MD={
grass:['noise','#5fae4d','#4c9a3d',4], pave:['brick','#cbbca3','#a99a80',2], plaza:['check','#dccbc3','#cdb7ae',4], road:['noise','#4b4e54','#44474c',8],
sand:['noise','#ead9a0','#dac98e',4], wood:['plank','#a06f3e','#8a5b30',2], tarmac:['noise','#5a5d62','#505358',8], conc:['noise','#dad7d0','#cfccc4',2],
concd:['noise','#9d9fa3','#92949a',2], white:['noise','#f3f3f0','#e9e9e5',2], quay:['noise','#b9b6ae','#aaa79f',2],
glassB:['glass','#aab6c0','#5a7f9c',4], glassD:['glass','#6e7b88','#2e4864',4], glassG:['glass','#b4c4bb','#5f9a86',4], glassW:['glass','#d7dde0','#8fb0c4',4], glassC:['glass','#c7ccd1','#3d6f8f',4],
mbs:['glass','#ecebe7','#35566c',4,'strip'], sky:['noise','#efefec','#e2e2de',2], pool:['noise','#3fc6da','#35b6cb',2], dome:['glass','#cfe6ee','#9ccbdc',4,'frame'],
silver:['noise','#bcc4cb','#aab3bb',2], red:['noise','#d8332b','#c52c25',2], orange:['noise','#f08a24','#df7d1b',2], pink:['noise','#f27fb4','#e373a7',2], purple:['noise','#8a4fc4','#7c44b3',2],
leaf:['noise','#4a9a3c','#3d8a31',2], leafd:['noise','#357a2e','#2b6b27',2], trunk:['lattice','#7a6a5c','#4d4036',4], brown:['noise','#7a4b26','#6b4020',2], green:['noise','#3fae4a','#369c41',2],
black:['noise','#2a2b30','#232428',2], yellow:['noise','#f1cf3a','#e2c02e',2], gold:['noise','#e9b923','#d6a81c',2], blue:['noise','#2f6fd0','#2863bd',2], cyan:['noise','#2fc2d0','#29b0bd',2], grey:['noise','#8b9098','#80858d',2],
terra:['noise','#c9663a','#b95a30',2], water:['noise','#7fd0f0','#6cc2e6',2], rock:['noise','#8c8579','#7d776c',2], stripe:['check','#f5f5f5','#d8332b',4], cream:['noise','#efe3c8','#e3d5b4',2],
scales:['scales','#f1f1ee','#b7bcc2',1], lamp:['glow','#fff6d0','#ffe9a0',2]
};
function texFor(name){
if(T.texs[name])return T.texs[name]; var d=MD[name]||MD.conc, kind=d[0], a=d[1], b=d[2], tile=d[3], ex=d[4];
var tx=cvTex(function(g,P){
g.fillStyle=a; g.fillRect(0,0,P,P);
if(kind==='noise'){ speck(g,P,b,70,name.length*977+7,3); speck(g,P,'rgba(255,255,255,.08)',40,name.length*31+3,2); grid(g,P,tile,.07); }
else if(kind==='brick'){ g.fillStyle=b; var n=Math.round(tile)*2, st=P/n; for(var i=0;i merged meshes + collision hash ---------- */
function Builder(opts){
this.o=opts||{}; this.parts={}; this.cols=[]; this.hash={}; this.nBox=0; this.nTri=0; this.lbls=[]; this.anim=[];
this.grp=new THREE.Group();
}
var BP=Builder.prototype;
BP.part=function(name,vc){
var p=this.parts[name]; if(!p){p=this.parts[name]={name:name,cur:null,list:[],vc:vc}; }
if(!p.cur||p.cur.n>63000){p.cur={pos:[],nor:[],uv:[],col:vc?[]:null,idx:[],n:0}; p.list.push(p.cur);} return p.cur;
};
/* one quad, vertices given counter-clockwise from outside */
BP.quad=function(name,v,nx,ny,nz,axis,col){
var tile=tileOf(name), k=1/tile, c=this.part(name,!!col), b=c.n, i;
for(i=0;i<4;i++){
var X=v[i][0],Y=v[i][1],Z=v[i][2]; c.pos.push(X,Y,Z); c.nor.push(nx,ny,nz);
if(axis===0)c.uv.push(Z*k,Y*k); else if(axis===1)c.uv.push(X*k,Z*k); else c.uv.push(X*k,Y*k);
if(col)c.col.push(col[0],col[1],col[2]);
}
c.idx.push(b,b+1,b+2,b,b+2,b+3); c.n+=4; this.nTri+=2;
};
/* box in metres. m = material name, or [side, top, bottom]. o: {nc:no collision, f:'tb' faces to skip e.g. 'b'} */
BP.box=function(m,x,y,z,w,h,d,o){
if(w<=0||h<=0||d<=0)return; o=o||{};
var gl=o.gap, gp=null, gi, kk;
if(gl){ if(!Array.isArray(gl))gl=[gl]; for(gi=0;giq.z0&&xq.x0){gp=q; break;}} }
if(gp){
var o2={}, rest=gl.filter(function(q){return q!==gp;}); for(kk in o)o2[kk]=o[kk]; if(rest.length)o2.gap=rest; else delete o2.gap;
if(xgp.x1)this.box(m,gp.x1,y,z,x+w-gp.x1,h,d,o2);
var xa=Math.max(x,gp.x0),xb=Math.min(x+w,gp.x1);
if(y+h>gp.y1)this.box(m,xa,gp.y1,z,xb-xa,y+h-gp.y1,d,o2);
return;
}
var ms,mt,mb; if(typeof m==='string'){ms=mt=mb=m;}else{ms=m[0]; mt=m[1]||ms; mb=m[2]||ms;}
var x1=x+w,y1=y+h,z1=z+d, skip=o.f||'', cl=o.col||null;
if(skip.indexOf('t')<0)this.quad(mt,[[x,y1,z1],[x1,y1,z1],[x1,y1,z],[x,y1,z]],0,1,0,1,cl);
if(skip.indexOf('b')<0&&y>0)this.quad(mb,[[x,y,z],[x1,y,z],[x1,y,z1],[x,y,z1]],0,-1,0,1,cl);
if(skip.indexOf('s')<0){
this.quad(ms,[[x,y,z1],[x1,y,z1],[x1,y1,z1],[x,y1,z1]],0,0,1,2,cl);
this.quad(ms,[[x1,y,z],[x,y,z],[x,y1,z],[x1,y1,z]],0,0,-1,2,cl);
this.quad(ms,[[x1,y,z1],[x1,y,z],[x1,y1,z],[x1,y1,z1]],1,0,0,0,cl);
this.quad(ms,[[x,y,z],[x,y,z1],[x,y1,z1],[x,y1,z]],-1,0,0,0,cl);
}
this.nBox++;
if(!o.nc&&!this.o.nocol)this.addCol(x,y,z,x1,y1,z1);
};
BP.addCol=function(x0,y0,z0,x1,y1,z1){
x0=Math.round(x0);y0=Math.round(y0);z0=Math.round(z0);x1=Math.max(x0+1,Math.round(x1));y1=Math.max(y0+1,Math.round(y1));z1=Math.max(z0+1,Math.round(z1));
var b=[x0,y0,z0,x1,y1,z1]; this.cols.push(b);
var cx0=x0>>5,cx1=(x1-1)>>5,cz0=z0>>5,cz1=(z1-1)>>5;
for(var a=cx0;a<=cx1;a++)for(var c=cz0;c<=cz1;c++){var key=(a+512)*2048+(c+512); (this.hash[key]||(this.hash[key]=[])).push(b);}
};
/* collision only (no drawing), e.g. an invisible wall */
BP.wall=function(x,y,z,w,h,d){ this.addCol(x,y,z,x+w,y+h,z+d); };
/* filled ellipse as one box per row. step = row height in metres (1 = true blocks). */
BP.ell=function(m,cx,cz,rx,rz,y,h,o,st){
st=st||1; var z0=Math.floor(cz-rz), z1=Math.ceil(cz+rz);
for(var zz=z0;zz=1)continue; var half=rx*Math.sqrt(1-dz*dz); var xa=Math.round(cx-half), xb=Math.round(cx+half); if(xb<=xa)continue;
this.box(m,xa,y,zz,xb-xa,h,st,o);
}
};
/* ring (hollow ellipse) */
BP.ring=function(m,cx,cz,rx,rz,t,y,h,o,st){
st=st||1; var z0=Math.floor(cz-rz), z1=Math.ceil(cz+rz), irx=rx-t, irz=rz-t;
for(var zz=z0;zz=1)continue; var half=rx*Math.sqrt(1-dz*dz), xa=Math.round(cx-half), xb=Math.round(cx+half); if(xb<=xa)continue;
var diz=irz>0?Math.abs(zz+st/2-cz)/irz:2;
if(diz>=1||irx<=0){this.box(m,xa,y,zz,xb-xa,h,st,o); continue;}
var ih=irx*Math.sqrt(1-diz*diz), ia=Math.round(cx-ih), ib=Math.round(cx+ih);
if(ia>xa)this.box(m,xa,y,zz,ia-xa,h,st,o); if(xb>ib)this.box(m,ib,y,zz,xb-ib,h,st,o);
}
};
/* stepped dome from stacked ellipses. solid unless thick given. */
BP.dome=function(m,cx,cz,rx,rz,h,step,o,thick){
var n=Math.ceil(h/step);
for(var k=0;k=s.bx1||z=s.bz1)return true;
if(s.polyHit&&s.polyHit(x,y,z))return true;
if(y<0&&s.land(x,z))return true;
var a=s.hash[((x>>5)+512)*2048+((z>>5)+512)]; if(!a)return false;
for(var i=0;i=b[0]&&x=b[1]&&y=b[2]&&z=this.w||j>=this.h)return 0; return this.a[j*this.w+i];};
/* emit ground: top quads (greedy merged) at y=0 and quay walls down to -4 where land meets water */
GP.emit=function(B){
var self=this, W=this.w,H=this.h,c=this.c,a=this.a,open={},i,j,key,r;
for(j=0;j<=H;j++){
var used={};
if(j0&&!a[j*W+i-1])B.quad('quay',[[X0,-4,Z0],[X0,-4,Z1],[X0,0,Z1],[X0,0,Z0]],-1,0,0,0);
if(j+10&&!a[(j-1)*W+i])B.quad('quay',[[X1,-4,Z0],[X0,-4,Z0],[X0,0,Z0],[X1,0,Z0]],0,0,-1,2);
}
}
};
T.Ground=Ground;
return T;
})();
/* ===== Area 1: Marina Bay at true scale (1 block = 1 metre). Positions come from real latitude and longitude. ===== */
(function(T){
var O_LAT=1.286833, O_LON=103.854528; /* the Merlion statue */
function P(lat,lon){return [Math.round((lon-O_LON)*111300), Math.round((O_LAT-lat)*110570)];}
T.P=P;
function hash2(a,b){var s=(a*73856093^b*19349663)>>>0; s=(s^(s>>>13))*1274126177>>>0; return (s^(s>>>16))>>>0;}
function lamp(B,x,z){B.box('grey',x,0,z,.4,4.7,.4,{nc:1}); B.box('lamp',x-.3,4.7,z-.3,1,.3,1,{nc:1});}
function tree(B,x,z,h,r,y0){h=h||6; r=r||h*.4; y0=y0||0; B.box('brown',x,y0,z,1,h-1,1); B.ell('leaf',x+.5,z+.5,r,r,y0+h-2,2,{nc:1}); B.ell('leafd',x+.5,z+.5,r*.6,r*.6,y0+h,1,{nc:1});}
function palm(B,x,z,h){h=h||7; B.box('brown',x,0,z,1,h,1); for(var k=0;k<4;k++){var a=k*Math.PI/2; B.box('leaf',x+.5+Math.cos(a)*2-1.5,h,z+.5+Math.sin(a)*2-1.5,3,.6,3,{nc:1});} B.box('leafd',x-.5,h+.5,z-.5,2,.8,2,{nc:1});}
function skyscraper(B,x,z,w,d,h,mat,top){B.box([mat,top||'concd'],x,0,z,w,h,d); if(h>120){B.box('silver',x+w/2-.5,h,z+d/2-.5,1,Math.min(14,h*.05),1,{nc:1});}}
/* a labelled box building */
function bld(B,x,z,w,d,h,mat,top){B.box([mat,top||'concd'],x,0,z,w,h,d);}
function buildBay(){
var BX0=-700,BX1=1500,BZ0=-520,BZ1=1000;
var B=new T.Builder({water:true}), G=new T.Ground(BX0,BZ0,BX1,BZ1,5), i,j,k,p;
/* ----- ground: city paving, parks, roads, then the bay water ----- */
G.fill(1);
G.rect(2,-40,-520,330,-200); /* Esplanade Park lawns */
G.rect(2,950,0,1500,1000); /* Gardens by the Bay lawns */
G.rect(2,-420,-520,-60,-200);
G.rect(4,640,-20,760,430); /* Event Plaza in front of Marina Bay Sands */
/* roads (about 18 m wide) */
G.rect(3,-60,-195,900,-175); G.rect(3,-60,790,900,812); G.line(3,-45,-175,-45,812,18); G.rect(3,760,-175,780,520); G.rect(3,780,500,1100,520);
G.rect(3,-700,150,-60,166); G.rect(3,-700,400,-60,416); G.rect(3,-340,-200,-324,812);
/* water: main bay, the inlet by the Helix, the channel beside the Gardens, open sea to the south and east */
G.rect(0,0,-20,640,780); G.rect(0,0,-170,830,-20); G.rect(0,790,-30,950,520); G.rect(0,BX0,940,BX1,BZ1); G.rect(0,1380,300,BX1,BZ1);
G.rect(0,950,520,1380,560); G.rect(0,0,780,300,860); G.rect(0,640,770,780,860);
/* land that pokes into the water: the Merlion jetty, the ArtScience peninsula, Bayfront corner */
G.rect(4,-70,-36,46,36); G.rect(4,470,30,650,130); G.rect(4,560,430,640,780);
/* wooden promenade round the bay */
G.rect(5,-12,40,0,780); G.rect(5,0,772,640,780); G.rect(5,-12,-34,0,40);
/* ----- Merlion Park: the Merlion statue is 8.6 m tall ----- */
(function(){
var cx=0,cz=0;
B.box(['conc','white'],cx-5,0,cz-5,10,1,10); /* wave base */
var rad=[3.3,3.6,3.6,3.4,3.0];
for(k=0;k<5;k++)B.ell('scales',cx,cz,rad[k],rad[k],1+k,1); /* fish body with scales, y 1 to 6 */
for(k=0;k<7;k++){var tx=cx-4.2-Math.round(Math.sin(k*.5)*2), ty=1.5+k; B.box('scales',tx-1.5,ty,cz-1.5,3,1,3);} /* tail curling up behind */
B.box('silver',cx-8.5,7.6,cz-3.5,5,1,7); B.box('white',cx-7.5,8.6-.0,cz-2.5,0,0,0);
B.ring('white',cx-1.2,cz,4.3,4.3,1.7,6,2.6); /* mane, y 6 to 8.6 */
B.box('cream',cx-1.7,6,cz-2.1,4.4,2.6,4.2); /* lion face */
B.box('brown',cx+2.7,6.7,cz-.6,.8,.8,1.2,{nc:1}); B.box('red',cx+2.7,6.05,cz-1.3,.9,.6,2.6,{nc:1}); B.box('white',cx+2.7,6.65,cz-1.2,.9,.15,.4,{nc:1}); B.box('white',cx+2.7,6.65,cz+.8,.9,.15,.4,{nc:1});
B.box('black',cx+2.7,7.7,cz-1.5,.5,.5,.6,{nc:1}); B.box('black',cx+2.7,7.7,cz+.9,.5,.5,.6,{nc:1});
for(k=0;k<10;k++){var t=k/9, wx=cx+3.6+t*8.4, wy=6.2+3.0*t-9.0*t*t; B.box('water',wx,Math.max(.2,wy),cz-.3,.8,.8,.6,{nc:1});}
B.box('water',cx+11.5,.1,cz-2.5,3,.2,5,{nc:1});
/* Merlion Cub: about 2 m tall */
B.box(['white','white'],cx+16,0,cz+10,1.8,1.2,1.8); B.ell('white',cx+16.9,cz+10.9,.9,.9,1.2,.8); B.box('cream',cx+17.4,1.3,cz+10.4,.8,.5,1,{nc:1});
for(k=-3;k<=3;k++)if(k)lamp(B,k*9,-14);
palm(B,-18,12,8); palm(B,-18,-12,7); palm(B,-26,0,9);
B.label('Merlion (8.6 m)',0,13,0,1);
})();
/* ----- Fullerton Hotel and the old shore-front ----- */
bld(B,-250,-20,90,60,30,'cream','concd'); B.label('The Fullerton Hotel',-205,36,10,.8);
/* ----- Esplanade - Theatres on the Bay (two spiky domes; the size is modelled from the photos, not measured) ----- */
(function(){
var cx=111,cz=-325;
B.box('conc',cx-100,0,cz-48,200,6,96,{});
B.dome(['silver','glassW'],cx-52,cz,46,38,36,3,{});
B.dome(['silver','glassW'],cx+48,cz-4,38,34,31,3,{});
for(k=0;k<14;k++){var sa=k/14*Math.PI*2; B.box('silver',cx-52+Math.cos(sa)*47-.5,6,cz+Math.sin(sa)*39-.5,1,3,1,{nc:1});}
B.label('Esplanade',cx,48,cz,1);
T.esp=[cx,cz];
})();
/* ----- Asian Civilisations Museum (Empress Place) ----- */
(function(){
var q=P(1.28750,103.85139), x=q[0]-55, z=q[1]-22;
B.box(['cream','concd'],x,0,z,110,20,44); B.box(['white','concd'],x+45,20,z+8,20,16,28); B.box(['white','red'],x+50,36,z+13,10,12,18);
for(k=0;k<12;k++)B.box('white',x+6+k*8.6,0,z+44,1.4,12,1.4);
B.label('Asian Civilisations Museum',q[0],64,q[1],1);
T.acm=q;
})();
/* ----- Raffles Place and the Marina Bay skyline (heights from the list of tallest buildings; plan sizes modelled) ----- */
var SK=[['UOB Plaza One',1.28555,103.84972,280,46,46,'glassB'],['One Raffles Place',1.28469,103.85103,280,44,44,'glassD'],['Republic Plaza',1.28278,103.85111,280,44,44,'glassC'],
['CapitaSpring',1.28361,103.85000,280,40,40,'glassG'],['Ocean Financial Centre',1.28320,103.85299,245,42,42,'glassB'],['One Raffles Quay (North Tower)',1.28139,103.85181,245,44,44,'glassW'],
['Marina Bay Financial Centre T2',1.28004,103.85419,245,40,40,'glassD'],['The Sail @ Marina Bay',1.28110,103.85279,245,40,40,'glassC'],['OCBC Centre',1.28528,103.85025,198,30,60,'glassW']];
var skp=[];
SK.forEach(function(s){var q=P(s[1],s[2]); skp.push([q[0],q[1],s[4]]); skyscraper(B,q[0]-s[4]/2,q[1]-s[5]/2,s[4],s[5],s[3],s[6]); B.label(s[0]+' ('+s[3]+' m)',q[0],s[3]+14,q[1],1.2);});
/* MBFC tower 3 (239 m) and tower 1 (192 m) */
(function(){var q=P(1.28004,103.85419); skyscraper(B,q[0]+50,q[1]-20,38,38,239,'glassB'); skyscraper(B,q[0]-92,q[1]-20,38,38,192,'glassC');})();
/* filler city blocks, so the skyline is full (heights are only illustrative) */
for(i=0;i<9;i++)for(j=0;j<14;j++){
var fx=-690+i*70, fz=170+j*55, h=hash2(i+3,j+9), near=false;
for(k=0;k-110||(fz>=390&&fz<=430))continue; if(h%5===0)continue;
var hh=40+(h>>>4)%120, wd=36+(h>>>9)%20, dd=34+(h>>>13)%18; bld(B,fx,fz,wd,dd,hh,['glassB','glassW','glassC','glassG','glassD'][(h>>>3)%5],'concd');
}
for(i=0;i<32;i++){var h2=hash2(i,77); bld(B,-690+(i%2)*40,-500+i*32,40,28,34+(h2>>>5)%60,['glassB','glassW','glassC'][h2%3],'concd');}
for(i=0;i<22;i++){var h3=hash2(i,5); bld(B,-480+i*90,-515,60,30,30+(h3>>>6)%70,['glassB','glassW','glassC'][h3%3],'concd');}
/* ----- Marina Bay Sands ----- */
(function(){
var cx=712, zc=[144,249,354], tw=44, td=62, TOP=200, i2;
var lobbyH=14, SLICE=8;
zc.forEach(function(cz,ti){
var x0=cx-tw/2, z0=cz-td/2;
/* ground-floor hall with a door on the bay side (west) */
B.box('mbs',x0,0,z0,tw,lobbyH,2,{f:'b'}); B.box('mbs',x0,0,z0+td-2,tw,lobbyH,2,{f:'b'}); B.box('mbs',x0+tw-2,0,z0,2,lobbyH,td);
B.box('mbs',x0,0,z0+2,2,lobbyH,(td-4)/2-3); B.box('mbs',x0,0,cz+3,2,lobbyH,(td-4)/2-3); B.box('mbs',x0,7,cz-3,2,lobbyH-7,6);
B.box('lamp',x0+4,lobbyH-.3,cz-10,tw-8,.3,20,{nc:1});
B.box(['white','white'],x0+2,lobbyH,z0+2,tw-4,1,td-4);
/* the tower body leans in slices (outer towers lean toward the middle) */
var n=Math.floor((TOP-lobbyH-1)/SLICE);
for(i2=0;i2<=n;i2++){
var y=lobbyH+1+i2*SLICE, hgt=Math.min(SLICE,TOP-y), sh=ti===1?0:Math.round(i2*(ti===0?.62:-.62));
if(hgt<=0)break; B.box(['mbs','sky'],x0,y,z0+sh,tw,hgt,td);
}
T['mbsTop'+ti]=cz;
});
/* the SkyPark: 340 m long, 38 m at its widest, about 200 m above the ground, cantilever 66.5 m past the north tower */
var sx=cx-19, sz0=47, sz1=387, SY=192;
B.box(['sky','wood','white'],sx,SY,sz0,38,7,sz1-sz0); /* hull, top at 199 m */
B.box(['sky','wood'],sx,SY+7,sz0,38,1,232-sz0); B.box(['sky','wood'],sx,SY+7,382,38,1,sz1-382); /* deck surface at 200 m */
B.box(['sky','wood'],sx,SY+7,232,10,1,150); B.box(['sky','wood'],sx+24,SY+7,232,14,1,150);
B.box('silver',sx-.5,SY-1,sz0+10,39,1,sz1-sz0-20,{nc:1});
/* the infinity pool: 150 m long, one block lower than the deck */
B.box('pool',sx+10,SY+7.05,232,14,.05,150,{nc:1,f:'b'});
for(i2=0;i2<5;i2++)B.box('white',sx+10+i2*3.5,SY+7.1,232,.2,.02,150,{nc:1,f:'b'});
/* gardens, sun beds and the observation deck */
for(i2=0;i2<22;i2++){var tz=60+i2*14; if(tz>228&&tz<385)continue; tree(B,sx+3,tz,4.5,2.6,200); tree(B,sx+33,tz+6,4.5,2.6,200);}
for(i2=0;i2<10;i2++){tree(B,sx+3,232+i2*15,4,2.2,200); tree(B,sx+33,236+i2*15,4,2.2,200);}
B.box('plaza',cx-12,200,60,24,.4,50,{nc:1}); B.label('Observation Deck',cx,215,70,1);
/* glass rails round the whole deck */
var rl='railT';
B.box(rl,sx,200,sz0,.4,2.2,sz1-sz0); B.box(rl,sx+37.6,200,sz0,.4,2.2,sz1-sz0); B.box(rl,sx,200,sz0,38,2.2,.4); B.box(rl,sx,200,sz1-.4,38,2.2,.4);
/* lift pads: ground hall of the middle tower, and the SkyPark */
B.box('gold',cx-2,0,249-2,4,.15,4,{nc:1}); B.box('gold',cx-2,200,249-2,4,.15,4,{nc:1});
B.box('lamp',cx-2,203.8,249-2,4,.2,4,{nc:1});
B.label('Lift to the SkyPark',cx,6,249,.6); B.label('Lift down',cx,207,249,.6);
B.label('Marina Bay Sands (about 200 m)',cx,236,249,1.6);
T.lifts=[{x:cx,z:249,r:3.2,y:0,tx:cx,tz:262,ty:200,txt:'Lift up to the SkyPark (about 200 m)'},{x:cx,z:249,r:3.2,y:200,tx:cx,tz:262,ty:0,txt:'Lift down to the ground'}];
T.mbs=[cx,249];
/* the Shoppes and Expo behind the towers */
bld(B,560,440,160,330,26,'white','sky'); bld(B,640,-10,60,60,24,'cream','concd');
})();
/* ----- ArtScience Museum (lotus) ----- */
(function(){var q=P(1.28611,103.85917); B.ell(['white','sky'],q[0],q[1],28,28,0,10); for(k=0;k<10;k++){var a=k/10*Math.PI*2, fx=q[0]+Math.cos(a)*20, fz=q[1]+Math.sin(a)*20; B.box(['white','sky'],fx-4,10,fz-4,8,6+k%3*2,8); B.box(['white','sky'],fx-3+Math.cos(a)*4,16+k%3*2,fz-3+Math.sin(a)*4,6,6,6,{nc:1});} B.label('ArtScience Museum',q[0],38,q[1],1); T.art=q;})();
/* ----- the Helix bridge (280 m): a deck with two twisting steel ribbons ----- */
(function(){
var ax=595,az=-10,bx=835,bz=-160, L=Math.hypot(bx-ax,bz-az), n=Math.floor(L/3), ux=(bx-ax)/L, uz=(bz-az)/L;
for(i=0;i<=n;i++){
var px=ax+ux*i*3, pz=az+uz*i*3, ph=i*3/38*Math.PI*2, s1=Math.sin(ph)*5, s2=-s1, c1=(Math.cos(ph)+1)*3.5+1.5;
B.box(['wood','wood'],Math.round(px)-3,-1,Math.round(pz)-3,6,1,6,{f:'b'});
B.box('silver',px-ux*0-.5+(-uz)*s1,c1,pz+(ux)*s1-.5,1,1,1,{nc:1}); B.box('silver',px+(-uz)*s2-.5,6.5-c1+1.5,pz+(ux)*s2-.5,1,1,1,{nc:1});
}
B.label('Helix Bridge (280 m)',715,16,-85,1); T.helix=[715,-85];
})();
/* ----- Singapore Flyer: 165 m tall, wheel 150 m across, 28 capsules ----- */
(function(){
var fx=981,fz=-287, hub=90, grp=new THREE.Group(); grp.position.set(fx,hub,fz);
var tor=new THREE.Mesh(new THREE.TorusGeometry(75,.9,6,64),T.matFor('silver')); tor.rotation.y=Math.PI/2; grp.add(tor);
var tor2=new THREE.Mesh(new THREE.TorusGeometry(73.5,.6,6,64),T.matFor('silver')); tor2.rotation.y=Math.PI/2; tor2.position.x=2.2; grp.add(tor2);
var sp=new THREE.BufferGeometry(), pa=[]; for(k=0;k<14;k++){var a=k/14*Math.PI*2; pa.push(0,0,0, 0,Math.sin(a)*75,Math.cos(a)*75);} sp.setAttribute('position',new THREE.Float32BufferAttribute(pa,3));
grp.add(new THREE.LineSegments(sp,new THREE.LineBasicMaterial({color:0xcfd5db})));
var cg=new THREE.BoxGeometry(5,4,5.5), cm=T.matFor('glassC'), caps=[];
for(k=0;k<28;k++){var a2=k/28*Math.PI*2, c=new THREE.Mesh(cg,cm); c.position.set(3.2,Math.sin(a2)*75,Math.cos(a2)*75); grp.add(c); caps.push(c);}
B.grp.add(grp); B.anim.push(function(dt){grp.rotation.x+=dt*.02; caps.forEach(function(c){c.rotation.x=-grp.rotation.x;});});
B.box('glassC',fx-14,0,fz-30,30,12,60); B.box('silver',fx-3,0,fz-28,3,hub,3); B.box('silver',fx-3,0,fz+25,3,hub,3); B.box('silver',fx-3,hub-4,fz-28,3,3,56,{nc:1});
B.label('Singapore Flyer (165 m)',fx,178,fz,1.2); T.fly=[fx,fz];
})();
/* ----- Gardens by the Bay ----- */
(function(){
var gx=1010, gz=578;
function superTree(x,z,h){
var w=h>=40?5:4; B.box('trunk',x-w/2,0,z-w/2,w,h-9,w);
for(var yy=6;yy>>10)%900),6+(h4>>>20)%5,3);}
})();
/* ----- street lights and trees along the bay ----- */
for(i=0;i<40;i++){lamp(B,-8,60+i*18); lamp(B,646,40+i*18<430?40+i*18:430);}
for(i=0;i<30;i++){palm(B,-30,60+i*24,7+(i%3));}
/* ----- bounds ----- */
var s={id:'bay',name:'Marina Bay',B:B,G:G,bx0:BX0,bx1:BX1,bz0:BZ0,bz1:BZ1,water:true,fly:false};
G.emit(B); B.build(); s.hash=B.hash; s.grp=B.grp; s.land=function(x,z){return G.at(x,z)>0;};
s.lm=[{id:'merlion',x:0,z:0,r:30},{id:'mbs',x:712,z:249,r:150},{id:'gbb',x:1010,z:560,r:150},{id:'gbb',x:1175,z:120,r:100},{id:'esp',x:111,z:-325,r:90},{id:'acm',x:T.acm[0],z:T.acm[1],r:60},
{id:'x:flyer',x:981,z:-287,r:70},{id:'x:art',x:T.art[0],z:T.art[1],r:45},{id:'x:helix',x:715,z:-85,r:80},{id:'x:raffles',x:-420,z:260,r:160}];
s.places=[{n:'Merlion',x:10,z:16,ty:0,to:[0,0]},{n:'Esplanade',x:111,z:-250,ty:0,to:[111,-325]},{n:'Asian Civilisations Museum',x:T.acm[0],z:T.acm[1]+34,ty:0,to:T.acm},{n:'Raffles Place skyline',x:-300,z:300,ty:0,to:[-420,240]},
{n:'Helix Bridge',x:597,z:-9,ty:0,to:[715,-85]},{n:'Marina Bay Sands',x:640,z:249,ty:0,to:[712,249]},{n:'ArtScience Museum',x:T.art[0]-40,z:T.art[1],ty:0,to:T.art},{n:'Singapore Flyer',x:940,z:-250,ty:0,to:[981,-287]},
{n:'Gardens: Supertree Grove',x:T.gbb[0]-30,z:T.gbb[1]+95,ty:0,to:T.gbb},{n:'Gardens: Domes',x:1175,z:200,ty:0,to:T.dom}];
s.lifts=T.lifts; s.spawn={x:12,z:18,to:[0,0]};
s.fog=[500,3000]; s.near=.15; s.far=4200; s.lblMax=3000;
s.extra=[{id:'x:flyer',name:'Singapore Flyer',sub:'Marina Centre, Singapore',f:['The Singapore Flyer is a giant observation wheel. It is 165 metres tall.','The wheel is 150 metres wide. You are standing at the bottom of a wheel as tall as a 55-storey building.','It has 28 air-conditioned capsules. Each capsule holds up to 28 people.','One full turn takes about 32 minutes, so it moves very slowly.','It opened in 2008.'],q:'The Flyer has 28 capsules and each holds 28 people. How many people can all the capsules hold together?',a:'28 × 28 = 784 people.'},
{id:'x:art',name:'ArtScience Museum',sub:'Marina Bay Sands, Singapore',f:['The ArtScience Museum looks like a lotus flower. It has ten “fingers” around a round base.','It has 21 gallery spaces.','Moshe Safdie designed it, the same architect as Marina Bay Sands.','It opened on 17 February 2011.'],q:'The museum has 10 fingers and 21 galleries. How many more galleries than fingers are there?',a:'21 − 10 = 11.'},
{id:'x:helix',name:'Helix Bridge',sub:'Marina Bay, Singapore',f:['The Helix Bridge is 280 metres long. It is a bridge for people on foot.','Its steel frame twists like the double helix of DNA.','It joins Marina Centre to Marina Bay Sands.','It was fully opened on 18 July 2010.'],q:'The bridge is 280 m long. You walk 4 m every second. How many seconds do you need to cross it?',a:'280 ÷ 4 = 70 seconds.'},
{id:'x:raffles',name:'Raffles Place skyline',sub:'Central Business District, Singapore',f:['The tall towers of the business district rise beside the bay.','UOB Plaza One, One Raffles Place, Republic Plaza and CapitaSpring are each 280 metres tall.','The Central Business District has a height limit of 280 metres. A taller building needs special permission.','In this map every block is one metre, so you can count how tall each tower really is.'],q:'A tower is 280 m tall and one storey is about 4 m. About how many storeys is that?',a:'280 ÷ 4 = 70 storeys.'}];
return s;
}
T.SC.bay=buildBay;
})(TZ);
/* ===== Island map: Singapore in true proportions, 1 block = 250 m across (heights are 10 times too tall so they show: 1 block high = 25 m).
The coastline comes from the geoBoundaries Singapore outline (CC BY 4.0), drawn at 250 m per block. Land use and reservoirs are only approximate. ===== */
(function(T){
var MW=225, MH=138, MARG=16, GW=MW+2*MARG, GH=MH+2*MARG, LON0=103.5953, LAT1=1.48, MLON=111320*Math.cos(1.35*Math.PI/180), MLAT=110574, CELL=250;
var MASK64="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf////4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/////AAAAAAAAAAAAAAAAAAAAAAAAAAAfYAAP/////0AAAAAAAAAAAAAAAAAAAAAAAAAAH/gAB//////wAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAf/////+AAAAAAAAAAAAAAAAAAAAAAAAAA//4AH//////4AAAAAAAAAAAAAAAAAAAAAAAAAP//kA///////AAAAAAAAAAAAAcAAAAAAAAAAAD///z///////8AAAAAAAAAAAA/wAAAAAAAAAAA////////////wAAAAAAAAAAAePAAAAAAAAAAA////////////+AAAAAAAAAAAAADAAAAAAAAAAf////////////wcAAAAAAAAAAACHAAAAAAAAAf////////////8PwAAAAAAAAAQAf+AAAAAAAAH/////////////B/gAAHAAAAAOAH/+AAAAAAAB/////////////8//AAD+AAAAD4f//4AAAAAAAP///////////////8AA/8AAAAfj///gAAAAAAD////////////////58D/wAAAD+f//+AAAAAAAf/////////////////gP///+Af7///8AAAAAAD//////////////////Af///4B/P///gAAAAAA//////////////////8A///+AP////+AAAAAAH//////////////////wB///wB/f///wAAAAAD///////////////////AD///AHz///+AAAAAA///////////////////8AP/wAAAf///wAAAAAH///////////////////wAf8AAAD///8AAAAAA////////////////////AB/AAAfz///kAAAAAH///////////////////4AOAAADgf//8AAAAAA////////////////////gAAAAAQB///gAAAAAH///////////////////8AAAAAAAP//4AAAAAA////////////////////wAAPgAIA///AAAAAAP////////////////////AAH/gAAD//4AAAAAB////////////////////8AA//AAAP//AAAAAAf////////////////////4AP/8AAB//0AAAAAH/////////////////////gD//gAAH/wAAAAAA/////////////////////////+AAAfwAAAAAAH/////////////////////////wAAB8AAAAAAB/////////////////////////+AAAMAAAAAAAP/////////////////////////4AAAAAAAAAAH//////////////////////////gAAAAAAAAAA//////////////////////////4AAAAAAAAAAP//////////////////////////+AAAAAAAAAB///////////////////////////+AAAEAAAAAf///////////////////////////8AAAAAAAAD////////////////////////////4AAEAAAAA/////////////////////////////gAAgAAAAP////////////////////////////4AAEAAAAB/////////////////////////////EAAAAAAAf////////////////////////////4AAAAAAAP////////////////////////////+AAAAAAAB/////////////////////////////gAAAAAAAP////////////////////////////8AAAAAAAB/////////////////////////////AAAAgAAAP////////////////////////////4AAAAAAAB/////////////////////////////AAAAQAAAP////////////////////////////wAAAAAAAD////////////////////////////+AAAAAAAAf////////////////////////////gAAAAAAAD////////////////////////////8AAAAAAAA/////////////////////////////gAAAAAAAH/////////////////////////////wAAAAAAB//////////////////////////////AAAAAAAP/////////////////////////////4AAAAAAB/////////////////////////////zAAAAAAAf/////////////////////////5//+YAAAAAAD53//////////////////////+AP/gAAAAAAAAfE//////////////////////+AAB4AAAAAAAAH8P/////////////////////4AAAAAAAAAAAAA/hv//3+////////////////4AAAAAAAAAAAAAP+D/e+f7///////////////4AAAAAAAAAAAAAB/w/7/x3n//////////////wAAAAAAAAAAAAAAP/B/P+A+B/////////////gAAAAAAAAAAAAAAD/8H84AEAA/f//////////gAAAAAAAAAAAAAAAf/gfwAAgAAB//////////gAAAAAAAAAAAAAAAD/+B+AAEAAAP/////////wAAAAAAAAAAAAAAAAf/wDAAH2AAD/////////4AAAAAAAAAAAAAAAAB//AQHw/8AAH////////8AAAAAAAAAAAAAAAAAP/4AD+D/gAAf////////AAAAAAAAAAAAAAAAAB//gB/if+ADg////////gAAAAAAAAAAAAAAAAAP/4Af47/wA/H///////4AAAAAAAAAAAAAAAAAD//AH+Pv/gH8///////4AAAAAAAAAAAAAAAAAB/+AD/h8//AP/f//////AAAAAAAAAAAAAAAAAAf/wA/8///8Afw//////wAAAAAAAAAAAAAAAAAD/+Af/////gB+D/////8AAAAAAAAAAAAAAAAAA//wH/////8ADg/////PAAAAAAAAAAAAAAAAAAH/4A/////gAAAHn///4wAAAAAAAAAAAAAAAAABP8QD/5/j/AAAB8f/4fGAAAAAAAAAAAAAAAAAAJ+AAP+H+PgAAAPBh8x4AAAAAAAAAAAAAAAAAAAPAAB/4f8YAAAD4AAfFAAAAAAAAAAAAAAAAAAAB6AAP/h/wAAAAeAfn8AAAAAAAAAAAAAAAAAAAAPAAA/+P8AAAABgB/fwAAAAAAAAAAAAAAAAAAADgAAH/x+AAAAAEAH/oAAAAAAAAAAAAAAAAAAAA4AAAf+PAAAAAAAAf/8AAAAAAAAAAAAAAAAAAAHAAAB/wAAAAAAAAA//wAAAAAAAAAAAAAAAAAAA8AAAP/AAAAAAAAAB/8AAAAAAAAAAAAAAAAAAAHgAAA/4AAAAAAAAAP/AAAAAAAAAAAAAAAAAAAD+AAAP/AAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAPwAAD/8AAAAAAAAAB8AAAAAAAAAAAAAAAAAAAAfAAAP/AAAAAcAAAAHAAAAAAAAAAAAAAAAAAAAA4AAB/wAAAHDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAD//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAf/+AAAAADAAAAAAAAAAAAAAAAAAACAAABwAAAAP/wAAAAD4AAAAAAAAAAAAAAAAAAAQAAAEAAAAAP7AAAAAcAAAAAAAAAAAAAAAAAAAEAAAFAAAAAAfcAAAABwAAAAAAAAAAAAAAAAAADAAAAQAAAAAAAAAAAAnAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAH4AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAMAAAAAAAAAAAAAAAAAAAAAEAAAAB4AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf4AHnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/AA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/8AH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
function decodeMask(){var s=atob(MASK64), a=new Uint8Array(MW*MH), i; for(i=0;i>3)>>(7-(i&7)))&1;} return a;}
function gx(lon){return MARG+(lon-LON0)*MLON/CELL;}
function gz(lat){return MARG+(LAT1-lat)*MLAT/CELL;}
T.island={GW:GW,GH:GH,gx:gx,gz:gz};
/* places on the map (lat, lon) and which true-scale area opens from each */
var LMX={merlion:[1.286833,103.854528,'bay'],mbs:[1.2837,103.8607,'bay'],gbb:[1.2825,103.8640,'bay'],esp:[1.28972,103.85556,'bay'],acm:[1.28750,103.85139,'bay'],
changi:[1.3625,103.9885,'changi'],jewel:[1.3644,103.9915,'changi'],fc:[1.2943,103.8462,'fc'],bg:[1.3151,103.8162,'bg'],sultan:[1.3022,103.8581,'sultan'],chinatown:[1.2810,103.8444,'chinatown'],
hawpar:[1.28417,103.78222,'hawpar'],sentosa:[1.248,103.830,'sentosa'],zoo:[1.403782,103.79414,null]};
T.LMX=LMX;
function ellMask(a,cx,cz,rx,rz,fn){for(var j=Math.max(0,Math.floor(cz-rz)); j<=Math.min(GH-1,Math.ceil(cz+rz)); j++)for(var i=Math.max(0,Math.floor(cx-rx)); i<=Math.min(GW-1,Math.ceil(cx+rx)); i++){var X=(i+.5-cx)/rx, Z=(j+.5-cz)/rz; if(X*X+Z*Z<=1)fn(i,j);}}
function buildIsland(){
var B=new T.Builder({nocol:true}), land=new Uint8Array(GW*GH), H=new Float32Array(GW*GH), colr=new Array(GW*GH), mk=decodeMask(), i,j,k;
for(j=0;j1.7&&u!==1)c=CGH[Math.min(2,Math.floor((H[q]-1.7)/2))]; colr[q]=c;}
/* land columns */
var VC='_vc';
function top(i,j,y,c){B.quad(VC,[[i,y,j+1],[i+1,y,j+1],[i+1,y,j],[i,y,j]],0,1,0,1,c);}
function sideE(i,j,y0,y1,c){B.quad(VC,[[i+1,y0,j+1],[i+1,y0,j],[i+1,y1,j],[i+1,y1,j+1]],1,0,0,0,c);}
function sideW(i,j,y0,y1,c){B.quad(VC,[[i,y0,j],[i,y0,j+1],[i,y1,j+1],[i,y1,j]],-1,0,0,0,c);}
function sideS(i,j,y0,y1,c){B.quad(VC,[[i,y0,j+1],[i+1,y0,j+1],[i+1,y1,j+1],[i,y1,j+1]],0,0,1,2,c);}
function sideN(i,j,y0,y1,c){B.quad(VC,[[i+1,y0,j],[i,y0,j],[i,y1,j],[i+1,y1,j]],0,0,-1,2,c);}
function nb(i,j){return (i<0||j<0||i>=GW||j>=GH||!land[j*GW+i])?0:H[j*GW+i];}
var CB=C('#8a7f6a');
for(j=0;j=0&&b>=0&&a=GW||j>=GH)return 0; return land[j*GW+i]?H[j*GW+i]:.1;}
/* ----- landmark models: small block icons standing on white plates (not to scale), joined to the real spot by a line ----- */
var ICONS={}, order=['merlion','mbs','gbb','esp','acm','chinatown','fc','sultan','bg','changi','jewel','hawpar','sentosa','zoo'], pos={};
order.forEach(function(id){var L=LMX[id]; pos[id]={rx:gx(L[1]),rz:gz(L[0]),x:gx(L[1]),z:gz(L[0])};});
for(k=0;k<160;k++){
order.forEach(function(a){order.forEach(function(b){ if(a>=b)return; var A=pos[a],Bq=pos[b], dx=Bq.x-A.x, dz=Bq.z-A.z, d=Math.hypot(dx,dz)||.01, D=9.5; if(d0?x-len/2:x-len/2;
B.box('white',x0,3,z-w/2,len,hb,w,{nc:1}); B.box('white',x0+(dir>0?len:-3),3.6,z-w/2+.6,3,hb-1.2,w-1.2,{nc:1});
B.box('white',x+(dir>0?-3:-7),3.2,z-len*.5,len*.2,.6,len,{nc:1}); /* wings */
B.box('red',x0+(dir>0?0:len-7),3+hb,z-.4,7,len*.2,.8,{nc:1}); B.box('white',x0+(dir>0?1:len-9),3.2,z-len*.16,5,.5,len*.32,{nc:1});
B.box('grey',x-6,0,z-len*.22,3,3,2,{nc:1}); B.box('grey',x-6,0,z+len*.2,3,3,2,{nc:1}); B.box('grey',x0+(dir>0?len-10:8),0,z-1,2,3,2,{nc:1});
}
function buildChangi(){
var BX0=-600,BX1=900,BZ0=-500,BZ1=500;
var B=new T.Builder({}), G=new T.Ground(BX0,BZ0,BX1,BZ1,5), i,j,k;
G.fill(2); G.rect(1,-200,-250,520,330); G.rect(7,-200,330,520,500); G.rect(7,330,-250,900,500); G.rect(7,520,-500,900,-250); G.rect(3,-600,-30,-200,30); G.rect(3,-600,330,330,350);
G.rect(7,640,-500,700,500); G.rect(7,780,-500,840,500); G.rect(4,-150,-120,150,150);
/* runway centre lines */
for(j=-490;j<490;j+=40){B.box('white',668,0,j,1,.04,18,{nc:1,f:'b'}); B.box('white',808,0,j,1,.04,18,{nc:1,f:'b'});}
/* ----- Jewel Changi Airport: glass roof 200 m x 150 m; the Rain Vortex is 40 m tall ----- */
var JX=0,JZ=0;
B.dome(['dome'],JX,JZ,100,75,46,3,{gap:[{x0:JX-9,x1:JX+9,z0:JZ+60,z1:JZ+90,y1:9},{x0:JX-9,x1:JX+9,z0:JZ-90,z1:JZ-60,y1:9}]},3);
B.ell('rock',JX,JZ,16,16,0,2); B.ell('water',JX,JZ,9,9,2,38,{nc:1}); /* Rain Vortex: 40 m */
B.ell('pool',JX,JZ,13,13,2,.6,{nc:1});
for(i=0;i<46;i++){var a=i*2.399, rr=22+((i*7)%34), tx=JX+Math.cos(a)*rr*1.3, tz=JZ+Math.sin(a)*rr*.95; if(Math.abs(tx-JX)>88||Math.abs(tz-JZ)>66)continue; B.box('brown',tx,0,tz,1,5+(i%4),1); B.ell('leaf',tx+.5,tz+.5,3,3,4+(i%4),3,{nc:1});}
/* Canopy Park bridge: 50 m long, 23 m above the ground (look up!) */
B.box('railT',JX-25,23,JZ+40,50,1,3,{nc:1}); B.box('silver',JX-25,22,JZ+40,50,.5,3,{nc:1});
B.label('Rain Vortex (40 m)',JX,48,JZ,1); B.label('Jewel Changi Airport',JX,66,JZ,1.5); B.label('Canopy Bridge (23 m high, 50 m long)',JX,28,JZ+41,.8);
/* ----- terminals (simplified blocks) ----- */
B.box(['glassW','white'],130,0,-110,230,26,120); B.box(['glassC','white'],130,0,60,260,24,120); B.box(['glassB','white'],-330,0,-110,200,24,130);
B.box('white',100,0,-30,40,18,60,{nc:1}); B.label('Terminal 1',230,36,-50,1); B.label('Terminal 2',260,34,120,1); B.label('Terminal 3',-230,34,-45,1);
plane(B,330,150,1,58); plane(B,440,150,1,64); plane(B,550,150,1,52); plane(B,668,-120,1,60);
/* ----- the control tower: 80 m including its mast ----- */
(function(){
var cx=470,cz=-200, W=14;
/* base hall (door on the south) */
B.box('white',cx-W/2,0,cz-W/2,W,6,2); B.box('white',cx-W/2,0,cz-W/2,2,6,W); B.box('white',cx+W/2-2,0,cz-W/2,2,6,W);
B.box('white',cx-W/2,0,cz+W/2-2,(W-4)/2,6,2); B.box('white',cx+2,0,cz+W/2-2,(W-4)/2,6,2); B.box('white',cx-2,5,cz+W/2-2,4,1,2);
B.box('lamp',cx-4,5.7,cz-4,8,.3,8,{nc:1}); B.box('gold',cx-2,0,cz-2,4,.15,4,{nc:1});
B.box(['white','conc'],cx-W/2,6,cz-W/2,W,16,W); B.box(['white','conc'],cx-5,22,cz-5,10,42,10);
for(i=0;i<9;i++)B.box('glassC',cx-5.2,28+i*4,cz-5.2,10.4,1.2,10.4,{nc:1});
/* the control room: glass all round, 66 m above the ground */
B.box(['conc','conc'],cx-13,64,cz-13,26,2,26); B.box('white',cx-14,63,cz-14,28,1,28,{nc:1});
B.box('railT',cx-13,66,cz-13,26,6.5,1.2); B.box('railT',cx-13,66,cz+11.8,26,6.5,1.2); B.box('railT',cx-13,66,cz-13,1.2,6.5,26); B.box('railT',cx+11.8,66,cz-13,1.2,6.5,26);
B.box(['white','white'],cx-15,72.5,cz-15,30,1.5,30); B.box('white',cx-8,72.5+1.5,cz-8,16,1,16,{nc:1});
B.box('red',cx-.5,74,cz-.5,1,6,1,{nc:1}); B.box('white',cx-.5,74+6,cz-.5,1,0,1,{nc:1});
B.box('lamp',cx-1.5,66,cz-1.5,3,.5,3,{nc:1}); B.box('black',cx-5,66,cz-8,10,1.2,2,{nc:1});
B.box('gold',cx+6,66,cz+6,3,.15,3,{nc:1});
B.label('Changi Control Tower (80 m)',cx,92,cz,1.4); B.label('Lift to the control room',cx,4,cz+8,.6); B.label('Lift down',cx+7.5,71,cz+7.5,.6);
T.chLifts=[{x:cx,z:cz,r:3.5,y:0,tx:cx+7.5,tz:cz+7.5,ty:66,txt:'Lift up to the control room (66 m)'},{x:cx+7.5,z:cz+7.5,r:3,y:66,tx:cx,tz:cz+7,ty:0,txt:'Lift down to the ground'}];
})();
for(i=0;i<12;i++){var lx=-190+i*60; B.box('grey',lx,0,335,.4,5,.4,{nc:1});}
var s={id:'changi',name:'Changi Airport',B:B,G:G,bx0:BX0,bx1:BX1,bz0:BZ0,bz1:BZ1,water:false,fly:false};
G.emit(B); B.build(); s.hash=B.hash; s.grp=B.grp; s.land=function(x,z){return true;};
s.lm=[{id:'jewel',x:0,z:0,r:120},{id:'changi',x:470,z:-200,r:60}];
s.places=[{n:'Jewel Changi Airport',x:0,z:110,to:[0,0]},{n:'Rain Vortex',x:0,z:20,to:[0,0]},{n:'Control tower',x:470,z:-170,to:[470,-200]},{n:'Terminal 1',x:250,z:-40,to:[250,-100]},{n:'Runway',x:640,z:0,to:[500,0]}];
s.lifts=T.chLifts; s.spawn={x:0,z:112,to:[0,0]}; s.entry={jewel:{x:0,z:112,to:[0,0]},changi:{x:470,z:-170,to:[470,-200]}};
s.fog=[350,2200]; s.near=.15; s.far=3500; s.lblMax=1600; s.extra=[];
return s;
}
T.SC.changi=buildChangi;
})(TZ);
/* ===== True-size areas from OpenStreetMap data: Fort Canning, Botanic Gardens, Sultan Mosque, Chinatown, Haw Par Villa, Sentosa.
Map data (c) OpenStreetMap contributors, licensed under the Open Database Licence (ODbL) https://www.openstreetmap.org/copyright
The numbers below are a reduced copy of that data (a derivative database, made with tools/osm2area.js). 1 block = 1 metre.
Buildings are the real outlines lifted to their real height (height tag, else levels x 3 m, else 6 m: those are estimates).
There is no height data for the ground, so hills are flat. ===== */
(function(T){
var OSM={
fc:{"n":"Fort Canning","H":200,"base":"grass","c":[1.2943,103.8462],"b":[[20,0,-1,-1,0,387,-44,7,20,6,-2,0,21,-2,1,2,6,0,37,-2,-8,-40,11,-4,-22,-18,4,-13,-46,20,-5,-5,-22,38,-8,5,15],[6,0,15260864,9067068,0,114,-184,-7,3,-5,-18,-2,-18,7,0,-16,-73,40,-9,16,76,4,-3,5,18,2,18,-4,0,15,77,-40,9],[4,2,-1,-1,0,296,-84,16,-2,9,0,4,37,-9,0,-16,3],[6,1,-1,-1,0,11,-389,3,-11,-15,0,-1,6],[3.5,2,-1,-1,0,236,-29,0,25,24,0,0,-25],[6,1,-1,-1,0,-62,-153,-25,-6,-2,6,-2,7,26,7,0,-7],[6,1,-1,-1,0,256,-380,-2,-3,-5,0,-2,0,-2,0,-5,3,0,2,-2,2,0,4,2,3,0,2,5,2,2,0,2,0,5,-2,2,-2,0,-3,2,-2,-2,-4],[6,1,-1,-1,0,325,-363,0,5,13,0,9,0,0,-5,-11,0],[6,1,-1,-1,0,180,-135,11,-2,0,9,-8,2],[3,0,-1,-1,0,-394,292,-6,10,0,42,13,8,11,-18,4,2,14,-22],[6,1,-1,-1,0,-318,252,-20,31,15,9,18,-31],[6,1,-1,-1,0,247,400,5,-4,4,4],[4,2,-1,-1,0,-400,280,6,-10,9,6,-9,16,-6,10],[3,0,15260864,9067068,0,169,-113,7,27,-40,9,-7,-27],[3,0,15260864,9067068,0,120,-299,-33,7,-5,-18,34,-6],[3,0,15260864,9067068,0,78,-341,38,-6,4,17,-40,7],[6,1,-1,-1,0,-45,-91,14,-2,-2,-7,-12,0],[9,4,13816528,13816528,0,-187,-360,-7,-12,-98,73,-20,-24,98,-75,-4,-2,60,0,-11,9,9,11],[9,4,13816528,12731706,0,-135,-400,-25,20,-27,20,-58,44,27,35,58,-46,11,13,27,-18,-12,-15,58,-44,-6,-9]],"s":[],"r":[[3,400,-193,-22,3],[3,343,-318,-80,13,-32,11,-22,20,-11,33],[3,207,-199,18,20,24,13,29,2,67,-13,33,-13],[3,62,-310,52,-15,20,9,6,9,34,188,17,39,5,27],[4,387,-170,-120,35,-93,16],[3,203,-221,-29,4],[3,378,-190,22,44],[3,154,-241,13,0,7,24,4,33,-13,5],[3,198,-241,9,42],[3,345,-327,-7,-31],[4,400,123,-8,-1,-49,15],[1,-62,-400,-9,35,-29,38],[3,-34,400,-88,-86,-27,-51,-34,-82,-6,-55,13,-80,-4,-33,-14,-31,-35,-33,-40,-26,-47,-16],[4,-294,168,38,-4,73,17],[3,-303,108,14,-24,9,-9,13,-4,87,9],[3,-276,334,20,-20,4,-24,-6,-25,-36,-68,-15,-16,15,0,5,-33,-5,-22,-9,-18,-97,-47],[3,-10,400,-46,-135,20,9,89,91],[3,312,-86,-14,-40],[3,-227,-164,105,29],[3,400,334,-26,-137,-9,-22],[3,-118,-126,58,-2,24,-25,83,-37],[3,307,-389,-7,0,-20,-11],[3,-316,-93,-20,-68,-2,-71,11,-58,18,-62,65,-48],[2,-136,-161,-51,-23,-7,-6,-6,-16,2,-17,7,-9,73,-27,53,-51,41,-62,8,-28],[2,272,119,42,89,27,159,-4,33],[3,-276,334,-4,4,-16,49,16,13],[4,387,272,13,-4],[4,325,53,24,-2,3,11,31,11],[1,383,73,2,-4,15,-6],[3,316,-53,-13,0,-5,-29,16,0],[3,309,-93,-13,0,-9,-20],[3,287,-113,-49,60,-33,22],[4,400,-135,-102,9,-11,13],[3,345,-327,42,-7,13,2],[3,238,-53,9,20,16,0,2,31,-18,2],[2,272,119,-76,-172,-58,-22,-20,-18,-29,-104,-11,-75,-52,-128],[3,57,400,-19,-128,-11,-31,-25,-22,-44,-20,-23,-22,-13,-31,0,-168,-7,-64,-33,-40],[3,396,316,4,5],[0,-400,375,31,25],[3,307,-389,2,-11],[3,36,-380,25,-20],[4,-327,-290,100,126],[3,-236,341,-4,24,9,20,26,15],[3,385,-86,-20,-18,-22,-2,-11,-24],[3,400,-60,-10,-11,-5,-15,15,-36],[3,272,119,22,-97,13,-24],[3,307,-2,18,55,18,84,22,38],[4,-56,265,-20,7,-46,42,-65,24,-24,5],[3,-47,-186,-38,-9,-8,-13,4,-31],[3,-78,-22,13,-2,18,-16,7,-11,0,-22,-9,-18,2,-106,-9,-22,-33,-20],[3,-118,-126,-4,-9],[3,-122,-135,-14,-26],[3,-136,-161,47,8],[1,398,170,-15,-97],[1,-100,-327,-14,17,-28,16,-9,4,-9,-6],[1,-160,-296,-2,-9,4,-5,7,-2,13,5,27,-18,11,-2],[4,47,-349,-11,4,2,9],[3,-236,341,-16,-51],[3,-211,343,-65,-9],[4,-316,-93,-18,0],[2,365,175,-93,-56],[1,-400,235,17,-34,7,-4,13,4,0,16,-37,55],[3,-309,181,-49,34,-42,68],[3,378,-190,-31,-78,-4,-50,2,-9],[1,-400,51,4,2],[1,-396,53,73,38],[3,316,-49,-4,-37],[3,307,-2,9,-47],[3,247,-33,-13,0,0,33,13,0],[3,249,7,31,79],[3,247,0,2,7],[3,-65,-146,16,4],[3,336,-363,-2,-15,-7,-7,-20,-4],[3,267,-166,0,31],[4,-334,-93,-66,-1],[3,-400,18,46,11,89,42],[3,-400,349,46,31],[3,-400,218,13,-26,15,-6,18,15,-4,14],[4,-89,-239,-29,-20],[3,274,-400,-18,22],[4,47,-190,42,-7],[4,-227,-164,-13,16,46,130],[3,-256,164,40,-51],[4,-216,113,31,-16],[3,-49,-91,-36,5],[3,-354,380,20,20]],"w":[],"wi":[],"g":[[0,400,400,0,-210,-48,-121,-7,-14,-24,-86,13,-24,49,-14,17,5,0,-336,-800,0,0,758,57,42],[0,367,190,33,170,0,29,-15,5,-38,-199]],"wd":[],"sd":[[-300,-279,8,9,40,-31,-6,-11],[-240,-281,-3,0,-4,0,-5,2,-2,5,-2,4,-7,2,-2,0,-7,3,-2,2,0,4,0,7,2,2,7,2,7,0,2,5,4,0,5,-2,2,-5,0,-4,0,-5,2,-4,5,-9,2,-4,-2,-3]],"bc":[],"t":[300,-212,71,-106,40,-259,321,64,278,-177,272,44,29,356,-249,-184],"lb":[["METT Singapore",-93,-174,12],["Fort Canning Centre",65,-101,12],["Registry of Marriages",187,-4,26],["Fort Canning Gate",-38,-75,12],["Fort Canning Park",91,19,15]],"tri":947,"stats":{"bld":19,"est":8,"roads":86,"trees":8,"water":0,"green":2,"wood":0,"dropped":0}},
bg:{"n":"Botanic Gardens","H":250,"base":"grass","c":[1.3151,103.8162],"b":[[3,0,-1,-1,0,-11,-27,4,-2,-2,-11,25,-6,13,53,7,-3,9,36,-14,4,0,-4,-47,11,-2,-11,-22,4,-5,-15,38,-11,0,-5,11,-2,-2,-13,-6,2],[6,1,-1,-1,0,421,-484,20,6,6,3,-29,84,43,24,9,4,13,9,-11,18,-13,-7,-9,-4,-25,-16,-49,87,-29,-16,25,-42,22,-38,4,5,7,-13,-4,-3,0,-4,-14,-4],[6,0,-1,-1,0,-194,27,5,-5,-5,-2,11,-13,5,-5,2,-4,18,15,-20,22,-5,-4,-2,2],[6,1,-1,-1,0,-276,281,-2,11,-7,4,2,3,5,13,9,11,17,0,14,-7,7,-9,0,-11,-3,-11,-9,-6,-4,2,-5,-7,-8,-4,-9,2,-5,4],[6,1,-1,-1,0,443,378,18,-4,-7,-27,42,-11,-11,-42,-78,20,7,20,18,-4,2,4,-4,0,2,11,-14,2,3,11,15,-4],[6,1,-1,-1,0,490,265,-11,-44,-9,2,-3,-4,-11,2,11,44,9,-4,3,9],[6,1,-1,-1,0,455,500,1,-7,28,7],[3,0,-1,-1,0,313,500,-1,-5,-5,3,-2,-3,4,-2,-2,-4,-28,11,-6,0,-4,-11,5,-2,0,-3,-5,0,-15,-37,67,-25,8,20,-49,18,7,20,-13,4,2,0,27,-9,0,0,-5,-11,5,-2,4,16,5,-3,2,5,-5,2,6,18,11,0,17,-7,2,7,7,-2,-9,-25,-11,5,-5,-11,22,-9,18,42,0,0,-13,0,0,0,-1,0],[9,0,-1,-1,0,280,402,-8,-2,-3,27,-11,0,0,-33,9,0,5,-25,11,3],[9,0,-1,-1,0,300,283,-6,0,-5,27,-13,-3,4,-31,9,0,3,-24,11,0],[9,0,-1,-1,0,296,139,13,0,0,38,-13,0],[6,1,-1,-1,0,483,431,17,-6,0,43,-4,1,-4,-16,-18,7,-2,13,-13,-2,4,-31,16,2,-3,11,14,-4],[6,1,-1,-1,0,387,111,45,57,-9,7,-33,-47,-29,11,-7,-17],[6,1,-1,-1,0,-381,285,-6,18,-3,2,-20,-6,-4,24,11,4,9,3,9,8,2,0,2,3,3,4,6,9,5,9,22,-7,-9,-18,5,-4,15,-9,-13,-18,-18,-13],[6,1,-1,-1,0,-490,-166,-10,-15,0,-11,8,-7,-7,-9,49,-35,12,13,-9,7,17,22,-37,24,-3,-4],[6,0,-1,-1,0,-479,-111,23,-17,15,17,25,-19,18,22,-25,20,11,13,-15,11,-9,-9,-7,7],[6,1,-1,-1,0,-500,-176,19,23,-9,7,14,16,-23,15,-1,-2],[6,1,-1,-1,0,-496,-398,26,-9,3,9,-16,18,-17,2,0,-59,15,-10,6,11,-21,24,0,3],[6,1,-1,-1,0,-494,-500,0,11,11,0,0,14,-17,0,0,-25],[6,1,-1,-1,0,-459,-352,7,31,-44,9,-4,-17,0,-14],[6,1,-1,-1,0,-410,-409,34,40,-18,13,-33,-40],[6,1,-1,-1,0,-412,-369,-22,17,-18,-22,22,-17],[6,0,-1,-1,0,-378,-210,42,-35,13,15,5,-4,13,17,-42,33,-9,-11,-7,7],[6,1,-1,-1,0,-332,-425,32,0,0,7,8,0,0,27,-8,0,0,4,-32,0],[6,0,-1,-1,0,-318,-307,38,13,-7,13,15,7,-11,33,-35,-13,9,-25,-18,-6],[6,0,-1,-1,0,-383,-208,40,53,-4,5,13,17,-24,20,-29,-37,11,-9,-27,-33],[6,1,-1,-1,0,-445,-493,-5,-7,47,0,0,38,-13,0,0,6,-29,3],[6,1,-1,-1,0,-410,-257,32,42,44,-35,-4,-4,11,-9,-29,-36],[6,1,-1,-1,0,-425,-343,9,9,40,-33,7,7,-9,6,4,7,-38,33,-20,-24],[6,1,-1,-1,0,-287,-500,0,22,-18,0,-2,-22,-16,0,0,11,-33,0,0,-11],[6,0,-1,-1,0,205,-318,11,17,13,-6,5,6,6,-2,5,-7,-2,-6,11,-9,-25,-24,-15,-18,-18,9,7,9,8,-7,12,11,-5,2,7,11],[3,0,-1,-1,0,-183,7,-8,-7,-7,9,-9,-9,7,-7,2,3,18,-23,11,12,-5,6,-4,-4,-7,9,7,6],[3,0,-1,-1,0,-4,-66,13,-5,4,22,-46,9,-5,-24,34,-9],[6,1,-1,-1,0,-376,-464,9,6,9,-11,13,9,-13,18,13,9,-27,35,-35,-27],[6,1,-1,-1,0,479,-117,21,-6,0,50],[6,1,-1,-1,0,-256,-447,-4,100,26,0,3,-100],[6,1,-1,-1,0,-378,374,-32,20,5,8,2,9,2,7,36,0,-2,-16,-2,-11,-5,-8],[6,1,-1,-1,0,-474,407,7,4,4,5,4,6,0,7,34,-11,-5,-9,-4,-9,-9,-11,-2,-4,-7,-5],[6,1,-1,-1,0,-410,212,5,7,4,4,-4,7,-11,-11],[6,0,-1,-1,0,-62,-179,42,-11,2,4,7,-2,-2,-7,9,-4,2,11,13,-4,0,-5,42,-11,0,5,9,-3,7,27,-9,2,2,5,-6,0,4,17,13,-4,3,6,11,-4,2,11,4,16,-8,2,0,4,-47,13,-2,-4,-25,4,0,5,-44,11,-3,-5,-9,3,-6,-25,9,-4,0,-2,4,-2,-4,-16,-14,2,-2,-9,-4,3,-5,-18,7,-2],[6,1,-1,-1,0,394,-62,62,-46,44,55,0,7,-59,44]],"s":[],"r":[[0,349,-77,-40,31],[1,-231,-305,35,-93,9,-38,-2,-64],[3,-274,-11,31,-20,14,-15,11,-18,4,-20,14,-29,20,-26,26,-20],[3,151,420,-24,11,-22,22,-14,27,-4,20],[3,-276,9,-22,20,-5,11,0,15,18,40,31,38,51,28,23,31,6,18,0,66,16,45,44,73],[3,-47,500,47,-40,13,-22,5,-53,-16,-53,5,-27,26,-44],[4,-116,130,14,20,46,34,40,19],[1,89,-146,16,4,53,3],[1,76,13,-25,-11,-11,-24,0,-13,5,-11,15,-18,22,-5],[3,-385,500,7,-38,17,-31,3,-11,-11,-44,-16,-18],[1,-157,500,12,-47,31,-59,7,-22,-2,-47,-11,-33,-5,-53,9,-109],[1,-500,-234,30,-14,40,-33],[3,69,-292,-71,24,-123,14,-31,6],[3,65,-356,-16,-22,-107,-62,-38,-44,-6,-16],[3,-10,-500,8,11,44,20,38,44,60,29,7,11,11,42,0,31,-4,18,-56,53],[3,-174,-314,18,66,-4,51,6,38,45,29],[3,-7,-7,47,-15],[1,-500,152,21,9,38,0,23,25,53,46,24,29,25,42,11,44,-2,40,-14,46,-29,67],[3,-156,-500,0,42,-13,78,-5,66,-55,106,-11,40,0,31,-5,13,-9,13,-24,11],[3,9,-336,22,-5],[3,245,-285,-27,6],[3,145,-422,-20,-7,-14,-16,-2,-11,5,-31,6,-8,10,-5],[3,155,-500,28,13,6,14,0,15],[3,136,-484,29,-5,11,18,-2,13,-14,11,-13,2,-11,-4,-7,-22],[3,169,-270,0,11,-33,31,-7,18],[1,-91,-190,49,-18,75,-20,40,-15,18,0,11,4],[3,314,-354,15,-18,7,-26,13,-29,5,-24,-6,-49],[3,91,-243,-22,-49],[3,231,-420,-20,-2],[3,169,-270,-15,-24],[3,9,-336,-16,6,-29,-6,-11,2,-6,11,2,11,9,11,15,9,11,0,20,-9,12,-13,4,-22,-11,-27,-13,-13,-27,-13,-22,2,-18,13,-14,22,-4,29,-11,18,-14,4,-11,-4,-2,-40,11,-27,20,-30,0,-9,-18,-29,-20,-49,0,-11],[3,-116,130,-4,-11,2,-26,36,-66,2,-20,-2,-29,-23,-33,-6,-49,2,-26],[4,-58,-440,-38,29],[3,-283,299,-6,0],[3,-247,281,29,-16],[3,-289,299,-7,2],[1,176,-4,24,-11,29,-25],[3,-218,265,-16,-42,-38,-64,-26,-22,-34,-57,-6,-16,-3,-46,-6,-9,-43,-22],[3,-296,301,20,33,18,11,9,0,18,-9,11,-22,4,-29,-2,-20],[1,269,-144,-9,-15,0,-16,7,-15,13,-11,16,-5,16,5,13,11],[1,349,-77,-6,-25,-25,-40],[1,269,-144,-62,36],[1,332,-175,53,-13,27,-18,58,-115],[1,205,462,35,18,17,20],[3,136,-484,0,0],[3,-321,-71,-95,80,-20,22,-11,26,-3,29,9,75],[4,-198,133,15,-14,12,-17],[3,-16,203,12,-2,17,11,11,18,9,31],[1,247,-66,-16,-20,-24,-22],[1,247,-66,31,-29],[3,256,-414,40,29,18,31],[3,276,-500,0,20,-20,66],[1,-116,130,-24,-22,-105,-17,-11,-9,-13,-22,-7,-25,2,-46,-4,-89,-7,-50],[1,-450,15,-50,-26],[0,309,-46,-24,24,-18,26,-31,65,-11,37],[0,403,-115,-54,38],[3,65,-356,-34,15],[3,69,-292,-4,-64],[3,98,-414,-109,-46,-22,-20,-3,-20],[3,98,-414,2,12,-4,13,-14,0,-4,-9,4,-7,18,-2],[3,-33,-484,-18,-7,-6,-9],[1,481,-217,-5,-4,3,-18,11,-11,10,-4],[3,-183,100,29,-34,5,-13,0,-31,4,-15,25,-45,15,-17],[3,129,-402,16,-20,26,-7,14,-13,4,-16,31,11,16,-6,2,-11,-20,-18,10,-18],[3,169,-453,16,11],[3,167,-495,-2,6],[3,314,-354,-40,55,-29,14],[3,53,-489,-18,-11],[3,42,-469,11,-24,13,-7],[3,-338,22,20,-35,4,-20,-15,-31],[3,-198,133,0,22,-20,17,-5,36,-11,15],[3,-231,95,20,29,13,9],[3,-472,489,11,0,7,4,2,7],[3,-416,500,2,-22,-7,-14,-15,-6],[3,-410,336,-22,-2,-9,4,-40,53,-11,-2,-8,6],[3,-500,414,24,11,11,13,4,18,-2,19],[3,33,261,36,-4,18,6,13,11,47,95,4,51],[3,-196,29,-13,-11,4,-5,-4,-4,-2,4,-12,-6,0,-7,27,-38,-4,-26,-16,-11],[3,0,-9,-7,-42,-44,11,-22,-90,-18,-27,-7,-24],[1,296,-276,16,-11,15,-25,20,-15,25,-7],[3,441,-478,4,-4,18,-2,37,18],[3,220,-447,-2,25],[3,265,-407,13,-11,14,0,11,-9,4,-6,0,-20,16,-16,0,-11,-16,-11,-4,-9],[1,345,-215,-33,-24,-7,-9,-5,-20,-6,-11],[3,450,-347,11,-20],[3,500,-293,-17,8,-24,-16],[3,285,-314,-22,-2,-29,9,-16,28],[3,-356,11,4,-4],[3,-296,301,-13,-20],[4,-309,281,-14,6],[0,500,-178,-21,14],[1,452,126,-20,-22,-20,-47],[1,-500,131,50,-116],[1,-450,15,36,-30],[1,263,248,-36,-7],[1,207,442,29,-9,11,-13,16,-172],[0,207,429,0,13],[0,205,462,-4,38],[0,227,241,-20,188],[0,207,442,-2,20],[1,-414,-15,129,-135],[1,-285,-150,11,-20,43,-135],[1,500,391,-90,27],[1,-476,-500,2,38,-7,9,-19,8],[1,-430,-281,7,18,-2,35],[1,-327,-341,-16,47],[1,452,126,22,-26,5,-16,2,-42,19,-8],[1,385,0,38,-20],[1,207,-108,-47,33],[1,158,-139,40,22,9,9],[1,470,-321,26,-48],[3,-341,349,-8,-15],[1,470,-363,4,-9,7,-2,15,5],[1,459,-343,11,-20],[1,470,-321,-11,-11,0,-11],[3,461,-367,18,-7,21,-2],[3,325,-190,109,-128,16,-29],[3,-443,462,7,-4,6,-13,14,-9],[1,496,-369,4,-6],[1,412,57,-27,-57],[1,160,-75,-42,26],[1,147,-159,11,20],[1,229,-40,18,-26],[1,125,-31,13,11,38,16],[1,125,-31,-5,20,-11,15],[0,225,106,-2,44,4,91],[0,479,-164,-76,49],[1,109,4,-16,9,-17,0],[1,82,-69,27,9,9,11],[1,118,-49,7,18],[1,500,-227,-19,10],[1,263,248,9,-93],[1,-327,-341,96,36],[1,-430,-281,76,-60,27,0],[1,385,0,-36,-77],[1,500,185,-48,-59],[3,-309,281,-27,-36,-38,-37,-47,-42,-20,-11],[1,325,-190,7,15,0,14,-5,11,-9,8,-11,7,-13,2,-25,-11],[1,176,-4,-38,4,-22,-4],[3,500,-162,-131,89],[3,500,-202,-142,102],[3,345,-57,73,163,20,31,62,73],[3,369,-73,27,66,67,120,37,47],[3,345,-57,-38,30,-33,47,-25,53,-9,55,-9,115],[3,369,-73,-24,16],[3,-247,-301,7,-20],[3,-327,-341,2,-6,76,24,15,0,34,-82,4,-57,-2,-38],[3,-500,93,39,-82,69,-62,49,-53,49,-57,18,-31,31,-102,-2,-7,-82,-33],[3,358,-100,-22,18],[3,345,-57,-7,-12,-2,-13],[3,369,-73,-11,-27],[3,245,-71,27,-26,6,2],[3,131,0,7,-20],[3,265,-153,-67,36,-40,31,-20,2,-20,-7,-16,0,-33,9,-27,22,-9,20,0,20,16,31,13,11,18,5,31,-7,11,-5,9,-15],[3,336,-82,-4,-18,-23,-37],[3,191,422,14,-115,11,-59,4,-56,-2,-79],[3,218,113,9,-49,31,-60,20,-31,58,-55],[3,181,500,10,-78],[1,-387,-44,-25,-29],[1,-336,-97,-16,-16],[1,-314,-122,-24,-31,-3,-28],[1,-300,-330,-9,23],[1,-425,-283,-27,-35],[1,-349,-358,-27,-27],[1,-312,-334,5,-51],[1,-256,-314,7,-22],[1,-375,-500,1,20],[1,-336,-343,-13,-15],[3,-470,433,-2,7,-20,0,-8,5],[3,-500,463,1,8],[3,-383,338,-13,14,-14,-5,-11,2,0,7,5,4,13,0,7,5,-16,13,-4,0,-9,-11,-7,-18,-9,7,7,7,0,9,9,4,2,13,5,5,4,0,7,-11,20,-7,9,-13],[3,-472,161,-20,18,-8,12],[3,-500,196,4,14,13,5,60,-34],[3,-405,219,13,-9],[3,-383,440,11,9],[4,-416,436,13,-3,20,7],[4,-463,475,20,-13],[3,-472,489,9,-14],[4,-481,498,9,-9],[3,-484,500,3,-2],[1,-358,420,17,55],[4,-434,365,-4,4,-12,0,-4,-13],[3,-358,356,4,-4,0,-7,-9,-9,18,-13,-9,-7,-7,0,-6,-9,0,-6,-11,0,-3,11,-6,4,-11,-9,-7,3,0,8,22,9,2,7,-4,4],[3,-385,358,-11,-9],[3,-385,338,-11,11],[3,-396,349,-14,-13],[3,-500,471,1,0],[3,-499,416,-1,0],[3,-432,431,9,9],[4,-436,427,4,4],[3,-481,391,11,11,11,5,5,15,13,0,5,5],[3,-381,307,-6,-4],[3,-349,334,-3,-4],[3,-369,376,26,-13,2,-14],[3,-441,422,7,-6,-4,-5,0,-6,-14,-5,0,-6,-7,-5,0,-6,5,-5,13,2,5,5,11,26,7,3,6,-14,20,-2,2,7,9,-3,0,-6,5,-2,-9,-7,-2,-11],[3,-381,334,9,0],[3,-383,338,0,-15,9,-2,2,-7,5,0,4,11,-6,22,11,9],[3,-278,-100,-25,14,-18,15],[3,-147,38,-18,11,-9,-3,-2,-8,-7,4,-13,-13],[3,-80,7,73,-14],[3,-365,232,-11,13,-51,7,-73,37],[3,-490,283,9,4,27,-6,11,2,20,16,7,13],[4,-432,270,-6,0,-12,26,-13,7,4,20],[3,-365,232,-9,-24],[3,-176,38,11,-14,20,-11],[3,-223,4,-6,7],[3,-216,11,7,-9],[3,-183,42,-11,11,-20,-11,-4,4,15,16],[3,-214,42,0,-2],[4,-214,40,9,-11],[3,-205,29,5,-2],[4,249,-416,-18,-4],[3,256,-414,-7,-2],[3,185,-480,13,-7,4,-13],[4,211,-422,-6,-3],[3,205,-425,-34,-4],[4,294,-279,-20,-17],[3,274,-296,0,-3],[1,102,-239,23,20,4,9,2,26,16,25],[1,-109,-130,2,-36,16,-24],[3,-499,471,-1,24],[3,154,-294,35,0,18,20],[3,218,-279,-29,11],[4,189,-268,-20,9],[3,151,-292,-11,0,-15,9,-29,38],[3,96,-245,-3,-7,-6,-2],[3,245,-142,4,-13,0,-22,7,-20,16,-9,31,-4,18,7,8,8],[3,329,-195,5,5,40,-2,18,-9,60,-98,-2,-13,-16,-6]],"w":[[256,-206,-2,-2,-2,-2,-3,-5,0,-2,0,-2,3,-2,2,-2,4,-3,2,-2,5,0,4,0,3,5,2,2,0,4,0,5,-5,4,-4,0,-2,5,-5,0],[-369,0,-9,-4,-5,4,-9,7,-9,8,-6,14,-9,9,-5,13,-4,15,-2,20,0,16,4,15,7,20,-7,-2,-7,-2,-8,6,2,14,18,11,86,77,20,29,34,55,4,7,7,4,7,2,8,0,18,-6,7,-11,4,-18,3,-11,-5,-13,-29,-76,-11,-13,-22,-18,-13,-19,-29,-47,-14,-29,5,-26,0,-13,-3,-14,-2,-4,-6,-9,-7,-4]],"wi":[[-276,281,-2,11,-7,4,2,3,5,13,9,11,17,0,14,-7,7,-9,0,-11,-3,-11,-9,-6,-4,2,-5,-7,-8,-4,-9,2,-5,4]],"g":[[0,172,500,2,-29,26,-159,14,-117,-5,-78,16,-77,38,-67,53,-46,47,-33,104,-69,33,-22,0,-54,-6,-6,-40,-57,36,-71,-3,-9,13,-26,0,-80,-664,0,2,11,-25,168,-91,182,-165,166,-22,61,-34,65,-1,2,0,345],[0,-500,236,8,3,33,-11,16,-5,29,7,91,73,11,20,3,24,-3,42,-6,31,-31,69,-5,11,-146,0],[0,65,-356,4,64,-71,22,-123,13,-31,7,-15,-64,6,-66,11,-78,0,-42,52,0,6,16,38,44,38,24,44,25,14,6,11,7,7,6,4,7],[0,-289,327,6,9,11,11,12,2,11,0,11,-4,9,-4,6,-9,9,-16,31,-4,5,11,-13,7,6,8,27,25,7,33,0,22,-20,82,-173,0,8,-20,20,-44,13,-45,0,-46,0,-13],[1,-227,-500,-7,153,-84,208,-182,159,0,-520],[0,198,-307,-11,-7,-11,2,-18,0,0,-31,-11,-42,-7,-11,-42,-18,-18,-11,-18,-17,-6,-18,34,-40,306,0,-42,49,-5,24,-13,29,-7,26,-15,18,-18,22,-11,18,-11,-4,-14,-3,-42,16],[1,467,-128,33,66,0,-106],[1,216,500,20,-363,171,-46,-58,-135,118,-84,33,-40,0,668],[1,500,-153,-33,25,33,-40],[1,467,-128,33,66,0,-106]],"wd":[[167,500,2,-31,9,-67,16,-92,13,-115,-4,-78,15,-86,-7,-11,-60,26,-35,-6,-47,11,-38,26,-69,11,-33,5,-20,29,-18,70,-2,71,18,58,-3,59,-40,87,-6,33],[236,137,171,-46,-58,-135,-11,-2,-17,0,-29,22,-36,62,-16,42]],"sd":[[383,137,-20,7,11,33,18,-5],[-492,-303,18,-2,2,6,0,3,-2,0,-5,2,-2,0,-2,4,-2,3,-7,0,-2,0,-2,-12,0,-2],[-396,-414,-5,7,25,18,2,-7]],"bc":[],"t":[-209,179],"lb":[["National Parks Board Headquarters",5,-78,12],["Raffles Building",211,-187,12],["Visitor Centre",1,5,9],["Sembcorp Cool House",-188,163,12],["Shaw Foundation Symphony Stage",-129,147,12],["National Biodiversity Centre",112,-165,12],["Tan Hoon Siang Mist House",-193,200,12],["EJH Corner House",-91,9,12],["Singapore Botanic Gardens",62,-37,15]],"tri":2779,"wdA":37421.72811352184,"stats":{"bld":41,"est":27,"roads":228,"trees":1,"water":3,"green":5,"wood":2,"dropped":0}},
sultan:{"n":"Sultan Mosque","H":200,"base":"pave","c":[1.3022,103.8581],"b":[[9,0,-1,-1,0,-160,53,6,-9,92,-104,24,20,38,36,18,13,-20,22,33,31,-64,73,-14,2,-13,5,-9,-9],[6,1,-1,-1,0,400,-170,-6,-9,-4,4,-7,-11,-29,20,7,9,-5,4,13,18,5,-2,9,15,9,-4,6,7,2,-1],[6,1,-1,-1,0,400,-196,-6,4,-2,-5,-2,0,-3,-2,-4,4,-9,-15,26,-17],[3,0,-1,-1,0,-396,303,4,4,14,9,-22,26,0,-35],[6,1,-1,-1,0,-385,-274,38,37,-9,9,-38,-37],[6,1,-1,-1,0,-314,-287,-4,-3,18,-17,2,4],[3,0,-1,-1,0,-343,-272,11,-11,7,7,-9,11,7,6,-7,7,-7,-7,-15,-13,7,-7,0,0],[6,1,-1,-1,0,287,-343,-38,33,-13,-15,-2,-2,40,-31],[6,1,-1,-1,0,356,-389,-7,-9,3,-2,20,0],[6,1,-1,-1,0,174,-239,-14,-15,31,-25,14,14],[6,1,-1,-1,0,134,-195,6,5,-33,26,-5,-4],[6,1,-1,-1,0,200,385,11,11,25,-20,-11,-11],[6,1,-1,-1,0,62,-155,-4,-6,24,-20,5,6],[6,1,-1,-1,0,120,-234,-4,-7,29,-22,4,9],[6,1,-1,-1,0,180,53,7,7,-38,31,-4,-5],[6,1,-1,-1,0,211,201,5,7,-27,22,-4,-4],[6,1,-1,-1,0,176,46,4,7,-35,33,-7,-9],[6,1,-1,-1,0,216,208,4,4,-26,22,-5,-4],[6,1,-1,-1,0,169,40,7,6,-38,31,-7,-6],[6,1,-1,-1,0,220,212,7,7,-29,22,-4,-7],[6,1,-1,-1,0,165,33,4,7,-38,31,-4,-5],[6,1,-1,-1,0,227,219,4,4,-28,25,-5,-7],[6,1,-1,-1,0,158,27,7,6,-38,33,-7,-9],[6,1,-1,-1,0,231,223,5,7,-27,22,-6,-4],[6,1,-1,-1,0,194,374,6,11,25,-20,-11,-13,-9,4,4,4,-9,7],[6,1,-1,-1,0,263,374,-16,13,-7,-7,16,-15,4,7],[6,1,-1,-1,0,51,-148,9,9,-36,31,-6,-7],[6,1,-1,-1,0,116,-217,-31,27,-7,-7,31,-29],[6,1,-1,-1,0,151,20,7,7,-38,30,-6,-6],[6,1,-1,-1,0,236,230,4,4,-29,25,-2,-7],[6,1,-1,-1,0,240,234,5,9,-27,22,-7,-6],[6,1,-1,-1,0,245,243,7,7,-27,22,-7,-7],[6,1,-1,-1,0,252,250,6,7,-27,22,-6,-7],[6,1,-1,-1,0,269,270,7,6,-13,11,-14,14,-6,-7],[6,1,-1,-1,0,341,241,6,-4,16,20,-7,6,-11,-13],[6,1,-1,-1,0,58,-161,-7,-9,25,-20,6,9],[6,1,-1,-1,0,116,-241,-7,-9,29,-22,7,9],[6,1,-1,-1,0,263,263,6,7,-26,24,-7,-7],[6,1,-1,-1,0,42,-20,-4,-7,22,-37,5,7],[6,1,-1,-1,0,258,257,5,6,-27,24,-5,-8],[6,1,-1,-1,0,38,-27,-7,-4,25,-40,4,7],[6,1,-1,-1,0,31,-31,-4,-4,22,-40,7,4],[6,1,-1,-1,0,27,-35,-7,-7,25,-38,4,5],[6,1,-1,-1,0,45,-155,6,7,-33,33,-7,-9],[6,1,-1,-1,0,20,-42,-4,-7,26,-35,3,4],[6,1,-1,-1,0,16,-49,-7,-4,27,-38,6,7],[6,1,-1,-1,0,9,-53,-7,-7,27,-35,7,4],[6,1,-1,-1,0,2,-60,-4,-4,26,-38,5,7],[6,1,-1,-1,0,-2,-64,-9,-7,29,-37,6,6],[6,1,-1,-1,0,347,237,7,-7,15,22,-6,5],[6,1,-1,-1,0,165,341,18,19,11,-8,13,-9,-18,-22],[6,1,-1,-1,0,51,-170,-6,-7,24,-20,7,7],[6,1,-1,-1,0,109,-250,-7,-9,29,-20,7,7],[6,1,-1,-1,0,-11,-71,-7,-6,29,-36,7,5],[6,1,-1,-1,0,-18,-77,-6,-9,28,-33,7,6],[6,1,-1,-1,0,-24,-86,-7,-9,31,-31,4,7],[6,1,-1,-1,0,-31,-95,-9,-7,31,-33,9,9],[6,1,-1,-1,0,-40,-102,-7,-9,31,-31,7,7],[6,1,-1,-1,0,160,336,5,5,24,-20,-4,-5],[6,1,-1,-1,0,249,356,7,9,-16,15,-6,-8],[6,1,-1,-1,0,38,-164,7,9,-34,31,-7,-9],[6,1,-1,-1,0,105,-230,4,4,-31,29,-7,-6],[6,1,-1,-1,0,-47,-111,-6,-6,31,-31,6,6],[6,1,-1,-1,0,354,230,9,-4,15,19,-9,7],[6,1,-1,-1,0,156,330,4,6,25,-20,-5,-6],[6,1,-1,-1,0,45,-177,-12,-11,20,-18,7,0,9,9],[6,1,-1,-1,0,345,215,-7,4,-24,-31,7,-4],[6,1,-1,-1,0,142,316,14,14,24,-20,-13,-14],[6,1,-1,-1,0,245,352,4,4,-15,16,-7,-7],[6,1,-1,-1,0,29,-175,9,11,-34,31,-6,-4,-2,-9],[6,1,-1,-1,0,98,-237,7,7,-34,27,-4,-7],[6,1,-1,-1,0,363,226,6,-7,16,22,-7,4],[6,1,-1,-1,0,138,310,4,6,25,-20,-5,-6],[6,1,-1,-1,0,336,-49,7,9,-36,27,-2,-5,4,-4,-4,-5],[6,1,-1,-1,0,352,210,-7,5,-24,-31,6,-7],[6,1,-1,-1,0,131,301,7,9,24,-20,-6,-9],[6,1,-1,-1,0,238,343,7,9,-18,13,-4,-9],[6,1,-1,-1,0,93,-243,5,6,-31,27,-5,-5,27,-24],[6,1,-1,-1,0,209,-276,-2,-7,31,-24,5,6],[6,1,-1,-1,0,91,-122,-11,-13,22,-22,5,4],[6,1,-1,-1,0,369,219,7,-4,16,19,-7,7],[6,1,-1,-1,0,127,294,4,7,25,-20,-5,-7],[6,1,-1,-1,0,343,-40,6,9,-35,24,-7,-6],[6,1,-1,-1,0,374,396,3,4,-8,0],[6,1,-1,-1,0,358,203,-6,7,-25,-33,7,-5],[6,1,-1,-1,0,118,285,9,9,24,-20,-9,-9],[6,1,-1,-1,0,400,180,-2,1,-8,-2,-5,-4,15,-10],[6,1,-1,-1,0,234,336,4,7,-15,13,-7,-7],[6,1,-1,-1,0,111,276,7,9,24,-20,-6,-8],[6,1,-1,-1,0,400,165,-15,10,-4,-9,19,-12],[6,1,-1,-1,0,349,-31,5,9,-33,24,-7,-9],[6,1,-1,-1,0,369,391,5,5,-5,4,-9,0],[6,1,-1,-1,0,363,199,-5,4,-24,-31,7,-4],[6,1,-1,-1,0,105,268,6,8,25,-19,-7,-9],[6,1,-1,-1,0,400,154,-19,12,-7,-9,26,-14],[3,0,-1,-1,0,229,332,5,4,-18,13,-2,-4,11,-11],[6,1,-1,-1,0,111,252,-13,9,7,7,24,-20,-7,-7],[6,1,-1,-1,0,400,143,-26,14,-5,-4,31,-19],[6,1,-1,-1,0,354,-22,7,7,-34,26,-6,-9],[6,1,-1,-1,0,305,400,9,-6,5,6],[6,0,-1,-1,0,319,400,-5,-6,7,-5,9,11],[6,0,-1,-1,0,330,400,-9,-11,8,-6,16,17],[6,1,-1,-1,0,363,383,6,8,-9,9,-14,0,-1,-2],[6,1,-1,-1,0,369,186,0,9,-6,4,-22,-31,8,-9],[6,1,-1,-1,0,98,237,13,15,11,-11,-11,-15],[6,1,-1,-1,0,396,126,4,4,0,4,-31,19,-4,-7],[6,1,-1,-1,0,218,316,7,7,-18,15,-7,-8],[3,0,-1,-1,0,400,390,-2,1,-29,-39,14,-7,17,22],[6,1,-1,-1,0,400,367,-17,-22,9,-7,8,11],[6,1,-1,-1,0,91,228,7,9,13,-11,-6,-7],[6,1,-1,-1,0,392,117,4,9,-31,20,-4,-9],[6,1,-1,-1,0,227,316,-4,-6,15,-11,5,6,-5,5,-9,6],[6,1,-1,-1,0,361,-15,6,8,-35,27,-5,-9],[6,1,-1,-1,0,400,349,-8,-11,6,-8,2,2],[6,1,-1,-1,0,356,374,7,9,-18,15,-9,-9],[6,1,-1,-1,0,76,228,6,9,9,-9,14,-9,-7,-9],[6,1,-1,-1,0,387,111,5,6,-31,20,-7,-7],[6,1,-1,-1,0,214,310,4,6,-18,14,-4,-5],[6,1,-1,-1,0,69,219,7,9,22,-18,-7,-9],[6,1,-1,-1,0,383,106,4,5,-33,19,-2,-4],[6,1,-1,-1,0,223,310,-5,-5,16,-13,4,7],[6,1,-1,-1,0,367,-7,5,9,-34,25,-6,-7],[6,1,-1,-1,0,349,365,7,9,-20,15,-7,-9],[6,1,-1,-1,0,62,212,7,7,22,-18,-6,-6],[6,1,-1,-1,0,378,100,5,6,-31,20,-5,-7],[6,1,-1,-1,0,209,305,5,5,-18,15,-5,-7],[6,1,-1,-1,0,256,400,18,-13,9,13],[6,1,-1,-1,0,160,-254,-11,-14,31,-26,11,15],[6,1,-1,-1,0,82,-113,5,7,-20,40,-16,-16],[6,1,-1,-1,0,129,-201,5,6,-32,27,-4,-7],[6,1,-1,-1,0,58,206,4,6,23,-17,-5,-7],[6,1,-1,-1,0,374,93,4,7,-31,19,-4,-6],[6,1,-1,-1,0,218,305,-7,-9,16,-13,7,9],[6,1,-1,-1,0,372,2,6,9,-33,24,-7,-8],[6,1,-1,-1,0,321,387,-5,-7,27,-22,4,7],[6,1,-1,-1,0,51,199,7,7,22,-18,-7,-7],[6,1,-1,-1,0,369,88,5,5,-31,20,-5,-5],[6,1,-1,-1,0,205,299,4,6,-18,13,-4,-4],[6,1,-1,-1,0,45,192,6,7,22,-18,-6,-6],[6,1,-1,-1,0,365,82,4,6,-31,20,-4,-6],[6,1,-1,-1,0,211,296,-6,-6,18,-14,4,7],[6,1,-1,-1,0,378,11,7,9,-33,22,-7,-7],[6,1,-1,-1,0,316,380,-7,-6,29,-22,5,6],[6,1,-1,-1,0,40,186,5,6,22,-17,-5,-9],[6,1,-1,-1,0,361,75,4,7,-31,20,-5,-5],[6,1,-1,-1,0,200,294,5,5,-18,15,-4,-7],[6,1,-1,-1,0,367,354,-6,-9,37,-29,2,2,0,11],[6,1,-1,-1,0,356,71,5,4,-32,22,-4,-6],[6,1,-1,-1,0,291,400,-2,-2,23,-15,6,6,-13,11],[6,1,-1,-1,0,205,290,-5,-7,16,-13,7,6],[6,1,-1,-1,0,385,20,5,7,-32,24,-6,-9],[6,1,-1,-1,0,309,374,-4,-7,27,-22,6,7],[6,1,-1,-1,0,352,64,4,7,-31,20,-4,-5],[6,1,-1,-1,0,194,287,6,7,-17,13,-7,-6],[6,1,-1,-1,0,361,345,-5,-7,36,-28,6,6],[6,1,-1,-1,0,347,60,5,4,-31,22,-5,-6],[6,1,-1,-1,0,289,398,-4,-7,22,-15,5,7],[6,1,-1,-1,0,200,283,-6,-9,15,-13,7,9],[6,1,-1,-1,0,390,27,4,6,-31,22,-5,-4],[6,1,-1,-1,0,305,367,-7,-7,29,-22,5,7],[6,1,-1,-1,0,343,53,4,7,-31,20,-4,-7],[6,1,-1,-1,0,187,279,7,8,-18,14,-7,-7],[6,1,-1,-1,0,356,338,-4,-6,35,-29,5,7],[6,1,-1,-1,0,338,49,5,4,-31,20,-5,-4],[6,1,-1,-1,0,194,274,-7,-9,16,-13,6,9],[6,1,-1,-1,0,285,391,-7,-6,22,-18,7,9],[6,1,-1,-1,0,394,33,6,8,0,1,-31,22,-6,-9],[6,1,-1,-1,0,298,360,-4,-6,27,-22,6,6],[6,1,-1,-1,0,336,44,2,5,-31,20,-4,-5],[6,1,-1,-1,0,183,272,4,7,-18,15,-4,-7],[6,1,-1,-1,0,207,-283,-7,-7,34,-24,4,7],[6,1,-1,-1,0,80,-135,-7,-9,25,-20,4,7],[6,1,-1,-1,0,138,-210,-4,-7,28,-22,7,9],[6,1,-1,-1,0,352,332,-7,-7,38,-29,4,7],[6,1,-1,-1,0,332,38,4,6,-33,20,-5,-7],[6,1,-1,-1,0,278,385,-4,-7,22,-18,4,7],[6,1,-1,-1,0,187,265,-4,-4,17,-13,3,4],[6,1,-1,-1,0,400,52,-26,19,-5,-7,31,-22],[6,1,-1,-1,0,294,354,-5,-7,27,-22,5,7],[6,1,-1,-1,0,325,29,7,9,-34,19,-4,-6],[6,1,-1,-1,0,176,263,7,9,-18,15,-7,-8],[6,1,-1,-1,0,345,325,-4,-7,33,-24,4,-4,5,6],[6,1,-1,-1,0,321,24,4,5,-31,22,-5,-7],[6,1,-1,-1,0,274,378,-2,-4,-3,-2,23,-16,4,4],[6,1,-1,-1,0,183,261,-7,-11,18,-11,6,9],[6,1,-1,-1,0,400,62,-22,15,-4,-6,26,-19],[6,1,-1,-1,0,289,347,-6,-6,26,-23,7,7],[6,1,-1,-1,0,316,15,5,9,-32,20,-6,-6],[6,1,-1,-1,0,171,257,5,6,-18,16,-7,-7],[6,1,-1,-1,0,309,11,7,4,-33,23,-5,-5],[6,1,-1,-1,0,269,372,-2,-3,-2,-4,22,-16,5,7],[6,1,-1,-1,0,176,250,-5,-5,16,-13,7,7],[6,1,-1,-1,0,400,76,-15,10,-7,-9,22,-15],[6,1,-1,-1,0,283,341,-7,-7,29,-22,4,6],[6,1,-1,-1,0,305,2,4,9,-31,22,-4,-9],[6,1,-1,-1,0,165,248,6,9,-20,15,-6,-9],[6,1,-1,-1,0,305,2,-31,22,9,-15,13,-18],[6,1,-1,-1,0,265,365,-7,-7,22,-17,7,8],[6,1,-1,-1,0,171,245,-4,-6,16,-13,4,6],[9,0,-1,-1,0,400,86,-10,7,-5,-7,15,-10],[6,1,-1,-1,0,276,334,-4,-9,26,-22,7,9],[6,1,-1,-1,0,158,239,7,9,-20,15,-7,-9],[6,1,-1,-1,0,374,294,-33,24,-14,-17,31,-27],[6,1,-1,-1,0,258,358,-4,-6,22,-18,4,7],[6,1,-1,-1,0,167,239,-5,-5,16,-13,5,5],[6,1,-1,-1,0,400,99,-4,3,-6,-9,10,-7],[6,1,-1,-1,0,272,325,-7,-7,27,-22,6,7],[6,1,-1,-1,0,231,82,7,6,36,-28,-7,-9],[6,1,-1,-1,0,151,232,7,7,-20,15,-4,-6],[6,1,-1,-1,0,149,-268,-13,-15,31,-27,13,16],[6,1,-1,-1,0,73,-119,9,6,-31,31,-9,-11],[6,1,-1,-1,0,125,-206,4,5,-31,26,-5,-4],[6,1,-1,-1,0,327,301,-6,-7,33,-26,4,6],[6,1,-1,-1,0,238,88,7,9,35,-31,-6,-6],[6,1,-1,-1,0,254,352,-5,-7,20,-18,7,7],[6,1,-1,-1,0,162,234,-4,-6,16,-13,4,6],[6,1,-1,-1,0,265,318,-5,-6,27,-22,5,6],[6,1,-1,-1,0,245,97,4,5,36,-31,-5,-5],[6,1,-1,-1,0,147,226,4,6,-17,16,-7,-7],[6,1,-1,-1,0,321,294,-5,-7,33,-24,5,5],[6,1,-1,-1,0,249,102,5,9,38,-31,-7,-9],[6,1,-1,-1,0,158,228,-4,-5,17,-13,3,5],[6,1,-1,-1,0,249,345,-6,-7,22,-17,4,6],[6,1,-1,-1,0,260,312,-6,-7,13,-9,9,-9,7,-4,4,7],[6,1,-1,-1,0,254,111,4,6,38,-31,-4,-6],[6,1,-1,-1,0,140,217,7,9,-20,15,-7,-9],[6,1,-1,-1,0,316,287,-7,-6,34,-27,6,9],[6,1,-1,-1,0,258,117,7,9,38,-31,-7,-9],[6,1,-1,-1,0,243,338,-5,-6,22,-18,5,7],[6,1,-1,-1,0,154,223,-5,-6,18,-11,4,4],[6,1,-1,-1,0,96,69,4,6,-15,13,-5,-6],[6,1,-1,-1,0,265,126,7,7,35,-31,-4,-7],[3,0,-1,-1,0,136,210,4,7,-20,15,-4,-6],[6,1,-1,-1,0,309,281,-6,-11,33,-25,7,9],[6,1,-1,-1,0,278,142,36,-31,-7,-9,-35,31],[6,1,-1,-1,0,238,332,-4,-7,20,-18,6,7],[6,1,-1,-1,0,149,217,-4,-5,15,-13,7,7],[6,1,-1,-1,0,100,75,5,5,-14,13,-6,-5],[6,1,-1,-1,0,131,206,5,4,-20,16,-5,-5],[6,1,-1,-1,0,323,221,6,7,-40,31,-6,-7],[6,1,-1,-1,0,278,142,5,8,35,-31,-4,-8],[6,1,-1,-1,0,145,212,-5,-6,16,-14,4,7],[6,1,-1,-1,0,105,80,6,6,-15,14,-5,-7],[6,1,-1,-1,0,283,150,6,9,36,-31,-7,-9],[6,1,-1,-1,0,125,197,6,9,-20,15,-6,-6],[6,1,-1,-1,0,200,-290,-4,-4,22,-16,11,-8,5,4],[6,1,-1,-1,0,73,-144,-6,-4,24,-20,7,4],[6,1,-1,-1,0,134,-217,-7,-9,29,-22,6,9],[6,1,-1,-1,0,318,215,5,6,-40,31,-5,-7],[6,1,-1,-1,0,289,159,7,7,36,-29,-7,-9],[6,1,-1,-1,0,140,206,-4,-7,15,-11,5,4],[6,1,-1,-1,0,111,86,5,7,-23,18,-4,-5,7,-6],[6,1,-1,-1,0,296,166,4,6,38,-28,-6,-7],[6,1,-1,-1,0,120,190,5,7,-20,18,-5,-7],[6,1,-1,-1,0,312,208,6,7,-40,30,-6,-6],[6,1,-1,-1,0,300,172,7,9,36,-28,-5,-9],[6,1,-1,-1,0,136,199,-5,-4,16,-14,4,7],[6,1,-1,-1,0,116,93,6,7,-22,19,-4,-4,-3,-4],[6,1,-1,-1,0,114,184,6,6,-20,18,-7,-9],[6,1,-1,-1,0,305,199,7,9,-40,31,-7,-7],[6,1,-1,-1,0,131,195,-6,-7,17,-13,5,6],[6,1,-1,-1,0,122,100,7,6,-24,20,-5,-7],[6,1,-1,-1,0,109,177,5,7,-21,15,-4,-7],[6,1,-1,-1,0,300,192,5,7,-40,33,-5,-9],[6,1,-1,-1,0,363,257,29,-23,6,9,-31,25],[6,1,-1,-1,0,125,188,-5,-7,18,-11,4,5],[6,1,-1,-1,0,11,157,7,-7,22,27,-4,4],[6,1,-1,-1,0,18,150,4,-4,25,24,-7,7],[6,1,-1,-1,0,22,146,7,-7,24,27,-6,4],[6,1,-1,-1,0,29,139,7,-6,24,26,-7,7],[6,1,-1,-1,0,36,133,6,-7,25,27,-7,6],[6,1,-1,-1,0,129,106,5,7,-25,20,-4,-7],[6,1,-1,-1,0,102,170,7,7,-20,15,-7,-6],[6,1,-1,-1,0,42,126,5,-7,26,29,-6,5],[6,1,-1,-1,0,47,119,6,-6,29,29,-9,6],[6,1,-1,-1,0,60,104,5,-7,22,25,-7,6],[6,1,-1,-1,0,65,97,8,-9,16,18,4,5,3,4,0,0,-9,7],[6,1,-1,-1,0,73,88,7,-6,5,6,6,5,5,7,-7,6],[6,1,-1,-1,0,294,186,6,6,-40,31,-6,-6],[6,1,-1,-1,0,120,181,-4,-4,18,-13,4,6],[6,1,-1,-1,0,134,113,6,4,-24,22,-7,-6],[6,1,-1,-1,0,398,243,2,2,0,19,-22,17,-11,-13],[6,1,-1,-1,0,98,164,4,6,-20,16,-4,-7],[6,0,-1,-1,0,91,7,-4,8,-9,0,-31,-30,6,-12],[6,0,-1,-1,0,93,-2,-2,9,-38,-34,7,-8],[6,0,-1,-1,0,98,-9,-5,7,-33,-33,5,-7],[6,1,-1,-1,0,269,380,5,7,-18,13,0,0,-2,-4,-2,-2],[6,1,-1,-1,0,234,-327,-11,-14,40,-37,11,20],[6,1,-1,-1,0,67,-128,6,9,-31,26,-9,-7],[6,1,-1,-1,0,120,-212,5,6,-32,27,-4,-5],[6,1,-1,-1,0,289,177,5,9,-40,31,-5,-7],[6,1,-1,-1,0,400,285,-13,9,-9,-13,22,-17],[6,1,-1,-1,0,116,177,-2,-7,15,-11,5,5],[6,1,-1,-1,0,102,-18,-4,9,-33,-33,2,-9],[6,1,-1,-1,0,107,-27,-5,9,-35,-33,4,-9],[6,1,-1,-1,0,111,-38,-4,11,-36,-33,7,-11],[6,1,-1,-1,0,116,-49,-5,11,-33,-33,7,-11],[6,1,-1,-1,0,120,-66,0,11,-4,6,-31,-33,11,-15],[6,1,-1,-1,0,140,117,5,7,-25,22,-4,-7],[6,1,-1,-1,0,400,300,-6,5,-7,-11,13,-9],[6,1,-1,-1,0,91,155,7,9,-20,15,-7,-7],[6,1,-1,-1,0,167,-153,-5,9,-28,-28,8,-12,23,23],[6,1,-1,-1,0,174,-97,4,-7,20,18,-4,6],[6,1,-1,-1,0,162,-144,-4,7,-31,-29,7,-6],[6,1,-1,-1,0,283,170,6,7,-40,33,-6,-9],[6,1,-1,-1,0,400,311,-6,-6,6,-5],[6,1,-1,-1,0,114,170,-5,-4,16,-13,4,6],[6,1,-1,-1,0,178,-104,2,-9,23,20,-5,7],[6,1,-1,-1,0,158,-137,-2,9,-36,-31,7,-7],[6,1,-1,-1,0,180,-113,5,-6,20,17,-2,9],[6,1,-1,-1,0,156,-128,-5,9,-37,-34,6,-6],[6,1,-1,-1,0,185,-119,2,-7,22,18,-4,6],[6,1,-1,-1,0,151,-119,-4,11,-36,-36,3,-9],[6,1,-1,-1,0,187,-126,2,-11,22,24,-2,5],[6,1,-1,-1,0,147,-108,-5,6,-35,-35,4,-7],[6,1,-1,-1,0,189,-137,9,-5,31,36,-6,6,-12,-13],[6,1,-1,-1,0,142,-102,-6,11,-34,-35,5,-11],[6,1,-1,-1,0,145,124,4,4,-24,22,-5,-4],[6,1,-1,-1,0,87,150,4,5,-20,17,-4,-6],[6,1,-1,-1,0,198,-142,7,-8,33,37,-9,7],[6,1,-1,-1,0,136,-91,-2,7,-7,4,-31,-33,6,-13],[6,1,-1,-1,0,205,-150,9,-7,33,38,-9,6],[6,1,-1,-1,0,142,-203,9,-9,32,40,-9,6],[6,1,-1,-1,0,214,-157,6,-7,36,38,-9,7],[6,1,-1,-1,0,151,-212,9,-7,31,38,-8,9],[6,1,-1,-1,0,220,-164,9,-6,34,37,-7,7],[6,1,-1,-1,0,160,-219,9,-7,31,38,-9,7],[6,1,-1,-1,0,229,-170,7,-9,36,37,-9,9],[6,1,-1,-1,0,169,-226,7,-6,29,37,-5,7],[6,1,-1,-1,0,278,164,5,6,-40,31,-5,-4],[6,1,-1,-1,0,109,166,-4,-7,15,-11,5,5],[6,1,-1,-1,0,176,-232,4,-2,29,33,-4,6],[6,1,-1,-1,0,287,-170,-11,9,-27,-36,11,-9],[6,1,-1,-1,0,180,-234,9,-7,27,33,-7,7],[6,1,-1,-1,0,296,-175,-9,5,-27,-36,7,-6],[6,1,-1,-1,0,189,-241,9,-9,27,35,-9,7],[6,1,-1,-1,0,303,-179,-7,4,-29,-37,7,-5],[6,1,-1,-1,0,198,-250,7,-7,26,34,-6,8],[6,1,-1,-1,0,312,-186,-9,7,-29,-38,6,-6],[6,1,-1,-1,0,256,-243,-11,11,-31,-38,13,-9],[6,1,-1,-1,0,149,128,5,7,-25,22,-4,-7],[6,1,-1,-1,0,82,144,5,6,-20,16,-5,-7],[6,1,-1,-1,0,318,-190,-6,4,-32,-37,7,-5],[6,1,-1,-1,0,263,-250,-7,7,-29,-36,7,-6],[6,1,-1,-1,0,325,-197,-7,7,-31,-38,9,-6],[6,1,-1,-1,0,269,-254,-6,4,-29,-35,4,-5],[6,1,-1,-1,0,332,-201,-7,4,-29,-37,7,-7],[6,1,-1,-1,0,274,-259,-5,5,-31,-36,7,-6],[6,1,-1,-1,0,341,-206,-9,5,-29,-40,6,-4],[6,1,-1,-1,0,280,-263,-6,4,-29,-37,7,-5],[6,1,-1,-1,0,349,-212,-8,6,-32,-39,9,-7],[6,1,-1,-1,0,289,-272,-9,9,-28,-38,8,-6],[6,1,-1,-1,0,272,157,6,7,-40,33,-7,-9],[6,1,-1,-1,0,105,159,-7,-4,18,-13,4,6],[6,1,-1,-1,0,300,-279,-11,7,-29,-35,12,-9],[6,1,-1,-1,0,332,-261,9,-7,31,42,-9,7],[6,1,-1,-1,0,309,-287,-9,8,-28,-37,8,-7],[6,1,-1,-1,0,341,-268,8,-4,32,42,-9,4],[6,1,-1,-1,0,314,-301,-2,11,-3,3,-29,-36,12,-7],[6,1,-1,-1,0,349,-272,7,-7,31,42,-6,7],[6,1,-1,-1,0,356,-279,9,-6,33,42,-11,6],[6,1,-1,-1,0,400,-382,-13,-18,13,0],[6,1,-1,-1,0,154,135,4,4,-24,22,-5,-4],[6,1,-1,-1,0,365,-285,7,-5,28,36,0,10,-2,1],[6,1,-1,-1,0,400,-366,-22,-32,3,-2,6,0,13,18],[6,1,-1,-1,0,372,-290,6,-4,22,29,0,11],[6,1,-1,-1,0,400,-355,-28,-39,6,-4,22,32],[6,1,-1,-1,0,378,-294,7,-5,15,21,0,13],[6,1,-1,-1,0,400,-353,-10,6,-29,-38,11,-9,28,39],[6,1,-1,-1,0,385,-299,9,-6,6,8,0,19],[6,1,-1,-1,0,390,-347,-9,6,-27,-39,7,-5],[6,1,-1,-1,0,394,-305,6,-6,0,14],[6,1,-1,-1,0,381,-341,-9,7,-29,-40,11,-6],[6,1,-1,-1,0,265,148,7,9,-41,31,-6,-7],[6,1,-1,-1,0,98,155,-5,-7,18,-13,5,7],[6,1,-1,-1,0,372,-334,-9,7,-29,-40,9,-7],[6,1,-1,-1,0,363,-327,-9,4,-27,-37,7,-7],[6,1,-1,-1,0,354,-323,-9,7,-29,-40,11,-4],[6,1,-1,-1,0,345,-316,-11,9,-29,-40,11,-9],[6,1,-1,-1,0,158,139,7,7,-27,22,-4,-7],[6,1,-1,-1,0,249,400,3,-2,-7,-11,-16,13],[6,1,-1,-1,0,67,-148,-5,-7,25,-20,4,7],[6,1,-1,-1,0,127,-226,-7,-8,29,-20,7,6],[6,1,-1,-1,0,258,139,7,9,-40,33,-7,-9],[6,1,-1,-1,0,93,148,-4,-6,16,-14,6,7],[6,1,-1,-1,0,165,146,4,7,-27,22,-4,-7],[6,1,-1,-1,0,252,130,6,9,-40,33,-7,-8],[6,1,-1,-1,0,89,142,-4,-7,15,-13,5,6],[6,1,-1,-1,0,169,153,5,4,-27,22,-5,-4],[6,1,-1,-1,0,245,124,7,6,-41,34,-6,-9],[6,1,-1,-1,0,85,135,-5,-7,7,-6,9,-7,4,7],[6,1,-1,-1,0,174,157,4,4,-27,25,-4,-7],[6,1,-1,-1,0,238,117,7,7,-40,31,-5,-7],[6,1,-1,-1,0,178,161,5,7,-27,22,-5,-4],[6,1,-1,-1,0,234,108,4,9,-38,31,-6,-6],[6,1,-1,-1,0,183,168,4,7,-27,22,-4,-7],[6,1,-1,-1,0,211,396,4,4,14,0,16,-13,-9,-11],[6,1,-1,-1,0,263,374,6,6,-17,14,-5,-7],[6,1,-1,-1,0,263,-378,-40,37,-3,-4,-2,-2,2,-9,38,-31],[6,1,-1,-1,0,60,-139,7,11,-34,28,-9,-8],[6,1,-1,-1,0,116,-217,4,5,-31,28,-4,-6],[6,1,-1,-1,0,227,102,7,6,-40,34,-7,-9],[6,1,-1,-1,0,187,175,4,4,-26,22,-5,-4],[6,1,-1,-1,0,220,95,7,7,-40,31,-4,-7],[6,1,-1,-1,0,191,179,7,7,-27,22,-6,-7],[6,1,-1,-1,0,200,75,9,11,-38,31,-9,-11],[6,1,-1,-1,0,198,186,5,4,-27,22,-5,-4],[6,1,-1,-1,0,194,66,6,9,-38,31,-6,-9],[6,1,-1,-1,0,203,190,4,7,-27,22,-4,-7],[6,1,-1,-1,0,187,60,7,6,-38,31,-7,-6],[6,1,-1,-1,0,207,197,4,4,-26,25,-5,-7],[60,0,-1,-1,0,-125,15,5,3,-7,6,11,9,7,-6,11,8,33,-39,14,0,35,35,14,-13,4,4,9,-11,-67,-60,-15,16,-7,-7],[6,1,-1,-1,0,274,-400,9,15,24,-15],[11,0,-1,-1,0,236,-77,51,-38,38,53,-51,38],[6,1,-1,-1,0,287,-148,56,-40,11,18,-5,2,3,4,-49,34],[6,1,-1,-1,0,400,12,-4,-5,4,-3,0,-7,-8,-12,8,-7],[6,1,-1,-1,0,336,-139,13,22,-24,15,-16,-22],[6,1,-1,-1,0,300,-385,7,-4,-2,-5,11,-6,21,0,6,9,-27,15,-2,-4,-7,4],[9,0,-1,-1,0,-400,-222,13,10,-13,14],[6,1,-1,-1,0,-381,352,14,-16,4,5,20,-23,22,23,-20,24,5,2,-11,13],[29,0,14540253,15713933,0,250,-24,6,0,0,6,-6,0],[6,1,-1,-1,0,220,-345,-11,9,-6,6,6,9,9,11,11,-8,7,-7,-2,-2,-11,-14],[29,0,14540253,15713933,0,170,-58,6,0,0,6,-6,0],[29,0,14540253,15713933,0,141,7,6,0,0,6,-6,0],[29,0,14540253,15713933,0,221,42,6,0,0,6,-6,0],[6,1,-1,-1,0,-171,-363,0,11,-5,3,65,66,-43,40,-33,-36,-122,-121,206,0,16,17,-22,20,-29,-28,-18,4,-2,-2,-9,6,-4,9],[42,4,14077636,14077636,0,-220,124,-34,37,2,9,56,51,-64,73,37,33,58,-66,9,9,11,0,45,-49,0,-11,-107,-84],[3,4,5789272,-1,0,-76,400,-42,-40,-39,40],[3,4,10132122,-1,0,-76,336,-6,2,-7,-4,-4,-7,-3,-15,27,-33,22,13,0,9,7,9,107,88,0,2,-78,0,-5,-4,12,-11,-7,-11,0,-14,4,-11,-20,-15,-22,-9,-7,-4,-9,-7,-6,-11,-18,18,4,4,7,7],[6,4,13876131,-1,0,-280,157,15,15,40,38,-67,71,23,20,11,9,44,37,14,0,109,-121,0,-14,-25,-17,-15,-14,-87,-70,-22,0],[18,4,14077636,-1,0,-280,157,15,15,40,38,-67,71,23,20,11,9,44,37,14,0,109,-121,0,-14,-25,-17,-15,-14,-87,-70,-22,0],[60,4,4543041,-1,0,-265,172,-67,73,5,5,35,31,67,-71],[17,4,14540253,-1,0,145,-40,11,5,9,4,-9,20,-9,-4,-11,-5],[29,4,15713933,15713933,17,158,-20,2,-2,0,-5,0,-2,0,-4,-4,-2,-2,-3,-5,0,-2,3,-2,0,-3,4,-2,2,0,5,0,2,2,2,5,2,2,3,5,0,2,-3],[6,4,-1,-1,0,-118,360,11,-13,-7,-4,-4,-2,-7,0,-6,2,-51,57,25,0],[9,4,10132122,-1,0,-27,334,20,15,-4,11,0,14,7,11,-12,11,-2,4,-4,0,-56,-57,11,4,11,0,14,-2,6,-4],[9,4,10132122,-1,0,-78,343,2,-7,-2,-4,-7,-7,-4,-4,18,-18,6,11,9,7,7,4,22,9,-9,7,-6,4,-14,2,-11,0],[4,4,9742732,9742732,12,156,-7,69,29,2,-4,9,-20,2,-5,-71,-31,-2,7,-9,20],[12,4,14540253,9742732,0,207,-49,24,11,-4,9,22,9,3,2,2,0,-5,11,-4,9,-9,-4,-9,20,11,4,-4,11,-7,11,-2,-2,-2,0,-20,-9,-5,9,-24,-11,4,-9,-31,-13,0,0,0,0,4,-11,5,-5,0,-4,9,-20,2,-7,2,-4,5,-9,0,-2,2,2,27,11],[5,4,14540253,9742732,0,211,-60,-4,11,24,11,5,-11],[5,4,14540253,9742732,0,169,40,5,-9,24,11,-2,9],[17,4,14540253,15713933,0,249,-7,-4,9,11,5,4,-9],[17,4,14540253,15713933,0,238,22,-4,11,9,5,4,-11],[17,4,14540253,-1,0,236,-2,9,4,11,5,-9,20,-9,-5,-11,-4],[29,4,15713933,15713933,17,249,20,3,-5,0,-2,0,-4,-3,-2,-2,-3,-2,-2,-5,0,-2,0,-4,2,-3,3,0,4,0,2,0,5,3,2,2,2,4,0,3,0,4,0],[17,4,14540253,15713933,0,165,-31,-9,-4,4,-11,9,4,-2,4],[17,4,14540253,15713933,0,147,-15,-5,8,9,5,5,-5,0,-4],[16,4,14540253,5592405,12,155,10,6,0,0,6,-6,0],[16,4,14540253,5592405,12,165,14,6,0,0,6,-6,0],[16,4,14540253,5592405,12,148,1,6,0,0,6,-6,0],[16,4,14540253,5592405,12,170,-48,6,0,0,6,-6,0],[16,4,14540253,5592405,12,191,-45,6,0,0,6,-6,0],[16,4,14540253,5592405,12,181,-49,6,0,0,6,-6,0],[16,4,14540253,5592405,12,201,-41,6,0,0,6,-6,0],[16,4,14540253,5592405,12,205,-49,6,0,0,6,-6,0],[16,4,14540253,5592405,12,210,-47,6,0,0,6,-6,0],[16,4,14540253,5592405,12,223,-42,6,0,0,6,-6,0],[16,4,14540253,5592405,12,227,-40,6,0,0,6,-6,0],[16,4,14540253,5592405,12,231,-28,6,0,0,6,-6,0],[16,4,14540253,5592405,12,239,-24,6,0,0,6,-6,0],[16,4,14540253,5592405,12,245,-21,6,0,0,6,-6,0],[16,4,14540253,5592405,12,223,-31,6,0,0,6,-6,0],[16,4,14540253,5592405,12,220,37,6,0,0,6,-6,0],[16,4,14540253,5592405,12,248,-16,6,0,0,6,-6,0],[16,4,14540253,5592405,12,246,-11,6,0,0,6,-6,0],[16,4,14540253,5592405,12,228,29,6,0,0,6,-6,0],[16,4,14540253,5592405,12,226,35,6,0,0,6,-6,0],[16,4,14540253,5592405,12,214,34,6,0,0,6,-6,0],[16,4,14540253,5592405,12,207,31,6,0,0,6,-6,0],[16,4,14540253,5592405,12,200,28,6,0,0,6,-6,0],[16,4,14540253,5592405,12,191,35,6,0,0,6,-6,0],[16,4,14540253,5592405,12,195,36,6,0,0,6,-6,0],[16,4,14540253,5592405,12,177,29,6,0,0,6,-6,0],[16,4,14540253,5592405,12,173,26,6,0,0,6,-6,0],[16,4,14540253,5592405,12,176,19,6,0,0,6,-6,0]],"s":[],"r":[[1,272,-150,57,79],[1,387,201,13,19],[1,225,82,-54,48],[2,272,38,109,148,6,15],[2,272,38,2,-34,20,-28],[1,294,-24,44,-33],[2,183,367,46,-40],[1,53,104,176,223],[1,272,-383,26,40],[1,149,-343,27,33],[1,93,-250,-20,-20],[1,7,-208,26,33],[1,-64,-400,13,15],[1,400,116,-106,-140],[0,-363,212,-37,42],[0,-225,57,5,9,69,56],[0,-100,-139,116,-122],[0,252,-208,-12,13],[0,-269,-307,131,130],[1,-236,349,-42,-37],[3,-390,294,-2,13],[0,-362,-400,19,17],[1,-400,-254,48,51],[1,-305,-263,-47,-42],[0,-309,102,51,-60],[0,67,301,53,46],[1,107,283,-36,-42],[1,-400,-399,1,-1],[1,-400,-299,22,20],[1,358,-212,-13,-16],[2,225,82,47,-44],[1,167,133,49,59],[1,287,274,36,47],[3,242,-400,-298,281],[3,-280,71,26,17],[3,-280,71,-18,-16,40,-44,44,33],[1,338,230,-6,-7],[1,-47,383,16,17],[1,93,51,-26,-27],[1,142,104,-49,-53],[0,-385,-55,49,13,22,11,67,60],[0,2,161,-47,49],[0,-109,-82,-116,139],[1,229,327,58,-53],[1,-274,349,-51,51],[1,-249,372,-25,-23],[1,-271,400,13,-15],[0,-312,-352,23,25],[1,-114,-341,0,0],[1,-51,-385,-45,33,-13,5],[0,-100,-139,22,22],[0,-247,29,116,-129],[0,-247,29,22,28],[3,400,-85,-4,-6,0,-20,4,-7],[3,329,-71,71,-48],[3,-400,189,111,-127],[3,-254,88,9,7,-155,185],[3,-400,285,10,9,69,-11,107,95,-22,22],[0,-269,-307,-18,24],[1,-274,307,5,-6],[0,-78,-117,-31,35],[0,-131,-100,31,-39],[0,-225,57,-60,69],[0,400,-325,-62,44],[0,53,104,-51,57],[0,93,51,-40,53],[0,-258,42,11,-13],[1,287,274,18,-17],[0,-349,150,40,-48],[0,-127,299,-67,75],[0,-45,210,-82,89],[0,-200,380,-19,20],[0,120,347,50,53],[1,-51,-385,15,25],[1,-200,380,-36,-31],[3,298,-343,82,-57],[3,272,-150,128,-81],[3,151,-69,36,12,9,-3,9,-9,9,-15],[1,-400,377,21,23],[1,-241,400,-17,-15],[1,-363,212,2,22,23,20],[0,-45,210,112,91],[0,38,-237,-31,29],[0,-305,-263,-24,33],[1,216,192,47,56],[1,376,-7,11,16],[1,338,-57,38,50],[0,189,-146,-38,77],[0,142,-49,-49,100],[1,167,133,-25,-29],[0,131,-369,34,-31],[0,215,-400,-66,57],[0,149,-343,-111,106],[3,-245,95,38,-46],[0,-287,-283,-18,20],[0,-352,-203,-48,55],[0,-289,-327,20,20],[0,-343,-383,31,31],[0,-138,-177,38,38],[1,67,24,-47,-46],[1,-22,-62,-56,-55],[1,33,-175,74,87],[1,162,-172,-8,-9],[1,154,-181,-16,-18],[1,138,-199,-20,-22],[1,118,-221,-25,-29],[1,189,-146,-27,-26],[1,194,-285,40,53],[1,176,-310,18,25],[1,267,-389,5,6],[1,261,-400,6,11],[1,323,-305,2,4],[1,325,-301,13,20],[1,387,9,7,11],[1,394,20,6,8],[1,312,197,-49,-64],[1,263,133,-25,-33],[1,238,100,-13,-18],[1,345,-228,-13,-20],[1,332,-248,-14,-15],[1,376,387,11,13],[1,332,223,-20,-26],[1,71,241,-18,-20],[1,53,221,-22,-26],[1,31,195,-29,-34],[0,272,-223,-20,15],[0,338,-281,-20,18],[1,263,248,24,26],[1,-36,-360,32,55],[1,-4,-305,20,44],[0,151,-69,-9,20],[1,-38,-40,16,-22],[1,20,-22,-20,18],[1,0,-4,-16,2,-20,-16,-4,-4,2,-18],[3,398,-115,2,0],[3,400,-109,-4,-2],[1,-40,383,13,4,13,13],[1,171,130,-4,3],[1,387,201,13,-8],[1,-127,299,29,31],[1,-98,330,51,53],[3,-245,95,27,16,87,70],[1,-278,312,4,-5],[0,-194,374,-6,6],[1,-278,312,-43,-36],[1,-236,349,5,-6],[1,-231,343,37,31],[1,-338,254,13,7,51,46],[1,-274,307,14,11],[1,-321,276,-11,-8,-6,-14,6,-6,5,2,2,11],[0,-400,208,51,-58],[0,-285,126,-78,86],[0,-151,122,106,88],[1,234,-232,18,24],[1,107,-88,35,39],[1,240,-195,32,45],[1,20,-22,-42,-40],[1,323,321,53,66],[1,305,257,33,-27],[1,338,230,49,-29],[0,7,-208,-85,91],[1,-260,318,29,25],[1,-258,385,9,-13],[1,229,327,59,73],[1,329,-71,9,14],[0,318,-263,-46,40],[1,298,-343,25,38],[0,16,-261,115,-108],[0,-329,-230,-23,27],[1,-47,383,7,0],[3,-25,400,-97,-99,-93,99],[3,-127,303,92,97],[3,-400,-55,117,93],[3,-80,215,-114,137,-11,6,-151,-130],[3,-205,358,-13,16],[3,-56,-119,-40,-36,-11,9],[3,-78,-97,-38,-36,9,-13],[3,20,-265,-49,-98,-25,-37],[3,13,-259,-58,-101,-13,-20,-20,-20],[3,-53,234,22,-22],[3,184,400,-108,-104,-116,-90,0,-7,18,-22,-172,-144],[3,-53,234,-27,-19],[3,91,55,-75,-75,-92,-82],[3,11,-272,138,-128],[3,-80,215,-45,-36,-6,2],[3,60,330,-113,-96],[3,-62,190,-18,25],[3,-260,13,-67,-59,-49,-14,-24,-18],[3,-385,-385,-13,-15],[3,-371,-400,-14,15],[3,400,229,-17,-26],[3,392,197,8,11],[3,-278,312,-5,4],[0,240,-195,-51,49],[1,-352,-305,-31,-29],[1,-378,-279,49,49],[1,-378,-279,26,-26],[3,107,283,76,84],[3,216,91,-62,-73,-38,-14],[3,-22,177,102,-131],[0,-400,-67,15,12],[1,-196,-124,-87,-77],[1,-180,-219,-29,29],[1,-209,-190,0,0],[3,-116,-133,-269,-252],[3,-76,-102,-131,151,-7,-5],[3,-56,-119,-20,17]],"w":[],"wi":[],"g":[[1,186,400,-10,-11,-47,-51,-44,-37,-112,-93,31,-36,179,200,26,28],[1,-400,147,97,-109,-97,-82],[1,-189,40,-25,24,169,137,21,-22],[1,125,11,44,13,-4,16,46,46,52,-53,11,-55,-49,-62,-25,35,-42,-15],[1,7,161,205,239,39,0,3,-2,1,2,145,0,0,-415,-13,8,-49,-59,-51,57,-11,49,-65,42,-75,-73],[1,-400,-89,124,78,60,-51,-138,-119,-46,60],[0,16,-296,-49,-89,-13,-15,173,0]],"wd":[],"sd":[],"bc":[],"t":[394,223,229,80,236,73,223,73,240,69,231,66,247,62,236,62,256,57,243,55,263,51,249,49,269,44,258,44,265,38,129,31,116,42,138,22,-200,33,-191,23,-182,12,-173,1,-164,-9,-155,-20,-145,-30,-136,-41,-127,-52,-118,-62,-109,-73,-100,-83,-90,-94,-81,-104,-78,-108,-68,-118,-58,-128,-47,-137,-37,-147,-27,-156,-17,-166,-7,-176,4,-185,11,-192,21,-202,31,-212,41,-221,52,-231,62,-241,69,-248,79,-257,89,-267,99,-277,109,-287,119,-296,129,-306,140,-316,145,-321,155,-330,165,-339,176,-349,186,-358,197,-368,207,-377,217,-386,220,-389,231,-398],"lb":[["Raffles Hospital",-109,113,12],["Golden Landmark Shopping Centre",-24,27,15],["Masjid Sultan",98,-3,12],["Duo Galleria",-20,174,12],["Village Hotel (Bugis)",-34,3,66],["Masjid Sultan Annexe House",140,-35,17],["Parkview Square",-61,180,12],["Malay Heritage Centre",191,-75,12],["ABC Hostel",128,-166,12]],"tri":7093,"an":[98,-3],"anN":"Masjid Sultan","stats":{"bld":475,"est":407,"roads":207,"trees":65,"water":0,"green":1,"wood":0,"dropped":0,"expanded":32}},
chinatown:{"n":"Chinatown","H":200,"base":"pave","c":[1.281,103.8444],"b":[[10,0,-1,16724787,0,112,-400,-3,6,-9,14,-9,-5,-13,22,-5,9,-17,-11,20,-35],[15,0,-1,-1,0,-309,-82,40,22,-31,56,4,2,-18,31,18,11,-4,6,-5,-2,-4,7,-7,-5,-22,38,-5,-2,-4,6,-27,-15,5,-7,-5,-2,5,-7,-16,-8,24,-42,-2,-3,9,-13,2,0,7,-13,24,-44],[15,0,-1,-1,0,-321,-66,-24,44,-55,-32,0,-58],[20,0,-1,-1,0,-9,111,25,-23,-7,-8,7,-7,8,-7,9,7,20,-18,12,14,35,42,60,66,-33,29,-29,24,-60,-66],[30,0,-1,-1,0,-42,184,-7,4,-7,9,-15,57,0,7,2,4,4,5,7,2,7,0,38,2,13,2,9,0,9,-6,2,-11,-4,-11,-40,-58,-9,-4],[10,0,-1,-1,0,-2,-376,-34,-18,-6,14,-9,15,-25,-11,-26,51,55,29],[50,0,-1,-1,0,309,133,-109,82,56,68,107,-86],[12,0,-1,-1,0,400,198,-122,96,54,66,68,-53],[12,0,-1,-1,0,135,400,-21,-31,-5,31],[10,0,-1,-1,0,396,-46,-9,15,13,20,0,-48,-2,-1,-6,11],[10,0,-1,16724787,0,400,-363,-48,14,6,24,42,-9],[10,0,-1,-1,0,358,-243,-24,35,0,7,24,26,7,0,27,-33],[10,0,-1,16724787,0,363,-257,-9,9,4,5,34,35,8,-8,0,-1,-6,-6],[10,0,-1,16724787,0,352,-372,-16,31,7,5,18,-31],[10,0,-1,-1,0,240,-86,23,29,-34,24,-13,-16],[10,0,-1,-1,0,314,22,-38,27,-2,6,18,38,33,-29,2,-20],[6,0,-1,16724787,0,-294,310,-9,4,23,40,11,-5],[6,1,-1,16724787,0,-383,360,-11,9,21,31,15,0],[10,0,-1,16724787,0,13,-188,9,7,-20,33,-9,-5],[10,0,-1,16724787,0,78,-272,11,4,-20,36,-11,-7],[6,1,-1,16724787,0,-256,287,2,14,-33,20,-7,-11],[6,1,-1,16724787,0,-394,369,-6,5,0,6,13,20,14,0],[10,0,-1,16724787,0,-191,186,8,-7,23,40,-9,4],[10,0,-1,16724787,0,29,-230,9,7,-18,31,-9,-5],[10,0,-1,16724787,0,73,-332,12,7,-20,35,-12,-6],[6,1,-1,16724787,0,-400,400,0,0,13,0,-13,-20],[10,0,-1,16724787,0,4,-192,9,4,-20,35,-9,-4],[10,0,-1,16724787,0,69,-279,9,7,-20,33,-9,-4],[10,0,-1,16724787,0,-200,190,9,-4,22,37,-7,5],[10,0,-1,16724787,0,20,-234,9,4,-18,33,-9,-4],[10,0,-1,16724787,0,65,-336,8,4,-20,36,-8,-5],[10,0,-1,16724787,0,-4,-197,8,5,-20,35,-8,-7],[10,0,-1,16724787,0,60,-283,9,4,-20,36,-9,-7],[10,0,-1,16724787,0,-209,197,9,-7,24,38,-9,6],[10,0,-1,16724787,0,11,-239,9,5,-18,33,-9,-7],[10,0,-1,16724787,0,53,-343,12,7,-20,35,-9,-4],[10,0,-1,-1,0,-65,-394,23,14,6,-14,-10,-6,-15,0],[10,0,-1,16724787,0,-13,-203,9,6,-20,33,-9,-4],[10,0,-1,16724787,0,49,-290,11,7,-20,33,-11,-4],[10,0,-1,16724787,0,-218,201,9,-4,24,37,-11,7],[10,0,-1,16724787,0,2,-245,9,6,-18,31,-9,-4],[10,0,-1,16724787,0,45,-347,8,4,-17,38,-12,-7],[10,0,-1,-1,0,-76,-376,25,11,9,-15,-23,-14],[10,0,-1,16724787,0,-24,-208,11,5,-20,35,-12,-7],[10,0,-1,16724787,0,40,-294,9,4,-20,36,-9,-7],[10,0,-1,16724787,0,-227,208,9,-7,22,40,-9,4],[9,0,-1,16724787,0,-7,-250,9,5,-18,33,-8,-5],[10,0,-1,16724787,0,36,-352,9,5,-21,35,-8,-4],[6,0,-1,16724787,0,-303,314,-11,7,25,39,9,-6],[10,0,-1,16724787,0,114,-234,-5,8,-4,11,-9,-4,11,-20],[10,0,-1,16724787,0,-33,-215,9,7,-21,33,-8,-4],[10,0,-1,16724787,0,31,-299,9,5,-20,33,-9,-4],[10,0,-1,16724787,0,56,-365,-9,-4,18,-31,11,0],[10,0,-1,16724787,0,-238,212,11,-4,22,37,-9,7],[10,0,-1,16724787,0,-16,-254,9,4,-17,33,-9,-4],[10,0,-1,16724787,0,24,-358,12,6,-20,36,-9,-7],[10,0,-1,16724787,0,-42,-219,9,4,-20,36,-9,-5],[10,0,-1,16724787,0,20,-305,11,6,-20,34,-9,-5],[10,0,-1,16724787,0,47,-369,-11,-7,14,-24,15,0],[10,0,-1,16724787,0,-87,-389,-4,11,-38,-22,24,0],[6,0,-1,16724787,0,352,-400,-3,4,-6,-4],[10,0,-1,16724787,0,349,-396,-6,9,-21,-13,21,0],[10,0,-1,16724787,0,289,-400,-2,4,49,27,5,-7,-34,-20,-7,-4],[10,0,-1,16724787,0,287,-396,-4,9,49,27,4,-9],[10,0,-1,16724787,0,-247,219,9,-7,24,40,-9,7],[10,0,-1,16724787,0,-24,-259,8,5,-17,33,-9,-7],[10,0,-1,16724787,0,16,-363,8,5,-17,35,-9,-4],[10,0,-1,16724787,0,283,-387,-5,9,49,26,5,-8],[10,0,-1,16724787,0,278,-378,-4,9,47,26,6,-9],[10,0,-1,16724787,0,274,-369,-7,6,49,29,5,-9],[10,0,-1,16724787,0,267,-363,-4,9,49,29,4,-9],[10,0,-1,16724787,0,263,-354,-5,9,49,29,5,-9],[10,0,-1,16724787,0,-51,-223,9,4,-20,35,-7,-6],[10,0,-1,16724787,0,11,-310,9,5,-18,35,-11,-6],[10,0,-1,16724787,0,36,-376,-9,-4,11,-20,12,0],[10,0,-1,16724787,0,-91,-378,-7,11,-42,-24,5,-9,6,0],[10,0,-1,16724787,0,258,-345,-4,9,46,29,7,-9],[10,0,-1,16724787,0,254,-336,-7,9,49,28,4,-8],[10,0,-1,16724787,0,247,-327,-4,9,49,26,4,-7],[10,0,-1,16724787,0,243,-318,-5,8,49,27,5,-9],[10,0,-1,16724787,0,238,-310,-4,9,46,27,7,-9],[10,0,-1,16724787,0,-258,226,11,-7,24,40,-11,6],[10,0,-1,16724787,0,-33,-263,9,4,-18,31,-9,-4],[10,0,-1,16724787,0,7,-369,9,6,-18,36,-7,-5,-2,-6],[10,0,-1,16724787,0,234,-301,-5,7,47,29,4,-9],[10,0,-1,16724787,0,125,-292,4,-9,45,27,-7,9],[10,0,-1,16724787,0,229,-294,-6,9,49,28,4,-8],[10,0,-1,16724787,0,118,-281,7,-11,42,27,-5,8],[10,0,-1,16724787,0,223,-285,-5,9,49,28,5,-9],[10,0,-1,16724787,0,114,-272,4,-9,44,24,-6,9],[10,0,-1,16724787,0,218,-276,-4,8,46,29,7,-9],[10,0,-1,16724787,0,107,-263,7,-9,42,24,-5,11],[10,0,-1,16724787,0,214,-268,-5,9,49,27,2,-7],[10,0,-1,16724787,0,-60,-228,9,5,-18,33,-9,-5],[10,0,-1,16724787,0,2,-316,9,6,-20,34,-9,-5],[10,0,-1,16724787,0,27,-380,-9,-7,7,-13,13,0],[10,0,-1,16724787,0,-98,-367,-7,11,-40,-24,5,-11],[10,0,-1,16724787,0,102,-254,5,-9,44,26,-6,9],[10,0,-1,16724787,0,209,-259,-4,9,47,27,6,-9],[10,0,-1,16724787,0,96,-243,6,-11,43,26,-5,9,-26,-15,-7,-5],[10,0,-1,16724787,0,205,-250,-7,9,49,26,5,-8],[10,0,-1,16724787,0,140,-219,-4,9,-27,-16,5,-8],[10,0,-1,16724787,0,198,-241,-4,9,49,26,4,-9],[10,0,-1,16724787,0,136,-210,-7,9,-9,-5,-15,-9,4,-11],[10,0,-1,16724787,0,194,-232,-5,9,47,28,7,-11],[10,0,-1,16724787,0,69,-197,7,-9,42,25,-7,9],[10,0,-1,16724787,0,189,-223,-6,11,48,26,5,-9],[10,0,-1,16724787,0,-267,232,9,-6,24,39,-9,5],[10,0,-1,16724787,0,-42,-270,9,7,-18,31,-9,-5],[10,0,-1,16724787,0,65,-188,4,-9,42,25,-4,8],[10,0,-1,16724787,0,183,-212,-5,9,47,26,6,-9],[10,0,-1,16724787,0,58,-179,7,-9,42,24,-7,11],[10,0,-1,16724787,0,178,-203,-7,8,49,27,5,-9],[10,0,-1,16724787,0,53,-168,5,-11,42,26,-4,9],[10,0,-1,16724787,0,171,-195,-4,9,49,27,4,-9],[10,0,-1,16724787,0,47,-159,6,-9,43,24,-7,9],[10,0,-1,16724787,0,167,-186,-5,9,47,27,7,-9],[10,0,-1,16724787,0,42,-150,5,-9,42,24,-4,9],[10,0,-1,16724787,0,162,-177,-4,9,47,26,4,-8],[10,0,-1,16724787,0,-67,-234,7,6,-18,33,-9,-4],[10,0,-1,16724787,0,-9,-321,11,5,-20,35,-9,-6],[10,0,-1,16724787,0,18,-387,-11,-4,5,-9,13,0],[10,0,-1,16724787,0,-105,-356,-4,9,-42,-22,6,-11],[10,0,-1,16724787,0,36,-142,6,-8,43,24,-7,9],[10,0,-1,16724787,0,158,-168,-9,11,49,29,7,-14],[10,0,-1,16724787,0,31,-133,5,-9,42,25,-5,9],[10,0,-1,16724787,0,145,-144,-5,9,54,33,4,-9],[10,0,-1,16724787,0,140,-135,-4,7,51,35,7,-9],[10,0,-1,16724787,0,136,-128,-7,9,54,33,4,-7],[10,0,-1,16724787,0,129,-119,-4,8,51,34,7,-9],[10,0,-1,16724787,0,-283,234,-9,5,20,35,9,-4],[10,0,-1,16724787,0,-51,-274,9,4,-18,33,-9,-6],[10,0,-1,16724787,0,125,-111,-5,7,49,35,7,-8],[10,0,-1,16724787,0,120,-104,-6,9,51,35,4,-9],[10,0,-1,16724787,0,114,-95,-5,9,49,33,7,-7],[10,0,-1,16724787,0,109,-86,-4,6,49,36,4,-9],[6,0,-1,16724787,0,-314,321,-9,4,25,40,9,-5],[10,0,-1,16724787,0,-400,205,4,-2,-4,-7],[10,0,-1,16724787,0,-158,164,9,-5,22,38,-7,6],[10,0,-1,16724787,0,114,-310,8,5,-20,31,-4,2,-5,-2],[10,0,-1,16724787,0,-78,-239,11,5,-20,35,-9,-4],[10,0,-1,16724787,0,-20,-323,9,-2,2,4,-18,34,-11,-5],[10,0,-1,16724787,0,7,-391,-9,-7,1,-2,13,0],[10,0,-1,16724787,0,-109,-347,-7,11,-42,-22,7,-11],[10,0,-1,16724787,0,-292,239,-11,4,23,38,8,-7],[10,0,-1,16724787,0,-60,-279,9,5,-18,31,-9,-5],[10,0,-1,16724787,0,-89,-237,9,-4,2,2,-18,36,-11,-5],[10,0,-1,16724787,0,-303,243,-9,5,23,39,9,-6],[10,0,-1,16724787,0,-69,-285,9,6,-18,31,-9,-4],[10,0,-1,16724787,0,-100,-241,11,4,-18,29,-11,-7],[10,0,-1,16724787,0,-312,248,-11,6,27,42,7,-9],[10,0,-1,16724787,0,-80,-290,11,5,-18,33,-4,7,-9,-7],[10,0,-1,16724787,0,-111,-248,11,7,-18,26,-9,-4],[10,0,-1,16724787,0,-323,254,-11,5,27,42,11,-5],[10,0,-1,16724787,0,-89,-296,9,6,-20,38,-11,-5],[10,0,-1,16724787,0,-120,-252,9,4,-16,29,-9,-7],[10,0,-1,16724787,0,-334,259,-9,4,27,44,9,-6],[10,0,-1,16724787,0,-98,-301,5,2,4,3,-22,39,-9,-6],[6,0,-1,16724787,0,-323,325,-9,7,25,40,9,-7],[10,0,-1,16724787,0,96,-243,-11,17,11,7,11,-20],[10,0,-1,16724787,0,-136,-261,16,9,-16,26,-11,-4,-2,-9],[10,0,-1,16724787,0,-343,263,-11,5,29,42,9,-3],[10,0,-1,16724787,0,-149,-400,4,2,-20,35,-9,-6,19,-31],[10,0,-1,16724787,0,-354,268,-9,4,27,44,11,-6],[10,0,-1,16724787,0,-155,-400,-19,31,-9,-5,16,-26],[10,0,-1,16724787,0,-363,272,-11,7,29,42,9,-5],[10,0,-1,16724787,0,-167,-400,-16,26,-8,-4,12,-22],[10,0,-1,16724787,0,-374,279,-9,4,27,44,11,-6],[10,0,-1,16724787,0,-179,-400,-12,22,-12,-7,11,-15],[10,0,-1,16724787,0,-383,283,-11,4,29,47,9,-7],[6,0,-1,16724787,0,-332,332,-11,4,27,40,9,-4],[10,0,-1,16724787,0,-167,170,9,-6,24,39,-8,5],[10,0,-1,16724787,0,58,-212,9,4,-18,31,-9,-4],[10,0,-1,16724787,0,102,-316,12,6,-21,36,-11,-5],[10,0,-1,16724787,0,-192,-400,-11,15,-8,-4,6,-11],[10,0,-1,16724787,0,-394,287,-6,4,0,12,22,38,13,-7],[10,0,-1,16724787,0,-205,-400,-6,11,-9,-7,2,-4],[10,0,-1,16724787,0,-400,329,13,20,9,-8,-22,-38],[10,0,-1,16724787,0,-218,-400,-2,4,-9,-4],[10,0,-1,16724787,0,-400,348,4,6,9,-5,-13,-20],[10,0,-1,16724787,0,-385,234,-11,7,-4,-9,0,-28,2,-1],[10,0,-1,16724787,0,-400,358,4,-4,-4,-6],[10,0,-1,16724787,0,-396,241,-4,2,0,-11],[6,1,-1,16724787,0,-343,336,-9,7,27,40,9,-7],[10,0,-1,16724787,0,33,-177,12,7,-21,35,-11,-7],[6,1,-1,16724787,0,-352,343,-11,4,27,42,11,-6],[10,0,-1,16724787,0,-176,175,9,-5,25,38,-9,4],[10,0,-1,16724787,0,49,-219,9,7,-18,31,-11,-5],[10,0,-1,16724787,0,93,-321,9,5,-20,37,-9,-6],[6,1,-1,16724787,0,-363,347,-9,7,25,42,11,-7],[10,0,-1,16724787,0,22,-181,11,4,-20,35,-11,-6],[10,0,-1,16724787,0,89,-268,4,5,0,4,-15,31,-9,-4],[6,1,-1,16724787,0,-372,354,-11,6,25,40,5,0,6,-4],[10,0,-1,16724787,0,-183,179,7,-4,25,37,-9,7],[10,0,-1,16724787,0,38,-223,11,4,-20,33,-9,-6],[10,0,-1,16724787,0,85,-325,8,4,-20,36,-8,-5],[9,0,-1,16724787,0,-252,323,3,11,-3,4,-17,11,-7,-11],[6,1,-1,16724787,0,-252,312,0,11,-24,15,-4,-8],[6,1,-1,16724787,0,-254,301,2,11,-28,18,-7,-9],[10,0,-1,16724787,0,-80,-400,-7,11,-18,-11],[10,0,-1,16724787,0,225,-148,-14,22,9,2,14,-20],[10,0,-1,16724787,0,207,-55,9,6,24,-37,9,-14,-11,-2],[10,0,-1,16724787,0,220,-124,18,7,7,-2,7,-11,-18,-14],[10,0,-1,16724787,0,327,-330,5,-8,42,26,-5,9],[10,0,-1,16724787,0,187,-73,4,4,25,-39,-9,-3,-4,7,-16,22],[10,0,-1,16724787,0,323,-321,4,-9,42,27,-6,9],[10,0,-1,16724787,0,318,-312,5,-9,40,27,-5,7],[10,0,-1,16724787,0,312,-305,6,-7,40,25,-6,8],[10,0,-1,16724787,0,307,-296,5,-9,40,26,-7,9],[10,0,-1,16724787,0,300,-285,7,-11,38,26,-7,9],[10,0,-1,16724787,0,296,-276,4,-9,38,24,-4,9],[10,0,-1,16724787,0,292,-268,4,-8,38,24,-7,7],[10,0,-1,16724787,0,285,-261,7,-7,35,23,-4,8],[10,0,-1,16724787,0,280,-250,5,-11,38,24,-7,9],[10,0,-1,16724787,0,278,-243,2,-7,36,22,-4,7],[10,0,-1,16724787,0,191,-69,9,7,27,-42,-11,-4],[10,0,-1,16724787,0,272,-232,6,-11,34,22,-7,11],[10,0,-1,16724787,0,200,-62,7,7,31,-47,-11,-2],[10,0,-1,16724787,0,267,-226,5,-6,33,22,-7,7],[10,0,-1,16724787,0,263,-217,4,-9,31,23,-4,8],[10,0,-1,16724787,0,258,-208,5,-9,31,22,-5,7],[10,0,-1,16724787,0,254,-201,4,-7,31,20,-4,9],[10,0,-1,16724787,0,247,-188,7,-13,31,22,-7,9],[6,0,-1,16724787,0,243,-179,4,-9,31,18,-6,11],[10,0,-1,16724787,0,236,-166,7,-13,29,20,-9,11],[10,0,-1,16724787,0,229,-157,7,-9,27,18,-5,9],[10,0,-1,16724787,0,225,-148,4,-9,29,18,-6,9,-18,-14],[10,0,-1,16724787,0,-129,215,2,-18,-22,-38,44,-29,5,-2,4,2,3,5,-21,82],[10,0,-1,16724787,0,-238,343,-2,-9,55,-9,-2,9],[10,0,-1,16724787,0,-240,334,0,-9,55,-11,0,11],[10,0,-1,16724787,0,-114,217,0,11,-17,-2,0,-11,2,0],[10,0,-1,16724787,0,-240,325,0,-9,57,-11,-2,9],[6,1,-1,16724787,0,-240,316,0,-11,57,-11,0,11],[10,0,-1,16724787,0,-114,228,-2,11,-29,-5,0,-11,14,3],[10,0,-1,16724787,0,-240,305,-3,-11,56,-13,4,13],[10,0,-1,16724787,0,-243,294,0,-11,52,-13,4,11],[10,0,-1,16724787,0,-116,239,-2,6,-40,-2,0,-9,13,0],[10,0,-1,16724787,0,-243,283,0,-7,47,-17,5,11],[10,0,-1,16724787,0,-118,245,-4,9,-52,-2,0,-7,5,-2,11,0],[6,0,-1,16724787,0,-231,376,0,-11,33,0,0,11],[6,0,-1,16724787,0,-122,254,0,11,-52,-4,-4,0,-2,-9,6,0],[6,0,-1,16724787,0,-231,387,0,-11,33,0,0,11],[6,0,-1,16724787,0,-229,400,-2,-13,33,0,2,11,-29,2],[10,0,-1,16724787,0,-122,265,-5,11,-42,-4,-5,0,0,-11],[6,0,-1,16724787,0,-225,400,29,-2,1,2],[10,0,-1,16724787,0,-127,276,-2,11,-33,-4,-7,0,0,-11],[10,0,-1,16724787,0,-129,287,-2,12,-31,-5,0,-11],[10,0,-1,16724787,0,-131,299,-5,11,-29,-7,3,-9],[10,0,-1,16724787,0,-136,310,0,8,-29,-4,0,-11],[10,0,-1,16724787,0,-136,318,-2,14,-29,-7,2,-11],[10,0,-1,16724787,0,-138,332,0,9,-29,-5,0,-11],[6,0,-1,16724787,0,-142,354,-3,9,-42,0,0,-11],[6,0,-1,16724787,0,-145,363,3,11,-45,0,0,-11],[6,0,-1,16724787,0,-142,374,0,9,-41,4,-4,0,0,-13],[6,0,-1,16724787,0,-142,383,2,8,-40,7,-3,-11],[6,0,-1,16724787,0,-140,391,1,9,-41,0,0,-2],[12,0,-1,16724787,0,-80,294,0,7,51,13,5,-15,-47,-12],[12,0,-1,16724787,0,-80,301,-2,13,53,11,0,-11],[12,0,-1,16724787,0,-82,314,-3,11,54,11,2,-11],[12,0,-1,16724787,0,-85,325,-4,16,56,13,2,-18],[12,0,-1,16724787,0,-89,341,0,11,53,13,3,-11],[12,0,-1,16724787,0,-89,352,-2,13,53,11,2,-11],[12,0,-1,16724787,0,-91,365,0,11,53,9,0,-9],[12,0,-1,16724787,0,-91,376,-2,11,53,9,2,-11],[12,0,-1,16724787,0,-93,387,0,9,20,4,33,0,0,-4],[12,0,-1,16724787,0,-93,396,0,4,20,0],[10,0,-1,16724787,0,305,-35,-7,-9,36,-25,7,7],[10,0,-1,16724787,0,263,71,-5,-11,-44,17,2,9],[10,0,-1,16724787,0,309,-29,-4,-6,36,-27,8,7],[10,0,-1,16724787,0,258,60,-2,-9,-47,15,5,11],[10,0,-1,16724787,0,263,-57,6,11,-35,22,-5,-9],[9,0,-1,16724787,0,314,-20,-5,-9,40,-26,5,6],[10,0,-1,16724787,0,256,51,-4,-11,-45,17,2,9],[10,0,-1,16724787,0,269,-46,7,8,-36,23,-6,-9],[10,0,-1,16724787,0,321,-11,-7,-9,40,-29,7,9],[10,0,-1,16724787,0,252,40,-5,-11,-42,17,2,11],[10,0,-1,16724787,0,276,-38,7,9,-40,27,-3,-7,0,-6],[10,0,-1,16724787,0,325,-4,-4,-7,40,-29,4,9],[10,0,-1,16724787,0,247,29,-4,-9,-43,18,5,8],[6,1,-1,16724787,0,31,307,7,11,-14,82,-56,0,1,-11,9,-53,4,-37],[10,0,-1,16724787,0,283,-29,6,11,-40,27,-6,-11],[10,0,-1,16724787,0,332,4,-7,-8,40,-27,7,7],[10,0,-1,16724787,0,243,20,-5,-13,-42,22,4,9],[10,0,-1,16724787,0,289,-18,7,11,-42,27,-5,-11],[10,0,-1,16724787,0,336,13,-4,-9,40,-28,6,9],[10,0,-1,16724787,0,238,7,-4,-7,-43,20,5,9],[10,0,-1,16724787,0,296,-7,7,9,-43,29,-6,-11],[10,0,-1,16724787,0,341,20,-5,-7,42,-28,7,6],[10,0,-1,16724787,0,234,0,-5,-11,-44,22,6,9],[10,0,-1,16724787,0,303,2,6,11,-44,29,-5,-11],[10,0,-1,16724787,0,347,29,-6,-9,44,-29,5,9],[10,0,-1,16724787,0,229,-11,-20,-27,-29,40,5,9],[10,0,-1,16724787,0,352,38,-5,-9,43,-29,4,9],[10,0,-1,16724787,0,209,-38,-9,-6,-24,40,4,6],[10,0,-1,16724787,0,358,46,-6,-8,42,-29,4,6],[10,0,-1,16724787,0,200,-44,-6,-9,-25,40,7,9],[10,0,-1,16724787,0,363,53,-5,-7,40,-31,2,3,0,10],[10,0,-1,16724787,0,194,-53,-9,-7,-23,38,7,9],[10,0,-1,16724787,0,369,62,-6,-9,37,-25,0,12],[10,0,-1,16724787,0,185,-60,-9,-6,-20,33,6,11],[10,0,-1,16724787,0,374,71,-5,-9,31,-22,0,13],[10,0,-1,16724787,0,381,77,-7,-6,26,-18,0,10],[10,0,-1,16724787,0,385,86,-4,-9,19,-14,0,13],[10,0,-1,16724787,0,392,95,-7,-9,15,-10,0,13],[10,0,-1,16724787,0,396,104,-4,-9,8,-6,0,12],[10,0,-1,-1,0,274,-84,9,-4,6,2,7,11,7,-7,6,2,-22,16],[10,0,-1,-1,0,292,-53,-5,-11,22,-16,14,5],[10,0,-1,16724787,0,272,95,-5,-11,-47,11,3,11],[10,0,-1,-1,0,298,-44,-6,-9,31,-22,11,6],[10,0,-1,16724787,0,267,84,-4,-13,-47,15,4,9],[10,0,-1,16724787,0,336,-341,-7,-2,16,-31,7,2],[10,0,-1,16724787,0,387,-252,13,-12,0,-24,-2,-2,-24,22],[10,0,-1,16724787,0,394,-223,6,-5,0,-10,-13,-14,-13,-16,-11,11],[10,0,-1,16724787,0,400,-184,-31,29,-8,-9,39,-37],[10,0,-1,16724787,0,400,-167,-22,21,-9,-9,31,-29],[10,0,-1,16724787,0,400,-151,-13,12,-9,-7,22,-21],[10,0,-1,16724787,0,369,-137,9,9,-37,51,-12,-5],[10,0,-1,16724787,0,400,-136,-6,6,-7,-9,13,-12],[10,0,-1,16724787,0,378,-128,12,11,-36,48,-13,-8],[10,0,-1,16724787,0,390,-117,6,6,-35,51,-7,-9],[10,0,-1,16724787,0,300,-102,3,-6,4,-9,2,-9,23,22,-11,13,-21,-9],[10,0,-1,16724787,0,396,-111,4,4,0,13,-31,43,-8,-9],[6,1,-1,16724787,0,400,-72,-22,30,-9,-9,31,-43],[10,0,-1,16724787,0,312,-190,-7,11,49,46,9,-11],[10,0,-1,16724787,0,305,-179,-7,9,47,46,9,-9],[10,0,-1,16724787,0,298,-170,-6,11,46,46,7,-11],[10,0,-1,16724787,0,292,-159,-7,9,24,24,23,22,6,-9],[9,0,-1,16724787,0,285,-150,-5,8,27,25,2,-9],[10,0,-1,16724787,0,280,-142,-4,7,27,27,4,-9],[10,0,-1,16724787,0,276,-135,-4,7,28,26,3,-6],[10,0,-1,16724787,0,272,-128,-9,13,37,15,0,-2],[10,0,-1,-1,0,223,106,13,38,11,4,22,-26,3,-27],[10,0,-1,-1,0,162,49,16,13,-11,11,-16,-13],[10,0,-1,-1,0,-105,-179,-9,18,-46,-27,9,-15],[10,0,-1,-1,0,73,-354,114,64,29,-48,-11,-7,-7,11,-4,-2,-5,6,-24,-13,-7,9,-4,11,-9,-4,2,-5,-22,-13,-5,7,-42,-25],[10,0,-1,-1,0,109,-394,16,9,7,-15,-20,0],[6,1,-1,-1,0,365,-400,-2,4,24,18,13,-4,0,-18],[75,0,11848938,10065807,12,-180,-341,-7,-4,-11,-7,-5,9,-2,-2,-9,15,3,3,-9,15,-3,-2,-8,15,2,3,-5,9,9,4,-4,4,0,3,0,0,6,2,0,2,0,-2,5,-7,2,-2,22,13,0,5,20,11,9,-14,5,-8,-2,-3,-7,-4,2,-4,2,-3,-2,0,0,-2,-2,0,0,0,-2,2,-3,5,-26,-16,11,-17,6,2,0,0,3,0,0,-2,0,0,0,-3,-9,-4],[6,1,-1,-1,0,-40,-38,-38,34,22,24,43,-42],[6,1,-1,-1,0,91,-38,-40,60,45,29,31,-47,-25,-15,9,-13],[9,0,-1,-1,0,194,64,-23,20,-22,22,31,33,5,5,6,0,5,0,4,-2,5,-5,2,-2,2,-7,0,-4,0,-5],[6,1,-1,-1,0,341,108,4,5,-27,20,40,53,42,-31,0,-1,-44,-59],[6,1,-1,-1,0,-265,179,-11,-18,51,-26,-6,-13,-41,22,-4,-7,-65,31,16,31,16,-9,6,9],[4,2,-1,-1,0,-174,-161,-9,17,27,14,9,-18],[4,2,-1,-1,0,-252,84,-15,9,-9,13,-9,0,-9,18,14,2,8,-4,12,-5,11,-6,9,-20],[61,4,9738393,-1,0,174,268,75,95,18,20,38,-29,-109,-137,-38,31],[38,4,15724527,6447452,0,-369,157,-14,-22,42,-24,14,22],[12,4,9738393,-1,0,147,250,18,24,75,95,12,11,-23,20,-18,0,-24,-31,-29,-37,-22,-33,-20,-34],[15,4,-1,-1,0,-65,-153,-8,16,11,7,-7,13,-11,-7,-9,16,16,8,2,-2,49,27,-5,4,20,11,3,-4,4,-7,2,-4,-6,-5,6,-11,7,3,4,-9,5,-7,2,-4,-20,-9,-4,9,-49,-27,4,-9],[3,4,-1,-1,0,4,-60,-8,-4,4,-7,9,5],[3,4,-1,-1,0,27,-100,-9,-4,-5,7,9,6],[18,4,-1,-1,0,-45,-124,23,11,-14,22,-20,-11],[6,4,-1,-1,0,11,-71,11,7,-6,11,-12,-7,5,-6],[6,4,-1,-1,0,27,-100,11,5,-7,13,-11,-6,2,-3],[15,4,15724527,6447452,0,-400,102,17,33,14,22,-23,11,-8,-18],[9,4,15724527,6447452,0,-341,111,14,22,15,-9,-2,-5,-4,3,-7,-14,4,-4,-2,-4],[60,4,9738393,10134701,0,270,400,10,-9,7,9],[6,5,-1,-1,0,136,-376,0,0,2,0,2,0,0,2,2,2,0,0,0,3,-2,2,0,2,-2,0,-2,0,0,0,-2,-2,-3,0,0,-2,0,-3,0,0,0,-2,0,0,3,-2],[6,5,-1,-1,0,142,-365,3,0,0,0,2,0,0,2,2,3,0,0,0,2,-2,0,0,2,0,0,-2,0,0,0,-3,0,0,0,0,0,-2,0,0,0,0,-2,-2,0,0,0,0,-2,0,-3,2,0,0,-2,0,0],[6,5,-1,-1,0,225,-363,-5,0,-2,9,5,0],[6,5,-1,-1,0,145,-385,2,2,2,0,0,0,0,3,0,0,0,2,0,2,0,0,-2,2,0,0,-2,0,-3,0,0,-2,-2,0,0,0,0,-2,0,-2,0,0,2,-3,0,0,3,-2],[6,5,-1,-1,0,147,-396,2,0,0,0,2,2,0,0,0,3,0,0,0,2,0,0,0,2,-2,0,-2,0,0,0,-2,0,0,0,-3,-2,0,0,0,0,0,-2,0,-3,0,0,3,0,0,-2],[6,5,-1,-1,0,107,-363,2,0,0,0,0,-2,0,-2,0,0,-2,-2,0,0,-2,0,0,0,-3,0,0,2,0,0,0,2,0,2,0,0,0,0,3,3,2,0],[38,4,15724527,6447452,0,-312,124,3,6,44,-77,-20,-13,-36,64,-4,4,7,14,4,-3],[21,4,14869198,-1,0,-396,-400,-4,5,0,17,18,-22],[24,4,14869198,5919568,0,-382,-400,-18,22,0,11,24,20,42,-53],[75,4,11848938,10065807,0,-164,-274,6,0,0,6,-6,0],[75,4,11848938,10065807,0,-186,-347,6,0,0,6,-6,0],[63,4,11848938,10065807,0,-374,-321,-4,-2,-3,0,0,2,-2,3,0,0,0,2,0,0,0,0,-2,0,-2,4,0,0,2,2,2,3],[63,4,11848938,10065807,0,-381,-300,6,0,0,6,-6,0],[63,4,11848938,10065807,12,-314,-263,-2,0,4,-11,-9,-5,7,-13,-7,-2,0,-2,-4,-3,0,0,-4,0,2,-2,-16,-9,-2,0,-4,-2,0,-2,-16,-9,-2,2,-2,0,0,3,-5,-3,-9,14,0,2,5,4,4,2,38,23,-20,35,-5,-2,-2,0,0,0,-2,2,0,2,0,0,7,2,20,14,2,-3,0,0,11,-15,-2,-2,2,-5,2,0],[60,4,15724527,6447452,0,-400,-194,57,33,-4,6,-2,5,13,8,2,-6,5,-7,91,51,11,-18,-13,-6,2,-2,-7,-5,0,2,-22,-11,2,-2,-7,-4,-2,2,-20,-11,0,-2,-6,-5,0,2,-23,-11,0,-2,-4,-4,-2,2,-23,-13,0,0,-4,-5,-2,2,-20,-11,0,-2,-7,-4,-2,2,-13,-7],[80,4,15724527,6447452,0,-165,-100,-6,-4,4,-7,-7,-2,3,-9,-7,-2,-7,-4,-2,6,2,3,-4,4,-5,-2,-6,13,4,2,-4,9,-5,-2,-6,13,4,2,-11,23,-5,-3,-8,14,4,2,-4,9,-5,-5,-9,16,7,2,-5,9,-4,-5,-9,14,7,4,-5,7,-4,-3,-9,14,7,2,-5,7,5,2,-3,6,14,7,4,-11,7,2,7,-11,-7,-4,7,-9,4,2,7,-11,-5,-4,5,-9,4,4,7,-13,-5,-5,5,-8,6,4,7,-13,-7,-5,14,-20,4,3,9,-14,-7,-4,5,-7,7,3],[9,4,-1,-1,0,50,400,6,-31,33,7,2,-7,11,3,-5,28]],"s":[],"r":[[0,-122,352,13,-98],[2,-185,-367,71,42],[1,-31,-400,20,11],[1,-254,279,7,35],[1,-158,349,-22,-2],[1,187,199,-2,7,-36,24,-62,53],[1,129,-46,29,31],[1,314,-212,60,-84],[2,-93,-188,186,100],[1,-238,-86,0,20,-49,93],[1,245,-111,-45,-11],[1,-165,-57,-13,24],[1,-194,-352,7,-8],[2,258,-376,-40,-24],[1,198,190,-42,-46],[3,369,332,14,17,17,6],[1,-178,-33,-56,86],[3,-131,-113,35,-64,3,-11],[3,-400,-247,197,108,27,4,45,22,-3,2],[1,-286,400,3,-2],[3,149,230,-11,-13],[1,-76,-49,-22,34],[1,314,-212,22,26],[2,237,-400,15,9,-12,13,-38,-22],[2,-63,-400,-91,170,230,126,-9,16,-243,-133,98,-179],[1,398,126,-55,-77],[3,249,-100,18,-4,71,35,29,31,33,47],[0,87,283,24,29],[1,-31,55,-36,-11,-69,-48],[1,-96,272,47,7],[1,-49,279,-2,-7],[1,-13,274,0,11],[2,-114,-325,261,148],[3,-11,-389,4,-9],[1,-47,-287,27,-45],[1,354,345,15,-13],[1,218,-153,111,-199],[0,76,-62,-20,20,-49,62,-38,35],[0,-334,252,-66,29],[1,-283,398,5,2],[3,-400,365,146,-86],[1,-287,27,-22,46,-43,24,-9,0,-6,-4,-33,-59],[1,-287,27,53,26],[1,-400,-184,146,82,16,16],[0,62,285,-84,-101,-38,-36],[0,-47,133,-13,15],[0,-47,133,38,37,87,102],[0,-80,130,-20,-17],[1,309,97,34,-48],[1,316,11,-67,-111],[3,207,-119,-56,84,-46,-38,-5,4,-15,29,-5,18,-38,62,23,15,0,14],[3,180,-77,45,50,11,20,24,58,25,73],[3,316,11,-56,40],[0,231,-325,-84,148],[0,136,-157,-60,95],[1,-252,-398,-4,-2],[1,-140,-398,-4,-2],[1,-139,-400,-1,2],[1,102,-263,-26,46],[1,102,-263,-122,-69],[1,49,-172,-131,-73,-9,4,-43,-27,-8,-2],[0,-96,272,-9,82],[1,-245,349,4,51],[1,-245,349,-13,3,-45,26],[3,-180,347,-3,-68,-13,-34],[1,87,283,-16,13],[0,-100,113,-49,35,-136,80,-49,24],[0,118,352,-47,-56],[1,309,97,-24,27],[3,71,230,34,40,95,130],[3,250,400,66,-51,53,51],[0,-60,148,-20,-18],[0,-122,400,0,-48],[1,-236,-279,42,-73],[3,108,400,6,-33],[1,-186,400,-3,-53],[0,76,-62,-3,11,-57,95,-63,89],[0,147,-177,-11,20],[0,111,312,54,75],[0,151,400,-33,-48],[1,198,190,40,-24],[1,285,124,-47,42],[1,-158,349,36,3],[1,-196,245,60,-33],[1,-254,279,58,-34],[1,-187,-360,2,-7],[1,-180,347,-9,0],[1,-245,349,56,-2],[3,49,-172,-27,48],[3,131,-312,-29,49],[1,-252,-400,0,2],[1,-140,-398,-25,44],[1,-49,279,36,6],[1,-400,-333,4,3],[1,-396,-330,-4,7],[0,-105,354,0,46],[0,-109,254,29,-124],[1,-285,228,13,24],[1,-247,314,2,35],[1,-36,400,16,-97],[1,-91,-86,-14,20],[1,76,-217,-27,45],[1,-11,-389,18,9],[1,7,-380,124,68],[1,131,-312,69,42],[1,280,-166,34,-46],[1,245,-111,35,-55],[1,343,49,-27,-38],[1,249,-100,-4,-11],[1,-134,-111,-31,54],[1,-105,-66,29,17],[1,369,332,31,-23],[1,400,398,-46,-53],[1,-272,252,18,27],[1,209,-142,9,-11],[1,200,-122,9,-20],[1,336,-365,21,-35],[1,329,-352,7,-13],[1,374,-296,26,-15],[1,-303,378,-37,22],[1,336,-186,36,36],[1,-122,352,17,2],[2,-70,-400,-44,75],[3,-7,-398,1,-2],[3,-381,239,98,-47,216,-148,60,-62,-2,-9,-15,-11,11,-17],[1,-136,-4,-42,-29],[4,318,-80,18,-24],[1,336,-104,36,-46],[1,367,-367,-58,-33],[1,400,-371,-33,4],[1,-252,-398,67,31],[1,-383,201,-17,-31],[1,-334,252,-29,-9,-20,-42],[1,372,-150,28,28],[0,-60,148,-20,64],[1,-134,-111,43,25],[1,-91,-86,129,68],[1,183,31,35,86],[1,93,-88,7,19,29,23],[0,-31,55,-69,58],[0,-80,212,-16,60],[0,165,387,9,13],[1,200,-122,-64,-35],[1,218,117,20,49],[0,78,272,9,11],[0,71,296,-9,-11],[1,62,285,16,-13],[1,187,199,11,-9],[1,78,272,102,-75,7,2],[0,265,-385,-34,60],[0,274,-400,-9,15],[1,-20,-332,27,-48],[3,-270,-400,83,40],[3,-400,-342,10,10,-10,20],[3,114,367,-54,-73,-29,-24,-69,-95,-18,-16],[3,137,400,-23,-33],[3,105,270,91,-60,87,-66,58,-62,31,4],[3,-245,349,-87,51],[3,-31,133,-25,26],[3,-102,69,17,31,25,15],[3,-93,148,0,-20,-16,-11,-22,-26],[3,73,268,43,-31,62,-56,53,-31],[1,158,-15,16,22,9,24],[1,-252,-398,-4,9],[1,-234,53,47,115],[1,183,31,-25,9,-20,20],[3,100,111,54,-47,-9,-9],[2,-114,-325,-51,95],[1,-107,15,-113,71],[1,-147,-13,13,-22],[1,-134,-35,-15,-11,22,-34,22,14],[1,-98,-15,-36,-20],[1,-91,-86,6,-11],[1,73,-51,5,29,-42,64,-32,20],[3,-13,-55,29,15,17,-13,29,-51],[2,-107,-155,0,0],[1,229,254,-42,-55],[0,-80,130,22,-4,11,7],[2,-165,-230,72,42],[1,-47,133,34,0,84,97,2,38],[1,400,128,-2,-2],[3,-13,285,22,2,22,-17],[3,-56,159,-9,0,-28,-11],[3,-60,115,29,18],[3,358,-323,29,18],[4,358,-323,-20,-13],[3,338,-336,-13,-9],[3,42,77,-15,-17],[3,100,111,-62,53],[3,27,60,-9,-7],[3,38,97,89,109],[3,49,71,0,0],[3,131,210,-4,-4],[4,138,217,-7,-7],[3,102,245,-4,-8,33,-27],[3,-31,133,47,-67,11,-6,15,-20],[3,29,40,40,-64],[3,65,-15,8,4],[3,51,-84,42,29]],"w":[],"wi":[],"g":[[1,100,400,7,-40,-36,-44,-6,-2,-23,-2,-16,88],[0,400,330,-19,15,2,4,17,23],[1,400,-290,-2,0,2,2,0,24,-13,12,13,14,0,102,-6,6,6,5],[0,400,162,-2,-1,2,-10,0,-145,-46,-50,-29,-29,-25,-11,-40,-16,3,-11,53,23,27,17,29,33,28,39],[1,-400,160,13,24,104,-60,134,-235,-251,-137],[1,47,46,124,140,-93,82,-120,-135,69,-73],[0,-73,-108,55,28,14,-24,-54,-29],[1,78,-102,-174,-95,-44,86,-260,-140,0,-149,657,0],[0,160,122,-15,-16,26,-24,-17,-16,-14,11,-24,-26,-11,9,-25,-14,-11,18,65,80],[0,211,128,-4,9,-7,9,-11,0,0,11,-9,2,7,11,31,-15]],"wd":[[-270,400,-2,-24,-39,24,-16,0,62,-40,11,3,5,6,2,31]],"sd":[],"bc":[],"t":[],"lb":[["Chinatown Complex",-137,-160,15],["Maxwell Food Center",28,58,26],["URA Centre (East Wing)",141,103,56],["Buddha Tooth Relic Temple",-9,-49,36],["Fairfield Methodist Church",-16,122,36],["Maxwell Reserve",-1,175,12],["Kreta Ayer Heights",-159,-83,12],["Kreta Ayer People's Theatre",-183,-32,21],["Sri Mariamman Temple",77,-187,16]],"tri":5702,"wdA":339.17232625038923,"an":[-9,-49],"anN":"Buddha Tooth Relic Temple","stats":{"bld":374,"est":24,"roads":199,"trees":0,"water":0,"green":5,"wood":1,"dropped":0,"expanded":3}},
hawpar:{"n":"Haw Par Villa","H":200,"base":"grass","c":[1.28417,103.78222],"b":[[6,0,-1,-1,0,-369,157,40,46,29,38,-9,9,-83,73,-8,-10,0,-126],[3,0,-1,-1,0,-400,65,2,4,2,6,-4,6],[6,1,-1,-1,0,-91,137,0,7,-2,6,0,9,2,7,2,6,7,5,6,2,7,5,4,2,5,6,0,7,11,-2,7,-2,-3,-7,7,-2,-7,-9,-6,-7,-2,-4,-7,-2,-7,-5,-2,-6,0,-7,-2,-11,-5,2,-2,-9,-7,5],[6,1,-1,-1,0,-71,-7,-22,16,-9,6,24,31,9,-8,2,2,14,17,29,-19,-23,-27,-6,4],[6,1,-1,-1,0,-22,-95,0,4,-5,5,-4,-2,-2,-3,0,-4,4,-5,5,0],[6,1,-1,-1,0,-93,-40,4,5,0,4,-2,4,-7,3,-4,-5,0,-6,2,-3],[6,1,-1,-1,0,-114,-55,3,-7,-5,-7,-6,0,-5,7,5,7],[6,1,-1,-1,0,223,217,-12,48,23,5,0,-7,9,2,-3,9,23,5,13,-56,-20,-4,-2,11,-14,-4,3,-5],[6,1,-1,-1,0,338,349,-6,31,9,3,-3,8,-2,9,40,0,2,-11,9,2,9,-28],[6,1,-1,-1,0,240,283,23,7,-11,57,-25,-6,2,-7,-6,0,-3,7,-24,-7,4,-16,-2,-2,-2,16,-27,-5,14,-59,26,6,-2,13,24,5,-2,9,7,2],[6,1,-1,-1,0,272,144,-5,24,16,2,-3,14,-20,-7,-4,24,113,27,7,-25,-11,-2,2,-11,14,2,4,-22],[6,1,-1,-1,0,-236,301,20,-14,11,12,51,70,-15,9],[3,0,-1,-1,0,-36,270,-2,-2,-7,-3,-35,-26,-2,-7,0,-9,11,-13,49,33,4,2,-13,20,7,5,-5,4],[6,1,-1,-1,0,-80,-369,11,-9,4,4,9,-11,54,47,-2,2,51,46,-18,18],[6,1,-1,-1,0,-82,-252,22,-11,4,-2,11,22,-26,11],[6,1,-1,-1,0,-109,-347,13,-13,92,79,-14,13],[6,1,-1,-1,0,-91,-272,-5,2,5,11,-16,7,-11,4,-16,-33,34,-13],[6,1,-1,-1,0,-183,-263,-24,-55,65,-27,26,24,11,25,-33,13,16,33,-23,9,-20,9,-13,-33],[4,2,-1,-1,0,-13,46,69,91,13,11,16,-6,0,6,-3,13,-11,9,-38,14,-11,4,-20,-4,-22,-14,-16,-20,-6,-22,2,-26,13,-25],[6,1,-1,-1,0,-60,-263,-22,11,-7,2,0,-2,-2,-7,-5,-11,5,-2,22,-11],[6,1,-1,-1,0,11,-18,47,-33,2,-2,25,35,-49,36],[6,1,-1,-1,0,176,100,24,30,-49,36,-24,-31],[18,0,-1,-1,0,322,-400,7,6,58,53,-55,62,-56,-53,7,-6,-29,-27,-7,7,-45,-42],[6,1,-1,-1,0,289,-279,-40,38,103,108,40,-35],[6,1,-1,-1,0,398,-155,-46,42,48,57,0,-97],[6,1,-1,-1,0,-11,-400,20,24,11,-7,20,38,13,-7,12,22,17,-8,16,28,47,-26,-11,-18,14,-46],[15,0,-1,-1,0,252,374,-52,-14,-33,-6,-11,35,35,9,9,2,45,0],[9,0,-1,-1,0,-347,-241,13,-7,-7,-11,-42,27,-15,9,6,8,7,12,16,-9,-7,-11],[4,2,-1,-1,0,-331,-400,4,64,13,22,7,-4,-2,-7,-4,-75],[6,1,-1,-1,0,-178,-199,9,22,-14,5,-13,6,-9,-20],[6,1,-1,-1,0,180,-223,-6,22,17,4,5,-22],[6,1,-1,-1,0,160,-181,20,28,-24,14,-18,-29],[6,1,-1,-1,0,-365,-265,7,8,-40,29,-2,-2,0,-10],[6,1,-1,-1,0,-390,-294,7,11,-17,13,0,-17],[6,1,-1,-1,0,-372,-195,-4,-11,33,-15,2,4,7,-2,2,9],[6,1,-1,-1,0,-358,-164,-5,-13,34,-15,2,6,4,-2,5,7],[6,1,-1,-1,0,-345,-137,-7,-13,34,-14,2,5,7,-2,2,8],[6,1,-1,-1,0,-120,71,-11,9,-14,-14,20,-15,0,-5,3,-2,2,0,4,0,2,7,9,-7,7,9,-13,9,-5,4,-4,-4,-5,2],[6,1,-1,-1,0,-265,-234,2,4,0,2,5,-2,-2,-7],[6,1,-1,-1,0,400,-2,-6,4,-7,-6,13,-11],[6,1,-1,-1,0,-136,122,27,37,-7,5,-9,-11,-6,4,-7,-7,7,-6,-11,-16],[6,1,-1,-1,0,118,327,-11,14,-9,-7,11,-13],[6,1,-1,-1,0,-73,316,2,-4,-11,-13,-5,4],[4,2,-1,-1,0,2,312,-2,4,13,9,5,-4],[4,2,-1,-1,0,-140,170,-5,5,11,13,5,-2],[4,2,-1,-1,0,-198,113,-2,2,4,7,5,-3],[9,0,-1,-1,0,-369,-212,28,-18,-6,-11,-29,18],[6,1,-1,-1,0,-400,-304,2,-1,8,11,-10,7],[6,1,-1,-1,0,-400,-261,24,-18,11,14,-35,25],[6,1,-1,-1,0,-332,-210,3,7,-5,2,2,6,-33,14,-7,-14],[6,1,-1,-1,0,-318,-181,2,6,-7,5,2,6,-31,14,-6,-14],[6,1,-1,-1,0,-307,-153,2,7,-4,2,2,7,-31,13,-7,-13],[6,1,-1,-1,0,276,-75,-9,6,-15,-19,6,-7],[6,1,-1,-1,0,258,-57,-6,6,-18,-20,9,-6],[6,1,-1,-1,0,-140,7,13,-7,11,20,-11,7],[6,1,-1,-1,0,33,248,-2,-3,-2,-2,0,-2,0,-4,2,-3,2,-2,3,0,2,0,2,0,2,2,3,0,2,5,0,2,-2,2,0,2,-3,3,-4,0,-2,0],[6,1,-1,-1,0,-40,-164,-2,5,-3,4,-6,-2,-2,-2,0,-5,4,-4,4,0],[6,1,-1,-1,0,-76,-146,0,4,-4,5,-5,-2,-4,-3,2,-4,2,-4,7,0],[6,1,-1,-1,0,-176,-221,22,-11,5,6,4,11,-24,12],[6,1,-1,-1,0,-149,-203,11,-5,2,5,2,6,-11,5],[6,1,-1,-1,0,-127,-212,13,-7,5,11,-13,7,-3,-5],[6,1,-1,-1,0,400,247,-28,-6,4,-20,14,5,4,-16,-13,-2,4,-22,15,3],[6,1,-1,-1,0,369,330,-2,6,33,7,0,-63,-19,-4,2,-6,-54,-11,-4,24,13,2,-4,16,-13,-2,-5,19]],"s":[],"r":[[3,-272,208,94,-73],[1,-334,124,-31,6,-16,14],[1,-289,241,-69,-73],[1,-289,241,4,7,-2,9,-103,88,-10,-4],[1,-334,124,-20,20,0,13,11,13,20,5,45,51,0,4,-11,11],[1,-400,102,15,-20,0,-7,-15,-18],[3,29,230,-22,7,-27,-22,-16,-5,-13,-13],[3,-20,215,-58,-104],[3,-56,150,-11,9],[3,-78,111,-15,-16,-18,-33],[3,-93,95,-3,13,11,25],[4,-272,208,18,26],[4,-216,164,20,26],[4,-178,135,42,60],[4,-178,135,-16,-22],[3,27,232,0,0],[3,29,230,-9,-24,13,-9,0,-13],[3,-122,44,-54,-99,-13,-7],[3,-147,-20,-9,5],[3,-4,-62,-3,-2,14,-13,4,-16,-31,-37,-40,28,27,38,26,0],[3,7,-166,-36,-40,-33,-13],[3,-67,40,-13,11,-25,-36,40,-30,-55,-73,-27,-20,-2,17,11,53],[3,-98,-51,5,-4],[3,-107,-46,7,8],[3,36,-122,11,16,22,-5,33,-28,9,-11,-4,-18,7,-24],[3,-127,-22,34,31],[3,-127,-22,-2,-11,9,-5],[3,120,-199,-24,-49],[4,36,-122,-12,9],[3,-98,-51,-22,13],[4,-93,-55,44,-29],[3,96,-248,-103,95,31,40],[3,40,-62,5,-20,-21,-31],[4,-136,-27,-2,-11],[3,-129,-33,-9,-5],[4,-127,-22,-9,-5,-11,7],[3,-107,-46,-7,-9],[4,120,-199,-6,7],[3,-194,113,9,-2,2,-5],[3,-196,190,5,-2,-72,-93],[1,267,358,-13,-6,-98,-20,-25,2],[0,-88,400,-117,-150],[1,400,-349,-6,4,-31,91,-7,4,-15,-2],[1,-98,-372,105,93],[1,159,-400,-12,48],[1,-98,-372,-13,14],[0,-45,398,52,-44],[1,-390,133,-10,-9],[0,-312,-77,32,-45,8,-20,-4,-28,-22,-47,-43,-70,-42,-54],[1,-278,-93,6,2,3,7,-3,13,-6,7,-16,2],[0,42,360,-11,12],[1,-390,133,54,-45],[0,-400,-37,86,110],[0,-314,73,18,22],[0,-247,148,36,47],[0,-45,398,2,2],[0,-336,88,-64,-90],[0,-236,212,-67,-84],[3,-140,245,69,71,13,-9],[1,-312,-77,18,-11,16,-5],[0,-278,-93,-20,40,-5,31,5,22,9,20,78,102],[0,31,372,-24,-18],[0,78,400,-47,-28],[0,42,360,47,34],[0,16,343,26,17],[0,7,354,9,-11],[0,31,372,-31,28],[1,89,394,-5,6],[3,-111,62,-11,-18],[3,-82,232,-11,-2,-49,-75,-18,-36],[1,-211,-396,25,-4],[4,-287,-296,4,4,52,-29,17,-51],[3,-314,137,83,-68],[3,-327,-20,-27,-2,-46,-60],[1,-336,88,22,-15],[0,-314,113,-22,-25],[0,-303,128,-11,-15],[1,-50,-400,-19,4,-16,9,-13,15],[1,-183,-108,7,-36,14,-22,20,-15,57,-25,72,-42,17,-4,49,-2,43,6,24,11],[0,-211,122,64,81,60,67,78,55],[0,-82,287,-67,-68,-69,-89],[1,-381,144,-9,-11],[0,-383,-341,-17,-17],[0,-400,-375,39,41,20,29],[1,-398,148,-2,2],[1,-398,148,8,-15],[1,-381,144,-17,4],[0,-102,330,57,68],[0,-211,195,109,135],[0,-9,325,25,18],[1,131,334,-42,60],[4,-327,-400,2,64],[4,-318,-400,4,64],[1,-349,-383,-18,-17],[3,-341,-305,20,-16,-4,-15,11,0],[1,-358,168,-23,-24],[1,-314,113,-20,11],[3,62,400,-66,-40],[3,93,387,9,7,12,-11,11,2,21,15],[0,-341,-305,56,88,25,60,2,15,-20,49],[0,-218,130,-98,-126,-11,-24,-2,-33,17,-24],[1,149,-212,38,35],[1,187,-177,-2,18,20,35,62,69],[1,118,-232,0,0],[1,187,-177,7,-7,29,-2,177,158],[1,-176,75,-11,-42,-7,-44,11,-97],[1,-183,-108,-17,-25,-9,-20,-5,-42,11,-15,3,-13,-29,-69],[1,-272,-71,5,14,15,11,3,6,-14,29,0,15,9,23,34,44,29,29],[1,337,-400,-8,6],[1,329,-394,-11,3,-6,-9],[1,372,-363,-33,-37],[0,-296,95,49,53],[1,-296,95,-18,18],[0,7,354,-89,-67],[3,-314,137,-86,-108],[3,-111,400,-143,-166],[3,-341,-77,-8,57,86,115],[3,11,321,74,55,8,11],[3,-71,316,67,44],[3,85,376,49,-62,20,18],[3,154,332,-27,33,-2,20],[3,388,-400,12,16],[1,341,-252,-67,-64],[1,-191,100,24,24,5,0,4,-13,-4,-16,-14,-20],[3,-116,270,23,-27],[3,400,-233,-19,-66,11,-28,-5,0],[4,209,-119,45,-40],[1,-349,-383,6,14,-2,13,-16,22,-8,7],[1,-383,-341,25,-17,9,-25],[1,-349,-383,2,-17],[3,-321,-276,32,-23,2,3],[3,58,-64,149,192],[2,45,-133,0,0],[2,89,-146,0,0],[2,76,-166,0,0],[2,76,-166,0,0],[2,62,-164,0,0],[2,56,-148,0,0],[4,-91,-203,15,13],[3,-76,-190,20,18],[3,-47,-148,0,0],[1,-285,-217,22,-13],[3,-136,195,43,48],[3,-45,265,16,-22,5,2,-14,20],[3,-49,270,36,24,-5,7,29,20],[0,-205,250,-31,-38],[3,-196,190,56,55],[3,-93,243,4,-9,7,-2],[3,-93,243,44,27],[1,-298,-100,-16,-11],[3,302,400,-24,-20,-6,-20,-5,-2],[1,-372,153,-28,27],[3,-309,250,4,2,9,-9,9,11],[4,-307,252,13,-13],[1,-263,391,8,9],[4,252,-161,24,-23],[4,-214,-372,3,-24],[3,-234,-318,9,19,-4,7],[3,51,241,7,13],[3,4,-49,7,-6],[4,11,-55,20,4,9,-11],[3,45,-82,13,18],[3,-65,-73,-35,-60,0,-11,4,-9,14,-8,22,-7],[3,-147,-108,-13,-36,-23,-28],[3,-145,-241,23,51],[3,-136,-203,11,-3],[3,-149,-226,60,-26],[3,-105,-245,-2,-7],[0,89,394,10,6],[3,-9,-35,13,-14,-8,-13],[3,-67,228,9,0],[3,-67,228,5,4],[3,-67,228,2,9],[3,-76,232,7,-6,2,2],[4,-82,232,6,0],[3,-49,270,4,-5],[3,-107,380,12,20],[3,-80,327,-5,3,7,11],[3,-20,259,4,4],[3,-24,265,8,-2],[4,-18,254,-40,-26],[4,-24,265,-41,-28],[4,-20,259,-42,-27],[3,-31,270,7,4,8,-11],[3,-38,265,7,5],[3,-254,234,7,-4,-11,-22,-34,-44,-24,-25,2,-2],[3,-231,69,37,44],[3,-125,184,-11,11]],"w":[[-67,-18,-22,-31,-4,3,-5,-5,-4,-6,2,-3,-18,-24,-13,-13,-14,-7,0,11,3,18,8,24,5,9,4,-4,5,8,6,7,-6,4,6,9,5,-2,9,11,15,-9,7,9],[127,-212,-2,-3,-5,-4,0,-4,0,-7,2,-4,5,-3,4,-2,5,0,4,2,5,3,2,2,2,4,0,5,-2,4,-2,4,-3,3,-4,2,-7,0],[-400,256,6,7,-6,6],[0,-199,2,-2,-4,-9,-2,-7,0,-4,2,-5,6,-2,3,2,4,5,0,4,0,2,0,7,-2,5,0,2,0,4,2,2,2,0,3,0,2,-2,2,-4,-2,-5,0,-4,0,-9,4,0,0,9,0,2,0,7,0,2,0,4,-4,3,-5,0,-4,0,-2,-5,-3,-2,-2,2],[20,-226,2,3,2,-3,0,0,3,0,0,-2,0,-2,2,0,2,0,2,2,3,2,0,3,-5,2,-2,2,0,2,0,5,4,0,3,-3,2,0,0,-2,0,-2,-2,-2,2,0,0,-2,0,-5,-2,-2,-3,-4,-2,0,-2,0,-5,2,-2,2],[24,-208,3,-2,2,0,2,0,2,2,0,2,-2,3,-2,0,-2,0],[40,-230,0,-4,0,-3,2,-2,5,0,4,0,2,5,0,2,0,4,-2,2,-4,3,-5,0,-2,-3]],"wi":[[4,-203,3,-7,0,-5,2,-4,-2,-2,-3,-2,-2,0,-2,0,0,4,0,4,0,5],[42,-232,0,-2,3,-3,2,0,2,3,2,2,0,2,-2,2,-2,2,-2,0,-3,-2]],"g":[[0,-229,-117,-20,-78,-16,-33,-35,-71,62,-33,89,-40,35,36,87,82,20,-7,56,0,98,2,49,29,204,184,0,25,-106,125,-216,261,-69,-100,-176,-141,-9,-9,-11,-11,-4,-4,-7,-65,-7,-64,7,-44],[1,-229,-400,2,6,43,-6,126,0,143,124,93,-71,96,66,60,11,66,-27,0,-103],[1,312,400,-23,-20,-11,-26,-6,6,-7,5,-94,-22,-15,2,-14,0,16,-20,-27,-18,163,-203,106,56,0,240],[0,-400,-95,44,57,4,-42,18,-17,-66,-108],[0,8,400,16,-17,28,17],[0,367,-254,33,69,0,-102],[1,-391,-400,8,9,22,26,32,40,26,-24,-5,-51],[1,258,400,9,-26,2,2,0,2,26,22],[1,156,345,-27,42,20,13,109,0,9,-26,-2,-9,-94,-22],[1,289,380,23,20,-17,0,-26,-22,0,-2,-2,-2,-2,-9,7,-5,6,-6],[1,-358,135,-23,15,-19,16,0,212,155,-128,-87,-115],[1,-400,120,26,-51,-26,-33],[0,-247,263,-138,137,268,0],[1,-292,-133,-35,-108,-56,-89,-17,-13,0,129,13,19,51,84],[0,329,35,-98,89,-55,-62,91,-100]],"wd":[[294,104,87,-104,9,4,10,-5,0,161],[400,152,-93,-50,74,-89,17,-4,2,-1],[274,-281,-56,60,-67,-44,-80,0,107,-82]],"sd":[[287,250,31,7,-15,77,2,9,-16,6,-4,-13,0,-4,-13,-5],[296,365,29,-11,4,9,-29,13]],"bc":[],"t":[],"lb":[["The Kendall",146,-178,24],["CSIT Building",36,-178,12],["The Studio/Rise of Asia Museum (2024)",-75,-141,12],["Seasuites",101,191,21],["The Sixth Milestone",-33,13,12],["Haw Par Villa (CCL) Ventilation",-98,163,12],["Ten Courts of Hell",-34,83,12],["Cloud Pavilion",-53,-134,12],["Hell's Museum",-60,28,12]],"tri":2116,"wdA":7029.86109404725,"an":[-34,83],"anN":"Ten Courts of Hell","stats":{"bld":63,"est":51,"roads":188,"trees":0,"water":9,"green":6,"wood":3,"dropped":0}},
sentosa:{"n":"Sentosa","H":300,"base":"grass","c":[1.248,103.83],"b":[[6,1,-1,-1,0,-594,-498,51,-35,-45,-67,-12,0,0,93],[6,1,-1,-1,0,-515,-600,19,25,-5,2,-4,-4,-2,2,17,22,-13,9,-11,-18,-16,11,7,9,-9,7,-46,-65],[6,1,-1,-1,0,345,-29,2,-17,-18,0,0,17],[6,1,-1,-1,0,423,-57,2,-36,-91,-7,-2,36],[6,1,-1,-1,0,376,-44,-2,17,-18,0,2,-17],[6,1,-1,-1,0,434,-20,53,7,3,-18,-54,-7],[6,1,-1,-1,0,434,-51,65,7,2,-18,-65,-7],[6,1,-1,-1,0,387,-27,16,3,2,-16,-15,-2],[6,1,-1,-1,0,0,-60,149,18,5,-42,-107,-11,0,-13,53,4,2,-35,-69,-9,-4,53,-25,-2],[3,0,-1,-1,0,-445,310,20,-16,-9,-11,-4,4,-18,-19,6,-5,-11,-11,-20,18,9,11,0,-2,7,6,-2,0,6,9,2,0],[3,0,-1,-1,0,-341,405,18,-16,-9,-11,-4,5,-18,-20,7,-5,-9,-11,-22,20,9,9,2,0,4,4,0,3,7,8,2,-2],[3,0,-1,-1,0,-532,272,18,-15,-9,-12,-5,5,-17,-20,6,-4,-9,-11,-22,19,9,9,2,-2,5,7,-2,2,8,9,0,-2],[6,1,-1,-1,0,-314,511,18,-16,-9,-11,-4,5,-18,-20,6,-5,-8,-11,-23,20,9,9,2,-2,5,7,-2,2,9,9,0,-3],[9,0,-1,-1,0,-492,-106,20,2,5,-38,-7,0,0,-6,-7,-2,2,-16,9,0,0,-6,76,8,-11,107,-71,-9,0,-7,-5,0,0,-11,-13,-2],[6,1,-1,-1,0,-365,-124,56,7,-5,46,-29,-4,0,4,9,0,0,14,-27,-3,0,-13,-4,0,-2,13,-29,-2,2,-24,22,2,0,-11,-18,0,5,-27,11,3,0,4,7,2],[6,1,-1,-1,0,-109,-144,-7,0,0,-9,-33,0,0,-8,-2,-3,-5,0,-2,-2,-2,-4,-2,-2,-3,-7,3,-5,2,-4,4,-4,7,-3,7,3,4,2,2,2,5,0,75,2,0,-13,52,0,-3,15,-4,0,-2,29,-11,0,0,11,-32,-2,0,-13,-53,-2],[6,1,-1,-1,0,-40,-102,2,-13,-22,-2,-2,13],[6,1,-1,-1,0,-418,195,17,-16,-26,-26,-16,15],[3,0,-1,-1,0,-600,167,6,5,-6,5],[3,0,-1,-1,0,-599,159,-1,-2,0,-14,3,-4,5,5,9,-7,13,16,-11,8,-7,-11],[3,0,-1,-1,0,-599,139,-1,-1,0,-29,8,10,-5,3,7,8],[6,1,-1,-1,0,-574,82,-26,7,0,-27,6,-2,2,11,13,-2],[6,1,-1,-1,0,-594,106,-3,-13,14,-2,2,11,16,-2,2,13,-18,4,-2,-6,-2,2,-3,-7],[6,1,-1,-1,0,-570,86,-9,-31,11,-2,5,16,2,0,2,8,9,-2,5,20,-11,2,-3,-6,-4,2,-2,-9],[3,0,-1,-1,0,-568,142,-4,-14,13,-4,3,9,22,-7,2,11,-31,9,-2,-4],[3,0,-1,-1,0,-556,166,-5,-13,13,-5,3,11,22,-6,2,11,-13,4,-5,0,3,7,-14,4,0,-7,-2,0,-2,-6],[3,0,-1,-1,0,-585,206,-14,-18,9,-7,9,11,22,-15,7,9,-25,20,-2,-5],[6,0,-1,-1,0,-544,600,3,-3,2,3,30,0,-1,-1,-4,-4,-2,-5,-5,5,-7,-9,36,-31,-22,-29,-29,25,-16,-20,52,-44,8,11,-17,15,40,47,15,-14,7,7,4,4,-26,22,17,21,90,0,13,-12,-58,-64,-22,22,-5,-4,-6,-7,15,-15,-15,-20,26,-25,0,-2,5,-2,-5,-4,0,-20,7,-2,-7,-7,0,-7,-29,-35,-11,11,-13,-16,-9,-11,9,-11,-38,-44,-11,9,-2,-2,-3,2,-11,-11,-35,29,-18,-20,-8,6,0,-21,8,-7,-8,-10,0,114,1,2,16,-14,0,3,2,-3,9,9,-28,24,0,104,10,12,20,24,-11,7,-7,-9,-2,2,-2,-2,-8,7,0,20]],"s":[],"r":[[1,-510,599,7,-4,4,5],[1,-514,-71,18,14,44,13],[1,-510,-119,-35,-5,11,-82,35,7],[3,-256,-24,33,-3,12,-4],[3,-165,-57,25,-5,29,2,231,31,100,7,34,11],[3,165,49,9,-27],[3,600,332,-32,-16,-23,-17],[3,600,425,-28,-5,-13,-6,-29,8,-18,-4],[3,600,96,-37,-3,-27,-9,-20,-2,-46,6,-18,-2,-58,-15,-96,9,-80,2,-9,-2,-24,-25,-20,-6,-74,-7,-13,11,-31,7,-100,-20,-43,-2,-171,-29,-16,-7],[3,-10,600,10,-21,0,-37,16,-86,17,-69,0,-35,18,-29,2,-69,9,-26,7,-71,22,-115],[3,243,515,11,-31,13,-59,13,-91,-6,-29,-18,-26,-25,-84,-24,-42,-24,-18],[3,229,600,11,-47,3,-31,-9,-44,-3,-33,27,-67,-53,-130,-11,-45,-3,-39,-24,-71,-11,-44],[1,-510,600,0,-1],[3,-528,440,45,-40],[1,-441,542,-13,11],[1,-454,553,-49,42],[1,-321,600,-2,-14,-73,-66,-5,-20],[3,-474,391,58,-50],[1,-600,328,44,-36,0,-22,-16,-20],[1,-487,268,4,13,20,29,47,31],[3,-600,559,10,-8],[3,-600,-465,48,-33],[3,-541,-484,54,46],[1,-490,-310,9,-31],[1,-394,-349,0,-5,-51,-29],[1,-496,-414,13,-39],[3,600,67,-426,-45,-305,-26,-52,-16,-62,-37,-104,-118,-43,-35,-38,-18,-60,-15,-17,0,-45,9],[3,-543,-310,42,20,83,20,42,20,38,31,71,73,40,49,29,24,33,18,80,17,294,27,45,-4,29,6,317,33],[0,-503,-259,-51,11],[3,207,27,2,-38],[3,-585,-321,11,-26,4,-36,-4,-22,-26,-38],[3,-543,-310,-9,-6,-2,-20],[3,-581,-334,18,-4,9,2],[1,-563,-338,2,-62,-4,-11,-14,-16],[1,-432,-263,5,-9],[3,254,-11,75,11,271,25],[1,245,-35,-25,-31,-115,-98,-25,-31,-47,-121,-6,-47,6,-39,36,-69,9,-24,18,-105],[3,-392,-210,23,-33],[0,-548,-301,47,22,78,18,49,27],[1,-534,-64,-56,-64,-10,-20],[1,-514,-71,4,-48],[1,-253,600,44,-58,13,-33,0,-71,-13,-36,-27,-35,-36,-29,-153,-106,-22,-26,-32,-45,-22,-64,-20,-95],[3,-209,402,9,-11,2,-17,-89,-146,-25,-27,-22,-33,-9,-29,-9,-53,7,-64,18,-11,44,-9,16,-11,11,-15],[1,-259,600,-110,-127],[1,-521,2,-4,-22],[3,-570,-217,-11,27,-2,18],[3,-583,-172,9,30,46,60],[3,-528,-82,12,29,6,44],[3,-510,-9,0,7],[3,-563,-77,-25,-20,-12,-17],[3,-412,221,-29,-29,-24,-39,-25,-62,-20,-93],[3,-178,509,2,-36,-4,-40],[3,-180,433,-20,-42,-27,-35,-185,-135],[3,-232,600,34,-45,20,-46],[3,-521,2,11,-4],[3,-263,330,-9,8],[0,-600,-306,15,-8,20,2,17,11,7,22,-4,22,-14,14,-20,4,-21,-7],[1,-559,-243,-41,45],[1,-597,-456,-3,2],[1,-534,-64,20,-7],[1,-452,-44,160,17],[1,-452,-44,-4,13,-7,7,-16,4,-46,0],[3,-205,-177,5,-66,-40,-106,-25,-87,-29,-46,-38,-73,-4,-16,2,-29],[3,-205,-177,-13,-7,-36,-37,-9,-27,-20,-35,-4,-22,-18,-27,-2,-77,13,-73],[3,67,-226,20,-4,13,-9,5,-9,2,-20,-22,-119,8,-31,27,-44,7,-20,7,-35,-5,-83],[3,87,-402,-14,-38,-13,-16],[3,22,-212,-2,-36,4,-31,-17,-66,6,-93,34,-133,0,-29],[0,-280,-135,46,49,31,22,36,20,40,11,336,31],[0,207,15,-296,-26,-45,-7,-40,-11,-40,-20,-33,-24,-47,-53],[1,-98,-115,-31,-31,-36,-9,-40,-22],[3,67,-226,-25,-4,-35,35,-5,11,9,51,-11,11,-9,3,-53,-7,-25,11,-11,0],[1,-98,-115,0,22,-22,16,-45,20],[1,-280,-135,44,-37,31,-5],[0,258,20,-51,-5],[0,600,51,-244,-22],[1,258,20,2,-18],[0,260,2,340,31],[0,209,-2,51,4],[1,236,-46,29,-9,24,-2,207,19],[1,245,-35,-74,-11,-4,-5,0,-35,-16,-7,-95,-9],[1,260,2,-15,-37],[1,-416,341,47,44,15,31,-2,35,-13,22],[1,-369,473,-21,-4,-11,31],[1,-401,500,-40,42],[1,-410,-321,16,-28],[1,-427,-272,4,-18],[1,-423,-290,13,-31],[1,-499,-199,81,11],[1,-510,-119,11,-80],[1,-597,-456,-3,-6],[0,-398,-230,-29,-13,-32,-9,-44,-7],[1,-600,-473,21,-14],[1,-579,-427,-18,-29],[0,356,29,-98,-9],[1,-443,-332,33,11],[1,-481,-341,5,-2,33,11],[1,-472,-303,-18,-7],[1,-434,-402,-11,19],[1,-483,-453,49,44,0,7],[1,-445,-383,-51,-31],[0,-294,-126,-64,-71,-40,-33],[0,-374,-234,31,24,63,75],[1,-280,-135,-14,9],[1,-423,-290,-49,-13],[3,-570,-217,5,-9,13,-8],[3,-600,-196,19,10],[3,-600,-318,15,-3],[3,-561,-511,20,27],[3,-496,66,33,20,31,11],[1,-525,-20,-9,-44],[1,-600,283,19,20,49,-7,16,3,60,75,66,71,0,24],[3,-292,-27,36,3],[3,-479,600,-20,-21,-15,16],[3,572,420,-9,33],[3,552,491,-4,31],[3,543,560,-4,35],[3,475,600,6,-34,2,-64,7,-46,6,-20,16,-18,56,-102],[3,513,600,23,-151,27,-47,2,-26,-4,-46],[3,572,420,5,-11],[3,585,378,3,-9],[3,592,314,8,11],[3,554,276,9,7],[3,147,93,4,13],[3,160,153,7,31],[3,174,217,6,24],[3,180,272,3,73],[3,109,93,-11,37],[3,89,179,-2,40],[3,82,254,-4,49],[3,73,330,-2,55],[3,-600,213,28,-5,58,-47,-36,-110,-13,-7,-37,8],[3,-600,104,8,9,7,17,17,18,3,7,-35,27],[3,-600,204,3,8],[3,-568,159,7,5,16,24],[1,-600,-33,26,0,11,4,11,25,-2,13,-9,11,-11,2,-11,-2,-15,-17],[1,-525,-20,-18,2],[1,-559,-22,16,4,-9,14],[1,-585,-314,22,-24,7,31]],"w":[[569,600,-1,-10,2,-17,4,-33,-2,-18,0,-13,7,-11,11,-18,10,-10,0,-198,-1,0,-7,-4,-7,-7,-15,-2,-5,-5,-22,-4,-18,-13,-11,-7,-7,-4,-4,0,-7,0,-6,2,-5,6,-20,51,-6,7,-9,4,-12,3,-8,-3,-12,-4,-6,-9,-5,-9,-4,-35,-16,-24,-13,-7,-42,-18,-3,-2,-4,-2,0,-2,-2,-3,2,-4,0,-2,2,-3,5,-2,2,0,15,-4,25,2,47,7,24,0,42,4,41,11,57,29,15,24,0,250,-6,9,-4,4,-2,11,-3,16,-2,11,2,27,-1,29],[-287,150,-11,-22,-9,-11,-7,-13,-2,-13,4,-7,12,-2,15,2,27,16,24,-3,14,0,13,3,11,6,9,9,9,11,7,16,0,15,-5,15,-4,7,-3,9,0,9,5,9,-2,4,-5,5,-4,4,-7,0,-7,0,-4,-2,-27,-20,-24,-22,-16,-7],[-78,-600,0,1,5,26,0,9,0,7,-3,9,-9,22,-4,15,-2,7,0,11,2,15,2,20,2,9,3,9,0,9,0,13,-3,11,-2,13,-6,18,-7,16,-7,15,-4,2,-7,7,-2,2,-5,2,-2,0,-2,-2,-5,-4,-4,-11,-4,-11,-16,-31,-4,-7,-3,-16,-2,-15,0,-9,0,-15,5,-31,0,-7,-3,-7,-2,-4,-11,-13,-5,-7,-13,-20,-4,-6,-7,-7,-4,-5,-16,-19,-4,-7,-2,-14],[-530,436,-4,-5,-31,-35,35,-31,20,22,16,20,-9,7,-29,-36,-20,18,29,33],[-499,418,9,-7,9,11,27,31,-36,31,-31,-37,-4,-5,6,-6,29,33,20,-18],[-436,88,4,-42,7,-4,-2,-18,6,-4,16,2,13,-2,7,11,13,9,7,9,0,8,2,9,-9,11,-13,3,-13,-5,-9,-2,-14,9,-11,9],[-550,442,18,20,-45,38,16,17,-7,5,-32,-40,0,-11,6,9]],"wi":[],"g":[[0,600,600,0,-502,-88,-18,-160,-14,-479,-37,-207,-36,-20,27,-11,84,80,80,118,79,18,237,-34,73,-23,27],[0,304,-600,-157,432,-138,-9,-11,-29,-158,7,-11,24,-98,16,-87,-95,2,-73,-73,-186,-40,-87],[1,-490,-188,-20,155,223,24,4,-26,31,-29,-100,-108],[1,-600,-322,6,-1,20,-2,18,4,24,14,33,15,107,29,36,9,2,-73,-73,-186,-40,-87,-133,0],[1,-600,-46,28,-7,22,18,20,84,14,53,31,68,-85,42,-30,9],[1,-271,600,48,-65,9,-29,3,-61,-54,-89,-127,-84,-44,-35,-49,-67,-85,42,-30,9,0,379],[0,304,-600,-157,432,-138,-9,-11,-29,-158,7,-11,24,-98,16,-87,-95,2,-73,-73,-186,-40,-87]],"wd":[[272,-13,-150,-153,161,-434,317,0,0,692,-279,-37],[594,544,6,34,0,-38],[-487,318,100,122,2,24,-5,47,90,75,-3,14,28,0,28,-40,31,-40,-47,-7,49,-75,56,-27,-13,-146,-96,-66,-98,-62,-4,-53,-38,-9,-25,18,-9,-2,11,-80,-66,0,26,148,47,64,5,71,-25,18,-20,-11]],"sd":[[-594,168,-5,-7,9,-8,5,6,-3,2,3,5,-5,2,-2,-2],[-579,100,-2,-14,7,0,2,2,2,0,2,5,-4,0,2,4],[-552,73,-7,2,-6,-20,9,-2],[-590,69,-2,-12,7,-2,2,2,2,0,2,5,-4,0,2,4],[-556,130,-3,-6,23,-5,0,5],[-545,155,0,-7,20,-4,2,4],[-579,188,-4,-4,18,-14,2,7]],"bc":[],"t":[594,517,481,593,492,573,492,544,501,520,532,436,570,336,525,299,479,276,510,283,545,318,265,321,274,239,312,223,-594,276,-592,263,-541,276,-516,285,-525,287,-512,294,-510,279,-574,447,-539,478,-563,504,-577,542,-481,590,-407,524,-398,534,-388,545,-379,555,-369,565,-360,576],"lb":[["Sentosa Development Corporation",-293,-274,12],["Villa Du Jardin 3",-228,141,9],["Villa Du Jardin 1",-163,242,12],["Villa Du Jardin 4",-272,122,9],["Villa Du Jardin 2",-176,189,9],["The Tanjong",-4,120,15],["The Serapong",-79,-154,15],["Sentosa Golf Club",-79,-154,15]],"tri":2483,"wdA":89552.29278022864,"stats":{"bld":28,"est":17,"roads":138,"trees":32,"water":7,"green":3,"wood":3,"dropped":0}}
};
var CFG={
fc:{name:'Fort Canning Park',gs:13,ws:8,across:400,r:90},
bg:{name:'Singapore Botanic Gardens',gs:16,ws:7,across:500,r:120},
sultan:{name:'Sultan Mosque and Kampong Glam',gs:24,ws:8,across:400,r:45},
chinatown:{name:'Chinatown',gs:24,ws:8,across:400,r:60},
hawpar:{name:'Haw Par Villa',gs:22,ws:7,across:400,r:80},
sentosa:{name:'Sentosa (golf course and resort area)',gs:38,ws:8,across:600,r:120}
};
var TILE={grass:4,pave:2,road:8,sand:4,leafd:2,plaza:4,water:2,pool:2,concd:2,conc:2};
var PAL=[0xcfc4ae,0xc2b8a6,0xb7bfc2,0xcdb69b,0xbdaf9b,0xc9c0b0];
var LC={};
function lin(hex){var c=LC[hex]; if(!c){var k=new THREE.Color(hex).convertSRGBToLinear(); c=LC[hex]=[k.r,k.g,k.b];} return c;}
function sarea(p){var s=0,i,a,b; for(i=0;i0?p.slice().reverse():p; } /* rings are kept with signed area < 0, which makes roofs face up and walls face out */
function dec(r,o){var p=[],x=0,z=0,i; for(i=o;i+1z)!==(p[j][1]>z))&&(x<(p[j][0]-p[i][0])*(z-p[i][1])/(p[j][1]-p[i][1])+p[i][0]))c=!c; } return c;}
/* ear clipping for a ring with signed area < 0; returns indices */
function earclip(p){
var n=p.length, V=[], out=[], i, k=0, guard=0;
for(i=0;i3&&guard++<6000){
var m=V.length, found=false, t, j;
for(t=0;t-1e-7)continue;
var ok=true;
for(j=0;j=0){V.splice(rem,1); k=0;} else { for(j=1;j>>0; return function(){s=(s+0x6D2B79F5)>>>0; var t=s; t=Math.imul(t^(t>>>15),t|1); t^=t+Math.imul(t^(t>>>7),t|61); return ((t^(t>>>14))>>>0)/4294967296;};}
function bbox(p){var a=1e9,b=1e9,c=-1e9,d=-1e9; p.forEach(function(q){if(q[0]c)c=q[0]; if(q[1]d)d=q[1];}); return [a,b,c,d];}
/* ---------- decode once per area, with the collision hash ---------- */
var PREP={};
function prep(id){
if(PREP[id])return PREP[id];
var D=OSM[id], H=D.H, P={id:id,D:D,H:H,cell:{},bl:[],wl:[],wi:D.wi.map(function(r){return orient(dec(r,0));})}, i;
function reg(it){ var b=it.bb, x0=Math.floor(b[0]/16), x1=Math.floor(b[2]/16), z0=Math.floor(b[1]/16), z1=Math.floor(b[3]/16), a, c;
for(a=x0;a<=x1;a++)for(c=z0;c<=z1;c++){var key=(a+64)*256+(c+64); (P.cell[key]||(P.cell[key]=[])).push(it);} }
D.b.forEach(function(r,n){
var o={h:r[0],fl:r[1],cw:r[2],cr:r[3],mh:r[4],p:orient(dec(r,5))}; o.bb=bbox(o.p); o.k=0;
o.nc=!!(o.fl&2)||o.mh>=2; /* roofs and overhangs: you can walk under them */
o.cx=(o.bb[0]+o.bb[2])/2; o.cz=(o.bb[1]+o.bb[3])/2; P.bl.push(o); if(!o.nc)reg(o);
});
D.w.forEach(function(r){ var o={k:1,p:orient(dec(r,0))}; o.bb=bbox(o.p); P.wl.push(o); reg(o); });
P.hit=function(x,y,z){
var px=x+.5, pz=z+.5, a=P.cell[(Math.floor(px/16)+64)*256+(Math.floor(pz/16)+64)]; if(!a)return false;
for(var i=0;ib[2]||pzb[3])continue;
if(o.k===0){ if(y>=o.h||y+1<=o.mh)continue; if(inPoly(px,pz,o.p))return true; }
else { if(y>=3)continue; if(inPoly(px,pz,o.p)){ var ok=true; for(var j=0;jm||z<-m||z>m)return false; if(P.hit(x,0,z)||P.hit(x,1,z)||P.hit(x,2,z))return false; var d=[[5,0],[-5,0],[0,5],[0,-5],[4,4],[-4,-4],[4,-4],[-4,4]]; for(var i=0;i0)pts=pts.slice().reverse(); var b=L.n; pts.forEach(function(q){L.pos.push(q[0],y,q[1]); L.nor.push(0,1,0); L.uv.push(q[0]/tile,q[1]/tile);}); L.n+=4; L.idx.push(b,b+1,b+2,b,b+2,b+3); tris+=2; }
/* ground: base, landuse, parks, woods, sand, pools, water, then roads and paths on top */
var baseM=D.base==='pave'?'pave':'grass', bq=[[-H,-H],[-H,H],[H,H],[H,-H]];
addPoly(layer('base',1,baseM),orient(bq),0,TILE[baseM]);
D.g.forEach(function(r){ var p=orient(dec(r,1)); if(p.length<3)return; if(r[0]===1)addPoly(layer('use',2,'plaza'),p,0.01,4); else addPoly(layer('grass',3,'grass'),p,0.02,4); });
D.wd.forEach(function(r){ var p=orient(dec(r,0)); if(p.length>2)addPoly(layer('wood',4,'leafd'),p,0.03,2); });
D.bc.forEach(function(r){ var p=orient(dec(r,0)); if(p.length>2)addPoly(layer('sand',5,'sand'),p,0.04,4); });
D.w.forEach(function(r){ var p=orient(dec(r,0)); if(p.length>2)addPoly(layer('water',6,'water'),p,0.05,2); });
D.wi.forEach(function(r){ var p=orient(dec(r,0)); if(p.length>2)addPoly(layer('isle',7,'grass'),p,0.06,4); });
D.sd.forEach(function(r){ var p=orient(dec(r,0)); if(p.length>2)addPoly(layer('pool',8,'pool'),p,0.07,2); });
var RW=[12,6.5,6,2.4,2], RM=['road','road','plaza','pave','concd'], RL=[9,9,10,11,11], RY=[.08,.08,.09,.1,.1];
var mask=new Uint8Array((2*H/2+4)*(2*H/2+4)), MW=H+2; /* 2 m cells that trees must keep off */
function mark(x,z,rad){ var cx=Math.floor((x+H)/2), cz=Math.floor((z+H)/2), r=Math.ceil(rad/2), a, c; for(a=-r;a<=r;a++)for(c=-r;c<=r;c++){ var u=cx+a, v=cz+c; if(u>=0&&v>=0&&u63000)return c; var l=Math.hypot(b[0]-a[0],b[1]-a[1]), k=c.n;
pushV(c,a[0],y0,a[1],nx,0,nz,u0/tile,y0/tile,col); pushV(c,b[0],y0,b[1],nx,0,nz,(u0+l)/tile,y0/tile,col); pushV(c,b[0],y1,b[1],nx,0,nz,(u0+l)/tile,y1/tile,col); pushV(c,a[0],y1,a[1],nx,0,nz,u0/tile,y1/tile,col);
c.idx.push(k,k+1,k+2,k,k+2,k+3); c.n+=4; tris+=2; return c; }
function cap(c,p,y,col,down){
var ix=earclip(p), k=c.n, t; if(k>62000)return;
p.forEach(function(q){pushV(c,q[0],y,q[1],0,down?-1:1,0,q[0]/4,q[1]/4,col);}); c.n+=p.length;
for(t=0;t=0?lin(o.cw):lin(PAL[(Math.abs(Math.round(o.cx*7+o.cz*13)))%PAL.length]), rc=lin(o.cr>=0?o.cr:0x8d8f93).map(function(v){return v*.75+.25*.27;}), y0=o.mh>0?o.mh:0, tower=h>=25&&o.cw<0, u=0, a, b, l, g=null;
nb++; if(o.fl&1)est++;
var vc=B.part('_vc',true);
if(o.fl&2){ cap(vc,p,h,lin(0xa9abae),false); cap(vc,p,h-.35,lin(0x8f9195),true); return; }
if(tower){ g=B.part('glassB',false); }
for(j=0;j0)cap(vc,p,y0,col,true);
});
/* trees: the mapped ones, plus a deterministic scatter inside woods and parks (spacing from CFG), within the triangle budget */
var rnd=rng(id.length*7919+H), spots=[], cand=[], budget=Math.max(0,Math.min(1100,Math.floor((24000-tris-1500)/20)));
for(i=0;i=MW-1||cz>=MW-1||mask[cz*MW+cx])continue; if(P.hit(Math.floor(qx),0,Math.floor(qz)))continue;
if(Math.abs(qx)>H-6||Math.abs(qz)>H-6)continue; cand.push([qx,qz,0]); } }
D.wd.forEach(function(r){scatter(r,C.ws,0);});
D.g.forEach(function(r){ if(r[0]===0)scatter(r,C.gs,1); });
for(i=cand.length-1;i>0;i--){ j=Math.floor(rnd()*(i+1)); var tmp=cand[i]; cand[i]=cand[j]; cand[j]=tmp; }
spots=spots.concat(cand).slice(0,budget);
var GREENS=[0x3f8a3a,0x357a2e,0x4a9a3c,0x2f7030,0x56a243], trunkC=lin(0x6b4f35);
spots.forEach(function(s){
var x=s[0], z=s[1], hh=6+rnd()*6, R=2.4+rnd()*1.6, th=3+rnd(), gc=lin(GREENS[Math.floor(rnd()*GREENS.length)]), c=B.part('_vc',true), q, ang=rnd()*6.28;
if(c.n>62000)return; var t=.22, sq=[[-t,-t],[-t,t],[t,t],[t,-t]];
for(q=0;q<4;q++){ var a=sq[q], b=sq[(q+1)%4], nx=-(b[1]-a[1])/(2*t), nz=(b[0]-a[0])/(2*t); wall(c,[x+a[0],z+a[1]],[x+b[0],z+b[1]],0,th,nx,nz,trunkC,4,0); }
var ring=[], top=[x,th+hh*.9,z], by=th-.6;
for(q=0;q<6;q++){ ring.push([x+Math.cos(ang+q*1.0472)*R,by,z+Math.sin(ang+q*1.0472)*R]); }
function tri(a,b,c2,flip){ var ux=b[0]-a[0],uy=b[1]-a[1],uz=b[2]-a[2],vx=c2[0]-a[0],vy=c2[1]-a[1],vz=c2[2]-a[2], nx=uy*vz-uz*vy, ny=uz*vx-ux*vz, nz=ux*vy-uy*vx, nl=Math.hypot(nx,ny,nz)||1; if(flip){nx=-nx;ny=-ny;nz=-nz;}
var kk=c.n; [a,b,c2].forEach(function(v){pushV(c,v[0],v[1],v[2],nx/nl,ny/nl,nz/nl,0,0,gc);}); if(flip)c.idx.push(kk,kk+2,kk+1); else c.idx.push(kk,kk+1,kk+2); c.n+=3; tris++; }
var ctr=[x,by-.3,z];
for(q=0;q<6;q++){ var r0=ring[q], r1=ring[(q+1)%6]; tri(r0,r1,top,true); tri(r0,r1,ctr,false); }
});
/* edge of the map: a see-through wall so you can tell where the data stops */
B.box('railT',-H,0,-H,2*H,7,1,{nc:1}); B.box('railT',-H,0,H-1,2*H,7,1,{nc:1}); B.box('railT',-H,0,-H,1,7,2*H,{nc:1}); B.box('railT',H-1,0,-H,1,7,2*H,{nc:1});
/* labels: the named places from the data */
D.lb.slice(0,8).forEach(function(l,n){ B.label(l[0],l[1],(D.anN&&l[0]===D.anN)?34:l[3]+4,l[2],1); });
B.build();
/* ground layers as their own meshes (offset in depth so they never flicker) */
var disp=[];
Object.keys(layers).forEach(function(nm){
var L=layers[nm]; if(!L.idx.length)return;
var g=new THREE.BufferGeometry(); g.setAttribute('position',new THREE.Float32BufferAttribute(L.pos,3)); g.setAttribute('normal',new THREE.Float32BufferAttribute(L.nor,3)); g.setAttribute('uv',new THREE.Float32BufferAttribute(L.uv,2)); g.setIndex(L.idx);
var m=new THREE.MeshLambertMaterial({map:T.texFor(L.m),polygonOffset:true,polygonOffsetFactor:-L.lv,polygonOffsetUnits:-L.lv*2}); disp.push(m);
var mesh=new THREE.Mesh(g,m); mesh.frustumCulled=false; B.grp.add(mesh);
});
var s={id:id,name:C.name,B:B,G:null,bx0:-H,bx1:H,bz0:-H,bz1:H,water:false,fly:false,osm:true,across:C.across,est:est,nb:nb};
s.hash={}; s.grp=B.grp; s.land=function(x,z){return true;}; s.polyHit=P.hit;
var an=D.an||[0,0];
s.lm=[{id:id,x:an[0],z:an[1],r:C.r}];
s.spawn=P.entry; s.entry={}; s.entry[id]=P.entry;
s.places=[{n:'Start',x:P.entry.x,z:P.entry.z,to:P.entry.to}];
D.lb.slice(0,6).forEach(function(l){ var q=P.near(l[1],l[2]+14); s.places.push({n:l[0],x:q[0],z:q[1],to:[l[1],l[2]]}); });
s.fog=[220,900]; s.near=.15; s.far=2200; s.lblMax=420; s.extra=[];
s.extraDispose=function(){ disp.forEach(function(m){m.dispose();}); };
s.tris=Math.round(tris); s.trees=spots.length;
T.osmInfo={id:id,tris:s.tris,trees:s.trees,buildings:nb,est:est,groups:B.grp.children.length};
return s;
}
Object.keys(OSM).forEach(function(id){ T.SC[id]=function(){return buildOSM(id);}; });
})(TZ);
/* ===== running the zoomable map: load/unload areas, flying on the island map, zoom in and out, lifts, places list ===== */
(function(T){
function $(id){return document.getElementById(id);}
var ENTRY={
bay:{merlion:{x:12,z:18,to:[0,0]},mbs:{x:640,z:249,to:[712,249]},gbb:{x:1000,z:650,to:[1010,578]},esp:{x:111,z:-250,to:[111,-325]},acm:null},
changi:{jewel:{x:0,z:112,to:[0,0]},changi:{x:470,z:-170,to:[470,-200]}}
};
function entryFor(area,id){var e=ENTRY[area]&&ENTRY[area][id]; if(!e&&T.osmEntry)e=T.osmEntry(area); if(!e&&area==='bay'){var q=T.P(1.28750,103.85139); e={x:q[0],z:q[1]+36,to:[q[0],q[1]]};} return e;}
T.unload=function(){
var s=T.cur; if(!s)return;
scene.remove(s.grp);
s.grp.traverse(function(o){if(o.geometry)o.geometry.dispose();});
s.B.lbls.forEach(function(sp){try{sp.material.map.dispose(); sp.material.dispose();}catch(e){}});
if(s.extraDispose)s.extraDispose();
T.cur=null;
};
T.freeAll=function(){
T.unload(); var k; for(k in T.texs)T.texs[k].dispose(); T.texs={}; for(k in T.mats)T.mats[k].dispose(); T.mats={};
if(T.save){camera.near=T.save.near; camera.far=T.save.far; camera.updateProjectionMatrix(); try{boundsGroup.visible=true;}catch(e){} T.save=null;}
try{clouds.forEach(function(g){g.visible=true;}); skyLast=-1;}catch(e){}
T.busy=0; T.anim=null; T.run=false; var f=$('zfx'); if(f)f.style.opacity=0; var pl=$('tourPl'); if(pl)pl.hidden=true;
['tourBar2','tourRead'].forEach(function(i){var e=$(i); if(e)e.hidden=true;});
};
/* load an area or the island. o.at = {x,z,y,yaw,pitch,to:[x,z]} */
T.load=function(id,o){
o=o||{}; T.unload(); var t0=performance.now(), s=T.SC[id](); s.buildMs=Math.round(performance.now()-t0);
var cx=(s.bx0+s.bx1)/2, cz=(s.bz0+s.bz1)/2;
if(s.water){
var wm=new THREE.Mesh(new THREE.PlaneGeometry(12000,12000,48,48),new THREE.MeshLambertMaterial({color:col('#2f7fd0'),transparent:true,opacity:.7,depthWrite:false,side:THREE.DoubleSide})); wm.rotation.x=-Math.PI/2; wm.position.set(cx,-.12,cz); s.grp.add(wm);
var sb=new THREE.Mesh(new THREE.PlaneGeometry(12000,12000,48,48),new THREE.MeshLambertMaterial({color:col('#1d3f66')})); sb.rotation.x=-Math.PI/2; sb.position.set(cx,-4.02,cz); s.grp.add(sb);
}else if(!s.fly){
var gg=new THREE.PlaneGeometry(12000,12000,48,48), uv=gg.attributes.uv; for(var q=0;q0?'You are '+Math.round(l.ty)+' metres up. Look around, and stay inside the glass rails.':'Back on the ground.',true);});
};
T.goPlace=function(p){
var s=T.cur; if(!s||T.busy)return; var pl=$('tourPl'); if(pl)pl.hidden=true;
if(s.fly){ player.x=p.x+.5; player.z=p.z+9; player.y=18-EYE; yaw=0; pitch=-.6; return; }
T.busy=1; T.fade(1,function(){player.x=p.x+.5; player.z=p.z+.5; player.y=0; player.vy=0; yaw=Math.atan2(-(p.to[0]-p.x),-(p.to[1]-p.z)); pitch=-.04; T.fade(0); T.busy=0;});
};
T.places=function(){
var s=T.cur, pl=$('tourPl'); if(!s||!pl)return; if(!pl.hidden){pl.hidden=true; return;}
var h=''+(s.fly?'Fly to a landmark':'Go to a place')+'', list;
if(s.fly){list=s.icons.map(function(ic){var m=LMS.filter(function(x){return x.id===ic.id;})[0]; return {n:m.name,done:!!(S.st.sg&&S.st.sg[ic.id]),ic:ic};});}
else list=s.places.map(function(p){return {n:p.n,p:p};});
pl.innerHTML=h; list.forEach(function(it){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=(it.done?'✓ ':'')+it.n; b.addEventListener('click',function(){T.goPlace(it.ic?{x:it.ic.x,z:it.ic.z}:it.p);}); pl.appendChild(b);});
pl.hidden=false;
};
T.findInfo=function(id){var r=null; LMS.forEach(function(m){if(m.id===id)r=m;}); if(!r&&T.cur&&T.cur.extra)T.cur.extra.forEach(function(m){if(m.id===id)r=m;}); return r;};
/* ---------- every frame ---------- */
T.tick=function(dt){
var s=T.cur; if(!s)return;
if(T.anim){var a=T.anim; a.t+=dt; a.f(Math.min(1,a.t/a.dur)); if(a.t>=a.dur){T.anim=null; a.end();}}
s.B.anim.forEach(function(f){f(dt);});
var cp=camera.position, i;
for(i=0;is.lblMax||d<2){sp.visible=false; continue;} sp.visible=true; var hh=Math.max(.3,d*.04)*sp.userData.k; sp.scale.set(hh*6.25,hh,1);}
try{clouds.forEach(function(g){g.visible=false;});}catch(e){}
if(!s.fly&&player.y<-9&&!T.busy){var a2=s.spawn; player.x=a2.x+.5; player.z=a2.z+.5; player.y=0; player.vy=0;}
/* what is near? */
var best=null,bd=1e9,ic=null;
if(s.fly){ s.icons.forEach(function(c){var d=Math.hypot(player.x-c.x,player.z-c.z); if(d<5&&d=LMS.length){var g=typeById(BY.r_gold); inv[g.id]=(inv[g.id]||0)+1; S.st.rr=(S.st.rr||0)+1; extra='+8 blocks and a Gold block for finishing your Singapore passport!';}
m._rw=extra; try{updateHotbar();}catch(e){}
}
var TOURM=null;
function lmSet(id){var r=null; try{READ.forEach(function(x){if(x.id==='lm_'+id)r=x;});}catch(e){} return r;}
function openTour(m){
if(!m)return; pauseGame(); try{document.querySelector('#tourPanel .tmaths b').textContent='Maths link'; var nn=document.querySelectorAll('#tourPanel .lvldesc'); nn[nn.length-1].textContent=(TZ.cur&&TZ.cur.fly)?'On this map 1 block is 250 metres, so each model is only a small picture of the real place. Tap Zoom in to see it at its real size.':(TZ.cur?'Here 1 block is 1 metre, so the sizes you see are the real sizes.':'Our block models are small and simple. The real ones are much bigger.');}catch(e){}
document.getElementById('tourT').textContent=m.name; document.getElementById('tourSub').textContent=m.sub;
document.getElementById('tourF').innerHTML=m.f.map(function(t){return '
':'/>');
h+='';
for(var c=0;c';}
h+=''+LABST.cells+' cell'+(LABST.cells>1?'s':'')+'+ -';
h+='';
for(var i=0;i<5;i++){var x=X[i],y=Y[i],v=p[i],g='';
if(v==='Wire')g+='';
else if(v==='Gap')g+='GAP';
else if(v==='Switch (open)')g+='';
else if(v==='Switch (closed)')g+='closed';
else {var lit=st.lit, br=st.bright; g+=''+(lit?'':'')+'';}
if(i===2){var op=g.indexOf('>')+1; g=g.slice(0,op)+''+g.slice(op).replace(/';}
h+=g+'';}
var msg=st.lit?('The circuit is complete. '+(st.bulbs>1?'Both bulbs are lit '+(st.bright==='dim'?'but dim: the cells are shared between more bulbs.':'.'):'The bulb is lit '+(st.bright==='bright'?'and bright: more cells push more current.':'.'))):(st.gap?'There is a gap, so the circuit is broken.':(st.open?'The switch is open, so no current flows.':(st.bulbs?'':'There is no bulb in the loop yet.')));
area.innerHTML='
'+msg+'
Tap a dashed box to change its part: wire, open switch, closed switch, bulb or gap.
';
area.querySelectorAll('.labslotg').forEach(function(g){g.addEventListener('click',function(){if(LABST.done)return; var i=+g.getAttribute('data-i'); LABST.p[i]=CIRO[(CIRO.indexOf(LABST.p[i])+1)%CIRO.length]; LABST.draw();});});
var bat=area.querySelector('.labbat'); if(bat)bat.addEventListener('click',function(){if(LABST.done)return; LABST.cells=LABST.cells%3+1; LABST.draw();});
};
LABST.draw();
}
function labCirState(){
var p=LABST.p, gap=p.indexOf('Gap')>=0, open=p.indexOf('Switch (open)')>=0, bulbs=p.filter(function(x){return x==='Bulb';}).length, lit=!gap&&!open&&bulbs>0, ratio=bulbs?LABST.cells/bulbs:0;
return {gap:gap,open:open,bulbs:bulbs,lit:lit,bright:!lit?'off':(ratio>1?'bright':(ratio<1?'dim':'normal')),cells:LABST.cells};
}
var SVGN='http://www.w3.org/2000/svg';
function gearPath(cx,cy,r,n,rot){var d='',k,p=2*Math.PI/n,ro=r+5,ri=r-1; function pt(a,rr){d+=(d?'L':'M')+(cx+Math.cos(a)*rr).toFixed(1)+' '+(cy+Math.sin(a)*rr).toFixed(1);} for(k=0;k';
h+='A '+n1+'B '+n2+'';
task='Gear A (driver, '+n1+' teeth) turns '+rev+' times. Gear B has '+n2+' teeth. How many times does B turn?';
var res=rev*n1/n2; ans=String(res%1===0?res:res.toFixed(1)); opts=[ans,String(rev),String(rev*2),String(rev*3),String(Math.max(1,Math.round(rev/2)))].filter(function(v,i,a){return a.indexOf(v)===i;}).slice(0,4); if(opts.indexOf(ans)<0)opts[0]=ans;
why='B turns '+rev+' x '+n1+' / '+n2+' = '+ans+' times. A gear with more teeth turns more slowly.';
} else {
var cnt=(mode==='dir')?2:3, ts=[14,14,14], xs=[], cx0=100, ccw=Math.random()<.5, i;
for(i=0;i'+'ABC'[i]+'';}
h+=''+(ccw?'\u21BA':'\u21BB')+'';
var tgt=cnt-1, same=(tgt%2===0); task='Gear A turns '+(ccw?'anticlockwise':'clockwise')+'. All the gears mesh. Which way does gear '+'ABC'[tgt]+' turn?';
var dirA=ccw?'anticlockwise':'clockwise', dirB=ccw?'clockwise':'anticlockwise'; ans=same?dirA:dirB; opts=['clockwise','anticlockwise']; why='Meshing gears turn in opposite directions, so each gear along the chain reverses the one before it.';
}
} else if(kind==='lev'){
var W,dl,dr,E,tries=0; do{W=pick([2,3,4,5,6]); dl=pick([1,2,3,4]); dr=pick([1,2,3,4,6]); E=W*dl/dr; tries++;}while((E%1!==0||E>12||dr===dl)&&tries<200);
var mode2=pick(['bal','cls']);
if(mode2==='bal'||true){
var u=26; h+='';
h+=''+W+' N';
h+=''+dl+' m'+dr+' m?fulcrum';
task='The lever balances. The load is '+W+' N, '+dl+' m left of the fulcrum. How much effort (N) is needed '+dr+' m to the right?';
ans=String(E); opts=[ans,String(W),String(W+dl),String(Math.max(1,E+2)),String(E*2)].filter(function(v,i,a){return a.indexOf(v)===i;}).slice(0,4); if(opts.indexOf(ans)<0)opts[0]=ans;
why='Balanced: load x distance = effort x distance, so '+W+' x '+dl+' = '+(W*dl)+', and '+(W*dl)+' / '+dr+' = '+E+' N.';
}
} else {
var nr=pick([1,2,3,4]), L=pick([12,16,24,40]), isFix=Math.random()<.3;
if(isFix){
h+=''+L+' N\u2193 pull';
task='A single FIXED pulley lifts a '+L+' N load. What does the pulley change?'; ans='The direction of the force'; opts=['The direction of the force','The size of the effort','The weight of the load','The speed of the Earth']; why='A fixed pulley only changes the direction of the force. You pull down to lift up, with the same effort.';
} else {
h+=''; var k; for(k=0;k';}
h+='pulley'+L+' N';
h+=''+nr+' rope'+(nr>1?'s':'')+' hold the load';
var E2=L/nr; task='A load of '+L+' N is held up by '+nr+' supporting rope'+(nr>1?'s':'')+'. Ignoring friction, what effort (N) is needed to lift it?'; ans=String(E2%1===0?E2:E2.toFixed(1)); opts=[ans,String(L),String(L*2),String(L/2%1===0?L/2:Math.round(L/2))].filter(function(v,i,a){return a.indexOf(v)===i;}); if(nr===2&&opts.length<3)opts.push(String(L/4));
why='The load is shared between the '+nr+' ropes, so the effort is '+L+' / '+nr+' = '+ans+' N. More ropes means less effort, but you pull more rope.';
}
}
svg.innerHTML=h; ob('labTask').textContent=task; mechChoices(task,opts,ans,why);
}
function labCheck(){
if(!LABST||LABST.done)return; var m=ob('labMsg');
if(LABKIND==='ord'){
var d=LABST.d, n=d.items.length; if(LABST.pl.length=2; msg='Two bulbs in one closed loop (a series circuit) both light.'; fb='You need two bulbs and a complete loop.';}
if(T==='off'){ok2=st.open&&!st.gap&&st.bulbs>0; msg='An open switch breaks the circuit, so the bulb stays off.'; fb='You need a bulb, one open switch and no gaps.';}
if(T==='bright'){ok2=st.lit&&st.bulbs===1&&st.cells>=2; msg='More cells push more current through the bulb, so it is brighter.'; fb='Light exactly one bulb and use 2 or 3 cells.';}
if(T==='dim'){ok2=st.lit&&st.bulbs>=2&&st.cells=2; msg='With more cells for the same two bulbs, each gets more current and shines brighter.'; fb='Tap the battery to add cells.';}
if(ok2){LABST.done=true; labAward('Correct! '+msg);} else {LABST.miss=1; m.textContent=fb; sfx('wrong');}
}
}
ob('labBtn').addEventListener('click',openLab); ob('labBack').addEventListener('click',closeModals); ob('labCheck').addEventListener('click',labCheck); ob('labNew').addEventListener('click',function(){labStart(LABKIND);}); ob('labList').addEventListener('click',labMenu);
/* teacher view: fold each section so the panel is short and clear */
(function(){
var open={'Classes':1,'Teacher account':1,'Teachers (owner only)':1,'Start here: 4 steps for a new teacher':1,'Start here: 6 steps for a new teacher':1,'Live class board':1};
[].slice.call(document.querySelectorAll('#teachPanel .tmaths')).forEach(function(sec){
var t=sec.firstElementChild; if(!t||t.tagName!=='B')return;
var d=document.createElement('details'); d.className='tmx'; if(open[t.textContent])d.open=true;
var s=document.createElement('summary'); s.appendChild(t); d.appendChild(s);
while(sec.firstChild)d.appendChild(sec.firstChild); sec.appendChild(d);
});
})();
/* ===== Teacher first-run fixes (wk/teacher) =====
1. A Learner's starting level now comes from the class NAME the teacher typed (for example P1A), not from the random class code.
2. Class controls: the class box takes the class CODE, offers the teacher's own classes, and fills itself when there is only one.
3. Results: a clear note when Learners have joined but no class session has run (only live-class work is counted).
4. Owner-only download button moves to the More tab; Start-here steps open by default (see the fold block above).
5. Remove one Learner from a class, and delete a class with all its Learner records at the end of term. */
(function(){
var CLSN={}, _cfc=classFromCode, _mc=maybeClass;
classFromCode=function(){var c=(S.me&&S.me.cls)||'', nm=CLSN[c]; if(nm){var m=/^P?([1-6])/i.exec(nm); return m?+m[1]:0;} return _cfc();};
maybeClass=function(){
var c=(S.me&&S.me.cls)||'';
if(!c||!dbh||CLSN[c]!==undefined)return _mc();
function go(n){CLSN[c]=n||''; _mc();}
try{dbh.doc('plans/cls_'+c).get().then(function(sn){var d=sn.exists?sn.data():null; go(d&&d.kind==='class'?String(d.n||''):'');}).catch(function(){go('');});}catch(e){go('');}
};
/* A teacher who is already signed in on this device goes straight to the game (the Welcome box asks for a Learner name and has no Skip button). */
var BOOTED=false, _ow=openWho; setTimeout(function(){BOOTED=true;},0);
openWho=function(){
if(!BOOTED){var t=null,k=null; try{t=localStorage.getItem('mb.tid'); k=localStorage.getItem('mb.tk');}catch(e){} if(t&&k)return;}
return _ow.apply(this,arguments);
};
var _rp=renderPlan;
renderPlan=function(){_rp.apply(this,arguments); try{planClsHelp();}catch(e){}};
function planClsHelp(){
var ins=[].slice.call(document.querySelectorAll('#planBody input')).filter(function(i){var a=i.getAttribute('aria-label')||''; return a==='Class for the session'||a==='Class to publish the plan to';});
if(!ins.length)return;
function fill(){
var codes=Object.keys(TCLS||{}), dl=document.getElementById('tcDl');
if(!dl){dl=document.createElement('datalist'); dl.id='tcDl'; document.body.appendChild(dl);}
dl.innerHTML=''; codes.forEach(function(c){var o=document.createElement('option'); o.value=c; o.textContent=clsName(c); dl.appendChild(o);});
ins.forEach(function(i){i.placeholder='Class code, e.g. '+(codes[0]||'K7Q3XM'); i.setAttribute('list','tcDl'); if(!i.value&&codes.length===1)i.value=codes[0];});
}
fill();
if(!Object.keys(TCLS||{}).length&&dbh){try{dbh.collection('plans').where('kind','==','class').get().then(function(sn){sn.docs.forEach(function(d){var x=d.data()||{}; TCLS[String(d.id).replace(/^cls_/,'')]={n:x.n||'',s:x.s||'',tn:x.tn||'',et:x.et||'',wk:Array.isArray(x.wk)?x.wk:[]};}); fill();}).catch(function(){});}catch(e){}}
}
var _rt=renderTeach;
renderTeach=function(){var sh=_rt.apply(this,arguments); try{
var b=document.getElementById('tBody'), old=document.getElementById('tNoLive'); if(old)old.remove();
if(b&&sh&&sh.length&&!sh.some(function(x){return x.a>0;})){var p=document.createElement('p'); p.id='tNoLive'; p.className='lvldesc'; p.style.cssText='background:#fff4d6;border-radius:8px;padding:8px;font-weight:700'; p.textContent='These Learners have joined, but no answers are counted yet. Work only shows here when it is done while your class is live. Open the Live class tab, tap Open class controls, then Start class. Home practice is never counted.'; b.insertBefore(p,b.firstChild);}
}catch(e){} return sh;};
var _ti=tabInit;
tabInit=function(){var P=document.getElementById('teachPanel'), first=!!(P&&!P.dataset.tabs); _ti.apply(this,arguments); if(first){var r=document.getElementById('regCsv'); r=r&&r.closest('.row2'); if(r)r.dataset.tab='more';}};
var _sm=stuMap;
stuMap=function(sn){var r=_sm.apply(this,arguments); try{sn.docs.forEach(function(d,i){if(r[i])r[i]._id=d.id;});
/* one row per Learner per device: an older record stored as students/ and the newer students/. for the same name and class are merged (newest kept; both ids remembered so Remove Learner clears both) */
var seen={}, out=[]; r.forEach(function(x){var dv=String(x._id||'').split('.')[0], k=dv+'|'+x.c+'|'+String(x.n).toLowerCase(), j=seen[k];
if(j===undefined){seen[k]=out.length; x._ids=[x._id]; out.push(x); return;}
var o=out[j], ids=o._ids.concat([x._id]); if((x.ts||0)>(o.ts||0)){x._ids=ids; out[j]=x;} else o._ids=ids;});
r=out;}catch(e){} return r;};
function hdr(){var h=tlHdr(); h['content-type']='application/json'; return h;}
/* remove one Learner from the class list */
(function(){
var row=document.getElementById('pwBtn'); row=row&&row.parentNode; if(!row)return;
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.id='pwRm'; b.textContent='Remove Learner from class list'; row.insertBefore(b,document.getElementById('pwMsg'));
var msg=document.getElementById('pwMsg');
b.addEventListener('click',function(){
var sel=document.getElementById('pwSel'), v=sel.value||''; if(!v){msg.textContent='Choose a Learner first.'; return;}
var p=v.split('|'), cls=p[0], nm=p.slice(1).join('|');
var ids=(tCloud||[]).filter(function(r){return (r.c||'')===cls&&String(r.n).toLowerCase()===nm.toLowerCase()&&r._id;}).reduce(function(a,r){return a.concat(r._ids||[r._id]);},[]);
if(!ids.length){msg.textContent=nm+' is not saved on the site (only a progress code was added), so there is nothing to remove. Tap Clear imported codes to remove it.'; return;}
if(b.dataset.sure!==v){b.dataset.sure=v; b.textContent='Tap again to remove '+nm; msg.textContent=''; return;}
b.dataset.sure=''; b.textContent='Remove Learner from class list'; msg.textContent='Removing...';
Promise.all(ids.map(function(id){return dbh.doc('students/'+id).delete();}).concat([dbh.doc('names/'+nameKey(cls,nm)).delete().catch(function(){})])).then(function(){
tCloud=tCloud.filter(function(r){return ids.indexOf(r._id)<0;}); msg.textContent='Removed '+nm+' from the class list. If '+nm+' still has the class in My classes and plays again, they will appear again. Ask them to tap Leave there.'; renderTeach(); try{tcRender();}catch(e){}
}).catch(function(e){msg.textContent='Could not remove: '+(/forbidden/i.test(e&&e.message)?'only the teacher who owns this class can remove a Learner, and the site may need updating.':(e&&e.message||'try again')); });
});
})();
/* end of term: delete the class and every Learner record kept for it */
var _tr=tcRender;
tcRender=function(){_tr.apply(this,arguments); try{
var box=document.getElementById('tcList'); if(!box)return;
[].slice.call(box.querySelectorAll('.row2')).forEach(function(row){
var rm=[].slice.call(row.querySelectorAll('button')).filter(function(x){return x.textContent==='Remove'||/^Tap again to remove$/.test(x.textContent);})[0]; if(!rm)return;
var m=/Code:\s*([A-Z0-9]+)/.exec(row.textContent); if(!m)return; var code=m[1];
var nb=rm.cloneNode(false); nb.textContent='Delete class and Learner records'; nb.type='button'; rm.parentNode.replaceChild(nb,rm);
nb.addEventListener('click',function(){
if(nb.dataset.sure!=='1'){nb.dataset.sure='1'; nb.textContent='Tap again: deletes the class and every Learner record'; var mm=document.getElementById('tcMsg'); if(mm)mm.textContent='Download the class CSV first (Results tab). After this, the class code stops working and its Learner records are gone.'; setTimeout(function(){nb.dataset.sure=''; nb.textContent='Delete class and Learner records';},8000); return;}
nb.disabled=true;
fetch('/api/class/purge',{method:'POST',headers:hdr(),body:JSON.stringify({code:code})}).then(function(r){return r.json().catch(function(){return {};}).then(function(j){if(r.status===404||(!r.ok&&!j.error)){throw new Error('old');} if(!r.ok)throw new Error(j.error||'failed'); return j;});}).then(function(j){
tCloud=(tCloud||[]).filter(function(r){return r.c!==code;}); delete TCLS[code]; var mm=document.getElementById('tcMsg'); if(mm)mm.textContent='Deleted the class '+code+' and '+(j.pupils|0)+' Learner record'+((j.pupils|0)===1?'':'s')+'.'; tcLoad();
}).catch(function(e){nb.disabled=false; nb.dataset.sure=''; nb.textContent='Delete class and Learner records'; var mm=document.getElementById('tcMsg'); if(mm)mm.textContent=(e&&e.message==='old')?'The site has not been updated yet, so the class could not be deleted here. Ask the site owner to update the site.':'Could not delete: '+(e&&e.message||'try again');});
});
});
}catch(e){}};
})();
/* ===== Teacher plays and checks (wk/teacher) =====
1. A device that is signed in as a teacher never sends class results, live-board beats, leaderboard scores or a class Learner record, even if the teacher joined a class to test it.
2. "What Learners see" button on each class: the class level, plan, PIN state and session state as a Learner will meet them (read only). */
(function(){
function isTch(){return !!(tlLS('mb.tid')&&tlLS('mb.tk'));}
var _ca=classAct; classAct=function(){return isTch()?false:_ca();};
var _le=lbEligible; lbEligible=function(){return isTch()?false:_le();};
var _bp=boardPush; boardPush=function(){if(isTch())return; return _bp();};
var _ss=syncSoon; syncSoon=function(){if(isTch())return; return _ss();};
/* Results: a Refresh button, so a teacher sees new Learner work without closing and reopening Teacher view */
var _tb=tabInit;
tabInit=function(){var P=document.getElementById('teachPanel'), first=!!(P&&!P.dataset.tabs); _tb.apply(this,arguments); if(!first||document.getElementById('resRef'))return; try{
var sel=document.getElementById('tClass'); var host=sel&&(sel.closest('.row2')||sel.parentNode); if(!host)return;
var row=document.createElement('div'); row.className='row2'; row.dataset.tab='results';
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.id='resRef'; b.textContent='Refresh results';
var m=document.createElement('span'); m.className='lvldesc'; m.id='resRefMsg'; m.setAttribute('role','status'); m.textContent='Shows what Learners did up to the last few seconds.';
b.addEventListener('click',function(){m.textContent='Loading...'; try{loadCloudStudents(function(){renderTeach(); tcRender(); m.textContent='Updated at '+new Date().toLocaleTimeString('en-SG', {timeZone:'Asia/Singapore',hour:'2-digit',minute:'2-digit',second:'2-digit'})+'.';});}catch(e){m.textContent='Could not refresh. Try again.';}});
row.appendChild(b); row.appendChild(m); host.parentNode.insertBefore(row,host);
}catch(e){}};
/* A new Learner who joins while the first-visit question is open now still starts at the level in the class name */
try{var sm=document.getElementById('startModal'); if(sm&&window.MutationObserver)new MutationObserver(function(){if(sm.hidden)setTimeout(function(){try{maybeClass();}catch(e){}},300);}).observe(sm,{attributes:true,attributeFilter:['hidden']});}catch(e){}
/* CSV files: a Learner name that starts with = + - or @ could run as a formula in Excel. Put an apostrophe in front so it stays plain text. */
function csvSafe(t){
t=String(t); var o='', inQ=false, st=true, i, c;
for(i=0;i=PRG_RULE.nwMin&&ok/att=PRG_RULE.maMin&&ok/att>=PRG_RULE.maPct;}
function rgSplit(n){var m=/^(.*?) #([A-Za-z0-9]{1,4})$/.exec(String(n==null?'':n)); return m?{n:m[1],rg:m[2]}:{n:String(n==null?'':n),rg:''};}
function rgCmp(a,b){return (a.rg?0:1)-(b.rg?0:1)||String(a.rg).localeCompare(String(b.rg),undefined,{numeric:true})||String(a.n).localeCompare(String(b.n));}
(function(){
var $=function(i){return document.getElementById(i);};
/* ---- counting: full 10-question rounds and ran-out-of-time answers, class sessions only ---- */
function cs(){return S.cs=S.cs||{a:0,f:0,sk:{},h:{}};}
function rdDone(n){var c=cs(); c.rf=(c.rf|0)+n; var h=c.h[dkey(0)]; if(h)h.r=(h.r|0)+n; var a=S.ss; if(a&&a.length)a[a.length-1].r=(a[a.length-1].r|0)+n;}
var _rs=recordSkill; recordSkill=function(){var r=_rs.apply(this,arguments); try{if(classAct()&&mode==='play'&&!trk()&&window.QSIT>0&&window.QSIT%10===0)rdDone(1);}catch(e){} return r;};
var _re=roundEnd; roundEnd=function(t){try{if(classAct()&&t&&t.len>=10)rdDone(Math.floor(t.len/10));}catch(e){} return _re.apply(this,arguments);};
var _ot=onTimeout; onTimeout=function(w){try{if(!w&&!locked2&&classAct()&&cq&&!cq.rec)cs().to=(cs().to|0)+1;}catch(e){} return _ot.apply(this,arguments);};
/* ---- Learner side: always type your own name and register number; if the class has a list, it is checked against it ---- */
var ROS={code:'',list:null,t:null,tries:0}, RSORT=function(a,b){return rgCmp({rg:a[0],n:a[1]},{rg:b[0],n:b[1]});};
function rgClean(v){return String(v||'').toUpperCase().replace(/[^A-Z0-9]/g,'').slice(0,4);}
function rosList(d){var L=[]; if(d&&Array.isArray(d.ro))d.ro.forEach(function(s){var i=String(s).indexOf('|'); if(i>0)L.push([String(s).slice(0,i),String(s).slice(i+1)]);}); return L.sort(RSORT);}
function lev(a,b){var m=[],i,j; for(i=0;i<=a.length;i++){m[i]=[i];} for(j=1;j<=b.length;j++)m[0][j]=j; for(i=1;i<=a.length;i++)for(j=1;j<=b.length;j++)m[i][j]=Math.min(m[i-1][j]+1,m[i][j-1]+1,m[i-1][j-1]+(a[i-1]===b[j-1]?0:1)); return m[a.length][b.length];}
var nrm=function(t){return String(t).toLowerCase().replace(/[^\p{L}\p{N}]/gu,'');};
/* the typed register number must be on the list and the typed name must be close to the listed name (case ignored, small spelling slack). Returns the listed [rg,name] or null. */
function rosMatch(L,rg,nm){var e=L.filter(function(x){return x[0].toUpperCase()===rg;})[0]; if(!e)return null; var a=nrm(nm), b=nrm(e[1]); return a&&(a===b||lev(a,b)<=Math.max(1,Math.ceil(b.length/4)))?e:null;}
var ROSBAD='Check your name and register number, or ask your teacher.';
function rosUI(){var cl=$('whoCls').value.trim(); $('whoRgF').hidden=!cl;}
function rosFetch(){
clearTimeout(ROS.t); var cl=$('whoCls').value.trim().toUpperCase();
if(cl.length<3||!dbh){ROS.code='';ROS.list=null; rosUI(); if(cl.length>=3&&ROS.tries++<3)ROS.t=setTimeout(rosFetch,1500); return;}
ROS.t=setTimeout(function(){try{dbh.doc('plans/cls_'+cl).get().then(function(sn){ROS.code=cl; ROS.list=rosList(sn.exists?sn.data():null); rosUI();}).catch(function(){ROS.code='';ROS.list=null;});}catch(e){}},350);
}
$('whoCls').addEventListener('input',function(){ROS.tries=0; rosUI(); rosFetch();});
var _ow=openWho; openWho=function(){var r=_ow.apply(this,arguments); try{var sp=rgSplit($('whoName').value); $('whoName').value=sp.n; $('whoRg').value=sp.rg; ROS.tries=0; rosUI(); rosFetch();}catch(e){} return r;};
var _wg=whoPlayerGo; whoPlayerGo=function(){
var ne=$('whoName'), msg=$('whoMsg'), cl=$('whoCls').value.trim().toUpperCase().slice(0,10), sp=rgSplit(ne.value.trim()), L=(ROS.code===cl&&ROS.list)||[], nm=sp.n.trim(), rg=rgClean($('whoRg').value||sp.rg);
var full=cl&&rg&&nm?nm.slice(0,18-rg.length)+' #'+rg:nm, same=full===S.me.name&&cl===S.me.cls;
if(cl&&nm&&!same){
if(!rg){msg.textContent='Please type your register number. It helps your teacher tell Learners with the same name apart.'; $('whoMore').open=true; try{$('whoRg').focus();}catch(e){} return;}
if(L.length){var e=rosMatch(L,rg,nm); if(!e){msg.textContent=ROSBAD; $('whoMore').open=true; return;} nm=e[1]; full=nm.slice(0,18-rg.length)+' #'+rg;}
}
ne.value=full; _wg();
};
/* My classes > Join class: also asks for the register number */
(function(){
var b=$('clsJoin'), nb=b.cloneNode(true); b.parentNode.replaceChild(nb,b);
nb.addEventListener('click',function(){
var m=$('clsMsg'), c=$('clsCode').value.toUpperCase().replace(/[^A-Z0-9]/g,'').slice(0,10), rg=rgClean($('clsRg').value);
if(!c){m.textContent='Type the class code from your teacher.'; return;}
if(!S.me.name){m.textContent='Please tell us your name first (Power up, then Who is playing).'; return;}
if(!dbh){m.textContent='Open the game online to join a class.'; return;}
var base=rgSplit(S.me.name); if(!rg&&base.rg)rg=base.rg;
if(!rg){m.textContent='Please type your register number. It helps your teacher tell Learners with the same name apart.'; return;}
m.textContent='Checking the code...';
try{dbh.doc('plans/cls_'+c).get().then(function(sn){
var d=sn.exists?sn.data():null; if(!d||d.kind!=='class'){m.textContent='That code does not work. Ask your teacher.'; return;}
var L=rosList(d), nm=base.n; if(L.length){var e=rosMatch(L,rg,nm); if(!e){m.textContent=ROSBAD; return;} nm=e[1];}
var full=nm.slice(0,18-rg.length)+' #'+rg, nk=nameKey(c,full);
return nameGet(nk,'').then(function(ns){var o=ns.exists?ns.data():null;
if(o&&o.u&&o.u!==uid&&full!==S.me.name){m.textContent='Someone in your class already uses '+full+'. If this is you, check your secret code. If not, check your register number with your teacher.'; return;}
return dbh.doc('names/'+nk).set({u:uid,k:'',t:Date.now()}).catch(function(){}).then(function(){
var l=clsMine(); if(!l.some(function(z){return z.code===c;}))l.push({code:c,n:String(d.n||c).slice(0,20),s:String(d.s||'').slice(0,20)});
TCLS[c]={n:d.n,s:d.s}; S.me.name=full; clsUse(c); m.textContent='Joined '+clsName(c)+' as '+full+'.'; $('clsCode').value='';});});
}).catch(function(){m.textContent='Could not check the code. Try again.';});}catch(e){m.textContent='Could not check the code.';}
});
})();
/* ---- Teacher side: class list (register number + first name or nickname) ---- */
function rosParse(t){
var ok=[], bad=[], seen={}, isR=function(x){return /^[A-Za-z0-9]{1,4}$/.test(x)&&/\d/.test(x);};
String(t||'').split(/\r?\n/).forEach(function(ln){
ln=ln.trim(); if(!ln)return;
if(/@|\d{6,}/.test(ln)){bad.push(ln.slice(0,20)+': looks like an email or phone number'); return;}
var p=ln.split(/[\t,;]+/).map(function(x){return x.trim();}).filter(Boolean); if(p.length<2)p=ln.split(/\s+/);
var rg='', nm='';
if(p.length>1&&isR(p[0])){rg=p[0]; nm=p.slice(1).join(' ');} else if(p.length>1&&isR(p[p.length-1])){rg=p[p.length-1]; nm=p.slice(0,-1).join(' ');}
rg=rg.toUpperCase(); nm=nm.replace(/[^\p{L}\p{N} .'\-]/gu,'').replace(/\s+/g,' ').trim().slice(0,14);
if(!rg||!nm){bad.push(ln.slice(0,20)+': needs a register number and a first name'); return;}
if(seen[rg]){bad.push(ln.slice(0,20)+': register number '+rg+' is used twice'); return;}
seen[rg]=1; ok.push([rg,nm]);
});
ok.sort(function(a,b){return rgCmp({rg:a[0],n:a[1]},{rg:b[0],n:b[1]});}); return {ok:ok,bad:bad};
}
var _tc=tcRender;
tcRender=function(){_tc.apply(this,arguments); try{
var box=$('tcList'); if(!box)return;
[].slice.call(box.querySelectorAll('.row2')).forEach(function(row){
if(row.querySelector('.pros'))return; var m=/Code:\s*([A-Z0-9]+)/.exec(row.textContent); if(!m)return; var code=m[1];
var b=document.createElement('button'); b.type='button'; b.className='btn small pros'; b.style.minHeight='40px'; b.textContent='Class list'; row.appendChild(b); row.style.flexWrap='wrap';
var P=document.createElement('div'); P.hidden=true; P.style.cssText='flex-basis:100%;background:var(--inset);border-radius:8px;padding:8px';
P.innerHTML='
One Learner per line: register number, then first name or nickname, for example 12 Aisyah. Paste from a spreadsheet if you like. First name or nickname and register number only: no surname, email or phone number. Learners type their own name and register number when they join, and it is checked against this list. No login is needed.
';
row.appendChild(P); var ta=P.querySelector('textarea'), bs=P.querySelectorAll('button'), st=P.querySelector('span');
function put(d){d=d||{}; var L=Array.isArray(d.ro)?d.ro:[]; ta.value=L.map(function(s){return String(s).replace('|',' ');}).join('\n'); st.textContent=L.length?L.length+' Learners on the list.':'No list yet. Without a list, Learners type their name and register number.';}
function write(L,msg){st.textContent='Saving...'; dbh.doc('plans/cls_'+code).get().then(function(sn){var d=sn.exists?sn.data():{kind:'class'}; if(L.length)d.ro=L; else delete d.ro; return dbh.doc('plans/cls_'+code).set(d);}).then(function(){st.textContent=msg;}).catch(function(){st.textContent='Could not save. Only the teacher who owns this class can.';});}
b.addEventListener('click',function(){P.hidden=!P.hidden; b.textContent=P.hidden?'Class list':'Hide class list'; if(!P.hidden){st.textContent='Loading...'; dbh.doc('plans/cls_'+code).get().then(function(sn){put(sn.exists?sn.data():null);}).catch(function(){st.textContent='Could not load the list.';});}});
bs[0].addEventListener('click',function(){var r=rosParse(ta.value); if(!r.ok.length&&!r.bad.length){st.textContent='Type or paste the list first.'; return;} if(r.bad.length){st.textContent='Not saved. Fix these lines: '+r.bad.slice(0,4).join('; ')+(r.bad.length>4?' and '+(r.bad.length-4)+' more':'')+'.'; return;} if(r.ok.length>60){st.textContent='Not saved. The list can have up to 60 Learners.'; return;} ta.value=r.ok.map(function(e){return e[0]+' '+e[1];}).join('\n'); write(r.ok.map(function(e){return e[0]+'|'+e[1];}),'Saved: '+r.ok.length+' Learners on the list.');});
bs[1].addEventListener('click',function(){ta.value=''; write([],'The list was cleared. Learners type their name and register number.');});
});
}catch(e){}};
/* ---- Results: help list, heat map, weeks (analytics) ---- */
var st=document.createElement('style');
st.textContent='#teachPanel .btn.small,#teachPanel select,#teachPanel details.tmx>summary{min-height:40px}#prgBox details{margin:8px 0}#prgBox summary{min-height:44px;display:flex;align-items:center;font-weight:800;cursor:pointer}#prgBox .prgB{overflow:auto;max-height:70vh;max-width:100%;border:2px solid var(--line);border-radius:8px}#prgBox .prgB:focus{outline:3px solid #2a6fd6}'
+'table.hm{width:auto;font-size:.8rem}table.hm th,table.hm td{text-align:center;padding:4px 6px;border:1px solid var(--line);white-space:normal}table.hm thead th{min-width:74px;max-width:92px;overflow-wrap:break-word;text-transform:none;letter-spacing:0;font-size:.75rem;position:sticky;top:0;background:var(--panel);z-index:2}table.hm tbody th{text-align:left;white-space:nowrap;position:sticky;left:0;background:var(--panel);z-index:1;text-transform:none;letter-spacing:0;font-size:.85rem;color:var(--fg)}table.hm thead th:first-child{left:0;z-index:3}'
+'.hm td b{display:block;font-size:.95rem}.hm td small{display:block;font-size:.7rem}.hmM{background:#b9e5c4;color:#0b3a1b}.hmG{background:#fbe6a2;color:#4a3400}.hmW{background:#f4b1a9;color:#6a0c05}.hmF{background:#e1e5ea;color:#2b3440}.hmN{color:var(--muted)}'
+'.hmLg{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0}.hmLg span{padding:4px 8px;border-radius:6px;font-size:.8rem;font-weight:700}'
+'.wkr{display:grid;grid-template-columns:7.5em 1fr;gap:2px 8px;align-items:center;margin:6px 0}.wkb{height:14px;background:var(--inset);border:1px solid var(--line);border-radius:4px;overflow:hidden}.wkb i{display:block;height:100%;background:#2a6fd6}.wkn{grid-column:1/-1;font-size:.85rem}'
+'#prgBox ol.prgL{margin:6px 0;padding-left:2.4em}#prgBox ol.prgL li{margin:6px 0}#prgPH{display:none}'
+'@media print{body.prgP>*:not(#prgPH){display:none!important}body.prgP{background:#fff!important}body.prgP #prgPH{display:block;color:#000;background:#fff}#prgPH .prgB{overflow:visible;max-height:none;border:0}#prgPH table.hm{font-size:9px}#prgPH table.hm th,#prgPH table.hm td{position:static;padding:2px 3px;border:1px solid #444;background-clip:border-box;-webkit-print-color-adjust:exact;print-color-adjust:exact}#prgPH .hmLg span{-webkit-print-color-adjust:exact;print-color-adjust:exact;border:1px solid #444}}';
document.head.appendChild(st);
var tb=$('tBody'), box=document.createElement('div'); box.id='prgBox'; box.dataset.tab='results'; tb.parentNode.insertBefore(box,tb.nextSibling);
function ago(d){var p=d.split('-'), t=new Date(); t.setHours(0,0,0,0); return Math.round((t-new Date(+p[0],+p[1]-1,+p[2]))/864e5);}
function dshort(d){var p=d.split('-'); return wyDate(new Date(+p[0],+p[1]-1,+p[2]).getTime());}
function sect(id,title,inner){return ''+title+''+inner+'';}
function who(x){return esc((x.rg?x.rg+' ':'')+x.first);}
var RC={};
function help(rows,code){
var R=PRG_RULE, info=rows.map(function(x){var l=''; Object.keys(x.h).forEach(function(d){if((x.h[d][0]|0)>0&&d>l)l=d;}); return {x:x,l:l,d:l?ago(l):null};}), live=info.some(function(i){return i.d!==null&&i.d=R.hDays)){sc+=3; w.push(!x.a?'Has joined but has not answered a class question yet.':i.d===null?'No class answers in the last 8 weeks.':'No class answers for '+i.d+' days (last on '+dshort(i.l)+').');}
if(x.a>=R.hLowN&&x.acc=R.hTo&&x.a&&x.to/x.a>=R.hToPct){sc+=1+x.to/x.a; w.push('Ran out of time on '+x.to+' of '+x.a+' questions.');}
if(w.length)out.push({x:x,w:w,sc:sc});});
out.sort(function(a,b){return b.sc-a.sc||rgCmp({rg:a.x.rg,n:a.x.first},{rg:b.x.rg,n:b.x.first});});
var have={}; rows.forEach(function(x){have[String(x.rg).toUpperCase()]=1;});
var nj=(RC[code]||[]).filter(function(e){return !have[e[0].toUpperCase()];});
return sect('prgHelp','Who needs help now','
Listed when a Learner has not answered in class for '+R.hDays+' days or more while the class has, has under '+R.hLowPct+'% right first try after '+R.hLowN+' or more answers, or ran out of time on '+R.hTo+' or more questions (at least '+Math.round(R.hToPct*100)+'% of them). Most urgent first. Names on the class list who have not joined are shown last.
Nobody needs help right now by these rules.'+(live?'':' No class answers in the last '+R.hDays+' days, so nobody is flagged as inactive.')+'
'));
}
function cell(nm,k,w){
if(!w||!w.att)return '
–
';
var p=Math.round(w.ok/w.att*100), s=needsWork(w.att,w.ok)?'W':masteredSkill(w.att,w.ok)?'M':w.att'+p+'%'+w.ok+'/'+w.att+(s==='M'?' ✓':s==='W'?' !':'')+'';
}
function heat(rows,id){
var tot={}; rows.forEach(function(r){r.rows.forEach(function(w){tot[w.k]=(tot[w.k]||0)+w.att;});});
var ks=Object.keys(tot).sort(function(a,b){return tot[b]-tot[a]||(aNo class answers yet.');
var t='
Mastered ✓: '+PRG_RULE.maMin+'+ recent answers, '+Math.round(PRG_RULE.maPct*100)+'%+ right first tryGetting thereNeeds work !: under '+Math.round(PRG_RULE.nwPct*100)+'% on '+PRG_RULE.nwMin+'+ recent answersToo few answers (under '+PRG_RULE.nwMin+')
Each cell shows the % right first try and right/answers, counting the last 8 class answers on that topic. Topics shown: the '+ks.length+' most practised.
';
return sect(id,'Mastery heat map',''+t);
}
function wkMon(d){var p=d.split('-'), t=new Date(+p[0],+p[1]-1,+p[2]); t.setDate(t.getDate()-((t.getDay()+6)%7)); return ldate(t.getTime());}
function weeks(rows){
var m=new Date(); m.setHours(12,0,0,0); m.setDate(m.getDate()-((m.getDay()+6)%7)); var W=[], i;
for(i=5;i>=0;i--){var d=new Date(m); d.setDate(d.getDate()-7*i); W.push(ldate(d.getTime()));}
var C={}; W.forEach(function(w){C[w]={a:0,c:0,r:0,n:0};});
var per=rows.map(function(x){var o={}; W.forEach(function(w){o[w]={a:0,c:0,r:0};}); Object.keys(x.h).forEach(function(d){var w=o[wkMon(d)], v=x.h[d]; if(!w)return; w.a+=v[0]|0; w.c+=v[1]|0; w.r+=v[3]|0;}); W.forEach(function(w){var q=o[w]; C[w].a+=q.a; C[w].c+=q.c; C[w].r+=q.r; if(q.a)C[w].n++;}); return {x:x,o:o};});
var t='
Class answers only, week by week (weeks start on Monday). Home practice is never counted.
';});
return sect('prgWeeks','Progress over weeks',t+'
');
}
function prgRender(shown){
if(!shown||!shown.length||!(prgOn('heat')||prgOn('weeks')||prgOn('help'))){box.innerHTML=''; return;}
var by={}, ord=[], h='
';});
box.innerHTML=h;
[].forEach.call(box.querySelectorAll('.prgPr'),function(b){b.addEventListener('click',function(){
var d=b.closest('details'), ph=document.createElement('div'); ph.id='prgPH'; var cn=b.closest('[data-c]').querySelector('h3').textContent;
ph.innerHTML='
Mastery heat map: '+esc(cn)+'
Printed '+new Date().toLocaleDateString('en-SG',{day:'numeric',month:'short',year:'numeric'})+'. Class answers only.
'+d.querySelector('.prgB').outerHTML+d.querySelector('.hmLg').outerHTML+d.querySelector('.hmLg').nextSibling.outerHTML;
document.body.appendChild(ph); document.body.classList.add('prgP');
var done=function(){document.body.classList.remove('prgP'); if(ph.parentNode)ph.remove(); window.removeEventListener('afterprint',done);}; window.addEventListener('afterprint',done);
setTimeout(function(){try{window.print();}catch(e){} setTimeout(done,60000);},50);});});
}
var _rt=renderTeach; renderTeach=function(){var sh=_rt.apply(this,arguments); try{prgRender(sh);}catch(e){} return sh;};
})();
/*MCBEGIN*/
/* ===== Music at more levels, Computing for Sec 4, exit tickets (z7) =====
No new questions: every question below already existed. Each existing Music question or generator carries year-band tags:
p3 (P3-4), p5 (P5-6), s1 (Sec 1-2), s34 (Sec 3-4), q (Post-Sec). Tags were chosen against the MOE Music Syllabus 2023
(Primary and Lower Secondary), Stage 2 = P3-4, Stage 3 = P5-6, Stage 4 = Sec 1-2 (tables 2.6b to 2.6d, pages 21 to 23).
Items the lower syllabus does not cover stay in Sec 3-4 and Post-Sec only. */
var MCT={"ws":["s34 q","s1 s34 q","s34 q","s34 q","p3 p5 s1 s34 q","p5 s1 s34 q","p3 p5 s1 s34 q","p5 s1 s34 q","s1 s34 q","s1 s34 q","s34 q","p3 p5 s1 s34 q","p5 s1 s34 q","s34 q","s34 q","s34 q","s34 q","s1 s34 q","s34 q","s34 q","s34 q","s1 s34 q","s34 q"],"as":["p5 s1 s34 q","p5 s1 s34 q","p3 p5 s1 s34 q","p3 p5 s1 s34 q","p3 p5 s1 s34 q","p3 p5 s1 s34 q","p3 p5 s1 s34 q","p5 s1 s34 q","p5 s1 s34 q","p5 s1 s34 q","p5 s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","p5 s1 s34 q","p5 s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","p3 p5 s1 s34 q","s1 s34 q"],"jz":["s1 s34 q","s34 q","s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s34 q","s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","s34 q","s34 q","s1 s34 q","s1 s34 q","s34 q","s1 s34 q","s1 s34 q"],"pp":["p5 s1 s34 q","s1 s34 q","s1 s34 q","p3 p5 s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","p3 p5 s1 s34 q","s34 q","s34 q","s34 q","p5 s1 s34 q","s34 q","s34 q","s34 q","s1 s34 q","s34 q","s1 s34 q","s1 s34 q"],"fm":["s1 s34 q","s1 s34 q","s1 s34 q","s1 s34 q","p5 s1 s34 q","p5 s1 s34 q","s1 s34 q","s1 s34 q","s34 q","s34 q","p5 s1 s34 q","s34 q","s34 q","s1 s34 q","p5 s1 s34 q"]};
var MCBL={p3:'P3-4',p5:'P5-6',s1:'Sec 1-2',s34:'Sec 3-4',q:'Post-Sec'};
var MCMU=MUM.map(function(m){return [m[0],m[1],m[2]];});
var MCDESC={p3:'Music for P3 and P4: note names on the staff, note values, bars, tempo and dynamics terms, instruments from Singapore and Southeast Asia, form and listening. Turn Sound on for the listening questions.',p5:'Music for P5 and P6: notation and rhythm, terms, instruments and music of Asia, rondo and variations, pop songs, sound in film and mixing. Turn Sound on for the listening questions.',s1:'Music for Sec 1 and 2: rhythm and notation, terms, world music, texture, jazz and blues, pop and film music. Turn Sound on for the listening questions.',s34:'Foundations for Sec 3 and 4 music: reading notes, rhythm, scales and keys, terms and listening, then Western classical, Asian music, jazz, pop and film. Tunes are old public-domain melodies played by the game itself. Turn Sound on for the listening questions.',q:'Music for Post-Sec: theory, listening and the main genres, from notation to jazz, pop and film. Tunes are old public-domain melodies played by the game itself. Turn Sound on for the listening questions.'};
var MCALTD={p3:{rd:'Note names on the treble staff, lines and spaces',ws:'Binary and ternary form, ostinato',as:'Instruments of the Malay, Chinese and Indian music in Singapore and Southeast Asia',pp:'Verse and chorus, looping'},p5:{rd:'Note names on the treble and bass staff',ws:'Binary, ternary, rondo, variations and syncopation',as:'Instruments and music of Southeast Asia, China and India',pp:'Hooks, looping and panning',fm:'Music in film and mixing'},s1:{ws:'Forms, texture, ostinato and cadences',as:'Malay, Chinese, Indian and gamelan music',jz:'Blues, swing and improvisation',pp:'Song structure, riffs and recording',fm:'Diegetic music, leitmotif, Foley and sampling',tm:'Italian terms for tempo, dynamics and articulation'}};
MCALTD.p3.tm=MCALTD.p5.tm=MCALTD.s1.tm;
var MCI={}, MCG={}, MCCUR=null;
function mcAdd(m,tags,q,a,w,why){(MCI[m]=MCI[m]||[]).push({q:q,a:a,w:w,why:why,b:String(tags).split(/\s+/)});}
function mcG(m,tags,f){(MCG[m]=MCG[m]||[]).push({b:String(tags).split(/\s+/),f:f});}
(function(){
Object.keys(MUB).forEach(function(k){MUB[k].forEach(function(b,i){var t=(MCT[k]||[])[i]; if(t)mcAdd(k,t,b[0],b[1],b[2],b[3]);});});
})();
/* ---- the existing generators, tagged ---- */
var MCTSYL=['allegro','andante','presto','largo','moderato','piano (p)','forte (f)','mezzo piano (mp)','mezzo forte (mf)','pianissimo (pp)','fortissimo (ff)','crescendo','decrescendo','ritardando','accelerando','staccato','legato'];
function mcTermQ(rev){var pool=TERMS.filter(function(t){return MCTSYL.indexOf(t[0])>=0;}), t=pick(pool), k=rev?0:1, seen={}, near=[]; seen[t[0]]=1; seen[t[1]]=1;
shuffle(pool.slice()).forEach(function(x){if(near.length<3&&!seen[x[k]]&&!seen[x[1-k]]&&x[1]!==t[1]){seen[x[k]]=1; near.push(x);}});
if(rev)return {q:'Which term means "'+t[1]+'"?',a:t[0],w:near.map(function(x){return x[0];}),why:t[0]+' means '+t[1]+'.'};
return {q:'What does "'+t[0]+'" mean?',a:t[1],w:near.map(function(x){return x[1];}),why:t[0]+' means '+t[1]+'.'};}
function mcTreble(){var x,i=0; do{x=MUG.rd[0](); i++;}while(i<30&&String(x.figAlt).indexOf('treble')!==0); return x;}
mcG('rd','p3',mcTreble); mcG('rd','p5 s1 s34 q',MUG.rd[0]); mcG('rd','p3 p5 s1 s34 q',MUG.rd[1]);
mcG('rh','p5 s1 s34 q',MUG.rh[0]); mcG('rh','p3 p5 s1 s34 q',MUG.rh[1]); mcG('rh','p3 p5 s1 s34 q',MUG.rh[2]); mcG('rh','p3 p5 s1 s34 q',MUG.rh[3]); mcG('rh','s1 s34 q',MUG.rh[4]);
mcG('sc','s34 q',MUG.sc[0]); mcG('sc','s34 q',MUG.sc[1]); mcG('sc','s34 q',MUG.sc[2]); mcG('sc','s34 q',MUG.sc[3]);
mcG('tm','p3 p5 s1',function(){return mcTermQ(0);}); mcG('tm','p3 p5 s1',function(){return mcTermQ(1);});
mcG('tm','s34 q',MUG.tm[0]); mcG('tm','s34 q',MUG.tm[1]);
mcG('li','p3 p5 s1 s34 q',MUG.li[0]); mcG('li','p3 p5',MUG.li[1]); mcG('li','p3 p5 s1 s34 q',MUG.li[2]); mcG('li','p3',MUG.li[3]); mcG('li','p3 p5 s1 s34 q',MUG.li[4]); mcG('li','p3 p5',MUG.li[5]);
/* ---- choosing what to show ---- */
function mcLv(){var l=window.LRNLV; if(l==='P'||l==='S'||l==='Q')return l; try{return lrnLevel();}catch(e){return 'S';}}
function mcPb(){var v=''; try{v=localStorage.getItem('mbPb')||'';}catch(e){} if(v==='p3'||v==='p5')return v; var g=0; try{g=effGr();}catch(e){} return g>=5?'p5':'p3';}
function mcBands(){var l=mcLv(); if(l==='P')return [mcPb()]; if(l==='Q')return ['q']; return [lrnBand()==='34'?'s34':'s1'];}
function mcHas(tags,bands){for(var i=0;i=0)return true; return false;}
function mcPoolN(m,bands){return (MCI[m]||[]).filter(function(x){return mcHas(x.b,bands);}).length;}
function mcGens(m,bands){return (MCG[m]||[]).filter(function(g){return mcHas(g.b,bands);});}
function mcMods(bands){return MCMU.filter(function(m){var n=mcPoolN(m[0],bands), g=mcGens(m[0],bands).length; return n>=5||g>=2||(g>=1&&n>=2);});}
function mcItem(m,bands){
var items=(MCI[m]||[]).filter(function(x){return mcHas(x.b,bands);}), gens=mcGens(m,bands);
if(gens.length&&(!items.length||Math.random()<.5)){var x=pick(gens).f(); if(x)return x;}
return items.length?pick(items):null;
}
function mcAnyMod(bands){var ms=mcMods(bands); return ms.length?pick(ms)[0]:null;}
function mcKick(m,bands){var mm=MCMU.filter(function(z){return z[0]===m;})[0]; return {nm:mm?mm[1]:m, lbl:MCBL[bands[0]]};}
/* ---- the Music window ---- */
function mcRender(){
var box=ob('muMods'), pn=ob('muPanel'), bands=mcBands(), lv=mcLv(); box.innerHTML='';
pn.querySelector('h2').textContent='Music practice';
pn.querySelector('.lvldesc').textContent=MCDESC[bands[0]];
if(lv==='P'){var cur=bands[0], lab=document.createElement('div'); lab.className='label'; lab.style.margin='4px 0'; lab.textContent='My year'; box.appendChild(lab); var sg=document.createElement('div'); sg.className='seg'; sg.style.marginBottom='8px';
[['p3','P3-4'],['p5','P5-6']].forEach(function(o){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[1]; b.setAttribute('aria-pressed',String(cur===o[0])); b.addEventListener('click',function(){try{localStorage.setItem('mbPb',o[0]);}catch(e){} mcRender();}); sg.appendChild(b);}); box.appendChild(sg);}
var go=function(m){return function(){startMusic(m);};};
var all=document.createElement('button'); all.type='button'; all.className='btn primary'; all.textContent='Mixed practice'; all.addEventListener('click',go('all')); box.appendChild(all);
mcMods(bands).forEach(function(m){var id=m[0], n=mcPoolN(id,bands), g=mcGens(id,bands).length, b=document.createElement('button'); b.type='button'; b.className='btn';
var desc=(id==='tm'&&(bands[0]==='s34'||bands[0]==='q'))?('Grade '+tgCur()+' of 5 ('+Math.min(TGN,(S.st.tgc||{})[tgCur()]||0)+'/'+TGN+' to clear)'):((MCALTD[bands[0]]||{})[id]||m[2]);
b.textContent=m[1]+': '+desc+(n?' ('+n+' questions'+(g?' and unlimited practice':'')+')':' (unlimited practice)'); b.addEventListener('click',go(id)); box.appendChild(b);});
}
function mcOpen(){
if(modalOpen())return; pauseGame(); mcRender();
ob('muModal').hidden=false; try{ob('muPanel').focus({preventScroll:true});}catch(_){}
}
/* ---- exit tickets: Music banks MU and Computing banks CP read the same questions as the practice windows ---- */
var MCEB={MU:{p3:['P3','P4'],p5:['P5','P6'],s1:['Sec 1','Sec 2'],s34:['Sec 3','Sec 4'],q:['Post-Sec']},CP:{s34:['Sec 3','Sec 4'],q:['Post-Sec']}};
function mcEtBank(lv,sub){var sj=sub==='mu'?'MU':'CP', k='q'; Object.keys(MCEB[sj]).forEach(function(b){if(MCEB[sj][b].indexOf(lv)>=0)k=b;}); return sj+k;}
function mcIsBank(bank){return /^(MU(p3|p5|s1|s34|q)|CP(s34|q))$/.test(String(bank));}
function mcBankLabel(bank){return (bank.slice(0,2)==='MU'?'Music':'Computing')+' ('+(bank.slice(2)==='s34'&&bank.slice(0,2)==='CP'?'Sec 3-4, G1 modules':MCBL[bank.slice(2)])+')';}
(function(){
Object.keys(MCEB.MU).forEach(function(k){
var bank='MU'+k, cnt={}, rows=[];
MCMU.forEach(function(m){var items=(MCI[m[0]]||[]).filter(function(x){return x.b.indexOf(k)>=0;}); if(items.length<3)return; cnt[m[0]]=1; items.forEach(function(x){var b=[bank,x.q,x.a,x.w,x.why,0]; b.tp=m[0]; rows.push(b);});});
LST[bank]={}; MCMU.forEach(function(m){if(cnt[m[0]])LST[bank][m[0]]=m[1];});
rows.forEach(function(b){SCB.push(b);});
});
Object.keys(MCEB.CP).forEach(function(k){
var bank='CP'+k, cnt={}, rows=[];
CPM.forEach(function(m){var items=CPQ.filter(function(x){return x.m===m[0];}); if(items.length<3)return; cnt[m[0]]=1; items.forEach(function(x){var b=[bank,x.q,x.a,x.w,x.why,0]; b.tp=m[0]; rows.push(b);});});
LST[bank]={}; CPM.forEach(function(m){if(cnt[m[0]])LST[bank][m[0]]=m[1];});
rows.forEach(function(b){SCB.push(b);});
});
})();
try{sexedApply();}catch(e){}
/*MCEND*/
/* ---------- learn hub: level -> subject ---------- */
var LRNSUBS=[
['etBtn','Exit ticket','Set by your teacher','PSQ'],
['mathBtn','Maths','Primary 1 to 6','P'],['exBtn','Maths exam questions','Primary 5 and 6','P'],['scBtn','Science','Primary 3 to 6','P'],
['g1Btn','Maths','Objectives for your subject band','SQ','12|34'],['scBtn','Science','Topics for your level','SQ','12|34'],['dtBtn','Design and Technology','Sec 1 to 2','SQ','12'],['cpBtn','Computing','Sec 3 and 4: computers, networks, safe use, documents, media, spreadsheets and coding','S','34'],['cpBtn','Computing','Computers, networks, safe use, documents, media, spreadsheets and coding','Q','','',1],
['hiBtn','History','G1 topics first','SQ','12'],['geBtn','Geography','G1 topics first','SQ','12'],['ssBtn','Social Studies','Identity, living together, migration, conflict and the environment','SQ','12'],['ssBtn','Social Studies','G1 Humanities','S','34','1'],['liBtn','Literature','Perspectives in texts and admirable characters','SQ','12'],['liBtn','Literature','G1 Humanities','S','34','1'],['muBtn','Music','Notes, rhythm, terms, instruments, form and listening (P3 to P6)','P'],['muBtn','Music','Sec 1 and 2: rhythm, notation, terms, world music, jazz, pop and film','S','12'],['muBtn','Music','Sec 3 and 4: theory, listening, Western classical, Asian music, jazz, pop and film','S','34'],['muBtn','Music','Theory, listening, Western classical, Asian music, jazz, pop and film','Q','','',1],
['cnBtn','华文 Chinese','Characters and units','P'],['mlBtn','Bahasa Melayu','Peribahasa','P'],['rdBtn','Reading','Levelled reading','PSQ','12|34'],['wBtn','Singapore and the world','Places, people and ideas','PSQ','12|34'],
['fireBtn','Fire safety','Calling for help, escaping, kitchens, batteries and equipment','PSQ'],
['enBtn','English','Primary 3 to Sec 2: grammar, vocabulary, cloze and reading','PS'],
['dlBtn','Everyday life','Money, work, Singapore, daily science and puzzles','Q'],
['sgBtn','Know your Singlish',"Singapore's own flavour of English: lah, lor, kopi orders and more",'Q','','',1]];
/*AMBEGIN*/
/* ---------- Additional Maths (STARTER, Sec 3-4): generators ----------
Source of truth for the generators. Needs the game helpers rnd, pick, shuffle, Q and sup (shimmed in dump_am.js for the verifier).
Syllabus: MOE 2020 G2 and G3 Additional Mathematics Syllabuses. G3 content pp.16-18, G2 content pp.22-23, G2 Sec 5 content pp.24-25. */
var AMT=[
['a1','Quadratic functions',1,'G3 syllabus A1 (p.16); G2 syllabus A1 (p.22)'],
['a2','Equations and inequalities',1,'G3 A2 (p.16); G2 A2 (p.22)'],
['a3','Surds',1,'G3 A3 (p.16); G2 A3 (p.22)'],
['a4','Polynomials and partial fractions',1,'G3 A4 (p.16); G2 A4 (p.22)'],
['a5','Binomial expansions',0,'G3 A5 (p.16). Not in G2 until Sec 5 (G2 syllabus p.24)'],
['a6','Exponential and logarithmic functions',0,'G3 A6 (pp.16-17). Not in G2 until Sec 5 (G2 syllabus p.24)'],
['g1','Trigonometry',1,'G3 G1 (p.17); G2 G1 (pp.22-23)'],
['g2','Coordinate geometry',1,'G3 G2 2.1 to 2.4 (p.17); G2 G2 2.1 to 2.4 (p.23)'],
['c1','Differentiation and integration',1,'G3 C1 (p.18); G2 C1 with x^n only at Sec 3-4 (pp.23, 25)']
];
var AMG=[];
function AMgen(t,id,name,syl,f){AMG.push({id:t+'.'+id,t:t,n:name,s:syl,f:f});}
function amTopic(t){return AMT.filter(function(x){return x[0]===t;})[0];}
function amNZ(a,b){var v; do{v=rnd(a,b);}while(!v); return v;}
function amG(a,b){a=Math.abs(a); b=Math.abs(b); while(b){var t=a%b; a=b; b=t;} return a||1;}
function amFr(n,d){if(d<0){n=-n;d=-d;} var g=amG(n,d); return [n/g,d/g];}
function amFS(f){return f[1]===1?String(f[0]):f[0]+'/'+f[1];}
function amFAdd(a,b){return amFr(a[0]*b[1]+b[0]*a[1],a[1]*b[1]);}
function amFMul(a,b){return amFr(a[0]*b[0],a[1]*b[1]);}
function amSub(n){var m='₀₁₂₃₄₅₆₇₈₉'; return String(n).split('').map(function(c){return m.charAt(+c);}).join('');}
function amLog(b){return b===10?'lg ':'log'+amSub(b)+' ';}
function amNeg(s){return String(s).replace(/-/g,'−');}
/* polynomial text, highest power first; a coefficient may be a string such as 'k' (shown as kx^n, added) */
function amPoly(cs,v){v=v||'x'; var d=cs.length-1,s='',i; for(i=0;i1?sup(n):''));}
else {var ab=Math.abs(c); neg=c<0; body=(n===0)?String(ab):((ab===1?'':String(ab))+v+(n>1?sup(n):''));}
if(!s)s=(neg?'−':'')+body; else s+=(neg?' − ':' + ')+body;} return s||'0';}
function amAsc(a,b,v){v=v||'x'; var s=String(a); if(b)s+=(b<0?' − ':' + ')+(Math.abs(b)===1?'':Math.abs(b))+v; return s;}
function amMul(p,q){var r=[],i,j; for(i=0;i=0)?(a.split('.')[1].length):0,st=dec?Math.pow(10,-dec):1,d;
for(d=1;d<30&&w.length<3;d++){[n+d*st,n-d*st].forEach(function(v){var s=dec?v.toFixed(dec):String(Math.round(v)); if(x.min!=null&&v+×*\/])-(?=[\d(√a-zπ.])/g;
var tx=String(text).replace(re,'$1−').replace(/ - /g,' − ').replace(/ \+ −/g,' − ').replace(/ − −/g,' + '), wy=String(why).replace(re,'$1−').replace(/ - /g,' − ').replace(/ \+ −/g,' − ').replace(/ − −/g,' + ');
function cl(s){return s.replace(/(?0?'minimum':'maximum';
var e=amPoly([a,b,c]);
if(ask==='val')return amQ('a1.minmax:val','The curve y = '+e+' has a '+w+' point. Find the '+w+' value of y.',k,[c,-k,h,k+a*h*h,k-1],
'Complete the square: y = '+a+'(x − '+h+')² + '+k+'. The squared term is never negative, so the '+w+' value of y is '+k+', when x = '+h+'.');
return amQ('a1.minmax:x','The curve y = '+e+' has a '+w+' point. Find the value of x at which y is '+(a>0?'least':'greatest')+'.',h,[-h,2*h,b,a*h,k],
'Complete the square: y = '+a+'(x − '+h+')² + '+k+'. The turning point is at x = '+h+'. (Check: x = −b/2a = '+(-b)+'/'+(2*a)+' = '+h+'.)');
});
function amDiscCase(){ /* a, b, c with a chosen sign of the discriminant */
var kind=pick(['pos','zero','neg']),t=0,a,b,c;
if(kind==='zero'){var r=amNZ(-4,4); a=pick([1,2,3,-1,-2]); b=-2*a*r; c=a*r*r; return [a,b,c];}
do{a=pick([1,2,3,-1,-2]); b=amNZ(-8,8); c=amNZ(-6,6); t++;}while(((kind==='pos')!==(b*b-4*a*c>0)||b*b-4*a*c===0)&&t<300);
if(t>=300)throw new Error('amretry'); return [a,b,c];
}
AMgen('a1','disc','Value of the discriminant','A2 2.1 (G3 p.16, G2 p.22)',function(){
var z=amDiscCase(),a=z[0],b=z[1],c=z[2],D=b*b-4*a*c;
return amQ('a1.disc','Find the value of the discriminant b² − 4ac of the equation '+amPoly([a,b,c])+' = 0.',D,[b*b+4*a*c,b*b-a*c,-(b*b)-4*a*c,4*a*c-b*b,b*b-2*a*c],
'a = '+a+', b = '+b+', c = '+c+'. b² − 4ac = ('+b+')² − 4('+a+')('+c+') = '+(b*b)+' − ('+(4*a*c)+') = '+D+'.');
});
AMgen('a1','nroots','Nature of the roots','A2 2.1 (G3 p.16, G2 p.22)',function(){
var z=amDiscCase(),a=z[0],b=z[1],c=z[2],D=b*b-4*a*c, A=D>0?'Two different real roots':D===0?'Two equal real roots':'No real roots';
return amQ('a1.nroots','Without solving, decide the nature of the roots of '+amPoly([a,b,c])+' = 0.',A,['Two different real roots','Two equal real roots','No real roots','Cannot be decided without solving'],
'Discriminant = b² − 4ac = ('+b+')² − 4('+a+')('+c+') = '+D+'. '+(D>0?'It is positive, so there are two different real roots.':D===0?'It is zero, so the two roots are equal.':'It is negative, so there are no real roots.'));
});
AMgen('a1','always','Always positive or always negative','A1 1.2 (G3 p.16, G2 p.22)',function(){
if(Math.random()<.6){
var a=pick([1,1,2,-1,-2]),b=(Math.abs(a)===2?pick([4,-4,8,-8]):pick([2,-2,4,-4,6,-6,8,-8])),B=b*b/(4*a),pos=a>0,e=amPoly([a,b,'k']);
var A=pos?'k > '+B:'k < '+B, wr=pos?['k < '+B,'k > '+(-B),'k ≥ '+B,'k < '+(-B)]:['k > '+B,'k < '+(-B),'k ≤ '+B,'k > '+(-B)];
return amQ('a1.always:k','Find the range of values of k for which '+e+' is '+(pos?'positive':'negative')+' for all real values of x.',A,wr,
'For all x we need no real roots and a '+(pos?'> 0':'< 0')+'. Discriminant: ('+b+')² − 4('+a+')k < 0, so '+(b*b)+' − '+(4*a)+'k < 0, which gives '+A+'.',{tb:110});
}
var n=rnd(2,5),A2=(-2*n)+' < k < '+(2*n);
return amQ('a1.always:m','Find the range of values of k for which x² + kx + '+(n*n)+' is positive for all real values of x.',A2,['k < '+(-2*n)+' or k > '+(2*n),(-n)+' < k < '+n,'k > '+(2*n),(-2*n)+' ≤ k ≤ '+(2*n)],
'No real roots: k² − 4(1)('+(n*n)+') < 0, so k² < '+(4*n*n)+', which gives '+A2+'.',{tb:110});
});
AMgen('a1','axis','Axis of symmetry','A1 (graph of a quadratic; G3 p.16, G2 p.22)',function(){
if(Math.random()<.6){var a=pick([1,2,3,-1,-2]),h=rnd(-6,7),b=-2*a*h,c=rnd(-9,9);
return amQ('a1.axis:eq','The axis of symmetry of the graph of y = '+amPoly([a,b,c])+' is the line x = k. Find the value of k.',h,[-h,2*h,b,a*h,c],'The axis of symmetry is x = −b/(2a) = −('+b+')/(2 × '+a+') = '+h+'.');}
var p=rnd(-6,3),q=p+rnd(1,5)*2;
return amQ('a1.axis:roots','The graph of a quadratic function crosses the x axis at x = '+p+' and x = '+q+'. The axis of symmetry is the line x = k. Find the value of k.',(p+q)/2,[p+q,(q-p)/2,-(p+q)/2,q-p],'The axis of symmetry is halfway between the roots: ('+p+' + '+q+')/2 = '+((p+q)/2)+'.');
});
AMgen('a1','model','Quadratic models (word problems)','A1 1.3 (G3 p.16, G2 p.22)',function(){
if(Math.random()<.5){var a=pick([1,2]),h=rnd(8,20),c=rnd(5,12)*10*a,P=a*h*h-c,ask=pick(['P','x']);
var st='A hawker sells x boxes of nasi lemak in one day. Her profit, P dollars, is given by P = '+amPoly([-a,2*a*h,-c],'x')+'. ';
if(ask==='P')return amQ('a1.model:profitP',st+'Find the greatest profit she can make in one day.',P,[a*h*h+c,P+c,h,2*a*h*h-c,-c],'Complete the square: P = −'+(a>1?a:'')+'(x − '+h+')² + '+P+'. The greatest profit is $'+P+', when x = '+h+'.',{tb:110});
return amQ('a1.model:profitX',st+'Find the number of boxes that gives the greatest profit.',h,[-h,2*h,P,a*h,h+a],'P = −'+(a>1?a:'')+'(x − '+h+')² + '+P+'. The greatest value is when x = '+h+'.',{tb:110});}
var m=rnd(1,4),c2=rnd(2,12),H=5*m*m+c2,ask2=pick(['H','t']);
var st2='A ball is thrown upwards from a balcony. Its height h metres after t seconds is given by h = '+amPoly([-5,10*m,c2],'t')+'. ';
if(ask2==='H')return amQ('a1.model:ballH',st2+'Find the greatest height of the ball.',H,[c2,H+c2,5*m*m,H-c2,10*m],'h = −5(t − '+m+')² + '+H+'. The greatest height is '+H+' m, at t = '+m+'.',{tb:110});
return amQ('a1.model:ballT',st2+'Find the time at which the ball is at its highest.',m,[-m,2*m,10*m,H,m+1],'h = −5(t − '+m+')² + '+H+'. The highest point is at t = '+m+' seconds.',{tb:110});
});
/* ============ A2 Equations and inequalities ============ */
function amSet(op,p,q){var out=(op==='>'||op==='≥'); if(out)return 'x '+(op==='>'?'<':'≤')+' '+p+' or x '+(op==='>'?'>':'≥')+' '+q; return p+' '+(op==='<'?'<':'≤')+' x '+(op==='<'?'<':'≤')+' '+q;}
AMgen('a2','ineq','Quadratic inequalities','A2 2.3 (G3 p.16, G2 p.22)',function(){
var p=rnd(-7,6),q=p+rnd(1,8),a=pick([1,1,1,2]),op=pick(['>','<','≥','≤']),form=pick(['std','re']);
var b=-a*(p+q),c=a*p*q, E=form==='std'?amPoly([a,b,c])+' '+op+' 0':amPoly([a,b,0])+' '+op+' '+(-c);
var flip={'>':'≤','<':'≥','≥':'<','≤':'>'},same={'>':'≥','<':'≤','≥':'>','≤':'<'};
var A=amSet(op,p,q), comp=amSet(flip[op],p,q), neg=amSet(op,-q,-p), strict=amSet(same[op],p,q);
var one=(op==='>'||op==='≥')?'x '+op+' '+q:'x '+op+' '+q;
return amQ('a2.ineq:'+form,'Solve '+E+'.',A,[comp,neg,strict,one],
(form==='re'?'Rearrange: '+amPoly([a,b,c])+' '+op+' 0. ':'')+'Factorise: '+(a>1?a:'')+'(x '+amSgn(-p)+')(x '+amSgn(-q)+') '+op+' 0. The roots are x = '+p+' and x = '+q+'. The curve opens upward, so it is '+((op==='>'||op==='≥')?'above the x axis outside the roots':'below the x axis between the roots')+': '+A+'.',{tb:110});
});
AMgen('a2','ineqint','Integers satisfying an inequality','A2 2.3 (G3 p.16, G2 p.22)',function(){
var p=rnd(-7,3),q=p+rnd(2,9),strict=Math.random()<.5, cnt=strict?q-p-1:q-p+1, op=strict?'<':'≤';
return amQ('a2.ineqint','How many integers x satisfy '+amPoly([1,-(p+q),p*q])+' '+op+' 0?',cnt,[q-p,q-p+1,q-p-1,q-p-2,cnt+2],
'Roots: x = '+p+' and x = '+q+'. The inequality holds '+(strict?'for '+p+' < x < '+q+', so the integers are '+(p+1)+' to '+(q-1)+'.':'for '+p+' ≤ x ≤ '+q+', so the integers are '+p+' to '+q+'.')+' That is '+cnt+' integers.',{min:0,tb:100});
});
AMgen('a2','simul','Simultaneous equations (one linear)','A2 2.2 (G3 p.16, G2 p.22)',function(){
if(Math.random()<.75){
var p=rnd(-5,3),q=p+rnd(1,6),m=rnd(-3,3),n=rnd(-6,6),u=m-(p+q),v=n+p*q, ask=pick(['xmax','xmin','ysum']);
var curve='y = '+amPoly([1,u,v]), lineR=m?amPoly([m,n]):String(n), line='y = '+lineR, yp=m*p+n, yq=m*q+n;
var st='The line '+line+' meets the curve '+curve+' at two points. ', sol='Equate: '+amPoly([1,u,v])+' = '+lineR+', which gives '+amPoly([1,-(p+q),p*q])+' = 0, so x = '+p+' or x = '+q+'. ';
if(ask==='xmax')return amQ('a2.simul:lc:xmax',st+'Find the larger value of x at these points.',q,[p,-q,-p,q+1,yq],sol+'The larger is '+q+'.',{tb:120});
if(ask==='xmin')return amQ('a2.simul:lc:xmin',st+'Find the smaller value of x at these points.',p,[q,-p,-q,p-1,yp],sol+'The smaller is '+p+'.',{tb:120});
return amQ('a2.simul:lc:ysum',st+'Find the sum of the two y-coordinates of these points.',yp+yq,[p+q,yp*yq,yp+yq+1,m*(p+q),yp-yq],sol+'Put these into '+line+': y = '+yp+' and y = '+yq+'. Sum = '+(yp+yq)+'.',{tb:120});
}
var p2=amNZ(-6,6),q2=amNZ(-6,6); if(p2===q2)throw new Error('amretry'); var s=p2+q2,t=p2*q2;
return amQ('a2.simul:xy','Solve the simultaneous equations x + y = '+s+' and xy = '+t+'. Find the larger value of x.',Math.max(p2,q2),[Math.min(p2,q2),-Math.max(p2,q2),s,t],
'From the first equation y = '+s+' − x. Then x('+s+' − x) = '+t+', so '+amPoly([1,-s,t])+' = 0, which factorises to (x '+amSgn(-p2)+')(x '+amSgn(-q2)+') = 0. The larger value is '+Math.max(p2,q2)+'.',{tb:120});
});
AMgen('a2','tangent','Tangent to a curve','A2 2.1 (G3 p.16, G2 p.22)',function(){
if(Math.random()<.6){
var u=rnd(-5,5),m=rnd(-4,4),t=0; while(((u-m)%2!==0)&&t<50){m=rnd(-4,4); t++;} if((u-m)%2!==0)throw new Error('amretry');
var v=rnd(-6,8),h=(u-m)/2,k=v-h*h;
return amQ('a2.tangent:k','The line y = '+(m?amPoly([m,'k']):'k')+' is a tangent to the curve y = '+amPoly([1,u,v])+'. Find the value of k.',k,[v+h*h,v-(u-m)*(u-m),h*h-v,v-h,v+k],
'Equate: '+amPoly([1,u,v])+' = '+(m?amPoly([m,'k']):'k')+', so '+amPoly([1,u-m,v])+' − k = 0. A tangent means one repeated root, so the discriminant is 0: ('+(u-m)+')² − 4('+v+' − k) = 0, giving k = '+k+'.',{tb:120});
}
var a=pick([1,2,-1,-2]),h2=rnd(-4,4),k0=amNZ(-8,8),b=-2*a*h2,c=a*h2*h2+k0,up=a>0;
var A=up?'k > '+k0:'k < '+k0, wr=up?['k < '+k0,'k = '+k0,'k > '+(-k0),'k ≥ '+k0]:['k > '+k0,'k = '+k0,'k < '+(-k0),'k ≤ '+k0];
return amQ('a2.tangent:range','The line y = k meets the curve y = '+amPoly([a,b,c])+' at two different points. Find the range of values of k.',A,wr,
'Equate: '+amPoly([a,b,c])+' = k, so '+amPoly([a,b,c])+' − k = 0. Two different roots need discriminant > 0: ('+b+')² − 4('+a+')('+c+' − k) > 0, which gives '+A+'. (The vertex of the curve is at y = '+k0+'.)',{tb:120});
});
AMgen('a2','count','Line and curve: how many points','A2 2.1 (G3 p.16, G2 p.22)',function(){
var u=rnd(-4,4),m=rnd(-3,3),v=rnd(-5,5),n=rnd(-5,5),D=(u-m)*(u-m)-4*(v-n),A=D>0?2:D===0?1:0;
return amQ('a2.count','How many points of intersection are there between the line y = '+(m?amPoly([m,n]):String(n))+' and the curve y = '+amPoly([1,u,v])+'?',A,['0','1','2','3'],
'Equate: '+amPoly([1,u-m,v-n])+' = 0. Discriminant = ('+(u-m)+')² − 4('+(v-n)+') = '+D+'. '+(D>0?'It is positive: 2 points.':D===0?'It is zero: 1 point (the line is a tangent).':'It is negative: no points.'),{min:0,tb:100});
});
/* ============ A3 Surds ============ */
AMgen('a3','simp','Simplify a surd','A3 3.1 (G3 p.16, G2 p.22)',function(){
var m=amSqf(),k=rnd(2,9),n=k*k*m,ask=pick(['a','b']),d=[2,3,4,5].filter(function(x){return k%x===0;}), sm=d.length?k/d[0]:k+1;
if(ask==='a')return amQ('a3.simp:a','Express √'+n+' in the form a√b, where a and b are positive integers and b is as small as possible. Find the value of a.',k,[m,k*k,sm,k+1,k-1],
n+' = '+(k*k)+' × '+m+', and '+(k*k)+' is a perfect square. √'+n+' = '+k+'√'+m+', so a = '+k+'.');
return amQ('a3.simp:b','Express √'+n+' in the form a√b, where a and b are positive integers and b is as small as possible. Find the value of b.',m,[k,k*k,m+1,m+2,n],
n+' = '+(k*k)+' × '+m+'. √'+n+' = '+k+'√'+m+', so b = '+m+'.');
});
function amSurdTerm(c,m){var ab=Math.abs(c); return (c<0?'-':'')+(ab===1?'':String(ab))+'√'+m;}
function amSurd(a,b,m){var s=''; if(a)s=String(a); if(b){var ab=Math.abs(b),t=(ab===1?'':String(ab))+'√'+m; if(!s)s=(b<0?'-':'')+t; else s+=(b<0?' - ':' + ')+t;} return s||'0';}
AMgen('a3','ops','Add and subtract surds','A3 3.1 (G3 p.16, G2 p.22)',function(){
var m=amSqf(),r=rnd(1,4),s=rnd(1,4),p=rnd(1,4),q=rnd(1,4),sg=pick(['+','−']),a=r*r*m,b=s*s*m,co=sg==='+'?p*r+q*s:p*r-q*s,sc=sg==='+'?p+q:p-q;
if(co===0||sc===0||a===b)throw new Error('amretry');
var wr=[amSurdTerm(sc,m),sc+'√'+(sg==='+'?a+b:a-b),amSurdTerm(p*r*q*s,m),amSurdTerm(co+1,m),amSurdTerm(-co,m)];
return amQ('a3.ops','Simplify '+(p===1?'':p)+'√'+a+' '+sg+' '+(q===1?'':q)+'√'+b+'.',amSurdTerm(co,m),wr,
'√'+a+' = '+r+'√'+m+' and √'+b+' = '+s+'√'+m+'. So '+p+'('+r+'√'+m+') '+sg+' '+q+'('+s+'√'+m+') = '+(p*r)+'√'+m+' '+sg+' '+(q*s)+'√'+m+' = '+amSurdTerm(co,m)+'.',{tb:100});
});
AMgen('a3','rat','Rationalise the denominator','A3 3.1 (G3 p.16, G2 p.22)',function(){
var m=pick([2,3,5,6,7]),c=rnd(1,3),d=m-c*c; if(d===0)throw new Error('amretry');
var kind=pick(['plus','minus','cminus']),t=amNZ(-4,4),n,den,a,b=t;
if(kind==='plus'){n=t*d; den='√'+m+' + '+c; a=-t*c;} else if(kind==='minus'){n=t*d; den='√'+m+' − '+c; a=t*c;} else {n=t*(c*c-m); den=c+' − √'+m; a=t*c;}
if(n===0||Math.abs(n)>60)throw new Error('amretry');
var sub=pick(['mc','sum']), A=amSurd(a,b,m), conj=kind==='plus'?'(√'+m+' − '+c+')':kind==='minus'?'(√'+m+' + '+c+')':'('+c+' + √'+m+')';
var ex='Multiply the top and bottom by the conjugate '+conj+'. The bottom becomes '+(kind==='cminus'?c+'² − '+m:m+' − '+c+'²')+' = '+(kind==='cminus'?c*c-m:d)+'. The result is '+A+'.';
if(sub==='mc')return amQ('a3.rat:mc','Rationalise the denominator of '+n+'/('+den+'). Give your answer in the form a + b√'+m+'.',A,[amSurd(-a,b,m),amSurd(a,-b,m),amSurd(-a,-b,m),amSurd(a,b+1,m)],ex,{tb:120});
return amQ('a3.rat:sum','Given that '+n+'/('+den+') = a + b√'+m+', where a and b are integers, find the value of a + b.',a+b,[a-b,-a-b,a*b,a+b+1,b],ex,{tb:120});
});
AMgen('a3','eqp','Equate rational and surd parts','A3 3.1 (G3 p.16, G2 p.22)',function(){
var m=pick([2,3,5,7]),sub=pick(['sq','prod']),ask=pick(['p','q']),p,q;
if(sub==='sq'){var a=amNZ(-4,4),b=amNZ(-3,3); p=a*a+b*b*m; q=2*a*b;
return amQ('a3.eqp:sq:'+ask,'Given that ('+amSurd(a,b,m)+')² = p + q√'+m+', where p and q are integers, find the value of '+ask+'.',ask==='p'?p:q,ask==='p'?[a*a+b*b,a*a-b*b*m,q,a*a+b*b*m+1]:[a*b,2*a*b*m,p,-q],
'Expand: ('+a+')² + 2('+a+')('+b+'√'+m+') + ('+b+'√'+m+')² = '+(a*a)+' + '+(2*a*b)+'√'+m+' + '+(b*b*m)+' = '+p+' + '+q+'√'+m+'.',{tb:100});}
var a1=amNZ(-4,4),b1=amNZ(-3,3),c1=amNZ(-4,4),d1=amNZ(-3,3); p=a1*c1+b1*d1*m; q=a1*d1+b1*c1;
return amQ('a3.eqp:prod:'+ask,'Given that ('+amSurd(a1,b1,m)+')('+amSurd(c1,d1,m)+') = p + q√'+m+', where p and q are integers, find the value of '+ask+'.',ask==='p'?p:q,ask==='p'?[a1*c1+b1*d1,a1*c1-b1*d1*m,q,p+1]:[a1*d1-b1*c1,a1*d1*b1*c1,p,-q],
'Expand: '+(a1*c1)+' + '+(a1*d1)+'√'+m+' + '+(b1*c1)+'√'+m+' + '+(b1*d1)+'(√'+m+')². The rational part is '+(a1*c1)+' + '+(b1*d1*m)+' = '+p+' and the surd part is ('+(a1*d1)+' + '+(b1*c1)+')√'+m+' = '+q+'√'+m+'.',{tb:100});
});
AMgen('a3','eq','Equation with a square root','A3 3.2 (G3 p.16, G2 p.22)',function(){
var b=rnd(-3,4),r=b+rnd(2,6),s=2*b+1-r,a=b*b-r*s;
if(s>=b)throw new Error('amretry');
var rad=a===0?'√x':'√(x '+amSgn(a)+')', rhs=b===0?'x':'x '+amSgn(-b);
return amQ('a3.eq','Solve the equation '+rad+' = '+rhs+'.',r,[s,r+1,b,-r,r-1],
'Square both sides: x '+amSgn(a)+' = ('+rhs+')², so '+amPoly([1,-(2*b+1),b*b-a])+' = 0, giving x = '+s+' or x = '+r+'. Check: x = '+s+' makes the right side '+(s-b)+', which is negative, but a square root cannot be negative. So only x = '+r+' works.',{tb:120});
});
/* ============ A4 Polynomials and partial fractions ============ */
AMgen('a4','rem','Remainder theorem','A4 4.2 (G3 p.16, G2 p.22)',function(){
var u=rnd(-5,5),v=rnd(-6,6),R=amNZ(-9,9);
if(Math.random()<.6){var r=amNZ(-4,4),f=amAddP(amMul([1,-r],[1,u,v]),[R]);
return amQ('a4.rem:lin','Find the remainder when '+amPoly(f)+' is divided by '+amXm(r)+'.',R,[amEv(f,-r),f[f.length-1],amEv(f,r+1),-R,R+r],
'Remainder theorem: put x = '+r+'. f('+r+') = '+R+'. The remainder is '+R+'.',{tb:100});}
var a=pick([2,3]),b=amNZ(-5,5),f2=amAddP(amMul([a,b],[1,u,v]),[R]), x0=(b%a===0)?String(-b/a):(-b)+'/'+a;
return amQ('a4.rem:ax','Find the remainder when '+amPoly(f2)+' is divided by ('+a+'x '+amSgn(b)+').',R,[amEv(f2,-b),amEv(f2,b),f2[f2.length-1],-R,R+a],
'Remainder theorem: put '+a+'x '+amSgn(b)+' = 0, so x = '+x0+'. Then f('+x0+') = '+R+'. The remainder is '+R+'.',{tb:110});
});
AMgen('a4','fac','Factor theorem: find a constant','A4 4.2 (G3 p.16, G2 p.22)',function(){
var r=amNZ(-4,4),p=rnd(-5,5),k=amNZ(-8,8),m=-(r*r*r+p*r*r+k*r),ask=pick(['k','m']);
if(m===0)throw new Error('amretry');
if(ask==='k'){var A=r*r*r+p*r*r+m, k2=(-r*r*r+p*r*r+m)/r;
return amQ('a4.fac:k','Given that '+amXm(r)+' is a factor of '+amPoly([1,p,'k',m])+', find the value of k.',k,[-k,(k2===Math.round(k2)?k2:null),k+p,k-r,k+1],
'Factor theorem: f('+r+') = 0. So ('+r+')³ + ('+p+')('+r+')² + k('+r+') + ('+m+') = 0, that is '+A+' + '+r+'k = 0, so k = '+k+'.',{tb:110});}
return amQ('a4.fac:m','Given that '+amXm(r)+' is a factor of '+amPoly([1,p,k,'m'])+', find the value of m.',m,[-m,r*r*r-p*r*r+k*r,m+r,m-k,m+1],
'Factor theorem: f('+r+') = 0. So ('+r+')³ + ('+p+')('+r+')² + ('+k+')('+r+') + m = 0, that is '+(r*r*r+p*r*r+k*r)+' + m = 0, so m = '+m+'.',{tb:110});
});
AMgen('a4','cubic','Solve a cubic using the factor theorem','A4 4.2 (G3 p.16, G2 p.22)',function(){
var rs=[rnd(-4,5),rnd(-4,5),rnd(-4,5)],g=pick(rs),i=rs.indexOf(g),oth=rs.filter(function(x,j){return j!==i;}),f=amMul(amMul([1,-rs[0]],[1,-rs[1]]),[1,-rs[2]]),ask=pick(['largest','smallest']);
var sorted=rs.slice().sort(function(a,b){return a-b;}), ans=ask==='largest'?sorted[2]:sorted[0], other=ask==='largest'?sorted[0]:sorted[2];
if(new Set(rs).size<2)throw new Error('amretry');
var quad=amMul([1,-oth[0]],[1,-oth[1]]);
return amQ('a4.cubic:'+ask,'Given that '+amXm(g)+' is a factor of f(x) = '+amPoly(f)+', solve f(x) = 0 and state the '+ask+' root.',ans,[other,g,-ans,rs[0]+rs[1]+rs[2],ans+1],
'Divide f(x) by '+amXm(g)+' to get '+amPoly(quad)+', which factorises to (x '+amSgn(-oth[0])+')(x '+amSgn(-oth[1])+'). The roots are '+sorted.join(', ')+'. The '+ask+' is '+ans+'.',{tb:120});
});
AMgen('a4','div','Division of polynomials','A4 4.1 (G3 p.16, G2 p.22)',function(){
if(Math.random()<.65){
var r=amNZ(-4,4),u=rnd(-5,5),v=rnd(-6,6),R=rnd(-9,9),f=amAddP(amMul([1,-r],[1,u,v]),[R]),ask=pick(['a','b','c']),ans=ask==='a'?u:ask==='b'?v:R;
var wr=ask==='a'?[f[1],f[1]-r,u-1,-u]:ask==='b'?[f[2],f[2]-r*u,v+1,-v]:[amEv(f,-r),f[3],R+1,-R];
return amQ('a4.div:lin:'+ask,'When '+amPoly(f)+' is divided by '+amXm(r)+', the quotient is x² + ax + b and the remainder is c. Find the value of '+ask+'.',ans,wr,
'Long division (or synthetic division with '+r+') gives '+amPoly(f)+' = '+amXm(r)+'('+amPoly([1,u,v])+') '+amSgn(R)+'. So a = '+u+', b = '+v+' and c = '+R+'.',{tb:120});
}
var c0=rnd(1,4),d=rnd(-5,5),p=amNZ(-6,6),q=amNZ(-6,6),f2=[1,d,c0+p,c0*d+q],ask2=pick(['p','q']);
return amQ('a4.div:quad:'+ask2,'When '+amPoly(f2)+' is divided by x² + '+c0+', the remainder is px + q. Find the value of '+ask2+'.',ask2==='p'?p:q,ask2==='p'?[c0+p,-p,p+1,p-c0]:[c0*d+q,q-c0*d,-q,q+1],
'The quotient is x '+amSgn(d)+', since (x² + '+c0+')(x '+amSgn(d)+') = '+amPoly([1,d,c0,c0*d])+'. Subtract this from '+amPoly(f2)+' to get the remainder '+amPoly([p,q])+'. So '+ask2+' = '+(ask2==='p'?p:q)+'.',{tb:130});
});
AMgen('a4','cubes','Sum and difference of two cubes','A4 4.3 (G3 p.16, G2 p.22)',function(){
var a=pick([1,1,2,3]),b=rnd(1,5),plus=Math.random()<.5,s=plus?'+':'−',sg=plus?'−':'+';
var ax=(a===1?'':a)+'x', A2=(a*a===1?'':a*a), AB=(a*b===1?'':a*b), B2=b*b, e=amPoly([a*a*a,0,0,plus?b*b*b:-b*b*b]);
if(Math.random()<.65){
var good='('+ax+' '+s+' '+b+')('+A2+'x² '+sg+' '+AB+'x + '+B2+')';
var w1='('+ax+' '+s+' '+b+')('+A2+'x² '+s+' '+AB+'x + '+B2+')', w2='('+ax+' '+sg+' '+b+')('+A2+'x² '+sg+' '+AB+'x + '+B2+')', w3='('+ax+' '+s+' '+b+')('+A2+'x² '+sg+' '+(2*a*b)+'x + '+B2+')', w4='('+ax+' '+s+' '+b+')('+ax+' '+s+' '+b+')('+ax+' '+sg+' '+b+')';
return amQ('a4.cubes:fac','Factorise '+e+' using a³ '+s+' b³ = (a '+s+' b)(a² '+sg+' ab + b²).',good,[w1,w2,w3,w4],
e+' = ('+ax+')³ '+s+' '+b+'³, so a = '+ax+' and b = '+b+'. Then (a '+s+' b)(a² '+sg+' ab + b²) = '+good+'.',{tb:120});
}
var bb=rnd(2,6),ps=Math.random()<.5;
return amQ('a4.cubes:p','Given that x³ '+(ps?'+':'−')+' '+(bb*bb*bb)+' = (x '+(ps?'+':'−')+' '+bb+')(x² + px + '+(bb*bb)+'), find the value of p.',ps?-bb:bb,[ps?bb:-bb,bb*bb,-bb*bb,2*bb],
'Use a³ '+(ps?'+':'−')+' b³ = (a '+(ps?'+':'−')+' b)(a² '+(ps?'−':'+')+' ab + b²) with a = x and b = '+bb+'. The middle term is '+(ps?'−':'+')+bb+'x, so p = '+(ps?-bb:bb)+'.',{tb:110});
});
function amLinF(c,d){return '('+(c===1?'':c)+'x '+amSgn(d)+')';}
AMgen('a4','pf','Partial fractions','A4 4.4 (G3 p.16, G2 p.22)',function(){
var kind=pick(['two','two','rep','quad']);
if(kind==='two'){
var c1=pick([1,1,2]),d1=rnd(-5,5),c2=1,d2=rnd(-5,5),A=amNZ(-5,5),B=amNZ(-5,5);
if(-d1/c1===-d2/c2)throw new Error('amretry');
var N=amAddP(amMul([A],[c2,d2]),amMul([B],[c1,d1])), ask=pick(['A','B']), x1=ask==='A'?amFr(-d1,c1):amFr(-d2,c2);
if(N.every(function(z){return z===0;}))throw new Error('amretry');
var den='('+amLinF(c1,d1)+amLinF(c2,d2)+')';
return amQ('a4.pf:two:'+ask,'Express ('+amPoly(N)+')/'+den+' in the form A/'+amLinF(c1,d1)+' + B/'+amLinF(c2,d2)+'. Find the value of '+ask+'.',ask==='A'?A:B,ask==='A'?[B,-A,A+B,A+1]:[A,-B,A+B,B+1],
'Write '+amPoly(N)+' = A'+amLinF(c2,d2)+' + B'+amLinF(c1,d1)+'. Put x = '+amFS(x1)+' so that the bracket for the other letter becomes 0. This leaves '+ask+' by itself, and gives '+ask+' = '+(ask==='A'?A:B)+'.',{tb:140});
}
if(kind==='rep'){
var a=rnd(-4,4),b=rnd(-4,4),A2=amNZ(-4,4),B2=rnd(-4,4),C2=amNZ(-5,5); if(a===b)throw new Error('amretry');
var N2=amAddP(amAddP(amMul([A2],amMul([1,-b],[1,-b])),amMul([B2],amMul([1,-a],[1,-b]))),amMul([C2],[1,-a])), ask2=pick(['A','C']), at=ask2==='A'?a:b;
if(N2.every(function(z){return z===0;}))throw new Error('amretry');
return amQ('a4.pf:rep:'+ask2,'Express ('+amPoly(N2)+')/('+amXm(a)+amXm(b)+'²) in the form A/'+amXm(a)+' + B/'+amXm(b)+' + C/'+amXm(b)+'². Find the value of '+ask2+'.',ask2==='A'?A2:C2,ask2==='A'?[B2,C2,-A2,A2+1]:[A2,B2,-C2,C2+1],
'Write '+amPoly(N2)+' = A'+amXm(b)+'² + B'+amXm(a)+amXm(b)+' + C'+amXm(a)+'. Put x = '+at+' to remove the other terms. This gives '+ask2+' = '+(ask2==='A'?A2:C2)+'.',{tb:150});
}
var a3=rnd(-4,4),c=rnd(1,3),A3=amNZ(-4,4),B3=rnd(-4,4),C3=rnd(-4,4);
var N3=amAddP(amMul([A3],[1,0,c*c]),amMul([B3,C3],[1,-a3]));
return amQ('a4.pf:quad','Express ('+amPoly(N3)+')/('+amXm(a3)+'(x² + '+(c*c)+')) in the form A/'+amXm(a3)+' + (Bx + C)/(x² + '+(c*c)+'). Find the value of A.',A3,[B3,C3,-A3,A3+1],
'Write '+amPoly(N3)+' = A(x² + '+(c*c)+') + (Bx + C)'+amXm(a3)+'. Put x = '+a3+' so that the last term is 0: the left side is '+amEv(N3,a3)+' and the right side is A('+(a3*a3+c*c)+'). So A = '+A3+'.',{tb:150});
});
AMgen('a4','exp','Multiply polynomials','A4 4.1 (G3 p.16, G2 p.22)',function(){
var p=pick([1,2,3,-1,-2]),q=amNZ(-5,5),r=pick([1,2,-1]),s=rnd(-5,5),t=amNZ(-6,6),prod=amMul([p,q],[r,s,t]),k=pick([1,2]),ans=prod[prod.length-1-k];
var wr=k===2?[p*s,q*r,p*r,q*s+p*t,-ans]:[p*t,q*s,q*t,p*s+q*t,-ans];
return amQ('a4.exp:'+k,'Expand ('+amPoly([p,q])+')('+amPoly([r,s,t])+'). Find the coefficient of '+(k===1?'x':'x²')+'.',ans,wr,
'Multiply each term: the full expansion is '+amPoly(prod)+'. The coefficient of '+(k===1?'x':'x²')+' is '+ans+'.',{tb:100});
});
/* ============ A5 Binomial expansions (G3 only at Sec 3-4) ============ */
AMgen('a5','coef','Coefficient in a binomial expansion','A5 5.1 to 5.3 (G3 p.16)',function(){
var n=rnd(4,7),k=rnd(1,Math.min(4,n)),a=pick([1,1,2,3,-1,-2]),b=pick([1,2,3,-1,-2,-3]),ans=amC(n,k)*amPow(a,n-k)*amPow(b,k);
if(String(ans).length>7)throw new Error('amretry');
return amQ('a5.coef','Find the coefficient of x'+(k>1?sup(k):'')+' in the expansion of ('+amAsc(a,b)+')'+sup(n)+' in ascending powers of x.',ans,[amC(n,k)*amPow(a,n-k),amC(n,k)*amPow(b,k),amC(n,k)*amPow(a,k)*amPow(b,n-k),-ans,amC(n,k-1)*amPow(a,n-k+1)*amPow(b,k-1)],
'The term in x'+(k>1?sup(k):'')+' is C('+n+', '+k+') × ('+a+')'+sup(n-k)+' × ('+b+'x)'+sup(k)+'. Multiply the numbers: '+amC(n,k)+' × '+amPow(a,n-k)+' × '+amPow(b,k)+' = '+ans+'. The coefficient is '+ans+'.',{tb:110});
});
AMgen('a5','ncr','Binomial coefficients and factorials','A5 5.2 (G3 p.16)',function(){
var sub=pick(['val','n','fact']);
if(sub==='val'){var n=rnd(5,10),r=rnd(2,4),v=amC(n,r);
return amQ('a5.ncr:val','Find the value of C('+n+', '+r+'), where C(n, r) = n!/(r!(n − r)!).',v,[(function(){var P=1,i; for(i=0;i=2&&T*z/x<=200;}); if(!ds.length)throw new Error('amretry');
var y=pick(ds),x=T*z/y, L=amLog(b); if(x===z||y===z||x<3||y<3||z<3)throw new Error('amretry');
return amQ('a6.laws:three','Evaluate '+L+x+' + '+L+y+' − '+L+z+'.',k,[k+1,k-1,T,x+y-z,2*k],
'Add the logs of x and y by multiplying, subtract by dividing: '+L+'('+x+' × '+y+' ÷ '+z+') = '+L+T+' = '+k+', since '+b+sup(k)+' = '+T+'.',{tb:100});
}
k=pick([1,2]); var u=rnd(2,5),x2=b*u,y2=amPow(b,2-k)*u*u,L2=amLog(b);
return amQ('a6.laws:two','Evaluate 2 '+L2+x2+' − '+L2+y2+'.',k,[k+1,k-1,2*k,x2*x2/y2,0],
'2 '+L2+x2+' = '+L2+(x2*x2)+'. Then '+L2+(x2*x2)+' − '+L2+y2+' = '+L2+(x2*x2/y2)+' = '+k+', since '+b+sup(k)+' = '+(x2*x2/y2)+'.',{tb:100});
});
function amLC(i,j,sgn){ /* i p +/- j q */
var t1=i===1?'p':i+'p', t2=j===1?'q':j+'q'; return t1+' '+sgn+' '+t2;}
AMgen('a6','express','Express a log in terms of others','A6 6.1 (G3 pp.16-17)',function(){
var i=rnd(1,3),j=rnd(1,3),div=Math.random()<.4,n=div?(amPow(2,i)+'/'+amPow(3,j)):String(amPow(2,i)*amPow(3,j));
var good=amLC(i,j,div?'−':'+'), wr=[amLC(j,i,div?'−':'+'),amLC(i,j,div?'+':'−'),(i*j)+'pq',amLC(i+j,i+j,div?'−':'+')];
return amQ('a6.express:'+(div?'div':'mul'),'Given that log_a 2 = p and log_a 3 = q, express log_a '+(div?'('+n+')':n)+' in terms of p and q.',good,wr,
(div?amPow(2,i)+'/'+amPow(3,j)+' = 2'+(i>1?sup(i):'')+' ÷ 3'+(j>1?sup(j):''):n+' = 2'+(i>1?sup(i):'')+' × 3'+(j>1?sup(j):''))+', so the log is '+i+' log_a 2 '+(div?'−':'+')+' '+j+' log_a 3 = '+good+'.',{tb:110});
});
function amSF(x){return String(Number(x.toPrecision(3)));}
AMgen('a6','solve','Solve an exponential equation','A6 6.2 (G3 pp.16-17)',function(){
var sub=pick(['pw','shift','coef']),a=pick([2,3,5,7,10]),b=rnd(5,200),c=rnd(1,4),kk=pick([2,3,5]),x,txt,why,wr;
var la=Math.log(a),lb=Math.log(b);
if(sub==='pw'){x=lb/la; txt=a+'^x = '+b; why='Take logs: x lg '+a+' = lg '+b+', so x = lg '+b+' / lg '+a+' = '+amSF(x)+'.'; wr=[la/lb,b/a,lb/a,(b-1)/la];}
else if(sub==='shift'){x=lb/la-c; txt=a+'^(x + '+c+') = '+b; why='Take logs: (x + '+c+') lg '+a+' = lg '+b+', so x + '+c+' = '+amSF(lb/la)+' and x = '+amSF(x)+'.'; wr=[lb/la,lb/la+c,lb/(la+c),(b-c)/la];}
else {var tot=kk*b; x=Math.log(tot/kk)/la; txt=kk+' × '+a+'^x = '+tot; why='Divide by '+kk+': '+a+'^x = '+b+'. Take logs: x = lg '+b+' / lg '+a+' = '+amSF(x)+'.'; wr=[Math.log(tot)/la,tot/a,lb/la+kk,Math.log(tot)/(kk*la)];}
if(Math.abs(x)<0.1||Math.abs(x)>=100||Math.abs(x-Math.round(x))<1e-9)throw new Error('amretry');
var step=Math.pow(10,Math.floor(Math.log10(Math.abs(x)))-2), x3=Number(x.toPrecision(3)); if(Math.abs(x-x3)>0.4*step)throw new Error('amretry');
return amQ('a6.solve:'+sub,'Solve '+txt+'. Give your answer correct to 3 significant figures.',amSF(x),wr.map(function(v){return isFinite(v)?amSF(v):null;}),why,{tb:120});
});
AMgen('a6','logeq','Solve a logarithmic equation','A6 6.2 (G3 pp.16-17)',function(){
if(Math.random()<.5){
var a=pick([2,3,5]),d=rnd(2,4),c=amNZ(-5,8),T=amPow(a,d); if(T>130)throw new Error('amretry'); var x=T-c, L=amLog(a);
return amQ('a6.logeq:simple','Solve '+L+'(x '+amSgn(c)+') = '+d+'.',x,[d-c,T+c,x+1,a*d-c,-x],
'Change to index form: x '+amSgn(c)+' = '+a+sup(d)+' = '+T+'. So x = '+T+(c<0?' + '+(-c):' − '+c)+' = '+x+'.',{tb:100});
}
var b=pick([2,3,5]),d2=rnd(2,3),T2=amPow(b,d2),fs=[],i; for(i=2;i<=T2;i++)if(T2%i===0&&T2/i130||X>300)throw new Error('amretry');
var f=amFr(n,m),L='log'+amSub(B)+' ';
return amQ('a6.chbase:pow','Evaluate '+L+X+'.',amFS(f),[amFS(amFr(m,n)),amFS(amFr(n-m,1)),amFS(amFr(n*m,1)),amFS(amFr(n+1,m)),amFS(amFr(n,m+1))],
(m===1?'Write '+X+' as a power of '+a+': '+X+' = '+a+sup(n)+'. So '+L+X+' = '+n+'.':'Write both numbers as powers of '+a+': '+B+' = '+a+sup(m)+' and '+X+' = '+a+sup(n)+'. Let '+L+X+' = t, so '+B+'^t = '+X+'. Then '+a+'^('+m+'t) = '+a+sup(n)+', so '+m+'t = '+n+' and t = '+amFS(f)+'.'),{tb:110});
}
var r=rnd(2,9),L1=pick([['p','q'],['a','b'],['x','y']]);
return amQ('a6.chbase:inv','Given that log_'+L1[0]+' '+L1[1]+' = '+r+', find the value of log_'+L1[1]+' '+L1[0]+'.','1/'+r,[String(r),String(-r),'1/'+(r*r),'-1/'+r,String(r+1)],
'log_'+L1[1]+' '+L1[0]+' = 1 ÷ log_'+L1[0]+' '+L1[1]+' = 1/'+r+'.',{tb:90});
});
AMgen('a6','model','Exponential and logarithmic models','A6 6.3 (G3 pp.16-17)',function(){
var sub=pick(['ph','h','pop','half']);
if(sub==='ph'){var k=rnd(2,9),h='0.'+Array(k).join('0')+'1';
return amQ('a6.model:ph','The pH of a solution is given by pH = −lg h, where h is the concentration of hydrogen ions. Find the pH when h = '+h+'.',k,[k+1,k-1,-k,10*k],
'pH = −lg('+h+') = −lg(10'+sup(-k)+') = '+k+'.',{tb:100});}
if(sub==='h'){var k2=rnd(2,6),h2='0.'+Array(k2).join('0')+'1';
return amQ('a6.model:h','The pH of a solution is given by pH = −lg h. A solution has pH '+k2+'. Find h.',h2,['0.'+Array(k2+1).join('0')+'1','0.'+Array(k2-1).join('0')+'1',String(amPow(10,k2)),'0.'+k2],
'−lg h = '+k2+', so lg h = −'+k2+' and h = 10'+sup(-k2)+' = '+h2+'.',{tb:100});}
if(sub==='pop'){var N0=pick([50,100,200,500]),t0=rnd(2,6),N=N0*amPow(2,t0);
return amQ('a6.model:pop','The number of bacteria in a dish is N = '+N0+' × 2^t after t hours. After how many hours is N = '+N+'?',t0,[t0+1,t0-1,N/N0,N/2,2*t0],
'Divide by '+N0+': 2^t = '+(N/N0)+' = 2'+sup(t0)+'. So t = '+t0+'.',{tb:100});}
var M0=pick([5,10,20,40])*8,t1=rnd(2,5),M=M0/amPow(2,t1);
return amQ('a6.model:half','The mass of a sample is M = '+M0+' × (1/2)^t grams after t hours. After how many hours is M = '+M+'?',t1,[t1+1,t1-1,M0/M,M0-M,2*t1],
'Divide by '+M0+': (1/2)^t = '+M+'/'+M0+' = 1/'+(M0/M)+' = (1/2)'+sup(t1)+'. So t = '+t1+'.',{tb:100});
});
/* ============ G1 Trigonometry ============ */
var AMEX=[[0,'0'],[0.5,'1/2'],[Math.SQRT1_2,'√2/2'],[Math.sqrt(3)/2,'√3/2'],[1,'1'],[Math.sqrt(3)/3,'√3/3'],[Math.sqrt(3),'√3'],[Math.SQRT2,'√2'],[2,'2'],[2*Math.sqrt(3)/3,'2√3/3']];
function amExact(v){var s=v<0?-1:1,a=Math.abs(v),i; for(i=0;i1e-9?'-':'')+AMEX[i][1]; return null;}
function amRad(d){return d*Math.PI/180;}
function amFn(f,d){var r=amRad(d); return f==='sin'?Math.sin(r):f==='cos'?Math.cos(r):f==='tan'?Math.tan(r):f==='sec'?1/Math.cos(r):f==='cosec'?1/Math.sin(r):1/Math.tan(r);}
function amAng(d,rad){if(!rad)return d+'°'; if(d===0)return '0'; var f=amFr(d,180),n=f[0],q=f[1]; var s=(n===1?'':n)+'π'; return q===1?s:'('+s+'/'+q+')';}
AMgen('g1','exact','Exact values of trig functions','G1 1.1, 1.3 (G3 p.17, G2 p.22)',function(){
var fn=pick(['sin','sin','cos','cos','tan','tan','sec','cosec','cot']),degs=[0,30,45,60,90,120,135,150,180,210,225,240,270,300,315,330,360],d=pick(degs),rad=Math.random()<.35;
var v=amFn(fn,d); if(!isFinite(v)||Math.abs(v)>5)throw new Error('amretry'); var ans=amExact(v); if(ans===null)throw new Error('amretry');
var comp={sin:'cos',cos:'sin',tan:'cot',sec:'cosec',cosec:'sec',cot:'tan'}[fn], vc=amFn(comp,d), pool=['0','1/2','√2/2','√3/2','1','√3/3','√3','√2','2','2√3/3'];
var wr=[(ans==='0')?null:(ans.charAt(0)==='-'?ans.slice(1):'-'+ans), (isFinite(vc)&&amExact(vc))?amExact(vc):null];
shuffle(pool.slice()).slice(0,6).forEach(function(s){wr.push(Math.random()<.5?s:'-'+s);});
var ph=((d%360)+360)%360, ref=ph<=90?ph:ph<=180?180-ph:ph<=270?ph-180:360-ph, base={sin:'sin',cos:'cos',tan:'tan',sec:'cos',cosec:'sin',cot:'tan'}[fn];
var bv=amFn(base,ref), bs=amExact(bv);
return amQ('g1.exact:'+fn+(rad?':rad':':deg'),'Find the exact value of '+fn+' '+amAng(d,rad)+'.',ans,wr,
(d%90===0?'The angle is on an axis, so use the graph: ':'The angle in the first quadrant that matches is '+ref+'°. ')+(fn==='sec'||fn==='cosec'||fn==='cot'?fn+' x = 1 ÷ '+base+' x. ':'')+base+' '+ref+'° = '+(bs===null?'?':bs)+'. Checking the sign in the quadrant gives '+fn+' '+amAng(d,rad)+' = '+ans+'.',{tb:90});
});
function amSolDeg(f,v,mul){var out=[],d; for(d=0;d<=360;d++){var w=amFn(f,d*mul); if(isFinite(w)&&Math.abs(w-v)<1e-9)out.push(d);} return out;}
AMgen('g1','solve','Solve a trig equation','G1 1.8 (G3 p.17, G2 p.23)',function(){
var fn=pick(['sin','cos','tan']), vals=fn==='tan'?[1/Math.sqrt(3),1,Math.sqrt(3),-1/Math.sqrt(3),-1,-Math.sqrt(3)]:[0.5,Math.SQRT1_2,Math.sqrt(3)/2,-0.5,-Math.SQRT1_2,-Math.sqrt(3)/2], v=pick(vals), vs=amExact(v);
if(vs===null)throw new Error('amretry');
if(Math.random()<.65){
var sols=amSolDeg(fn,v,1); if(sols.length<2)throw new Error('amretry'); var big=Math.random()<.5,ans=big?sols[sols.length-1]:sols[0];
return amQ('g1.solve:basic:'+(big?'max':'min'),'Solve '+fn+' x = '+vs+' for 0° ≤ x ≤ 360°. Find the '+(big?'largest':'smallest')+' solution, in degrees.',ans,[big?sols[0]:sols[sols.length-1],360-ans,180-ans>0?180-ans:null,ans+180<=360?ans+180:ans+90,ans+10],
'The solutions in the range are x = '+sols.join('°, ')+'°. The '+(big?'largest':'smallest')+' is '+ans+'°.',{tb:110});
}
var sols2=amSolDeg(fn,v,2); if(sols2.length<2)throw new Error('amretry');
return amQ('g1.solve:count','How many solutions does '+fn+' 2x = '+vs+' have in the range 0° ≤ x ≤ 360°?',sols2.length,['2','4','6','8'],
'Let u = 2x, so 0° ≤ u ≤ 720°, which is two full turns. '+fn+' u = '+vs+' has '+sols2.length+' solutions for u in this range. Each u gives one x = u/2, so there are '+sols2.length+' values of x.',{min:0,tb:110});
});
AMgen('g1','ident','Simplify with trig identities','G1 1.6, 1.7 (G3 p.17, G2 pp.22-23)',function(){
var V=pick(['A','x','θ']),k=rnd(2,5);
var T=[['1 − cos²V','sin²V',['cos²V','sin V','1 + sin²V']],['1 − sin²V','cos²V',['sin²V','cos V','1 + cos²V']],['sin V/cos V','tan V',['cot V','sin V cos V','sec V']],['cos V/sin V','cot V',['tan V','sin V cos V','cosec V']],
['sec²V − 1','tan²V',['sin²V','cot²V','1 + tan²V']],['1 + cot²V','cosec²V',['sec²V','tan²V','sin²V']],['tan V cos V','sin V',['cos V','sec V','cot V']],['sin²V/(1 − cos²V)','1',['sin V','cos²V','tan²V']],
['(1 − sin²V)/cos V','cos V',['sin V','sec V','1']],['sin V cosec V','1',['sin²V','tan V','cosec²V']],['cosec²V − cot²V','1',['tan²V','cosec V','sec²V']],['tan²V cos²V','sin²V',['cos²V','tan²V','1']],
['sin²V + cos²V + tan²V','sec²V',['tan²V','cosec²V','1 + sin²V']],[k+' − '+k+' sin²V',k+' cos²V',[k+' sin²V','cos²V',k+' + '+k+' cos²V']],[k+' cos²V + '+k+' sin²V',String(k),[k+' + 1','cos²V',k+' sin²V']]];
var t=pick(T),sw=function(s){return s.replace(/V/g,V);};
return amQ('g1.ident','Simplify '+sw(t[0])+'.',sw(t[1]),t[2].map(sw),
'Use sin²'+V+' + cos²'+V+' = 1, tan '+V+' = sin '+V+'/cos '+V+', sec²'+V+' = 1 + tan²'+V+' and cosec²'+V+' = 1 + cot²'+V+'. Applying them gives '+sw(t[1])+'.',{tb:90});
});
AMgen('g1','graph','Amplitude, period, maximum and minimum','G1 1.4, 1.5 (G3 p.17, G2 p.22)',function(){
var f=pick(['sin','cos','sin','cos','tan']),a=pick([1,2,3,4,-1,-2,-3]),b=pick([1,2,3,4]),c=rnd(-3,3),frm=(f!=='tan'&&Math.random()<.25)?'div':'mul';
if(f==='tan'){var ab=pick([1,2,3]),bt=pick([2,3,4,6]);
return amQ('g1.graph:tan','State the period, in degrees, of y = '+(ab===1?'':ab)+'tan '+bt+'x.',180/bt,[360/bt,180*bt,180,90/bt,360*bt],'The graph of y = tan x repeats every 180°. With '+bt+'x the period is 180°/'+bt+' = '+(180/bt)+'°.',{tb:90});}
var bs=frm==='div'?pick([2,3]):b, arg=frm==='div'?'(x/'+bs+')':bs===1?'x':bs+'x', head='y = '+(a===1?'':a===-1?'-':a)+f+' '+arg+(c?(c<0?' - '+(-c):' + '+c):''), ask=pick(['amp','period','max','min']);
var per=frm==='div'?360*bs:360/bs, mx=Math.abs(a)+c, mn=-Math.abs(a)+c;
if(ask==='amp')return amQ('g1.graph:amp','State the amplitude of '+head+'.',Math.abs(a),[a,Math.abs(a)+c,c,2*Math.abs(a),per],'The amplitude is the size of the number in front of '+f+', which is |'+a+'| = '+Math.abs(a)+'.',{tb:90});
if(ask==='period')return amQ('g1.graph:period','State the period, in degrees, of '+head+'.',per,[frm==='div'?360/bs:360*bs,180/bs,360,per/2,per*2],'The graph of y = '+f+' x repeats every 360°. For '+arg+' the period is '+(frm==='div'?'360° × '+bs:'360° ÷ '+bs)+' = '+per+'°.',{tb:90});
if(ask==='max')return amQ('g1.graph:max','Find the maximum value of '+head+'.',mx,[Math.abs(a),a+c,Math.abs(a)-c,mn,mx+1],'The biggest value of the '+f+' term is |'+a+'| = '+Math.abs(a)+'. Add '+c+': maximum = '+mx+'.',{tb:90});
return amQ('g1.graph:min','Find the minimum value of '+head+'.',mn,[-Math.abs(a),a+c,c,mx,mn-1],'The smallest value of the '+f+' term is −|'+a+'| = '+(-Math.abs(a))+'. Add '+c+': minimum = '+mn+'.',{tb:90});
});
AMgen('g1','dbl','Double-angle values','G1 1.6 (G3 p.17, G2 p.23)',function(){
var T=pick([[3,4,5],[5,12,13],[8,15,17],[7,24,25],[20,21,29]]),useSin=Math.random()<.5,o=useSin?T[0]:T[1],a=useSin?T[1]:T[0],h=T[2], ask=pick(['sin','cos','tan']);
if(Math.random()<.5){var z=o;o=a;a=z;}
var given=useSin?'sin A = '+o+'/'+h:'cos A = '+a+'/'+h, Hh=h*h, dd=a*a-o*o;
var ans,wr;
if(ask==='sin'){ans=amFS(amFr(2*o*a,Hh)); wr=[amFS(amFr(2*o,h)),amFS(amFr(o*a,Hh)),amFS(amFr(2*o*o,Hh)),amFS(amFr(2*a,h))];}
else if(ask==='cos'){ans=amFS(amFr(dd,Hh)); wr=[amFS(amFr(-dd,Hh)),amFS(amFr(a*a,Hh)),amFS(amFr(2*a,h)),amFS(amFr(a-o,h))];}
else {if(dd===0)throw new Error('amretry'); ans=amFS(amFr(2*o*a,dd)); wr=[amFS(amFr(2*o,a)),amFS(amFr(-2*o*a,dd)),amFS(amFr(o*o,a*a)),amFS(amFr(2*o*a,Hh))];}
return amQ('g1.dbl:'+ask,'Given that '+given+' and that A is acute, find the exact value of '+ask+' 2A.',ans,wr,
'The triangle has sides '+Math.min(o,a)+', '+Math.max(o,a)+', '+h+', so sin A = '+o+'/'+h+' and cos A = '+a+'/'+h+'. '+(ask==='sin'?'sin 2A = 2 sin A cos A = 2('+o+'/'+h+')('+a+'/'+h+') = '+ans+'.':ask==='cos'?'cos 2A = cos²A − sin²A = '+a*a+'/'+Hh+' − '+o*o+'/'+Hh+' = '+ans+'.':'tan 2A = sin 2A ÷ cos 2A = ('+(2*o*a)+'/'+Hh+') ÷ ('+dd+'/'+Hh+') = '+ans+'.'),{tb:120});
});
/* ============ G2 Coordinate geometry ============ */
function amPt(x,y){return '('+x+', '+y+')';}
function amH(v){return (v%1===0)?String(v):String(v);}
AMgen('g2','dist','Distance between two points','G2 2.1 to 2.3 (G3 p.17, G2 p.23); distance is E-Maths prerequisite',function(){
var x1=rnd(-6,6),y1=rnd(-6,6),sx=pick([1,-1]),sy=pick([1,-1]),A='A'+amPt(x1,y1),dx,dy;
if(Math.random()<.6){var tri=pick([[3,4,5],[6,8,10],[5,12,13],[8,15,17],[9,12,15],[7,24,25]]),sw=Math.random()<.5; dx=tri[sw?1:0]*sx; dy=tri[sw?0:1]*sy;
return amQ('g2.dist:int','Find the distance between '+A+' and B'+amPt(x1+dx,y1+dy)+'.',tri[2],[Math.abs(dx)+Math.abs(dy),dx*dx+dy*dy,Math.abs(Math.abs(dx)-Math.abs(dy)),tri[2]+1],
'Distance = √(('+(x1+dx)+' − '+x1+')² + ('+(y1+dy)+' − '+y1+')²) = √('+(dx*dx)+' + '+(dy*dy)+') = √'+(dx*dx+dy*dy)+' = '+tri[2]+'.',{tb:100});}
dx=rnd(1,6)*sx; dy=rnd(1,6)*sy; var n=dx*dx+dy*dy; if(!amSqFree(n)||Math.abs(dx)===Math.abs(dy))throw new Error('amretry');
return amQ('g2.dist:surd','Find the distance between '+A+' and B'+amPt(x1+dx,y1+dy)+'. Leave your answer as a surd.','√'+n,['√'+(Math.abs(dx)+Math.abs(dy)),'√'+Math.abs(dx*dx-dy*dy),String(Math.abs(dx)+Math.abs(dy)),'√'+(n+Math.abs(dx*dy))],
'Distance = √(('+dx+')² + ('+dy+')²) = √('+(dx*dx)+' + '+(dy*dy)+') = √'+n+'.',{tb:100});
});
AMgen('g2','mid','Midpoint of a line segment','G2 2.2 (G3 p.17, G2 p.23)',function(){
var x1=rnd(-7,7),y1=rnd(-7,7),x2=rnd(-7,7),y2=rnd(-7,7); if(x1===x2||y1===y2||(x1===y2&&x2===y1))throw new Error('amretry');
if(Math.random()<.6){var mx=(x1+x2)/2,my=(y1+y2)/2;
return amQ('g2.mid:mid','Find the midpoint of the line segment joining A'+amPt(x1,y1)+' and B'+amPt(x2,y2)+'.',amPt(mx,my),[amPt(x1+x2,y1+y2),amPt((x2-x1)/2,(y2-y1)/2),amPt(my,mx),amPt(mx,y1+y2)],
'Midpoint = ((x₁ + x₂)/2, (y₁ + y₂)/2) = (('+x1+' + '+x2+')/2, ('+y1+' + '+y2+')/2) = '+amPt(mx,my)+'.',{tb:90});}
var xm=rnd(-5,5),ym=rnd(-5,5),ask=pick(['x','y']),xb=2*xm-x1,yb=2*ym-y1;
return amQ('g2.mid:end:'+ask,'M'+amPt(xm,ym)+' is the midpoint of AB, where A is the point '+amPt(x1,y1)+'. Find the '+ask+'-coordinate of B.',ask==='x'?xb:yb,ask==='x'?[xm-x1,(xm+x1)/2,xm+x1,2*xm+x1]:[ym-y1,(ym+y1)/2,ym+y1,2*ym+y1],
'The midpoint x-coordinate is (x₁ + x₂)/2 and the same for y. So '+(ask==='x'?xm+' = ('+x1+' + x)/2, giving x = '+xb:ym+' = ('+y1+' + y)/2, giving y = '+yb)+'.',{tb:100});
});
AMgen('g2','line','Gradient and equation of a line','G2 2.1 (G3 p.17, G2 p.23)',function(){
var sub=pick(['grad','eq','xint']);
if(sub==='grad'){var x1=rnd(-6,6),y1=rnd(-6,6),x2=x1+amNZ(-6,6),y2=y1+rnd(-8,8),f=amFr(y2-y1,x2-x1);
return amQ('g2.line:grad','Find the gradient of the line through A'+amPt(x1,y1)+' and B'+amPt(x2,y2)+'.',amFS(f),[amFS(amFr(x2-x1,y2-y1===0?1:y2-y1)),amFS(amFr(y1-y2,x2-x1)),amFS(amFr(y2+y1,x2+x1===0?1:x2+x1)),amFS(amFr(y2-y1,1))],
'Gradient = (y₂ − y₁)/(x₂ − x₁) = ('+y2+' − '+y1+')/('+x2+' − '+x1+') = '+(y2-y1)+'/'+(x2-x1)+' = '+amFS(f)+'.',{tb:90});}
if(sub==='eq'){var m=amNZ(-4,4),c=amNZ(-8,8),xa=rnd(-3,0),xb=xa+rnd(1,4),ya=m*xa+c,yb=m*xb+c; if(Math.abs(c)===Math.abs(m))throw new Error('amretry');
return amQ('g2.line:eq','Find the equation of the line passing through '+amPt(xa,ya)+' and '+amPt(xb,yb)+'.','y = '+amPoly([m,c]),['y = '+amPoly([m,-c]),'y = '+amPoly([-m,c]),'y = '+amPoly([c,m]),'y = '+amPoly([-m,-c])],
'Gradient = ('+yb+' − '+ya+')/('+xb+' − '+xa+') = '+m+'. Use y = mx + c with '+amPt(xa,ya)+': '+ya+' = '+m+'('+xa+') + c, so c = '+c+'.',{tb:110});}
var m2=pick([2,3,-2,-3,4,-4,5,-5]),t=rnd(-4,4),x0=rnd(-5,5),y0=m2*t; if(y0===0)throw new Error('amretry'); var xi=x0-y0/m2;
return amQ('g2.line:xint','A line with gradient '+m2+' passes through the point '+amPt(x0,y0)+'. Find the x-coordinate of the point where the line meets the x axis.',xi,[x0+y0/m2,x0-y0*m2,-xi,y0-m2*x0,xi+1],
'On the x axis y = 0. Using y − y₁ = m(x − x₁): 0 − '+y0+' = '+m2+'(x − '+x0+'), so x − '+x0+' = '+(-y0/m2)+' and x = '+xi+'.',{tb:110});
});
AMgen('g2','parperp','Parallel and perpendicular lines','G2 2.1 (G3 p.17, G2 p.23)',function(){
var sub=pick(['par','perp','int']);
if(sub==='par'){var m=amNZ(-4,4),b=pick([2,3,4,5]),a=-m*b,d=rnd(-6,9),c=rnd(-5,5);
return amQ('g2.parperp:par','The line ax + '+b+'y = '+d+' is parallel to the line y = '+amPoly([m,c])+'. Find the value of a.',a,[m*b,-a*b,m,-m,a+b],
'Rewrite: '+b+'y = −ax + '+d+', so the gradient is −a/'+b+'. Parallel lines have equal gradients: −a/'+b+' = '+m+', so a = '+a+'.',{tb:110});}
if(sub==='perp'){var m3=pick([2,3,4,5,-2,-3,-4,-5]),t3=amNZ(-4,4),b3=m3*t3,c3=rnd(-5,5),d3=rnd(-6,9);
return amQ('g2.parperp:perp','The line y = '+amPoly([m3,c3])+' is perpendicular to the line kx '+(b3<0?'− '+(-b3):'+ '+b3)+'y = '+d3+'. Find the value of k.',t3,[-t3,m3*b3,b3/m3*-1,m3,t3+1],
'The gradient of kx '+(b3<0?'− '+(-b3):'+ '+b3)+'y = '+d3+' is −k/'+(b3<0?'('+b3+')':b3)+'. Perpendicular lines have gradients that multiply to −1: '+m3+' × (−k/'+(b3<0?'('+b3+')':b3)+') = −1, so k = '+b3+'/'+m3+' = '+t3+'.',{tb:120});}
var m4=pick([2,3,4,-2,-3,-4]),t4=amNZ(-3,3),x0=m4*t4,y0=rnd(-6,6),c4=rnd(-5,5),ans=y0+t4;
return amQ('g2.parperp:int','The line L passes through the point '+amPt(x0,y0)+' and is perpendicular to the line y = '+amPoly([m4,c4])+'. Find the y-intercept of L.',ans,[y0-t4,y0+m4*x0,y0-m4*x0,-ans,y0],
'The gradient of L is −1/'+m4+'. Using y = mx + c: c = y − mx = '+y0+' − (−1/'+m4+')('+x0+') = '+y0+' + '+t4+' = '+ans+'.',{tb:120});
});
function amArea2(P){var s=0,i; for(i=0;i0?' − ':' + ')+(Math.abs(2*a))+'x'; if(b)s+=(b>0?' − ':' + ')+(Math.abs(2*b))+'y'; if(c)s+=(c<0?' − ':' + ')+Math.abs(c); s+=' = 0';
var ans=ask==='r'?r:ask==='a'?a:b, wr=ask==='r'?[r*r,a*a+b*b-c===0?1:Math.abs(c),a+b+r,Math.abs(c),r+1]:ask==='a'?[-a,2*a,b,a*a,a+1]:[-b,2*b,a,b*b,b+1];
return amQ('g2.circle:'+ask,'The equation of a circle is '+s+'. Find '+(ask==='r'?'its radius.':'the '+(ask==='a'?'x':'y')+'-coordinate of its centre.'),ans,wr,
'For x² + y² + 2gx + 2fy + c = 0 the centre is (−g, −f) and r² = g² + f² − c. Here g = '+(-a)+', f = '+(-b)+', c = '+c+'. Centre = '+amPt(a,b)+' and r² = '+(a*a)+' + '+(b*b)+' − ('+c+') = '+(r*r)+', so r = '+r+'.',{tb:120});
});
/* ============ C1 Differentiation and integration ============ */
function amFPow(x0,e){ /* x0^e as fraction, e=[n,d]; null if not rational here */
if(e[1]===1){var t=e[0]; return t>=0?[amPow(x0,t),1]:amFr(1,amPow(x0,-t));}
if(e[1]===2){if(x0<=0)return null; var s=Math.round(Math.sqrt(x0)); if(s*s!==x0)return null; var t2=e[0]; /* x0^(t2/2)=s^t2 */ return t2>=0?[amPow(s,t2),1]:amFr(1,amPow(s,-t2));}
return null;
}
function amTermS(c,e){var ab=Math.abs(c),co=String(ab),x;
if(e[1]===2){x=(e[0]===1)?'√x':'1/√x'; return e[0]===1?(ab===1?'':co)+'√x':co+'/√x';}
if(e[0]===0)return co; if(e[0]===1)return (ab===1?'':co)+'x'; if(e[0]>1)return (ab===1?'':co)+'x'+sup(e[0]); if(e[0]===-1)return co+'/x'; return co+'/x'+sup(-e[0]);}
function amExprS(terms){var s='',i; for(i=0;i999||val[1]>12)throw new Error('amretry');
var yv=amEvT(terms,x0), unr=amEvT(terms.map(function(t){return [t[0]*t[1][0]/t[1][1],t[1]];}).filter(function(t){return Number.isInteger(t[0])}),x0);
var exp='Differentiate each term (multiply by the power, then reduce the power by 1): dy/dx = '+amExprS(d)+'. Put x = '+x0+': dy/dx = '+amFS(val)+'.';
return amQ('c1.deriv','Given y = '+amExprS(terms)+', find the value of dy/dx when x = '+x0+'.',amFS(val),[yv?amFS(yv):null,unr?amFS(unr):null,amFS(amFr(-val[0],val[1])),amFS(amFAdd(val,[1,1])),amFS(amFAdd(val,[-1,1]))],exp,{tb:120});
});
AMgen('c1','tangent','Gradient of tangent and normal','C1 1.1, 1.10 (G3 p.18, G2 p.23)',function(){
var terms=amRandTerms(false,false),sub=pick(['grad','normal','yint']),x0=pick([1,2,3,-1,-2]);
if(terms.every(function(t){return t[1][0]===0;}))throw new Error('amretry');
var d=amDerivT(terms),m=amEvT(d,x0),y0=amEvT(terms,x0); if(!m||!y0)throw new Error('amretry');
if(sub==='grad')return amQ('c1.tangent:grad','Find the gradient of the tangent to the curve y = '+amExprS(terms)+' at the point where x = '+x0+'.',amFS(m),[amFS(y0),amFS(amFr(-m[0],m[1])),m[0]!==0?amFS(amFr(m[1],m[0])):null,amFS(amFAdd(m,[1,1]))],
'dy/dx = '+amExprS(d)+'. At x = '+x0+', the gradient of the tangent is '+amFS(m)+'.',{tb:110});
if(sub==='normal'){if(m[0]===0)throw new Error('amretry'); var nm=amFr(-m[1],m[0]);
return amQ('c1.tangent:normal','Find the gradient of the normal to the curve y = '+amExprS(terms)+' at the point where x = '+x0+'.',amFS(nm),[amFS(m),amFS(amFr(m[1],m[0])),amFS(amFr(-m[0],m[1])),amFS(amFAdd(nm,[1,1]))],
'dy/dx = '+amExprS(d)+'. At x = '+x0+', the gradient of the tangent is '+amFS(m)+'. The normal is perpendicular, so its gradient is −1 ÷ '+amFS(m)+' = '+amFS(nm)+'.',{tb:120});}
var c=amFAdd(y0,amFMul(amFr(-m[0],m[1]),[x0,1])); if(c[1]!==1)throw new Error('amretry');
return amQ('c1.tangent:yint','The tangent to the curve y = '+amExprS(terms)+' at the point where x = '+x0+' meets the y axis at (0, c). Find c.',c[0],[amFS(y0)==='x'?null:y0[0]/y0[1],m[0]/m[1],c[0]+m[0]/m[1]*x0*2,-c[0]],
'At x = '+x0+', y = '+amFS(y0)+' and the gradient is '+amFS(m)+'. The tangent is y − '+amFS(y0)+' = '+amFS(m)+'(x − '+x0+'). At x = 0, y = '+amFS(y0)+' − ('+amFS(m)+')('+x0+') = '+c[0]+'.',{tb:130});
});
AMgen('c1','stat','Stationary points','C1 1.8, 1.9, 1.10 (G3 p.18, G2 p.23)',function(){
var p=rnd(-4,3),q=p+rnd(1,4)*1; if((p+q)%2!==0){q+=1;} var sg=pick([1,1,-1]),C=rnd(-6,8), cs=[1,-3*(p+q)/2,3*p*q,C].map(function(z,i){return i===3?C:sg*z;});
var f=function(x){return amEv(cs,x);}, ask=pick(['xbig','xsmall','ylo','yhi','nature']), ex='dy/dx = '+amPoly([sg*3,-3*sg*(p+q),3*sg*p*q])+' = '+(sg*3)+(p===0?'x':'(x '+amSgn(-p)+')')+(q===0?'x':'(x '+amSgn(-q)+')')+', which is 0 when x = '+p+' or x = '+q+'.';
if(p===q)throw new Error('amretry');
var stem='The curve y = '+amPoly(cs)+' has two stationary points. ';
var yp=f(p),yq=f(q);
if(ask==='xbig')return amQ('c1.stat:xbig',stem+'Find the larger value of x at the stationary points.',q,[p,-q,f(q),p+q,q+1],ex,{tb:130});
if(ask==='xsmall')return amQ('c1.stat:xsmall',stem+'Find the smaller value of x at the stationary points.',p,[q,-p,f(p),p+q,p-1],ex,{tb:130});
if(ask==='ylo')return amQ('c1.stat:ylow',stem+'Find the y-coordinate of the stationary point with the larger x.',yq,[yp,q,-yq,yq+1,yq-1],ex+' Then y = '+amPoly(cs).replace(/x/g,'('+q+')')+' = '+yq+' at x = '+q+'.',{tb:140});
if(ask==='yhi')return amQ('c1.stat:yhigh',stem+'Find the y-coordinate of the stationary point with the smaller x.',yp,[yq,p,-yp,yp+1,yp-1],ex+' Put x = '+p+' into the equation of the curve: y = '+yp+'.',{tb:140});
var xq=pick([p,q]), d2=sg*(6*xq-3*(p+q)), nat=d2>0?'Minimum point':'Maximum point';
return amQ('c1.stat:nature',stem+'State the nature of the stationary point at x = '+xq+'.',nat,['Minimum point','Maximum point','Point of inflexion','Cannot be decided'],
ex+' The second derivative is d²y/dx² = '+amPoly([sg*6,-3*sg*(p+q)])+'. At x = '+xq+' it is '+d2+', which is '+(d2>0?'positive, so it is a minimum point.':'negative, so it is a maximum point.'),{tb:140});
});
AMgen('c1','int','Definite integrals','C1 1.12, 1.13, 1.15 (G3 p.18, G2 p.23)',function(){
var sub=pick(['poly','poly','neg','lin']);
if(sub==='poly'){var F=[rnd(0,2),rnd(-3,3),rnd(-4,4),0].map(function(z,i){return z;}); F=[F[0],F[1],F[2],0]; if(F[0]===0&&F[1]===0)throw new Error('amretry');
var f=[3*F[0],2*F[1],F[2]],lo=rnd(-2,2),hi=lo+rnd(1,3),ans=amEv(F,hi)-amEv(F,lo);
return amQ('c1.int:poly','Evaluate ∫('+amPoly(f)+') dx between x = '+lo+' and x = '+hi+'.',ans,[amEv(F,hi)+amEv(F,lo),amEv(f,hi)-amEv(f,lo),amEv(F,hi),-ans,ans+1],
'Integrate: ∫('+amPoly(f)+') dx = '+amPoly(F)+'. Then F('+hi+') − F('+lo+') = '+amEv(F,hi)+' − ('+amEv(F,lo)+') = '+ans+'.',{tb:120});}
if(sub==='neg'){var k=rnd(1,6)*pick([1,2,3,4]),lo2=rnd(1,2),hi2=lo2+rnd(1,3),v=amFMul(amFr(k,1),amFAdd(amFr(1,lo2),amFr(-1,hi2)));
return amQ('c1.int:neg','Evaluate ∫ '+k+'/x² dx between x = '+lo2+' and x = '+hi2+'.',amFS(v),[amFS(amFMul(amFr(k,1),amFAdd(amFr(1,hi2),amFr(-1,lo2)))),amFS(amFMul(amFr(k,1),amFAdd(amFr(1,lo2),amFr(1,hi2)))),amFS(amFr(k*(hi2-lo2),1)),amFS(amFr(k*(hi2-lo2),lo2*hi2*2))],
'∫ '+k+'/x² dx = ∫ '+k+'x⁻² dx = −'+k+'/x. So the value is [−'+k+'/x] from '+lo2+' to '+hi2+' = (−'+k+'/'+hi2+') − (−'+k+'/'+lo2+') = '+amFS(v)+'.',{tb:130});}
var a=pick([2,3,4]),b=rnd(-2,3),n=pick([2,3]),lo3=rnd(-1,1),hi3=lo3+rnd(1,2),g=function(x){return amPow(a*x+b,n+1);},num=g(hi3)-g(lo3),v3=amFr(num,a*(n+1));
return amQ('c1.int:lin','Evaluate ∫('+a+'x '+amSgn(b)+')'+sup(n)+' dx between x = '+lo3+' and x = '+hi3+'.',amFS(v3),[amFS(amFr(num,n+1)),amFS(amFr(num,a)),amFS(amFr(amPow(a*hi3+b,n)-amPow(a*lo3+b,n),a*n)),amFS(amFr(-num,a*(n+1)))],
'∫('+a+'x '+amSgn(b)+')'+sup(n)+' dx = ('+a+'x '+amSgn(b)+')'+sup(n+1)+'/('+a+' × '+(n+1)+'). Put in the limits: ('+g(hi3)+' − ('+g(lo3)+'))/'+(a*(n+1))+' = '+amFS(v3)+'.',{tb:140});
});
AMgen('c1','area','Area under a curve','C1 1.14, 1.16 (G3 p.18, G2 p.23)',function(){
if(Math.random()<.5){var c=pick([1,2,3]),k=rnd(2,6),v=amFr(c*k*k*k,6);
return amQ('c1.area:roots','The curve y = '+amPoly([-c,c*k,0])+' meets the x axis at x = 0 and x = '+k+'. Find the area of the region between the curve and the x axis.',amFS(v),[amFS(amFr(c*k*k*k,3)),amFS(amFr(c*k*k,2)),amFS(amFr(c*k*k*k,12)),amFS(amFr(c*k,1))],
'Area = ∫ from 0 to '+k+' of ('+amPoly([-c,c*k,0])+') dx = [−'+(c===1?'':c)+'x³/3 + '+(c*k)+'x²/2] from 0 to '+k+' = '+amFS(v)+'.',{tb:140});
}
var a=pick([1,2,3]),c0=rnd(1,5),p=rnd(0,2),q=p+rnd(1,3),v2=amFAdd(amFr(a*(amPow(q,3)-amPow(p,3)),3),amFr(c0*(q-p),1));
return amQ('c1.area:lines','Find the area of the region bounded by the curve y = '+amPoly([a,0,c0])+', the x axis and the lines x = '+p+' and x = '+q+'.',amFS(v2),[amFS(amFAdd(amFr(a*(amPow(q,3)-amPow(p,3)),3),amFr(c0,1))),amFS(amFr(a*(amPow(q,3)-amPow(p,3))+c0*(q-p),1)),amFS(amFAdd(v2,[1,1])),amFS(amFAdd(amFr(a*(amPow(q,2)-amPow(p,2)),2),amFr(c0*(q-p),1)))],
'Area = ∫ from '+p+' to '+q+' of ('+amPoly([a,0,c0])+') dx = ['+(a===1?'':a)+'x³/3 + '+c0+'x] from '+p+' to '+q+' = '+amFS(v2)+'.',{tb:140});
});
AMgen('c1','indef','Indefinite integrals','C1 1.11, 1.12 (G3 p.18, G2 p.23)',function(){
var A=[rnd(1,4),rnd(-4,4),amNZ(-5,5)],f=[3*A[0],2*A[1],A[2]]; if(A[1]===0)throw new Error('amretry');
var good=amPoly([A[0],A[1],A[2],0]).replace(/ [+−] 0$/,'')+' + c', mk=function(cs){return amPoly(cs.concat([0]))+' + c';};
var g=amPoly([A[0],A[1],A[2]],'x'); /* integral of f is A0 x^3 + A1 x^2 + A2 x */
var good2=amPoly([A[0],A[1],A[2],0])+' + c';
return amQ('c1.indef','Find ∫('+amPoly(f)+') dx.',good2,[amPoly([f[0],f[1],f[2],0])+' + c',amPoly(A.concat([0]))+'',amPoly([f[0]*2,f[1]])+' + c',amPoly([A[0],A[1],0,0])+' + c'],
'Raise each power by 1 and divide by the new power: '+f[0]+'x² → '+A[0]+'x³, '+f[1]+'x → '+A[1]+'x² and '+f[2]+' → '+f[2]+'x. Do not forget + c. Answer: '+good2+'.',{tb:110});
});
/* make one question from a generator: retries when a random draw cannot give a clean question */
function amMake(g){var i,q; for(i=0;i<120;i++){try{q=g.f(); if(q&&q.options&&q.options.length===4&&(new Set(q.options)).size===4&&q.options.indexOf(q.answer)>=0&&!/NaN|undefined|Infinity/.test(q.text+'|'+q.options.join('|')+'|'+q.why)){q.amsyl=g.s; q.amgid=g.id; return q;}}catch(e){}} throw new Error('amfail:'+g.id);}
/* ---------- Additional Maths (STARTER, Sec 3-4): hub, practice, exit tickets, Do Now ----------
Reuses the Computing quiz state (CP) with CP.am=1, so the timer, keypad, hearts, rewards and closeModals() all behave as for every other subject.
G1 Learners never see it. G2 Learners see only the G2 topics (A5 and A6 are Sec 5 content for G2, syllabus p.24). G3 sees everything. */
var AMMOD='all';
function amG2ok(t){var r=amTopic(t); return !!r&&(GL!==2||r[2]===1);}
function amTopics(){return AMT.filter(function(r){return amG2ok(r[0]);});}
function amNext(){
var ts=amTopics().map(function(r){return r[0];}), t=(AMMOD==='all'||ts.indexOf(AMMOD)<0)?pick(ts):AMMOD;
var q=amMake(pick(AMG.filter(function(g){return g.t===t;})));
q.kickShow='Additional Maths (starter) · '+amTopic(t)[1];
q.wp=true; q.cp=true; q.opts=options(q); q.gi=0; q.lv=null; q.df='normal'; q.rv=false; q.rec=false; return q;
}
/* the new modal joins modalOpen() and closeModals() by wrapping them (keeps the long shared lines untouched) */
var amPrevModalOpen=modalOpen; modalOpen=function(){return amPrevModalOpen()||!ob('amModal').hidden;};
var amPrevCloseModals=closeModals; closeModals=function(){var r=amPrevCloseModals.apply(this,arguments); ob('amModal').hidden=true; return r;};
var amPrevCpNext=cpNext;
cpNext=function(){return (CP&&CP.am)?amNext():amPrevCpNext();};
function openAM(){
if(modalOpen())return; pauseGame(); var box=ob('amMods'); box.innerHTML='';
var note=ob('amNote');
if(GL===1){note.textContent='Additional Maths is for subject bands G2 and G3, so it is not part of the G1 course. Ask your teacher if you are not sure of your band.'; ob('amModal').hidden=false; try{ob('amPanel').focus({preventScroll:true});}catch(_){} return;}
note.textContent=GL===2?'Showing the G2 topics. Binomial expansions and exponential and logarithmic functions are Secondary 5 content for G2 (G2 syllabus, p.24), so they are not here.':(GL===3?'Showing all G3 topics.':'Choose your subject band (G2 or G3) in Menu, then Subjects, so you see the right topics. Showing all topics for now.');
var all=document.createElement('button'); all.type='button'; all.className='btn primary'; all.textContent='Mixed practice (all topics)'; all.addEventListener('click',function(){startAM('all');}); box.appendChild(all);
amTopics().forEach(function(r){var b=document.createElement('button'); b.type='button'; b.className='btn'; b.textContent=r[1]+(r[2]?'':' (G3 only)'); b.addEventListener('click',function(){startAM(r[0]);}); box.appendChild(b);});
ob('amModal').hidden=false; try{ob('amPanel').focus({preventScroll:true});}catch(_){}
}
function startAM(m){closeModals(); AMMOD=m; CP={n:0,ok:0,am:1}; openQuiz(); if(modal.hidden)CP=null;}
ob('amBtn').addEventListener('click',openAM); ob('amBack').addEventListener('click',closeModals);
/* hub, hide controls, teacher hide list */
(function(){
var i=MENUF.map(function(f){return f[0];}).indexOf('cpBtn'); MENUF.splice(i+1,0,['amBtn','Additional Maths (starter)']);
var j=PWSUB.indexOf('cpBtn'); PWSUB.splice(j+1,0,'amBtn');
var k=LRNSUBS.map(function(r){return r[0]+'|'+r[3];}).indexOf('cpBtn|S');
LRNSUBS.splice(k+1,0,['amBtn','Additional Maths (starter)','Sec 3 and 4 starter: quadratics, surds, polynomials, trigonometry, coordinate geometry, calculus (G3 also binomials, logs)','S','34','23']);
})();
/* exit tickets: ids bk:AM:; shown at Sec 3 and Sec 4 for the Additional Maths subject */
ETSUB.am='Additional Maths (starter)';
var amEtSubsFor=etSubsFor; etSubsFor=function(lv){var r=amEtSubsFor(lv).slice(); if(lv==='Sec 3'||lv==='Sec 4'){var i=r.indexOf('sc'); r.splice(i<0?r.length:i+1,0,'am');} return r;};
var amEtBank=etBank; etBank=function(lv,sub,gl){return sub==='am'?'AM':amEtBank(lv,sub,gl);};
var amEtBankLabel=etBankLabel; etBankLabel=function(b){return b==='AM'?'Additional Maths (starter)':amEtBankLabel(b);};
var amEtBankTopics=etBankTopics; etBankTopics=function(b){if(b!=='AM')return amEtBankTopics(b); return AMT.map(function(r){return {t:r[0],n:AMG.filter(function(g){return g.t===r[0];}).length,name:r[1]+(r[2]?'':' (G3 only)')};});};
var amEtTopics=etTopics; etTopics=function(lv,sub,gl){var o=amEtTopics(lv,sub,gl); if(sub==='am'&&(gl||3)===2)o=o.filter(function(x){return amTopic(x.id.split(':')[2]).slice(2,3)[0]===1;}); return o;};
function amBkId(id){var b=etBkParse(id); return (b&&b.bank==='AM')?b.t:null;}
var amEtPupilOk=etPupilOk; etPupilOk=function(id){var t=amBkId(id); if(t){if(GL===1)return false; return amG2ok(t);} return amEtPupilOk(id);};
var amEtCanX=etCanX; etCanX=function(id){var t=amBkId(id); if(t)return !!amTopic(t); return amEtCanX(id);};
var amEtSampleX=etSampleX; etSampleX=function(id,n){
var t=amBkId(id); if(!t)return amEtSampleX(id,n);
var out=[], seen={}, gs=AMG.filter(function(g){return g.t===t;}), tries=0;
while(out.length=0;}), out=[], seen={}, tries=0; if(!gs.length)return out;
while(out.lengthMy level
My subject band
'; var gs=ob('lrnGlSeg');
[[1,'G1'],[2,'G2'],[3,'G3']].forEach(function(o){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[1]; b.setAttribute('aria-pressed',String(GL===o[0])); b.addEventListener('click',function(){GL=o[0]; try{localStorage.setItem('mbGl',String(GL));}catch(e){} renderLearn();}); gs.appendChild(b);});
var bs=ob('lrnBdSeg'), cb=lrnBand();
[['12','Sec 1-2'],['34','Sec 3-4']].forEach(function(o){var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.textContent=o[1]; b.setAttribute('aria-pressed',String(cb===o[0])); b.addEventListener('click',function(){try{localStorage.setItem('mbBand',o[0]);}catch(e){} renderLearn();}); bs.appendChild(b);});
if(!GL){var p=document.createElement('p'); p.className='lvldesc'; p.textContent='Choose your subject band (G1, G2 or G3) so you see the right questions. Ask your teacher if you are not sure.'; gl.appendChild(p);}}
var box=ob('lrnSubs'); box.innerHTML=''; var n=0;
(function(){var mc=(S.mx||[]).length; if(!mc)return; var b=document.createElement('button'); b.type='button'; b.className='btn'; b.style.cssText='display:block;width:100%;text-align:left;margin-bottom:6px'; b.innerHTML=' '; b.firstChild.textContent='My mistakes ('+mc+')'; b.lastChild.textContent=premium()?'See what goes wrong most, then practise those questions':'Saved for you. Premium shows the analysis and lets you practise them'; b.addEventListener('click',function(){closeModals(); setTimeout(openMx,30);}); box.appendChild(b);})();
(function(){var lid=S.pwLast, le=lid&&ob(lid); if(!le||le.style.display==='none'||le.hidden)return; var nm=null; LRNSUBS.forEach(function(x){if(x[0]===lid&&x[3].indexOf(lv)>=0&&!nm)nm=x[1];}); if(!nm)return; var kb=document.createElement('button'); kb.type='button'; kb.className='btn primary'; kb.style.cssText='display:block;width:100%;text-align:left;margin-bottom:10px'; kb.textContent='Keep going: '+nm; kb.addEventListener('click',function(){window.LRNLV=lv; closeModals(); setTimeout(function(){window.LRNLV=lv; le.click();},30);}); box.appendChild(kb);})();
LRNSUBS.forEach(function(r){ if(r[3].indexOf(lv)<0)return; if(lv==='S'){ if(r[4]&&r[4].split('|').indexOf(lrnBand())<0)return; if(r[5]&&GL&&r[5].indexOf(String(GL))<0)return; } var src=ob(r[0]); if(!src||src.style.display==='none')return;
var b=document.createElement('button'); b.type='button'; b.className='btn'; b.style.cssText='display:block;width:100%;text-align:left;margin-bottom:6px'; b.innerHTML=' '; b.firstChild.textContent=r[1]; b.lastChild.textContent=(lv==='Q'&&!r[6]?'Choose a topic':r[2]);
b.addEventListener('click',function(){window.LRNLV=lv; try{S.pwLast=r[0]; save();}catch(e){} closeModals(); setTimeout(function(){window.LRNLV=lv; if(lv==='Q'&&!GL)GL=3; src.click();},30);}); box.appendChild(b); n++;});
if(!n)box.appendChild(Object.assign(document.createElement('p'),{className:'lvldesc',textContent:'No subjects are switched on. Ask your teacher or grown-up.'}));
ob('lrnNote').textContent=(lv==='Q'?'Post-Sec: pick a subject, then a topic. There are no levels here. ':'')+(lv==='S'?'Showing subjects for G'+(GL||'?')+', '+(lrnBand()==='34'?'Sec 3-4':'Sec 1-2')+'. ':'')+'Each subject has its own topics. You can come back here any time from Menu, then Subjects.';
}
ob('lrnBack').addEventListener('click',closeModals);
ob('lrnAll').addEventListener('click',function(){closeModals(); window.__legacySub=1; setTimeout(function(){try{var tr=ob('tray'), st=ob('subTray'); if(tr.hidden)ob('menuBtn').click(); if(st.hidden)ob('subBtn').click();}catch(e){} window.__legacySub=0;},50);});
/* ---- Read to me: reads any question and its answers aloud (for Learners who find reading hard) ---- */
(function(){
var qt=document.getElementById('qtext'), ans=document.getElementById('answers'); if(!qt||!ans||!window.speechSynthesis||!window.SpeechSynthesisUtterance)return;
var b=document.createElement('button'); b.type='button'; b.className='btn small'; b.id='readQ'; b.textContent='🔊 Read to me'; b.setAttribute('aria-label','Read the question and answers aloud'); b.style.margin='4px 0';
qt.parentNode.insertBefore(b,qt.nextSibling);
function say(t){var u=new SpeechSynthesisUtterance(t); u.lang=/[\u4e00-\u9fff]/.test(t)?'zh-CN':'en-GB'; u.rate=.85; speechSynthesis.speak(u);}
b.addEventListener('click',function(e){e.stopPropagation(); try{speechSynthesis.cancel();}catch(x){}
var t=(qt.textContent||'').replace(/\s+/g,' ').trim(); if(!t)return; say(t);
var L='ABCD', en=document.getElementById('entry'); if(en&&!en.hidden)return; var vis=Array.prototype.filter.call(ans.querySelectorAll('button'),function(o){return o.offsetParent!==null&&!o.hidden;}); vis.forEach(function(o,i){var v=(o.textContent||'').trim(); if(v)say('Answer '+L.charAt(i)+'. '+v);});});
try{new MutationObserver(function(){try{speechSynthesis.cancel();}catch(x){}}).observe(qt,{childList:true,characterData:true,subtree:true});}catch(x){}
})();
/* ===== Access settings block ===== */
/* ===================================================================================================
ACCESS SETTINGS (additive). One-handed play, tap-to-walk, switch scanning, dwell click, read aloud,
large buttons, choice instead of typing, no timers, reduce motion. Everything is OFF until a grown-up
switches it on in Menu > Access settings. Settings are kept per device (not per Learner) in localStorage.
=================================================================================================== */
var ACCKEY='mbAccess.v1';
var ACCDEF={big:0,rm:0,ra:0,raOpt:1,raFb:1,raAuto:0,raRate:0.9,raVoice:'',choice:0,noTimers:0,one:0,walk:0,hand:'r',km:'std',scan:0,scanMode:'auto',scanSpeed:1.5,scanKey:'',advKey:'',scanSay:0,dwell:0,dwellMs:1000,lock:0};
var accA={}, accRM0=reduceMotion, accMem=null;
/* what each setting is, in plain words. The Access screen and the printable teacher guide are both built from this list.
dev = how useful it is on each device: 2 main use, 1 also works, 0 not for it. t touch, m mouse, k keyboard, s switch, g eye-gaze */
var ACCI=[
{k:'big',g:'Seeing and reading',n:'Large buttons',d:'Every button is at least 56 pixels (about 15 mm) wide and tall, with more space between buttons, larger text and a thick two-colour focus outline.',dev:{t:2,m:2,k:1,s:2,g:2},w:'Target size (WCAG 2.5.8), focus visible (2.4.7)'},
{k:'rm',g:'Seeing and reading',n:'Reduce motion',d:'No screen shake, no full-screen flashes, no confetti, no spinning or pulsing pop-ups. The red flashing border on a timed question becomes a steady border.',dev:{t:1,m:1,k:1,s:1,g:1},w:'Three flashes (2.3.1), animation from interactions (2.3.3), pause/stop/hide (2.2.2)'},
{k:'ra',g:'Seeing and reading',n:'Read aloud',d:'A speaker button on every question speaks the question and the answer choices, and also reads the feedback. Uses the voices already on the device and needs no internet.',dev:{t:2,m:1,k:1,s:2,g:2},w:''},
{k:'raOpt',sub:'ra',n:'Also read the answer choices',d:'Off: the speaker button reads only the question.',dev:{t:1,m:1,k:1,s:1,g:1}},
{k:'raFb',sub:'ra',n:'Read the feedback',d:'Reads "Correct" or "Not this time" and the explanation when an answer is chosen.',dev:{t:1,m:1,k:1,s:1,g:1}},
{k:'raAuto',sub:'ra',n:'Read each new question by itself',d:'Starts reading as soon as a question appears. Some browsers need one tap on the speaker button first.',dev:{t:1,m:1,k:1,s:1,g:1}},
{k:'raRate',sub:'ra',n:'Speaking speed',d:'Slower or faster speech.',dev:{t:1,m:1,k:1,s:1,g:1}},
{k:'raVoice',sub:'ra',n:'Voice',d:'Automatic picks an English voice stored on the device. Online voices are not used.',dev:{t:1,m:1,k:1,s:1,g:1}},
{k:'choice',g:'Answering',n:'Choice instead of typing',d:'Every question that would need typed numbers or a drawn digit becomes a multiple-choice question with four choices, including exam questions.',dev:{t:2,m:1,k:1,s:2,g:2},w:''},
{k:'noTimers',g:'Answering',n:'No timers, no time pressure',d:'Question timers, the countdown on a pest fight, visit timers, eye-break forcing and speed bonuses are all off. The learn-first gate stays.',dev:{t:2,m:2,k:1,s:2,g:2},w:'Timing adjustable (2.2.1)'},
{k:'one',g:'Moving in the 3D world',n:'One-handed controls',d:'Shows a pad with walk and turn arrows, big Place block here and Break buttons that act on the block in front of you (no aiming), and sticky toggles for Break mode, Keep walking and Run.',dev:{t:2,m:2,k:1,s:1,g:1}},
{k:'walk',g:'Moving in the 3D world',n:'Tap or click to walk',d:'Tap or click a spot on the ground and Aura walks there. Dwelling on a spot with an eye-gaze pointer does the same. Turns off dragging to look and the mouse lock.',dev:{t:2,m:2,k:0,s:0,g:2}},
{k:'hand',g:'Moving in the 3D world',n:'Controls on the left or right',d:'Puts the pad, the menu buttons and the Jump button on the side of the screen your good hand can reach.',dev:{t:2,m:2,k:0,s:0,g:1}},
{k:'km',g:'Moving in the 3D world',n:'Keyboard layout for one hand',d:'Left-hand keys put every action under the left hand. Right-hand and arrow keys put every action under the right hand. See the key table below.',dev:{t:0,m:0,k:2,s:0,g:0}},
{k:'scan',g:'Switch and eye-gaze',n:'Switch scanning',d:'A highlight steps through the buttons on the screen. Press the switch (Space, Enter or the key you choose) to start, and press again to choose. Works in menus, the Learn hub, quizzes, the Game room board games and the 3D world buttons.',dev:{t:0,m:0,k:1,s:2,g:0}},
{k:'scanMode',sub:'scan',n:'Scan type',d:'Auto-scan moves by itself. Step-scan needs two switches: one moves the highlight, the other chooses.',dev:{t:0,m:0,k:0,s:2,g:0}},
{k:'scanSpeed',sub:'scan',n:'Scan speed',d:'How long the highlight stays on each button, from 0.8 to 5 seconds.',dev:{t:0,m:0,k:0,s:2,g:0}},
{k:'scanKey',sub:'scan',n:'Switch key',d:'Another key that works like the switch, for switch interfaces that send a different key.',dev:{t:0,m:0,k:0,s:2,g:0}},
{k:'advKey',sub:'scan',n:'Step key',d:'The second switch for step-scan. Enter is used if none is chosen.',dev:{t:0,m:0,k:0,s:2,g:0}},
{k:'scanSay',sub:'scan',n:'Speak each button while scanning',d:'Says the name of each highlighted button, for Learners who cannot see the screen well.',dev:{t:0,m:0,k:0,s:2,g:1}},
{k:'dwell',g:'Switch and eye-gaze',n:'Dwell click',d:'Rest the pointer on a button for a set time and it is pressed. A ring shows the progress. A Pause button stops it. It never presses anything when the page loads or while scrolling.',dev:{t:0,m:1,k:0,s:0,g:2},w:'Pointer gestures, single pointer (2.5.1), pointer cancellation (2.5.2)'},
{k:'dwellMs',sub:'dwell',n:'Dwell time',d:'From 0.5 to 3 seconds.',dev:{t:0,m:0,k:0,s:0,g:2}},
{k:'lock',g:'For the grown-up',n:'Lock these settings with the teacher PIN',d:'After this is on, Access settings open only after the teacher PIN (or grown-up PIN) is typed. Set it up once per device.',dev:{t:1,m:1,k:1,s:1,g:1}}
];
var ACCSPEEDS=[0.8,1,1.5,2,3,4,5], ACCDW=[0.5,0.8,1,1.5,2,3];
function accLoad(){
var o={}; try{o=JSON.parse(localStorage.getItem(ACCKEY)||'{}')||{};}catch(e){o=accMem||{};}
Object.keys(ACCDEF).forEach(function(k){accA[k]=(typeof o[k]===typeof ACCDEF[k])?o[k]:ACCDEF[k];});
accNorm(); window.__accS=accA;
}
function accNorm(){ /* keep every value inside its safe range, whatever was stored or passed in */
accA.raRate=Math.max(.5,Math.min(1.5,+accA.raRate||.9)); accA.scanSpeed=Math.max(.8,Math.min(5,+accA.scanSpeed||1.5)); accA.dwellMs=Math.max(500,Math.min(3000,+accA.dwellMs||1000));
if(accA.hand!=='l')accA.hand='r'; if(['std','left','right'].indexOf(accA.km)<0)accA.km='std'; if(accA.scanMode!=='step')accA.scanMode='auto';
}
function accSave(){ var s=JSON.stringify(accA); accMem=JSON.parse(s); try{localStorage.setItem(ACCKEY,s);}catch(e){} }
accLoad();
function accSet(k,v){ accA[k]=v; accNorm(); accSave(); accApply(); accRender(); }
function accAnyOn(){return !!(accA.big||accA.rm||accA.ra||accA.choice||accA.noTimers||accA.one||accA.walk||accA.hand==='l'||accA.km!=='std'||accA.scan||accA.dwell);}
function accKeyName(c){return !c?'none chosen':String(c).replace(/^Key/,'').replace(/^Digit/,'').replace(/^Arrow/,'Arrow ').replace(/^Numpad/,'Numpad ');}
/* ---------- apply every setting to the page ---------- */
function accApply(){
var c=document.documentElement.classList;
c.toggle('acc-big',!!accA.big); c.toggle('acc-rm',!!accA.rm); c.toggle('acc-one',!!(accA.one||accA.walk)); c.toggle('acc-left',accA.hand==='l');
c.toggle('acc-hasscan',!!accA.scan); c.toggle('acc-dwell',!!accA.dwell); c.toggle('acc-ra',!!(accA.ra&&accHasTTS()));
window.__accHandL=accA.hand==='l'; window.__accRM=!!accA.rm; window.__accFlat=!!(accA.scan||accA.dwell);
reduceMotion=accRM0||!!accA.rm;
try{accPadSync();}catch(e){} try{accRaSync();}catch(e){} try{accDwSync();}catch(e){} try{accScanSync();}catch(e){}
try{window.dispatchEvent(new Event('resize'));}catch(e){}
}
/* ---------- the Access screen ---------- */
var accModal=document.getElementById('accModal'), accFrom=false, accOpener=null, accCapture=null, accPinOK=false;
function accEl(t,cls,txt){var e=document.createElement(t); if(cls)e.className=cls; if(txt!=null)e.textContent=txt; return e;}
function accItem(k){return ACCI.filter(function(i){return i.k===k;})[0];}
function accDevText(dv){var n={t:'touch',m:'mouse',k:'keyboard',s:'switch',g:'eye-gaze'}, o=[]; ['t','m','k','s','g'].forEach(function(x){if(dv[x]===2)o.push(n[x]);}); return o.length?'Mainly for: '+o.join(', ')+'.':'';}
function accRowShell(it,ctl){
var r=accEl('div','accrow'+(it.sub?' accsub':'')), h=accEl('div','acchead'), s=accEl('span','',it.n); h.appendChild(s); if(ctl)h.appendChild(ctl); r.appendChild(h);
r.appendChild(accEl('p','',it.d)); var dt=accDevText(it.dev||{}); if(dt)r.appendChild(accEl('p','accdev',dt)); return r;
}
function accSw(k){
var it=accItem(k), b=accEl('button','btn accsw',accA[k]?'On':'Off'); b.type='button'; b.setAttribute('role','switch'); b.setAttribute('aria-checked',String(!!accA[k])); b.setAttribute('aria-label',it.n); b.dataset.ak=k;
b.addEventListener('click',function(){
if(k==='lock'&&!accA.lock&&!(typeof gpHas==='function'&&gpHas())){ accStat('Set a teacher PIN first: open Teacher controls, create the PIN, then come back and switch the lock on.'); return; }
if(k==='ra'&&!accA.ra&&!accHasTTS()){ accStat('This browser cannot read aloud, so this setting cannot be switched on here.'); return; }
accSet(k,accA[k]?0:1);
});
return accRowShell(it,b);
}
function accSegRow(k,opts,fmt){
var it=accItem(k), r=accRowShell(it,null), sg=accEl('div','accseg'); sg.setAttribute('role','group'); sg.setAttribute('aria-label',it.n);
opts.forEach(function(o){var b=accEl('button','btn small',o[0]); b.type='button'; b.dataset.ak=k+':'+o[1]; b.setAttribute('aria-pressed',String(accA[k]===o[1])); b.addEventListener('click',function(){accSet(k,o[1]);}); sg.appendChild(b);});
r.appendChild(sg); return r;
}
function accKeyRow(k){
var it=accItem(k), r=accRowShell(it,null), w=accEl('div','accseg'), cur=accEl('span','accstat','Now: '+accKeyName(accA[k])); w.appendChild(cur);
var b=accEl('button','btn small',accCapture===k?'Press a key now (Esc to cancel)':'Choose a key'); b.type='button'; b.dataset.ak=k+':set';
b.addEventListener('click',function(){accCapture=(accCapture===k)?null:k; accRender();}); w.appendChild(b);
if(accA[k]){var c=accEl('button','btn small','Clear'); c.type='button'; c.addEventListener('click',function(){accSet(k,'');}); w.appendChild(c);}
r.appendChild(w); return r;
}
function accStat(t){var s=document.getElementById('accStat'); if(s)s.textContent=t;}
function accRender(){
if(!accModal||accModal.hidden)return;
var top=accModal.scrollTop, fk=document.activeElement&&document.activeElement.dataset?document.activeElement.dataset.ak:null;
accModal.innerHTML='';
var p=accEl('section','quiz card'); p.id='accPanel'; p.tabIndex=-1; p.setAttribute('aria-label','Access settings'); p.setAttribute('role','dialog'); p.setAttribute('aria-modal','true');
var hd=accEl('div','qhead'), h2=accEl('h2','','Access settings'); hd.appendChild(h2); var x=accEl('button','btn small x','Done'); x.type='button'; x.id='accDone'; x.dataset.ak='done'; x.addEventListener('click',accClose); hd.appendChild(x); p.appendChild(hd);
var nt=accEl('div','accnote'); nt.appendChild(accEl('b','','For a teacher or parent: set this up once'));
nt.appendChild(accEl('span','','These settings stay on this device only. They do not change with the Learner or the class, and they are all off to begin with. Turn on only what the Learner needs, then let the Learner try it. A switch user, an eye-gaze user and a one-handed touch user can each have their own device. Tick "Lock" at the bottom so only a grown-up with the PIN can change them. A printable guide lists every setting and the device it suits.'));
p.appendChild(nt);
var st=accEl('p','accstat'); st.id='accStat'; st.setAttribute('role','status'); st.setAttribute('aria-live','polite'); p.appendChild(st);
var cur='', grp=null;
ACCI.forEach(function(it){
if(it.g&&it.g!==cur){cur=it.g; p.appendChild(accEl('h3','',cur)); grp=accEl('div','accgrp'); p.appendChild(grp);}
var row;
if(it.k==='scanMode')row=accSegRow('scanMode',[['Auto-scan (one switch)','auto'],['Step-scan (two switches)','step']]);
else if(it.k==='scanSpeed')row=accSegRow('scanSpeed',ACCSPEEDS.map(function(v){return [v+' s',v];}));
else if(it.k==='dwellMs')row=accSegRow('dwellMs',ACCDW.map(function(v){return [v+' s',Math.round(v*1000)];}));
else if(it.k==='hand')row=accSegRow('hand',[['Right side','r'],['Left side','l']]);
else if(it.k==='km')row=accSegRow('km',[['Standard','std'],['Left-hand keys','left'],['Right-hand and arrow keys','right']]);
else if(it.k==='scanKey'||it.k==='advKey')row=accKeyRow(it.k);
else if(it.k==='raRate')row=accRateRow(it);
else if(it.k==='raVoice')row=accVoiceRow(it);
else row=accSw(it.k);
if(it.sub&&!accA[it.sub])return; /* sub-options appear only when the main setting is on */
grp.appendChild(row);
if(it.k==='ra'&&accA.ra){var tb=accEl('button','btn small','Test the voice'); tb.type='button'; tb.dataset.ak='raTest'; tb.addEventListener('click',function(){accSay(['This is how the question will sound.']);}); var tr=accEl('div','accrow accsub'); tr.appendChild(tb); grp.appendChild(tr);}
if(it.k==='km'){grp.appendChild(accKeyTable());}
});
var ft=accEl('div','accfoot'); var gb=accEl('button','btn','Print the teacher guide'); gb.type='button'; gb.id='accGuide'; gb.dataset.ak='guide'; gb.addEventListener('click',accOpenGuide); ft.appendChild(gb);
var rb=accEl('button','btn','Switch everything off'); rb.type='button'; rb.id='accReset'; rb.dataset.ak='reset'; rb.addEventListener('click',function(){var keepLock=accA.lock; Object.keys(ACCDEF).forEach(function(k){accA[k]=ACCDEF[k];}); accA.lock=keepLock; accSave(); accApply(); accRender(); accStat('Everything is off. The lock was left as it was.');}); ft.appendChild(rb);
p.appendChild(ft);
if(accA.lock)p.appendChild(accEl('p','lvldesc','The lock is on. Opening these settings asked for the PIN.'));
accModal.appendChild(p); accModal.scrollTop=top;
if(fk){var f=accModal.querySelector('[data-ak="'+fk+'"]'); if(f)try{f.focus({preventScroll:true});}catch(e){}}
}
function accKeyTable(){
var d=accEl('div','accrow accsub'), t=accEl('table'); t.style.cssText='width:100%;border-collapse:collapse;font-size:.85rem'; t.setAttribute('aria-label','One-handed keys');
var rows=[['Action','Standard','Left-hand keys','Right-hand and arrow keys'],['Walk','W A S D','W A S D','Up and Down arrows'],['Turn','Left and Right arrows','Q and R','Left and Right arrows'],['Place block here','Click','F','Enter'],['Break block here','Right click','G','Backspace'],['Power up','E','E','U'],['Make (craft)','C','C','O'],['Break mode on/off','B','B','Y'],['Bag','I','Z','I'],['Menu','M','X','M'],['Pause','P','T','P'],['Next block page','Tab','Tab','Tab'],['Pick a block','1 to 8','1 to 5','Number keys or numpad'],['Jump','Space','Space','Space'],['Answer a question','1 to 6 or A to F','1 to 6 or A to F','Number keys or numpad'],['Next question','N or Enter','Space','N or Enter']];
rows.forEach(function(r,i){var tr=document.createElement('tr'); r.forEach(function(c){var e=document.createElement(i?'td':'th'); e.textContent=c; e.style.cssText='border:1px solid var(--line);padding:3px 5px;text-align:left'; tr.appendChild(e);}); t.appendChild(tr);});
var tw=accEl('div','acctw'); tw.appendChild(t); d.appendChild(accEl('p','','Key table (the standard keys always keep working as well):')); d.appendChild(tw); return d;
}
function accOpen(viaPin,opener){
if(accA.lock&&!viaPin&&!accPinOK){ accAskPin(); return; }
accFrom=modalOpen(); accOpener=opener||document.activeElement; accPinOK=false;
if(!accFrom){ try{pauseGame();}catch(e){} try{window.trayClose&&trayClose();}catch(e){} }
accModal.hidden=false; accRender(); accModal.scrollTop=0; try{document.getElementById('accPanel').focus({preventScroll:true});}catch(e){}
}
function accClose(){
accCapture=null; accModal.hidden=true; try{speechSynthesis.cancel();}catch(e){}
if(!accFrom){ try{closeModals();}catch(e){} } else if(accOpener&&accOpener.focus){ try{accOpener.focus({preventScroll:true});}catch(e){} }
}
function accAskPin(){
/* the lock reuses the existing PIN screen: the grown-up unlocks there and an Access settings button appears */
window.__accWant=true; var from=modalOpen(); openPlan();
try{ if(!from){ var mo=new MutationObserver(function(){ if(planModal.hidden){ mo.disconnect(); if(!modalOpen()&&accModal.hidden){ try{closeModals();}catch(e){} } } }); mo.observe(planModal,{attributes:true,attributeFilter:['hidden']}); } }catch(e){}
}
function accPlanBtn(parent){
/* called from the Teacher controls screen once the PIN has been accepted */
var box=mk('div','gpbox'); box.appendChild(mk('h4','','Access settings'));
box.appendChild(mk('p','lvldesc',window.__accWant?'The Access settings are locked. The PIN was right, so you can open them now.':'One-handed play, switch scanning, dwell click, read aloud, large buttons and more.'));
var b=mk('button','btn primary','Open Access settings'); b.type='button'; b.id='accFromPlan';
b.addEventListener('click',function(){ window.__accWant=false; accPinOK=true; var wasHidden=planModal.hidden; planModal.hidden=true; pinOK=false; accOpen(true); });
box.appendChild(b); parent.insertBefore(box,parent.firstChild);
}
document.addEventListener('click',function(e){
var b=e.target&&e.target.closest?e.target.closest('#accBtn,.acclink'):null; if(!b)return;
e.preventDefault(); if(b.id==='accBtn'){ accOpen(false,b); } else { accOpen(false,b); }
});
(function(){ /* the small "Access" link on the first screens */
function addLink(panelId){
var p=document.getElementById(panelId); if(!p||p.querySelector('.acclink'))return;
var d=document.createElement('p'); d.className='lvldesc'; d.style.margin='6px 0 0'; var b=accEl('button','acclink','Access'); b.type='button'; b.setAttribute('aria-label','Access settings'); d.appendChild(b); p.appendChild(d);
}
addLink('startPanel'); addLink('whoPanel');
})();
/* ---------- read aloud (browser SpeechSynthesis, no network) ---------- */
function accHasTTS(){try{return !!(window.speechSynthesis&&window.SpeechSynthesisUtterance);}catch(e){return false;}}
function accVoices(){try{return (speechSynthesis.getVoices()||[]).slice();}catch(e){return [];}}
function accEnVoices(){return accVoices().filter(function(v){return /^en([-_]|$)/i.test(v.lang||'')&&v.localService!==false;});}
function accPickVoice(){
var vs=accEnVoices(); if(!vs.length)return null;
if(accA.raVoice){var m=vs.filter(function(v){return v.name===accA.raVoice;})[0]; if(m)return m;}
var pref=['en-sg','en-gb','en-au','en-in','en-us','en'];
for(var i=0;i div'),function(w){
var q=accTxt(w.querySelector('p b')); if(q)out.push(q);
if(accA.raOpt){var ch=w.querySelector('.accch'); if(ch)out=out.concat(accOptParts([].slice.call(ch.querySelectorAll('button')))); }
});
return out;
}
/* exit tickets, Malay sayings, Chinese characters: a big text line followed by option buttons */
function accBigParts(big){
var out=[], ms=!!big.closest('#mlMod'), lang=ms?'ms-MY':null, prev=big.previousElementSibling;
if(prev&&prev.tagName==='P'&&!prev.classList.contains('lvldesc'))out.push({t:accTxt(prev),lang:lang});
out.push({t:accTxt(big),lang:lang});
var nxt=big.parentNode.querySelector('.cnopts'); if(accA.raOpt&&nxt)out=out.concat(accOptParts([].slice.call(nxt.querySelectorAll('button')).filter(accVis)).map(function(p){p.lang=lang; return p;}));
return out;
}
function accMkSpeak(getParts){
var b=accEl('button','btn small'); b.type='button'; b.className='btn small accspeak'; b.id=''; b.dataset.ak='speak';
b.setAttribute('aria-label','Read the question aloud');
b.innerHTML='Read aloud';
b.addEventListener('click',function(e){e.stopPropagation(); if(!accSay(getParts())){} });
return b;
}
function accRaSync(){
document.querySelectorAll('.accspeak').forEach(function(b){b.remove();});
accRaObs.forEach(function(o){try{o.disconnect();}catch(e){}}); accRaObs=[];
if(!(accA.ra&&accHasTTS()))return;
var qt=document.getElementById('qtext'); if(qt){var b=accMkSpeak(accQuizParts); b.id='accSpeak'; qt.parentNode.insertBefore(b,qt.nextSibling);}
var ei=document.getElementById('exIntro'); if(ei){var b2=accMkSpeak(accExamParts); b2.id='accSpeakEx'; ei.parentNode.insertBefore(b2,ei.nextSibling);}
accAddBig();
/* surfaces that are drawn again for each question (exit tickets, Malay sayings, Chinese characters) */
['etBody','mlBody','cnBody','hkBody'].forEach(function(id){var r=document.getElementById(id); if(!r)return; try{var o=new MutationObserver(function(){accAddBig();}); o.observe(r,{childList:true}); accRaObs.push(o);}catch(e){}});
/* feedback */
var fbRoots=['modal','exModal','etMod','mlMod','cnMod','hkModal']; var fo=new MutationObserver(function(ms){
if(!accA.ra||!accA.raFb)return; var hit=null;
ms.forEach(function(m){var n=m.target.nodeType===3?m.target.parentNode:m.target; var f=n&&n.closest?n.closest('#fb,.exfb,#exMsg,#etBody p[aria-live],#mlBody p[aria-live],#cnBody p[aria-live]'):null; if(f&&accTxt(f))hit=f;});
if(hit){clearTimeout(accFbT); accFbT=setTimeout(function(){accSay([accTxt(hit)]);},150);}
});
fbRoots.forEach(function(id){var r=document.getElementById(id); if(r)try{fo.observe(r,{childList:true,subtree:true,characterData:true});}catch(e){}}); accRaObs.push(fo);
/* read each new question by itself */
if(accA.raAuto&&qt){var ao=new MutationObserver(function(){ if(!accA.raAuto||document.getElementById('modal').hidden)return; clearTimeout(accAutoT); accAutoT=setTimeout(function(){accSay(accQuizParts());},500); }); ao.observe(qt,{childList:true,characterData:true,subtree:true}); accRaObs.push(ao);}
}
var accRaObs=[], accFbT=0, accAutoT=0;
function accAddBig(){
if(!accA.ra)return;
[].forEach.call(document.querySelectorAll('.mlbig'),function(big){
if(big.nextElementSibling&&big.nextElementSibling.classList.contains('accspeak'))return;
var b=accMkSpeak(function(){return accBigParts(big);}); big.parentNode.insertBefore(b,big.nextSibling);
if(accA.raAuto&&accVis(big))setTimeout(function(){accSay(accBigParts(big));},500);
});
}
function accVoiceRow(it){
var r=accRowShell(it,null), s=document.createElement('select'); s.setAttribute('aria-label','Voice'); s.dataset.ak='raVoice';
var o0=document.createElement('option'); o0.value=''; o0.textContent='Automatic (English voice on this device)'; s.appendChild(o0);
accEnVoices().forEach(function(v){var o=document.createElement('option'); o.value=v.name; o.textContent=v.name+' ('+v.lang+')'; if(accA.raVoice===v.name)o.selected=true; s.appendChild(o);});
s.addEventListener('change',function(){accSet('raVoice',s.value);}); r.appendChild(s);
if(!accEnVoices().length)r.appendChild(accEl('p','','No English voice stored on this device was found yet. The browser default will be used.'));
return r;
}
function accRateRow(it){
var r=accRowShell(it,null), w=accEl('div','accrange'), dn=accEl('button','btn small','Slower'), up=accEl('button','btn small','Faster'), rg=document.createElement('input'), val=accEl('span','accstat',Math.round(accA.raRate*100)+'%');
dn.type=up.type='button'; rg.type='range'; rg.min='0.5'; rg.max='1.5'; rg.step='0.1'; rg.value=String(accA.raRate); rg.setAttribute('aria-label','Speaking speed'); rg.dataset.ak='raRate';
function set(v){v=Math.max(.5,Math.min(1.5,Math.round(v*10)/10)); accSet('raRate',v);}
rg.addEventListener('change',function(){set(+rg.value);}); dn.dataset.ak='raRate:dn'; up.dataset.ak='raRate:up'; dn.addEventListener('click',function(){set(accA.raRate-.1);}); up.addEventListener('click',function(){set(accA.raRate+.1);});
w.appendChild(dn); w.appendChild(rg); w.appendChild(up); w.appendChild(val); r.appendChild(w); return r;
}
if(accHasTTS()){try{speechSynthesis.addEventListener('voiceschanged',function(){ if(!accModal.hidden&&accA.ra)accRender(); });}catch(e){}}
window.addEventListener('pagehide',function(){try{speechSynthesis.cancel();}catch(e){}});
/* ---------- choice instead of typing: the exam question ---------- */
var accExShowOrig=exShow;
exShow=function(){
accExShowOrig.apply(this,arguments);
try{accExChoices();}catch(e){}
try{if(accA.ra)accRaSync();}catch(e){}
};
function accExChoices(){
if(!accA.choice)return;
[].forEach.call(document.querySelectorAll('#exSteps input.exin'),function(inp){
var s=inp._s; if(!s||inp.dataset.acc)return; inp.dataset.acc='1'; inp.hidden=true; inp.tabIndex=-1;
var lab=inp.parentNode; if(lab&&lab.tagName==='LABEL'){lab.firstChild.textContent='Choose the answer'+(s.unit?' ('+s.unit+')':'')+': ';}
var grp=accEl('div','accch'); grp.setAttribute('role','radiogroup'); grp.setAttribute('aria-label','Choose your answer'); var opts=options({answer:s.ans});
opts.forEach(function(v){
var b=accEl('button','btn',String(v)); b.type='button'; b.setAttribute('role','radio'); b.setAttribute('aria-checked','false'); b.dataset.v=String(v);
b.addEventListener('click',function(){ if(inp.disabled)return; inp.value=String(v); [].forEach.call(grp.children,function(x){x.setAttribute('aria-checked',String(x===b));}); });
grp.appendChild(b);
});
lab.parentNode.insertBefore(grp,lab.nextSibling);
try{new MutationObserver(function(){ if(inp.disabled)[].forEach.call(grp.children,function(x){x.disabled=true;}); }).observe(inp,{attributes:true,attributeFilter:['disabled']});}catch(e){}
});
}
/* ---------- the exam message fix-up for choice mode ---------- */
var accExObs=null;
function accExFix(){
if(!accA.choice)return;
[].forEach.call(document.querySelectorAll('#exModal .exfb, #exMsg'),function(e){
if(e.textContent==='Type a number first.')e.textContent='Choose an answer first.';
else if(e.textContent==='Type an answer for every part first.')e.textContent='Choose an answer for every part first.';
});
}
(function(){ var m=document.getElementById('exModal'); if(!m)return; try{accExObs=new MutationObserver(accExFix); accExObs.observe(m,{childList:true,subtree:true,characterData:true});}catch(e){} })();
/* ---------- one-handed play: arrow pad, Place and Break buttons, tap to walk ---------- */
var accPad=null, accHold={up:0,down:0,left:0,right:0}, accDown={}, accKeep=0, accNudge={f:0,b:0,t:0}, accTgt=null, accMarker=null, accKeyHold={up:0,down:0}, accMoving=false, accRunOn=0, accJumpT=0;
function accBtn(parent,cls,label,aria,id){var b=accEl('button','accb '+cls,label); b.type='button'; b.setAttribute('aria-label',aria||label); if(id)b.id=id; parent.appendChild(b); return b;}
function accPadBuild(){
var view=document.getElementById('view'); if(!view||accPad)return;
accPad=accEl('div','accpad'); accPad.id='accPad'; accPad.setAttribute('role','group'); accPad.setAttribute('aria-label','One-handed controls'); accPad.dataset.side='r';
var pr=accEl('div','accpr'); accPad.appendChild(pr);
var pl=accBtn(pr,'place','Place','Place block here','accPlace'), br=accBtn(pr,'brk','Break','Break block here','accBreak');
pl.addEventListener('click',accPlaceHere); br.addEventListener('click',accBreakHere);
var cr=accEl('div','acccross'); accPad.appendChild(cr);
var dirs=[['up','▲','Walk forward'],['left','◀','Turn left'],['down','▼','Walk back'],['right','▶','Turn right']];
dirs.forEach(function(d){
var b=accBtn(cr,'arrow '+d[0],d[1],d[2],'accDir_'+d[0]); b.dataset.dir=d[0];
b.addEventListener('pointerdown',function(e){ if(e.button!==undefined&&e.button>0)return; e.preventDefault(); accHold[d[0]]=1; accDown[d[0]]=1; accKeep=0; accSyncToggles(); try{b.setPointerCapture(e.pointerId);}catch(_){} });
['pointerup','pointercancel','lostpointercapture'].forEach(function(ev){b.addEventListener(ev,function(){ accHold[d[0]]=0; setTimeout(function(){accDown[d[0]]=0;},500); });});
b.addEventListener('click',function(){ if(accDown[d[0]]){accDown[d[0]]=0; return;} accStep(d[0]); });
});
var tg=accEl('div','acctg'); accPad.appendChild(tg);
var bm=accBtn(tg,'tog','Break mode: off','Break mode, sticky toggle','accBrkMode'); bm.setAttribute('aria-pressed','false');
bm.addEventListener('click',function(){ try{brkSlot.click();}catch(e){} accSyncToggles(); });
var kw=accBtn(tg,'tog','Keep walking: off','Keep walking, sticky toggle','accKeep'); kw.setAttribute('aria-pressed','false');
kw.addEventListener('click',function(){ accKeep=accKeep?0:1; accTgt=null; accSyncToggles(); });
var rn=accBtn(tg,'tog','Run: off','Run, sticky toggle (in Singapore and zones)','accRun'); rn.setAttribute('aria-pressed','false'); rn.hidden=true;
rn.addEventListener('click',function(){ accRunOn=accRunOn?0:1; keys.ShiftLeft=!!accRunOn; accSyncToggles(); });
var jp=accBtn(tg,'tog','Jump','Jump','accJump');
jp.addEventListener('pointerdown',function(e){ e.preventDefault(); keys.Space=true; jp.dataset.dn='1'; try{jp.setPointerCapture(e.pointerId);}catch(_){} });
['pointerup','pointercancel','lostpointercapture'].forEach(function(ev){jp.addEventListener(ev,function(){keys.Space=false; setTimeout(function(){jp.dataset.dn='';},500);});});
jp.addEventListener('click',function(){ if(jp.dataset.dn){jp.dataset.dn=''; return;} keys.Space=true; setTimeout(function(){keys.Space=false;},180); });
view.appendChild(accPad);
try{new MutationObserver(accSyncToggles).observe(brkSlot,{attributes:true,attributeFilter:['aria-pressed','class']});}catch(e){}
}
function accSyncToggles(){
if(!accPad)return; var on=!!S.brk, bm=document.getElementById('accBrkMode'), kw=document.getElementById('accKeep'), rn=document.getElementById('accRun');
bm.textContent='Break mode: '+(on?'on':'off'); bm.setAttribute('aria-pressed',String(on));
kw.textContent='Keep walking: '+(accKeep?'on':'off'); kw.setAttribute('aria-pressed',String(!!accKeep));
rn.textContent='Run: '+(accRunOn?'on':'off'); rn.setAttribute('aria-pressed',String(!!accRunOn));
}
function accPadSync(){
var show=!!accA.one; if(show&&!accPad)accPadBuild(); if(!accPad)return;
accPad.hidden=!show; accPad.dataset.side=accA.hand==='l'?'l':'r'; accSyncToggles();
if(!show){accHold.up=accHold.down=accHold.left=accHold.right=0; accKeep=0;}
}
function accStep(d){ /* one press by keyboard, switch or dwell: a short walk or a small turn */
if(d==='up')accNudge.f=.3; else if(d==='down')accNudge.b=.3; else if(d==='left')accNudge.t+=.35; else accNudge.t-=.35;
accKeep=0; accTgt=null; accSyncToggles();
}
/* aim helper: choose the up-down angle that points at the block in front of the player, then use the game's own place and break */
function accAim(mode){
if(!camera)return false; var best=null, bestS=1e9, saveP=pitch;
var cands=mode==='place'?[-.56,-.42,-.7,-.3,-.85,-.15,-1.0]:[.15,0,-.15,-.3,-.45,-.6,-.8,-1.0,-1.2];
for(var i=0;i4.4)continue;
var sc=Math.abs(dist-2.4); if(sc0||(h.v<0&&h.y>=-D)))continue; var d2=Math.hypot(h.x+.5-player.x,h.z+.5-player.z)+Math.abs(h.y+.5-(player.y+.9))*.5;
if(d2>5)continue; var sc2=d2-(h.v>0?1.6:0); if(sc20?1:-1, sy=d.y>0?1:-1, sz=d.z>0?1:-1;
var tdx=d.x!==0?Math.abs(1/d.x):Infinity, tdy=d.y!==0?Math.abs(1/d.y):Infinity, tdz=d.z!==0?Math.abs(1/d.z):Infinity;
var tmx=d.x>0?(x+1-o.x)/d.x:d.x<0?(o.x-x)/-d.x:Infinity, tmy=d.y>0?(y+1-o.y)/d.y:d.y<0?(o.y-y)/-d.y:Infinity, tmz=d.z>0?(z+1-o.z)/d.z:d.z<0?(o.z-z)/-d.z:Infinity;
var t=0,fx=0,fy=0,fz=0;
for(var n=0;n<400;n++){
var v=getBlock(x,y,z); if(v!==0&&(fx||fy||fz))return {x:x,y:y,z:z,v:v,fx:fx,fy:fy,fz:fz,t:t};
if(tmxmaxD)break;
}
return null;
}
function accWalkTo(cx,cy){
if(paused||modalOpen()||!window.THREE||!camera||(TZ.cur&&TZ.cur.fly))return false;
var r=cv.getBoundingClientRect(); if(!r.width)return false;
var nx=((cx-r.left)/r.width)*2-1, ny=-((cy-r.top)/r.height)*2+1; camera.updateMatrixWorld(true);
var o=rayO().clone(), d=new THREE.Vector3(nx,ny,.5).unproject(camera).sub(camera.position).normalize();
var h=accRay(o,d,45);
if(!h){hint('Tap or click the ground to walk there.',true); return false;}
var tx=h.x+.5, tz=h.z+.5, ty;
if(h.fy===0){tx+=h.fx; tz+=h.fz; ty=h.y+.03;} else ty=(h.fy===1?h.y+1:h.y)+.03;
if(tx<.5||tz<.5||tx>LAND-.5||tz>LAND-.5){hint('That spot is outside the land.',true); return false;}
accTgt={x:tx,z:tz,t:0,lx:player.x,lz:player.z,lt:0,stuck:0}; accKeep=0; accSyncToggles();
if(THREE&&scene){
if(!accMarker){accMarker=new THREE.Mesh(new THREE.RingGeometry(.28,.42,28),new THREE.MeshBasicMaterial({color:0xffd24a,side:THREE.DoubleSide,transparent:true,opacity:.95,depthTest:false})); accMarker.rotation.x=-Math.PI/2; accMarker.renderOrder=998; scene.add(accMarker);}
accMarker.position.set(tx,ty,tz); accMarker.visible=true;
}
try{sfx('tick');}catch(e){}
return true;
}
function accStopWalk(msg){ accTgt=null; if(accMarker)accMarker.visible=false; if(msg)hint(msg,true); }
function accTick(dt){
if(accMarker&&accMarker.visible&&accTgt){var s=1+Math.sin(performance.now()/220)*.08; accMarker.scale.set(s,s,s);}
var anyHold=accHold.up||accHold.down||accHold.left||accHold.right||accKeyHold.up||accKeyHold.down, anyNudge=accNudge.f>0||accNudge.b>0||accNudge.t!==0;
if(!(anyHold||anyNudge||accKeep||accTgt)){ if(accMoving){accMoving=false; if(joy.id===null){joy.x=joy.y=0;}} return; }
if(paused||modalOpen()){ accKeep=0; accStopWalk(); accNudge.f=accNudge.b=accNudge.t=0; accSyncToggles(); return; }
if(joy.id!==null)return; /* a real touch joystick is in use */
var fwd=0, turn=0;
if(accHold.up||accKeyHold.up)fwd+=1; if(accHold.down||accKeyHold.down)fwd-=1; if(accKeep)fwd+=1;
if(accNudge.f>0){fwd+=1; accNudge.f-=dt;} if(accNudge.b>0){fwd-=1; accNudge.b-=dt;}
if(accHold.left)turn+=1; if(accHold.right)turn-=1;
if(accNudge.t!==0){var st=Math.min(Math.abs(accNudge.t),4*dt); var sg=accNudge.t>0?1:-1; yaw+=sg*st; accNudge.t-=sg*st; if(Math.abs(accNudge.t)<1e-4)accNudge.t=0;}
yaw+=turn*1.9*dt;
if(accTgt){
if(fwd||turn){ accStopWalk(); }
else{
var dx=accTgt.x-player.x, dz=accTgt.z-player.z, dist=Math.hypot(dx,dz);
if(dist<.42){ accStopWalk(); }
else{
var want=Math.atan2(-dx,-dz), diff=want-yaw; diff=Math.atan2(Math.sin(diff),Math.cos(diff)); var rate=5*dt;
yaw+=Math.max(-rate,Math.min(rate,diff)); if(Math.abs(diff)<.45)fwd=1;
accTgt.t+=dt; accTgt.lt+=dt;
if(accTgt.lt>.5){ var mv=Math.hypot(player.x-accTgt.lx,player.z-accTgt.lz); accTgt.lx=player.x; accTgt.lz=player.z; accTgt.lt=0;
if(mv<.25&&Math.abs(diff)<.6){ accTgt.stuck++; if(accTgt.stuck===1||accTgt.stuck===3){keys.Space=true; setTimeout(function(){keys.Space=false;},220);} if(accTgt.stuck>=5)accStopWalk('I could not walk all the way there. Try a spot a little closer, or go around.'); } else accTgt.stuck=0; }
if(accTgt&&accTgt.t>30)accStopWalk();
}
}
}
joy.x=0; joy.y=-Math.max(-1,Math.min(1,fwd)); accMoving=true;
}
/* tap or click on the 3D view: walk there (captured before the game's own look and place handlers) */
(function(){
var view=document.getElementById('view'), dn=null;
view.addEventListener('pointerdown',function(e){
if(!accA.walk||e.target!==cv||paused||modalOpen())return;
e.stopImmediatePropagation(); e.preventDefault(); if(e.button>0)return; dn={x:e.clientX,y:e.clientY,id:e.pointerId};
},true);
view.addEventListener('pointerup',function(e){
if(!accA.walk||!dn||e.pointerId!==dn.id)return; e.stopImmediatePropagation(); var m=Math.hypot(e.clientX-dn.x,e.clientY-dn.y); dn=null;
if(m<14&&e.target===cv)accWalkTo(e.clientX,e.clientY);
},true);
view.addEventListener('pointercancel',function(e){ if(dn&&e.pointerId===dn.id){dn=null;} },true);
view.addEventListener('pointermove',function(e){ if(accA.walk&&dn&&e.target===cv)e.stopImmediatePropagation(); },true);
})();
window.addEventListener('blur',function(){accHold.up=accHold.down=accHold.left=accHold.right=0; accKeyHold.up=accKeyHold.down=0;});
/* ---------- keyboard layouts for one hand, key capture, and the switch keys ---------- */
function accInField(t){return !!(t&&t.closest&&t.closest('input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),textarea,select,[contenteditable="true"]'));}
function accIsSwitch(code){ if(code==='Space'||code==='Enter'||code==='NumpadEnter')return 1; if(accA.scanKey&&code===accA.scanKey)return 1; return 0; }
function accIsAdv(code){ return accA.scanMode==='step'&&((accA.advKey&&code===accA.advKey)||(!accA.advKey&&(code==='Enter'||code==='NumpadEnter'))); }
function accKey(e,down){
if(e.ctrlKey||e.metaKey)return;
var c=e.code;
/* 1. choosing a switch key on the Access screen */
if(accCapture&&down){
e.preventDefault(); e.stopImmediatePropagation();
if(c==='Escape'){accCapture=null; accRender(); return;}
if(c==='Tab')return;
accA[accCapture]=c; accCapture=null; accSave(); accScanSync(); accRender(); accStat('Key chosen: '+accKeyName(c)+'.'); return;
}
if(accCapture&&!down){e.stopImmediatePropagation(); return;}
/* dwell pause shortcut */
if(down&&accA.dwell&&e.altKey&&c==='KeyD'){e.preventDefault(); accDwPause(!ACD.paused); return;}
if(e.altKey)return;
/* 2. Escape closes the Access screen or stops scanning */
if(down&&c==='Escape'){
if(ACS.on){e.preventDefault(); e.stopImmediatePropagation(); scStop('Scanning stopped.'); return;}
if(accModal&&!accModal.hidden){e.preventDefault(); e.stopImmediatePropagation(); accClose(); return;}
}
if(down&&c==='Escape'&&accA.scan&&accInField(e.target)){try{e.target.blur();}catch(x){} return;}
/* 3. switch keys */
if(accA.scan&&!accInField(e.target)&&(accIsSwitch(c)||accIsAdv(c))){
e.preventDefault(); e.stopImmediatePropagation();
if(down&&!e.repeat)scKey(c);
return;
}
/* 4. the one-handed keyboard layouts act only in the 3D world, never while a screen is open */
if(accA.km==='std'||accInField(e.target))return;
if(paused||modalOpen()){
if(accA.km==='left'&&down&&c==='Space'&&!e.repeat&&!document.getElementById('modal').hidden){ /* left hand: Space also means Next question */
var nb=document.getElementById('next'); if(nb&&!nb.hidden&&!(e.target&&e.target.closest&&e.target.closest('button'))){e.preventDefault(); e.stopImmediatePropagation(); nb.click();}
}
return;
}
var L=accA.km==='left';
function eat(){e.preventDefault(); e.stopImmediatePropagation();}
if(L){
if(c==='KeyQ'){eat(); keys.ArrowLeft=down; return;}
if(c==='KeyR'){eat(); keys.ArrowRight=down; return;}
if(!down||e.repeat)return;
if(c==='KeyF'){eat(); accPlaceHere();}
else if(c==='KeyG'){eat(); accBreakHere();}
else if(c==='KeyZ'){eat(); openBag();}
else if(c==='KeyX'){eat(); document.getElementById('menuBtn').click();}
else if(c==='KeyT'){eat(); setUserPause(!userPaused);}
}else{
if(c==='ArrowUp'&&!e.shiftKey){eat(); accKeyHold.up=down?1:0; return;}
if(c==='ArrowDown'&&!e.shiftKey){eat(); accKeyHold.down=down?1:0; return;}
if(!down||e.repeat)return;
if(c==='Enter'||c==='NumpadEnter'){eat(); accPlaceHere();}
else if(c==='Backspace'||c==='Delete'){eat(); accBreakHere();}
else if(c==='KeyU'){eat(); openPower();}
else if(c==='KeyO'){eat(); openCraft(false);}
else if(c==='KeyY'){eat(); brkSlot.click();}
}
}
window.addEventListener('keydown',function(e){accKey(e,true);},true);
window.addEventListener('keyup',function(e){accKey(e,false);},true);
/* ---------- switch scanning ---------- */
var ACS={on:false,groups:[],gi:0,ii:0,level:'group',timer:0,cycles:0,hl:[],started:0};
var ACSEL='button,a[href],input:not([type="hidden"]),select,textarea,summary,[role="button"],[role="switch"],[role="radio"]';
function scShown(e){
if(!e||e.hidden||e.disabled||e.getAttribute('aria-disabled')==='true'||e.closest('[hidden]')||e.closest('.acc-noscan,[aria-hidden="true"]'))return false;
var r=e.getBoundingClientRect(); if(r.width<2||r.height<2)return false;
if(r.bottom<0||r.right<0||r.top>innerHeight||r.left>innerWidth){ /* off screen: allowed only inside a scrolling list */ if(!e.closest('.tray,.modal,#hotbar'))return false; }
var cs=getComputedStyle(e); return cs.visibility!=='hidden'&&cs.display!=='none'&&cs.pointerEvents!=='none';
}
function scScope(){
var ro=document.getElementById('rareOv'); if(ro&&!ro.hidden)return {root:ro,kind:'pop'};
var gw=document.getElementById('gmWin'); if(gw&&!gw.hidden&&!document.getElementById('gmModal').hidden)return {root:gw,kind:'pop'};
var best=null, bz=-1;
[].forEach.call(document.querySelectorAll('.modal'),function(m){ if(m.hidden)return; var z=parseInt(getComputedStyle(m).zIndex,10)||0; if(z>=bz){bz=z; best=m;} });
if(best)return {root:best,kind:best.id==='gmModal'?'game':'modal'};
var tr=document.getElementById('tray'); if(tr&&!tr.hidden)return {root:tr,kind:'tray'};
var lo=[document.getElementById('pauseOv'),document.getElementById('lockOv')].filter(function(x){return x&&!x.hidden;})[0]; if(lo)return {root:lo,kind:'pop'};
return {root:document.getElementById('view'),kind:'hud'};
}
function scPri(e){ if(e.classList.contains('ans')||e.id==='next')return 0; if(e.classList.contains('accspeak'))return 1; if(e.id==='back'||/^(setBtn|accDone)$/.test(e.id))return 4; return 2; }
function scGather(){
var sc=scScope(), root=sc.root, groups=[], list;
if(sc.kind==='hud'){
var pick=function(sel,lab){var a=[].slice.call(document.querySelectorAll(sel)).filter(scShown); if(a.length)groups.push({label:lab,items:a});};
pick('#accPad button','Walk, turn, place and break'); pick('#earn,#bonusBtn,#craft,#homeHud,#menuBtn','Main buttons'); pick('#hotbar .slot','Blocks'); pick('#jump','Jump'); pick('.toolbar button','Top bar');
return groups;
}
if(sc.kind==='tray'){
var cur={label:'Menu',items:[]}; groups.push(cur);
[].forEach.call(root.children,function(ch){
if(ch.classList.contains('traycat')){ cur={label:accTxt(ch),items:[]}; groups.push(cur); return; }
if(ch.id==='subTray'){ [].forEach.call(ch.children,function(s){ if(s.classList.contains('traycat')){cur={label:accTxt(s),items:[]}; groups.push(cur);} else if(s.matches('button')&&scShown(s))cur.items.push(s); }); return; }
if(ch.matches('button')&&scShown(ch))cur.items.push(ch);
});
groups=groups.filter(function(g){return g.items.length;});
var mb=document.getElementById('menuBtn'); if(mb&&scShown(mb))groups.push({label:'Close the menu',items:[mb]});
return groups;
}
if(sc.kind==='game'&&window.AURA_GMSCAN&&!document.getElementById('gmPlay').hidden){
var gi=[]; try{gi=window.AURA_GMSCAN()||[];}catch(e){gi=[];}
gi=gi.filter(scShown); if(gi.length)groups.push({label:'Moves you can make',items:gi});
var rest=[].slice.call(document.querySelectorAll('#gmTools button,#gmView button,#gmUndo,#gmSwap,#gmNew,#gmBack,#gmX')).filter(scShown); if(rest.length)groups.push({label:'Game buttons',items:rest});
return groups;
}
list=[].slice.call(root.querySelectorAll(ACSEL)).filter(scShown);
/* a hidden number input or similar is not a choice */
list=list.filter(function(e){return !(e.tagName==='INPUT'&&e.type==='range');});
list=list.map(function(e,i){return [scPri(e),i,e];}).sort(function(a,b){return a[0]-b[0]||a[1]-b[1];}).map(function(x){return x[2];});
if(list.length<=8){ if(list.length)groups.push({label:'Choices',items:list}); return groups; }
for(var i=0;i=ACS.groups.length){ACS.gi=0; ACS.cycles++;}
}else{
ACS.ii++; if(ACS.ii>=g.items.length){ ACS.ii=0; ACS.cycles++; if(ACS.groups.length>1)ACS.level='group'; }
}
if(auto&&ACS.cycles>=3){ scStop('Scanning paused. Press your switch to start again.'); return; }
scShow(); scTimer();
}
function scStart(){
var gs=scGather(); if(!gs.length){scBar('Nothing to choose here.',''); setTimeout(function(){if(!ACS.on)scBar('');},1800); return;}
ACS.groups=gs; ACS.gi=0; ACS.ii=0; ACS.cycles=0; ACS.mode=accA.scanMode; ACS.level=gs.length>1?'group':'item'; ACS.on=true; ACS.started=Date.now();
scShow(); scTimer();
}
function scSelect(){
var g=ACS.groups[ACS.gi]; if(!g){scStop(); return;}
if(ACS.level==='group'){
if(g.items.length===1){ACS.level='item'; ACS.ii=0; scSelect(); return;}
ACS.level='item'; ACS.ii=0; ACS.cycles=0; scShow(); scTimer(); return;
}
var el=g.items[ACS.ii]; scStop('');
if(!el||!document.body.contains(el))return;
if(el.tagName==='INPUT'&&/^(text|number|email|password|search|tel|url)$/.test(el.type)||el.tagName==='TEXTAREA'||el.tagName==='SELECT'){ try{el.focus();}catch(e){} return; }
if(el.id==='jump'){ keys.Space=true; setTimeout(function(){keys.Space=false;},200); return; }
el.click();
}
function scKey(code){
if(!accA.scan)return;
if(accA.scanMode==='step'){
if(accIsAdv(code)){ if(!ACS.on){scStart();} else scAdvance(false); return; }
if(!ACS.on){scStart(); return;} scSelect(); return;
}
if(!ACS.on){scStart(); return;} scSelect();
}
function accScanSync(){ if(!accA.scan&&ACS.on)scStop(''); if(!accA.scan)scBar(''); }
/* the scan highlight must follow a screen that changes under it */
document.addEventListener('visibilitychange',function(){ if(document.hidden&&ACS.on)scStop(''); });
/* ---------- dwell click for eye-gaze and head pointers ---------- */
var ACD={on:false,paused:false,el:null,start:0,fired:false,x:0,y:0,px:-1,py:-1,since:0,raf:0,scrollUntil:0,ring:null,pbtn:null,moved:false};
var ACDSEL='button,a[href],summary,[role="button"],[role="switch"],[role="radio"],input[type="checkbox"],input[type="radio"],.slot,label.chk';
function acdTarget(n){
if(!n||!n.closest)return null;
if(ACD.paused){ var pb=n.closest('#accDwellPause'); return pb||null; }
if(n===cv||n.id==='gl')return accA.walk?cv:null;
var e=n.closest(ACDSEL); if(!e||e.disabled||e.getAttribute('aria-disabled')==='true'||e.closest('[hidden]'))return null;
if(e.closest('#accDwell'))return null; return e;
}
function accDwSync(){
var on=!!accA.dwell;
if(on&&!ACD.on){ACD.on=true; ACD.since=performance.now(); ACD.px=ACD.py=-1; acdBuild();}
if(!on&&ACD.on){ACD.on=false; acdReset(); if(ACD.pbtn)ACD.pbtn.hidden=true; if(ACD.ring)ACD.ring.hidden=true;}
if(ACD.pbtn)ACD.pbtn.hidden=!on;
}
function acdBuild(){
if(!ACD.ring){
var r=accEl('div'); r.id='accDwell'; r.hidden=true; r.setAttribute('aria-hidden','true'); r.innerHTML=''; document.body.appendChild(r); ACD.ring=r;
}
if(!ACD.pbtn){
var p=accEl('button','btn',''); p.type='button'; p.id='accDwellPause'; p.addEventListener('click',function(){accDwPause(!ACD.paused);}); document.body.appendChild(p); ACD.pbtn=p; accDwPause(false);
}
ACD.pbtn.hidden=false;
}
function accDwPause(v){
ACD.paused=!!v; if(!ACD.pbtn)return;
ACD.pbtn.textContent=v?'Dwell paused. Resume':'Pause dwell'; ACD.pbtn.setAttribute('aria-pressed',String(!!v)); ACD.pbtn.setAttribute('aria-label',v?'Dwell click is paused. Resume dwell click':'Pause dwell click (Alt and D)');
if(v)acdReset();
}
function acdReset(){
if(ACD.el&&ACD.el.classList)ACD.el.classList.remove('acc-dw'); ACD.el=null; ACD.fired=false; cancelAnimationFrame(ACD.raf); ACD.raf=0; if(ACD.ring)ACD.ring.hidden=true;
}
function acdFrame(now){
ACD.raf=0; if(!ACD.on||!ACD.el)return;
if(document.hidden){acdReset(); return;}
if(now=1){
/* the thing under the pointer must still be the thing we were dwelling on */
var under=document.elementFromPoint(ACD.x,ACD.y), t=acdTarget(under);
if(t===ACD.el){ ACD.fired=true; if(ACD.ring)ACD.ring.hidden=true; acdFire(t); }
else { acdReset(); return; }
}
if(!ACD.fired)ACD.raf=requestAnimationFrame(acdFrame);
}
function acdFire(t){
if(t===cv){ accWalkTo(ACD.x,ACD.y); return; }
if(t.id==='jump'||t.id==='accJump'){ keys.Space=true; setTimeout(function(){keys.Space=false;},200); return; }
t.click();
}
document.addEventListener('pointermove',function(e){
if(!ACD.on)return;
if(!e.isTrusted||e.pointerType==='touch')return;
var now=performance.now();
if(now-ACD.since<700)return; /* never act just after the page or the setting turned on */
if(e.clientX===ACD.px&&e.clientY===ACD.py)return; /* the browser repeats the last position after a screen change: not a real move */
ACD.px=e.clientX; ACD.py=e.clientY; ACD.x=e.clientX; ACD.y=e.clientY;
var t=acdTarget(e.target);
if(t!==ACD.el){
if(ACD.el&&ACD.el.classList)ACD.el.classList.remove('acc-dw');
ACD.el=t; ACD.fired=false; ACD.start=now;
if(t){ if(t!==cv)t.classList.add('acc-dw'); cancelAnimationFrame(ACD.raf); ACD.raf=requestAnimationFrame(acdFrame); }
else { cancelAnimationFrame(ACD.raf); ACD.raf=0; if(ACD.ring)ACD.ring.hidden=true; }
}
},true);
/* ---------- Game room: the legal moves that switch scanning and dwell can use ---------- */
/* (AURA_GMSCAN is defined inside the Game room code, next to the rules) */
/* ---------- the printable teacher guide ---------- */
function accGuideHTML(){
function esc(s){return String(s).replace(/&/g,'&').replace(//g,'>');}
var DV=['t','m','k','s','g'], DN={t:'Touch screen',m:'Mouse',k:'Keyboard',s:'Switch',g:'Eye-gaze'};
var cell=function(v){return v===2?'
Main use
':v===1?'
Works
':'
-
';};
var rows='', cur='';
ACCI.forEach(function(it){
if(it.g&&it.g!==cur){cur=it.g; rows+='
'+
[['Walk','W A S D','W A S D','Up and Down arrows'],['Turn','Left and Right arrows','Q and R','Left and Right arrows'],['Place block here','Click','F','Enter'],['Break block here','Right click','G','Backspace'],['Power up','E','E','U'],['Make (craft)','C','C','O'],['Break mode on and off','B','B','Y'],['Bag','I','Z','I'],['Menu','M','X','M'],['Pause','P','T','P'],['Next block page','Tab','Tab','Tab'],['Pick a block','1 to 8','1 to 5','Number keys or numpad'],['Jump','Space','Space','Space'],['Answer a question','1 to 6, or A to F','1 to 6, or A to F','Number keys or numpad'],['Next question','N or Enter','Space','N or Enter'],['Close a screen','Esc','Esc','Esc']].map(function(r){return '
'+r.map(function(c,i){return (i?'
':'
')+esc(c)+(i?'':'
');}).join('')+'
';}).join('')+'
';
return 'Aura Builders: Access guide for teachers
Aura Builders: Access guide for teachers
A short guide for setting up the game for a Learner with one usable arm, a switch, eye-gaze, a touch screen or a mouse.
'+
''+
'
Set up in five steps
Open the game on the Learner\'s own device. Choose Menu, then Access settings. On the first screens there is also a small Access link.
'+
'
Turn on only what the Learner needs. Everything is off to begin with, and a Learner who does not use these settings sees no change.
Let the Learner try it and adjust the speed, the dwell time or the side.
'+
'
Optional: choose Lock these settings with the teacher PIN. The lock uses the same PIN as Teacher controls, so create that PIN first.
The settings are saved on this device only. Another device, or another Learner on the same device, uses the same settings. Use Switch everything off to reset.
'+
'
Every setting, and the device it suits
Setting
What it does
'+DV.map(function(x){return '
'+DN[x]+'
';}).join('')+'
'+rows+'
'+
'
AAC users: the game does not replace a communication aid. Learners who use AAC usually answer with touch, a switch or eye-gaze, so use the columns for the way the Learner points. Read aloud supports Learners who cannot read the question; choice instead of typing means no number needs to be typed or drawn.
'+
'
One-handed keyboard layouts
The standard keys always keep working. A layout only adds keys.
'+'
'+km+'
'+
'
Which combination for which Learner
Learner
Suggested settings
'+
'
One usable arm, touch screen
Large buttons, One-handed controls, Tap or click to walk, Controls on the side of the working hand, Choice instead of typing.
'+
'
One usable arm, mouse and keyboard
One-handed controls or a keyboard layout for the working hand, Tap or click to walk, Choice instead of typing.
'+
'
One switch
Switch scanning (auto-scan, speed 2 to 3 seconds to start), Large buttons, Choice instead of typing, No timers, Read aloud with Speak each button.
'+
'
Two switches
Switch scanning with Step-scan, Large buttons, Choice instead of typing, No timers.
'+
'
Eye-gaze (pointer mode)
Dwell click (start at 1.5 seconds), Large buttons, Tap or click to walk, Choice instead of typing, No timers, Reduce motion.
'+
'
Please check on the real device
Switch hardware sends different keys. If Space and Enter do not work, use Switch key to choose the key your interface sends.
Eye-gaze systems that already click for the Learner do not need dwell click here. Turn it on only if the gaze software moves the pointer without clicking.
Voices differ between devices. Choose a voice in the Access screen and press Test the voice.
Typing a name or class code cannot be done by switch scanning alone. Set up the sign-in for the Learner first.
'+
'
Safety. The learn-first rule, hearts, storms and block rules do not change. "No timers" removes time pressure only. Eye breaks are not forced when it is on, so please give the Learner breaks yourself.
'+
'';
}
function accOpenGuide(){
var h=accGuideHTML(), w=null;
try{var url=URL.createObjectURL(new Blob([h],{type:'text/html'})); w=window.open(url,'_blank');}catch(e){}
if(!w){ accStat('Your browser blocked the new window. Use the link below to open the guide.'); var a=document.getElementById('accGuideLink'); if(!a){a=accEl('a','','Open the teacher guide'); a.id='accGuideLink'; a.target='_blank'; a.rel='noopener'; a.style.display='block'; a.style.margin='6px 0'; document.getElementById('accPanel').appendChild(a);} try{a.href=URL.createObjectURL(new Blob([h],{type:'text/html'}));}catch(e){} }
}
accApply();
/* ===== end Access settings block ===== */
/* ===== Home pests (Aura Builder mode). Optional, default OFF. Small 3D pests appear only beside a mess the Learner leaves; cleaning the mess (no questions) or answering 3 questions clears them. No scoring, no penalty, no blocks lost. ===== */
(function(){
var PZI={"ant":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVR42o2SMQ7DIAxFvz1FypIpY5K5V+gpepKeKlulXqIsPQBrqNSpU5dKnZpOIAtsBAsG/jcPG0CM42HZ5dx6pgpjnK9zPdUSncAAgCt+cD6oWrbM6zyl9TpPJjpbBJfHU42rCeQt5/sNWpyTkDxwPpAULJ8vACD0XTJETawJWTWI5jhC36mF5FqLWlrNEk2KNkGwZTRSy9rnkUZp1nSs3b6/3gV2vhc9Zhtb3l8ksL5rTVN0wflANA6FkcYhmSUFtaJadH87k3EhQNX9ZwAAAABJRU5ErkJggg==","cockroach":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAy0lEQVR42o1SMQ6DMAw8okxdIvUBJHN4QobufKE/4T88pANPgBn6AKQunSK5Q0UVkO2SyfKdz87pgMNLMVCKgc72KwjvSB6mmeUaSaD3NZzNcDaj97VEkwXuy5OtVYHt7BQDOZt/fWfzDmMFUgw0THPFGaVxKolYXgAAr2xZIy03fAsXAMAyrvDNtUC/F5wyEQB8c8Uyrhpl74EkomXEaEGRtpdc82+7JLLNGC2m0hfKGdbEx/w+1WOTqF2jBu0IpBioayN1bSQO2+oPAyRlfXeAwUgAAAAASUVORK5CYII=","gecko":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvklEQVR42qVSMRKEIAwMDL+4Qmora/2MT/ApPOAKSh+izJVU1vwECy8MMAmOYyogu8lmA0Ajpl7HqdexhVEt8ryM18VA3I8gKJzkyNvapfu2dsApkZzc7+8D1LnGKUruk7EEmczC+wDDoIs3axygJ6LugOd5GQuyNS7hckOLLWACC9WdqU1IuAnvQzNPjkB5kY9BjpCbaI0jPaA+lqTI+xGENa4g4xsqRLUqSfrvn/uyHE61HGaLPNnCXbwucAJrj3wySLovRAAAAABJRU5ErkJggg==","housefly":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA6klEQVR42mNgwAOstRT+W2sp/MenhhGbJhj7C5cEAwMDAwPPtxdw+aPXHqDoYUHXvFReDs73f/2LgYGBgQFZLJqB4T+yISz4nLdRlI2BEGDCJ+n/+hfcFUQbEP3wEU7F2OQYkf0/ZdMxBgYGBoYcPyt4AMIAz7cXDMjysHBgxKaZSVIDqwv+Pb+BYQiGF2AKsAFschgG5PhZMUyaOQ9D4aSZ8xhy/KwwxFmw2ZIQEYShMCEiiGEBPhdgMx0XQFbLhC15EgNQYgE9H6BHIXJUolvIgqwZJmGtxfAfW2ZCyCPUMuLLysgG4PImABSHanZwpeZ0AAAAAElFTkSuQmCC","mosquito":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA+UlEQVR42pVTIQ7CQBCca1CkBgcGKkkNpoLQP4BFYzDAYwCDwZGg+UN5QoOsKk0QtKRB0IRDbXNct7Ss3JvZm9m5Eygp17Zk2mzDfEbw/ECU4QyV4NqWREXpOEHN7ekMAFiMRzk4bbYBAOYzynsqzvMD0dBvIEBrOcXk9gIAHHpd3DdHVpFQpRGZitRwfdqL0P0ReDWffZHWu32BXBhAQ/INd/oAgPf1kp/riRicLwI9khiPJGaJbIwE5OJ0bUvSkJ9x06FrWzJLQ5mloVR7bArqdBWovwMdU7Cl3sApqJSvWxg4Qzlwhj8tGGWLKkuntgLu49T5bH/XB1VklK9+9qt1AAAAAElFTkSuQmCC","mouse":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwklEQVR42mNgwANupkb8ZyAXWGsp/P/95dn/31+e/bfWUsBpECMuzf6hEShiG1evYDh67QGGeiZsmhkYGBjCTh2Di8HY2FzCiK65Ly2PgYGBgeHg+1coCu0FxRgYGBgYimZNQnEJEwOFgGIDGHGFAbZAZGBgwAhIrC5A14xLDMMAbNGHbgh6TAx8IJJlwMkJff9xxsLJCX3/0RMRemIyLyjCnpBOTuj7b15QxAiLMmzRiKwZZ2ZCjhGYrdg0MzAwMAAAbvBKMeoggzIAAAAASUVORK5CYII="};
var PZ={on:false,g:null,mess:[],pests:[],tex:{},dots:[],puffs:[],puffTex:null,sp:0,last:0,classOff:false,nid:1,tip:'',chipKey:'',q:null,qt:0,gm:null,mats:{}};
var KD=[ /* k: name, size (m, enlarged so a child can see it), flying, speed */
{n:'ant',N:'ant',s:.26,f:0,v:.9,i:'ant',tip:'Ants follow crumbs. Wipe up spills and keep food covered, and the ants will not come back.'},
{n:'cockroach',N:'cockroach',s:.32,f:0,v:.8,i:'cockroach',tip:'Cockroaches look for food and rubbish. Cover food, wipe up spills and keep rubbish in a closed bin.'},
{n:'gecko',N:'house gecko',s:.42,f:0,v:.25,i:'gecko',tip:'House geckos are harmless and eat insects such as mosquitoes and flies. Let it stay, it is helping your home.'},
{n:'housefly',N:'housefly',s:.26,f:1,v:1.6,i:'housefly',tip:'Flies come to open food and rubbish. Cover food and put rubbish in a closed bin.'},
{n:'mosquito',N:'mosquito',s:.28,f:1,v:1.4,i:'mosquito',tip:'Mosquitoes breed in standing water. Turn pails over, change vase water often, clear roof gutters and cover bamboo pole holders. This is the Mozzie Wipeout habit.'},
{n:'mouse',N:'mouse',s:.36,f:0,v:.7,i:'mouse',tip:'Mice look for open food and rubbish. Keep food in boxes with lids and rubbish in a closed bin.'}
];
var MS=[ /* mess pieces: name, button, who comes (kinds), minutes-of-play before the first visitor, clean-up button, clean-up line */
{n:'crumbs',b:'Crumbs',k:[0,1],d:25,act:'Wipe up the crumbs',done:'Crumbs wiped up. No crumbs, no ants.'},
{n:'rubbish bag',b:'Rubbish bag',k:[3,1,5],d:40,act:'Tie up the bag and bin it',done:'Bag tied up and put in a closed bin. Closed bins keep flies out.'},
{n:'pail of water',b:'Pail of water',k:[4],d:60,act:'Tip out the water',done:'Water tipped out and the pail turned over. No standing water, no mosquitoes.'}
];
var CAP=6, PER=2, DAILY=12, NEED=3;
function st(mk){if(!S.st.pz&&mk)S.st.pz={}; return S.st.pz||null;}
function allowed(){return !(S.pl&&S.pl.hp===0)&&!PZ.classOff;}
function rm(){return !!reduceMotion||document.documentElement.classList.contains('acc-rm');}
function active(){
if(!PZ.on||document.hidden||paused||typeof visiting==='undefined'||visiting||TZ.cur||terr==='ocean'||boarded)return false;
try{if(modalOpen())return false;}catch(e){return false;}
try{if(JC&&JC.some(function(b){return Math.hypot(player.x-b.x,player.z-b.z)<5.2;}))return false;}catch(e){}
return true;
}
/* ---------- drawing: shared geometry and materials, built only when first needed ---------- */
function T(){return window.THREE;}
function icon(k){
if(PZ.tex[k])return PZ.tex[k]; var im=new Image(), t=new (T().Texture)(im); t.magFilter=T().NearestFilter; t.minFilter=T().NearestFilter; t.generateMipmaps=false;
im.onload=function(){t.needsUpdate=true;}; im.src=PZI[KD[k].i]; PZ.tex[k]=t; return t;
}
function dotTex(n){
if(PZ.dots[n])return PZ.dots[n]; var c=document.createElement('canvas'); c.width=48; c.height=16; var x=c.getContext('2d'); x.lineWidth=2;
for(var i=0;i=8)return; var s=new (T().Sprite)(new (T().SpriteMaterial)({map:PZ.puffTex,transparent:true,depthWrite:false})); s.position.set(x,y+.2,z); s.scale.set(.2,.2,1); group().add(s); PZ.puffs.push({s:s,t:0});
}
/* ---------- state ---------- */
function today(){return ymd(new Date());}
function persist(){
var s=st(true); s.on=PZ.on?1:0; s.m=PZ.mess.map(function(m){return [m.t,+m.x.toFixed(2),+m.y.toFixed(2),+m.z.toFixed(2),Math.round(m.a),m.id];});
s.p=PZ.pests.map(function(p){return [p.k,p.m,p.got|0];}); s.i=PZ.nid; try{save();}catch(e){}
}
function load(){
var s=st(false); PZ.mess=[]; PZ.pests=[]; if(!s)return; PZ.nid=s.i|0||1;
(Array.isArray(s.m)?s.m:[]).slice(0,CAP).forEach(function(r){if(r&&r[0]>=0&&r[0]<3)PZ.mess.push({t:r[0]|0,x:+r[1]||0,y:+r[2]||0,z:+r[3]||0,a:+r[4]||0,id:r[5]|0,o:null});});
(Array.isArray(s.p)?s.p:[]).slice(0,CAP).forEach(function(r){if(r&&r[0]>=0&&r[0]<6&&messOf(r[1]|0))PZ.pests.push({k:r[0]|0,m:r[1]|0,got:Math.min(NEED-1,r[2]|0),ph:Math.random()*6.28,r:.5+Math.random()*.3,o:null,d:null,idle:0});});
}
function clearView(){PZ.mess.forEach(function(m){if(m.o){group().remove(m.o); m.o=null;}}); PZ.pests.forEach(disposePest); PZ.puffs.forEach(function(f){group().remove(f.s);}); PZ.puffs=[];}
function showAll(){clearView(); PZ.mess.forEach(buildMess); PZ.pests.forEach(buildPest);}
function setOn(v){
if(v&&!allowed()){hint('Your teacher has turned home pests off.',true); ui(); return;}
PZ.on=!!v; var s=st(v); if(s)s.on=v?1:0;
if(v){load(); showAll();} else {clearView(); if(PZ.g)PZ.g.visible=false; hideChip();}
if(s)persist(); ui(); hint(v?'Home pests are on. They only come if you leave a mess, and you can always clean it up.':'Home pests are off.',true);
}
/* ---------- putting out a mess ---------- */
function put(t){
if(!PZ.on)return; var why='';
if(visiting||TZ.cur||terr==='ocean'||boarded)why='Go back to your own land to put out a mess.';
else if(!player.onGround)why='Stand on the floor first.';
else if(PZ.mess.length>=CAP)why='That is enough mess for now. Clean one up first.';
else if(player.x<1||player.z<1||player.x>LAND-1||player.z>LAND-1)why='Put it down on your own land.';
else {try{if(JC&&JC.some(function(b){return Math.hypot(player.x-b.x,player.z-b.z)<5.2;}))why='Not beside the Town Centre. Try your own house or garden.';}catch(e){}}
if(why){hint(why,true); return;}
var x=player.x,z=player.z,d=[1.5,1,.5],i;
for(i=0;i<3;i++){var cx=player.x-Math.sin(yaw)*d[i], cz=player.z-Math.cos(yaw)*d[i]; if(!solidAt(Math.floor(cx),Math.floor(player.y+.2),Math.floor(cz))&&!solidAt(Math.floor(cx),Math.floor(player.y+1),Math.floor(cz))){x=cx;z=cz;break;}}
var m={t:t,x:x,y:player.y,z:z,a:0,id:PZ.nid++,o:null}; PZ.mess.push(m); buildMess(m); persist(); closeTray();
hint('You put out '+(t===0?'some crumbs':t===1?'a rubbish bag':'a pail of water')+'. Pests may visit it. Clean it up to stop them.',true);
}
function closeTray(){try{var tr=document.getElementById('tray'); if(tr&&!tr.hidden)document.getElementById('menuBtn').click();}catch(e){}}
/* ---------- clearing ---------- */
function removePest(p,fx){var i=PZ.pests.indexOf(p); if(i<0)return; if(fx&&p.o)puff(p.o.position.x,p.o.position.y,p.o.position.z); disposePest(p); PZ.pests.splice(i,1);}
function cleanMess(m){
var s=st(true), had=PZ.mess.length;
PZ.pests.slice().forEach(function(p){if(p.m===m.id&&p.k!==2)removePest(p,true);});
puff(m.x,m.y,m.z); if(m.o)group().remove(m.o); PZ.mess.splice(PZ.mess.indexOf(m),1);
PZ.pests.forEach(function(p){if(p.m===m.id){p.m=PZ.mess.length?PZ.mess[0].id:0;}});
var line=MS[m.t].done, tidy=false;
if(!PZ.mess.length&&s.td!==today()){s.td=today(); s.t=(s.t|0)+1; tidy=true;}
persist(); ui(); say(line+(tidy?' Tidy home ✓':''));
}
function say(t){hint(t,true);}
function shoo(p){
if(p.k===2){say(KD[2].tip); return;}
if(!(S.pl&&S.pl.pq===0)){
PZ.q=p; PZ.qt=0; say('Answer '+NEED+' questions correctly to shoo the '+KD[p.k].N+'. Choose a subject.');
try{openPower();}catch(e){} if(!modalOpen())PZ.q=null; return;
}
clearPest(p);
}
function clearPest(p){
var tip=KD[p.k].tip; removePest(p,true); persist(); PZ.tip=tip; return tip;
}
/* the question window: credit a correct answer to the pest being shooed. This wraps the normal answer routine; the answer is recorded and rewarded exactly as usual. */
var _ra=resolveAnswer;
resolveAnswer=function(correct){
var live=!locked2&&qActive, p=PZ.q; _ra.apply(this,arguments);
try{
if(!live||!p||!correct||PZ.pests.indexOf(p)<0)return;
p.got=(p.got|0)+1; var f=document.getElementById('fb'), tip='';
if(p.got>=NEED){PZ.q=null; tip=clearPest(p); if(f)f.textContent=(f.textContent+' The '+KD[p.k].N+' is gone. '+tip).trim();}
else {if(p.d)p.d.material.map=dotTex(p.got); persist(); if(f)f.textContent=(f.textContent+' '+KD[p.k].N.charAt(0).toUpperCase()+KD[p.k].N.slice(1)+': '+p.got+' of '+NEED+'.').trim();}
}catch(e){}
};
var _sq=showQuestion;
showQuestion=function(){
_sq.apply(this,arguments);
try{if(PZ.q&&PZ.pests.indexOf(PZ.q)>=0&&mode==='play'){var d='',i; for(i=0;i0;
if(!ok){hideChip(); return;}
if(!PZ.mess.length&&!PZ.pests.length){hideChip(); return;}
if(PZ.q){PZ.qt+=dt; if(PZ.qt>.6)PZ.q=null;}
if(PZ.tip){var tp=PZ.tip; PZ.tip=''; say(tp);}
PZ.mess.forEach(function(m){m.a+=dt;});
PZ.sp+=dt; if(PZ.sp>=18){PZ.sp=0; spawn();}
var i,ins=0; for(i=PZ.pests.length-1;i>=0;i--){var p=PZ.pests[i]; if(p.k!==2&&!messOf(p.m)){removePest(p,false); continue;} place(p,dt); if(p.k!==2)ins++;}
PZ.pests.forEach(function(p){if(p.k===2){p.idle=ins?0:(p.idle||0)+dt; if(p.idle>40)removePest(p,false);}});
for(i=PZ.puffs.length-1;i>=0;i--){var f=PZ.puffs[i]; f.t+=dt; var u=f.t/.5; if(u>=1){group().remove(f.s); f.s.material.dispose(); PZ.puffs.splice(i,1);} else {f.s.scale.set(.2+u*.5,.2+u*.5,1); f.s.material.opacity=1-u;}}
chipTick();
}
function spawn(){
var s=st(true), td=today(); if(s.d!==td){s.d=td; s.n=0;}
if(PZ.pests.length>=CAP||(s.n|0)>=DAILY)return;
var hasG=PZ.pests.some(function(p){return p.k===2;}), ins=PZ.pests.filter(function(p){return p.k!==2;});
if(Math.random()<(hasG?.35:.7)){
var c=PZ.mess.filter(function(m){return m.a>=MS[m.t].d&&ins.filter(function(p){return p.m===m.id;}).length=90;}), k=ks[Math.floor(Math.random()*ks.length)];
var p={k:k,m:m.id,got:0,ph:Math.random()*6.28,r:.5+Math.random()*.3,o:null,d:null,idle:0}; PZ.pests.push(p); buildPest(p); s.n=(s.n|0)+1; persist(); return;}
}
if(ins.length&&!hasG&&PZ.pests.length',plant:'',book:'',frame:'',clock:''};
function crSmallIcon(k){return '';}
var CR_SMALL=[['cup','Cup'],['plant','Plant'],['book','Books'],['frame','Photo frame'],['clock','Wall clock']];
/* ---- calendar: real Singapore date, rest days, public holidays (Stage 1) ----
Public holidays 2026 and 2027: Ministry of Manpower lists (mom.gov.sg), read 30 Oct 2026. checked against MOM, 30 Oct 2026. Hari Raya and Vesak dates can be announced or moved, and an in-lieu Monday applies only when the holiday falls on a Sunday. */
var CR_HOL={'2026-01-01':"New Year's Day",'2026-02-17':'Chinese New Year','2026-02-18':'Chinese New Year','2026-03-21':'Hari Raya Puasa','2026-04-03':'Good Friday','2026-05-01':'Labour Day','2026-05-27':'Hari Raya Haji','2026-05-31':'Vesak Day','2026-06-01':'Vesak Day (day off in lieu)','2026-08-09':'National Day','2026-08-10':'National Day (day off in lieu)','2026-11-08':'Deepavali','2026-11-09':'Deepavali (day off in lieu)','2026-12-25':'Christmas Day',
'2027-01-01':"New Year's Day",'2027-02-06':'Chinese New Year','2027-02-07':'Chinese New Year','2027-02-08':'Chinese New Year (day off in lieu)','2027-03-10':'Hari Raya Puasa','2027-03-26':'Good Friday','2027-05-01':'Labour Day','2027-05-17':'Hari Raya Haji','2027-05-20':'Vesak Day','2027-08-09':'National Day','2027-10-28':'Deepavali','2027-12-25':'Christmas Day'};
var CR_DN=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], CR_MN=['January','February','March','April','May','June','July','August','September','October','November','December'];
function crPD(k){var m=/^(\d{4})-(\d\d)-(\d\d)$/.exec(String(k)); return m?new Date(Date.UTC(+m[1],+m[2]-1,+m[3])):null;}
function crKey(d){return d.getUTCFullYear()+'-'+(d.getUTCMonth()<9?'0':'')+(d.getUTCMonth()+1)+'-'+(d.getUTCDate()<10?'0':'')+d.getUTCDate();}
function crAddD(k,n){var d=crPD(k); if(!d)return k; d.setUTCDate(d.getUTCDate()+n); return crKey(d);}
function crDow(k){var d=crPD(k); return d?d.getUTCDay():0;}
function crFmt(k){var d=crPD(k); return d?CR_DN[d.getUTCDay()]+' '+d.getUTCDate()+' '+CR_MN[d.getUTCMonth()]:'';}
function crDayInfo(k){var w=crDow(k), h=CR_HOL[k]||''; return {key:k,dow:w,weekend:w===0||w===6,hol:h,rest:w===0||w===6||!!h,tag:h?('Public holiday: '+h):(w===0||w===6)?'Weekend, rest day':''};}
function crRestGroup(k){var g=k, i=0; while(i++<10&&crDayInfo(crAddD(g,-1)).rest)g=crAddD(g,-1); return g;} /* the first day of the run of rest days that holds k: one overtime day is allowed per run */
function crDKey(c){return (c&&c.dKey)||crToday();}
var crOTMode=false;
function crOTCheck(c,dk){
if(!CR_PATHS[c.path]||!CR_PATHS[c.path].ot)return 'Your job has no weekend or holiday work. Use today to rest, see family and friends, or do something you enjoy.';
if(c.ot&&c.ot[crRestGroup(dk)])return 'You have already worked overtime on this weekend or holiday. One overtime day is allowed each time.';
if(c.bars.e<30)return 'You are too tired for overtime. Rest and sleep first.';
return '';
}
function crWorkBtns(c){
var dk=crDKey(c), dd=crDayInfo(dk);
if(c.lastShift===c.day)return '
Shift done today
';
if(c.slept)return '
You are in bed for tonight
You cannot go to work while you sleep. A new day starts tomorrow.
You have already worked overtime on this weekend or holiday.
';
else h+='
Optional. Overtime uses more energy. One overtime day each weekend or holiday.
';
} else h+='
Your job has no weekend work. Use today to rest, see family and friends, or do something you enjoy.
';
return h;
}
return '';
}
function crTryWork(ot){
var c=crS(); if(!c)return; crRoll(); var dk=crDKey(c), dd=crDayInfo(dk);
if(c.lastShift===c.day){crMsg='You have already worked today. Come back tomorrow.'; return;}
if(c.slept){crMsg='You are in bed for tonight. A new day starts tomorrow.'; return;}
if(dd.rest&&!ot){crMsg='Today is a rest day, so there is no daily pay.'+(CR_PATHS[c.path].ot?' You can choose to work overtime.':''); return;}
if(ot){var why=dd.rest?crOTCheck(c,dk):'Overtime is only for weekends and public holidays.'; if(why){crMsg=why; return;}}
crOTMode=!!ot; crGoWork();
}
function crDailyX(path,rank,ot){var r=crRankRow(path,rank); return r?Math.round(r[1]/CR_WD*(ot?CR_OT:1)):0;}
/* themed blocks for a shift: each path has its own set; higher ranks add rarer pieces (Stage 1) */
var CR_BLK={
food:{base:['plank','terracotta','c_white','c_red','glass'],rare:['furnace','lamp','chest'],top:['ctable','stonebrick']},
health:{base:['glass','c_white','bed','lamp','fridge'],rare:['curtain','sofa','chest'],top:['ctable','bookshelf']},
train:{base:['glass','c_white','c_red','stonebrick','roof','lamp'],rare:['c_yellow','door','stone'],top:['ctable','rock']},
teacher:{base:['desk','books','pencilcase','whiteboard','bookshelf','schoolbag'],rare:['globe','abacus','clock'],top:['chest','ctable']},
bus:{base:['roof','c_yellow','glass','stonebrick','c_red'],rare:['lamp','door','stone'],top:['ctable','chest']}
};
function crBlockPool(path,rank){
var s=CR_BLK[path]||CR_BLK.food, pool=[]; function add(a,n){a.forEach(function(k){if(BY[k]){for(var i=0;i=4)add(s.rare,rank>=7?2:1); if(rank>=7)add(s.top,1); if(!pool.length)add(['plank','stone','brick'],1); return pool;
}
function crBlockCount(rank,ok){return ok+(ok>=CR_QB?3:0)+(rank>=7?2:rank>=4?1:0);}
function crShiftBlocks(path,rank,ok){
var n=crBlockCount(rank,ok), pool=crBlockPool(path,rank), got={}, i; for(i=0;i0)return {name:o[1],price:o[5],kind:'out'}; return null;}
function crOwnsItem(c,id){var b=crItemBase(id); if(!b)return false; return b.kind==='shop'?!!c.owned[id]:!!c.outfits[id];}
function crSellable(c,id){return CR_NOSELL.indexOf(id)<0&&crOwnsItem(c,id);}
function crSellList(c){var out=[]; CR_SHOP.forEach(function(s){if(crSellable(c,s[0]))out.push(s[0]);}); CR_OUT.forEach(function(o){if(o[5]>0&&crSellable(c,o[0]))out.push(o[0]);}); return out;}
function crSellPrice(c,id){var b=crItemBase(id); if(!b)return 0; var age=c.day-((c.ownD&&c.ownD[id])||c.day), pct=age>=CR_SELL_DAYS?CR_SELL_LATER:CR_SELL_FIRST; return Math.max(1,Math.min(b.price,Math.round(b.price*pct)));}
function crListed(c,id){var r=null; (c.sell||[]).forEach(function(x){if(x.id===id)r=x;}); return r;}
function crDependents(c,id){var d=[]; if(id==='desk'){['lamp','fan'].forEach(function(k){if(c.owned[k])d.push(crShopItem(k)[1]);});} return d;}
function crRemoveItem(c,id){
var b=crItemBase(id); if(!b)return; if(b.kind==='shop'){delete c.owned[id]; if(c.fp)delete c.fp[id];} else {delete c.outfits[id]; if(c.outfit===id)c.outfit='home';}
if(c.ownD)delete c.ownD[id]; c.sell=(c.sell||[]).filter(function(x){return x.id!==id;});
}
function crList(id,low){
var c=crS(), b=crItemBase(id); if(!c||!b||!crSellable(c,id)){return;} if(crListed(c,id)){crMsg='That item is already listed.'; return;}
var dep=crDependents(c,id); if(dep.length){crMsg='Sell or donate your '+dep.join(' and ')+' first. They sit on the desk.'; return;}
if((c.sell||[]).length>=8){crMsg='You can list up to 8 things at a time.'; return;}
var pr=crSellPrice(c,id); if(low)pr=Math.max(1,Math.round(pr*CR_LOWER));
c.sell.push({id:id,price:pr,ready:low?c.day:c.day+1,low:low?1:0}); crMsg='Listed: '+b.name+' for Aura $'+pr+'. '+(low?'A buyer is ready now.':'A buyer usually answers by tomorrow.'); crSave();
}
function crUnlist(id){var c=crS(); c.sell=(c.sell||[]).filter(function(x){return x.id!==id;}); crMsg='The listing was removed. You keep the item.'; crSave();}
function crAccept(id){
var c=crS(), l=crListed(c,id); if(!l||c.day0&&c.day-P[k].d>CR_FRESH[k][fr?1:0]){lost.push(k==='eggs'?P[k].n+' eggs':'your groceries'); P[k].n=0;}});
if(lost.length){crMail({k:'note',t:'Food that went bad',p:'You had to throw away '+lost.join(' and ')+'. '+(fr?'They were kept too long, even in the fridge.':'Without a fridge, eggs and fresh groceries go bad in a day or two. Buy only what you will use soon, or buy a fridge.')+' Instant noodles keep well without a fridge.'});}
}
function crPantryLine(c){
var P=c.pan, fr=!!c.owned.fridge, bits=[];
if(P.eggs.n>0)bits.push(P.eggs.n+' egg'+(P.eggs.n>1?'s':'')+' ('+Math.max(0,CR_FRESH.eggs[fr?1:0]-(c.day-P.eggs.d))+' more day'+(CR_FRESH.eggs[fr?1:0]-(c.day-P.eggs.d)===1?'':'s')+' fresh)');
if(P.groc.n>0)bits.push(P.groc.n+' grocery portion'+(P.groc.n>1?'s':'')+' ('+Math.max(0,CR_FRESH.groc[fr?1:0]-(c.day-P.groc.d))+' more day'+(CR_FRESH.groc[fr?1:0]-(c.day-P.groc.d)===1?'':'s')+' fresh)');
if(P.nood>0)bits.push(P.nood+' pack'+(P.nood>1?'s':'')+' of instant noodles');
return bits.length?bits.join(', '):'Nothing yet. Order food on your phone.';
}
function crMealFree(c){var s=['b','l','d'].filter(function(k){return !c.meals[k];}); return s[0]||'';}
function crDoEat(c,o){
var sl=crMealFree(c); if(!sl){crMsg='You have already had three meals today.'; return false;}
if(o.cost>0){var pp=crPriceParts(c,o.cost,o.taxed); if(c.coins=3||egg)?(egg?'Adding an egg gives you protein. Leave some of the soup to have less salt.':'Tip: noodles three days in a row. Add an egg and some vegetables, and leave some of the soup, to make it a better meal.'):(first?'Instant noodles are high in salt and low in vegetables. They are fine as a quick meal now and then.':'');
if(crDoEat(c,{n:egg?'Instant noodles with an egg':'Instant noodles',rest:egg?26:16,msg:(egg?'You feel fuller.':'You feel a little fuller.')+(note?' '+note:'')})){P.nood--; if(egg)P.eggs.n--; nd.seen=1;} else nd.last=nd.last;
}
crSave();
}
function crOrder(item){
var c=crS(), P=c.pan, pr=CR_FOODP[item]; if(!pr)return; var pp=crPriceParts(c,pr,1); if(c.coins=CR_LEI_MAX){crMsg='You have had a good rest today. More activities tomorrow.'; return;}
if(a.cost>0){var pp=crPriceParts(c,a.cost,0); if(c.coins=6&&fun<=1)sent='You worked a lot this week. A walk in the park could help you recharge.';
else if(fun>=3&&busy>=1)sent='You made time for work, rest and people this week. A mix like this helps you stay ready for work.';
else if(fun>=3)sent='You made lots of time for rest and fun this week. Well done.';
else if(busy===0&&fun===0)sent='A quiet week. Try one thing you enjoy this week, and a shift when you feel ready.';
else sent='Try to fit in one thing you enjoy each day, such as a walk or time with a friend. It helps you feel ready for work.';
crMail({k:'balance',t:'Your week: work and rest',p:'Week of '+crFmt(b.k)+'. Shifts: '+b.sh+'. Overtime days: '+b.ot+'. Nights you went to bed on time: '+b.ok+'. Leisure activities: '+b.lei+'. Times with family or friends: '+b.fam+'. '+sent});
c.bw={k:wk,sh:0,ot:0,ok:0,lei:0,fam:0};
}
/* ---- pure arithmetic (all money is whole Aura dollars) ---- */
function crRankRow(path,rank){var p=CR_PATHS[path]; return (p&&p.ranks.length)?p.ranks[Math.max(1,Math.min(10,rank|0))-1]:null;}
function crDaily(path,rank){var r=crRankRow(path,rank); return r?Math.round(r[1]/CR_WD):0;}
function crPayCalc(path,rank,ot){var g=crDailyX(path,rank,ot), lk=Math.round(g*CR_LOCK), er=Math.round(g*CR_ER); return {gross:g,locked:lk,employer:er,take:g-lk,store:lk+er};}
function crSkillAt(L){return Math.round(5*(L-1)*(L+4)*CR_Q/CR_QB);} /* skill points needed for skill level L: 0,30,70,120,... */
function crSkillLv(sp){var L=1; for(var i=2;i<=10;i++){if(sp>=crSkillAt(i))L=i;} return L;}
function crPeopleLv(pp){var L=1; for(var i=2;i<=10;i++){if(pp>=CR_PPAT[i-1])L=i;} return L;}
function crGstOn(c){return !!c&&c.age!=='7-10'&&CR_GST_ON_11PLUS;}
function crPriceHTML(pp){return pp.gst?'
Price '+crN(pp.sub)+'
GST (9%) '+crN(pp.gst)+'
Total '+crM(pp.tot)+'
':crM(pp.tot);}
function crPriceParts(c,p,taxed){var g=(crGstOn(c)&&taxed!==0)?Math.round(p*CR_GST):0; return {sub:p,gst:g,tot:p+g};}
function crBillRows(owned,c){var rows=CR_BILLBASE.map(function(r){return r.slice();}); owned=owned||{}; ['fridge','lamp','fan','aircon'].forEach(function(k){if(owned[k])rows.push(CR_APPL[k].slice());}); if(c){var nt=c.net?1:0; if(c.age==='7-10')rows.push(['Phone and internet',CR_PHONEBILL+CR_NETP[nt]]); else {rows.push(['Phone: mobile plan',CR_PHONEBILL]); rows.push(['Internet: '+(nt?'faster plan':'basic plan'),CR_NETP[nt]]);}} var t=0; rows.forEach(function(r){t+=r[1];}); return {rows:rows,total:t};}
function crShopItem(id){var r=null; CR_SHOP.forEach(function(s){if(s[0]===id)r=s;}); return r;}
function crOutItem(id){var r=null; CR_OUT.forEach(function(s){if(s[0]===id)r=s;}); return r;}
function crPromoNeed(c){
if(!c||!c.path||c.rank>=10)return null; var R=c.rank+1, np=(CR_PATHS[c.path].ranks[R-1]||[]);
return {to:R,title:np[0]||'',skill:R,shifts:CR_MINSH[c.rank-1],people:CR_PPNEED[R-1],peopleLive:!!CR_FLAGS.bonus};
}
function crPromoMet(c){var n=crPromoNeed(c); if(!n)return false; return crSkillLv(c.sp)>=n.skill&&c.shAt>=n.shifts&&(!n.peopleLive||crPeopleLv(c.pp)>=n.people);}
function crBizDef(path){return path==='health'?{name:'Home-care service',cap:3000,minRank:4}:{name:'Small food stall',cap:2000,minRank:4};}
function crBizProfit(c){var b=c.biz; if(!b)return 0; return Math.round(b.cap*.006*(.6+crSkillLv(c.sp)/10));}
function crBizRefund(b){return Math.round(b.cap*.6);}
/* ---- state ---- */
/* ---- outside the flat: neighbours, hidden animals, the estate lap, the garden plant ---- */
var CR_NEIGH={
tan:{n:'Mdm Tan',item:'a parcel',to:'rahim',pay:4,greet:['Good morning! The void deck is nice and cool at this time.','Hello! Did you have breakfast?','Hi there! Thank you for always saying hello.']},
rahim:{n:'Mr Rahim',item:'a bag of oranges',to:'mei',pay:4,greet:['Welcome! The minimart has cold drinks today.','Hello! Careful on the cycling path.','Good day! Nice to see you out and about.']},
mei:{n:'Auntie Mei',item:'a library book',to:'tan',pay:4,greet:['Hello! The swings are fun, but wait for your turn.','Good morning! I love the garden here.','Hi! Many children play here after school.']}
};
var CR_DISC={
cat:{n:'Tabby cat',badge:'Cat spotter',fact:'Many estate cats are looked after by the community. It is kind to watch quietly and not chase or feed them.'},
myna:{n:'Javan myna',badge:'Myna spotter',fact:'The Javan myna is a common bird in Singapore. It has a yellow beak and white patches on its wings.'},
butterfly:{n:'Butterfly',badge:'Butterfly spotter',fact:'Butterflies visit flowers to drink sweet nectar. Gardens with flowers bring them in.'},
snail:{n:'Garden snail',badge:'Snail spotter',fact:'Snails come out after rain and glide slowly on one muscular foot.'},
sunbird:{n:'Olive-backed sunbird',badge:'Sunbird spotter',fact:'The olive-backed sunbird is a small bird that feeds on nectar from flowers.'},
puppy:{n:'Puppy',badge:'Puppy spotter',fact:'Dogs in Singapore need a licence, and owners keep them on a leash in public places. Ask the owner before you pet a dog.'},
bunny:{n:'Rabbit',badge:'Rabbit spotter',fact:'A pet rabbit eats mostly hay, with some fresh vegetables. Rabbits get uncomfortable in hot weather, so they need shade and fresh water.'},
panda:{n:'Panda soft toy',badge:'Toy finder',fact:'Someone left a soft toy panda at the playground. Real giant pandas eat mostly bamboo.'}
};
var CR_PLANTS={
chilli:{n:'Chilli padi',days:7,note:'Small, very hot chillies used in many Singapore dishes.',real:'A real chilli padi plant takes about 3 to 4 months from seed to its first chillies (NParks). It is the slowest of the three.'},
basil:{n:'Basil',days:5,note:'A herb with soft leaves and small white flowers.',real:'A real basil plant grown from seed is ready to pick in about 60 to 70 days (University of Massachusetts), so it is faster than chilli.'},
pandan:{n:'Pandan',days:3,note:'Long, fragrant leaves used in Singapore cakes and rice.',real:'Pandan grows quickly, especially in the wet season, and you can pick its leaves at any time (NParks). A new plant grows from a rooted offshoot, not from seed.'}
};
function crPlStage(pl){ var P=CR_PLANTS[pl.k]; if(!P)return 0; if(pl.g<=0)return 0; if(pl.g>=P.days)return 3; return 1+Math.floor((pl.g-1)*2/Math.max(1,P.days-1))>2?2:1+Math.floor((pl.g-1)*2/Math.max(1,P.days-1)); }
function crOu(c){
var o=c.ou; if(!o||typeof o!=='object')o=c.ou={};
if(!o.ne)o.ne={d:0,done:{},carry:'',tot:0}; if(!o.dis)o.dis={}; if(!o.race)o.race={d:0,n:0,best:{run:0,cycle:0}}; if(!o.race.best)o.race.best={run:0,cycle:0}; if(!o.pl)o.pl={k:'',g:0,wd:0,hv:0}; if(!o.dance)o.dance={n:0,best:0,stars:0}; if(!o.dr)o.dr={st:0,n:0,d:0}; if(!o.ing)o.ing={chilli:0,basil:0,pandan:0};
if(o.ne.d!==c.day){o.ne.d=c.day; o.ne.done={}; o.ne.carry='';}
return o;
}
function crOuClean(o){
o=(o&&typeof o==='object')?o:{}; var ne=o.ne||{}, r=o.race||{}, b=r.best||{}, pl=o.pl||{};
var n={ne:{d:ne.d|0,done:{},carry:'',tot:Math.max(0,Math.min(9999,ne.tot|0))},dis:{},race:{d:r.d|0,n:Math.max(0,Math.min(9999,r.n|0)),best:{run:Math.max(0,Math.min(999,+b.run||0)),cycle:Math.max(0,Math.min(999,+b.cycle||0))}},pl:{k:'',g:0,wd:pl.wd|0,hv:Math.max(0,Math.min(9999,pl.hv|0))},xb:o.xb?1:0,ing:{chilli:0,basil:0,pandan:0}};
Object.keys(n.ing).forEach(function(k){ n.ing[k]=Math.max(0,Math.min(99,((o.ing||{})[k])|0)); });
Object.keys(CR_NEIGH).forEach(function(k){ if(ne.done&&ne.done[k])n.ne.done[k]=1; }); if(CR_NEIGH[ne.carry])n.ne.carry=ne.carry;
Object.keys(CR_DISC).forEach(function(k){ if(o.dis&&o.dis[k])n.dis[k]=1; });
if(CR_PLANTS[pl.k]){n.pl.k=pl.k; n.pl.g=Math.max(0,Math.min(CR_PLANTS[pl.k].days,pl.g|0));}
var dd0=o.dance||{}; n.dance={n:Math.max(0,Math.min(9999,dd0.n|0)),best:Math.max(0,Math.min(100,dd0.best|0)),stars:Math.max(0,Math.min(3,dd0.stars|0))};
var dr0=o.dr||{}; n.dr={st:Math.max(0,Math.min(3,dr0.st|0)),n:Math.max(0,Math.min(9999,dr0.n|0)),d:dr0.d|0};
return n;
}
function crBadges(c){
var o=crOu(c), all=Object.keys(CR_DISC).every(function(k){return o.dis[k];}), L=[];
Object.keys(CR_DISC).forEach(function(k){ L.push([CR_DISC[k].badge,!!o.dis[k],'Find the '+CR_DISC[k].n.toLowerCase()+' somewhere in the estate.']); });
L.push(['Estate Explorer',all||!!o.xb,'Find all '+Object.keys(CR_DISC).length+' animals.']); L.push(['Neighbourhood Helper',o.ne.tot>=3,'Deliver three errands for neighbours.']); L.push(['Green Thumb',o.pl.hv>=1,'Grow a plant in the garden and pick it.']); L.push(['Lap Runner',o.race.n>=1,'Finish one estate lap.']); L.push(['Road Rules Passed',o.dr.st>=1,'Pass the road-rules quiz at the driving lessons sign.']); L.push(['Licensed Driver',o.dr.st>=3,'Pass the driving test.']); L.push(['Class Regular',o.dance.n>=3,'Go to the void deck dance class three times.']); L.push(['In Step',o.dance.best>=85,'Finish the dance routine with a score of 85% or more.']);
return L;
}
function crNew(){
return {v:CRV,age:'',path:'',rank:1,sp:0,f0:0,pp:0,coins:CR_START,locked:0,day:1,dKey:'',lastShift:0,shAt:0,shifts:0,
bars:{f:80,w:80,g:80,e:100},meals:{b:0,l:0,d:0},owned:{},items:{},outfit:'home',outfits:{},mail:[],mid:1,ev:null,slept:'',biz:null,
hist:[],last:null,wk:{},ot:{},pan:{eggs:{n:0,d:0},groc:{n:0,d:0},nood:0},nd:{last:0,run:0,seen:0},net:0,eb:0,lz:{d:0,n:0},sell:[],ownD:{},bw:{k:'',sh:0,ot:0,ok:0,lei:0,fam:0},lp1:0,tab:'home',sel:'',hv:{m:'auto',r:3.5,z:1},fp:{},col:{},setup:0,welcomed:0,qa:0,qc:0,wt:0,o:0,ou:null};
}
function crClean(o){
var n=crNew(); if(!o||typeof o!=='object')return n;
function num(k,lo,hi,d){var v=+o[k]; n[k]=(isFinite(v)&&o[k]!==null&&o[k]!=='')?Math.max(lo,Math.min(hi,Math.floor(v))):d;}
if(o.hv&&typeof o.hv==='object')n.hv={m:/^(auto|up|cut|down)$/.test(o.hv.m)?o.hv.m:'auto',r:Math.max(0,Math.min(3,Math.floor(+o.hv.r)||0)),z:Math.max(.6,Math.min(2.2,+o.hv.z||1))};
if(o.age==='11-16')o.age='11-14'; if(o.age==='16+')o.age='15+'; n.age=(o.age==='7-10'||o.age==='11-14'||o.age==='15+')?o.age:'';
n.path=(typeof o.path==='string'&&CR_PATHS[o.path]&&CR_PATHS[o.path].open)?o.path:'';
num('rank',1,10,1); num('sp',0,1e6,0); num('f0',0,1e8,0); num('pp',0,1e5,0); num('coins',0,1e7,CR_START); num('locked',0,1e8,0); num('day',1,1e5,1);
num('lastShift',0,1e5,0); num('shAt',0,1e5,0); num('shifts',0,1e5,0); num('mid',1,1e9,1); num('qa',0,1e7,0); num('qc',0,1e7,0); num('wt',0,4e12,0); num('o',0,1,0);
n.setup=o.setup?1:0; n.welcomed=o.welcomed?1:0; n.dKey=/^\d{4}-\d{2}-\d{2}$/.test(o.dKey)?o.dKey:'';
var b=o.bars||{}; ['f','w','g','e'].forEach(function(k){var v=+b[k]; n.bars[k]=isFinite(v)?Math.max(0,Math.min(100,Math.round(v))):n.bars[k];});
var m=o.meals||{}; ['b','l','d'].forEach(function(k){n.meals[k]=m[k]?1:0;});
n.owned={}; if(o.owned&&typeof o.owned==='object'){CR_SHOP.forEach(function(s){if(o.owned[s[0]])n.owned[s[0]]=1;}); if(o.owned.bed2)n.owned.bed1=1;} /* the double bed is gone; a Learner who had one keeps a bed */
n.col={}; if(o.col&&typeof o.col==='object'&&typeof CR_COLS!=='undefined')Object.keys(CR_COLS).forEach(function(id){var q=o.col[id]|0; if(n.owned[id]&&q>0&&q [x, z, quarter turns]; missing ones are filled in by crfRepair */
n.items={}; if(o.items&&typeof o.items==='object')CR_SMALL.forEach(function(s){if(o.items[s[0]])n.items[s[0]]=1;});
n.outfits={}; if(o.outfits&&typeof o.outfits==='object')CR_OUT.forEach(function(s){if(s[5]>0&&o.outfits[s[0]])n.outfits[s[0]]=1;});
n.outfit='home'; CR_OUT.forEach(function(s){if(o.outfit===s[0]&&(s[5]===0||n.outfits[s[0]]))n.outfit=s[0];});
n.slept=(o.slept==='ok'||o.slept==='late')?o.slept:'';
n.mail=Array.isArray(o.mail)?o.mail.filter(function(x){return x&&/^(welcome|bill|interview|promo|note|sale|balance)$/.test(x.k);}).slice(0,16).map(function(x){
var r={k:x.k,id:x.id|0,d:x.d|0,read:x.read?1:0,t:String(x.t||'').slice(0,80),p:String(x.p||'').slice(0,520)};
r.dt=/^\d{4}-\d\d-\d\d$/.test(x.dt)?x.dt:''; if(x.k==='sale'){r.amt=Math.max(0,Math.min(1e6,+x.amt||0)); r.got=x.got?1:0;} if(x.k==='bill'){r.rows=(Array.isArray(x.rows)?x.rows:[]).slice(0,10).map(function(y){return [String(y[0]).slice(0,60),+y[1]||0];}); r.total=+x.total||0; r.paid=x.paid?1:0;} return r;}):[];
n.ev=(o.ev&&o.ev.k==='interview'&&(o.ev.day|0)>0)?{k:'interview',day:o.ev.day|0}:null;
n.biz=(o.biz&&(+o.biz.cap>0)&&typeof o.biz.name==='string')?{name:o.biz.name.slice(0,40),cap:Math.min(1e6,Math.floor(+o.biz.cap)),since:o.biz.since|0}:null;
n.ou=crOuClean(o.ou);
n.hist=Array.isArray(o.hist)?o.hist.slice(-14).map(function(x){return {d:x.d|0,r:x.r|0,n:x.n|0,g:x.g|0,t:x.t|0};}):[];
n.last=(o.last&&typeof o.last==='object')?{blk:o.last.blk|0,day:o.last.day|0,rank:o.last.rank|0,job:String(o.last.job||'').slice(0,60),gross:o.last.gross|0,locked:o.last.locked|0,employer:o.last.employer|0,take:o.last.take|0,right:o.last.right|0,n:o.last.n|0,sp:o.last.sp|0,items:Array.isArray(o.last.items)?o.last.items.slice(0,3).map(String):[],dt:/^\d{4}-\d\d-\d\d$/.test(o.last.dt)?o.last.dt:'',ot:o.last.ot?1:0,bt:String(o.last.bt||'').slice(0,160)}:null;
n.wk={}; if(o.wk&&typeof o.wk==='object'){Object.keys(o.wk).sort().slice(-70).forEach(function(k){if(/^\d{4}-\d\d-\d\d$/.test(k)&&(o.wk[k]===1||o.wk[k]===2))n.wk[k]=o.wk[k];});}
n.ot={}; if(o.ot&&typeof o.ot==='object'){Object.keys(o.ot).sort().slice(-20).forEach(function(k){if(/^\d{4}-\d\d-\d\d$/.test(k)&&o.ot[k])n.ot[k]=1;});}
n.tab=/^(home|work|shop|mail|cal|career|more|credits)$/.test(o.tab)?o.tab:'home'; n.sel=/^[a-z]{0,10}$/.test(o.sel||'')?(o.sel||''):'';
(function(){
var P=o.pan&&typeof o.pan==='object'?o.pan:{}; function pn(v,hi){v=Math.floor(+v); return isFinite(v)?Math.max(0,Math.min(hi,v)):0;}
n.pan={eggs:{n:pn((P.eggs||{}).n,99),d:pn((P.eggs||{}).d,1e5)},groc:{n:pn((P.groc||{}).n,99),d:pn((P.groc||{}).d,1e5)},nood:pn(P.nood,99)};
var d=o.nd&&typeof o.nd==='object'?o.nd:{}; n.nd={last:pn(d.last,1e5),run:pn(d.run,1e5),seen:d.seen?1:0};
n.net=o.net?1:0; n.eb=o.eb?1:0; n.lp1=o.lp1?1:0; var z=o.lz&&typeof o.lz==='object'?o.lz:{}; n.lz={d:pn(z.d,1e5),n:pn(z.n,20)};
n.ownD={}; var od=o.ownD&&typeof o.ownD==='object'?o.ownD:{}; Object.keys(n.owned).forEach(function(k){n.ownD[k]=od[k]!==undefined?pn(od[k],1e5):n.day;}); Object.keys(n.outfits).forEach(function(k){n.ownD[k]=od[k]!==undefined?pn(od[k],1e5):n.day;});
n.sell=[]; (Array.isArray(o.sell)?o.sell:[]).slice(0,8).forEach(function(x){if(x&&typeof x.id==='string'&&crItemBase(x.id)&&((n.owned[x.id])||(n.outfits[x.id]))&&CR_NOSELL.indexOf(x.id)<0&&!n.sell.filter(function(y){return y.id===x.id;}).length)n.sell.push({id:x.id,price:Math.max(1,pn(x.price,1e5)),ready:pn(x.ready,1e5),low:x.low?1:0});});
var w=o.bw&&typeof o.bw==='object'?o.bw:{}; n.bw={k:/^\d{4}-\d\d-\d\d$/.test(w.k)?w.k:'',sh:pn(w.sh,999),ot:pn(w.ot,999),ok:pn(w.ok,999),lei:pn(w.lei,999),fam:pn(w.fam,999)};
})();
try{crfRepair(n);}catch(e){}
return n;
}
/* Skill points come from the ONE shared learning record: first-try correct answers (S.st.f) counted since Career started, from Creative quizzes and Career shifts alike. */
function crSyncSP(c){c.sp=Math.max(0,((S.st.f|0)-(c.f0|0))); return c.sp;}
function crSummary(c){c=c||(S&&S.st&&S.st.cr); if(!c||!c.path)return null; return {p:c.path,r:c.rank,s:crSkillLv(c.sp),pl:crPeopleLv(c.pp),sh:c.shifts,d:c.day,lk:c.locked,t:c.wt|0};}
/* The starter flat (walls, openings, fixtures, furniture sizes and the walking rules) is defined with the home scene: see CRF_WALLS, CRF_FIX and CRF_PIECE below. */
/* ===================== CAREER MODE (beta preview): day loop, shifts, rules ===================== */
var crOpen=false, crW=false, CRSH=null, crMsg='', crTmr=null;
/* shop icons drawn as small flat pictures (used when an item has no icon of its own) */
var CR_SVGI=(function(){
var K='#3b2a20', o={};
function w(inner){return 'data:image/svg+xml;utf8,'+encodeURIComponent('');}
function r(x,y,wd,h,f,rx){return '';}
o.bed1=w(r(4,16,6,24,'#b98f5a')+r(4,26,40,10,'#6f9bd6')+r(12,21,11,6,'#ffffff',3)+r(40,32,4,8,'#b98f5a')+r(4,36,4,5,'#b98f5a')+r(40,36,4,5,'#b98f5a'));
o.ward=w(r(11,5,26,38,'#c9a46a')+'');
o.desk=w(r(4,17,40,6,'#cfaf84')+r(8,23,4,19,'#b98f5a')+r(36,23,4,19,'#b98f5a')+r(26,23,10,9,'#e2c79b')+'');
o.chair=w(r(14,5,5,24,'#e6c56e')+r(14,26,22,5,'#e6c56e')+r(14,31,4,12,'#b98f5a')+r(32,31,4,12,'#b98f5a'));
o.lamp=w(''+r(22,24,4,14,'#b98f5a')+r(14,38,20,4,'#8a6a3a'));
o.fan=w(''+r(22,31,4,8,'#8d99a0')+r(12,39,24,4,'#8d99a0'));
o.aircon=w(r(4,12,40,17,'#f2f4f6',3)+''+'');
o.sofa=w(r(7,14,34,13,'#92ad91',3)+r(3,22,9,16,'#7c9a7c',3)+r(36,22,9,16,'#7c9a7c',3)+r(10,26,28,11,'#a8c1a7')+r(8,38,4,4,'#b98f5a')+r(36,38,4,4,'#b98f5a'));
o.tv=w(r(10,6,28,20,'#2d3640',2)+r(13,9,22,14,'#6aa9d6',1)+r(4,30,40,10,'#c6a577')+r(8,40,4,3,'#8a6a3a')+r(36,40,4,3,'#8a6a3a'));
o.rug=w(r(4,15,40,18,'#c9d8cb',4)+r(9,19,30,10,'#a8c3ad',3)+'');
o.keyb=w(r(3,18,42,13,'#2b2f36',2)+r(5,22,38,8,'#f4f1ea',1)+r(9,22,3,5,'#16181c',1)+r(15,22,3,5,'#16181c',1)+r(27,22,3,5,'#16181c',1)+r(33,22,3,5,'#16181c',1)+r(7,31,3,10,'#8d99a0',1)+r(38,31,3,10,'#8d99a0',1));
o.guitar=w(''+r(20,4,4,20,'#5a3a22',1)+r(18,2,8,5,'#2b1d12',1));
o.ukulele=w(''+r(22,8,4,20,'#5a3a22',1)+r(21,5,6,4,'#2b1d12',1));
o.violin=w(''+r(20,5,4,16,'#2b1d12',1)+'');
o.drums=w(r(12,22,24,16,'#3b4a6a',3)+r(14,24,20,3,'#f4f1ea',1)+'');
o.erhu=w(r(20,3,3,26,'#3b1d12',1)+r(16,28,11,10,'#4a2418',2)+''+r(28,6,2,34,'#8a6a3a',1));
o.recorder=w('');
o.bottle=w(r(21,5,6,6,'#4a86c8',1)+r(17,11,14,32,'#6fb0e0',5)+'');
return o;
})();
var CRCOIN='';
function $c(id){return document.getElementById(id);}
function crS(){return (S.st&&S.st.cr)||null;}
function crTest(){try{return localStorage.getItem('mbCareerBeta')==='1'||/[?&]career=1(&|$)/.test(location.search);}catch(e){return false;}}
function crEnabled(){return !!(crTest()||!S.pl||S.pl.cr!==0);}
function crPad(n){return (n<10?'0':'')+n;}
function crToday(){
var o=null; try{o=window.__crDate||localStorage.getItem('mbCrDate');}catch(e){}
if(o&&/^\d{4}-\d{2}-\d{2}$/.test(String(o)))return String(o);
var d=new Date(Date.now()+8*3600000); return d.getUTCFullYear()+'-'+crPad(d.getUTCMonth()+1)+'-'+crPad(d.getUTCDate()); /* Singapore time (UTC+8) */
}
function crN(n){n=Math.round(n); return (n<0?'-':'')+String(Math.abs(n)).replace(/\B(?=(\d{3})+(?!\d))/g,',');}
function crMs(n,sg){return ''+sg+''+crM(n)+'';}
function crM(n){return ''+CRCOIN+'Aura $'+crN(n)+'';}
function crClamp(v){return Math.max(0,Math.min(100,Math.round(v)));}
function crSave(){var c=crS(); if(c)c.wt=Date.now(); try{save();}catch(e){}}
function crMail(m){
var c=crS(); m.id=c.mid++; m.d=c.day; m.dt=c.dKey||crToday(); m.read=0; c.mail.unshift(m);
while(c.mail.length>16){var i=c.mail.length-1; while(i>0&&c.mail[i].k==='bill'&&!c.mail[i].paid)i--; c.mail.splice(i,1);}
}
function crUnpaid(){var c=crS(); return c?c.mail.filter(function(m){return m.k==='bill'&&!m.paid;}).length:0;}
function crMailCount(){var c=crS(); return c?c.mail.filter(function(m){return (m.k==='bill'&&!m.paid)||!m.read;}).length:0;}
function crHasBed(c){return !!(c.owned.bed1||c.owned.bed2);}
function crJobTitle(c){var r=crRankRow(c.path,c.rank); return r?r[0]:'';}
function crLowNeeds(c){return c.bars.f<25||c.bars.w<25||c.bars.e<25;}
/* ---- day rollover (real dates; a test override is read by crToday) ---- */
function crRoll(){
var c=crS(); if(!c||!c.setup)return false; var t=crToday();
if(!c.dKey){c.dKey=t; return false;} if(c.dKey===t)return false;
var worked=c.lastShift===c.day, evTomorrow=!!(c.ev&&c.ev.day===c.day+1);
c.bars.e=c.slept==='ok'?100:c.slept==='late'?55:(evTomorrow?55:85);
c.bars.f=crClamp(c.bars.f-40); c.bars.w=crClamp(c.bars.w-35);
if(c.biz&&worked){var pr=crBizProfit(c); c.coins+=pr; crMail({k:'note',t:'Business profit',p:'Your '+c.biz.name+' made Aura $'+pr+' of profit on the day you worked.'});}
c.day++; c.dKey=t; c.clk=420; c.meals={b:0,l:0,d:0}; c.slept=''; try{crPantryAge(c); crWeekRoll(c,t);}catch(e){}
if(c.day%CR_MONTH===0){var b=crBillRows(c.owned,c); crMail({k:'bill',t:'Monthly bills',rows:b.rows,total:b.total,paid:0,p:'This month\'s bills for your flat, phone and internet. Pay them from your Aura dollars. An unpaid bill just waits for you.'});}
crSave(); return true;
}
/* ---- needs and home actions. Each returns a short message shown on the home screen ---- */
function crEat(slot,idx){
var c=crS(), m=(CR_MEALS[slot]||[])[idx|0]; if(!m)return; if(c.meals[slot]){crMsg='You have already had '+CR_MEALNAME[slot].toLowerCase()+' today.'; return;}
var pp=crPriceParts(c,m[1],m[2]); if(c.coins=12?'pm':'am'; h=h%12; if(h===0)h=12; return h+':'+(mm<10?'0':'')+mm+' '+ap;}
function crFaceSvg(m){ m=((Math.round(m)%1440)+1440)%1440; var hr=(m/60)%12*30, mn=(m%60)*6, t='';
[0,90,180,270].forEach(function(a){t+='';});
return ''; }
function crFaceSet(m){ var f=document.getElementById('crFace'); if(!f)return; m=((Math.round(m)%1440)+1440)%1440; var h=f.querySelector('.crhh'), n=f.querySelector('.crmh'); if(h)h.setAttribute('transform','rotate('+((m/60)%12*30)+' 16 16)'); if(n)n.setAttribute('transform','rotate('+((m%60)*6)+' 16 16)'); f.setAttribute('aria-label','Clock showing '+crClkTxt(m)); }
function crTick(c,min){ if(!c)return; c.clk=Math.min(1380,crClk(c)+min); }
function crClockAnim(from,to){
var el=$c('crClk'); if(!el)return; var m=from, id=setInterval(function(){ m+=50; if(m>=to){m=to; clearInterval(id);} var e=$c('crClk'); if(!e){clearInterval(id);return;} e.textContent=crClkTxt(m)+(m=2){crMsg='Pay your bills in the mailbox before you buy more.'; return;}
if(CR_NEEDS[id]&&!c.owned[CR_NEEDS[id]]){crMsg='You need a '+CR_NEEDS[id]+' first.'; return;}
var spot=null; if(CRF_PIECE[id]){ spot=crfFind(c,id); if(!spot){crMsg='There is no free space in your flat for this. Move something first, then buy it.'; return;} }
var pp=crPriceParts(c,s[3],1); if(c.coins0&&!c.outfits[id])return; c.outfit=id; crMsg='You are wearing: '+(id==='work'?'Work uniform':o[1])+'.'; crSave();}
function crPayBill(id){
var c=crS(), m=c.mail.filter(function(x){return x.id===id&&x.k==='bill'&&!x.paid;})[0]; if(!m)return; var pp=crPriceParts(c,m.total,1);
if(c.coinsc.day)return;
if(c.bars.e>=60){var n=crPromoNeed(c); c.rank=n.to; c.shAt=0; c.ev=null; var r=crRankRow(c.path,c.rank);
crMail({k:'promo',t:'Congratulations on your promotion',p:'You are now a '+r[0]+'. Your pay is Aura $'+crDaily(c.path,c.rank)+' a shift. Skills and steady work got you here.'}); crMsg='You did well at the interview. You are now a '+r[0]+'.';}
else {c.ev.day=c.day+1; crMail({k:'note',t:'Interview moved',p:'You were too tired at the interview, so it has been moved to tomorrow. Sleep before you go to bed tonight, and try again.'}); crMsg='You were too tired for the interview. It moves to tomorrow. Go to bed on time tonight.';}
crSave();
}
function crBizStart(){
var c=crS(), d=crBizDef(c.path); if(c.biz||c.rank0)||crCoins()=CR_AURA.dailyCap){try{hint('Round finished. You have earned Aura dollars from '+CR_AURA.dailyCap+' rounds today. Work a shift in Aura Life to earn more.',true);}catch(e){} return;}
a.n++; crAddCoins(CR_AURA.perRound); try{hint('Round finished! +'+CR_AURA.perRound+' Aura dollars. Aura dollars are earned by finishing a round, not by single answers.',true);}catch(e){} try{save();}catch(e){}
}
function crDefaultAge(){var g=S.gr|0; return g>=1&&g<=4?'7-10':g>=5?'11-14':'7-10';}
/* ---- shifts: 10 subject questions from the game's own engine, then (flagged) bonus, then pay ---- */
function crJobPath(j){return ({nurse:'health',farm:'food',hawker:'food',teacher:'teacher',train:'train',bus:'bus'})[j&&j.id]||'';}
function crOpenPaths(){return Object.keys(CR_PATHS).filter(function(k){return CR_PATHS[k].open&&CR_PATHS[k].ranks.length===10;});}
function crShiftMsg(title,text){
var body=$c('jobBody'); body.innerHTML=''; body.appendChild(mk('h3','sub',title)); body.appendChild(mk('p','jobq',text));
var b=mk('button','btn primary','Go home'); b.type='button'; b.addEventListener('click',crHomeFromWork); body.appendChild(b);
}
function crHomeFromWork(){crW=false; CRSH=null; try{closeModals();}catch(e){} var c=crS(); if(c){c.tab='work';} crRender();}
function crStartShift(j,sj){
var c=crS(); if(!c||!c.setup){crShiftMsg('Aura Life','Set up your Aura Life first.'); return;}
crRoll();
var jp=crJobPath(j);
if(c.lastShift===c.day){crShiftMsg('You have worked today','You have already finished your shift today. Come back tomorrow for your next day at work.'); return;}
if(jp&&CR_PATHS[jp]&&!CR_PATHS[jp].open){crShiftMsg(CR_PATHS[jp].name+' path','The '+CR_PATHS[jp].name+' path is not open yet. Your work is on the '+CR_PATHS[c.path].name+' path: '+CR_PATHS[c.path].places+'.'); return;}
if(jp!==c.path){crShiftMsg('This is not your workplace','You are on the '+CR_PATHS[c.path].name+' path. Your workplaces are the '+CR_PATHS[c.path].places+'.'); return;}
var dk0=crDKey(c), dd0=crDayInfo(dk0), otNow=false;
if(dd0.rest){var why0=crOTCheck(c,dk0); if(!crOTMode||why0){crShiftMsg('Rest day',(why0?why0+' ':'')+'Today is a rest day, so there is no daily pay.'); return;} otNow=true;}
var pt=(c.part&&c.part.path===c.path)?c.part:null; if(!pt)c.part=null;
if(!sj&&pt)sj=pt.sj;
if(!sj){crShiftPick(j,otNow); return;}
var dn=pt?pt.done:0, nq=CR_SIZES[dn]||3, qs=crBuildQs(sj,nq);
if(qs.length=0;});}
function crBuildQs(sj,n){ n=n||CR_Q;
var keep=[SC,SCMOD,G1S,G1MOD,MU,MUMOD,CP,CPMOD,mode], list=sj==='mix'?crSjList():CR_SJ.filter(function(x){return x[0]===sj;}), qs=[], i, q, guard=0;
if(!list.length)list=[CR_SJ[0]];
try{
mode='play';
for(i=0;i'+esc(c.name)+' '+esc(c.sub||'')+'
'+c.f.map(function(t){return '
'+esc(t)+'
';}).join('')+'
';}).join(''); dt.appendChild(nb); fb.parentNode.insertBefore(dt,fb.nextSibling); } }catch(e){}
try{recordSkill(q,ok?1:0,ms);}catch(e){}
var nx=mk('button','btn primary',s.i>=s.N-1?(s.done>=CR_SESS-1?'Finish shift':'Finish session'):'Next question'); nx.type='button';
nx.addEventListener('click',function(){s.i++; if(s.i>=s.N)crAfterQs(); else crShiftRender();}); body.appendChild(nx); try{nx.focus({preventScroll:true});}catch(e){}
}); box.appendChild(b);});
body.appendChild(box); body.appendChild(fb);
}
/* bonus round: wired but OFF. window.__crBonusQs (or a later approved list) must hold {q,o:[],a:index,w} items. */
function crBonusPool(){var a=(CR_FLAGS.bonus&&window.__crBonusQs)||[]; return Array.isArray(a)?a.slice(0,2):[];}
function crSessEnd(){
var s=CRSH, c=crS(), body=$c('jobBody'); if(!s||!c)return;
c.part={path:c.path,sj:s.sj,done:s.done+1,ok:s.ok,bok:s.bok}; crTick(c,CR_SESSMIN); crSave();
var left=CR_SESS-c.part.done; body.innerHTML='';
body.appendChild(mk('h3','sub','Session '+(s.done+1)+' of '+CR_SESS+' done'));
body.appendChild(mk('p','jobq','You got '+s.sok+' of '+s.N+' correct. The time is '+crClkTxt(crClk(c))+'. '+left+' more session'+(left>1?'s':'')+' to finish today\u2019s shift.'));
body.appendChild(mk('p','lvldesc','You are paid when the shift is finished. If you stop now, you can carry on with the next session later. Your progress is saved.'));
var row=mk('div','row2'), a=mk('button','btn primary','Start session '+(c.part.done+1)); a.type='button'; a.addEventListener('click',function(){var j=s.j; CRSH=null; crStartShift(j,s.sj);});
var b=mk('button','btn','Take a break'); b.type='button'; b.addEventListener('click',function(){CRSH=null; crW=false; try{closeModals();}catch(e){} c.tab='home'; crRender();});
row.appendChild(a); row.appendChild(b); body.appendChild(row);
}
function crAfterQs(){ if(CRSH.done=s.bq.length-1?'Finish shift':'Next'); nx.type='button';
nx.addEventListener('click',function(){s.bi++; if(s.bi>=s.bq.length)crShiftEnd(); else crShiftRender();}); body.appendChild(nx);
}); box.appendChild(bt);}); body.appendChild(box); body.appendChild(fb);
}
function crShiftEnd(){
var s=CRSH, c=crS(); if(!s||!c)return; var jp=c.path, ot=!!s.ot, pay=crPayCalc(jp,c.rank,ot), gain=s.ok, dk=crDKey(c);
var tired=false; S.st.a=(S.st.a|0)+CR_Q; S.st.f=(S.st.f|0)+s.ok; crSyncSP(c);
var blk=0, bt=[], bk=[]; try{var got0=crShiftBlocks(jp,c.rank,Math.round(s.ok*CR_QB/CR_Q)); Object.keys(got0).forEach(function(k){inv[BY[k]]=(inv[BY[k]]||0)+got0[k]; blk+=got0[k]; bt.push(got0[k]+' '+typeById(BY[k]).name); bk.push([k,got0[k],typeById(BY[k]).name]);}); updateHotbar();}catch(e){}
c.part=null; crTick(c,CR_SESSMIN); c.pp+=s.bok; c.qa+=CR_Q; c.qc+=s.ok; c.shifts++; c.shAt++; c.lastShift=c.day;
c.coins+=pay.take; c.locked+=pay.store;
c.bars.f=crClamp(c.bars.f-12); c.bars.w=crClamp(c.bars.w-12); c.bars.e=crClamp(c.bars.e+Math.min(20,Math.round(s.ok*CR_QB/CR_Q)*2)-(ot?15:0));
c.wk[dk]=ot?2:1; if(ot){c.ot[crRestGroup(dk)]=1;}
try{crAfterShift(c,ot);}catch(e){}
var got=[], gotK=[], n=Math.min(2,Math.floor(Math.round(s.ok*CR_QB/CR_Q)/4)), i; for(i=0;i14)c.hist.shift();
c.last={blk:blk,bt:bt.join(', '),bk:bk,ik:gotK,dt:dk,ot:ot?1:0,day:c.day,rank:c.rank,job:s.title,gross:pay.gross,locked:pay.locked,employer:pay.employer,take:pay.take,right:s.ok,n:CR_Q,sp:gain,items:got};
var promo=false; c.sp=crSyncSP(c); if(!c.ev&&crPromoMet(c)){c.ev={k:'interview',day:c.day+1}; promo=true; crMail({k:'interview',t:'Promotion interview tomorrow',p:'You have the skills and the experience for the next rank. Your interview is tomorrow morning. Go to bed on time so you are rested.'});}
crOTMode=false; crSave(); CRSH=null; crSlipRender(c,tired,promo);
}
var crEbNote='';
function crAfterShift(c,ot){
if(ot)c.bw.ot++; else c.bw.sh++;
crEbNote=''; if(c.eb){c.bars.e=crClamp(c.bars.e+CR_EB); c.eb=0; crEbNote='Your rest and fun gave you a little more energy for this shift (+'+CR_EB+').';}
}
function crSlipRender(c,tired,promo){
var body=$c('jobBody'), L=c.last; body.innerHTML=''; body.appendChild(mk('h3','sub','Shift finished'));
body.appendChild(mk('p','jobq','You answered '+L.right+' of '+L.n+' questions correctly on the first try.'+(L.dt?' '+crFmt(L.dt)+'.':'')));
var t=mk('div','crslip');
t.innerHTML='
'+(L.ot?'Overtime pay, '+CR_OT+' times daily pay':'Daily pay')+' ('+esc(L.job)+')'+crMs(L.gross,'')+'
'+
'
Locked savings (20% of your pay)'+crMs(L.locked,'\u2212')+'
'+
'
Aura dollars you can spend'+crMs(L.take,'')+'
'+
'
'+(c.age==='7-10'?'Your boss also adds to your Locked savings':'Employer adds to your Locked savings (17%)')+''+crMs(L.employer,'+')+'
';
body.appendChild(t);
body.appendChild(mk('p','lvldesc','You are paid for finishing the shift, whatever your score. Promotion needs skill, so keep practising. Skill points earned: '+L.sp+(tired?' (reduced because you are hungry, thirsty or tired)':'')+'. Skill level '+crSkillLv(c.sp)+' of 10.'));
if(crEbNote){body.appendChild(mk('p','lvldesc',crEbNote)); crEbNote='';}
if(L.blk){body.appendChild(mk('p','lvldesc','Blocks earned: '+L.blk+'. Use them in Aura Builder.')); var bw=mk('div','crrwd'); (L.bk||[]).forEach(function(x){var ch=mk('span','crchip',''); try{ch.appendChild(iconCanvas(x[0]));}catch(e){} ch.appendChild(document.createTextNode(x[1]+' '+x[2])); bw.appendChild(ch);}); if(bw.children.length)body.appendChild(bw); else if(L.bt)body.appendChild(mk('p','lvldesc',L.bt));}
if(L.items.length){body.appendChild(mk('p','lvldesc','Quiz reward for your flat:')); var iw=mk('div','crrwd'); L.items.forEach(function(nm,ix){var ch=mk('span','crchip',''); ch.innerHTML=crSmallIcon((L.ik||[])[ix]); ch.appendChild(document.createTextNode(nm)); iw.appendChild(ch);}); body.appendChild(iw);}
if(promo)body.appendChild(mk('p','jobq','You have the skills for a promotion. Your interview is tomorrow. Go to bed on time.'));
var row=mk('div','row2'), b=mk('button','btn primary','Go home'); b.type='button'; b.addEventListener('click',function(){crW=false; try{closeModals();}catch(e){} c.tab='home'; crRender();}); row.appendChild(b); body.appendChild(row);
}
/* ---- the start screen picker, the menu button and the teacher switch (all added from script, so no existing HTML changes) ---- */
/* Play time in Career: the plan's play limit also counts the time at home. Working a shift (the Town Centre) counts as learning and unlocks play. */
function crPlayTxt(){ try{ if(!planOn()||classBlocked())return ''; if(S.ph==='play')return 'Play '+fmt(playLeft()); if(dayRemain()<=0)return 'Play done today'; return S.pl.u==='q'?(Math.min(S.rd.q,S.pl.uq)+' of '+S.pl.uq+' questions to unlock play'):(fmt(Math.max(0,S.pl.um*60-S.rd.lm))+' of learning to unlock play'); }catch(x){return '';} }
var crPT=0;
setInterval(function(){
try{
if(!crOpen||document.hidden||!planOn()||classBlocked())return;
rollDay(); var atWork=false; try{var jm=document.getElementById('jobModal'); atWork=!!(jm&&!jm.hidden);}catch(x){}
if(atWork||(typeof modal!=='undefined'&&modal&&!modal.hidden)){ S.day.lm+=1; S.rd.lm+=1; }
else if(S.ph==='play'){ S.day.pm+=1; S.rd.pm+=1; }
if(S.ph==='learn'&&dayRemain()>0&&unlockMet()){ unlockPlay(); }
else if(S.ph==='play'&&!atWork){
var l=playLeft();
if(l<=60&&!window.__crW1){window.__crW1=1; crMsg='One minute of play left.'; try{crRender();}catch(x){}}
if(l>60)window.__crW1=0;
if(l<=0){ window.__crW1=0; crLeave(); endPlay(); return; }
}
var pc=document.getElementById('crPlayChip'), tx=crPlayTxt(); if(pc){ var bb=pc.querySelector('b'); if(bb&&bb.textContent!==tx)bb.textContent=tx; }
if(++crPT%15===0)save();
}catch(x){}
},1000);
function crEnter(){
if(!S.st.cr)S.st.cr=crNew(); var c=S.st.cr; crRoll();
try{ if(localStorage.getItem('mbFocus')!=='0'&&!(document.fullscreenElement||document.webkitFullscreenElement)){ var _de=document.documentElement, _rq=_de.requestFullscreen||_de.webkitRequestFullscreen; if(_rq){ var _r=_rq.call(_de); if(_r&&_r.catch)_r.catch(function(){}); } } }catch(e){} try{pauseGame();}catch(e){} crOpen=true; $c('crApp').hidden=false; document.body.classList.add('cr-on'); try{document.body.style.overflow='hidden';}catch(e){}
if(!c.setup)c.tab='home'; c.o=1; crRender(true);
}
function crLeave(){
try{if(S.st.cr)S.st.cr.o=0;}catch(e){}
crOpen=false; crW=false; $c('crApp').hidden=true; document.body.classList.remove('cr-on'); try{document.body.style.overflow='';}catch(e){}
crSave(); try{closeModals();}catch(e){}
try{if(FRESH&&!localStorage.getItem('mbStartDone')){var md=$c('startModal'); md.hidden=false; var p=$c('startPanel'); if(p)p.classList.remove('crhold');}}catch(e){}
}
function crConfirm(title,text,yes,no,cb){
$c('crConfirmT').textContent=title; $c('crConfirmP').textContent=text; $c('crConfirmYes').textContent=yes; $c('crConfirmNo').textContent=no;
var m=$c('crConfirm'); m.hidden=false; $c('crConfirmYes').onclick=function(){m.hidden=true; cb();}; $c('crConfirmNo').onclick=function(){m.hidden=true;};
try{$c('crConfirmPanel').focus({preventScroll:true});}catch(e){}
}
function crMenuClick(){
if(crOpen)return; if(typeof modalOpen==='function'&&modalOpen())return; if(!crEnabled()){try{hint('Aura Life is switched off. Ask your teacher.',true);}catch(e){} return;}
crConfirm('Go to Aura Life?','Aura Life is a beta. Your Aura Builder world is saved and paused. In Aura Life you work shifts, earn Aura dollars, pay bills and look after your own flat. Aura Life has its own money and its own flat.','Go to Aura Life','Stay in Aura Builder',crEnter);
}
function crInstallMenu(){
if($c('crMenuBtn'))return; var jb=$c('jobBtn'); var b=document.createElement('button'); b.className='btn small'; b.id='crMenuBtn'; b.type='button'; b.textContent='Have an Aura Life (beta)'; b.style.display='none';
if(jb&&jb.parentNode)jb.parentNode.insertBefore(b,jb.nextSibling); else return; b.addEventListener('click',crMenuClick);
}
function crSyncMenu(){var b=$c('crMenuBtn'); if(b)b.style.display=crEnabled()?'':'none';}
function crInstallPicker(){
var md=$c('startModal'); if(!md)return;
function inject(){
if(md.hidden||!crEnabled())return; var panel=$c('startPanel'); if(!panel||panel.querySelector('.crpick'))return;
var d=document.createElement('div'); d.className='crpick';
d.innerHTML='
Choose a mode
You can switch between them at any time.
'+
''+
'
';
panel.insertBefore(d,panel.firstChild); panel.classList.add('crhold');
$c('crPickCreative').addEventListener('click',function(){panel.classList.remove('crhold'); d.remove();});
$c('crPickCareer').addEventListener('click',function(){md.hidden=true; panel.classList.remove('crhold'); d.remove(); crEnter();});
}
try{new MutationObserver(inject).observe(md,{attributes:true,attributeFilter:['hidden']});}catch(e){}
inject();
}
function crPlanUI(){
var pb=$c('planBody'); if(!pb||pb.querySelector('.crplan'))return; var grids=pb.querySelectorAll('.plangrid'); if(!grids.length)return;
var w=planSeg('Aura Life (beta) for this class',[['Off',0],['On',1]],S.pl.cr===0?0:1,function(v){S.pl.cr=v; planChanged(false);});
w.classList.add('crplan'); var p=mk('p','lvldesc','When On, Learners can choose Have an Aura Life on the start screen and from the Menu. Aura Life has its own flat, money and day loop, and Aura Builder stays as it is. Publish the plan to change it for the class. Default: On (beta).'); w.appendChild(p);
grids[grids.length-1].appendChild(w);
var w2=planSeg('Aura dollars for finished quiz rounds',[['On',1],['Off',0]],S.pl.au===0?0:1,function(v){S.pl.au=v; planChanged(false);}); w2.classList.add('crplan2'); w2.appendChild(mk('p','lvldesc','Learners earn a small fixed amount of Aura dollars for each finished round of 10 questions, up to '+CR_AURA.dailyCap+' rounds a day, in any mode. Rounds answered too fast earn nothing. Nothing is paid for single answers.')); grids[grids.length-1].appendChild(w2);
}
/* ---- hooks into existing functions (wrapped at run time, so the original code is not edited) ---- */
(function(){
var _rs=recordSkill; recordSkill=function(q,ok,ms){var r=_rs.apply(this,arguments); try{crQuizCount(q,ok,ms);}catch(e){} return r;};
var _ss=startShift; startShift=function(j){if(crOpen&&crW)return crStartShift(j); return _ss.apply(this,arguments);};
var _g=gated; gated=function(){if(crOpen&&crW)return classBlocked(); return _g.apply(this,arguments);};
var _mo=modalOpen; modalOpen=function(){if(crOpen&&!crW)return true; return _mo.apply(this,arguments);};
var _rp=renderPlan; renderPlan=function(){var r=_rp.apply(this,arguments); try{crPlanUI();}catch(e){} try{crSyncMenu();}catch(e){} return r;};
var _br=buildReport; buildReport=function(){var r=_br.apply(this,arguments); try{var s=crSummary(); if(s&&r&&typeof r==='object')r.cr=JSON.stringify(s);}catch(e){} return r;};
})();
function crGoWork(){
var c=crS(); if(!c)return; crRoll(); if(c.slept){crMsg='You are in bed for tonight. A new day starts tomorrow.'; crRender(); return;} crW=true;
var jm=$c('jobModal'), jb=$c('jobBack'); try{closeModals();}catch(e){} CRSH=null; crW=true; var _oe=null; try{openJobs();}catch(e){_oe=e; try{console.warn('Town Centre',e);}catch(_){}}
if(jm&&jm.hidden){ /* the 3D town did not open (for example the browser had no graphics left): fall back to the job list so the shift can still start */
try{ crW=true; try{if(J3)j3Mode('none');}catch(x){} JB=null; renderJobs(); jm.hidden=false; jm.scrollTop=0; try{pauseGame();}catch(x){} }catch(e2){ _oe=_oe||e2; try{console.warn('Job list',e2);}catch(_){} }
}
if(jm&&jm.hidden){crW=false; crMsg='Town Centre could not open right now. Close the other tabs of this game and try again.'+(_oe&&_oe.message?' ('+String(_oe.message).slice(0,60)+')':''); crRender();}
else if(jb){if(!jb.dataset.crOld)jb.dataset.crOld=jb.textContent; jb.textContent='Back to Aura Life';}
}
(function(){
var jm=$c('jobModal'); if(!jm)return;
try{new MutationObserver(function(){if(jm.hidden){var jb=$c('jobBack'); if(jb&&jb.dataset.crOld){jb.textContent=jb.dataset.crOld; delete jb.dataset.crOld;} if(crW){crW=false; crOTMode=false; CRSH=null; if(crOpen)crRender();}}}).observe(jm,{attributes:true,attributeFilter:['hidden']});}catch(e){}
})();
/* ===================== CAREER MODE (beta preview): screens ===================== */
var CR_TABS=[['home','Home'],['work','Work'],['shop','Shop'],['mail','Mail'],['more','More']];
var CR_MORE=['cal','career','credits','design','value'];
var CR_TICO={home:'',work:'',shop:'',mail:'',more:''};
var crEx=true, crHintDone=false;
var CR_AIMG={}, CR_AR=null;
function crAuraImg(sh,pn,ex){
/* the Learner's own Aura (3D look) drawn once in the outfit colours, so the shop shows our Aura, not a stick figure */
var key=[sh,pn,ex,JSON.stringify((S&&S.av)||{})].join('|'); if(CR_AIMG[key]!==undefined)return CR_AIMG[key];
var url='';
try{
if(!CR_AR){ CR_AR={r:new THREE.WebGLRenderer({alpha:true,antialias:true,preserveDrawingBuffer:true}), sc:new THREE.Scene(), cam:new THREE.OrthographicCamera(-.56,.56,2.04,-.02,.1,20)};
CR_AR.r.setSize(150,280); CR_AR.r.setClearColor(0x000000,0); CR_AR.sc.add(new THREE.AmbientLight(0xffffff,.78)); var dl=new THREE.DirectionalLight(0xffffff,.55); dl.position.set(1.5,3,4); CR_AR.sc.add(dl); CR_AR.cam.position.set(0,0,6); CR_AR.cam.lookAt(0,0,0); }
var av0=sanitiseAv(S.av||{}), cc=avCols(av0);
function mix(h,f){var n=parseInt(String(h).replace('#',''),16); var r=Math.min(255,Math.round(((n>>16)&255)*f)), gg=Math.min(255,Math.round(((n>>8)&255)*f)), bb=Math.min(255,Math.round((n&255)*f)); return '#'+((1<<24)+(r<<16)+(gg<<8)+bb).toString(16).slice(1);}
var pal={out:sh,outS:mix(sh,.78),outL:mix(sh,1.2),leg:pn};
if(/^#[0-9a-f]{6}$/i.test(cc.sk)){pal.skin=cc.sk; pal.skinS=mix(cc.sk,.88);}
var g=mkAura({noPack:1,noAura:1,noHat:1,plain:1,pal:pal}); g.scale.setScalar(.9);
function B(w,h,d,c2,px,py,pz){var b2=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),new THREE.MeshLambertMaterial({color:col(c2)})); b2.position.set(px,py,pz); g.add(b2);}
var sc9=1/.9;
if(ex==='apron')B(.5,.5,.04,'#c0463a',0,.86,.2);
if(ex==='blazer'){B(.7,.58,.4,'#1f3a5f',0,.88,0); B(.26,.38,.28,'#1f3a5f',-.45,.99,0); B(.26,.38,.28,'#1f3a5f',.45,.99,0); B(.14,.36,.04,'#f4f4f0',0,.95,.2);}
if(ex==='coat'){B(.72,.6,.42,'#f2c230',0,.88,0); B(.28,.4,.3,'#f2c230',-.46,.98,0); B(.28,.4,.3,'#f2c230',.46,.98,0);}
g.position.y=0; CR_AR.sc.add(g); CR_AR.r.render(CR_AR.sc,CR_AR.cam); url=CR_AR.r.domElement.toDataURL('image/png'); CR_AR.sc.remove(g); try{ CR_AR.r.dispose(); CR_AR.r.forceContextLoss(); }catch(e2){} CR_AR=null;
}catch(e){ url=''; }
CR_AIMG[key]=url; return url;
}
function crFigure(c,id){
var o=crOutItem(id)||CR_OUT[0], sh=o[2], pn=o[3], ex=o[4], av=(S&&S.av)||{}, sk=av.sk||'#f1c7a0', hr=av.hr||'#3b2a1a';
if(id==='work'){var u=CR_UNI[c.path]||CR_UNI.food; sh=u[0]; pn=u[1]; ex=u[2];}
var aimg=crAuraImg(sh,pn,ex);
var s='';
s+='';
s+='';
s+='';
s+='';
s+='';
if(ex==='apron')s+='';
if(ex==='blazer')s+='';
if(ex==='coat')s+='';
s+='';
if(id==='work'&&c.path==='food')s+='';
s+='';
return s;
}
function crBar(label,v,low){return '
'+label+'
'+(v<25?'Low':v)+'
';}
function crEv(c){
if(!c.ev)return ''; var h='';
if(c.ev.day<=c.day)h+='
Today: promotion interview. Your interview is this morning. You need to feel rested to do well.
';
else if(c.ev.day===c.day+1)h+='
Tomorrow: promotion interview. Go to bed on time tonight. Tap your bed to sleep.
';
return h;
}
function crSelPanel(c){
var s=c.sel, h='', k;
function kitchen(){return crKitchenHTML(c);}
if(s==='bed'){
h+='
Bed
';
if(!crHasBed(c))h+='
You need a bed to sleep. Buy one in the Shop.
';
else if(c.slept)h+='
You are in bed for tonight. The clock runs fast while you sleep. A new day starts tomorrow.
';
else h+='
Sleep restores your Energy for tomorrow.'+(c.ev&&c.ev.day<=c.day+1?' You have an interview coming, so rest well.':'')+'
Tap something in your flat, or choose it from the list: the kitchen, toilet, shower, mailbox, front door or a piece of furniture you have bought. Everything is drawn to scale.
';
if(s)h=''+h;
return h;
}
function crHome(c){
var h='', r=crRankRow(c.path,c.rank), di=crDayInfo(crDKey(c));
h+='
'+esc(crFmt(crDKey(c)))+''+(di.tag?' '+esc(di.tag)+'':'')+' Day '+c.day+'
';
h+=crEv(c);
if(crUnpaid())h+='
You have '+crUnpaid()+' unpaid bill'+(crUnpaid()>1?'s':'')+' in your mailbox.
Eat and drink to stay ready for work. Sleep before a big day.
'+crWorkBtns(c)+'
';
h+='
'+crPlan(c)+'
';
h+=crStripHTML(c)+'
'+crSelPanel(c)+'
';
return h;
}
function crWork(c){
var r=crRankRow(c.path,c.rank), pay=crPayCalc(c.path,c.rank), h='
Your job
'; if(crMsg){h+='
'+esc(crMsg)+'
'; crMsg='';}
h+='
'+esc(r[0])+'
Rank '+c.rank+' of 10 · '+esc(CR_PATHS[c.path].name)+' path
One shift a day: 3 sessions of quiz. Finish the shift to get paid.
'+crWorkBtns(c)+'
';
h+='
Daily pay
'+crM(pay.gross)+'
Paid when you finish a shift on a working day.
';
h+='
Locked savings
'+crM(c.locked)+'
20% of your pay is locked away for your future.
';
var ic=function(d){return '';};
var ab=function(d,t,x){return '
'+ic(d)+'
'+t+''+x+'
';};
h+='About my pay and my shift
';
h+=ab('','Daily pay','Monthly pay divided by 22 days. Same whatever your score.');
h+=ab('','Rest days','No daily pay on weekends and holidays.'+(CR_PATHS[c.path].ot?' Overtime pays '+CR_OT+' times.':''));
h+=ab('','Locked savings','Kept for your future. You cannot spend them now'+(c.age==='7-10'?'.':'. Your employer adds 17% more.'));
h+=ab('','Real-life pay',crN(r[1])+' Singapore dollars a month'+(r[3]?' ('+r[3]+')':r[2]?' (an estimate for play)':' (middle value, MOM 2025)')+'.');
h+=ab('','One shift a day','3 sessions, '+CR_Q+' questions. Stop between sessions and carry on later. You are paid when all 3 are done'+(c.age==='7-10'?'.':'. A bonus round may follow.'));
h+=ab('','Skills and blocks','Your answers build your skill level and earn blocks from your path.');
h+=ab('','Workplaces',esc(CR_PATHS[c.path].places)+'.');
h+='
Single answers do not earn Aura dollars. Pay is estimated from published Singapore wage data and can vary with qualifications and experience.
';
if(c.last){var L=c.last; h+='
Last pay slip ('+esc(L.dt?crFmt(L.dt):'day '+L.day)+')
'+esc(L.job)+''+crM(L.gross)+'
Locked savings- '+crM(L.locked)+'
Aura dollars you can spend'+crM(L.take)+'
'+L.right+' of '+L.n+' correct. Skill points: '+L.sp+'.
';}
return h;
}
var crShopTab='';
/* ---- colour choice when buying: [name, hex]. Index 0 is the original look. Only these main parts change colour. ---- */
var CR_COLS={
fridge:[['White',0xf1f3f5],['Silver',0xb8bec4]],
bed1:[['Blue',0x8fb0c9],['Sage',0x92ad91],['Grey',0xa9adb3],['Rose',0xd9a9a9]],
ward:[['White',0xeae5db],['Light wood',0xcfaf84],['Grey',0xb9bcc0]],
desk:[['Light wood',0xcfaf84],['White',0xece8df],['Dark wood',0x8a5a2b]],
sofa:[['Sage',0x92ad91],['Blue',0x8fb0c9],['Grey',0xa9adb3],['Terracotta',0xc98a6b]],
tv:[['Wood',0xc6a577],['White',0xece8df],['Dark wood',0x8a5a2b]],
table:[['Light wood',0xcca876],['White',0xece8df],['Dark wood',0x8a5a2b]],
rug:[['Sage',0xc9d8cb],['Blue',0xbcd0e0],['Cream',0xeadfc8],['Rose',0xe3c3c3]]
};
var CR_MAINS={fridge:[[0xf1f3f5,1]], bed1:[[0x8fb0c9,1]], ward:[[0xeae5db,1],[0xdcd5c7,.95]], desk:[[0xcfaf84,1]], sofa:[[0x92ad91,1],[0xa9c1a7,1.15]], tv:[[0xc6a577,1]], table:[[0xcca876,1]], rug:[[0xc9d8cb,1],[0xd8e3d8,1.06]]};
function crTint(h,f){ if(f===1)return h; var r=Math.min(255,Math.round(((h>>16)&255)*f)), g=Math.min(255,Math.round(((h>>8)&255)*f)), b=Math.min(255,Math.round((h&255)*f)); return (r<<16)|(g<<8)|b; }
function crColHex(h){ return '#'+('000000'+h.toString(16)).slice(-6); }
var crPickCol={};
function crSetCol(id,i){ var c=crS(), L=CR_COLS[id]; if(!L||!(i>=0&&i'+L.map(function(x,i){return '';}).join('')+''+esc(L[cur][0])+'';
}
/* ---- home value (play money): the empty flat with its built-ins, plus each piece you own ---- */
var CR_HOMEBASE=250000; /* about the middle of the 2-room Flexi Type 2 BTO prices, Jun 2026 (HDB): $157,000 to $406,000 before grants */
var CR_HDBHIST=[['1970 (one family)',7200],['1970s',15000],['1980s',50000],['1990s',120000],['2000s',110000],['2018',291000]];
function crHomeValue(c){ var rows=[['Empty flat with built-ins',CR_HOMEBASE]], tot=CR_HOMEBASE; CR_SHOP.forEach(function(s){ if(c.owned[s[0]]){ rows.push([s[1],s[3]]); tot+=s[3]; } }); return {rows:rows,tot:tot}; }
function crValueTab(c){
var v=crHomeValue(c), h='
Home value
This is the value of your flat in play money. A furnished flat is worth more than a bare one.
';
v.rows.forEach(function(r){ h+='
'+esc(r[0])+''+crM(r[1])+'
'; });
h+='
Your home is worth'+crM(v.tot)+'
';
h+='
A new flat today
In June 2026, a new 2-room Flexi (Type 2) BTO flat, with one bedroom, was priced from about $157,000 to $406,000 before grants, depending on the town. Your flat starts at about the middle of that range.
Source: HDB, June 2026 BTO sales exercise. Prices are indicative ranges for a 99-year lease.
';
h+='
What a new 3-room HDB flat cost
Rounded prices in Singapore dollars over the years.
';
CR_HDBHIST.forEach(function(q){ h+='
'+esc(q[0])+'$'+crN(q[1])+'
'; });
h+='
HDB began on 1 February 1960. The first flats were built for rent only, and flats were sold from 1964. Prices are rounded from public sources (EdgeProp, Home and Decor, NLB Infopedia). Check the official HDB data for exact figures.
Furniture uses real Singapore sizes. Buy a piece, then choose where it goes in your flat. Small things such as cups, plants and books are earned from quizzes.
';
if(crUnpaid()>=2)h+='
Pay your bills in the mailbox before you buy more.
Next promotion: '+esc(need.title)+'pay Aura $'+crN(crDaily(c.path,need.to))+' a shift
Skill level '+need.skill+' (you have '+sk+')
'+need.shifts+' shifts at this rank (you have '+c.shAt+')
'+(need.peopleLive?'People skills level '+need.people+' (you have '+crPeopleLv(c.pp)+')':'People skills: not needed until the bonus round is on')+'
When all three are done, you get a promotion interview the next morning. Go to bed on time.
';}
else h+='
You are at the top rank.
';
h+='
The '+esc(path.name)+' path
Rank
Job
Daily pay
Real-life pay a month (Singapore dollars)
';
path.ranks.forEach(function(r,i){h+='
'+(i+1)+'
'+esc(r[0])+(r[2]?' (estimate)':'')+'
'+crM(crDaily(c.path,i+1))+'
'+crN(r[1])+'
';});
h+='
Real-life pay: Ministry of Manpower, Occupational Wage Survey 2025, Table 4 (median gross monthly wage, full-time resident employees). Not endorsed by MOM. Rows marked estimate are for play. Daily pay is the monthly pay divided by 22 working days. Pay shown is estimated from published Singapore wage data. Real pay can vary with qualifications and experience.
';
h+='
My resume
A resume is a short page that tells an employer about you. This one fills itself in from your work.
Work so far: '+(c.shifts|0)+' shifts over '+(c.day|0)+' days. Now at rank '+c.rank+' of 10 ('+esc(crJobTitle(c))+').
Skills: skill level '+crSkillLv(c.sp)+' of 10 ('+(c.sp|0)+' skill points), people skills '+crPeopleLv(c.pp)+' of 10.
Steady habits: '+(c.bw&&c.bw.ok?c.bw.ok+' nights of sleeping on time. ':'')+'Savings of '+crM(c.locked||0)+' in Locked savings.
At an interview, say what you can do and give one example from your shifts.
';
var d=crBizDef(c.path);
h+='
Own a business (optional)
';
if(c.biz)h+='
'+esc(c.biz.name)+'
You put in '+crM(c.biz.cap)+'. It earns about '+crM(crBizProfit(c))+' on each day you work. Closing it gives back 60%, so you lose part of your money.
';
else h+='
'+esc(d.name)+'
You need rank '+d.minRank+' and '+crM(d.cap)+' saved. There are no loans or debts. If you close it, you lose part of the money you put in.
'+(c.rankReach rank '+d.minRank+' first.':c.coinsKeep saving. You have Aura $'+crN(c.coins)+'.':'')+'
';
h+='
Change your path
You can pick a different path. You start again at rank 1. Your skills, Aura dollars, Locked savings and flat stay.'+(c.biz?' Close your business first.':'')+'
Aura dollars are play money and have no cash value. Aura Life is a beta.
';
return h;
}
var crCalOff=0;
function crCalTab(c){
var dk=crDKey(c), base=crPD(dk), y=base.getUTCFullYear(), mo=base.getUTCMonth()+crCalOff, first=new Date(Date.UTC(y,mo,1)), yy=first.getUTCFullYear(), mm=first.getUTCMonth();
var dim=new Date(Date.UTC(yy,mm+1,0)).getUTCDate(), lead=first.getUTCDay();
var billIn=CR_MONTH-(c.day%CR_MONTH), billKey=crAddD(dk,billIn);
var h='
Calendar
'+CR_MN[mm]+' '+yy+'
';
h+='
Days of '+CR_MN[mm]+' '+yy+'. Marks are written in words as well as shown in colour.
Rest Saturday and Sunday: rest days, no daily pay.
Holiday Singapore public holiday: a rest day too.
Today The day it is now in Singapore.
Worked A day you finished a shift. Overtime A rest day you chose to work.
Bill due About when your next bill letter arrives (every '+CR_MONTH+' days of play).
';
if(list.length)h+='
Public holidays this month
'+list.map(function(x){return '
'+esc(x)+'
';}).join('')+'
';
h+='
Next bill letter: about '+esc(crFmt(billKey))+'. Public holiday dates come from the Ministry of Manpower list for 2026 and 2027 and should be checked each year.
';
return h;
}
/* ---- Stage 2 screens ---- */
var crPhApp='';
function crStripHTML(c){
var u=crUnpaid(); function T(k,lab){ return ''; }
return '
Inventory'+T('phone','Phone')+T('laptop','Laptop')+T('leisure','Rest and fun')+T('kitchen','Kitchen')+'
';
}
function crKitchenHTML(c){
var left=3-['b','l','d'].filter(function(k){return c.meals[k];}).length, P=c.pan, h='
Cooking takes a little energy and time. An egg fills you up about two thirds as much as a proper meal; noodles about half.
';
h+='
In your kitchen
'+esc(crPantryLine(c))+'
'+(c.owned.fridge?'Your fridge keeps eggs for about two weeks and groceries for about a week.':'You have no fridge. Eggs and groceries go bad in a day or two. A fridge is optional, and it is in the Shop.')+' Order food on your phone.
';
h+='
To eat out, go through the front door: hawker centre or cafe. You can also order delivery on your phone.
';
return h;
}
function crEatOutHTML(c){
var left=3-['b','l','d'].filter(function(k){return c.meals[k];}).length, h='
Eat out ('+left+' meal'+(left===1?'':'s')+' left today)
'+[['groc','Groceries','Order eggs, groceries and noodles'],['deliv','Food delivery','Meals brought to your door'],['second','Second-hand','Sell or donate things you own'],['polls','Polls','Share your ideas']].map(function(a){return '';}).join('')+'
';
}
return h;
}
function crSecondHTML(c){
var h='
Second-hand
Sell or give away things you own. Reusing things saves resources and keeps them out of the bin. Buyers are game characters: there is no chat and nobody to meet. The price is '+Math.round(CR_SELL_FIRST*100)+'% of the shop price, and '+Math.round(CR_SELL_LATER*100)+'% once you have owned it for '+CR_SELL_DAYS+' days. You can list lower to sell sooner.
';
var ids=crSellList(c); if(!ids.length)h+='
You have nothing to sell yet. Things you buy in the Shop will appear here.
';
ids.forEach(function(id){
var b=crItemBase(id), l=crListed(c,id), pr=crSellPrice(c,id), own=c.day-((c.ownD&&c.ownD[id])||c.day);
h+='
'+esc(b.name)+'
Shop price Aura $'+b.price+'. You have had it '+own+' day'+(own===1?'':'s')+'.
';
if(l){ if(c.day>=l.ready)h+='
A buyer will pay '+crM(l.price)+'.
'; else h+='
Listed for Aura $'+l.price+'. A buyer usually answers by tomorrow.
Cannot be sold: the kitchen counter, toilet, basin, shower, front door and gate (they come with the flat), your water bottle, and your free clothes. Donating gives an item away for free.
';
return h;
}
function crLaptopHTML(c){
var h='
Laptop
Your laptop is free, like your phone.
';
if(!c.lp1){c.lp1=1; h+='
A computer for school. In Singapore, the Infocomm Media Development Authority (IMDA) runs programmes such as NEU PC Plus, so that families who need help can get an affordable computer for school. Ask your school for details, and look on the IMDA website for the latest information.
';}
else h+='About computers for school
In Singapore, the Infocomm Media Development Authority (IMDA) runs programmes such as NEU PC Plus, so that families who need help can get an affordable computer for school. Ask your school for details, and look on the IMDA website for the latest information.
';
h+='
Work from home
Laptop shifts are for office-type jobs. Your job, '+esc(crJobTitle(c))+', is done at the workplace in town, so there are no laptop shifts for you yet.
';
h+='
Home internet
Basic: Aura $'+CR_NETP[0]+' a month, limited choice and short waits. Faster: Aura $'+CR_NETP[1]+' a month, more choice and smooth video. The connection is for fun only. It never changes your pay, and a slow connection never stops you from earning.
';
return h;
}
function crLeisureHTML(c){
var left=Math.max(0,CR_LEI_MAX-(c.lz.d===c.day?c.lz.n:0)), h='
Rest and fun
Time for yourself, family and friends helps you feel ready for work. After one, you get a little extra energy at your next shift (+'+CR_EB+'). You do not need it to move up. Activities that count today: '+left+' of '+CR_LEI_MAX+'.
';
[['home','At home'],['out','Out and about'],['fam','Family'],['fri','Friends']].forEach(function(g){
h+='
Family and friends here are made-up characters. Nobody types messages and nobody real is contacted.
';
return h;
}
function crAct2(el,c,a,x,y){
switch(a){
case 'selpick': c.sel=x; if(x!=='phone')crPhApp=''; return true;
case 'out': crOutAct(x); return true;
case 'phapp': crPhApp=x; return true;
case 'cook': crCook(x); return true;
case 'herb': crHerbCook(x); return true;
case 'hawk': crHawker(+x); return true;
case 'cafe': crCafe(+x); return true;
case 'order': crOrder(x); return true;
case 'deliv': crDeliver(+x); return true;
case 'net': crSetNet(+x); return true;
case 'leis': crLeisure(x); return true;
case 'sell': {
var bed=(x==='bed1'&&!c.owned.bed2&&c.owned.bed1);
if(bed){crConfirm('Sell your only bed?','You will not be able to sleep until you buy another bed. Are you sure?','Sell it','Keep it',function(){crList(x,+y===1); crRender();}); return true;}
crList(x,+y===1); return true; }
case 'unlist': crUnlist(x); return true;
case 'accept': crAccept(x); return true;
case 'donate': crConfirm('Donate this?','You give it away for free and it leaves your flat.','Donate','Keep it',function(){crDonate(x); crRender();}); return true;
case 'collect': crCollect(+x); return true;
}
return false;
}
/* ===================== Polls (Stage 3) =====================
Learner side: a quiet list of polls (Career phone, and a small Polls button next to the Class hub). Teacher / owner side: a card in the teacher account.
Tick-box only. Every poll ends with "Prefer not to say". Owner polls pay Aura $5 for taking part (the same whatever you choose); teacher polls pay nothing.
The server decides the reward and its limits (1 paid poll a day, Aura $20 a week); this code only credits it. */
var PL={polls:null,msg:'',busy:0,host:null,res:{}};
function plOnline(){return /^https?:/.test(location.protocol);}
function plCls(){try{return String((S.me&&S.me.cls)||'').trim().toUpperCase();}catch(e){return '';}}
function plH(){var h={}; try{h=pmHdr();}catch(e){} h['content-type']='application/json'; return h;}
function plLoad(cb){
if(!plOnline()){PL.polls=[]; PL.msg='Polls need an internet connection.'; cb&&cb(); return;}
fetch('/api/poll/list?class='+encodeURIComponent(plCls()),{headers:plH(),cache:'no-store'}).then(function(r){return r.ok?r.json():Promise.reject(r.status);}).then(function(j){PL.polls=j.polls||[]; PL.msg=''; cb&&cb();}).catch(function(){PL.polls=PL.polls||[]; PL.msg='Polls could not load just now. Try again later.'; cb&&cb();});
}
function plBox(p){
var h='
'+esc(p.q)+'
'+(p.k==='c'?'A poll from your teacher.':'A poll from Make Aura.')+(p.max>1?' Tick up to '+p.max+'.':' Tick one.')+'
';
if(p.done)return h+'
Answered Thank you.
';
h+='';
if(p.rw)h+='
Thank you for sharing your ideas. You get Aura $'+p.rw+' for taking part, whatever you choose. Nobody is paid for a particular answer. Only one paid poll a day counts.
';
return h+'';
}
function plRender(host){
if(!host)return; PL.host=host; var h='
Polls ask what Learners think. Answers are only counted. Nobody sees your name with your answer, and you can choose Prefer not to say.
';
if(PL.msg)h+='
'+esc(PL.msg)+'
';
if(PL.polls===null)h+='
Loading polls...
'; else if(!PL.polls.length)h+='
No polls right now.
'; else h+=PL.polls.map(plBox).join('');
host.innerHTML=h;
if(!host.__plw){host.__plw=1;
host.addEventListener('change',function(e){var t=e.target; if(!t||t.dataset.pl!=='tick')return; var box=t.closest('.plbox'), p=(PL.polls||[]).filter(function(x){return x.id===box.dataset.pid;})[0]; if(!p)return;
var all=[].slice.call(box.querySelectorAll('input[data-pl=tick]')), last=p.ch.length-1;
if(t.checked&&+t.dataset.i===last)all.forEach(function(x){if(x!==t)x.checked=false;});
else if(t.checked){all[last].checked=false; var on=all.filter(function(x){return x.checked;}); if(on.length>p.max){t.checked=false; PL.msg=''; try{hint('You can tick up to '+p.max+'.',true);}catch(_){}}}});
host.addEventListener('click',function(e){var b=e.target.closest('[data-pl=send]'); if(!b||PL.busy)return; var box=b.closest('.plbox'); var picks=[].slice.call(box.querySelectorAll('input[data-pl=tick]')).filter(function(x){return x.checked;}).map(function(x){return +x.dataset.i;});
if(!picks.length){PL.msg='Tick an answer first. You can tick Prefer not to say.'; plRender(host); return;}
PL.busy=1; fetch('/api/poll/vote',{method:'POST',headers:plH(),body:JSON.stringify({id:box.dataset.pid,cls:plCls(),picks:picks})}).then(function(r){return r.json().then(function(j){return {s:r.status,j:j};});}).then(function(x){PL.busy=0;
if(x.s===200){var rw=x.j.reward|0; if(rw>0){try{crAddCoins(rw); if(typeof crS==='function'&&crS()&&crS().setup)crSave(); else save();}catch(_){}}
PL.msg=rw>0?'Thank you for sharing your ideas. You get Aura $'+rw+' for taking part.':'Thank you for sharing your ideas.'+((PL.polls||[]).filter(function(q){return q.id===box.dataset.pid&&q.rw;}).length?' You have already had your paid poll for today or this week, so there is no Aura money this time.':'');
(PL.polls||[]).forEach(function(q){if(q.id===box.dataset.pid)q.done=true;});}
else if(x.s===409){PL.msg='You have already answered this poll.'; (PL.polls||[]).forEach(function(q){if(q.id===box.dataset.pid)q.done=true;});}
else PL.msg='Your answer could not be sent. Try again later.';
plRender(host); try{if(typeof crOpen!=='undefined'&&crOpen)crChips(crS());}catch(_){}}).catch(function(){PL.busy=0; PL.msg='Your answer could not be sent. Try again later.'; plRender(host);});});
}
}
function plHook(){var c=null; try{c=crS();}catch(e){} if(!c||c.tab!=='home'||c.sel!=='phone'||typeof crPhApp==='undefined'||crPhApp!=='polls')return; var h=document.getElementById('plHost'); if(!h)return; var body=h; if(PL.polls===null){plRender(h); plLoad(function(){plRender(document.getElementById('plHost'));});} else plRender(h);}
/* small Polls button next to the Class hub, and a plain modal */
function plInstallBtn(){
var hb=document.getElementById('hubOpen'); if(!hb||document.getElementById('plOpenBtn'))return; var b=document.createElement('button'); b.className='btn small'; b.id='plOpenBtn'; b.type='button'; b.textContent='Polls'; hb.parentNode.insertBefore(b,hb.nextSibling);
var m=document.createElement('div'); m.className='modal'; m.id='plModal'; m.hidden=true; m.innerHTML='
Polls
'; document.body.appendChild(m);
b.addEventListener('click',function(){if(typeof modalOpen==='function'&&modalOpen())return; try{pauseGame();}catch(e){} PL.polls=null; m.hidden=false; plRender(document.getElementById('plModalBody')); plLoad(function(){plRender(document.getElementById('plModalBody'));}); try{document.getElementById('plPanel').focus({preventScroll:true});}catch(e){}});
document.getElementById('plBack').addEventListener('click',function(){m.hidden=true; try{closeModals();}catch(e){}});
}
/* teacher and owner: make polls and see counts */
function plAdmUI(){
var sec=document.getElementById('tlSec'); if(!sec||typeof TWHO==='undefined'||!TWHO||!(TWHO.admin||TWHO.teacher))return; var ex=document.getElementById('plAdm');
if(ex){return;} var d=document.createElement('div'); d.id='plAdm'; d.className='tmaths';
var own=!!TWHO.admin;
d.innerHTML='Polls
'+(own?'Owner polls go to all Learners and pay them Aura $5 for taking part.':'A poll goes to your own class only. It pays nothing.')+' Tick-box choices only. "Prefer not to say" is added for you. Do not ask for personal details (health, family, money, address, contact or religion). Counts show only when 5 or more Learners have answered.
'+
''+
''+
'
'+(own?'':'')+'
'+
'
';
sec.appendChild(d);
var cs=document.getElementById('plCls'); if(cs){var codes=Object.keys(typeof TCLS!=='undefined'?TCLS:{}); cs.innerHTML=codes.map(function(c){return '';}).join('');}
var say=function(t){document.getElementById('plMsg').textContent=t;};
document.getElementById('plMake').addEventListener('click',function(){
var body={q:document.getElementById('plQ').value,ch:document.getElementById('plC').value.split(/\n/),max:+document.getElementById('plMax').value};
if(own)body.pre=(document.getElementById('plPre')||{}).value||''; else {body.cls=(document.getElementById('plCls')||{}).value||''; if(!body.cls){say('Make a class first.'); return;}}
fetch('/api/poll/create',{method:'POST',headers:Object.assign({'content-type':'application/json'},tlHdr()),body:JSON.stringify(body)}).then(function(r){return r.json().then(function(j){return {s:r.status,j:j};});}).then(function(x){
if(x.s===200){say('Posted.'); document.getElementById('plQ').value=''; document.getElementById('plC').value=''; plMine();} else say(x.j.detail||x.j.error||'Could not post the poll.');}).catch(function(){say('Could not post the poll.');});
});
plMine();
}
function plMine(){
var box=document.getElementById('plMine'); if(!box)return;
fetch('/api/poll/mine',{headers:tlHdr(),cache:'no-store'}).then(function(r){return r.json();}).then(function(j){
box.innerHTML=(j.polls||[]).map(function(p){return '
';
}
/* ===================== Career: design furniture (OFF until switched on: ?design=1 or localStorage mbDesign=1) =====================
A Learner builds a piece from 1x1, 1x2 and 1x3 parts on a front-view grid. 1 pixel = 10 cm, so the grid is drawn to scale.
Each type has a maximum size. Saving stores it on the device and sends it to Make Aura for approval. All prices are play money. */
function crDsOn(){ try{ return localStorage.getItem('mbDesign')==='1'||/[?&]design=1(&|$)/.test(location.search)||window.__crDesign===true; }catch(e){ return false; } }
var DS_PAL=['#c8a477','#8a5a2b','#f2f0ea','#b9bcc0','#5f646b','#2b2b2e','#2f4a6b','#4f7a5a','#9bb39a','#b5603f','#d4a537','#c98f8f'];
var DS_PALN=['Light wood','Dark wood','White','Light grey','Dark grey','Black','Navy','Green','Sage','Terracotta','Mustard','Dusty rose'];
function dsRp(s,n){ var a=[]; for(var i=0;iT.p[1])return 'The price must be between Aura $'+T.p[0]+' and Aura $'+T.p[1]+'.';
var n=dsCells(d.parts).n; if(n=0;i--){ var p=d.parts[i]; for(var j=0;j=T.w||cy>=T.h)return;
var hit=dsPartAt(d,cx,cy), msg=document.getElementById('dsMsg'); function say(t){ if(msg)msg.textContent=t||''; }
if(DS.tool==='remove'){ if(hit>=0){ d.parts.splice(hit,1); d.dirty=1; } }
else if(DS.tool==='colour'){ if(hit>=0){ d.parts[hit].c=DS.col; d.dirty=1; } }
else {
var l=DS.len, v=DS.vert?1:0, ok=true, i;
for(i=0;i=T.w||y>=T.h){ say('That part does not fit inside the biggest '+T.n.toLowerCase()+' ('+T.w*10+' × '+T.d*10+' × '+T.h*10+' cm).'); ok=false; break; } if(dsPartAt(d,x,y)>=0){ say('Another part is already there.'); ok=false; break; } }
if(ok){ d.parts.push({x:cx,y:cy,l:l,v:v,c:DS.col}); d.dirty=1; say(''); }
}
dsDraw();
}
function dsPic(parts,k){ var b=dsBox(parts), cv=document.createElement('canvas'); cv.width=Math.max(1,b.w)*k; cv.height=Math.max(1,b.h)*k; cv.style.maxWidth='100%'; var g=cv.getContext('2d'); parts.forEach(function(p){ g.fillStyle=DS_PAL[p.c]||'#999'; for(var i=0;iDesign furniture
Coming soon.
';
if(!DS)DS={v:'list',tool:'place',len:1,vert:0,col:0,k:20,msg:''};
var h='
Build a piece of furniture from 1×1, 1×2 and 1×3 parts. Each square is 10 cm, so your drawing is to scale. Save it and it goes to Make Aura for approval. If it is approved and made, other Learners can buy it with play money.
';
h+='
Start a new design
';
h+='
Trace: a faint blueprint shows a normal one. Place parts over it, then colour them. Free: a blank grid.
';
var L=dsList(c); h+='
My designs
';
if(!L.length)h+='
No designs yet.
';
L.forEach(function(d,i){ var T=DS_TY[d.type]||{n:d.type}; h+='
';
}
function dsEditHTML(c){
var d=DS.cur, T=DS_TY[d.type], h='';
if(DS.msg){ h+='
'+esc(DS.msg)+'
'; DS.msg=''; }
h+='
'+esc(T.n)+' · '+(d.mode==='trace'?'tracing the blueprint (blue)':'free design')+' · biggest allowed '+T.w*10+' wide × '+T.d*10+' deep × '+T.h*10+' high cm. Front view.
'+(d.st==='pending'?'This design is waiting for approval. Saving again replaces it.':'Saving keeps it on this device and sends it to Make Aura to look at. You will see the answer here.')+' All prices are play money.
';
return h;
}
function dsAfterRender(){
var c=crS(); if(!c||c.tab!=='design'||!DS||DS.v!=='edit'||!DS.cur)return; var cv=document.getElementById('dsCv'); if(!cv)return; var T=DS_TY[DS.cur.type], v=document.getElementById('crView'), aw=Math.max(200,(v?v.clientWidth:340)-48);
DS.k=Math.max(8,Math.min(26,Math.floor(aw/T.w))); cv.width=T.w*DS.k; cv.height=T.h*DS.k; cv.style.width=cv.width+'px'; cv.style.height=cv.height+'px';
cv.addEventListener('pointerdown',dsPointer);
var nm=document.getElementById('dsName'), pr=document.getElementById('dsPrice'); if(nm)nm.addEventListener('input',function(){ DS.cur.name=nm.value; }); if(pr)pr.addEventListener('input',function(){ DS.cur.price=+pr.value|0; });
dsDraw();
}
var dsSynced=0;
function dsShop(){
var box=document.getElementById('dsShopList'); if(!box)return;
try{ fetch('/api/furn/shop',{cache:'no-store'}).then(function(r){return r.json();}).then(function(j){
var b=document.getElementById('dsShopList'); if(!b)return; b.innerHTML=''; var it=(j&&j.items)||[];
if(!it.length){ b.textContent='Nothing here yet. Approved designs from Learners will appear here.'; return; }
it.forEach(function(d){ var T=DS_TY[d.type]||{n:d.type}, w=document.createElement('div'); w.className='dsrow'; var l=document.createElement('div'); var p=dsPic(d.parts,8); l.appendChild(p); var t=document.createElement('div'); t.innerHTML=' '; t.firstChild.textContent=d.name; t.lastChild.textContent=T.n+' · by '+d.by+' · Aura $'+d.price+' · buying opens soon'; w.appendChild(l); w.appendChild(t); b.appendChild(w); });
}).catch(function(){ var b=document.getElementById('dsShopList'); if(b)b.textContent='Could not load just now.'; }); }catch(e){}
}
function crDsAct(el,a,c){
var x=el.dataset.a; if(!DS)DS={v:'list',tool:'place',len:1,vert:0,col:0,k:20,msg:''};
if(a==='ds_new'){ var sel=document.getElementById('dsType'), ty=(sel&&DS_TY[sel.value])?sel.value:'chair', T=DS_TY[ty]; DS.cur={did:dsId(),type:ty,name:'',price:T.p[0]*2>T.p[1]?T.p[0]:T.p[0]*2,dep:T.d,mode:x==='trace'?'trace':'free',parts:[],st:'draft'}; DS.v='edit'; DS.tool='place'; }
else if(a==='ds_edit'){ var d=dsList(c)[+x]; if(d){ DS.cur=JSON.parse(JSON.stringify(d)); DS.v='edit'; } }
else if(a==='ds_del'){ var dd=dsList(c)[+x]; if(dd){ var did=dd.did; crConfirm('Delete this design?','It will be removed from this device and from the waiting list.','Delete','Keep',function(){ var L=dsList(crS()); for(var i=0;i=0)L2[idx]=rec; else L2.push(rec); crSave(); DS.cur=rec;
var by=''; try{ by=String((S.me&&S.me.name)||'').trim().split(/\s+/)[0].slice(0,20); }catch(e){}
try{ fetch('/api/furn/save',{method:'POST',headers:{'content-type':'application/json','x-user-id':dsUid()},body:JSON.stringify({did:rec.did,type:rec.type,name:rec.name,price:rec.price|0,dep:rec.dep|0,mode:rec.mode,parts:rec.parts,by:by})}).then(function(r){return r.json().then(function(j){return {s:r.status,j:j};});}).then(function(o){
var cc=crS(); var q=dsList(cc).filter(function(z){return z.did===rec.did;})[0]; if(!q)return;
if(o.j&&o.j.ok){ q.st='pending'; q.reason=''; DS.msg='Saved and sent to Make Aura. It is waiting for approval.'; } else { DS.msg=(o.j&&o.j.error)||'Saved on this device. It could not be sent just now.'; }
crSave(); crRender(); }).catch(function(){ DS.msg='Saved on this device. There is no connection, so it was not sent yet. Open this design and save again later.'; crRender(); }); }catch(e){}
DS.msg='Saving…'; crRender(); return;
}
crRender();
}
function crRender(focus){
if(!crOpen)return; var c=crS(); if(!c)return; if(c.setup)crSyncSP(c); var v=$c('crView'); try{crRoll();}catch(e){}
var nav=$c('crTabs');
if(!c.setup){nav.innerHTML=''; v.innerHTML=crSetup(c); $c('crSub').textContent='Beta · Set up'; crChips(c); return;}
nav.innerHTML=''; CR_TABS.forEach(function(t){var b=document.createElement('button'); b.type='button'; b.setAttribute('role','tab'); var on=(c.tab===t[0])||(t[0]==='more'&&CR_MORE.indexOf(c.tab)>=0); b.setAttribute('aria-selected',String(on)); b.dataset.go=t[0]; b.setAttribute('aria-label',t[1]+((t[0]==='mail'&&crMailCount())?', '+crMailCount()+' to read':'')); b.innerHTML=''+t[1]+''+((t[0]==='mail'&&crMailCount())?''+crMailCount()+'':''); nav.appendChild(b);});
var fn={home:crHome,work:crWork,shop:crShop,mail:crMailTab,cal:crCalTab,career:crCareerTab,more:crMoreTab,credits:crCreditsTab,design:crDsTab,value:crValueTab}[c.tab]||crHome;
v.innerHTML=fn(c); crChips(c); try{plHook();}catch(e){} $c('crSub').textContent='Beta · '+CR_PATHS[c.path].name+' path · '+crFmt(crDKey(c))+' · Day '+c.day;
if(crClkAnimFrom>=0){ var _a=crClkAnimFrom; crClkAnimFrom=-1; crClockAnim(Math.max(_a,1200),1860); }
try{dsAfterRender();}catch(e){}
try{ if(c.tab==='design'&&!dsSynced){dsSynced=1; dsSync();} if(c.tab==='shop')dsShop(); }catch(e){}
if(focus){try{v.focus({preventScroll:true});}catch(e){}}
}
function crChips(c){
var r=c.path?crRankRow(c.path,c.rank):null;
$c('crChips').innerHTML=''+crFaceSvg(c.slept?420:crClk(c))+'Day '+c.day+' · '+(c.slept?'7:00 am · slept':crClkTxt(crClk(c)))+''+(crPlayTxt()?''+crPlayTxt()+'':'')+''+crM(c.coins)+'Locked savings '+crN(c.locked)+''+(r?'Rank '+c.rank+'Skill '+crSkillLv(c.sp)+'/10People '+crPeopleLv(c.pp)+'/10':'');
}
function crAct(el){
var c=crS(), a=el.dataset.act, x=el.dataset.a, y=el.dataset.b;
if(a&&a.slice(0,3)==='ds_'){ crDsAct(el,a,c); return; }
if(a==='shopcol'){ crSetCol(x,+y); crRender(); return; }
if(crAct2(el,c,a,x,y)){crRender(); return;}
if(a==='work'||a==='workot'){crTryWork(a==='workot'); if(crMsg)crRender(); return;}
if(a==='calm'){crCalOff+= +x; crRender(); return;}
if(a==='eat')crEat(x,+y); else if(a==='drink')crDrink(); else if(a==='wash')crWash(x); else if(a==='sleep')crSleep(x);
else if(a==='placebuy'){c.tab='home'; crPlacePending=x; crSave();}
else if(a==='buy')crBuy(x); else if(a==='buyout')crBuyOut(x); else if(a==='wear')crWear(x); else if(a==='paybill')crPayBill(+x);
else if(a==='interview')crInterview(); else if(a==='bizstart')crBizStart(); else if(a==='bizclose')crConfirm('Close the business?','You get back 60% of what you put in. You lose the rest.','Close it','Keep it',function(){crBizClose(); crRender();});
else if(a==='path')crConfirm('Change path?','You will start again at rank 1 on the '+CR_PATHS[x].name+' path. Your skills, Aura dollars, Locked savings and flat stay.','Change path','Stay',function(){crChangePath(x); crRender();});
else if(a==='shoptab'){crShopTab=x;} else if(a==='sstep'){crSS=+x;} else if(a==='age'){c.age=x; crSave();} else if(a==='setage'){c.age=x;}
else if(a==='begin'){crStartCareer(c.age||crDefaultAge(),x); c.tab='home';}
crRender();
}
var crShopOvOn=false;
function crShopOvEl(){
var e=document.getElementById('crShopOv');
if(!e){ e=document.createElement('div'); e.id='crShopOv'; e.className='crshopov'; e.setAttribute('role','region'); e.setAttribute('aria-label','Shop'); e.hidden=true; document.getElementById('crApp').appendChild(e);
e.addEventListener('click',function(ev){ if(ev.target.closest('[data-closeshop]')){ crShopClose(); return; } var b=ev.target.closest('[data-act]'); if(b&&!b.disabled)crAct(b); }); }
return e;
}
function crShopFill(){ var c=crS(), e=crShopOvEl(); if(!c)return; var st=e.scrollTop; e.innerHTML=''+crShop(c); e.scrollTop=st; }
function crShopOpen(){ crShopOvOn=true; var e=crShopOvEl(); e.hidden=false; crShopFill(); try{ e.scrollTop=0; }catch(_){} }
function crShopClose(){ crShopOvOn=false; var e=document.getElementById('crShopOv'); if(e)e.hidden=true; }
(function(){ var _rs=crRender; crRender=function(f){ var m=crMsg; _rs.apply(this,arguments); if(crShopOvOn){ var c=crS(); if(!c||c.tab!=='home'||crPlacePending||!crOpen)crShopClose(); else { crMsg=m; crShopFill(); } } }; var _lv0=crLeave; crLeave=function(){ crShopClose(); return _lv0.apply(this,arguments); }; document.addEventListener('keydown',function(ev){ if(ev.key==='Escape'&&crShopOvOn){ ev.stopPropagation(); crShopClose(); } },true); })();
/* ---------- Community line dancing: the void deck class, or at home along to the TV ---------- */
var CRDANCE=null, CR_DNC={bpm:84, fall:2.0, cin:4};
var CR_DPH=[[0,3,0,3,1,2,1,2],[0,0,3,3,1,1,2,2],[1,0,2,3,1,0,2,3]];
var CR_DCOL=['#3b82c4','#2f9e6a','#d99a2b','#c8573e'], CR_DLAB=['Left','Up','Down','Right'], CR_DNOTE=[523,659,392,880], CR_DROT=[-90,0,180,90];
function crDanceEl(){
var e=document.getElementById('crDance'); if(e)return e;
e=document.createElement('div'); e.id='crDance'; e.className='crdnc'; e.hidden=true; e.setAttribute('role','region'); e.setAttribute('aria-label','Line dancing');
var bt=''; for(var d=0;d<4;d++)bt+='';
e.innerHTML='
Community line dancing
'+bt+'
Tap the arrow, or press the arrow keys, when it reaches the line.
';
document.getElementById('crApp').appendChild(e);
e.addEventListener('pointerdown',function(ev){ var b=ev.target.closest('.cdb'); if(b){ ev.preventDefault(); crDanceHit(+b.dataset.d); } });
e.addEventListener('click',function(ev){ var b=ev.target.closest('[data-dx]'); if(!b||b.disabled)return; var k=b.dataset.dx; if(k==='leave'||k==='done'){ crDanceStop(); try{crRender();}catch(_){} } else if(k==='again'){ var m=CRDANCE?CRDANCE.mode:'class'; crDanceStop(); crDanceStart(m); try{crRender();}catch(_){} } });
return e;
}
function crDanceStart(mode){
var c=crS(); if(!c||!CRH||CRDANCE)return false;
if(c.slept){crMsg='You are in bed for tonight. A new day starts tomorrow.'; return false;}
if(!c.lz)c.lz={d:0,n:0}; if(c.lz.d!==c.day){c.lz.d=c.day; c.lz.n=0;}
if(c.lz.n>=CR_LEI_MAX){crMsg='You have had a good rest and some fun today. More activities tomorrow.'; return false;}
if(c.bars.e<25||c.bars.w<20){crMsg='You are too tired or thirsty to dance now. Rest, and have a drink first.'; return false;}
if(mode==='tv'&&!(c.owned&&c.owned.tv)){crMsg='You need a TV to dance along to.'; return false;}
var steps=[]; CR_DPH.forEach(function(p){p.forEach(function(d){steps.push(d);});}); var b=60/CR_DNC.bpm, now=performance.now()/1000;
CRDANCE={mode:mode,b:b,steps:steps,res:[],pts:0,combo:0,t0:now+.8,sch:-1,pop:'',popT:-9,ph:-1,done:false,flash:[0,0,0,0],last:now,pct:0};
c.sel=''; CRH.keys={}; CRH.path=null; CRH.then=null; if(CRH.pose!=='stand')crhStand(); if(mode==='class')CRH.yaw=Math.PI;
var e=crDanceEl(); e.hidden=false; document.getElementById('crApp').classList.add('crdancing'); crhCam(); document.getElementById('crDT').textContent=mode==='tv'?'Dance along to the TV':'Community line dancing'; document.getElementById('crDC').hidden=false; document.getElementById('crDB').hidden=false; document.getElementById('crDH').hidden=false; document.getElementById('crDR').hidden=true;
try{ crhSay(''); }catch(_){} crMsg='';
if(CRH)CRH.dirty=true; requestAnimationFrame(crDanceLoop); crhLoop(); return true;
}
function crDanceStop(){
var D=CRDANCE; CRDANCE=null; var e=document.getElementById('crDance'); if(e)e.hidden=true; try{ document.getElementById('crApp').classList.remove('crdancing'); if(CRH)crhCam(); }catch(_){}
if(CRH){ var ch=CRH.ch; if(ch){ [ch.legL,ch.legR,ch.armL,ch.armR].forEach(function(p){p.rotation.x=0; p.rotation.z=0;}); ch.fig.position.y=0; }
(CRO.dancers||[]).forEach(function(d){d.pg.position.y=0; d.pg.rotation.z=0;}); CRH.dirty=true; crhLoop(); }
}
function crDanceHit(d){
var D=CRDANCE; if(!D||D.done)return; var t=performance.now()/1000-D.t0, bi=-1, bd=9;
D.flash[d]=t;
for(var i=0;i=0&&i1){ crDanceStop(); try{crRender();}catch(_){} return; } D.last=now;
var t=now-D.t0; D.ph=t/D.b;
if(!D.done){
var nb=Math.floor((t+.25)/D.b), total=CR_DNC.cin+D.steps.length; while(D.sch.38){ D.res[i]=0; D.combo=0; D.pop='Missed'; D.popT=t; D.popC=0; } }
document.getElementById('crDH').textContent=t<0?'Get ready':t=4?' In a row: '+D.combo:'');
if(t>(CR_DNC.cin+D.steps.length)*D.b+.7)crDanceFinish();
}
crDanceDraw(t); if(CRH)CRH.dirty=true; crhLoop();
if(CRDANCE)requestAnimationFrame(crDanceLoop);
}
function crDanceFinish(){
var D=CRDANCE, c=crS(); if(!D||!c)return; D.done=true;
var N=D.steps.length, pct=Math.round(D.pts/(3*N)*100), stars=pct>=85?3:pct>=65?2:pct>=40?1:0, cnt=[0,0,0,0]; D.res.forEach(function(r){cnt[r|0]++;});
if(!c.lz)c.lz={d:0,n:0}; if(c.lz.d!==c.day){c.lz.d=c.day; c.lz.n=0;}
c.lz.n++; c.bw.lei++; crTick(c,20); c.bars.e=crClamp(c.bars.e-12); c.bars.w=crClamp(c.bars.w-10); c.bars.f=crClamp(c.bars.f-4); c.eb=1;
var dn=crOu(c).dance, prev=dn.best; dn.n++; if(pct>dn.best)dn.best=pct; if(stars>dn.stars)dn.stars=stars; D.pct=pct;
var left=CR_LEI_MAX-c.lz.n, nb=(pct>prev&&prev>0);
crMsg='You danced for 20 minutes. It counts as exercise and as one of your three leisure activities today. It pays nothing. You will have a little more energy at your next shift.';
crSave();
var st=''; for(var s=0;s<3;s++)st+=(s'+st+'
Score '+pct+'%'+(nb?' (a new best)':'')+'. Best so far '+dn.best+'%.
Perfect '+cnt[3]+' Good '+cnt[2]+' OK '+cnt[1]+' Missed '+cnt[0]+'
Counts as exercise and one of today’s three leisure activities ('+Math.min(3,c.lz.n)+' used). It uses some energy and pays nothing.'+((dn.n===3)?' New badge: Class Regular.':'')+((pct>=85&&dn.best===pct&&!D.b85)?'':'')+'
'+(left<=0?'
No more activities today. Come back tomorrow.
':'');
r.hidden=false; document.getElementById('crDC').hidden=true; document.getElementById('crDB').hidden=true; document.getElementById('crDH').hidden=true;
}
function crDanceDraw(t){
var cv=document.getElementById('crDC'); if(!cv||cv.hidden)return; var D=CRDANCE, g=cv.getContext('2d'), W=cv.width, H=cv.height, lw=W/4, hy=H-34, top=-22, b=D.b, cin=CR_DNC.cin;
g.clearRect(0,0,W,H); g.fillStyle='#1d2a33'; g.fillRect(0,0,W,H);
for(var l=0;l<4;l++){ g.fillStyle=l%2?'rgba(255,255,255,.04)':'rgba(255,255,255,.09)'; g.fillRect(l*lw,0,lw,H); }
g.fillStyle='rgba(255,255,255,.3)'; g.fillRect(0,hy-1.5,W,3);
function arrow(cx,cy,d,sz,fill,line){ g.save(); g.translate(cx,cy); g.rotate(CR_DROT[d]*Math.PI/180); g.beginPath(); g.moveTo(0,-sz); g.lineTo(sz,0); g.lineTo(sz*.38,0); g.lineTo(sz*.38,sz); g.lineTo(-sz*.38,sz); g.lineTo(-sz*.38,0); g.lineTo(-sz,0); g.closePath(); if(fill){g.fillStyle=fill; g.fill();} if(line){g.lineWidth=2.5; g.strokeStyle=line; g.stroke();} g.restore(); }
for(var d=0;d<4;d++){ var lit=t-D.flash[d]<.14; arrow(d*lw+lw/2,hy,d,22,lit?CR_DCOL[d]:null,CR_DCOL[d]); }
for(var i=0;i0)continue; var T=(cin+i)*b, y=hy-(T-t)/CR_DNC.fall*(hy-top); if(y<-30||y>H+30)continue; var dr=D.steps[i]; g.globalAlpha=r===0?.3:1; arrow(dr*lw+lw/2,y,dr,22,CR_DCOL[dr],'rgba(255,255,255,.85)'); g.globalAlpha=1; }
g.textAlign='center'; g.fillStyle='#f3efe4';
if(t0;i--){ var j=Math.floor(Math.random()*(i+1)), t=a[i]; a[i]=a[j]; a[j]=t; } return a; }
function crDrOu(c){ return crOu(c).dr; }
function crDqStart(){
var c=crS(); if(!c)return; var qs=crDShuf(CR_DQ).slice(0,10).map(function(q){ var os=crDShuf(q.o.map(function(t,i){return {t:t,k:i===q.a};})); return {q:q.q,o:os.map(function(x){return x.t;}),a:os.findIndex(function(x){return x.k;}),e:q.e}; });
CRDQ={qs:qs,i:0,ok:0,fb:''}; c.sel='o_dq';
}
function crDqAct(a){
var c=crS(); if(!c||!CRDQ)return; var q=CRDQ.qs[CRDQ.i];
if(a.indexOf('dqa:')===0){ if(CRDQ.fb)return; var ok=(+a.slice(4)===q.a); if(ok)CRDQ.ok++; CRDQ.fb=(ok?'Correct. ':'Not quite. ')+q.e; return; }
if(a==='dqn'){ if(!CRDQ.fb)return;
if(CRDQ.i<9){ CRDQ.i++; CRDQ.fb=''; return; }
var dr=crDrOu(c), n=CRDQ.ok, pass=n>=8; crTick(c,15); c.bars.e=crClamp(c.bars.e-3);
if(pass){ if(dr.st<1)dr.st=1; crMsg='You got '+n+' of 10. You passed the road-rules quiz. In the game you now have a Provisional Driving Licence, so you can take practice drives. A real learner must pass a theory test and an eyesight test first.'; }
else crMsg='You got '+n+' of 10. You need 8 to pass. Read the tips and try again.';
CRDQ=null; c.sel='o_drive'; crSave(); return; }
}
function crDrvRoute(mode){
if(mode==='test')return [{t:'cone',z:30,lane:1},{t:'zebra',z:62},{t:'car',z:92,lane:0},{t:'light',z:122},{t:'zebra',z:158},{t:'car',z:186,lane:1},{t:'light',z:216},{t:'finish',z:246}];
return [{t:'car',z:34,lane:1},{t:'zebra',z:70},{t:'light',z:106},{t:'cone',z:138,lane:0},{t:'finish',z:166}];
}
function crDrvEl(){
var e=document.getElementById('crDrive'); if(e)return e;
e=document.createElement('div'); e.id='crDrive'; e.className='crdnc crdrv'; e.hidden=true; e.setAttribute('role','region'); e.setAttribute('aria-label','Driving practice');
e.innerHTML='
Practice drive
';
document.getElementById('crApp').appendChild(e);
function rel(){ if(CRDRV)CRDRV.brake=false; }
e.addEventListener('pointerdown',function(ev){ var b=ev.target.closest('.cdv'); if(!b||!CRDRV)return; ev.preventDefault(); var k=b.dataset.v; if(k==='l')CRDRV.lane=Math.max(0,CRDRV.lane-1); else if(k==='r')CRDRV.lane=Math.min(2,CRDRV.lane+1); else CRDRV.brake=true; });
['pointerup','pointercancel','pointerleave'].forEach(function(n){ e.addEventListener(n,function(ev){ if(ev.target.closest&&ev.target.closest('.cdbrk'))rel(); }); });
e.addEventListener('click',function(ev){ var b=ev.target.closest('[data-vx]'); if(b){ crDrvStop(); try{crRender();}catch(_){} } });
return e;
}
function crDrvStart(mode){
var c=crS(); if(!c||!CRH||CRDRV||CRDQ)return false; var dr=crDrOu(c), fee=mode==='test'?30:15;
if(c.slept){crMsg='You are in bed for tonight. A new day starts tomorrow.'; return false;}
if(dr.st<1){crMsg='Pass the road-rules quiz first.'; return false;}
if(mode==='test'&&dr.st<2){crMsg='Take a practice drive first.'; return false;}
if(dr.st>=3){crMsg='You already have your driving licence.'; return false;}
if(dr.d===c.day){crMsg='Driving lessons are one a day. Come back tomorrow.'; return false;}
if(c.coins1){ crDrvStop(); try{crRender();}catch(_){} return; } D.last=now;
if(!D.done){
var tv=D.brake?0:8; if(D.ve.z-16)e.p=0; if(e.p!==undefined&&e.p<1.15)e.p+=dt/3.6; if(!e.bad&&e.p!==undefined&&e.p<1.1&&D.pz=e.z){ e.bad=1; crDrvMist(D,'You did not give way at the zebra crossing.'); } }
else if(e.t==='light'){ if(e.st===undefined)e.st='red';
if(e.st==='red'){ if(D.v<.25&&D.z>e.z-7&&D.z1.4)e.st='green'; } if(D.pz=e.z){ e.st='green'; crDrvMist(D,'You went through a red light.'); } } }
else if(e.t==='finish'){ if(D.z>=e.z)crDrvFinish(); }
});
if(!D.done&&now-D.t0>170)crDrvFinish();
}
crDrvDraw(); if(CRDRV)requestAnimationFrame(crDrvLoop);
}
function crDrvFinish(){
var D=CRDRV, c=crS(); if(!D||!c||D.done)return; D.done=true; var dr=crDrOu(c), pass=D.mist<=D.lim, test=D.mode==='test';
c.coins-=D.fee; crTick(c,45); c.bars.e=crClamp(c.bars.e-8); c.bars.w=crClamp(c.bars.w-6); c.bars.f=crClamp(c.bars.f-3); dr.d=c.day; dr.n++;
if(pass){ if(test)dr.st=3; else if(dr.st<2)dr.st=2; }
var txt; if(pass&&test)txt='You passed the driving test and have your driving licence. New badge: Licensed Driver. You can now hire a car to go to work.'; else if(pass)txt='Good practice drive. You can take the driving test next.'; else txt='Not yet. '+D.mist+' mistakes, and '+D.lim+' is the most allowed. Lessons are one a day, so try again tomorrow.';
crMsg=txt+' It cost you Aura $'+D.fee+' (play money). Lessons use some energy and water.'; crSave();
var r=document.getElementById('crVR'); r.innerHTML='
';
r.hidden=false; document.getElementById('crVC').hidden=true; document.getElementById('crVB').hidden=true; document.getElementById('crVH').hidden=true;
}
function crDrvDraw(){
var cv=document.getElementById('crVC'); if(!cv||cv.hidden)return; var D=CRDRV, g=cv.getContext('2d'), W=cv.width, H=cv.height, S=9, cy=H-70, rx=70, lw=66.6, laneX=function(l){return rx+lw*(l+.5);}, Y=function(z){return cy-(z-D.z)*S;};
g.fillStyle='#5e8c61'; g.fillRect(0,0,W,H); g.fillStyle='#3b4047'; g.fillRect(rx,0,lw*3,H); g.fillStyle='#d8d2c0'; g.fillRect(rx-4,0,4,H); g.fillRect(rx+lw*3,0,4,H);
g.fillStyle='rgba(255,255,255,.7)'; for(var l=1;l<3;l++){ for(var y=-60+D.dash*S;yH+60)return;
if(e.t==='car'){ var x=laneX(e.lane); g.fillStyle='#6f7a89'; g.fillRect(x-17,y-26,34,52); g.fillStyle='#a9c4d6'; g.fillRect(x-13,y-16,26,12); g.fillStyle='#222'; g.fillRect(x-19,y-20,3,12); g.fillRect(x+16,y-20,3,12); g.fillRect(x-19,y+8,3,12); g.fillRect(x+16,y+8,3,12); }
else if(e.t==='cone'){ var x2=laneX(e.lane); g.fillStyle='#ee7a2a'; g.beginPath(); g.moveTo(x2,y-14); g.lineTo(x2+11,y+10); g.lineTo(x2-11,y+10); g.closePath(); g.fill(); g.fillStyle='#fff'; g.fillRect(x2-6,y-2,12,4); }
else if(e.t==='zebra'){ g.fillStyle='rgba(255,255,255,.85)'; for(var k=0;k<9;k++)g.fillRect(rx+4+k*22,y-14,12,28); if(e.p!==undefined&&e.p<1.1){ var px=rx-14+(Math.min(1,e.p))*(lw*3+28); g.fillStyle='#e0b48a'; g.beginPath(); g.arc(px,y,6,0,6.3); g.fill(); g.fillStyle='#d9544a'; g.fillRect(px-5,y+4,10,9); } g.fillStyle='#2c5aa0'; g.fillRect(rx+lw*3+8,y-26,8,8); }
else if(e.t==='light'){ g.fillStyle='#f3efe4'; g.fillRect(rx,y-3,lw*3,6); g.fillStyle='#222'; g.fillRect(rx+lw*3+8,y-22,12,28); g.fillStyle=e.st==='green'?'#2fbf6a':'#e5483a'; g.beginPath(); g.arc(rx+lw*3+14,y-12,5,0,6.3); g.fill(); }
else if(e.t==='finish'){ for(var q=0;q<12;q++){ g.fillStyle=q%2?'#fff':'#111'; g.fillRect(rx+q*(lw*3/12),y-6,lw*3/12,6); g.fillStyle=q%2?'#111':'#fff'; g.fillRect(rx+q*(lw*3/12),y,lw*3/12,6); } }
});
var cx=rx+lw*(D.lx+.5); g.fillStyle='#d94a3a'; g.fillRect(cx-17,cy-26,34,52); g.fillStyle='#bfe3f0'; g.fillRect(cx-13,cy-14,26,12); g.fillStyle='#222'; g.fillRect(cx-19,cy-20,3,12); g.fillRect(cx+16,cy-20,3,12); g.fillRect(cx-19,cy+8,3,12); g.fillRect(cx+16,cy+8,3,12); if(D.brake){ g.fillStyle='#ff3b30'; g.fillRect(cx-14,cy+24,8,4); g.fillRect(cx+6,cy+24,8,4); }
g.fillStyle='rgba(0,0,0,.55)'; g.fillRect(0,0,W,22); g.fillStyle='#f3efe4'; g.font='600 13px system-ui,sans-serif'; g.textAlign='left'; g.fillText('Speed '+Math.round(D.v*3.6)+' km/h',8,15); g.textAlign='right'; g.fillText('Mistakes '+D.mist+' of '+D.lim,W-8,15);
}
window.addEventListener('keydown',function(e){
if(!CRDRV)return; var k=e.key;
if(k==='Escape'){ crDrvStop(); try{crRender();}catch(_){} e.preventDefault(); e.stopImmediatePropagation(); return; }
if(CRDRV.done)return;
if(k==='ArrowLeft'||k==='a'||k==='A'){ if(!e.repeat)CRDRV.lane=Math.max(0,CRDRV.lane-1); }
else if(k==='ArrowRight'||k==='d'||k==='D'){ if(!e.repeat)CRDRV.lane=Math.min(2,CRDRV.lane+1); }
else if(k==='ArrowDown'||k==='s'||k==='S'||k===' '){ CRDRV.brake=true; }
else if(k!=='ArrowUp'&&k!=='w'&&k!=='W')return;
e.preventDefault(); e.stopImmediatePropagation();
},true);
window.addEventListener('keyup',function(e){ if(!CRDRV)return; var k=e.key; if(k==='ArrowDown'||k==='s'||k==='S'||k===' '){ CRDRV.brake=false; e.stopImmediatePropagation(); } },true);
(function(){ var _l2=crLeave; crLeave=function(){ try{crDrvStop();}catch(_){} CRDQ=null; return _l2.apply(this,arguments); }; })();
function crBind(){
var v=$c('crView'), nav=$c('crTabs');
v.addEventListener('click',function(e){
var b=e.target.closest('[data-act]'); if(b&&!b.disabled){crAct(b); return;}
var g=e.target.closest('[data-go]'); if(g){var c=crS(); if(g.dataset.go==='shop'&&c.tab==='home'){crShopOpen(); return;} c.tab=g.dataset.go; crSave(); crRender(true); return;}
var o=e.target.closest('[data-obj]'); if(o){var c2=crS(); c2.sel=o.dataset.obj; crRender(); try{$c('crSel').scrollIntoView({block:'nearest',behavior:'smooth'});}catch(_){} }
});
v.addEventListener('keydown',function(e){if((e.key==='Enter'||e.key===' ')&&e.target.closest&&e.target.closest('[data-obj]')){e.preventDefault(); var o=e.target.closest('[data-obj]'); crS().sel=o.dataset.obj; crRender();}});
nav.addEventListener('click',function(e){var g=e.target.closest('[data-go]'); if(g){var c=crS(); if(g.dataset.go==='shop'&&c.tab==='home'){crShopOpen(); return;} c.tab=g.dataset.go; crSave(); crRender(true);}});
$c('crBackBtn').addEventListener('click',function(){crConfirm('Back to Aura Builder?','Aura Life is saved. You will go back to your Aura Builder world, which has its own blocks and bag.','Go to Aura Builder','Stay in Aura Life',crLeave);});
}
/* ===================== Career home: the starter flat (metres; x east, z south, y up; origin = outer north-west corner of the flat) =====================
Pure data and geometry first (no drawing), so placing, walking and reaching can be tested without a screen.
Measured from the design boards: flat 9 m x 9 m, walls 0.15 m, ceiling 2.5 m, front door 0.9 x 2.0 m, corridor 1.4 m deep. */
var CRH=null, CRH_FAIL=false, CRH_WIRED=0, crPlacePending='';
var CRF_C=.25, CRF_NX=36, CRF_NZ=42, CRF_R=.25, CRF_RG=.22, CRF_H=2.5, CRF_LOW=.12;
/* walls: b = [x0,z0,x1,z1] (the real thickness, 0.15 m), v = runs along z (1) or x (0), ext = outward normal for outer walls, ops = openings along the wall, s = structural */
var CRF_WALLS=[
{id:'N',v:0,b:[0,0,9,.15],ext:[0,-1],s:1,ops:[['win',5.3,6.5],['win',7.5,8.6]]},
{id:'W',v:1,b:[0,.15,.15,8.85],ext:[-1,0],s:1,ops:[['win',3,4.1]]},
{id:'E',v:1,b:[8.85,.15,9,8.85],ext:[1,0],s:1,ops:[]},
{id:'S',v:0,b:[0,8.85,9,9],ext:[0,1],s:1,ops:[['door',2.85,3.75]]},
{id:'D',v:1,b:[4.2,.15,4.35,6.4],ext:null,s:1,ops:[]},
{id:'B',v:0,b:[.15,5.4,4.2,5.55],ext:null,s:1,ops:[['door',3,3.9]]},
{id:'T',v:1,b:[2.4,5.55,2.55,7.55],ext:null,s:1,ops:[]},
{id:'U',v:0,b:[.15,7.4,2.4,7.55],ext:null,s:1,ops:[['door',.9,1.8]]}
];
var CR_FEES={hack:25,dispose:15};
function crHackCost(w){return (w&&!w.s)?CR_FEES.hack+CR_FEES.dispose:0;} /* walls a Learner builds later: structural walls (all of these) cannot be hacked */
function crfSpans(w){
var a0=w.v?w.b[1]:w.b[0], a1=w.v?w.b[3]:w.b[2], ops=w.ops.slice().sort(function(p,q){return p[1]-q[1];}), sp=[], cur=a0;
ops.forEach(function(o){ if(o[1]>cur)sp.push(['wall',cur,o[1]]); sp.push([o[0],o[1],o[2]]); cur=o[2]; });
if(cur>>0; return seed/2147483648-1; }
var ev=[], i, n, x, ph;
if(id==='drums')ev=CR_DRUM.map(function(e){return [e[0],e[1]*K,0];}); else ev=(CR_RIFF[id]||[]).map(function(e){return [e[0],e[1]*K,e[2]*K];});
var end=0; ev.forEach(function(e){ end=Math.max(end,e[1]+(id==='drums'?1.0:e[2]+1.6)); });
var N=Math.ceil(end*SR)+1, out=new Float32Array(N);
function mix(st,arr,g){ var o=Math.round(st*SR); for(var j=0;j0?(id==='drums'?.6:.5)/pk:1); for(i=0;ib[0]+e&&a[1]b[1]+e;}
function crfFreeAt(o,x,z,r){
var r2=r*r-1e-6;
for(var i=0;i=0&&j>=0&&i.62)continue; var s=(x-fx)*(x-fx)+(z-fz)*(z-fz)+d*3; if(s8.85+e||r[3]>8.85+e)return 'It has to stay inside your flat.';
for(i=0;i9.5)continue;
t=Math.abs(x-b[2]); if(tb[1]){dx=t; bx=b[2];} /* left edge to the right face of a wall */
t=Math.abs(x+d[0]-b[0]); if(tb[1]){dx=t; bx=b[0]-d[0];} /* right edge to the left face */
t=Math.abs(z-b[3]); if(tb[0]){dz=t; bz=b[3];}
t=Math.abs(z+d[1]-b[1]); if(tb[0]){dz=t; bz=b[1]-d[1];}
}
return [bx,bz];
}
/* where a newly bought piece goes: its place in the design if that is free, otherwise the nearest free spot */
function crfFind(c,id){
var P=CRF_PIECE[id], k0=P.k, a=P.at, cand=[], k;
if(!crfCheck(c,id,a[0],a[1],k0))return [a[0],a[1],k0];
var seen={};
for(k=0;k<4;k++){ var d=crfDims(id,k); for(var xi=0;xi<=36;xi++)for(var zi=0;zi<=36;zi++){ var sn=crfSnap(id,xi*.25,zi*.25,k), r=[sn[0],sn[1],sn[0]+d[0],sn[1]+d[1]], bad=false, i, key=k+','+sn[0]+','+sn[1]; if(seen[key])continue; seen[key]=1;
if(r[0]<.15-1e-6||r[1]<.15-1e-6||r[2]>8.85+1e-6||r[3]>8.85+1e-6)continue;
for(i=0;i=r*r)continue; hit=true;
if(d2>1e-10){var d=Math.sqrt(d2), p=(r-d)/d; x+=dx*p; z+=dz*p;}
else { var l=x-b[0], rr=b[2]-x, t=z-b[1], bt=b[3]-z, m=Math.min(l,rr,t,bt); if(m===l)x=b[0]-r; else if(m===rr)x=b[2]+r; else if(m===t)z=b[1]-r; else z=b[3]+r; } }
if(!hit)break; }
return [x,z];
}
function crfMove(o,x,z,vx,vz,dist){
var n=crfResolve(o,x+vx*dist,z+vz*dist,CRF_R); if(crfFreeAt(o,n[0],n[1],CRF_R-.03))return n; return [x,z];
}
function crfLine(o,x0,z0,x1,z1){var dx=x1-x0, dz=z1-z0, n=Math.ceil(Math.sqrt(dx*dx+dz*dz)/.06); for(var k=0;k<=n;k++){if(!crfFreeAt(o,x0+dx*k/n,z0+dz*k/n,CRF_RG-.01))return false;} return true;}
/* click-to-walk: A* on the 0.25 m grid (8 neighbours, no corner cutting), then straightened */
function crfPath(o,f,x0,z0,x1,z1){
var s=crfNearest(f,x0,z0), e=crfNearest(f,x1,z1); if(!s||!e)return null;
var a=crfIdx(s[0],s[1]), b=crfIdx(e[0],e[1]), N=CRF_NX*CRF_NZ, g=new Float32Array(N).fill(1e9), par=new Int32Array(N).fill(-1), closed=new Uint8Array(N), open=[], si=a[1]*CRF_NX+a[0], ei=b[1]*CRF_NX+b[0];
function h(i,j){var dx=Math.abs(i-b[0]), dz=Math.abs(j-b[1]); return (dx+dz)+(Math.SQRT2-2)*Math.min(dx,dz);}
g[si]=0; open.push([h(a[0],a[1]),si]);
while(open.length){
var bi=0; for(var q=1;q=(u.min||CRF_H)-.05; });
(CRH.wi||[]).forEach(function(q){ if(q.wi===st.idx)q.o.visible=h>=CRF_H-.05; });
}
function crhBuildGround(){
var g=CRH.gndG, C=CRC, sl=crhMat(C.SLAB), fl=crhMat(C.FLOOR), cs=crhMat(0xc6c3bd), cf=crhMat(0xdad8d3);
function slab(x0,z0,x1,z1,mats){var m=new THREE.Mesh(CRH.ub,mats); m.scale.set(x1-x0,.2,z1-z0); m.position.set((x0+x1)/2,-.2,(z0+z1)/2); g.add(m); return m;}
slab(0,0,9,9,[sl,sl,fl,sl,sl,sl]); slab(0,9,9,10.4,[cs,cs,cf,cs,cs,cs]);
var tile=new THREE.Mesh(CRH.ub,crhMat(0xdce6eb)); tile.scale.set(2.25,.008,1.85); tile.position.set(1.275,0,6.475); g.add(tile);
/* corridor edge: lime parapet with a pale cap, returns at both ends */
var LIME=0xc6d77e, CAP=0xeceedd;
crhBox(g,9,.6,.12,LIME,4.5,0,10.34); crhBox(g,9.04,.05,.18,CAP,4.5,.6,10.34);
crhBox(g,.12,.6,1.4,LIME,-.06,0,9.7); crhBox(g,.12,.6,1.4,LIME,9.06,0,9.7); crhBox(g,.18,.05,1.46,CAP,-.06,.6,9.7); crhBox(g,.18,.05,1.46,CAP,9.06,.6,9.7);
/* the gate: two leaves, swung open to the outside */
[2.8,3.8].forEach(function(x){ var lf=crhGateLeaf(); lf.position.set(x,0,9.1); g.add(lf); });
}
/* ---- Aura: 1.4 m tall, blocky, flat shaded ---- */
function crhBuildChar(){
var SK=0xf4c99e, SK2=0xe0ae80, OUT=0x2f6690, OUT2=0x4380b0, LEGC=0x244f72, INK=0x1f2a36, ACC=0xffc83d, ACCS=0xe6a626, ACCL=0xffde80, AURA=0x5ee6d3;
var g=new THREE.Group(), fig=new THREE.Group(); g.add(fig);
var shirt=new THREE.MeshLambertMaterial({color:OUT}), pants=new THREE.MeshLambertMaterial({color:LEGC}), glow=new THREE.MeshBasicMaterial({color:AURA});
function ab(p,u,v,z,w,d,h,col,zb,ub){var m=new THREE.Mesh(CRH.ub,(col&&col.isMaterial)?col:crhMat(col)); m.scale.set(w,h,d); m.position.set(u+w/2-(ub||0),z-(zb||0),v+d/2); p.add(m); return m;}
var legL=new THREE.Group(), legR=new THREE.Group(); legL.position.set(-.1,.4,0); legR.position.set(.1,.4,0); fig.add(legL,legR);
[[legL,-.1],[legR,.1]].forEach(function(q){ ab(q[0],q[1]-.09,-.1,.09,.18,.2,.31,pants,.4,q[1]); ab(q[0],q[1]-.1,-.12,0,.2,.28,.09,INK,.4,q[1]); });
var pack=new THREE.Group(); fig.add(pack);
ab(pack,-.17,-.29,.4,.34,.16,.34,ACC); ab(pack,-.17,-.295,.5,.34,.012,.02,ACCS); ab(pack,-.17,-.295,.62,.34,.012,.02,ACCS); /* brick backpack */
ab(fig,-.22,-.13,.4,.44,.26,.32,shirt); ab(fig,-.22,.1,.66,.44,.03,.06,OUT2);
var armL=new THREE.Group(), armR=new THREE.Group(); armL.position.set(-.29,.72,0); armR.position.set(.29,.72,0); fig.add(armL,armR);
[[armL,-.355],[armR,.225]].forEach(function(q){ ab(q[0],q[1],-.075,.525,.13,.15,.195,shirt,.72,q[1]+.065); ab(q[0],q[1],-.075,.42,.13,.15,.105,SK,.72,q[1]+.065); });
[-.152,.117].forEach(function(u){ab(fig,u,.13,.475,.035,.012,.245,ACCS);}); /* straps */
ab(fig,-.0175,.13,.63,.035,.012,.023,ACC); ab(fig,-.064,.13,.595,.128,.012,.035,ACC); ab(fig,-.041,.13,.565,.082,.012,.03,ACC); [-.053,.018].forEach(function(u){ab(fig,u,.13,.535,.035,.012,.03,ACC);}); /* star badge */
ab(fig,-.23,-.14,.405,.46,.28,.07,INK); ab(fig,-.035,.14,.41,.07,.012,.058,ACC); [-.21,.115].forEach(function(u){ab(fig,u,.14,.35,.095,.05,.09,ACCS); ab(fig,u,.14,.42,.095,.05,.02,ACC);}); /* belt and two pouches */
ab(fig,-.29,-.25,.72,.58,.5,.54,SK); /* head */
ab(fig,-.29,.255,1.19,.58,.012,.07,INK); /* ink fringe */
[-.185,.115].forEach(function(u){ab(fig,u,.255,1.0,.07,.012,.1,INK); ab(fig,u+.018,.262,1.065,.034,.012,.034,glow);}); /* eyes with aura-teal highlights */
[-.255,.215].forEach(function(u){ab(fig,u,.255,.9,.07,.012,.034,SK2);}); ab(fig,-.1,.255,.86,.034,.012,.034,INK); ab(fig,.066,.255,.86,.034,.012,.034,INK); ab(fig,-.066,.255,.826,.132,.012,.034,INK);
var hat=new THREE.Group(); fig.add(hat); ab(hat,-.31,-.27,1.26,.62,.54,.12,ACC); ab(hat,-.05,-.27,1.38,.1,.54,.02,ACCL); ab(hat,-.31,.27,1.26,.62,.1,.04,ACCS); /* yellow hard hat */
var cap=new THREE.Group(); cap.visible=false; fig.add(cap); ab(cap,-.29,-.26,1.26,.58,.52,.16,0xf4f1ea); ab(cap,-.22,-.2,1.42,.44,.4,.06,0xf4f1ea); /* kitchen cap with the Food uniform */
var aura=new THREE.Group(); fig.add(aura);
/* no floating cubes around Aura */
var sh=new THREE.Mesh(new THREE.CircleGeometry(.3,14),new THREE.MeshBasicMaterial({color:0x000000,transparent:true,opacity:.2,depthWrite:false})); sh.rotation.x=-Math.PI/2; sh.position.y=.02; g.add(sh);
g.scale.setScalar(.946); /* Aura is 1.4 m tall, the same as everywhere else in the game (the hat and glow made him 1.48 m) */
CRH.sc.add(g); CRH.ch={g:g,fig:fig,legL:legL,legR:legR,armL:armL,armR:armR,pack:pack,aura:aura,hat:hat,cap:cap,shirt:shirt,pants:pants,shadow:sh,ph:0};
}
function crhDress(c){
var ch=CRH.ch; if(!ch)return; var o=crOutItem(c.outfit)||CR_OUT[0], sh=o[2], pn=o[3], cook=false;
if(c.outfit==='work'){ var u=CR_UNI[c.path]||CR_UNI.food; sh=u[0]; pn=u[1]; cook=(c.path==='food'); }
ch.shirt.color.set(sh||'#2f6690'); ch.pants.color.set(pn||'#244f72'); ch.hat.visible=!cook; ch.cap.visible=cook; CRH.dirty=true;
}
function crhBuildHL(){
var m=new THREE.Mesh(CRH.ub,new THREE.MeshBasicMaterial({color:0xffd34d,transparent:true,opacity:.28,depthTest:false,depthWrite:false})); m.renderOrder=9; m.visible=false; CRH.sc.add(m);
var e=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1,1,1).translate(0,.5,0)),new THREE.LineBasicMaterial({color:0x2f5f9e,depthTest:false})); e.renderOrder=10; e.visible=false; CRH.sc.add(e);
CRH.hl=m; CRH.hle=e;
}
function crhHighlight(id){
if(CRH.hover===id)return; CRH.hover=id; var o=id&&CRH.objs[id];
if(!o||!o.rect){CRH.hl.visible=false; CRH.hle.visible=false; CRH.dirty=true; return;}
var b=o.rect; [CRH.hl,CRH.hle].forEach(function(m){m.visible=true; m.scale.set(b[2]-b[0]+.04,o.h+.02,b[3]-b[1]+.04); m.position.set((b[0]+b[2])/2,0,(b[1]+b[3])/2);}); CRH.dirty=true;
}
/* ---- the scene ---- */
/* ---- outside the flat: the ground floor of the block (Career explore view only; the Renovate view is the flat) ---- */
var CRO={x:60};
var CR_TRANSPORT={bus:{n:'Bus',fare:4,min:50,d:{e:3,w:2,f:1},txt:'A bus to the Town Centre and back. About 25 minutes each way.'},taxi:{n:'Taxi',fare:20,min:24,d:{e:1,w:0,f:0},txt:'A taxi to the Town Centre and back. About 12 minutes each way. It costs more.'},car:{n:'Car',fare:12,min:30,d:{e:2,w:1,f:0},txt:'A hired car-share car to the Town Centre and back. About 15 minutes each way. You need a driving licence.'},cycle:{n:'Bicycle',fare:1,min:40,d:{e:8,w:8,f:4},txt:'Hire a shared bicycle and ride to the Town Centre and back on the cycling path. About 20 minutes each way. A shared bicycle costs Aura $1 for each hour. Ride slowly near people and wear a helmet.'},walk:{n:'Walking',fare:0,min:80,d:{e:10,w:10,f:5},txt:'Walk to the Town Centre and back. It is free, but it takes about 40 minutes each way.'}};
var CR_MART=[['water','Bottle of water',2,'w',30],['fruit','Fruit pack',3,'f',18],['snack','Plain biscuits',3,'f',12]];
function crhBuildOutside(){
if(CRH.outG)return; var g=new THREE.Group(); CRH.outG=g; CRH.sc.add(g); CRH.outObs=[]; CRH.outPick=[]; CRH.outObjs={};
var X=CRO.x;
function R(x0,z0,x1,z1,y,h,col){ return crhBox(g,x1-x0,h,z1-z0,col,X+(x0+x1)/2,y,(z0+z1)/2); }
function K(x0,z0,x1,z1){ CRH.outObs.push([X+x0,z0,X+x1,z1]); }
function tag(id,ms){ ms.forEach(function(m){ m.userData.obj=id; CRH.outPick.push(m); }); }
function def(id,name,rect,at,face){ CRH.outObjs[id]={name:name,sel:id,rect:[X+rect[0],rect[1],X+rect[2],rect[3]],at:[X+at[0],at[1]],face:[X+face[0],face[1]],h:2}; }
function tree(x,z){ R(x-.15,z-.15,x+.15,z+.15,0,1.4,0x7a5230); R(x-.9,z-.9,x+.9,z+.9,1.2,1.4,0x3f8f4f); R(x-.6,z-.6,x+.6,z+.6,2.5,.9,0x4aa05a); K(x-.2,z-.2,x+.2,z+.2); }
/* ground, paths, pavement, road */
R(-2,-2,32,24,-.2,.2,0xa9d694); R(2,2.4,28,9,0,.012,0xdad7cf); R(5,9,7,15,0,.012,0xdad7cf); R(0,15,30,17.2,0,.02,0xd5d3cd); R(-2,17.4,32,24,0,.012,0x6e7479); R(0,17.1,30,17.4,0,.12,0xbdbab3);
for(var i=0;i<7;i++)R(1+i*4.2,20.6,3.2+i*4.2,20.85,.014,.01,0xf2e6b0);
/* the block: a void deck on the ground floor, flats above */
R(2,-1,28,2.4,0,2.6,0xcfc7b8); R(2,-1,28,3.5,2.6,6.6,0xe9e3d6); K(2,-1,28,2.4);
[3,9,15,21,27].forEach(function(x){ R(x,2.8,x+.5,3.3,0,2.6,0xd8d2c4); K(x,2.8,x+.5,3.3); });
for(var f=0;f<2;f++){ R(2,3.45,28,3.55,3.4+f*3,.14,0xc9d7a0); for(var w=0;w<9;w++)R(3+w*2.75,3.46,4.7+w*2.75,3.56,3.9+f*3,1.2,0x9db6c6); }
var lf=[R(5,2.32,6.4,2.4,0,2.2,0xb7bfc5),R(5.68,2.3,5.72,2.4,0,2.2,0x7d868c),R(5,2.3,6.4,2.4,2.3,.35,0x2f7d5a)]; tag('o_lift',lf); def('o_lift','Lift (up to your flat)',[5,2.3,6.4,3],[5.7,3.9],[5.7,2.3]);
var bn=[R(10,4.2,12.2,4.9,0,.45,0xb5895a),R(10,4.1,12.2,4.22,.45,.5,0xb5895a),R(14,4.2,16.2,4.9,0,.45,0xb5895a),R(14,4.1,16.2,4.22,.45,.5,0xb5895a)]; K(10,4.1,12.2,4.9); K(14,4.1,16.2,4.9); tag('o_bench',bn); def('o_bench','Bench to rest',[10,4.1,16.2,4.9],[13.1,5.7],[13.1,4.9]);
/* minimart */
var mt=[R(21,5,27,9,0,3,0xf4efe4),R(20.7,8.7,27.3,9.5,2.3,.25,0xd64545),R(22,8.98,26,9.1,2.62,.45,0x2c5aa0),R(23.2,9.0,24.8,9.1,0,2.1,0x9ed3e6),R(21.4,9.0,22.8,9.1,.5,1.3,0xbfe3f0),R(25.2,9.0,26.6,9.1,.5,1.3,0xbfe3f0)]; K(21,5,27,9.1); tag('o_mart',mt); def('o_mart','Minimart',[21,5,27,9.1],[24,10.4],[24,9.1]);
/* playground */
R(3,9.5,10,14.5,0,.03,0xe8d7a5);
var pl=[R(4,10.2,5.4,11.4,0,1.5,0xd9534f),R(3.9,10.1,5.5,11.5,1.5,.12,0xe8c53a)]; for(var s2=0;s2<5;s2++)pl.push(R(4.4,11.4+s2*.38,5,11.78+s2*.38,1.1-s2*.22,.1,0xf0c040));
pl.push(R(7,11,7.15,11.15,0,1.8,0x4a6fa5),R(9,11,9.15,11.15,0,1.8,0x4a6fa5),R(7,11,9.15,11.15,1.8,.12,0x4a6fa5),R(7.3,11.02,7.9,11.12,.45,.06,0xd9534f),R(8.1,11.02,8.7,11.12,.45,.06,0x3a9d5d),R(7.58,11.05,7.62,11.09,.5,1.3,0x555555),R(8.38,11.05,8.42,11.09,.5,1.3,0x555555));
K(4,10.2,5.4,11.4); K(4.4,11.4,5,13.2); K(7,10.9,9.15,11.25); tag('o_play',pl); def('o_play','Playground',[3.5,9.8,9.5,13.5],[7.2,13.3],[7.2,11.3]);
/* community garden */
var gd=[]; [[12.5,10],[16,10],[12.5,12.4],[16,12.4]].forEach(function(q,qi){ gd.push(R(q[0],q[1],q[0]+2.5,q[1]+1.4,0,.35,0x8a6a45),R(q[0]+.1,q[1]+.1,q[0]+2.4,q[1]+1.3,.35,.06,0x5a4030)); for(var k=0;k<4;k++)gd.push(R(q[0]+.25+k*.55,q[1]+.4,q[0]+.5+k*.55,q[1]+.7,.4,.28+(k%2)*.18,[0x4caf50,0x66bb6a,0xe57373,0xf2c230][(k+qi)%4])); K(q[0],q[1],q[0]+2.5,q[1]+1.4); });
gd.push(R(19.4,10.2,19.55,10.35,0,1.0,0x7a5230),R(19,10.15,19.95,10.25,.8,.5,0x2f7d5a)); K(19,10.1,19.95,10.4); tag('o_garden',gd); def('o_garden','Community garden',[12,9.8,20,14],[15.4,11.9],[15.4,11.1]);
/* bus stop and taxi stand on the pavement, a bus and a taxi on the road */
var bs=[R(8,15.2,14,16.9,2.4,.15,0x2c5aa0),R(8.05,15.3,8.2,15.45,0,2.4,0x9aa3a8),R(13.8,15.3,13.95,15.45,0,2.4,0x9aa3a8),R(8.05,16.7,8.2,16.85,0,2.4,0x9aa3a8),R(13.8,16.7,13.95,16.85,0,2.4,0x9aa3a8),R(9,16.2,12.8,16.7,0,.45,0xbf9a6a),R(14.6,16,14.75,16.15,0,2.8,0x8f9aa3),R(14.3,16,15.05,16.06,2.1,.75,0xf2c230),R(14.45,16.07,14.9,16.1,2.3,.35,0x2b2f33),
R(8,17.9,15,19.6,.3,1.5,0xf2f2f2),R(8,17.9,15,19.6,.85,.28,0xd0392f),R(8.1,17.86,14.9,17.92,1.15,.55,0x9ed3e6),R(8.8,17.8,9.8,17.95,0,.5,0x222222),R(13.2,17.8,14.2,17.95,0,.5,0x222222)];
K(9,16.2,12.8,16.7); tag('o_bus',bs); def('o_bus','Bus stop',[8,15.2,14.8,16.9],[11,15.55],[11,16.5]);
var tx=[R(20,15.4,20.15,15.55,0,2.6,0x8f9aa3),R(19.7,15.4,20.45,15.46,2.0,.65,0x2c5aa0),R(21.5,17.8,25.5,19.4,.25,.65,0x2b6cc4),R(22.2,17.9,24.9,19.3,.9,.55,0x2b6cc4),R(22.3,17.86,24.8,17.92,.95,.4,0x9ed3e6),R(23,18.3,23.9,18.9,1.45,.2,0xf2c230),R(21.8,17.75,22.5,17.9,0,.4,0x222222),R(24.5,17.75,25.2,17.9,0,.4,0x222222)];
K(19.9,15.4,20.3,15.6); tag('o_taxi',tx); def('o_taxi','Taxi stand',[19.5,15.3,26,16.9],[23,15.6],[23,16.6]);
/* outdoor exercise corner: a rubber floor with a pull-up bar, a leg press and a stretching bar */
var ft=[R(21,10.4,29.2,14.6,0,.03,0x7da6b8),R(21.8,11.2,22.0,11.4,0,2.2,0x4a6fa5),R(24.2,11.2,24.4,11.4,0,2.2,0x4a6fa5),R(21.8,11.2,24.4,11.4,2.2,.12,0x4a6fa5),R(22.6,11.25,22.7,11.35,1.3,.9,0x888888),
R(26,11,28.4,11.9,0,.5,0x8f9aa3),R(26,11,26.25,11.9,.5,.7,0x4a6fa5),R(26.3,11.2,27.2,11.7,.5,.12,0xd9534f),R(27.4,12,28.4,12.15,.15,.5,0x555555),
R(22,13.5,22.15,13.65,0,1.0,0xf2c230),R(25,13.5,25.15,13.65,0,1.0,0xf2c230),R(22,13.5,25.15,13.65,1.0,.1,0xf2c230),R(27,13.6,27.8,14.2,0,.45,0xb5895a)];
K(21.8,11.2,24.4,11.4); K(26,11,28.4,11.9); K(22,13.5,25.15,13.65); K(27,13.6,27.8,14.2); tag('o_fit',ft); def('o_fit','Exercise corner',[21,10.4,29.2,14.6],[25.6,12.6],[25.6,11.6]);
/* shared bicycles, and the path east to the Town Centre */
var bk=[R(15.3,15.4,18.6,15.55,0,.35,0x8f9aa3)]; for(var q=0;q<3;q++){ var bx=15.5+q*1.0; bk.push(R(bx,15.7,bx+.1,16.9,.35,.04,0x2f7d5a),R(bx,15.7,bx+.1,15.8,0,.7,0x333333),R(bx,16.8,bx+.1,16.9,0,.7,0x333333),R(bx,15.9,bx+.1,16.7,.28,.28,0x2f7d5a),R(bx-.05,16.2,bx+.15,16.35,.7,.05,0x333333)); }
K(15.3,15.4,18.6,16.95); tag('o_bike',bk); def('o_bike','Shared bicycles',[15.3,15.3,18.6,16.95],[17,15.0],[17,16.2]);
var tw=[R(29.1,15.4,29.25,15.55,0,2.4,0x8f9aa3),R(27.9,15.4,29.4,15.46,1.9,.55,0x2f7d5a),R(28.8,15.38,29.2,15.42,2.45,.4,0x2f7d5a),R(27.5,15.5,30,17.1,0,.03,0xcfe3c0)]; K(29,15.4,29.35,15.6); tag('o_town',tw); def('o_town','Path to the Town Centre',[27.5,15.3,30,17.1],[28.2,16.2],[29.6,16.2]);
[[2,7],[29,7],[1.2,13],[11,8.2],[3.2,16.1]].forEach(function(t){ tree(t[0],t[1]); });
/* neighbours */
function hit(x,z,w,h){ return crhBox(g,w,h,w,0xffffff,X+x,0,z,{transparent:true,opacity:0,depthWrite:false}); }
function person(k,x,z,shirt,hair,pants,skin,deco,ry){ var id='o_n_'+k, ms=[], pg=new THREE.Group(); pg.position.set(X+x,0,z); pg.scale.setScalar(.946); g.add(pg);
function ab(u,v,y,w,d,h,col){ var m=crhBox(pg,w,h,d,col,u+w/2,y,v+d/2); ms.push(m); return m; }
var SK2=0xe0ae80, INK=0x1f2a36; /* same blocky build as Aura, but no hard hat, backpack, badge or teal eye glints */
ab(-.19,-.1,0,.18,.2,.4,pants); ab(.01,-.1,0,.18,.2,.4,pants); ab(-.2,-.12,0,.2,.28,.09,INK); ab(.0,-.12,0,.2,.28,.09,INK);
ab(-.22,-.13,.4,.44,.26,.32,shirt); ab(-.355,-.075,.525,.13,.15,.195,shirt); ab(.225,-.075,.525,.13,.15,.195,shirt); ab(-.355,-.075,.42,.13,.15,.105,skin); ab(.225,-.075,.42,.13,.15,.105,skin);
ab(-.29,-.25,.72,.58,.5,.54,skin); ab(-.3,-.26,1.2,.6,.52,.14,hair); ab(-.3,.255,1.1,.6,.012,.1,hair);
[-.185,.115].forEach(function(u){ab(u,.255,1.0,.07,.012,.1,INK);}); [-.255,.215].forEach(function(u){ab(u,.255,.9,.07,.012,.034,SK2);}); ab(-.066,.255,.826,.132,.012,.034,INK);
if(deco){ pg.rotation.y=ry||0; K(x-.3,z-.25,x+.3,z+.25); CRO.dancers.push({pg:pg}); return pg; }
ms.push(hit(x,z,1.1,2)); K(x-.3,z-.25,x+.3,z+.25); tag(id,ms); def(id,CR_NEIGH[k].n,[x-.5,z-.4,x+.5,z+.4],[x,z+1.15],[x,z]); }
person('tan',13.1,6.4,0xd48ca0,0xd8d8d8,0x5a5f6b,0xf4c99e); person('rahim',26.2,10.0,0x4b8bbe,0x2b2b2b,0x3d3d46,0xd9a577); person('mei',10.9,12.9,0x6aa86a,0x3b2a22,0x4a4a55,0xf4c99e);
/* void deck dance floor */
CRO.dancers=[];
var dnc=[R(1.4,5.5,7.4,8.9,0,.03,0x5b7a96),R(1.6,5.7,7.2,8.7,.03,.012,0x7596b2),R(7.7,5.6,8.3,6.3,0,.8,0x2b2f36),R(7.78,5.68,8.22,5.95,.8,.1,0x3a4048),R(1.0,5.6,1.12,5.72,0,1.5,0x7a5230),R(.6,5.6,1.55,5.72,1.5,.5,0x2f7d5a)]; K(7.7,5.6,8.3,6.3); K(.95,5.55,1.17,5.77);
person('i0',4.4,6.2,0xe0603a,0x2b2b2b,0x2f3b4a,0xe0b48a,true,0); person('d1',2.6,6.9,0x2f9e8a,0x3b2a22,0x4a4a55,0xf4c99e,true,Math.PI); person('d2',6.2,6.9,0xd9a93a,0xd8d8d8,0x3d3d46,0xd9a577,true,Math.PI); person('d3',2.9,8.3,0x8a6fb0,0x2b2b2b,0x5a5f6b,0xe0b48a,true,Math.PI); person('d4',5.9,8.3,0x4b8bbe,0x3b2a22,0x2f3b4a,0xf4c99e,true,Math.PI);
tag('o_dance',dnc); def('o_dance','Dance class',[1.2,5.3,7.6,9.0],[4.4,7.7],[4.4,6.2]);
/* hidden animals */
function critter(id,ms,x,z,at,face){ ms.push(hit(x,z,1.0,.9)); tag('o_d_'+id,ms); def('o_d_'+id,CR_DISC[id].n,[x-.4,z-.4,x+.4,z+.4],at,face); }
function petAt(k,x,z,ry,sc,y,body,acc){ var K=PETK[k], ob=K.body, oa=K.acc, m; if(body){K.body=body; K.acc=acc;} try{ m=mkPetMesh({k:k,n:'',a:null}); } finally { K.body=ob; K.acc=oa; } if(m.userData.lb)m.remove(m.userData.lb); m.scale.multiplyScalar(sc); m.position.set(X+x,y||0,z); m.rotation.y=ry; g.add(m); return m; }
critter('cat',[petAt('cat',2.5,13.3,Math.PI/2,.8)],2.5,13.3,[3.5,13.3],[2.5,13.3]);
critter('myna',[petAt('parrot',15.1,4.15,Math.PI/2,.42,.95,'#6b4a32','#f4f4f4')],15.1,4.15,[15.1,5.5],[15.1,4.2]);
critter('butterfly',[R(14.2,11.85,14.28,11.95,1.0,.05,0x333333),R(13.95,11.78,14.2,11.96,1.0,.03,0xf2994a),R(14.28,11.78,14.53,11.96,1.0,.03,0xf2994a),R(14.0,11.9,14.2,12.04,1.0,.03,0xf6c27a),R(14.28,11.9,14.48,12.04,1.0,.03,0xf6c27a),R(13.95,11.78,14.02,11.96,1.0,.035,0x333333),R(14.46,11.78,14.53,11.96,1.0,.035,0x333333),R(14.21,11.8,14.23,11.84,1.05,.05,0x333333),R(14.25,11.8,14.27,11.84,1.05,.05,0x333333)],14.25,11.9,[14.9,11.95],[14.25,11.9]);
critter('snail',[R(9.1,10.34,9.52,10.5,0,.06,0xd9c8a8),R(9.45,10.36,9.58,10.48,.0,.15,0xd9c8a8),R(9.12,10.3,9.36,10.54,.06,.2,0x9a7a52),R(9.16,10.34,9.32,10.5,.26,.08,0x7d5f3d),R(9.2,10.38,9.28,10.46,.34,.05,0x5f452b),R(9.5,10.37,9.53,10.4,.15,.1,0xd9c8a8),R(9.5,10.44,9.53,10.47,.15,.1,0xd9c8a8),R(9.5,10.37,9.53,10.4,.25,.03,0x222222),R(9.5,10.44,9.53,10.47,.25,.03,0x222222)],9.3,10.42,[9.7,11.3],[9.3,10.4]);
critter('sunbird',[petAt('parrot',19.4,10.25,Math.PI/2,.3,1.3,'#6f7a2c','#f2c230')],19.4,10.25,[19.7,9.6],[19.4,10.25]);
critter('puppy',[petAt('puppy',16.6,7.0,0,.8)],16.6,7.0,[16.6,8.2],[16.6,7.0]);
critter('bunny',[petAt('bunny',11.2,10.0,Math.PI,.65)],11.2,10.0,[11.2,9.0],[11.2,10.0]);
critter('panda',[petAt('panda',6.4,12.2,0,.6)],6.4,12.2,[6.4,13.3],[6.4,12.2]);
/* driving centre sign and a lesson car */
var drv=[R(17.6,4.2,19.6,5.0,.25,.45,0xf2f2f2),R(18.0,4.25,19.2,4.95,.7,.4,0x9ed3e6),R(17.7,4.15,18.0,4.3,0,.25,0x222222),R(19.2,4.15,19.5,4.3,0,.25,0x222222),R(17.7,4.9,18.0,5.05,0,.25,0x222222),R(19.2,4.9,19.5,5.05,0,.25,0x222222),R(18.5,4.5,18.7,4.7,1.1,.14,0xd9534f),R(17.3,5.3,17.45,5.45,0,1.4,0x7a5230),R(16.85,5.3,17.9,5.45,1.4,.5,0x2c5aa0),hit(18.6,4.6,2.2,1.4)]; K(17.6,4.2,19.6,5.0); K(17.25,5.25,17.5,5.5); tag('o_drive',drv); def('o_drive','Driving lessons',[16.8,3.9,19.9,5.7],[18.6,6.1],[18.6,5.0]);
/* the estate lap: start flag and three checkpoint flags (the checkpoints show during a lap) */
var rf=[R(8.9,7.5,9.05,7.65,0,1.7,0x555555),R(9.05,7.5,9.8,7.65,1.2,.45,0xf2f2f2),R(9.05,7.5,9.4,7.65,1.2,.45,0x222222),hit(9.3,7.6,1.4,1.6)]; tag('o_race',rf); def('o_race','Estate lap (start)',[8.6,7.2,10,7.9],[9.3,8.6],[9.3,7.5]);
var cpG=new THREE.Group(); CRH.cpG=cpG; g.add(cpG); CRO.cps=[[19.5,8.4],[27,15.2],[6.5,15.9]];
CRO.cps.forEach(function(q,qi){ var X0=X+q[0]; crhBox(cpG,.12,1.6,.12,0x2f7d5a,X0,0,q[1]); crhBox(cpG,.8,.4,.1,0xf2c230,X0+.45,1.1,q[1]); crhBox(cpG,1.6,.02,1.6,0xf2c230,X0,0,q[1],{transparent:true,opacity:.35,depthWrite:false}); }); cpG.visible=false;
/* my plant: a raised bed of my own in the garden */
var pb=[R(18.9,11.0,20.6,12.2,0,.3,0x8a6a45),R(19,11.1,20.5,12.1,.3,.05,0x5a4030),hit(19.75,11.6,2,1.2)]; K(18.9,11.0,20.6,12.2); tag('o_plot',pb); def('o_plot','My garden plant',[18.9,10.9,20.7,12.3],[19.8,12.9],[19.8,11.9]);
CRH.plG=new THREE.Group(); g.add(CRH.plG); CRH.plPos=[X+19.75,11.6]; crhPlantDraw();
/* the edge of the walkable ground */
CRH.outObs.push([X-5,-5,X,40],[X+30,-5,X+35,40],[X-5,-5,X+35,-1],[X-5,17.3,X+35,40]);
g.visible=false;
}
var CRRACE=null;
function crhPlantDraw(){
if(!CRH||!CRH.plG)return; var c=crS(); if(!c)return; var pl=crOu(c).pl, G=CRH.plG; while(G.children.length)G.remove(G.children[0]);
if(!pl.k)return; var cx=CRH.plPos[0], cz=CRH.plPos[1], th=(pl.wd!==c.day&&pl.g240){ crMsg='The lap took too long and has stopped. Try again when you are ready.'; crRaceStop(); try{crRender();}catch(e){} return; }
var q=CRO.cps[R.cp], dx=CRH.pos.x-(CRO.x+q[0]), dz=CRH.pos.z-q[1];
R.u=(R.u||0)+dt; if(R.u>.4){ R.u=0; crhSay('Lap '+Math.floor(t/60)+':'+('0'+Math.floor(t%60)).slice(-2)+' Checkpoint '+(R.cp+1)+' of 3'); }
if(Math.hypot(dx,dz)<1.2){ R.ask=1; CRH.keys={}; CRH.path=null; CRH.then=null; var c=crS(); c.sel='o_q'; try{crRender();}catch(e){} }
}
function crRaceStart(mode){
var c=crS(); if(!c)return; if(c.slept){crMsg='You are in bed for tonight. A new day starts tomorrow.'; return;}
if(c.bars.e<30||c.bars.w<25){crMsg='You are too tired or thirsty for a lap. Rest and drink first.'; return;}
if(mode==='cycle'&&c.coins<1){crMsg='Not enough Aura dollars to hire a bicycle.'; return;}
var qs=[]; try{ qs=crBuildQs(lrnLevel()==='S'?'g1':'math',3); }catch(e){}
if(qs.length<3){crMsg='The questions would not load. Try again in a moment.'; return;}
if(mode==='cycle')c.coins-=1;
CRRACE={mode:mode,cp:0,t0:performance.now(),pen:0,ok:0,qs:qs,ask:0,fb:'',sel:-1}; if(CRH&&CRH.cpG)CRH.cpG.visible=true; c.sel='';
crMsg='Go! '+(mode==='cycle'?'Ride':'Run')+' to the three green flags. A maths question waits at each one. Wrong answers add 5 seconds.'; crSave();
}
function crRaceFinish(){
var c=crS(), R=CRRACE, o=crOu(c); var t=Math.round(((performance.now()-R.t0)/1000+R.pen)*10)/10, m=R.mode;
var b=o.race.best[m]||0, rec=(!b||t'+a[1]+'';}).join('');
}
function crRide(kind){
var c=crS(), t=CR_TRANSPORT[kind]; if(!c||!t)return;
var dk=crDKey(c), dd=crDayInfo(dk);
if(c.slept){crMsg='You are in bed for tonight. A new day starts tomorrow.'; return;}
if(c.lastShift===c.day){crMsg='You have already worked today. Come back tomorrow.'; return;}
if(dd.rest){var why=crOTCheck(c,dk); if(why){crMsg=why; return;}}
if(c.coins=2){ crRaceFinish(); } else { CRRACE.cp++; CRRACE.ask=0; CRRACE.fb=''; c.sel=''; crMsg='Keep going to the next green flag.'; } return; }
if(a.indexOf('plant:')===0){ var pl2=crOu(c).pl, pk=a.slice(6); if(pl2.k||!CR_PLANTS[pk])return; pl2.k=pk; pl2.g=0; pl2.wd=0; crTick(c,5); crMsg='You planted the '+CR_PLANTS[pk].n.toLowerCase()+'. Water it once a day.'; crSave(); try{crhPlantDraw();}catch(e){} return; }
if(a==='water'){ var pl3=crOu(c).pl; if(!pl3.k||pl3.g>=CR_PLANTS[pl3.k].days)return; if(pl3.wd===c.day){crMsg='You already watered it today. Come back tomorrow.'; return;} pl3.wd=c.day; pl3.g++; crTick(c,5); crMsg=pl3.g>=CR_PLANTS[pl3.k].days?'Your plant is full-grown. You can pick it now.':'You watered the plant. It grew a little.'; crSave(); try{crhPlantDraw();}catch(e){} return; }
if(a==='harvest'){ var pl4=crOu(c).pl; if(!pl4.k||pl4.g=CR_LEI_MAX){crMsg='You have had a good rest and some fun today. More activities tomorrow.'; return;} if(c.bars.e<25||c.bars.w<20){crMsg='You are too tired or thirsty to exercise now. Rest, and have a drink first.'; return;} c.lz.n++; c.bw.lei++; crTick(c,20); c.bars.e=crClamp(c.bars.e-8); c.bars.w=crClamp(c.bars.w-12); c.bars.f=crClamp(c.bars.f-6); c.eb=1; crMsg='You exercised for 20 minutes. You feel stronger, a little tired and thirsty. You will have a little more energy at your next shift.'; crSave(); return; }
if(a==='dance'){ crDanceStart('class'); return; }
if(a==='bench'){ crTick(c,10); c.bars.e=crClamp(c.bars.e+4); crMsg='You rested on the bench for 10 minutes and watched the neighbourhood go by.'; crSave(); return; }
if(a==='garden'){ crMsg='The sign says residents grow the plants here together. Community gardens like this are part of the Community in Bloom programme run with NParks.'; return; }
}
function crOutPanel(c,s){
var h='', out=!!(CRH&&CRH.out);
function btn(a,label,dis){ return ''; }
if(s==='o_lift'){ h+='
Lift
'+(out?'The lift takes you up to your floor.':'The lift takes you down to the ground floor of the block.')+'
'+btn(out?'up':'down',out?'Go up to my floor':'Go down to the ground floor')+'
'; }
else if(s==='o_bus'||s==='o_taxi'){ var k=s==='o_bus'?'bus':'taxi', t=CR_TRANSPORT[k]; h+='
'+(k==='bus'?'Bus stop':'Taxi stand')+'
'+esc(t.txt)+'
Fare: '+crM(t.fare)+' for the trip there and back. Time on the clock: about '+t.min+' minutes. Play prices, not real fares.
'+btn(k,'Go to work by '+k)+'
Your work is in the Town Centre. You can only go once a day, and not while you are asleep.
'; }
else if(s==='o_mart'){ h+='
Minimart
A small shop in the block. Snacks are not a meal, so they do not use up your three meals.
'+CR_MART.map(function(m){ var pp=crPriceParts(c,m[2],1); return btn('mart:'+m[0],esc(m[1])+' '+crM(pp.tot)); }).join('')+'
'; }
else if(s==='o_play'){ h+='
Playground
Swings, a slide and soft sand. Playing uses a little water and gives you energy.
'+btn('play','Play for 20 minutes')+'
'; }
else if(s==='o_badges'){ var bl=crBadges(c), got=bl.filter(function(b){return b[1];}).length; h+='
My badges ('+got+' of '+bl.length+')
'+bl.map(function(b){ return '
'+esc(b[0])+''+(b[1]?'Collected.':esc(b[2]))+'
'; }).join('')+'
Look for small animals as you walk around the estate. Walk up close and tap them.
'; }
else if(s.indexOf('o_d_')===0&&CR_DISC[s.slice(4)]){ var dd=CR_DISC[s.slice(4)], dn=Object.keys(CR_DISC).filter(function(k){return crOu(c).dis[k];}).length; h+='
'+esc(dd.n)+'
'+esc(dd.fact)+'
Badge: '+esc(dd.badge)+'. You have found '+dn+' of '+Object.keys(CR_DISC).length+' animals.
You have '+esc(CR_NEIGH[cy].item)+' from '+esc(CR_NEIGH[cy].n)+' for '+esc(N.n)+'.
'+btn('give','Give '+CR_NEIGH[cy].item+' to '+N.n.split(' ')[0]+' '+N.n.split(' ').slice(1).join(' '))+'
';
else if(cy) h+='
You are already carrying '+esc(CR_NEIGH[cy].item)+' for '+esc(CR_NEIGH[CR_NEIGH[cy].to].n)+'.
';
else if(ou.ne.done[nk]) h+='
Thank you for the help today. Come back tomorrow.
';
else h+='
'+esc(N.n)+' asks: please take '+esc(N.item)+' to '+esc(CR_NEIGH[N.to].n)+'. It pays Aura $'+N.pay+'.
'+btn('offer:'+nk,'Yes, I will take it')+'
'; }
else if(s==='o_race'){ var rr=crOu(c).race, bb=rr.best; if(CRRACE){ h+='
Estate lap
A lap is in progress. Run to the green flags.
'+btn('rstop','Stop the lap')+'
'; } else { h+='
Estate lap
Run or cycle to three checkpoints around the estate. A maths question waits at each one. Every wrong answer adds 5 seconds.
Best time: running '+(bb.run?bb.run+' s':'none yet')+', cycling '+(bb.cycle?bb.cycle+' s':'none yet')+'. A lap earns no Aura dollars. It is for exercise, fun and beating your best time.
'+btn('race:run','Run (free)')+btn('race:cycle','Cycle (Aura $1 an hour)')+'
'; } }
else if(s==='o_q'&&CRRACE){ var q=CRRACE.qs[CRRACE.cp]; h+='
Checkpoint '+(CRRACE.cp+1)+' of 3
'+esc(q.text)+'
'; if(CRRACE.fb){ h+='
'+esc(CRRACE.fb)+'
'+btn('rnext',CRRACE.cp>=2?'Finish the lap':'Keep going')+'
'; } }
else if(s==='o_plot'){ var pl=crOu(c).pl; h+='
My garden plant
';
if(!pl.k){ h+='
This bed is yours. Choose what to plant. Water it once a day. Pandan takes 3 days, basil 5 days and chilli padi 7 days. The game grows plants much faster than real life. It never gets bigger than a full-grown plant.
'; }
else { var P=CR_PLANTS[pl.k], th=(pl.wd!==c.day&&pl.g'+esc(P.n)+'. '+esc(P.note)+'
'+(pl.g>=P.days?'Full-grown and ready to pick.':'Growth: day '+pl.g+' of '+P.days+'.'+(th?' It looks thirsty.':' Watered today.'))+'
'+(pl.g>=P.days?btn('harvest','Pick it'):btn('water','Water the plant',pl.wd===c.day))+'
A plant that is not watered just waits. It never dies.
'+esc(P.real)+'
'; } }
else if(s==='o_bike'){ var tb=CR_TRANSPORT.cycle; h+='
Shared bicycles
'+esc(tb.txt)+'
Hire fee: '+crM(tb.fare)+' for each hour. Time on the clock: about '+tb.min+' minutes. It uses energy and water. Play prices.
'+btn('cycle','Cycle to work')+'
'; }
else if(s==='o_town'){ var tw2=CR_TRANSPORT.walk, tc2=CR_TRANSPORT.cycle; h+='
Path to the Town Centre
An arrow sign points east along the pavement to the Town Centre, where you work. You can walk, cycle, take the bus or take a taxi.
'+btn('walk','Walk to work (free)')+btn('cycle','Cycle to work '+crM(tc2.fare))+(crOu(c).dr.st>=3?btn('car','Drive a hired car '+crM(CR_TRANSPORT.car.fare)):'')+'
Walking takes about '+tw2.min+' minutes there and back and uses energy and water. The bus stop and taxi stand are along the pavement.
'; }
else if(s==='o_fit'){ h+='
Exercise corner
A pull-up bar, a leg press and a stretching bar on a soft floor. Many estates have a fitness corner like this, free for residents.
'+btn('fit','Exercise for 20 minutes')+'
Children and teenagers aged 7 to 18 are encouraged to be active for at least 60 minutes a day, at a moderate to hard effort (Health Promotion Board). Adults are encouraged to do 150 to 300 minutes of moderate activity a week (SportSG and HPB).
'; }
else if(s==='o_dance'){ var dn2=crOu(c).dance, lzn=(c.lz&&c.lz.d===c.day)?c.lz.n:0; h+='
Community line dancing
A dance class at the void deck. The instructor leads a short routine and the neighbours join in. Arrows appear on the beat. Tap the matching key or button.
The Health Promotion Board runs community line dance classes like this in many neighbourhoods. It counts as exercise and as one of your three leisure activities each day ('+lzn+' used today). It uses some energy and pays nothing.
'+(dn2.n?'Best score '+dn2.best+'% ('+dn2.stars+' of 3 stars). Classes attended: '+dn2.n+'.':'You have not been to a class yet.')+'
'+btn('dance','Join the class')+'
'; }
else if(s==='o_dq'&&CRDQ){ var dq=CRDQ.qs[CRDQ.i]; h+='
Road-rules quiz: question '+(CRDQ.i+1)+' of 10
'+esc(dq.q)+'
'; if(CRDQ.fb){ h+='
'+esc(CRDQ.fb)+'
'+btn('dqn',CRDQ.i>=9?'See my result':'Next question')+'
'; } }
else if(s==='o_drive'){ var dv=crDrOu(c), dd2=(dv.d===c.day); h+='
Driving lessons
A real learner driver learns to drive at a driving centre. This is a shorter version for the game. Everything here is play money.
';
if(dv.st>=3) h+='
You have your driving licence. You can hire a car to go to work. The option is on the path to the Town Centre.
'+btn('car','Drive a hired car to work '+crM(CR_TRANSPORT.car.fare))+'
';
else if(dv.st<1) h+='
Step 1: the road-rules quiz. Answer 10 questions and get 8 right. Real learners must pass a theory test and an eyesight test before they get a Provisional Driving Licence, which lets them take practical lessons. It is an offence to take practical lessons without one.
'+btn('dstart','Take the road-rules quiz')+'
';
else if(dv.st===1) h+='
You passed the quiz and have a Provisional Driving Licence in the game. Step 2: a practice drive. Change lane to avoid things, stop for people at the zebra crossing and stop at the red light. A practice drive costs '+crM(15)+'. Three mistakes or fewer is a pass.
'+btn('dp','Practice drive',dd2)+'
'+(dd2?'
Lessons are one a day. Come back tomorrow.
':'');
else h+='
Practice drive passed. Step 3: the driving test costs '+crM(30)+'. You may make only one mistake. You can also practise again for '+crM(15)+'.
'+btn('dt','Take the driving test',dd2)+btn('dp','Practice again',dd2)+'
'+(dd2?'
Lessons are one a day. Come back tomorrow.
':'');
h+='
Driving centres in Singapore include Bukit Batok Driving Centre, ComfortDelGro Driving Centre and Singapore Safety Driving Centre.
'; }
else if(s==='o_bench'){ h+='
Bench
A bench in the void deck, the open space on the ground floor of the block.
'+btn('bench','Rest for 10 minutes')+'
'; }
else if(s==='o_garden'){ h+='
Community garden
Raised beds that the residents look after together.
'+btn('garden','Read the garden sign')+'
'; }
return h;
}
function crhInit(){
if(CRH||CRH_FAIL)return !!CRH;
try{
if(!window.THREE)throw 0;
var host=document.createElement('div'); host.className='crhost'; host.id='crHost';
var arrow='';
host.innerHTML='
'+
''+
''+
'
'+
'
'+
''+
'
'+
'';
var cv=host.querySelector('canvas');
var rd=new THREE.WebGLRenderer({canvas:cv,antialias:true,alpha:true,powerPreference:'low-power'});
if(!rd.getContext())throw 0;
rd.setPixelRatio(Math.min(window.devicePixelRatio||1,1.5)); rd.setClearColor(0x000000,0);
var sc=new THREE.Scene(), cam=new THREE.OrthographicCamera(-8,8,6,-6,.1,160);
sc.add(new THREE.AmbientLight(0xffffff,.6)); var dl=new THREE.DirectionalLight(0xffffff,.42); dl.position.set(-5,12,4); sc.add(dl);
CRH={host:host,cv:cv,rd:rd,sc:sc,cam:cam,mats:{},ub:null,wallG:new THREE.Group(),furG:new THREE.Group(),gndG:new THREE.Group(),obs:[],free:null,reach:null,
pos:{x:3.375,z:8.2},yaw:Math.PI,path:null,keys:{},dirty:true,last:0,pose:'stand',raf:0,hover:'',sig:'',inside:false,hl:null,pick:[],objs:{},w:0,h:0,pl:null,selPiece:'',wi:[]};
var ub=new THREE.BoxGeometry(1,1,1); ub.translate(0,.5,0); CRH.ub=ub;
sc.add(CRH.gndG); sc.add(CRH.wallG); sc.add(CRH.furG);
crhBuildGround(); crhBuildWalls(); crhBuildChar(); crhBuildHL();
var ro=null; try{ro=new ResizeObserver(function(){crhResize();}); ro.observe(host.querySelector('.crscene'));}catch(e){}
CRH.ro=ro; crhWire();
return true;
}catch(e){ CRH=null; CRH_FAIL=true; try{console.warn('Aura Life 3D home unavailable, using the flat plan instead',e);}catch(_){} return false; }
}
function crhOwnedSig(c){return Object.keys(c.owned||{}).sort().join(',')+'|'+Object.keys(c.items||{}).sort().join(',')+'|'+JSON.stringify(c.fp||{})+'|'+JSON.stringify(c.col||{});}
function crhRebuildFree(c){ CRH.uobs=crfObs(c,null,null); CRH.free=crfGrid(CRH.uobs); if(!CRH.out)CRH.obs=CRH.uobs; }
function crhPlaceGroup(g,id,q){ var d=crfDims(id,q[2]|0); g.position.set(q[0]+d[0]/2,0,q[1]+d[1]/2); g.rotation.y=-(q[2]|0)*Math.PI/2; }
function crhFurniture(c){
var G=CRH.furG; while(G.children.length){G.remove(G.children[0]);} CRH.pick=[]; CRH.objs={}; CRH.wi=[];
function reg(id,g,rect,h,sel,name){ g.traverse(function(m){if(m.isMesh){m.userData.obj=m.userData.sub||id; CRH.pick.push(m);}}); CRH.objs[id]={g:g,rect:rect,h:h,sel:sel,name:name}; }
CRF_FIX.forEach(function(f){ if(f.id==='gate'||f.id==='mailbox'){ if(f.id==='mailbox')CRH.objs.mailbox={g:CRH.mbG,rect:f.rect,h:1.45,sel:'mailbox',name:f.name}; return; }
var g=crhFixture(f.id,c); g.position.set((f.rect[0]+f.rect[2])/2,0,(f.rect[1]+f.rect[3])/2); G.add(g); reg(f.id,g,f.rect,f.h,f.sel,f.name); });
CRH.pick=CRH.pick.concat(CRH.pickW||[]);
var pid=CRH.pl?CRH.pl.id:'';
CRF_ORDER.forEach(function(id){ if(!c.owned[id]||!c.fp[id]||id===pid)return; var g=crhPiece(id,c,false); crhPlaceGroup(g,id,c.fp[id]); G.add(g); reg(id,g,crfPieceRect(id,c.fp[id]),CRF_PIECE[id].h,CRF_PIECE[id].sel,CRF_PIECE[id].n); });
if(CRH.objs.desk)CRH.objs.laptop={g:CRH.objs.desk.g,rect:CRH.objs.desk.rect,h:CRH.objs.desk.h,sel:'laptop',name:'Laptop'};
if(CRH.objs.bed1)CRH.objs.phone={g:CRH.objs.bed1.g,rect:CRH.objs.bed1.rect,h:CRH.objs.bed1.h,sel:'phone',name:'Phone'};
if(c.owned.aircon){ var ga=new THREE.Group(); crhBox(ga,.28,.3,.9,0xf4f6f8,0,0,0); crhBox(ga,.02,.04,.8,0x4f8fe0,.14,.08,0); ga.position.set(8.71,1.9,4.5); ga.rotation.y=Math.PI; G.add(ga); CRH.wi.push({wi:2,o:ga}); } /* living room, on the east wall, facing into the room */
if(c.items.plant){ var pg=new THREE.Group(); crhBox(pg,.2,.2,.2,0xd9825a,0,0,0); crhBox(pg,.18,.28,.18,0x5aa05a,0,.2,0); crhBox(pg,.3,.1,.3,0x6fcf84,0,.4,0); pg.position.set(8.65,0,8.65); G.add(pg); }
if(c.items.frame){ var fr=new THREE.Group(); crhBox(fr,.45,.32,.03,0x7a5a3a,0,0,0); crhBox(fr,.36,.24,.035,0xf6dfb5,0,.04,0); fr.position.set(1.95,1.4,.17); G.add(fr); CRH.wi.push({wi:0,o:fr}); }
if(c.items.clock){ var ck=new THREE.Group(); crhBox(ck,.3,.3,.04,0xffffff,0,0,0); crhBox(ck,.02,.12,.045,0x3b2a20,0,.15,0); crhBox(ck,.1,.02,.045,0x3b2a20,0,.15,0); ck.position.set(4.85,1.9,.17); G.add(ck); CRH.wi.push({wi:0,o:ck}); }
CRH.sig=crhOwnedSig(c); crhRebuildFree(c); crhFixPos(); CRH.walls.forEach(crhApplyWall);
if(CRH.pl)crhGhost(); CRH.dirty=true;
}
function crhFixPos(){ if(CRH.out)return; var p=CRH.pos; if(!crfFreeAt(CRH.obs,p.x,p.z,CRF_RG-.02)){ var q=crfNearest(CRH.free,p.x,p.z); if(q){p.x=q[0];p.z=q[1];} } }
/* ---- camera and wall modes ---- */
function crhCam(lite){
var c=crS(), hv=c.hv||(c.hv={m:'auto',r:3.5,z:1}), th=Math.PI/4+hv.r*Math.PI/2, el=Math.atan(1/Math.SQRT2), D=60, t=(crEx&&CRH.pos)?[CRH.pos.x,.6,CRH.pos.z]:[4.5,.6,4.9]; if(CRDANCE&&crEx){ t=[t[0]+Math.sin(th)*2.6,t[1],t[2]+Math.cos(th)*2.6]; }
var cam=CRH.cam; cam.position.set(t[0]+Math.sin(th)*Math.cos(el)*D,t[1]+Math.sin(el)*D,t[2]+Math.cos(th)*Math.cos(el)*D); cam.lookAt(t[0],t[1],t[2]);
var asp=CRH.w/Math.max(1,CRH.h), hh=(crEx?Math.max(4.3,5.8/asp):Math.max(5.7,8.0/asp))/hv.z; if(CRH.out)hh*=1.5; var bt=crEx?.2:.08; cam.left=-hh*asp; cam.right=hh*asp; cam.top=hh*(1+bt); cam.bottom=-hh*(1-bt); /* the header covers the top of the scene, so the view is centred a little lower: nothing sits under it */ cam.updateProjectionMatrix(); cam.updateMatrixWorld();
CRH.th=th; CRH.dirty=true; if(!lite)crhNear();
}
function crhMode(){ var hv=crS().hv, m=hv.m; if(m==='auto')return CRH.inside?'cut':'up'; return m; }
/* Cutaway: every wall drops to a 0.12 m stub except the two back walls (the two away from the camera), which stay full height */
function crhNear(){
var d=[Math.sin(CRH.th),Math.cos(CRH.th)], mode=crhMode();
CRH.walls.forEach(function(w){
var tg=CRF_H;
if(mode==='down')tg=CRF_LOW;
else if(mode==='cut'){ var near=w.ext?((w.ext[0]*d[0]+w.ext[1]*d[1])>.2):true; tg=near?CRF_LOW:CRF_H; }
w.tg=tg;
});
CRH.dirty=true; var mb=CRH.host.querySelectorAll('.crsmode button'), cur=crS().hv.m; for(var i=0;i=NX||b>=NZ||!free(a,b))continue; var dd=di*di+dj*dj; if(dd=NX||b2>=NZ)continue; var k2=b2*NX+a2; if(prev[k2]!==-2||!free(a2,b2))continue; if(di2&&dj2&&(!free(ci+di2,cj)||!free(ci,cj+dj2)))continue; prev[k2]=cur; q.push(k2); } }
var ek=ej*NX+ei; if(prev[ek]===-2)return [[x1,z1]];
var pts=[]; for(var k3=ek;k3!==-1;k3=prev[k3]){ var i3=k3%NX; pts.push([wx(i3),wz((k3-i3)/NX)]); } pts.reverse();
var out=[], ax=x0, az=z0, idx=0;
while(idxidx;m--){ if(los(ax,az,pts[m][0],pts[m][1])){far=m;break;} } if(far===idx)far=Math.min(idx+1,pts.length-1); out.push(pts[far]); ax=pts[far][0]; az=pts[far][1]; if(far>=pts.length-1)break; idx=far; }
out.push([x1,z1]); return out;
}
function crhGo(x,z,then){
if(!crEx)return false; /* Renovate view: no walking */
if(CRH.out){ crhStand(); CRH.path=crhOutPath(CRH.pos.x,CRH.pos.z,x,z); CRH.then=then||null; CRH.stk=0; CRH.dirty=true; crhSay(''); return true; }
var p=crfPath(CRH.obs,CRH.free,CRH.pos.x,CRH.pos.z,x,z); crhStand();
if(!p){CRH.path=null; CRH.then=null; return false;}
CRH.path=p; CRH.then=then||null; CRH.dirty=true; crhSay(''); if(!p.length)crhArrived(); return true;
}
function crhStand(){ var ch=CRH.ch; CRH.pose='stand'; ch.fig.position.y=0; ch.g.quaternion.identity(); ch.g.rotation.set(0,CRH.yaw||0,0,'YXZ'); [ch.legL,ch.legR,ch.armL,ch.armR].forEach(function(l){l.rotation.x=0;}); ch.pack.visible=true; ch.aura.visible=true; ch.g.position.set(CRH.pos.x,0,CRH.pos.z); ch.shadow.visible=true; CRH.dirty=true; }
function crhArrived(){ var t=CRH.then; CRH.then=null; CRH.path=null; var ch=CRH.ch; ch.legL.rotation.x=ch.legR.rotation.x=ch.armL.rotation.x=ch.armR.rotation.x=0; if(t)t(); CRH.dirty=true; }
function crhFace(x,z){ var dx=x-CRH.pos.x, dz=z-CRH.pos.z; if(dx||dz)CRH.yaw=Math.atan2(dx,dz); }
function crhTick(ts){
CRH.raf=0; if(!crhActive())return;
var dt=Math.min(.25,(ts-(CRH.last||ts))/1000); CRH.last=ts;
var rm=crhReduce(), moved=false, ch=CRH.ch, sp=(CRRACE&&CRH.out)?(CRRACE.mode==='cycle'?3.4:2.6):1.6;
var k=CRH.keys, kx=(k.d?1:0)-(k.a?1:0), kz=(k.s?1:0)-(k.w?1:0);
var asleep=!!(crS()&&crS().slept);
if(asleep){ kx=0; kz=0; CRH.path=null; CRH.then=null; if(CRH.pose!=='lie'){ var _q=crS(); if(_q.fp&&_q.fp.bed1&&_q.owned.bed1)crhPose('lie'); } }
else if(CRH.pose==='lie'){ crhStand(); }
if((kx||kz)&&!CRH.pl&&crEx){
CRH.path=null; CRH.then=null; if(CRH.pose!=='stand')crhStand();
var th=CRH.th, fwd=[-Math.sin(th),-Math.cos(th)], rt=[Math.cos(th),-Math.sin(th)], vx=rt[0]*kx-fwd[0]*kz, vz=rt[1]*kx-fwd[1]*kz, L=Math.hypot(vx,vz); vx/=L; vz/=L;
var np=crfMove(CRH.obs,CRH.pos.x,CRH.pos.z,vx,vz,sp*dt); moved=(np[0]!==CRH.pos.x||np[1]!==CRH.pos.z); CRH.pos.x=np[0]; CRH.pos.z=np[1];
CRH.yaw=Math.atan2(vx,vz);
} else if(CRH.path&&CRH.path.length){
var tg=CRH.path[0], dx=tg[0]-CRH.pos.x, dz=tg[1]-CRH.pos.z, dd=Math.hypot(dx,dz), st=sp*dt;
var tx, tz; if(dd<=st){tx=tg[0]; tz=tg[1];} else {tx=CRH.pos.x+dx/dd*st; tz=CRH.pos.z+dz/dd*st;}
var ox0=CRH.pos.x, oz0=CRH.pos.z; var r=crfResolve(CRH.obs,tx,tz,CRF_R); CRH.pos.x=r[0]; CRH.pos.z=r[1];
if(CRH.out){ if(Math.hypot(CRH.pos.x-ox0,CRH.pos.z-oz0)10){CRH.path=null; CRH.then=null; CRH.stk=0; crhSay('Aura cannot get through there.'); moved=false;} } else CRH.stk=0; }
if(CRH.path){ if(dd<=st+.02||Math.hypot(tg[0]-CRH.pos.x,tg[1]-CRH.pos.z)<.04){CRH.path.shift();} else CRH.yaw=Math.atan2(dx,dz);
moved=true; if(!CRH.path.length){crhArrived();} }
}
var ins=CRH.pos.z<8.9&&CRH.pos.x>.1&&CRH.pos.x<8.9; if(ins!==CRH.inside){CRH.inside=ins; if(crS().hv.m==='auto')crhNear();}
crhWallStep(dt);
if(CRRACE&&CRH.out)crRaceTick(dt);
if(CRH.pose==='stand'){
if(moved){ ch.ph+=dt*(rm?0:9); var s=Math.sin(ch.ph)*(rm?0:.6); ch.legL.rotation.x=s; ch.legR.rotation.x=-s; ch.armL.rotation.x=-s*.8; ch.armR.rotation.x=s*.8; ch.fig.position.y=rm?0:Math.abs(Math.sin(ch.ph))*.03; }
if(crEx&&moved)crhCam(true);
ch.g.position.set(CRH.pos.x,0,CRH.pos.z); var dy=CRH.yaw-ch.g.rotation.y; while(dy>Math.PI)dy-=2*Math.PI; while(dy<-Math.PI)dy+=2*Math.PI; ch.g.rotation.y+=rm?dy:dy*Math.min(1,dt*14);
}
if(CRDANCE){ try{crDanceAnim(dt,ch,rm);}catch(e){} CRH.dirty=true; }
if(moved||CRH.dirty){ CRH.dirty=false; CRH.rd.render(CRH.sc,CRH.cam); }
crhLoop();
}
function crhActive(){ return !!(CRH&&crOpen&&CRH.host.isConnected&&!document.hidden&&crS()&&crS().tab==='home'); }
function crhLoop(){ if(!CRH||CRH.raf||!crhActive())return; CRH.raf=requestAnimationFrame(crhTick); }
function crhResize(){
if(!CRH)return; var el=CRH.host.querySelector('.crscene'), w=el.clientWidth, h=el.clientHeight; if(!w||!h)return;
var pr=Math.min(window.devicePixelRatio||1,crEx?1:1.5); if(CRH.pr!==pr){CRH.pr=pr; CRH.rd.setPixelRatio(pr); CRH.w=0;}
if(w===CRH.w&&h===CRH.h)return; CRH.w=w; CRH.h=h; CRH.rd.setSize(w,h,false); crhCam();
}
function crfRot(px,pz,k){ for(var i=0;i<(k&3);i++){var t=px; px=-pz; pz=t;} return [px,pz]; } /* a quarter turn clockwise seen from above, k times */
/* seat of a piece in the flat: world position, seat top height and the way a sitter faces */
function crhSeat(c,id){
var q=c.fp&&c.fp[id]; if(!q||!c.owned[id])return null; var P=CRF_PIECE[id], k=q[2]|0, d=crfDims(id,k), cx=q[0]+d[0]/2, cz=q[1]+d[1]/2, loc, face, hgt;
if(id==='sofa'){loc=[.3-P.w/2,.9-P.d/2]; face=[-1,0]; hgt=.51;} else if(id==='chair'){loc=[.225-P.w/2,.225-P.d/2]; face=[1,0]; hgt=.48;} else return null;
var r=crfRot(loc[0],loc[1],k), f=crfRot(face[0],face[1],k); return {x:cx+r[0],z:cz+r[1],y:hgt,fx:f[0],fz:f[1]};
}
/* ---- poses used by the actions ---- */
function crhPose(kind,id){
if(!CRH||!CRH.ch)return; var c=crS(), ch=CRH.ch;
crhStand();
if(kind==='lie'){
var q=c.fp&&c.fp.bed1; if(!q||!c.owned.bed1)return; var k=q[2]|0, d=crfDims('bed1',k), cx=q[0]+d[0]/2, cz=q[1]+d[1]/2, h=crfRot(0,-1,k); /* head end = the headboard */
CRH.pose='lie'; var X=new THREE.Vector3(-h[1],0,h[0]), Y=new THREE.Vector3(h[0],0,h[1]), Z=new THREE.Vector3(0,1,0);
ch.g.quaternion.setFromRotationMatrix(new THREE.Matrix4().makeBasis(X,Y,Z)); ch.g.position.set(cx-h[0]*.5,.74,cz-h[1]*.5); ch.shadow.visible=false; ch.pack.visible=false; ch.aura.visible=false;
} else if(kind==='sit'){
var s=crhSeat(c,id); if(!s)return; CRH.pose='sit'; CRH.pos.x=s.x; CRH.pos.z=s.z;
var yaw=Math.atan2(s.fx,s.fz); if(id==='chair'&&c.fp.desk){ var dr=crfPieceRect('desk',c.fp.desk), dx=(dr[0]+dr[2])/2-s.x, dz=(dr[1]+dr[3])/2-s.z; if(Math.hypot(dx,dz)<1.1)yaw=Math.atan2(dx,dz); }
ch.g.rotation.set(0,yaw,0,'YXZ'); CRH.yaw=yaw; ch.g.position.set(s.x,s.y-.38,s.z); ch.legL.rotation.x=ch.legR.rotation.x=-Math.PI/2; ch.armL.rotation.x=ch.armR.rotation.x=-.5; ch.shadow.visible=false; ch.pack.visible=false; ch.aura.visible=false;
}
CRH.dirty=true; crhLoop();
}
document.addEventListener('click',function(e){ var m=e.target&&e.target.closest&&e.target.closest('#crSMsg'); if(m)m.hidden=true; });
function crhSay(t){ var m=CRH&&CRH.host.querySelector('#crSMsg'); if(!m)return; if(t){m.textContent=t; m.hidden=false;} else m.hidden=true; }
/* ---- choosing things: walk there, then open the usual panel ---- */
function crhTarget(id){ /* a fixture or an owned piece */
var d=null; CRF_FIX.forEach(function(x){if(x.id===id)d=x;}); if(d)return {id:id,sel:d.sel,rect:d.rect,at:d.at,face:d.face,piece:0};
var c=crS(), P=CRF_PIECE[id]; if(P&&c.owned[id]&&c.fp[id])return {id:id,sel:P.sel,rect:crfPieceRect(id,c.fp[id]),piece:1};
if(id==='laptop'||id==='phone'){ var hs=id==='laptop'?'desk':'bed1'; if(c.owned[hs]&&c.fp[hs])return {id:id,sel:id,rect:crfPieceRect(hs,c.fp[hs]),piece:0}; }
return null;
}
function crhChoose(id){
if(CRH.pl)return; var c=crS();
if(id==='aircon'){ crhSay('Your aircon is fitted high on the right-hand wall of the living room, across from the sofa. It is already in place. Look up at the wall.'); return; }
if(c.slept&&crEx){ crhSay('You are asleep. A new day starts tomorrow.'); return; }
if(CRH.out&&id.indexOf('o_')===0){ if(id==='o_badges'){c.sel='o_badges'; crRender(); return;} var oo=CRH.outObjs[id]; if(!oo)return; crhGo(oo.at[0],oo.at[1],function(){ crhFace(oo.face[0],oo.face[1]); c.sel=oo.sel; crOutArrive(c,oo.sel); crSave(); crRender(); }); return; }
if(!crEx&&id!=='bedbuy'&&id!=='clothes'){ var t0=crhTarget(id); if(!t0)return; CRH.selPiece=t0.piece?id:''; if(t0.sel){c.sel=t0.sel; crSave(); crRender();} return; } /* Renovate view: just select it, no walking */
if(id==='bedbuy'){ c.sel='bed'; crRender(); return; }
if(id==='clothes'){ if(c.owned.ward&&c.fp.ward){id='ward';} else { c.sel='ward'; crRender(); return; } }
var t=crhTarget(id); if(!t)return; CRH.selPiece=t.piece?id:''; crhHighlight(id);
var st=t.at||crfStandNear(CRH.free,crhReachNow(),t.rect,[CRH.pos.x,CRH.pos.z]); if(!st){crhSay('Aura cannot reach that.'); return;}
var ok=crhGo(st[0],st[1],function(){
if(t.face)crhFace(t.face[0],t.face[1]); else crhFace((t.rect[0]+t.rect[2])/2,(t.rect[1]+t.rect[3])/2);
crhHighlight(''); crhPrep(id); if(t.sel){ c.sel=t.sel; crSave(); crRender(); }
});
if(!ok)crhSay('Aura cannot reach that.');
}
function crhReachNow(){ return crfReach(CRH.free,CRH.pos.x,CRH.pos.z); }
function crhPrep(id){ var c=crS(); CRH.sitAt=null; if(id==='sofa'&&c.owned.sofa)CRH.sitAt='sofa'; else if((id==='desk'||id==='chair')&&c.owned.chair)CRH.sitAt='chair'; }
/* ---- input ---- */
function crhFloorPt(e){ var r=CRH.cv.getBoundingClientRect(), nx=((e.clientX-r.left)/r.width)*2-1, ny=-((e.clientY-r.top)/r.height)*2+1, rc=CRH.rc||(CRH.rc=new THREE.Raycaster()); rc.setFromCamera({x:nx,y:ny},CRH.cam); var pl=new THREE.Plane(new THREE.Vector3(0,1,0),0), pt=new THREE.Vector3(); return rc.ray.intersectPlane(pl,pt)?{x:pt.x,z:pt.z,rc:rc}:{rc:rc}; }
function crhPointer(e,tap){
var fp=crhFloorPt(e), rc=fp.rc;
if(CRH.pl){ if(fp.x!==undefined&&(tap||CRH.pl.drag)){ var d=crfDims(CRH.pl.id,CRH.pl.k); crhPlaceAt(fp.x-d[0]/2,fp.z-d[1]/2,CRH.pl.k); } return; }
var hit=rc.intersectObjects(CRH.out?(CRH.outPick||[]):CRH.pick,false), id=hit.length?hit[0].object.userData.obj:'';
if(!tap){crhHighlight(id); CRH.cv.style.cursor=id?'pointer':'default'; return;}
if(id){crhChoose(id); return;}
if(fp.x!==undefined){ crhHighlight(''); if(!crhGo(fp.x,fp.z))crhSay('Aura cannot walk there.'); }
}
/* ---- placing furniture ---- */
function crhGhost(){
var P=CRH.pl; if(!P)return; var c=crS(); if(CRH.ghost){CRH.furG.remove(CRH.ghost); CRH.ghost=null;}
var g=crhPiece(P.id,c,true); crhPlaceGroup(g,P.id,[P.x,P.z,P.k]);
var PP=CRF_PIECE[P.id], col=P.err?0xd9534f:0x3fa66b, pl=new THREE.Mesh(CRH.ub,crhMat(col,{transparent:true,opacity:.5,depthWrite:false})); pl.scale.set(PP.w,.03,PP.d); pl.position.set(0,.012,0); g.add(pl);
CRH.furG.add(g); CRH.ghost=g; CRH.dirty=true;
}
function crhPlaceStart(id){
var c=crS(); if(!c||!c.owned[id]||!c.fp[id]||!CRH)return false; CRH.path=null; CRH.then=null; crhStand(); CRH.selPiece=id;
var q=c.fp[id]; CRH.pl={id:id,x:q[0],z:q[1],k:q[2]|0,ox:q[0],oz:q[1],ok0:q[2]|0,err:'',drag:false};
crhFurniture(c); crhPlaceAt(q[0],q[1],q[2]|0,1); CRH.host.querySelector('#crPl').hidden=false; crhPlaceUI(); crhSay('');
try{CRH.cv.focus({preventScroll:true});}catch(e){} return true;
}
function crhPlaceAt(x,z,k,nomag){
var P=CRH.pl; if(!P)return; var s=crfSnap(P.id,x,z,k,nomag); P.x=s[0]; P.z=s[1]; P.k=k&3;
P.err=crfCheck(crS(),P.id,P.x,P.z,P.k);
crhGhost(); crhPlaceUI();
}
function crhPlaceUI(){
var P=CRH.pl, h=CRH.host; if(!P)return; var nm=CRF_PIECE[P.id].n, t=h.querySelector('#crPlT');
t.innerHTML=''+esc(nm)+': '+(P.err?''+esc(P.err)+'':'this spot is free. Press Place here, or move or turn it first.')+' Tap or drag in the picture, use the arrows, and Turn (R) turns it a quarter.';
var ok=h.querySelector('#crPlOk'); ok.setAttribute('aria-disabled',P.err?'true':'false'); ok.classList.toggle('crdim',!!P.err);
}
function crhAxisFor(b){ /* the floor direction that moves up/down/left/right on the screen for this camera */
var th=CRH.th, rt=[Math.cos(th),-Math.sin(th)], fw=[-Math.sin(th),-Math.cos(th)], s={ul:[-1,1],ur:[1,1],dr:[1,-1],dl:[-1,-1]}[b], best=null, bs=-9;
[[1,0],[-1,0],[0,1],[0,-1]].forEach(function(a){ var v=s[0]*(a[0]*rt[0]+a[1]*rt[1])+s[1]*(a[0]*fw[0]+a[1]*fw[1]); if(v>bs){bs=v; best=a;} }); return best;
}
function crhPlaceMove(b){
var P=CRH.pl; if(!P)return; var a=crhAxisFor(b), x=P.x+a[0]*CRF_C, z=P.z+a[1]*CRF_C, s=crfSnap(P.id,x,z,P.k);
if(Math.abs(s[0]-P.x)<1e-6&&Math.abs(s[1]-P.z)<1e-6)s=crfSnap(P.id,x,z,P.k,1);
crhPlaceAt(s[0],s[1],P.k,1);
}
function crhPlaceRot(){
var P=CRH.pl; if(!P)return; var d=crfDims(P.id,P.k), cx=P.x+d[0]/2, cz=P.z+d[1]/2, k=(P.k+1)&3, n=crfDims(P.id,k);
crhPlaceAt(cx-n[0]/2,cz-n[1]/2,k);
}
function crhPlaceEnd(msg){
var c=crS(); CRH.pl=null; if(CRH.ghost){CRH.furG.remove(CRH.ghost); CRH.ghost=null;} CRH.host.querySelector('#crPl').hidden=true; crhFurniture(c); crhSay(msg||''); crhLoop();
}
function crhPlaceOk(){
var P=CRH.pl; if(!P)return; var c=crS(); if(P.err){ crhSay(P.err); return; }
c.fp[P.id]=[P.x,P.z,P.k]; crSave(); crhPlaceEnd(CRF_PIECE[P.id].n+' placed.'); crhSyncList();
}
function crhPlaceCancel(){
var P=CRH.pl; if(!P)return; crhPlaceEnd('Kept where it was.'); crhSyncList();
}
function crhSyncList(){ var c=crS(); if(CRH&&c)CRH.host.querySelector('#crObjL').innerHTML=crhList(c); }
function crhWire(){
var cv=CRH.cv, host=CRH.host, down=null;
cv.addEventListener('pointerdown',function(e){down=[e.clientX,e.clientY]; try{cv.focus({preventScroll:true});}catch(_){} if(CRH.pl){CRH.pl.drag=true; crhPointer(e,true);} });
var orb=false, lastX=0;
cv.addEventListener('pointerdown',function(e){ orb=false; lastX=e.clientX; });
cv.addEventListener('pointerup',function(e){ if(CRH.pl){CRH.pl.drag=false; down=null; return;} if(down&&!orb&&Math.hypot(e.clientX-down[0],e.clientY-down[1])<8)crhPointer(e,true); down=null; orb=false; });
cv.addEventListener('pointermove',function(e){ if(CRH.pl){ if(CRH.pl.drag)crhPointer(e,false); return; }
if(down&&(e.buttons&1)){ if(!orb&&Math.abs(e.clientX-down[0])>8)orb=true; if(orb){ var hv=crS().hv; hv.r=(((hv.r||0)-(e.clientX-lastX)*4/700)%4+4)%4; lastX=e.clientX; crhCam(true); crhLoop(); return; } }
lastX=e.clientX; if(e.pointerType==='mouse')crhPointer(e,false); });
cv.addEventListener('pointerup',function(){ try{ if(orb){ crSave(); crhCam(); } }catch(e){} });
cv.addEventListener('pointerleave',function(){ if(!CRH.pl)crhHighlight(''); else CRH.pl.drag=false; });
cv.addEventListener('wheel',function(e){ e.preventDefault();
/* a trackpad sends many small wheel events: zoom in proportion to how far the fingers moved (pinch = ctrlKey), not one big step per event */
var dy=e.deltaY*(e.deltaMode===1?16:e.deltaMode===2?200:1); dy=Math.max(-80,Math.min(80,dy)); if(!dy)return;
crhZoom(Math.exp(-dy*(e.ctrlKey?.012:.002)));
},{passive:false});
var gz0=1; cv.addEventListener('gesturestart',function(e){e.preventDefault(); gz0=crS().hv.z;}); cv.addEventListener('gesturechange',function(e){e.preventDefault(); var hv=crS().hv; hv.z=Math.max(.6,Math.min(2.2,gz0*e.scale)); crhCam();}); cv.addEventListener('gestureend',function(e){e.preventDefault(); crSave();});
host.addEventListener('click',function(e){
var b=e.target.closest('button'); if(!b)return; var c=crS(), hv=c.hv;
if(b.dataset.v==='rl'){hv.r=(hv.r+3)%4; crSave(); crhCam(); if(CRH.pl)crhPlaceUI();} else if(b.dataset.v==='rr'){hv.r=(hv.r+1)%4; crSave(); crhCam(); if(CRH.pl)crhPlaceUI();}
else if(b.dataset.v==='zi')crhZoom(1.2); else if(b.dataset.v==='zo')crhZoom(1/1.2);
else if(b.dataset.m){hv.m=b.dataset.m; crSave(); crhNear();}
else if(b.dataset.sobj){crhChoose(b.dataset.sobj);}
else if(b.dataset.pl){ var a=b.dataset.pl; if(a==='rot')crhPlaceRot(); else if(a==='ok')crhPlaceOk(); else if(a==='cancel')crhPlaceCancel(); else crhPlaceMove(a); }
crhLoop();
});
if(CRH_WIRED)return; CRH_WIRED=1;
document.addEventListener('focusin',function(e){if(CRH)CRH.wf=(e.target===CRH.cv);});
window.addEventListener('keydown',function(e){ if(!crhActive())return; var k=crhKey(e); if(!k)return;
if(CRH.pl){ /* placing: arrows move the piece one step, R turns it, Enter places, Escape puts it back */
var mv={w:'ur',s:'dl',a:'ul',d:'dr'}[k]; if(mv){crhPlaceMove(mv); e.preventDefault(); return;}
if(k==='r'){crhPlaceRot(); e.preventDefault(); return;} if(k==='Enter'){crhPlaceOk(); e.preventDefault(); return;} if(k==='Escape'){crhPlaceCancel(); e.preventDefault(); return;}
}
if(k==='r'){ if(CRH.selPiece&&crS().owned[CRH.selPiece]&&CRH.selPiece!=='aircon'){ if(crhPlaceStart(CRH.selPiece))crhPlaceRot(); e.preventDefault(); } return; }
if(k==='Enter'||k==='Escape')return;
if(k==='q'||k==='e'){var hv=crS().hv; hv.r=(hv.r+(k==='e'?1:3))%4; crSave(); crhCam(); crhLoop(); return;}
if(k==='+'||k==='-'){crhZoom(k==='+'?1.2:1/1.2); crhLoop(); return;}
CRH.keys[k]=1; e.preventDefault(); crhLoop(); });
window.addEventListener('keyup',function(e){ var k=crhKey(e); if(k&&CRH)delete CRH.keys[k]; });
window.addEventListener('blur',function(){if(CRH)CRH.keys={};});
}
function crhKey(e){
if(e.ctrlKey||e.metaKey||e.altKey)return ''; var t=e.target, tag=t&&t.tagName; if(tag==='INPUT'||tag==='TEXTAREA'||tag==='SELECT')return '';
var open=false; try{['crConfirm','jobModal','quizModal','startModal'].forEach(function(i){var m=document.getElementById(i); if(m&&!m.hidden)open=true;});}catch(_){} if(open)return '';
var k=e.key, map={ArrowUp:'w',ArrowDown:'s',ArrowLeft:'a',ArrowRight:'d',w:'w',a:'a',s:'s',d:'d',W:'w',A:'a',S:'s',D:'d',q:'q',Q:'q',e:'e',E:'e','+':'+','=':'+','-':'-',r:'r',R:'r',Enter:'Enter',Escape:'Escape'};
var m=map[k]||''; if(!m)return '';
if((k.indexOf('Arrow')===0)&&!(t&&t.id==='crCanvas'))return ''; /* arrows move Aura only while the picture has focus; WASD works anywhere on Home */
if(tag==='BUTTON'&&(k===' '||k==='Enter'))return '';
if((m==='Enter'||m==='Escape')&&!CRH.pl)return '';
return m;
}
var crhZT=0;
function crhZoom(f){ var hv=crS().hv; hv.z=Math.max(.6,Math.min(2.2,hv.z*f)); crhCam(); clearTimeout(crhZT); crhZT=setTimeout(crSave,400); }
/* ---- hooking into the Home screen ---- */
function crhList(c){
if(CRH&&CRH.out)return crhOutList();
var h=''; CRF_FIX.forEach(function(d){ if(d.nolist)return; var lab=d.name; if(d.id==='mailbox'&&crMailCount())lab+=' ('+crMailCount()+')'; h+=''; });
CRF_ORDER.forEach(function(id){ if(c.owned[id]&&c.fp&&c.fp[id])h+=''; });
if(c.owned.aircon)h+='';
if(c.owned.desk&&c.fp&&c.fp.desk)h+='';
if(c.owned.bed1&&c.fp&&c.fp.bed1)h+='';
if(!c.owned.bed1)h+='';
if(!c.owned.ward)h+='';
return h;
}
function crhSync(){
var c=crS(); if(!c||!c.setup||c.tab!=='home')return; if(!crhInit())return;
var slot=document.getElementById('crSceneSlot'); if(slot){slot.parentNode.replaceChild(CRH.host,slot);}
if(CRH.sig!==crhOwnedSig(c)||!CRH.free){ var before=JSON.stringify(c.fp); crfRepair(c); if(JSON.stringify(c.fp)!==before)crSave(); if(!CRH.pl)crhFurniture(c); }
CRH.host.querySelector('#crObjL').innerHTML=crhList(c);
if(CRH.pendingMsg){crhSay(CRH.pendingMsg); CRH.pendingMsg='';}
crhDress(c); crhResize(); crhCam(); CRH.dirty=true;
if(crPlacePending){ var id=crPlacePending; crPlacePending=''; crhPlaceStart(id); }
crhLoop(); if(CRH.wf){try{CRH.cv.focus({preventScroll:true});}catch(e){}}
}
function crhDispose(){
if(!CRH)return; try{cancelAnimationFrame(CRH.raf);}catch(e){} try{if(CRH.ro)CRH.ro.disconnect();}catch(e){}
try{CRH.sc.traverse(function(o){if(o.geometry&&o.geometry!==CRH.ub)o.geometry.dispose();}); CRH.ub.dispose(); CRH.rd.dispose(); if(CRH.rd.forceContextLoss)CRH.rd.forceContextLoss();}catch(e){}
try{CRH.host.remove();}catch(e){}
CRH=null;
}
/* ---- floor plan drawn to scale (also the picture when 3D is not available): 1 square = 1 metre ---- */
function crPlan(c){
var sel=c.sel, h='', fp=c.fp||{}, o=c.owned||{};
function R(x0,z0,x1,z1,f,cl,rx){return '';}
function T(x,z,t,sz,cl){return ''+t+'';}
function G(id,label,inner,rc){return ''+(rc?'':'')+inner+'';}
h+='';
}
/* ---- hooks into the Home screen (wrapped at run time, so existing code is not edited) ---- */
(function(){
var _h=crHome; crHome=function(c){
var m=crMsg, h=_h(c); if(CRH_FAIL)return h;
try{ if(!window.THREE||!crhInit())return h; }catch(e){return h;}
CRH.pendingMsg=m||'';
h=h.replace(/
[\s\S]*?<\/div>/,'');
return h.replace(/
[\s\S]*?<\/svg><\/div>/,'Floor plan, drawn to scale
'+crPlan(c)+'
');
};
var _r=crRender; crRender=function(f){ _r.apply(this,arguments); try{ if(crOpen&&crS()&&crS().tab==='home')crhSync(); else if(CRH&&CRH.raf){cancelAnimationFrame(CRH.raf); CRH.raf=0;} }catch(e){try{console.warn('3D home',e);}catch(_){}} };
var _l=crLeave; crLeave=function(){ _l.apply(this,arguments); try{crhDispose();}catch(e){} };
function moveRow(c){ var id=CRH&&CRH.selPiece; if(!id||!c.owned[id]||!CRF_PIECE[id]||CRF_PIECE[id].sel!==c.sel)return ''; return ''; }
var _sp=crSelPanel; crSelPanel=function(c){
var s=c.sel, h='';
if(s==='sofa')return '
Sofa
A two-seat sofa, 1.8 m long. Sit down and rest for a moment.
'+moveRow(c);
if(s==='tv')return '
TV console
A low console with a 32 inch TV. Watch a little, then get back to your day.
Put on a dance video and copy the steps. It counts as exercise and as one of your three leisure activities.
'+moveRow(c);
if(CR_INSTR[s]){ var IN=CR_INSTR[s], lzl=(c.lz&&c.lz.d===c.day)?c.lz.n:0; return '
'+esc(IN.n)+'
'+esc(IN.f)+'
Playing is a leisure activity. '+Math.max(0,CR_LEI_MAX-lzl)+' of '+CR_LEI_MAX+' left today. It pays nothing, but you feel fresher at your next shift.
'+moveRow(c); }
if(s==='rug')return '
Rug
A rug 2.0 m by 1.75 m. It lies flat, so you can walk over it.
'+moveRow(c);
if(s==='desk')return '
Desk
A desk for your own study. Shifts at the Work tab count towards your skill level.
'+(c.owned.recorder?'':'')+'
'+moveRow(c);
if(s==='table')return '
Dining table
A table with two chairs. Meals are bought at the kitchen counter, then you eat here.
');
if(s==='kitchen'&&c.path==='food')h+='';
return h+moveRow(c);
};
var _a=crAct; crAct=function(el){
var a=el.dataset.act, c=crS();
if(a==='place'){ if(CRH&&crhActive())crhPlaceStart(el.dataset.a); return; }
if(a==='relax'||a==='study'){
var sa=CRH&&CRH.sitAt; if(sa)crhPose('sit',sa); crMsg=(a==='relax')?(sa?'You sit on the sofa and rest.':'You rest for a moment.'):(sa?'You sit at your desk.':'You stand at your desk.'); crRender(); return;
}
if(a==='dance'){ crDanceStart('tv'); crRender(); return; }
if(a==='music'){ var mid=el.dataset.a, MI=CR_INSTR[mid]||{n:'Recorder',notes:[523,587,659,698,784],v:'sine',f:''};
if(!c.lz)c.lz={d:0,n:0}; if(c.lz.d!==c.day){c.lz.d=c.day; c.lz.n=0;}
if(c.lz.n>=CR_LEI_MAX){ crMsg='You have had a good rest today. More activities tomorrow.'; crRender(); return; }
c.lz.n++; c.eb=1; c.bw.lei++; crTick(c,20); crInstrPlay(mid);
crMsg='You played the '+MI.n.toLowerCase()+' for a while. It was relaxing. You will have a little more energy at your next shift.'; crSave(); crRender(); return; }
if(a==='watch'){ crMsg='You watch a little TV and rest your eyes.'; crRender(); return; }
if(a==='cook'){
if(typeof foodCookRound==='function'){ try{foodCookRound(function(res){ if(res&&res.cancelled){crMsg='You stopped cooking.';}else{ var cc=crS(), st=(res&&res.stars!=null)?res.stars:0, ate=false; try{ ate=crDoEat(cc,{n:'Your own cooked dish',rest:20+st*4,en:2,msg:'You cooked a dish'+(res&&res.stars!=null?' and earned '+st+' of 3 stars':'')+', then ate it. You feel fuller. Cooking gives no Aura dollars.'}); if(ate)crSave(); }catch(e){} if(!ate&&!/three meals/.test(crMsg||''))crMsg='You cooked a dish. Cooking gives no Aura dollars.'; else if(!ate)crMsg='You cooked a dish and earned '+st+' of 3 stars, but you have already had three meals today, so you saved it for tomorrow. Cooking gives no Aura dollars.'; } crRender(); });}catch(e){crMsg='Cooking is not ready yet.'; crRender();} }
else { crMsg='Cooking at the counter arrives with the cooking step. It is not in this preview.'; crRender(); }
return;
}
_a.apply(this,arguments);
};
var _s=crSleep; crSleep=function(kind){ _s.apply(this,arguments); try{ if(CRH&&crS().slept)crhPose('lie'); }catch(e){} };
})();
/* ---- explore mode: the whole screen is the home; Renovate gives the plan view for furniture ---- */
function crBuyFirstHTML(c){
var L=[['Bed','bed1'],['Wardrobe for clothes','ward'],['Desk and chair','desk+chair'],['Dining table','table'],['Fridge','fridge'],['Fan or aircon to keep cool','fan|aircon'],['Sofa','sofa']];
var todo=L.filter(function(x){var ids=x[1].split(/[+|]/); return !((x[1].indexOf('+')>=0)?ids.every(function(i){return c.owned[i];}):ids.some(function(i){return c.owned[i];}));});
var done=L.length-todo.length;
var h='
To do first ('+done+' of '+L.length+' done)
';
todo.slice(0,3).forEach(function(x){ h+='
'+esc(x[0])+'
'; });
return h+'
';
}
function crMoreTab(c){
return '
More
Calendar
Work days, rest days and holidays.
'+
'
Home value
What your flat and furniture are worth, and what HDB flats cost over the years.
'+
'
Aura Life summary
Your rank, skill level and next promotion.
'+
'
Credits and licences
Who and what helped to make this game.
'+(crDsOn()?'
Design furniture
Build your own furniture from small parts, to scale.
':'')+'
';
}
var CR_CREDITS=[
['Aura Builders','Made by Make Aura.'],
['3D drawing','three.js, MIT licence, copyright the three.js authors.'],
['Chinese stroke order','Stroke data from Make Me a Hanzi, under the Arphic Public License (Arphic Technology Co., Ltd.).'],
['Font','Nunito, SIL Open Font Licence 1.1, from Google Fonts.'],
['Artwork','Some of the pictures were made with AI image tools. They are used under the tool provider’s terms.'],
['Money','All money in the game is play money. Aura dollars have no cash value.']
];
function crCreditsTab(c){
var h='
Credits and licences
'; CR_CREDITS.forEach(function(x){h+='
'+esc(x[0])+'. '+esc(x[1])+'
';});
return h+'
';
}
function crExApply(){
var app=$c('crApp'), c=crS(); if(!app||!c)return; var on=!!(crEx&&c.tab==='home'&&CRH&&!CRH_FAIL&&c.setup);
app.classList.toggle('crex',on);
if(!CRH)return; var sc=CRH.host.querySelector('.crscene'); if(!sc)return;
try{ if(CRH.ch&&CRH.ch.g)CRH.ch.g.visible=!!crEx||c.tab!=='home'; }catch(e){}
var st=sc.querySelector('#crExSt'), bt=sc.querySelector('#crExBtn'), hn=sc.querySelector('#crExHint');
if(!st){ st=document.createElement('div'); st.id='crExSt'; st.className='crexst'; st.setAttribute('aria-label','How you feel'); sc.appendChild(st);
bt=document.createElement('button'); bt.id='crExBtn'; bt.type='button'; bt.className='crbtn alt small crexbtn'; sc.appendChild(bt);
bt.addEventListener('click',function(){ crEx=!crEx; if(!crEx&&CRH&&CRH.out){crhGoIn();} if(!crEx){try{crS().hv.z=1; crS().hv.m='up';}catch(e){}} crExApply(); try{crhResize();}catch(e){} crhCam(); try{CRH.cv.focus({preventScroll:true});}catch(e){} crhLoop(); });
var wb=document.createElement('button'); wb.id='crExWork'; wb.type='button'; wb.className='crbtn small crexwork'; wb.textContent='Go to work'; sc.appendChild(wb);
wb.addEventListener('click',function(){ try{ crTryWork(false); if(crMsg)crRender(); }catch(e){} });
var ob=document.createElement('button'); ob.id='crExOut'; ob.type='button'; ob.className='crbtn alt small crexout'; ob.textContent='Go outside'; sc.appendChild(ob);
ob.addEventListener('click',function(){ try{ crhChoose(CRH.out?'o_lift':'lift'); }catch(e){} });
hn=document.createElement('div'); hn.id='crExHint'; hn.className='crexhint'; sc.appendChild(hn);
/* on-screen walking arrows, so a touch screen can walk straight away without a keyboard */
var pd=document.createElement('div'); pd.id='crExPad'; pd.setAttribute('aria-label','Walking joystick. Drag to walk.'); pd.style.cssText='position:absolute;left:84px;bottom:74px;z-index:6;width:124px;height:124px;border-radius:50%;border:2px solid rgba(60,70,80,.45);background:rgba(255,255,255,.55);touch-action:none;user-select:none;-webkit-user-select:none';
var kn=document.createElement('i'); kn.style.cssText='position:absolute;left:50%;top:50%;width:52px;height:52px;margin:-26px 0 0 -26px;border-radius:50%;background:rgba(60,70,80,.6);pointer-events:none'; pd.appendChild(kn);
var jid=null;
function jset(e){ var r=pd.getBoundingClientRect(), dx=e.clientX-(r.left+r.width/2), dy=e.clientY-(r.top+r.height/2), m=Math.sqrt(dx*dx+dy*dy), mx=r.width/2-26; if(m>mx){dx*=mx/m; dy*=mx/m; m=mx;} kn.style.transform='translate('+dx+'px,'+dy+'px)'; if(!CRH)return; var k=CRH.keys; delete k.w; delete k.a; delete k.s; delete k.d; if(m>14){ if(dy<-14)k.w=1; if(dy>14)k.s=1; if(dx<-14)k.a=1; if(dx>14)k.d=1; crhLoop(); } }
function jend(){ jid=null; kn.style.transform=''; if(CRH){ delete CRH.keys.w; delete CRH.keys.a; delete CRH.keys.s; delete CRH.keys.d; } }
pd.addEventListener('pointerdown',function(e){ e.preventDefault(); jid=e.pointerId; try{pd.setPointerCapture(jid);}catch(x){} jset(e); });
pd.addEventListener('pointermove',function(e){ if(e.pointerId===jid){ e.preventDefault(); jset(e); } });
['pointerup','pointercancel','lostpointercapture'].forEach(function(n){ pd.addEventListener(n,function(e){ if(e.pointerId===jid||n==='lostpointercapture')jend(); }); });
sc.appendChild(pd); }
st.innerHTML=on?('Food '+Math.round(c.bars.f)+'Water '+Math.round(c.bars.w)+'Energy '+Math.round(c.bars.e)+''):'';
st.style.display=on?'':'none';
try{ var sl=$c('crSel'); if(sl)sl.classList.toggle('crbf',!!sl.querySelector('.crbuy1')); }catch(e){}
bt.textContent=crEx?'Renovate':'Load home'; bt.setAttribute('aria-pressed',String(!crEx)); bt.style.display=(c.tab==='home')?'':'none';
hn.textContent=((window.matchMedia&&matchMedia('(pointer:coarse)').matches)||touchUI)?'Drag the joystick to walk, or tap the floor. Tap an object to use it.':'Drag the joystick or use W A S D or the arrow keys to walk. Tap an object to use it.'; try{ var _w=sc.querySelector('#crExWork'); if(_w)_w.style.display=(on&&c.tab==='home')?'':'none'; }catch(e){} hn.style.display=(on&&!crHintDone)?'':'none'; try{ var _pd=sc.querySelector('#crExPad'); if(_pd){ var _t=(window.matchMedia&&matchMedia('(pointer:coarse)').matches)||touchUI; _pd.style.display=(on&&!CRH.pl)?'':'none'; } }catch(e){}
if(on&&!crHintDone){ crHintDone=true; setTimeout(function(){ try{ hn.style.display='none'; }catch(e){} },9000); }
}
(function(){
var _r2=crRender; crRender=function(f){ var _m0=crMsg; _r2.apply(this,arguments); try{ if(_m0&&crEx&&CRH&&crS()&&crS().tab==='home'){ crhSay(_m0); var _sp=$c('crSel'); if(_sp&&!_sp.hidden){ var _bn=document.createElement('div'); _bn.className='crbanner info'; _bn.setAttribute('role','status'); _bn.textContent=_m0; var _h3=_sp.querySelector('h3,.crsel-t,p'); _sp.insertBefore(_bn,_sp.children[1]||null); } } }catch(e){} try{crExApply(); if(CRH&&crOpen){ if(CRH.w){crhResize();} crhCam(); }}catch(e){try{console.warn('explore',e);}catch(_){}} };
var _pe=crhPlaceEnd; crhPlaceEnd=function(msg){ _pe.apply(this,arguments); if(CRH&&CRH.reEx){ CRH.reEx=false; crEx=true; try{crExApply(); crhResize(); crhCam(); crhSay(msg||''); crhLoop();}catch(e){} } };
var _ps=crhPlaceStart; crhPlaceStart=function(){ if(crEx){CRH.reEx=true; crEx=false; crExApply(); try{crhResize();}catch(e){} crhCam();} return _ps.apply(this,arguments); };
var _sp2=crSelPanel; crSelPanel=function(c){ var r=_sp2.apply(this,arguments); if(!c.sel||r.indexOf('Tap something in your flat')>=0)return crBuyFirstHTML(c); return r; };
})();
/* ---- Career: start-up (runs once, after everything above exists) ---- */
try{if(S.st.cr)S.st.cr=crClean(S.st.cr);}catch(e){}
try{crBind(); crInstallMenu(); crSyncMenu(); crInstallPicker();}catch(e){try{console.warn('Aura Life could not start',e);}catch(_){}}
/* after a refresh or an update, go back to Career if the Learner was in it (c.o is saved with the Learner) */
(function(){var tries=0; function back(){tries++; try{var c=S.st&&S.st.cr; if(!c){if(tries<8)setTimeout(back,1500); return;} if(!c.setup||!c.o||crOpen)return; if(!crEnabled()){return;} if(typeof modalOpen==='function'&&modalOpen()){if(tries<20)setTimeout(back,1500); return;} crEnter();}catch(e){}} setTimeout(back,2500);})();
setInterval(function(){try{crSyncMenu(); if(crOpen&&!crW&&crS()&&crS().setup&&crToday()!==crS().dKey){crRoll(); crRender();}}catch(e){}},5000);
/* ===================== end CAREER MODE ===================== */
if(window.__test)window.__test({evalIn:function(c){return eval(c);},etR:etR,etRec:etRec,etTopics:etTopics,GRIDGEN:GRIDGEN,gridCheck:gridCheck,etSample:etSample,SYL:SYL,SYLI:SYLI,ET:function(){return ET;},etStart:etStart,etBadges:etBadges,etTRender:etTRender,getTCLS:function(){return TCLS;},setTCLS:function(x){TCLS=x;},setTCloud:function(x){tCloud=x;},setTW:function(x){TWHO=x; tlRender();},ML:function(){return ML;},CN:function(){return CN;},cnSets:cnSets,CNIDX:CNIDX,setDb:function(d){dbh=d;uid='testuser1';},setTC:function(x){tCloud=x;renderTeach();},openTour:openTour,LMS:LMS,EXT:EXT,gb:function(x,y,z){return tv[tvi(x,y,z)]},sy:surfaceY,player:player,S:S,renderTopics:renderTopics,LEVELS:LEVELS,topicMap:topicMap,TOPICS:TOPICS,OBJT:OBJT,WPT:WPT,OBJ:OBJ,getCls:function(){return String(S.me&&S.me.cls)+'|'+typeof sess+'|'+sessLive(sess);},getU:function(){return S.day.u;},getSess:function(){return sess;},rec:function(q,ok){return recordSkill(q,ok);},freeLeft:freeLeft,classAct:classAct,setSess:function(d){sess=d; S.me.cls='P1A';},SCB:SCB,msKey:msKey,MSBUF:MSBUF,msSample:msSample,RECIPES:RECIPES,S:S,rollDay:rollDay,G1GEN:G1GEN,options:options,killZ:killZ,spawnZombie:spawnZombie,zc:function(){return zombies.length;},quadFig:quadFig,enSt:function(){return {energy:energy,tired:tired(),hearts:hearts,rev:REV,revdone:REVDONE};},setEn:function(v){energy=v; drawEnergy();},faint:faint,doBreak:doBreak,doPlace:doPlace,cqAns:function(){return cq&&cq.answer;},openLab:openLab,labStart:labStart,labCheck:labCheck,LABORD:LABORD,labState:function(){return LABST;},SC:function(){return SC;},scNext:scNext,startSci:startSci,openSci:openSci,SCB:SCB,GD:GD,MUG:MUG,muNext:muNext,startMusic:startMusic,openMusic:openMusic,muState:function(){return MU;},lobWall:lobWall,wallCheck:wallCheck,openE21:openE21,openThink:openThink,startThink:startThink,EPQ:EPQ,epNext:epNext,e21:e21,e21Total:e21Total,epState:function(){return EP;},openComputing:openComputing,startComputing:startComputing,CPQ:CPQ,cpNext:cpNext,GENS:GENS,cpState:function(){return CP;},openKitchen:openKitchen,startCook:startCook,COOK:COOK,ckBuild:ckBuild,ckState:function(){return CK;},pantryAdd:function(k,n){S.pan=S.pan||{}; S.pan[k]=(S.pan[k]||0)+n;},setTP:setTP,tpState:function(){return {TP:TP,dist:tpDist,vis:tpOuter&&tpOuter.visible,head:tpHead,amp:tpAmp};},maybeClass:maybeClass,openClass:openClass,dayCheck:dayCheck,openEnd:openEnd,openPrem:openPrem,weeklySummary:weeklySummary,mbLevelUI:mbLevelUI,streakDays:streakDays,setDb:function(d,u){dbh=d;uid=u;},bonusShow:bonusShow,bonusStart:bonusStart,bonState:function(){return {BON:BON,BONT:BONT,shown:bnShown,idle:bnIdle};},setIdle:function(v){bnIdle=v;},wpBuild:wpBuild,wpState:function(){return WPS;},analysisHtml:analysisHtml,guideOpen:guideOpen,guideNext:guideNext,guideClose:guideClose,guideAuto:guideAuto,gdOpenNow:function(){return gdOpen;},setBrT:function(v){S.brT=v;},mbSet:function(a){MBC=a;mbShow('');},mbCheck:mbCheck,mbNew:mbNew,mbNewAlg:mbNewAlg,mbNewArea:mbNewArea,mbC:function(){return MBC;},roomEditTest:function(x,y,z,v){world[wi(x,y,z)]=v; roomEdit(x,y,z,v);},cellAt:function(x,y,z){return world[wi(x,y,z)];},openLobby:openLobby,showLob:showLob,roomEnter:roomEnter,myScore:myScore,roomInfo:function(){return room&&{code:room.code,my:room.my.length,peers:Object.keys(room.peers).length};},setGT:function(m){S.gt=m; skyLast=-1;},skyInfo:function(){return {h:skyHour(),clear:renderer.getClearColor(new THREE.Color()).getHexString(),tx:document.getElementById('gclockTx').textContent,night:stars.visible};},refLink:refLink,openInvite:openInvite,setRefN:function(n){refN=n;renderInvite();},refCapture:refCapture,setInv:function(k,v){inv[BY[k]]=v;},getInv:function(k){return inv[BY[k]]||0;},setInv:function(k,v){inv[BY[k]]=v;},getInv:function(k){return inv[BY[k]]||0;},petsSync:petsSync,petsList:petsList,openPets:openPets,PETK:PETK,effTopic:effTopic,setSess:function(x){sess=x;sessCheck();},freeLeft:freeLeft,pmValid:pmValid,pmChk:pmChk,premium:premium,enterTour:enterTour,openStudio:openStudio,scNext:scNext,openExam:openExam,exQ:function(){return EXQ;},msState:function(){return MSST;},msSet:function(t){MSTAB=t;MSST=null;msRender();},openElements:openElements,elUnlock:elUnlock,wset:function(x,y,z,k){world[wi(x,y,z)]=BY[k];dirty=true;},wget:function(x,y,z){return getBlock(x,y,z);},byid:function(k){return BY[k];},rainStart:rainStart,rainStop:rainStop,waterPlants:waterPlants,wxState:function(){return wx;},openStar:openStar,conList:conList,modalOpen:modalOpen,wpBuild:wpBuild,tankFig:tankFig,setView:function(x,y,z,yw,pt){player.x=x;player.y=y;player.z=z;yaw=yw;pitch=pt;dirty=true;},openTour:openTour,LMS:LMS,tourState:function(){return {near:tourNear&&tourNear.id,st:S.st.sg,n:S.st.sgn,vis:!!visiting};},P:function(){return player;},view:function(a,b){yaw=a;pitch=b;},brkOpen:function(){return !!brk;},startBreak:startBreak,sanitiseAv:sanitiseAv,mkAvatar:mkAvatar,openAv:openAv,mkZombie:mkZombie,zTier:zTier,FORMS:FORMS,startDuel:startDuel,hearts:function(){return hearts;},cq:function(){return cq;},save:save,setVisitLeft:function(v){visitLeft=v;},LEVELS:LEVELS,options:options,strike:strike,destroy:destroy,world:function(){return world;},tv:function(){return tv;},inv:function(){return inv;},S:S,genTerrain:genTerrain,surfaceY:surfaceY,collides:collides,getBlock:getBlock,RECIPES:RECIPES,BY:BY,setTerr:function(t){terr=t;},timeLimit:timeLimit,expandLand:expandLand,LANDget:function(){return LAND;},loseFromBag:loseFromBag,launchBoat:launchBoat,spawnZombie:spawnZombie,zombies:function(){return zombies;},boats:function(){return boats;},boarded:function(){return boarded;},player:player,setCam:function(a,b){yaw=a;pitch=b;},sail:sail,leaveBoat:leaveBoat,banishAll:banishAll,redeem:redeem});
/* ---- everyone picks their level once: Primary year, Secondary level and band, or Post-Sec ---- */
(function(){
var seg=document.getElementById('gradeSeg'), md=document.getElementById('gradeModal'), oth=document.getElementById('gradeOther'); if(!seg||!md)return; var tries=0, shown=false, pBd='', pGl=0;
var h2=md.querySelector('h2'), desc=md.querySelector('.lvldesc');
function btn(t,fn,on){ var b=document.createElement('button'); b.type='button'; b.className='btn'+(on?' primary':''); b.textContent=t; b.addEventListener('click',fn); seg.appendChild(b); return b; }
function lbl(t){ var d=document.createElement('div'); d.className='label'; d.style.cssText='width:100%;margin:8px 0 2px'; d.textContent=t; seg.appendChild(d); }
function done(){ closeModals(); try{showQuestion();}catch(e){} }
function stepMain(){
h2.textContent='Which year are you in?'; desc.textContent='Pick your school level so the game gives you the right questions. You can change it later in the game settings.'; seg.innerHTML=''; oth.hidden=false;
for(var n=1;n<=6;n++)(function(n){ btn('P'+n,function(){ try{localStorage.setItem('mbLv','P');}catch(e){} S.gr=n; S.level=PSTART[n]; try{updateLevels();}catch(e){} lastKey=''; try{save();}catch(e){} done(); }); })(n);
lbl('Not in Primary school?'); btn('Secondary',stepSec); btn('Post-Sec (ITE, polytechnic, JC, adult)',function(){ try{localStorage.setItem('mbLv','Q'); if(!GL){GL=3; localStorage.setItem('mbGl','3');}}catch(e){} done(); });
}
function stepSec(){
h2.textContent='Secondary: choose your level'; desc.textContent='Choose your level and your subject band. Ask your teacher if you are not sure of your band.'; seg.innerHTML=''; oth.hidden=true;
lbl('My level'); [['12','Sec 1-2'],['34','Sec 3-4']].forEach(function(o){ var b=btn(o[1],function(){pBd=o[0]; [].forEach.call(seg.querySelectorAll('[data-g=b]'),function(x){x.classList.remove('primary');}); b.classList.add('primary'); go.disabled=!(pBd&&pGl);}); b.dataset.g='b'; });
lbl('My subject band'); [[1,'G1'],[2,'G2'],[3,'G3']].forEach(function(o){ var b=btn(o[1],function(){pGl=o[0]; [].forEach.call(seg.querySelectorAll('[data-g=g]'),function(x){x.classList.remove('primary');}); b.classList.add('primary'); go.disabled=!(pBd&&pGl);}); b.dataset.g='g'; });
var go=btn('Start',function(){ try{localStorage.setItem('mbLv','S'); localStorage.setItem('mbBand',pBd); localStorage.setItem('mbGl',String(pGl));}catch(e){} GL=pGl; done(); }); go.disabled=true; go.style.marginTop='10px';
var bk=btn('Back',stepMain);
}
oth.hidden=false; oth.style.display='none'; stepMain(); oth.hidden=true;
function check(){
tries++; if(shown)return;
if(window.__test&&!window.__gradeTest)return;
var lv='P'; try{lv=lrnLevel();}catch(e){}
var need=(lv==='P'&&!(S.gr|0))||(lv==='S'&&!GL); if(!need)return;
if(!S.me||!S.me.name||modalOpen()){ if(tries<40)setTimeout(check,2500); return; }
shown=true; stepMain(); md.hidden=false; try{document.getElementById('gradePanel').focus({preventScroll:true});}catch(e){}
}
window.gradeCheck=check; setTimeout(check,2500);
})();
/* ---- fallback connector: the game brings its own link to the server if the hosting page did not add one ---- */
try{
(function () {
'use strict';
if (window.__ab && window.__ab.use) return; /* the hosting page already provided it */
var LS = null; try { LS = window.localStorage; } catch (e) {}
function get(k) { try { return LS && LS.getItem(k); } catch (e) { return null; } }
function set(k, v) { try { LS && LS.setItem(k, v); } catch (e) {} }
// Teacher key: open the game once with /#admin=YOURKEY . It is stored on that device only.
var m = /[#&]admin=([^&]+)/.exec(location.hash || '');
if (m) { set('mb.adminKey', decodeURIComponent(m[1])); try { history.replaceState(null, '', location.pathname + location.search); } catch (e) {} }
if (/[#&]admin=off/.test(location.hash || '')) { try { LS.removeItem('mb.adminKey'); } catch (e) {} }
var uid = get('mb.uid');
if (!uid) {
var a = new Uint8Array(15); (window.crypto || {}).getRandomValues ? crypto.getRandomValues(a) : a.forEach(function (_, i) { a[i] = Math.random() * 256; });
uid = 'u' + Array.prototype.map.call(a, function (b) { return ('0' + b.toString(16)).slice(-2); }).join('');
set('mb.uid', uid);
}
function headers(extra) {
var h = { 'x-user-id': uid }; var k = get('mb.adminKey'); if (k) h['x-admin-key'] = k; var ti = get('mb.tid'), tk = get('mb.tk'); if (ti && tk) { h['x-teacher-id'] = ti; h['x-teacher-key'] = tk; }
for (var x in extra) h[x] = extra[x]; return h;
}
function call(method, path, body) {
return fetch(path, { method: method, headers: headers(body !== undefined ? { 'content-type': 'application/json' } : {}), body: body !== undefined ? JSON.stringify(body) : undefined, cache: 'no-store' })
.then(function (r) { return r.json().then(function (j) { if (!r.ok) throw new Error(j && j.error || ('HTTP ' + r.status)); return j; }); });
}
function snap(j) { return { exists: !!j.exists, data: function () { return j.data; }, id: j.id }; }
function docRef(path) {
var p = path.split('/'); var url = '/api/doc/' + encodeURIComponent(p[0]) + '/' + encodeURIComponent(p.slice(1).join('/'));
return {
get: function () { return call('GET', url).then(snap); },
set: function (d) { return call('PUT', url, d); },
update: function (d) { return call('GET', url).then(function (j) { var cur = j.exists ? j.data : {}; for (var k in d) cur[k] = d[k]; return call('PUT', url, cur); }); },
delete: function () { return call('DELETE', url); }
};
}
function colRef(name, q) {
return {
where: function (f, op, v) { if (op !== '==') throw new Error('Only == is supported'); return colRef(name, { f: f, v: v }); },
get: function () {
var u = '/api/col/' + encodeURIComponent(name) + (q ? '?field=' + encodeURIComponent(q.f) + '&value=' + encodeURIComponent(q.v) : '');
return call('GET', u).then(function (j) { return { docs: j.docs.map(function (d) { return { id: d.id, exists: true, data: function () { return d.data; } }; }) }; });
}
};
}
var db = { doc: docRef, collection: function (n) { return colRef(n); } };
var user = { id: function () { return Promise.resolve(uid); } };
var downloads = {
save: function (o) {
return new Promise(function (res, rej) {
try {
var blob = new Blob([o.data], { type: 'text/csv;charset=utf-8' }), a = document.createElement('a');
a.href = URL.createObjectURL(blob); a.download = o.filename || 'download.csv'; document.body.appendChild(a); a.click();
setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 500); res(true);
} catch (e) { rej(e); }
});
}
};
window.__ab = window.__ab || {};
window.__ab.use = function (name) {
if (name === 'db') return Promise.resolve(db);
if (name === 'user') return Promise.resolve(user);
if (name === 'downloads') return Promise.resolve(downloads);
return Promise.resolve(null);
};
})();
}catch(e){}
initCloud(); if(!S.me.name&&!(window.__test&&S.me.skip))openWho(); else {setTimeout(maybeClass,300); setTimeout(dayCheck,900);}
/* ---------- mini apps: ?app=cn (Chinese), ml (Malay), sg (Visit Singapore) open one part of the game, with a way into the full game ---------- */
(function(){
var app=''; try{app=(new URLSearchParams(location.search).get('app')||'').toLowerCase();}catch(e){}
var MAP={cn:['cnBtn','cnBack','Chinese characters'],ml:['mlBtn','mlBack','Bahasa Melayu'],sg:['tourBtn',null,'Visit Singapore']};
if(!MAP[app])return; var m=MAP[app]; document.body.classList.add('miniapp');
var full=location.pathname+(location.search.replace(/([?&])app=[^&]*&?/,'$1').replace(/[?&]$/,''));
var bar=document.createElement('a'); bar.id='miniBar'; bar.href=full; bar.textContent='Open the full Aura Builders (13 subjects)'; bar.setAttribute('aria-label','Open the full Aura Builders game');
document.body.appendChild(bar);
var st=document.createElement('style'); st.textContent='#miniBar{display:none} body.miniapp #miniBar{display:block;position:fixed;left:50%;transform:translateX(-50%);bottom:max(10px,env(safe-area-inset-bottom,0px));z-index:9999;padding:9px 16px;border-radius:10px;background:#e0a22f;color:#1b1b1b;font-weight:800;text-decoration:none;box-shadow:0 3px 12px rgba(0,0,0,.4);font-size:.9rem;white-space:nowrap}'; document.head.appendChild(st);
if(m[1]){ var bk=document.getElementById(m[1]); if(bk){ bk.textContent='Open the full game'; bk.addEventListener('click',function(e){ e.stopImmediatePropagation(); e.preventDefault(); location.href=full; },true); } }
function go(){ try{ var sm=document.getElementById('startModal'); if(sm)sm.hidden=true; var wm=document.getElementById('whoModal'); if(wm&&!wm.hidden&&S.me.name)wm.hidden=true; var b=document.getElementById(m[0]); if(b)b.click(); }catch(e){} }
var tries=0, iv=setInterval(function(){ tries++; var open=document.querySelector('.modal:not([hidden])'); var wm=document.getElementById('whoModal'); if(wm&&!wm.hidden){ if(tries>40)clearInterval(iv); return; } if(open&&open.id!=='startModal'&&open.id!=='whoModal'){ clearInterval(iv); return; } if(tries>3){ go(); } if(tries>12)clearInterval(iv); },700);
})();
/* ---------- stay on the current screen after a reload (Career, Chinese, Malay) ---------- */
(function(){
function put(v){try{if(v)localStorage.setItem('mbScr',v); else localStorage.removeItem('mbScr');}catch(e){}}
var _en=crEnter; crEnter=function(){var r=_en.apply(this,arguments); put('career'); return r;};
var _lv=crLeave; crLeave=function(){var r=_lv.apply(this,arguments); put(''); return r;};
var P=[['cn','cnBack','cnBtn'],['ml','mlBack','mlBtn']];
function cur(){ if(document.body.classList.contains('cr-on'))return 'career'; for(var i=0;i30){clearInterval(iv); restored=true; return;}
var who=document.getElementById('whoModal'); if(who&&!who.hidden)return;
var sm=document.getElementById('startModal'); if(sm&&!sm.hidden){sm.hidden=true;}
clearInterval(iv);
if(want==='career'){ if(typeof crEnabled==='function'&&crEnabled()) crEnter(); }
else { for(var i=0;i30*86400000)localStorage.removeItem('mbRef');
}catch(e){}
})();
mbLevelUI();
if(!THREE_OK){
document.getElementById('pTitle').textContent='3D could not load';
document.getElementById('pText').textContent='The 3D library did not load. Check your internet connection and reload the page.';
document.getElementById('pKeys').hidden=true; promptEl.hidden=false;
}else{
try{
setupScene();
savedBoats.forEach(function(q){boats.push(mkBoat(q[0],q[1],0));});
if(window.ResizeObserver)new ResizeObserver(resize).observe(view); else window.addEventListener('resize',resize);
try{window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change',applyTheme);}catch(_){}
new MutationObserver(applyTheme).observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});
requestAnimationFrame(function(t){lastT=t; frame(t);});
}catch(err){
document.getElementById('pTitle').textContent='3D is not available';
document.getElementById('pText').textContent='This device or browser cannot show 3D graphics (WebGL). The flat version of Aura Builders still works.';
document.getElementById('pKeys').hidden=true; promptEl.hidden=false;
}
}
/* ===== COOKSTEP: kitchen mini-step for the Food path =====
cookStep(opts, done)
opts.dish one of 'fried_rice','fried_noodles','laksa','kaya_toast','fishball_soup','teh_tarik','satay' (random if omitted)
opts.burnMs ms before the gentle 'getting dark' hint (default 12000; never with noTimers / reduceMotion)
opts.noTimers true = no waiting at all (defaults to the Access "No timers" setting)
opts.reduceMotion true = no animation (defaults to the Access / device setting)
opts.onClose optional, called after the screen is removed
done({stars:1..3, dish:id, mistakes:n, safe:bool}) stars 0 + cancelled:true if the Learner leaves with Back / Escape.
It never gives money, blocks or any reward by itself: the caller decides what (if anything) to do with the result.
Images are decoded only when the step opens (see ckAssets). */
var cookStep=(function(){
var CK_IMG=null, CK_BUSY=false;
function ckAssets(){ return {"ing_rice":"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","ing_chicken":"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","ing_eggs":"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","ing_noodles":"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","ing_coconut_milk":"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","ing_flour":"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","ing_bok_choy":"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","ing_chilli":"UklGRvQPAABXRUJQVlA4WAoAAAAQAAAAWAAAXwAAQUxQSGUJAAABj8cgkqQ49OBfNeSPgIjI4U5ezLGQY84y3chKHuSYPyUYNpIUhLsDPovaf8G8PUT0fwJGP227kWzeJtKU3KCSXBdQsv1XcHEX4BHgeQriZAOpJN7vF1i5pGSRbrx6346hoG0bKeYPe7sHQkRMQFPpK+WCRS5KnezDHlHoiKain761bVveNtu2bT+u65LMdhKnzMycDt3MzMzMzMzM9z0FnYB7qMzM3DCzGQI1SrrOfSBxLMkzEBET4EmSbdmWJElCesyq3NNi/hOt32rcc+97zCOIiAlgxcoIs+xInLZ2z2wps2JFxwDWspTydT//8++/dKm1YsQ5n/ni+QOgD3zON//5gavf+b3vXGGtEHHu9/7x8TMvGcQ9Ef1f+MEl9TIv3vzJPq8M0ffOP3/6st4bX7m2WA/g8utcZkWWikt6WZnB+b/53bqK1nTt3+6puODCyDKpkir5ylDGGx74dDfh+hM36QFx3urChqSK2q+IEP1/23cuFeVxyx88VDrjFblDoahOL7SbZENH/tEfdFFEKmqbDystlR3ZU8lAtvL1lXaDOHnwlN6TPnHRQqEIjW6viSWtfVOVJFvkI7uINjvzqkuvPrm76O6XBCo3j3KC0dhdFhZYxYNV2lgQr//ktQMVUqQkJMfYxgm8lI4eqQSAs0MvSW4bmbWv+s6l87lDScIi2Lt9JgCkCKWF3s4yQM53jZK1D8W5b337lVbmwGAEMXUEDIFTcq1zEHNsPF9VSZtG5fQ3vf6Ktc6zJJBlkCp9FUBK9J93w1vXDciykRd2kDfaQ+Lqf75+ECogLI5VwNWXQMBZH/zFn//81TedIiGRKiMvk2hLQ72v78d+vcrtsiF7+k4ivebDN57hMq+XcBn7d+L2gJHx4OfPcloDsjLf9/9x3vS9K5Xknz/WAmLfULSH49DTk/AXaoI1oDqzNz235muvH5ACHYaJRHua3eM9crWyzFVg1R/sHVx3dlWgvcxV5cmdVlto68OlwEtzbMAA1XenMgxyaI1s8fqvE2oD1W99LLeCHNcchUQiCyWB1lpA59deVUbrQtufXhQIMs+FMwJzokN5ree755bRKmW1Z4fDGGIZ1gkIgWXcjayiXPf5gdSqYGbt6lISOSf5agfIkPdvpMVS1rk2CTiB37iTANyxSa3iNHeAhNBE6kGXXRIiC7myu2hR8JY+kowgTHkeDJKWZVQWw5XWSP3/6SxBYgkL6xODuQog5eOntCZ4w0syAmQn1zQtt4ckIFTmh9e0JOj8y3ASxz8dc2A9ug7m2Lmeloh1D456qfsgH66dCh1TurcVYmy2Mzm2u3ZhPcrtJsBy0dOK8ObxSDbolLosT2d0kJGFU38L7MXJowlttLtzHrfcNmHZxEALguzkfqCQMffr0YcGYxepBcwXJxfCXCd64IMunQRg1aeap0bt5G5nPJzH+XCyy9jglB3c3YL5NNjhkG5+b5CrBSRvf7FpLeqiwoHsjziOmQymsfNQs8RkvkYg0h9DlTEweyA1L84pG2HIH5pYAuzs8C43SfQPdhDGsD+klIsoq/t30eTQu1c1JMCD/Y4xc8wCI68/2qTglJuqxrJI6ndEsosKUxaHnqplzVH22fXOhBAz2742nw64zDdtwU3Juez2/c44bv7AEopmO6s/PqqmSMWP9h4QIYklW7O6zG0EKRt5vBHNKSunVEnoMmT2hZVjB0GWek+aNc2UK5f2kalXFYLSF7QQJEedXJ+PZpjamWcmxauYCG3ZZnsWKo+VOrsmaO6iBmsKkddLcl9ojyjqwf5Xpv7GXFPETE+eAlQkD0e+X6k4FibVDKFTTs9yjl/FDhuHvpE0l1VjzyxNFB2nX9Qtg3KbD9tndVF5rWx0lCYq8rddsUphzB5kqO3gu7mu97927EHLsxr7pv75fjHPS5GvBxKhqOz95ZuJ8X21VVJPxH7HKEhQLYa8vmDqh5N+/ijzYchtz9ZggHB2aMoXxfwRcr+kvjGd1qMGhVDq966yL2S1maORC+bhGGS+OddhoMaZXgwfS8zUS6Ei7FDI8x4JSRRU5g/aH0UGrjjj+FOzq/PuJqMTQzOnKA/WWHaIaidB6HhZjDGbumvLeVi8h5T6Rse0LLvwX/eeV90ACZFsg+0HqQ4xIGOUd57T4WWJyBb9/9273AbJKZGcbIK4Mx/6mESqDfYjlp1rpjFvvE7JEQhjjFk6j5fGsalRllWa2Dughs227q4WZaNRWzyhxwtjQC5O78PLEd1ru1E5Qx0kGdkwt+Pl3FreLF1AJM8WLN+NkzpNSu+Z+5DkFLHv4d2Z5SXKLqFgNqI6TxNjR0+USjbbTgunOpXJR+490OFS2JfEMMW8sZMHBokmnPtYpZFwmuevlepYfObmB8fO7nUYdHExmhFNRZZ3ymrCmf+7drGqAKMlTGooP/LS/Rv3HDz4t839luRssOTp4USimelXF6Y8kIUFApfOs5HHbn10w9zwzoV31c011QECZ96537gpoz+8tp5nBDjsZJcqYm7zI0+P9aWxkQm1yOyEsbCsjN7eBZq98z3v7AIMlM5yFQXTmx9/aiInsvLobJ5d5HZRw7ZVlgk1K6268pxVRWDnRVbWZiZ3PPvsiyMF9c5K1lMSXoXqsMLGpJKO+TlE8+sRNTeK8tDU8PDEzPrnx2eBGF+VN/qSleNgrjNbSqR6qtFKkUYPbH2K5EZZESVA4J7BvpDgUrp0iHqXjdm9I3Vang7tr7OkhOCVp/ZUMBgWWzBm239qc9t2LuCW5WywCSlf/es/e/0opYoEIBBOJeWR/SNg2jxFJ9UfPzZMMpmlBeT5kUlEe6vk1DWq6908zL0hoDgyB0F7Cy6+tLNh+4oUBMo1sftlEu1+yUXn9cv4zS5dbo2NWJjYW9Lukd1w4RndybYsmIe2kKOYHFkEt5n8iq+srgonH4ucxwCGo5PTi4g2l/t/8LGOIkEybKYuk1FZUhl99qVFRLsH1zx6oEYkcVyBfIzDJEceR59cv38e0/YZr9u5u0ISBgQgUpAwVDk0snHTbCBW5Pt27CsRlgEdh1IQRX3Hk9umcwCvBHH1A/sazo8jgZxQuMLki0+OdbCCpZ5vbewvGxkps0BGWaSFXU9unQOEVwzB4M/WndGVwEEQWSMtbN+5ZaIHMiezkhV9h1f3n31ST0dAo17bv3n9wgCQuc5KF89vORLkxrWjM1P7Z2ugSA1WIgBWUDggaAYAANAeAJ0BKlkAYAA+kTyaSCWjoqEzlRyQsBIJbADMkctYXf1+M80+u/4P8Z7scY+2r/qPV34rXTd8w363etN6Qf8H6gH+R6kv0APLX/bT4PP3C/db2j///nKtovHzIAzBNNg0MvG8bMuhCPoWGjIF7s7uiMmf0cdza7Pd8zAzpOFF94TI9KMgBv18hLpV2LZlK+SQuvv/yG0EoL8wizMcLLDqwd42PikBxK3eQSatoMm24ApvRlKNcAbkjUXQjY2dq7KoDo2lgWNrOXzZk9jOv8tf0q2LFj24sDn243pn/snCUexpHioueZO/wvrr3LCeT8/80ksPBKmQB06cbF0IAAD+/bToVfn9wDnYyR4uTHI3bDOLQyrDlIWZIWrAGuC1PWvMI7LLkQkIg+2RL9rx9PHP0I0i8cllsno6/w80IYOlW1M4znoibcRjqGSmYYmfcL1325XzVNgsJYBDF0DhwHEWIfG7G8Uq9IzzNXHogDdUfX6VWqZKnL9uOeEDkfvi3AOnyzcx/tFGikQh/wD7j/214oGqWbb3RRfDsmYfHb2yctcYAsr9D3ZpCgbo0KD1pCSP0chU3ICK32Nh0IQ20rRCsn6dLQRE13M0wbpoV/jNRCaaajUsBH8f+AzclhB5/mLh2HRH2PNhsYUzGlHXS3AnzwLE51+UnayJrWRX3Vc/3SzO28ps7/VFUkBdxHHkUwHPkqBaQ18DjJGPCaCvkpLwuCkPQCq6SDygHodvKzCxwpNtq90YB+/12U8ZxwWwAMIkHBA1McS21k/Q8/2P+Ne0YlmvNbb5VzgCE2Ne5tN8Azot+v3+dDi8NLxKBbY971ceM0U1RjgT7Z0HaiHMUTUkeOutKhc/Vyo5YNPk8xE83ztZseHjB5+U48sbRVIF+JoD24Ths+k0CPyuiRKK35RZVs3PPb/8B5JIS0HgiUiNbJXOmUnuNQtgNi9svMDQhamHm0faxH6irJCpwR3rDLDFZpaArbZIisRvdQi1kymIXJxYYzOR8wLFHbjQBU93pYr7p6qT3F0fSsCfWgJ2sp3obD12p1H+5t5HEIQZUBv5mGPvucDQOMv4P5UNPvez8vc4BDoI47ebFYk5GFPHxT/KVrFOa7QW3xbbxdQvYwaB1HHjYUCL23TzqGkQrXUFt60OmfAu1bUwrS6WSWN7Tvm2eHHv6kXj8Y1xKaL0o2KvjOwlq3GYHfpzSqBKYXdPpRqdmHeFR3igVufGmv3HjtaR34SRcJV94BjbbfMvGs8vbCbydHiE3xpO4sZ8W9wSJI5BPnGpIKKUgvvcCyeK9HcUM/N1EFUpFR4xw8pH9fJJaSvID5OfE+nWD92GJ4Z0XdrsSQY0f3gtHRsCmA1+ez3y61hQdgrOdXkc3hnQQjvCmfU9FSa77N0pcoMhxo/Zw/siaX6qeERPi+plZZs43WB2jujqxibV6y8iz7rlcp9eJdF0xWnDt8kfR2q4JFJnq4TNdyjkAmjBqbBQU/QxGWEq2RU9ko5GBsUIjYef3BZGWAqCXavYsh89barF6oTV+868niDgsbL/3PE2bIIQ66A2f7AUHqnxGAswqYKhL0NzV/oy1dfj/fM/XGs/VqCUrasTeAbQ8FBbc3lmwvFXVBN50OLYJNKu6elsZPHvG2NEhyxfzA5Xcl6U/1K4Wy47/wniVlVrZVx8oEIdp51XbbIuZ0uohQ9SdmU0P9ooCp6n/rIWzf8wSH8r85Wm36Qj0/8P51lMZ7TXF0svU2NMQO2XI+JXridVOg224aFdiEI+84TTQziLjSPQ35lr85cz82EnXCIsuXkbxK1ir8YI5/cC9W7Ac1ivnMud2wOHUwKUt3RUHAkMWgFnX/dsXkIQO+YoM/06dH7fd1z0c6wPzNjy44ITqMqEcQQxQr2GfVB2v0QB7ncZyvbU8pvAIRLfRrwAKO99noTKHnBJq1D7cgEi3JD6j0f3p47UG/n0Decg15o3BoNyogu8apvM7tjHnQptlVOwXOE8vefcx+Gf3sOvNHdlXmxaM+PmSrfrLXL0mV13JMYdwCEj+sqCX4VD80eGg3YVOaxJJT7u5QIY2479Xegw4VwgBU9vYf8CwyT17baQ0OlcMOfSf1xhn7GK5AwZX+6pfuGsPFXMHLQABTMpbnwg1oaDUavYSqzU0ZlSqVEhL1xSeBOvNuyqrkf72iA8gCqIAAAA","ing_bread":"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","ing_beef":"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","ing_ice":"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","ing_red_beans":"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","ing_milk":"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","ing_fish":"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","ing_potato":"UklGRswOAABXRUJQVlA4WAoAAAAQAAAAXQAAXwAAQUxQSJEIAAABx8agbSNJ5+zyJ/3dHYOIyMefrMhQucck5DpMyRpkm75kDSo/IiR5za+Ew7ZtA8l2kiL63v4L94eI6P8ESJJtd3dLqnr0BvD2Phfs3UjUBbx7JZgGYp9OZp54N5ngJKnsbxrsqUr38cW8bVsJUPXnHH9fFQNB27Zx+MPe9lOIiAnwXV3NtxQd6H7rrSfrgqCyZ/e8bds5Sdq2j2nbz/MXSJZtXbbtqa2Zr2t6TX1d0+uaXiPbtu2rqrps267Q73fsg8iKiozoiJgA35IkWZIk2RbSXJ7mbf7/R+/8oGrmFvUFETEBjKCSAy16zrMjiKrL/YPHbrZNUZlHwxRoxabHP/spi437O9dPXDy8/USPImL8irK57fEveuKyetmTAUzOvnf0r/863KGIGC/las2rX7xucVkpJ2EwVGHl8uyf/37qCnmsBE9590uSI2UBWKbfDtfj/NE//uZejFFm0Tvf9PgOZJAZ0Mg2hTp//UF2GpfE6k9ub/aSEEM0RKTGlSevJI1HZvbTex9RErIGsIzM3FXUUmphjYHonitzTn5fu1gRVW9qCWjkKurtewRt/WbBACF59nFrqUaOdRvlsH1u+mWIYs1jF6ORStxasagE9NSyV4CRQVK1dGmJRkpfP5Er29rDwn7otzCu2nvJowTfaecKm/vkvl/AjqCxa2nKoyOW7WyEAXaxF+2VBSDy3VdTG6VXXklhbL5engOH+6P47OKUR8bpmf+xlD/sBgGElsR9jUpiw9Z/xzz2xXVOAzRmKntExJatEJm/7LDBUa3yDY0ILFmiRqs/WfplU030zqHRME1FyB9GgLFtSMdaaSQkpouVyvN+WrFbEMiTBy/Lo4DJCTbP+XDUZc6YuHuWUc2IeqFflmdjcKIoTpRpFFKKDPJjn12zumuehOcvyT3affvlbFprnnebE42VWH8kBY21yzZU0icWzI5NmoSxmG8pljzluU9ZsyD8YXPGiixJUhTzpMTEU1/xsrVyQJ/dyzKoAq5NzYsSPPFDz3/sZAelxP5oc51zh3vzkWDZ277xp6lamSWg7xpCqLQAvjMPicaLPvO3/2wvkJhz+6rBHlgcUswDU5sfN5ULkcd+61jOIKrc71wBDyepmpkFo7Dbh5fHBWWkqGrn9iOG3D3XAkmGf9nRLnvzPlFKiq09JwhJQ5CnbiFhgbTIWdgXJGchiu6NdZfbhpQeRl74nEKAmXPRMbPRvigKFlLkN505f+nsk2YgaSC58dR1CAl0JCRIYjPb9DDJmcqZ1c+F+z//3LtWQhqoO7OqdJ8YsESSEJE8tweFoGdTP3hwz+dfNUF6kHsLV2QxeCMOKUJB61J6uIoMjl07d+753fsWkubKNJ600EkPVa6Ve9Bc85gfMyhRVfnoLtwnrteXVyLXgi6zF+Wv4wKmcj47jfriyK0G4nUPkiqRhD6K9WRDcfVKMnD/WJVsPQzyfiWVQntT3V4OMFi3TrYS6OL5qRADll+LhqLUm5/X9Au7cf004Th7uxnCL3yYcpZyTx9gfYQcXLmKfGYyVczr1JwzZojUF3fLtvddUr0zW2peCg1Vqm6fG7AwZfPYb7oLnpE8PzZyzjBH3xUBefLctfUrI81PdKuyOv6BoWjeObV4OgToOxLMYN4P+yKQc3U8kMw8hsqSUUdP+V4oZu7mxBC7LWeWDrUde2LfDBCNeiLQQ90LssLRfJg+WCAgCfCQ7kklSvrp24AlZIvhdzl3yD9aiFyZYXaZ3Iu1lh1d9rRfppBVVbLAXyxDY4OFyM9hT2Uway4hQHt3ba6pjnt06wnzspwp584ny9iXEW0zNHk79qZuc+bcXDxbOhh4tObL7OiyzIcLuUfrRDsx6MrZjuiNkH9iN9b795laNUiDEmM5e/Pc1E9jlx7o6P8lTJfNPcPyJj9m+6k553FSxUqBbi/brHkM2w+vp9nm/mAIHBtmKiTqISRLl2uv1puk3JdnUwbdFSt7WYU3MTRnR96vF38ZzuX0Vhta7jloD/e9Wp57Mb0xkW9A9CJJyr3kMdSt9ubrSZgBLo5gbm7f1cnB2uXcYUWXz3sKM3fbKg8A6eZfd2lslGtKgnZUfaOHl10U+fL/AKfOvvp//pd7l3PO1PHx3qw9rP8Xror/H6bfPG9zCwwk9CDNAVhDGfO2PAsBeH3VnSO5vrQTIEkDyHqAAPsl9IoES/SrrHFNcwC1TruWAISsPniQEX+aKGcCW4KifbiVH2CXra4wCSFAyCBwOMti24xYS72jg5FkqVxVv5YYUOWNTtREyPQ7AbKjyFWIL+c+WK4JTBia7dBAVK19R240s2SCfkkFXDnyuIXVUB4LpQskIXvRtcvZg2DY/+0vX1WzaBZF0Qe922cO/v38R55e1b6ZGWOYbdHvIl+9FDyk4c7Bo2fOHDp/+fKlrCgvnbpw5tQlil0q/UmK+VmEr/783/fwQ1BsYNFELSd3r90HVPDYX+595AF+15wJAQZspXbRvsaQJRFm7sJOUtLr/rq7MsbWi5k6sGEgwphU2zad8pD6JRCYoD+lyY/vY27bLklzHQQgMKm+bHkEI1zw7F8WAWbQ/DhsbONcX1hjtJXyh0tM/7qhW1ISAkcVZWLUM084lUr3PS55zJw2JbreRaOmzC/BtrGOYJj8S2DkyO12hRn5rN/+A1cmzH2LqZQS2Lrw53uIMcxPeuLyHHICATZggQELm8jF//9cx4xnffXyJbOKRBIapF8hO03c+vEpxrl3Z3ZdvVQSYNnMKStc1XTvkd/X0Tj5zo3GpqUiAGxhGQWumo329j/eAsU4JbmzYNn0ZM1RlUnIcpSJ2uSFvbvuQa7MWFvJ96/dmplqFrlTCZli0r3r//mnoYge4y+3Ll0/d+7K7VWzJJFuHrh45VwDEl0eFZVcXTtxYMfjN802KW8e6TQzFFGZ4QIAVlA4IBQGAABwHQCdASpeAGAAPp1Cmkmlo6IhKdXdwLATiWwNwOU9UebUhZFG7VaQWBPgpbfPzAecB6MvOZ6jr0AOkp/dP0xsF7bPgBGn4yerUZ/9ALpT/sd7MbiMTTI7FLMfA3TiivZBw31NVs1UeeYERxX3z2hzkpImzydErXk6HN2EBAQfuvbE9dKUYnURGncIDVF9/H/Dk/WPSkYP81xQBu8yEybPJq1EWqLINaNlcPgaXK65cXvn1mCXckGOYcxwbwNCPihIE6oSwNZyj5lLgdul3t1Lp9SPBWjiqOetpGueZ+LEg924lhsniTXsVwmFdkCIAteU2XLAAP79Nmuv8rwxy79dxt4mEP/cCxm6Ap//Nxe1iNIS+lvDDwtfKmur75tDXGHIUq/u79MSoRX+keveFcLnXdOrDGg19P3zcjebQPiV46Hnr1mPzqCa6zZRZnsK0tQrAblCP5su5yo5LSbBRY/Ppf6RD89uu/9JBEiWIeEGePiRo8Sz2vxxa+imH3/y/mt//dtf/sf3/+k3MKtQtUVPzq8u1lW+UBJ/nfTs9WQ9oFAAonFw7KJPCGO3CH2CqoJ2BMA3u7usYDf2Rp/55UxXAi/++1BTeIvqNfGw6xRczM+ktPk53itP8XG+VhQ/vS/y/qd3UiRJWuvL4Dys1hqlzdk81OjQenuxSJdozsFbrD5Ol7rfwdgmeG6zVDZVwVKJz8/8wQqLHeqJ0+X0dZWKED1SifFVoDTHDmXCuVqU2stTfeHqXvNX6R/s9Jar2DxF0+95XGlPN9wfNT3XrSFt5/iXGJ37W1A1xKyeRNsFyyKiCOpntG6eOadKSnk78K41M930h3Ghr4w9MEHNhFHcltCaF7/yMhEV8t4I6G92nbbZalGBtsYIXb9/HyuePFAVJFt8Bq8Lw7q6SsjcC7mI90nJ51X6QKJg/UHxYyde/x/x2uOiC2f7dU+/bfc7KuIjLjo24t5vMGRQa6o3XIAGOKpt/wY5vtvHaaWRKDM1kTHdm+YGKosIar3L45Ml6LGJy4r2dQKUqyN/CnhWy7iIQqvLTsDQJC+Jq6OyDoVYLU0D3JOKsQF2Qhr/GG4Y1Cml94aLI/DIbFkoltlOSY59owvPmpgnIvmg0PCan4JPWYIBCq8/vOyoruPhvJvGb6bYqTKjLynuYMDXJIweZdxaSoUxMAgH+CFx+QKNwp/iB4dvm3Q82+GflaXBYOHvHJQRtO9c0ch+CY/HoRCF2JEnHv5Mgwj15HQrImTZ/H+uJUg01P20ZiS5MT8q4hgcxc6slPRLn4CvNck6TYlxeGWo8De2QiTllBSkuxG3zb++kTq846bStuTnWaRpz569U4z4Dcy/vRH6Av6Enok0vgjyV963CnmoTZ8+AlU7dtf64ML2t/b84+bAhEn0LKvXlCdChAUptvFq2sQmYpYL1RgsgHFQLcSyhAQi5Dce1DPRscawQzogz0nmwshdGoXlRULuxa7vvJE/Ob/ZMB5ceUsn94Rc6F5Fr7EhXP4x92HTO4eEBiimZSuOdBqgYovLZNE4WZivodwVrXVxDRvaNIuB+n+EG/tj3qDFxM2lHsi/ELM0GnHOTCXKYFHwsC+vgGrocNsrdfTPgTNm7sJ6tdkQpEXSdbZi7ZdKVdF/GQiEA0WTC5LxpJjQF8zCBjhPUe5kBdHte5g4N7RKbD0EijN2chyQ3YIyoZeyYCgJc/yaJm+Uz5gyz1d0sBG1KphUIu1QKJD0bP/gXQ2BQ7XzYBPwJ10W8aN9B942ENlowsl7YX50L37wQL6zOdqLRlYgijcOkoN7SxjsiMUt3xvSZc60dYUKMZZ0sllODm7knt8XVbKvAx7IUYogogBfaK5Tv77o01T7v5J2TcEdVO9aYGvnw6jYZie2uJsx7Pjc2mFSyOh9a6aiSW4Y6HtyfE55lSEEj1BkbPOUYm9qxsb7SUCRoYcwwovpI4WQ5Xh9VMVNrknbyp/9vej2s2BtX/25ZL24KGkWKvvofcLKVER56uTY6EN5qIilCyhwBKzz6btuY7LkO+pyraeLVYyEEBJ9a0aBvbDPhhkCz4p6btUJ7JSt9uwAAA==","ing_pineapple":"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","ing_sugar":"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","ing_tea_leaves":"UklGRkoQAABXRUJQVlA4WAoAAAAQAAAAXwAAUgAAQUxQSA8IAAABr8agbSNJ56yOP+h9IUREfm4RIcg2iDzlMJBZxEiUUMkeocjph1yTrHnILcGwAdIwIGqR7P+Ld0RE/ycAAH8BQCJZJBcS3+tScfsuiWrMrbq7kyZx1eld9yQpn+3KTN7BJu2ZSOhuyhpJQG9I6gcg+QEAg0HbRpJi/rD3vyMQERNQloWm0OYfrBQzy6KpscpzZNuqbdu25WnB5h1l3nrQOfpLtGHkQKut9T7GUiAiJsCXtm1r2+z/d5yXZMVO4zC2fZiZmXkA7zjeIbxDeMfwLr3rzMzMXG6DT8NJY5Tuc6HhTCAiJsAXlABLOEH9zfO/uxJCBpJszn5gTFYBow88f//T4ze/+vMe+xbuRzpbQlEy+ehMd9RVFE8+PVtrl8XOt/+6ke126g/e2/3Oei3ZPish20y//fhj063GsLKo93qEqlp9baOxvVNv5OW3v7YNAen0CdmQNZ/6wotDeRW25JJMQKIWlVR1emz95d//X9wAQqdLATB83/MPzj42008QgC1kC9kWWJDlvds3r/3lb6uG0GlRBBQXnn7g6YfHCpdVSCAsY+4W+8rGznLYvfanf0wFZHEqsgxG3v7Si/fWSX2sQByEQT5gfzuR5ex+5Yuvj0AenyZVTLz66fuPfvx7vvXNWgjWSKQ7s/3Xd0Va/OtvfrNOnIxUMvvhZ69OlO1vvi3WQhPWSMmTtflvUpFt//WXP+icjBn76NM35qpe5AiylofJi+OjwaXzeut3vZtVyMcjmWfffbZZWpl505rb6cGGZTsVtRk2Fh3HYy6++Ohgl8iQabVY3r0bMxgLlBiYaq1sOY4WxGMvTHUVEgfnjUkH1o1hWRZEpcFzy9ecHyWczVwQmQRCOPJyc+6y1qFdEA6IcL82eO2fyg4XDDWjFEA4bOGV1iBLmBbM0bKwhBymdmWEOBT1P/7if9/0Td+MJpWnDYK2ze2mq+acah/f14QPU4z+bCU1ryclTcg11JDCsi4Yp0AH5TX2YTIvdkGOw/L48nS5V9fG+2RLd6i+wSrW0EGiZ+0Uckzt8jyVq3sBmM7f23ky5rygyzV5unaQF/egiytW/t6TZV9dreEerBeS5ym5fmPoxNrh6JT4079lcelyVNhhrsvy8LDLjFlrTGh24pmdf/6zndDqT7eVDuiEkS6hkUHI42E3iA6ozP78N9J8eyixf7rrIueKCPJiUg8muSr67z6BL+W1pP0eFpFjaDO7vDHtAU0XZ/2JZ8R8wyAJ7UTpUFKFnPcgKdLNPIzQo/V8vV4J2yAMuc25MbexSyRz7qIOEkT16H3D24WFeFhJN/clD+c20Y0uD0WksYemnEscfrkNdZqn8yCpGzrsYETUZlJmIR3aim6ul+T9iV7RRUDkDYPE4XMukVTvKsnDG7O/QgA+RHI/55rnXTqQrnc7aT+nblu2QIdXR1IvyLmQ0IkwV+PodvolCGjvKOTlaAqS0im3iX3bdzqlMKB3PO/BcUmuUYdrHY5Wd3OrL2Owz9ruOojU4XHoYozaa909GUE+uTxMSchNuuTeAtJau7OFbdCjDl3a4XluU4IOoRvZNku7rXUQYA+SSN4eyW0ilyiDEbDYb81DuuvhXJNPXR5eEg5CQMo35/FiC4xPJeck9MoOo+50kU7XIDmWVmD5dpZkdWkOOabychc10SWnVLm1rIVVWLoVCeygyWcPU7lxo6wuZSzf3AmtXTUGHY7b22ajEQpFJdVyL5ztrVaK7q297K65hvKZQZaQ25ScF8b52v8Bri7nd33NsQuKSpdU7oqAuLGAzY0rGHbaJ2m6zILctnaHhaP31/WwXU7MWLSDPmeklPRMyX3gfLLjsCO1RwvzRVOpJJduIt3Eopwe30Mg7RQTaLYd9q6xmTTkM1dgVv0wWGU5k9t86swW5AtmUjNfEftqNW+meb69krBTn4el2egfVG1PZh8vYM/W0Elf4WNweJ0DYLM5/r/X3p98ySna8gGqyrk+3tCzTom+wMf3zVVz2KEvN6uQdIRray2DNMpn2yqf/3+PxQfDSeJYwzzO2yfLFlENfvqrffOs0TnHMb21dxXIOPpPvdk3HmcsRmGdmbVKBKxq+KXm/15QbNwgM4Z93utV89Q8MuNvvCiuXQobb8zeNfYOC9bglKrGHMnr8f+lujl0WM8WtZfmHDZcFQMcq8pLO3nl7WM7kNcz9UKug81U1YalY0GM13pVAqF9yp4FeXMIhKwqk48Jo0occT26ruxZhxiBseTEsZvIpQqEL/Eocq1nEbBCRGNAnKBEuVPlMtbFsG6uu7zbQPRUhE4AizvzW8GRlxnlGnsQpiBltd3NMnHia/+73gnJDo3muLlNN0uCNdC9sVziEzOt+bLgyDGPwy63Y+Z/eQlxGoOLD4r2oDXnLkm6a1jr4bFtBafT2YWxEiELWQA64GHcbYGF7Gp//732RVDjfL2UAKFjWGQ/LEARvV/+bfNllZrN+wAsA2L/tUsLAoMSQKXY+zBf2MSDFxp9Qugw1mi5kTHY0V/+3Jfvj85M5TZICNOa8wOgciqy5Ssb1BczZXdwbKSGQSCCXcJCxpVjoLj9xz9t4/nyYqtFrVHPkgHtcm1hODnLI83/+of/qGR+iAqvru8tFI2iRlm10mI+WpFnsH7zzz/4UylxRhUB559/7um5qQBSAoMIAqq1pVv/Xa1yfZsfriISA1Pn7zs/OTo9XA9ldir7e9vrC4utNATEt/mBR5RAFIPnGvlwHVebKXU6rRZABqVPBwBWUDggFAgAALAiAJ0BKmAAUwA+kTiWSCWjIiEyue5gsBIJagDHahNZ3oF+v5cQCL8F+b0LbcXzAfst6ynpZ84DqKPRA8uj2N/7Z/1vSdzCX8AN6PyiBbjNHJHUCw47LezmlFpj52P/c8q+oZ5R3UUfrotywbC9CueTzhP/b5T6t7giDLLiayvJY/1r4MkzDkuFRFD+Qx3eKhziQHJ+EeXOFeKwGllL3ZZaik78QCy0iSryjrc8C9y45DZhTmlmsFtjT776OMMyXx33hpbdaOtYurcBV9TdzrvrdKz9mJZ5cQbyBDDc9bt36FgxhflBjvc4E5El4IBSLTR6TRSNCk4yc3w/odYL7mNjFMYGbentkUmcfbCT7JXLIK2TwCSN/qDoUx1tugAA/rhfnVWr4V9R7ZoTA5f9Ek6ELjZP/DsKeysYnG/5kvvr778Q++kZ1c58/odKlWmc6q1IwQwXxNCX+GS3NUAvF6vD4OwlYqd5OuPh7UAnTh5hFggLkY14qOHqN/wRp+pC7DP/tTdPUTb4vUsRZPQpm0q9cuB+RJMr483ZlfPQw6U2fkpya9TywBXHLEN/RbxYAfw1F0GqavyGMnZ5njEy/JLWs8xxxkI+vVTS29xqVtM35HcE8tTjtEwrjxlLlPXNZNiY61M0351jnAsBxXtsN0IOeAdMYilvYjRCRIWuKFsdRWvBomctuKWoxNrYAH1+AHcsIHIce/hWOSpeD3lV7d9S7i98VrYoHv77DVaxIUVAKJGp/WvpHXJDMFbblQAjRjGsL0dU1oGuoplq3OKz/FPMl/MKFXWmK8bGFMv+1qgrBoA07tsCeTTPYDaDUp5AMtmau4lRulvsIUqhuEOGPU5TuWl7zlIFVyL8/fRfJKsf988cKmi+101WeOUoKhsXKCKmev+/cT37DemeKGHo5LQ+/GQQHVJLvhTNwsEGkAtKd8zBk9D+NLXTktoMpXwpychv3WNvYawMcF5D/JMBJiB81CnOKo0m4vN0Z0x17Q3aFG6WY0Y1uUTIpkHjNj5IFL76yQOkRGCT+fqyllgcpUNMvlgb9EJ3EyFX5qHotHlhR1F1Xupudxv+GfB+rbO23Xxv4DpD7ZLTx9qKChmQbBGjlX36qlh8fDnnzMgRyBr0mz50ebcXL3ljszPBKNgjri/XpsC3qOyyo1hPs9xWZ50JTqIL35E+QrcIvoCqwpyPBjAxMO3LFJFz+v+48Y3FVK2PVmKCnCEjSP1HtfgyslPxuP42yFqky5g9KQ2PdvU3Yd1NQ56ViZJQ/SsHSMbIPr/w9u0yVpnk9nFFor3wGk/ASSJMx81QP++e9eg+4UkJ7ZmcogvPM5m7R6FX9VncdadMIcpsAvyxCiijAmCNl0+94Y/AjZqmdZMg1z2c1nJZXm4p1ObiGtsK+hwmPwlzLkLVFy9UTd1hosM02dB9vAqD1D1CXXeb5MNGtdOPY8cBgnnQ0ddFoCdiPonXkz3gUTX1TgNYDXmTqBajUkvI+2T4a1CscEBG/5TVJlpLV8apdkfub4U0Oxs+1PUKyFRJ2Cc5ZXApbb0YRAvf+T469Ei4VU7BYWe6wfpu3oqZuv4e2pzlFaADZjSG6DHELzZ2Q9/8+qDL0pX6S2VjFu6cF8xk/FXIehmWrA1EN49AUKb0Rwm8B8KV/cjnKsl/HXUdrcBTowwh/jXeQ7jznjQmGpkhiSVQTXQZ1sUCvmFE+mwBUsqmyip7QrsV5MpyjeCi3sARtBH0y46czbON87omZdMnQIj5lt6SZLCL6Vp18ZGwStsEAdBL41PFfQnA2rr9NgbqRVahMs8SDIVWWpdu/P3HMOjw5CGrUuY8E0qnhBAT+c+ajTJrNaDOMMNbBqWJ+b8Mh5pXfkckdwEsy6Zo8co4KK38+xVGqDHRVLaHfYPsk9WG9ulhQ9xrsWblankJbSc1MNKefru5Lo/xtyuMfcYRiFdP43dMKBGPfIaKWkeZOQ53b2bxhjEhxStfQCk90whGzuVaDMroxfzVBtwsEvJ1b84ouudLiG1OWCUere85QmOlwbOE1dazly5Qr/9raoib/2PoTQowkV35oRWh/AuBB2qHeopTep3P1gM6jpnyb0+R820pZebmS4u3d3pyel3ZD/XgVzeEL8DW05ro5/yxr6/ghld/JqIwWljQjfFznH8HmthZ96CCHTGUFAveLiFq37PM8Cc7yWA+G+T2s28DMrZgaDWZYjuLMNbiJRzoVALc/WwpYRUKwl669dhdhUEAvXlNXkfoinoEAAhz1iWz5IwqSYHF198HCzn+ZPZpS2SZIQrbqcSgoYkWJ8AOGLUrUMi8fOxfI0MiI0EOAKGDLrMxmasQ9QlggSFnclxXjBjl1UHmgHyBYSwV93L/4QmyTcGDTXbCz9dcl3ftvMXD2+U1qVUq7vFLyWyCP4Siwg1YV4o00P7yaA4Sr1flC+RAExHcygNa3bQ2bKR/MKq/balS+CVrFWvwbcQ8Kqzc3wu0JeUrYxU9b2NXf0VLd2yM5KBQw3tmVE1pHFbke4FbwQ/HJtZKyR7ufHDH/bho9jzv967SyaP5+aneO+2d+nupabJK/W5CsOmYgfwXAJh9aH+Cy2DwjwXyCSUL4VCsfyAxIhwhDSQQYd4a+NN7D40scXOpuqwDuws6HpHa704w9eHExA/qv/7d5xFbp/inHK/U8sG8DL+BSSF6JouqnKrsWk2igS/okKr5wH3tha113+3nd+dL/+VMxHPao7HN0A07bmfuQGxyb7eNjOpgkgAAAAA=","ware_stove_off":"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","ware_stove_on":"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","ware_wok":"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","ware_pot":"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","ware_grill":"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","ware_steamer":"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","ware_pan":"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","ware_board":"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","cook_wok_fried_rice":"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","cook_wok_noodles":"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","cook_pot_laksa":"UklGRoQSAABXRUJQVlA4WAoAAAAQAAAAnwAAgAAAQUxQSEMHAAABV8egbSRH5xx/1O0LgojI4U228lsiR4Tpc1nBhCL5LWf+SShqJEnZgddl/RuGIwUR/Z+AaDHGpjTBQKMtAHaEzkhc/EWpG5KPSNIwb/AbkDdg72hjrUHTnJlR9wA0sLz//9o20vvzk506XGacMjPzMjMz796Yz/QH7JGZ8Tg8U4bsMBZ3pjQQmJQcO7Y+hzipY0t69hgRE0Czi5qm7gIwKS3AtRRGKO5cN9W14otnKiKORwhw6ggMRPkR+XVLIgBPPriA2tWenjJc6bmFqsMGAU4RYdSWd27zugjIrV8fRhAVcqNQHjJcPH4bvXyol2IZEDgtoL1t8hvmEa1aNYJ8oLYZXYDi4VjqPT4Ynz5dpfSaScsJ09q2b5u0drqJqG3VQqMZMAKIHZ85H/PC3QNxbxpEcyYveuOs2fMjDFg1RL0NFiCjvnOlWw9sXjIhuSTQrCVdB9Z3zGvBgGhYAwiqVx/4+QeWtQBKHmHCtHU793dObseAaHQDiPLLpx569GIFlDB2rjB58/YD89pFLBDNaSyVeg+fOJGXUYKIqGPnZ3fvWdAmI0Qz26L86uFdMwvYiWFYsm/PR1cWZALNb4vqzu17praRmGFqx8y17XlBIBljFM3dsm9bLiGmr1/9xiUETCA5DdGCu1pnt91WDdcSajSJaP+39y3sAIlEFeCpb1740FANVMMgNRagacu7MQksyM9o+9nhsowWrckBVx8bygupgdQSsG1QAmGscs/RIqBlewuWnz92++NLJshuFLlz1xzbSCS1qZYMkG8RUCn5K7v2zm0NjYLbts4m4c2olmVB+5TtWw90xWoEA4pQwuFRBGBsohnr22mI2k68Ow/CAPb49Z8vCpHCAkQjPnf/YDBpPULj1ffwRZPakj1cQuMhnzs0EJxa2AveMM+Mo9V/z1mTYqCl756ncUDXnx4QaW5Pf/87C2gcDDjNgJlbFgbuXCMRwqS80PZ9eWtskrBtRqYepntqnjFLQL6zq6s9MkapFwirVmlMaOKMGW/8+ve+94mpMZnQ0eatbR5Dd9eUNy9g0ao8SIQsIHccWMuonQt27+laMs2MqkxAfvmsWu0bPvi2pTMCBlQjK8oj1Hbg+1+fGhkQmbRl3xf3zDCIbCq6DmzOxUJk1e7Xb8sTyK7a8dnlZFrNWZxHWUWCGYtyFpnUVC68CusOFkxGVfGhx6F1YkAZBb92GxyTVc1QWYCyilU9/lhFZFabvtPnyK4m9P3n/jLOKrGMQGRVBwQ22dRYlX6yqnGQBg89JymTCELp1aFnLreQWSvXzvRcnxuRVa1n7y4VyLD2yU7u1JVMUddzp0s4I2iEdEdnHymJrCBRz+owWdL1kCRngtzMtjrFL1+qkgU968ubqavik8eqchZY8pMDdTEM9g+TBeLFP9xfJ+f2v66VkHoOt479ry4YuvasCqC049bpS/UB51cvDBiUauBKXC+QoIABpdn4mv5Zc+6KACOUQdDZwtYdXRtmK8iAsgdlWtu6Di6avaPDIR9Q9hCG1mjRjs5q98EVAWUOEBjlf/vT359HZFSBy6XhKtjOJIAkAcLOKNg2YNsZZVSdEWnrhpIKj8SpIzWUu8ukq3Gx1FiTQsqgoUPPNBLYacPwc5dQI6WxjRsqNk6ZRi89fUlkWKt4+Fky7lA560hZx/zf1+li2WowKV2oPHOx2mBxsYLTI1b1kUesRjLXj70onBqiODBEY6t44ukKqWl45HhFbijz8j97Kjgl0MAjz1Vp9OIDf+lVShieuOeW3GgUn3ikiFMB9fZcjGnCM787XrVTgQv3XMdNUDpx6KbsVIgrFs04dOL+myIVFxzstJrBT/7uv6SiZ61uozmHz1x2GgimzA7NAQYnH7D2Te1WUwyduSrS0N1TIppSXrJ7tpNPCp/4Ps3asX91SD6IPvWDpmHuG2YbJd8Hvxo3Tce2lZGTLyxYcxs3CQvfMi1ONmEQFZq2bX6bcZIBgSYXWAkWF9bMaLaRshPKYuLeRU0mgQhKJEO5GDryTTZS5cEKOHkI1UdPlUhA69q914JIovjF8xUlAbfOXX/lSjVpbOTYJiHjRw4PyQaUAIYYgWUS9EZ/FQJg1GyxAJeHQSSphHy7WHSQUVPFlIe5+cwfn7JIWIdXf/env58hRLiGmsBgvXb3M/E/jv6qxyStCCK/bMOb1k7NjTCosQwSGjh0LUc+kNytSze8bXrMhDkdbiQDEgxdunV+MBIgJZIw0D6/1Z765k2L2wwY1VDdbIGEfelSdeCZvrkRkkj+aNq6T+xrQUGmigJGdyAMmCDHDoNP9MWvvhBPagEw6diy43UFd+yYET/31HDXjpk57tQgCC7HFx8tE79cjDpIV5FrDfHEfXMrT/WUJ+/buqdjbCq0UC1WYfD4K3Gp6o4ACJwitZWPPFxF+QVbWq2xtGzZEl16sFdEBefBSBCTtgIMCEwkM9bcskVR/+PXIQACYtJagEGm/sI0LwBWUDggGgsAADA4AJ0BKqAAgQA+nUKbSaWjpiIrdgwAwBOJQbfstMf35T/LPYL8JjMu/n7vRJtuvM75z/px/2++x7zPfp/cL38+fMTccXgBexN/CJsx+8w387/2XsF/yb+xdZv0Cv1xbgLX0LCIc5dJWCULdR3HmbMtsz7S3wJVYoiWLW4p0YBWkO2LKSD4EetUKAk8pTopR3zNip7OqbXol0cdKh8NR5Ywm7dgnqt9AVULSbp5865PDz2sOOqzw6RNUFlzLhbAUOEAYwWFLWjoqBP1HX+1joaxo8o3lKHR0WAvcWD4RcKSs9AucGjWyXSUnNp90CIRy98WkM0zEEp6WcWk9uUut2fxtqDyH4fOUn4b3W00JVVle4WMrCFWC09EVKKocmXJz4Kf+EPb7TQ6FVygpv/5dHDtSpnfQ81fwBZiMXzUYR91Wvv0q4gxbZ68BNDIffTffeCinrHIu6+vWk2y07zRY5jNZfLufeaVTNrr7sfVfpsxiM8Yf+Hd65dBWV5WL6rDHCdmAz6bQF19HGy7RTTizNtjD+jQKdh4CnJJZoXSTOluk0ADyJOKSN6BZ0iD8iJ9fg/iH0YELORIl2tflWZ6zQtwojjzCjhCUaWAAP79NnE/3G+P2qJ5jUk7KRZLlvP73HNtnJAP/6e8cTEx+ub1ti29M2OGJ8y2/PQbwyaVhAyHWDICWiYJxUx194VDeLNc2ZIaSnlNSrrCbi85PWWMqCXPVLnTYTA48BV+YR2gPe7PYgiPUxluKQlia3TP81Cba5wLG11ewmBoxOC9j1JrkJoxiEeu9YO27ib0nsMoa3PDThJOH4L7+W8KZ939LnF3WSyCov4GHFDxEX5Ew492E768TaCWzUg3Hx0MGrPl1+Sb1QGA+H/wIom0UbzkvTTtdASiWD5I9mcneYldJBe3gf+NgSQ/fwjxHlBjG7ZR6q3Jz4GnAYr3Fa9rND0NxX/Fk423mp6CDP1v5pBQvFIAw2ZMFMVBzEvvksOxLbnbQPZ+SuYlLILHkVaBsYqQvyg9JuHnwi63vdCtBNCa83NTHt+m9o5auP+2WZtl10MuwIir3+4v9eI4kWX4lX9BGrX+c28XJaGBM3h5HXkRmpSFZ1zgzTIkbVoZe6vrzW5uJpbnz6cW3TjzvPh2OJx9C4A81JX/LRcDeGtmsUj9hZ6eC5pM3v5We71oQxA9kuOG+MXJXLw3hHoq9++JBSEnQ4PEmT0YVFI8XmVeKfLThsJem6lEaz0lYuxNEsKCU9/Rq/MyAu4GL3/5ohbNIgHnILiWsJ53ImF04xsUFV0IZQYqQBsQsE5AZ8PYhXrUWBcDdjI4CzT3oxjaSu3N3xRcFndFyHxttP/7ggsNvDlh2ZEncQiyg+p33az8l+fdC9p3lg6RHbzm60A62klezQ88Bi0PHc6umt2bNwTuK0zdsYMLp+A2nUQ1Nzu1urpRRm6PnV4bI664om7K+HTWZaF03V4LMkfH5iQCbCKSizpXIdmi3V7YJy0oNfJS5QiYdoiiP4D0mZEu9vJJpTy8Kfhc6VAzDp3Fxibbl6vioktB+fNJ4fs4tZUBT4Cx2aTSkV8SSSOGiZxPn24c3h758y2TaG8wWwhUvErzPpMopMKyCbdhDqDODfnAaNWfIEWZYd1VTnYZXdM9LocXeorWsFYsgVH5o233WO2CAs1kDyyuw26lUP1vKipC9IdJAS0EYQq+QS064m+k8ZKaFluAV5/QeFeJMdOYIBo6yL8ZqyxoUyoQFKWMwgoknQQyVeTvDbcCI9FLLNeaONPoqlGUTVd48cWyBYud1iLrjxY34xhynVtz9XaAwrWYBh4gN779Cu8FhhJA159lh4sf5mUCqdIbAhZOm5DvlUy2lyS1Xs9Z1EtQKPeW6ELbZtCzKjbWbTNr0uevALGgSsE2eNELaoECqaUidcntTILI0SOYjG9Wkf6wCkxGS1gJ/6rhPTqyqDIu6tJJy7/hG03MTuE7ax50NQXpOkgYaeznHHEKGhO8nYwLgt2kmdBxF7rMXRo9hzGtUIPFDuw2J/lzOD6NF456jA3NRdYIpvYnoFmi083vcxAqv9A/UA1E4/AzV6O6le7HiG34Idt4EoDrUoLS++sGeKE4gmYppGDqq4AXgZfJvR6nMO9j7MEncZMx8eaS/PCz7r/rUKsDpoyF3j8jYPCxTda442ZV75cV9K5OuKVh5oPCOZEwN/mu1EZ44YecUpJOuEotmtaHXDX8yeLzxTS3MCOltAuEY/953oXnHrWY+pWk0R0zAJD73QBcNI4Qcm7E4SzYot7k9ixz4Bxzgpjiv90M1xGrhas0Nqj4UBUaBi1GU8Hu/7KSqcQbdRTT2P7kg45dM+KHwqfs5NMvDhCk8uGyVQ4HfgjAhDVjUGrDx5/8rYXRSZUbQ9KbKaq7cER4SuadWPVQeQs/lA25CPtAfcdQKVrUym2TVDvmIpb8jhimBBPxUJgeKadrOOTxzOMTH7AcscNMXgNa8Oe1QJR2gbSasON6ZCKytBbwxaCS7Wib4XqK7msBbX+pFdFmmdj7H1yBd1hh0HIS5O54/+ngDLofR1qNcaOOmXQE/3mNV06fTZjfxiB4rHIp1on3MnnsniViBKl3S24fn/OfPIfYfeYHC4iDYLkSRBuhG7tzEPPSEtHxnjtIFgFyvlX80G3B3a9S3G8KNIkI8UIc7LB9rl6VaTJYP364VES7TZiHF2rviutuQ705/Nq63gSznyQUfjaOqSK7FKiotsBQYzNKm0n+H1zwdw1l2S6ERdwEHvg9JdfPA/9KHvrjCsPtQqDMyzPwpyVBJht0fSD3+XAANsWIouE2sElSM8Kr4V4pj5xLcpmI80ql54hCv1UfGSVv/Df8fejI7TdCy2qvgDjJCl+lsPVnYHIVJVFgIx8EYMAkMi74M4A8EQ+vh4BQY0gK3JJLsVUjsloJeavNjdtyJPmDqCGYNNa+nAjhQCiZzYgARlEYl9qPXpl5DJGmFmVChDRDRngLAG5SkDK17r+71UhUeULdInBuu+0PoRTZPVm7Ysv0B/iDlzYNHVmX1ZMXL4mESOIt63a2T5WmkIXpuzKntcIjt6kGYtHI4zAm2UzH/61AfSfygsyR4XnN7sihld4AiExVQd4RjMdHVsSqFJ62P+1Mvgy9RjDoDHAC7gexyv7pjf3toSvXXams5OSvdZr3+E8YdqF9S5nfHlqbguHh9ES6D8jvuBz0fOghZ+B+OUV0VnsoPHLPvD9+P7BCc1X4I7/YDYZMMfFa7Qvi+zVkokOD1d7jqZrHOBrusJhwLXoFCXGo5ydnqShGIX1WDzduVSrS0Lm302kbyJ0Z3AZUDoLWwuWjTL+oayaTD1RcWHFKwKXqDYZ4oF86QBQepSTpDLFrjQpBZ+I65nCOt/PcBRed+/thE7c7Pua6CYhlX6tEj8C0qw5vxzjNcKJpJqqxhIt1GWlj92+8/zUarHFo5r0YUwk86t0SfVd6fOx8efQvv4TMDrFds+zBfPRqgZBg9jV4Z/fC/7834FtPsFKX2vmy59a9h2LVGbgh0uE3lJ0Cf7QVQzcnVtQZBNdg3wDsWQAmx8TgTVDj9+Pk2OARuzdS7RjZfPBeMFUFFvUUyamnfqZCMEBfD0Ksv98rdf15w2NYBbxx1U1SiirNGDLyJ7A4UZ6XLpoYOHMalT77xH7SmgNVktmY2e3JtYFEMPhsj3hiOfxcBfjznvrv/uBqn6eSNZhs1n9afhzfGHHrJNp1cwKEZ3OrDi5sqjNL72sJG3iObfsQemDZX3/6hEbf+eotIAAAAAAAAAA=","cook_pot_laksa_burnt":"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","cook_pot_fishball_soup":"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","cook_pot_broth":"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","cook_grill_skewers":"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","dish_fried_rice":"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","dish_mee_goreng":"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","dish_laksa":"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","dish_kaya_toast":"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","dish_fishball_noodles_pack":"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","dish_teh_tarik":"UklGRjAFAABXRUJQVlA4WAoAAAAQAAAATAAATQAAQUxQSCACAAABkCNte9w2P+DSA+ogoFtaZyBrLaUv5p6spTMgakMehMak84DwhNjx/1mriIAgSW7cZha6Qdo5AAh8AgAwjiYGaTEhOzRJkR4q+lE9kKTG89EfGjnKcTVo0os39NSGJ7xTxuJp84bO037CfB7GfiEpHLqfjH3DUmAONettaFh48YV+sn04cy56B1ue9QJAnJftJxRsHSUDfnQPKJhRHfcfkDDz6Lj3gA7+Azp4DyjR/b8giGMqeHFMBz/5kcGPY0p09CCRlEmhXbmgMpXdlrMACKci8OTIAcKpiHgGU1Gi+2uDQODFIRF4MdCP80lADPwXuZZHIfGKCUAgw0RefgFiGYbEFsswJDbUDOMamsSG6Rh2Rqu3pUHw93o2FTRFyYv6/nqeXDcpBQMHmovv/811kVmdM28nbevyY/d/jfS3yeNpNjWx1sxvLyLdzGPFirV6enmCtLCi2G7LpYES3GhiVL5oOSHMfD6wQiof7kPDisBIwkWg9NsloFTUElBqfVGwkIHJcd2spXIBor997GZpuABrhttqdqs+Ub7146x3r8q1q8BEe747r6q1q0j8MNx1IcnaVSh2iBQg6ylnhS5eu3KUWxrdL3HLWjvU85SuXRXSqbWbXhadXNVMLGrWIvgnuV8vo8pYb/XKYEXU5+bQ9+HQyJyVYAY1xeS7cr/Vz+8uf7PkGnV9PcvX86ge6r3lQEGMe0vi8hmoiHm/SzQAVlA4IOoCAABwEgCdASpNAE4APp0+mUiloyIhLhXdsLATiUAZgpCyWC2Bbuh1eMA46d+KdDcgaGwcx5ULOwY1ZlA82ycXtAYAZJU2iRHuh5mrpP1kAcJ/9tLJeu1aN1JoR8o1CICJcOPVe1jp3QVfN91/cj80y5p5XZoOcM9hi61UM30XiOuZ2J861jcBT2p8/y/ZkUupYZ1i78vnYgrTOXcXAAAA/t5NdtPKOkfijog1JtHUXqlygNQx14rgb/xRXwfksxIyDu9pReoImraay6ubU3e882aRY7YPiVZTjba1h0mnO3ezBvXUQWvJonYxvVX+g3nztuCY32u9iuvNLPAMsXkfwfDZZ3Mp1/JWdQbK2zhfKji7fIQbcOO4XccOD814smOsHjz2wt8/W2hY2nyagw1l3I0vnBTwJZnwCJ9L9BNfErzliv8pPpsvXB4l0fEyI+8yLN11yj5zy0EGMR1kYHopNYVdJAnk006MP0Mnf940ax7G1nwEhPry2b2jt5Pvp3M6zTUxa08gtuAgrSXEiCWUvsCcK8H7Gowd5cBoTjWv9Vgyh1SjOQ+uM8xXltVaLCve/G5vEYjx/NZjTXbH4bQzo1DCYxJ62Vcvp1FFAs9rk7i2dsOIMX4gSfj65VW2IEs5+InkUYaw/bUqVJxpogd56/P9JDkpHgAucOX2W3lIP/o4748E78SNARaMQyJEO7mUFmdQ7RGBeGFTTAbMn0HTZS1JaeRyWa7qviuSONzqhdIUAAcMr5hToPLEIEhHpnJg1Jp9WWxrG4VQ5q80TsL4XDuH/0ZhEGEJFjpSULNI0ZmsWiqPAmVQi3rv/n7aY+gZK/cUtJiVGU3xwKfHfvFir1XVcBiyacKlI25jx0g6NkrS1OTI/HC55jvWp16xI8IElhBw2wdnLIpAynIJR7Ce5xSK6kKk5nZ6YYly72EI346pLNIl8I9EGQK+6mQ0/ZK4XgYmXnCSXmL4yV1LsFan0cVTbY2fM14PnKAAAA==","dish_satay":"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","fx_steam_l":"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","fx_steam_m":"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","fx_steam_s":"UklGRkQEAABXRUJQVlA4WAoAAAAQAAAAIwAAKgAAQUxQSKcCAAABAQVtGznK9+b/8ye8MET0P0ACM/uuBXuZLrDu7k83bHv3tbe2uVddNljSMSSJIRBI2p98g4hIHRS0bYAc/NsmBxETkJS0bTskWc/7h4p9bBszewU25z4rMGfegW3btm3baEXF/7+D+CMylxARE4AQQ9pyk3Z1t/K/3wmJ6Qo2u2KDNsZV7Qvnoikl5hY3aQMS4fYNmfLC7nNdCtiQZnfZDk+j2nVzl6XBmNZrzONJSKt/wVLAPTvERmgCWXHDX8EGMMYm1U/dLY+zF45bchoDcfaREyIeoyIUQcLqgQ3UgbFBEZYjg2WGmmKMTLNY72zEoAHi+vMhxIzwnsfssv782pVQzrIJnlsrdWRd7nDxhuBkjIwMBpvlT73843Oot2W12AVJIoAAjDF2mdo31gd4sSEFJMSwwRgnp7r8B4obyq4GISYBYy/5p5+079oChACNAYMhwhsrtquQJPpSDizLYJKLP18nipFSxoIMNujGYIMG8gaRNcbl1aVtRgqbsaafimRjDyBGG0DunvhUMQF2ZtBgwKL/8VVNl5INYIMxgAFjBN2/2u6A2BrAAoSEAWywIPz/Ep5ZXnWJkUbCxmBwmruttvjj6haPwCAwBrub/+A2IHD4N3XMSAA2/ZQM9fKLKMB6r4p2D4TIm7po//1iUwyY15suAViABAKbd17/7tt/SGQfJRoj9QymHy77DBD5x4jJYECAcOYvlZHhgy7qCia0TQuJwaR6n0ZgZQQYp/Ib8BB4blM72iAE2KkL1YMojTDMr56bJQ2BZpbu+w55BIh/77zmqyJh+tYvLz/8L2ZC89/Di9slk6sfPv1XplnrjNMSzsTwTVdNRe3+J1ZtMhDnv3tKmgpQLczMItEsYcNo1n3i/RBjem854rj1wisXfv3mPcSEAFZQOCB2AQAAMAkAnQEqJAArAD6NPJZIJSOiITHczfigEYlnAMWYEwrQBshDnq4syILvxiholn7cIz/KtZgoVAG3YapQnRgjlYZuTaJ1gt+cN8IIw9aP9Z98oAD+/BPbhfupZP/qVUPDAjCt89fJjw/S/iztv69NV1FKcB95Q9pf55qVPJmNxN3Phg5uOV8xC/Xunla5tkfwPBv7IdPXvCGfdiz+m2ruZIvznsAnfd+0B3/+vhylZxAIpHY6eH9s0v+7b6oEQavVVaG3/Na4U/c6kCx8tiQu8w85dOWiFOTDDqt4nKOOLpcLJnZ7eKzNh/sOfuaY6zL4Xg2Zkkh6YbMcImoAAVPpNHYal8alWiW6SVxRUHj+1rLyO3lfMchGQmSgZQIkQgtdl+2AVGMJzd+m3XTmf41s1b4H3pF++ietacdQff6qB1HwY+ctUMqw/4dENtigh3jA62TnZE5Ypys8H0AKg7dX/xsmWm1yDMiFcrIbaKF+/c3pjI2AAAA=","fx_sparkle":"UklGRr4OAABXRUJQVlA4WAoAAAAQAAAATgAATwAAQUxQSMMHAAABp8egbSRH5+j4oy5fAUREDl/nqulpzllyzjHIzwIQG0lSJGVX5cGD/w4PPFgQ0f8J4N+3/iSgvWtdAq1emJW27FVh8eJBtVG4WG1Gzh2hXQSqy1wMtN/gFNCkzOCy13fLvZb3d+Hr1M4k+f6mTw/aNknQE0tjsO1hm4Sf29+4Bi1s28+50fP+h0lj1c1ubdt217Zt27Z5ZNu2WdvX1m6mSju/72D+/39m0sVpREwA/we9pqX/JKLkxJH6Rxn6/h3l6B/kiOXvtf7nEO0fXT/jkIj0D0G7B/+u2fLUPuwtUp1M0pHrV6T5J62SBNobvGReLITY7TCQGbVzRWhJ9UF0vOzWAxIo6NsamZBn2v6oF66eismztwyKEyhGXNzMeWCWf8r+ClUyYEz3ZP2Ym4oQprBZwoQZ0eoKFCR6vDHrjZFSzoifsCiFggDDwASOsIrsPzO14bZylBsRm/DhppoWrUMh4VglEVZUPbh+a83U00rIvWfjUuv3SAoQJIojX320XmGo+WmZZ61AoFx4hokCeTKQfICqoY0/3UmwQHu2pwUmDCx7Ldt4BjIzcz3zCRkfPvJLFwIvz0MgD8wwsl06tGOnCCBPknv7vH77RP1kU66bUxVU3HFCnySZAmEGyoaY8nlM+JtZYdcjJhahDJB1GJwwAWLQK39Wez5ItEr9MCxbB88XZj5IRtWCKMFWVOHhP3FuKm1+4Ll1X47MVvW1ny90Zj6A8TrBhhmB46ZvBQtKPzm2nGyXHXz6X2bmY6bVV20PiOR7hGx/yRMr5AyQgXUhhxtXp4W/sxU3DcNXNB1caAFGJNl0sQzDZAKUPQkhAWbm/XLyXfIh1ndg3OTnmxYykCFyagCWBsfmNStf/c1HlA5p7kBBEphhgOUIDCpbRJxW33X1/PnLY34VeREQ4b2me8wkox6Om56C3Z/eucmlzRDa9GpL8L54Z4eCpNjltYm4RH2svumb5WlqV4MHwpTe2hO0s8bJAiB6ytbx/UT9bNj9k90AAqoPGJwwxRAIRTE/0fLBLsPIrRQAGxCAiW5XX1XqhCSTK+jdJt8PuGpaMkjKSkhJ+EuJ8r4xQwCG9RpSsV0BuVZeeVmeX2i17iTD8HMer33jBUhBkdKSaN0ikx57eJTqVnjx5R4i0LwNd35I3aND77lhXxROxC7dsPosr04FR392OeHT81fFUTip8qFNc45NUgf33UVj3fs/maQgAQgLZUgIUxhv3ePfNmfNNoUyZ7XjkmzfbRgoQ4YZWXWFLaIgAZLElt/WRF1EBgpIlkZNSUNYrKzUAwm8fJlZVqrH9G8ZB0RmEzMJmWGYT/FRR1c4TBhWddpdExsJijv17RVHZDXWqP8zR/fsUkyybY/u/a4zQOBZsioCiIFfD8kzyACt/+WWAw8/4pqPhrfwyLI5z61cn7r6sFOe+/6b75f6iN49J0enNQYY+hMOkelg82fnX/TmVz+tyAOzLJgZ8iyVF5n+8W13/bkxlcoA87bl9WjZHxnlF66UMwEGez6elFcxZOy4qz76agXOsoC5HdOXz60sPW1EdcMJ9747BzOQat+fFmlZTmbldStwEmZa8vnYBJkl5x5w1VLh6uLY/tHDdx+5oCYRTwJeVdtzlmAGpLcRUiDDEAuneoAkoOys9VvBwphMqd55eZ4DEAjKDJwDQyFANJWQ9jj8JVH811QHCoGQCRBgABIoroo2eciFEOSfmVy3RqAAQGzZgMD8zCEAidCisNOeqj5JjLBG9enpz1aCUVeTM8kHb6dPnS1R8sbChBnhlde8SQREXUW0uAGWYRhSNnIpfCUpAHU6sn8sADNCSiEkZUOi7sLreEDVJieBy+9cTh29oJyXVO/bNA4IOhwQe3uGI7PtOe1RmMJ+k8vrSfTwVx68pqsBlU0iBpSfn5Jp6x+FBIuGF0xq8NyL9UGKXbFp0vDGEG957QktAY9mOxo41a5GYUb+3p5DWqN6QMWdGyNOljz06c8uHohA0R2FEU8YIWMnLDEVJ+uBKDltak1Unmt26+MHntPJMoh1L7PFBQSK2PgPNkFhu3yUIwOBkCGc2jTCt3azs6mRIGIgJLckBZaTaPMqhwwBhovkJzKM5V+nNm6MBVQOPspAArCIF/GyV9LvvMHOCDRqf12agVLLds/fgFxGyfk/LXBgPrQ//ryTWmZJtG6y/76mALPaT56Yh5EpduwiU9bq7VQNCL9256z9dBDKUqMzBkYiknxIti81wsoHgvyVXH1LWZbAq8g3wMDQrljfShRgBBol5/+0YDdYhpktuL63yLpDhpCAWVOjMvwbtEiYH1A5+Kh5HgjMafElpSKH0vI5ewBha1cZ/rK2oxs4FACxjQIwx+KLh5MTlD9tlzATIY11B3ZR4R3LMD+RaSaMerjHsETHSoVA6WNKrdOcmiBAFa09kwdYroTFilpd1Rf5ia07qcKKBV6QqB5jzkmIelhxaNsLO5qfacv3S3DIsHV/Iz+I9l/RqAqM3Iuio28YncBfxC9eUYNEurhVEcGi8SnX9UPUSyXyPELk3bR5q0Cyzr0VBMTzouyFInr0jJQZBmljb1UIoPL8mTKH5jz2jVkYqR7VteJ8ZeL4ZxQVcunZEv+cVmzX8A8qxoy4BP2DaP8XhuofBBr1Lec/reoPAFZQOCDUBgAAECAAnQEqTwBQAD6ZQJhIJaQioTC7G1CwEwlsAL8vmAVvqvmbV5/KbaGbDs6z8f4f1R+YB+sfpf/ED3TftV6gP2o/XT3YvyA90HoAf0z/O9YL/a/9R7AH67+qv/xf23+Bj9sP2q+AP9gP//1gHCd/03tt+1zLnNjr6dCPip0+uYN/euSP9Z+wB/F/6h1fPQA/YBp7+A3XIb+7Er5UTegknL2poUKK1bXfvVtqffcjKRBP3n/R86Hw5qIxzmXEB6nScwzt3LHjKm02yeg2rKlMrxEMuyiQih/LPtHBzG2HNI12Jfhv06IXYM59hhvm05sU2STOFJLKCIEh+lKf8BhJIOH+5JPuZZb0AAD+7SeJ/4KXdXWRXUW79mudulsRi/gOuNP9v/hALwVVfqL4ompwJK60v2i/0LQkW3/vsasTrCf7HQPGkJbs1dLxd3lxT19fNWjKFozjd12gV2K/LqqllOH+iGEfhgH7rZXnG/xGQbZCtsxocskHAfCfSwr2n5z8hDYg7yk8qwdtaafYumnIBkksHsOCCF3AAMk/H7/wwvD+ID9SHOKyX/wifU0aEW1OBqxxg3+paxKmVae98IUH6cO+QW44hih54aHfoiQtEtSg7Obou+kKMuUgN39/GmIa3GABUJgtTD5TwvG99PXe2/yh+xS8IDMAIb47PYMuXuWEk4n2gzPsTvlnVZp/6azb7Scv2PezDi/Pp8y6oKHuGXiGMBSSaYbcns/dxY9lUMdj3LMfdTMrsvxYDJ8NRovcxKEd7ibMTu4Bh3UPnBEpDwqm+4lfh5PlgMNeppunZpvqy+Tu65UrQzNa0jE5FcEMb8NLQPanFmqPsRADFZszdCWvss0NwAKEiKzNz+IV6rY/O8OfPMnrMvU46JJ+L4qP/hXoiE+BsU/NJ+BgvAgq4/c/UhwRI3GgVVLKXNa0NJKVkDSY8zk2+5yH6Qf/Vdrng4htOUM/RaDdu9H2m7pEQlw2OK2g1LL2mzebXrBT1MImdWdTW3Ge5MCpG1jz1P4EVcdM3SgHNlmfrzX5x3N2AAMInfRXz2LoRe8PnUIaMShB3UJipqkr6mdvnz7EJh1G8QuZR5NR/ScgRblsWeCYKPA17bgPgtgeva3+A+sdqqvWecxoshi0lB0pgpYBi1Jvqd4dMdMTlSCOdOtHNKlYlhGs53UnfBYs48XiDjCV1qoK+yAcLLAmBKWOyyO1EKy4etMdOWEnjpN6LcUdfXjWWq1T0lDxb8kEAjwS2VqlZVOUT0G8PMHaj18/EAHE0qwLd91KjibTDY8rHqA3i1Rbpz/hyV03uuU96Oqyp6nPh6IsWbRjm6lRb5ipvd3Vb1lLujHxL7kvTudRf4NyNLhgB1zjxOpc4hwenMHeuOZ7L5wFuYWYziizrEacz60si3um48CQLe2zK+djR9De7+fSb9eeBNtxFm7wBkQhcH26dyWOlwewI/Q1CBablh9a5VdT1RqWkIcexoqYSuACkD/eXz/82lMNU0BOU6xpgVUdbNyt3Euv2sv1pBe+eHUTZ1GUzH5Y6H3wdUtEdvtpuLdG5qSfRbY8iGrKs4FpIEuu3JAvfVBjXeKdXx3/wqoEttyzq/HtKC4CSsCi/4ix3FCiV3qi3wQs0I+V8Ut9qCK/TUaww0iUe+wjMn3iWO5+WRwt/7IUD/NhiexLyUCEbbYsgbHph1zcqMEfh7/lWAfBG8BNS5tX95+7gXDeaKmWkWDbK2ZrcXI+bhsdX8o1Y2G0aji6Lv/zlFhm/7qWxbMtMJ5OELbV/Ee0Wz9rfoaq/vOf82+kaENgAJySgSQbSN1EsX+L8RsFvkJd9iUaMHPFDnAG9iYz+5FegPG6tZBIp5IgsC/NlQvssnlIVYFR32eEBdiQ0N9mbi6alkDtl6y6N/NyingsnOwVtpf+yok6qyjS4LpTCKqlk+xkrWSK3v2YV3R1jZyyIJI1Al3n8YGEw/YneqMwhjcNCRPV6sSDGjCINAa913tQDb8cxBkYWJ/jeH9UuXptOQMVezjV+C2iO0XwEEP5ibRBQAiOxgwvw9qUkAbOPPQjd1qsVruFBnn8+pzwMM/GPIsr3nThOlln8sBYeJ/HrCAv+MjqqHDhwa2cRaOXq5hp8LT8TK4fFbG1AxvO3eiF1m31vW0DsTYzLVQ2w8gKQwgsHdlxS+899vKmPkEd+qqJiyw+GAe2sQNROHfuUP5BDMKathMISMVc2AaYtfyyQuN+vzxPeO03JeP2yHJjVMibkfT/7KiLXbeq/GBJQY2vbH19UdueGclKnEugoE7S0Lik03Y7twK3f+x4J1rk1dwIsP5CYqYMZERD+tLAAAA=","fx_smoke":"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","star_on":"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","star_off":"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","bg_kitchen":"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"}; }
var ING={rice:'Rice',chicken:'Raw chicken',eggs:'Eggs',noodles:'Noodles',coconut_milk:'Coconut milk',flour:'Flour',bok_choy:'Bok choy',chilli:'Chilli',bread:'Bread',beef:'Raw beef',ice:'Ice cubes',red_beans:'Red beans',milk:'Milk',fish:'Raw fish',potato:'Potatoes',pineapple:'Pineapple',sugar:'Sugar',tea_leaves:'Tea leaves'};
var WARE={wok:'Wok',pot:'Soup pot',grill:'Charcoal grill',steamer:'Steamer',pan:'Frying pan'};
var RECIPES={
fried_rice:{name:'Fried rice',say:'One plate of fried rice, please.',ing:['rice','eggs','chicken'],ware:'wok',cooked:'cook_wok_fried_rice',dish:'dish_fried_rice',
wareWhy:'Fried rice is stirred over high heat, so a wok works best.',heat:'Turn on the stove',cookTxt:'Stirring it all together...',
tip:'Raw chicken must be cooked right through, with no pink inside. Wash your hands after touching it.'},
fried_noodles:{name:'Fried noodles',say:'One plate of fried noodles, please.',ing:['noodles','eggs','bok_choy'],ware:'wok',cooked:'cook_wok_noodles',dish:'dish_mee_goreng',
wareWhy:'Noodles are stir-fried, so use the wok.',heat:'Turn on the stove',cookTxt:'Stir-frying the noodles...',
tip:'Keep raw food and cooked food on separate boards and plates.'},
laksa:{name:'Noodle soup, laksa style',say:'One bowl of laksa-style noodle soup, please.',ing:['noodles','coconut_milk','chilli'],ware:'pot',cooked:'cook_pot_laksa',burnt:'cook_pot_laksa_burnt',dish:'dish_laksa',
wareWhy:'Soup needs a deep pot to hold the broth.',heat:'Turn on the stove',cookTxt:'Simmering the soup...',
tip:'Hot soup can scald. Carry the bowl with two hands and tell a grown-up if you spill.'},
kaya_toast:{name:'Kaya toast',say:'One kaya toast, please. Kaya is a jam made from coconut milk, eggs and sugar.',ing:['bread','coconut_milk','eggs','sugar'],ware:'grill',cooked:null,grillItem:'bread',dish:'dish_kaya_toast',
wareWhy:'Toast is browned over a grill, not boiled in a pot.',heat:'Light the grill',cookTxt:'Toasting the bread...',
tip:'Wash your hands before you touch food, and keep eggs away from food that will not be cooked.'},
fishball_soup:{name:'Fish ball noodle soup',say:'One bowl of fish ball noodle soup, please.',ing:['noodles','fish','bok_choy'],ware:'pot',cooked:'cook_pot_fishball_soup',dish:'dish_fishball_noodles_pack',
wareWhy:'Soup needs a deep pot to hold the broth.',heat:'Turn on the stove',cookTxt:'Simmering the soup...',
tip:'Raw fish must be cooked through, and kept apart from cooked food.'},
teh_tarik:{name:'Teh tarik (milk tea)',say:'One teh tarik, please.',ing:['tea_leaves','milk','sugar'],ware:'pot',cooked:'cook_pot_broth',dish:'dish_teh_tarik',
wareWhy:'Tea is brewed in a pot of hot water.',heat:'Turn on the stove',cookTxt:'Brewing the tea...',
tip:'Hot drinks can scald. Let the cup cool a little before you sip.'},
satay:{name:'Chicken satay',say:'One serving of chicken satay, please.',ing:['chicken','sugar','coconut_milk'],ware:'grill',cooked:'cook_grill_skewers',dish:'dish_satay',
wareWhy:'Satay sticks are grilled over charcoal, not boiled.',heat:'Light the grill',cookTxt:'Grilling the sticks...',
tip:'Chicken must be cooked right through, with no pink inside. Wash your hands after touching raw chicken.'}
};
var ORDER=['fried_rice','fried_noodles','laksa','kaya_toast','fishball_soup','teh_tarik','satay'];
function $(t,c,h){var e=document.createElement(t); if(c)e.className=c; if(h!=null)e.innerHTML=h; return e;}
function shuffle(a){a=a.slice(); for(var i=a.length-1;i>0;i--){var j=Math.floor(Math.random()*(i+1)),t=a[i];a[i]=a[j];a[j]=t;} return a;}
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});}
function list(a){return a.length<2?a.join(''):a.slice(0,-1).join(', ')+' and '+a[a.length-1];}
function loadImgs(cb){
if(CK_IMG){cb(CK_IMG);return;}
var A=ckAssets(), out={}, n=0, total=0, k;
for(k in A)total++;
for(k in A){(function(k){var im=new Image(); im.onload=im.onerror=function(){n++; if(n===total){CK_IMG=out; cb(out);}}; im.src='data:image/webp;base64,'+A[k]; out[k]=im.src;})(k);}
}
function open(opts,done){
opts=opts||{}; if(CK_BUSY)return; CK_BUSY=true;
var acc=window.__accS||{}, rm=opts.reduceMotion!=null?!!opts.reduceMotion:!!(acc.rm||(typeof reduceMotion!=='undefined'&&reduceMotion));
var noT=opts.noTimers!=null?!!opts.noTimers:!!acc.noTimers;
var id=RECIPES[opts.dish]?opts.dish:ORDER[Math.floor(Math.random()*ORDER.length)], R=RECIPES[id];
var prevFocus=document.activeElement, timers=[], finished=false, root=$('div'); root.id='ckRoot'; if(rm)root.className='ckRM'; root.setAttribute('role','dialog'); root.setAttribute('aria-modal','true'); root.setAttribute('aria-label','Kitchen order: '+R.name);
var st={phase:'pick',picked:[],mist:0,ware:null,safe:false,hinted:0};
root.innerHTML='
Setting up the kitchen...
';
document.body.appendChild(root);
function finish(res){ if(finished)return; finished=true; timers.forEach(clearTimeout); document.removeEventListener('keydown',onKey,true); if(root.parentNode)root.parentNode.removeChild(root); CK_BUSY=false; try{if(prevFocus&&prevFocus.focus)prevFocus.focus();}catch(e){} if(typeof done==='function'){try{done(res);}catch(e){console.error(e);}} if(typeof opts.onClose==='function'){try{opts.onClose();}catch(e){}} }
function cancel(){ finish({stars:0,dish:id,mistakes:st.mist,safe:st.safe,cancelled:true}); }
function onKey(e){
if(e.key==='Escape'||e.key==='Esc'){e.preventDefault();e.stopPropagation();cancel();return;}
if(e.key==='Tab'){var f=[].slice.call(root.querySelectorAll('button:not([disabled])')); if(!f.length)return; var i=f.indexOf(document.activeElement);
if(e.shiftKey&&(i<=0)){e.preventDefault();f[f.length-1].focus();} else if(!e.shiftKey&&(i===f.length-1||i<0)){e.preventDefault();f[0].focus();}}
}
document.addEventListener('keydown',onKey,true);
root.querySelector('.ckBack').addEventListener('click',cancel);
loadImgs(function(IM){ if(finished)return; build(IM); });
function build(IM){
var main=root.querySelector('.ckMain');
var decoyI=shuffle(Object.keys(ING).filter(function(k){return R.ing.indexOf(k)<0;})).slice(0,8-R.ing.length);
var ings=shuffle(R.ing.concat(decoyI));
var wares=shuffle([R.ware].concat(shuffle(Object.keys(WARE).filter(function(k){return k!==R.ware;})).slice(0,3)));
var grill=R.ware==='grill';
main.innerHTML=
'
2Cook it in the '+esc(WARE[R.ware].toLowerCase())+'
'+
'
3'+(grill?'Light the grill':'Turn on the stove')+' and cook, then plate it
'+
'
'+
''+
'
'+
'
Ingredients
Cookware
'+
'
';
var stage=main.querySelector('#ckStage'), picked=main.querySelector('#ckPicked'), msg=root.querySelector('#ckMsg');
var heatBtn=main.querySelector('#ckHeat'), washBtn=main.querySelector('#ckWash');
function put(cls,key,css,alt){var im=new Image(); im.src=IM[key]; im.alt=alt||''; im.className=cls||''; im.style.cssText=css; stage.appendChild(im); return im;}
/* counter layout, % of the kitchen picture: board left, stove or grill in the middle */
put('', 'ware_board','left:66%;bottom:36%;width:14%');
var stove=null, wareImg=null;
if(!grill) stove=put('','ware_stove_off','left:39%;bottom:36%;width:22%');
var thumbs=[];
function say(t,k){msg.className='ckMsg'+(k?' '+k:''); msg.textContent=t;}
function setSteps(){
var o=main.querySelectorAll('.ckOrder li'); var ph=st.phase, idx={pick:0,ware:1,heat:2,cook:2,plate:2,done:3}[ph];
for(var i=0;iOn the board:':'Nothing on the board yet.';
st.picked.forEach(function(k){picked.appendChild($('span','ckChip',''+esc(ING[k])));});
}
function mist(t){st.mist++; st.hinted++; say(t,'hint');}
function missing(){return R.ing.filter(function(k){return st.picked.indexOf(k)<0;});}
function focusNext(){ var t=null; if(st.phase==='ware')t=main.querySelector('#ckWare button'); else if(st.phase==='heat')t=heatBtn; else if(st.phase==='plate')t=main.querySelector('#ckPlate'); if(t)setTimeout(function(){try{t.focus({preventScroll:true});}catch(e){}},30); }
/* ingredients */
var ingBox=main.querySelector('#ckIng');
ings.forEach(function(k){
var b=$('button','ckBtn',''+esc(ING[k])+''); b.type='button'; b.setAttribute('data-k',k); b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){
if(finished)return;
if(st.phase!=='pick'){ if(st.phase==='ware')say('The ingredients are ready. Now choose the cookware.','hint'); return; }
if(st.picked.indexOf(k)>=0){say('You already added '+ING[k].toLowerCase()+'.','hint');return;}
if(R.ing.indexOf(k)<0){
var left=missing();
mist(st.hinted>=1?'That one is not in this order. Still to add: '+list(left.map(function(x){return ING[x].toLowerCase();}))+'.':'That one is not in this order. Look at the order card and try again.');
return; }
st.picked.push(k); b.setAttribute('aria-pressed','true');
var n=st.picked.length, th=put('ckPop','ing_'+k,'left:'+(67+(n-1)*4.2)+'%;bottom:'+(46+(n%2)*2)+'%;width:7.5%'); thumbs.push(th);
renderPicked();
if(!missing().length){ st.phase='ware'; setSteps(); say('Good. Everything is on the board. Now choose the cookware.','good'); focusNext(); }
else say('Good. '+missing().length+' more to add.','good');
});
ingBox.appendChild(b);
});
/* cookware */
var wareBox=main.querySelector('#ckWare');
wares.forEach(function(k){
var b=$('button','ckBtn',''+esc(WARE[k])+''); b.type='button'; b.setAttribute('aria-pressed','false');
b.addEventListener('click',function(){
if(finished)return;
if(st.phase==='pick'){ mist('Add all the ingredients first: '+list(missing().map(function(x){return ING[x].toLowerCase();}))+'.'); return; }
if(st.phase!=='ware'){return;}
if(k!==R.ware){ mist(R.wareWhy); return; }
st.ware=k; st.phase='heat'; b.setAttribute('aria-pressed','true'); setSteps();
thumbs.forEach(function(t){t.style.display='none';});
var css=grill?'left:38%;bottom:40%;width:24%':'left:39%;bottom:52%;width:22%';
wareImg=put('ckPop','ware_'+k,css);
if(grill)wareImg.className+=' ckNo';
if(grill&&R.grillItem){put('','ing_'+R.grillItem,'left:45%;bottom:60%;width:9%');}
say(grill?'Good. Now light the grill.':'Good. Now turn on the stove.','good'); focusNext();
});
wareBox.appendChild(b);
});
/* wash hands bonus */
washBtn.addEventListener('click',function(){ if(finished||st.safe)return; st.safe=true; washBtn.setAttribute('aria-pressed','true'); washBtn.textContent='Hands washed'; washBtn.disabled=true; say('Good habit. Clean hands keep food safe.','good'); try{heatBtn.focus({preventScroll:true});}catch(e){} });
/* heat */
heatBtn.addEventListener('click',function(){
if(finished)return;
if(st.phase==='pick'){ mist('First add the ingredients, then put them in the cookware.'); return; }
if(st.phase==='ware'){ mist('First choose the cookware to cook in.'); return; }
if(st.phase!=='heat')return;
st.phase='cook'; heatBtn.disabled=true; setSteps();
if(stove){stove.src=IM.ware_stove_on;}
if(wareImg){ wareImg.classList.remove('ckNo'); if(R.cooked&&IM[R.cooked])wareImg.src=IM[R.cooked]; }
var wait=(rm||noT)?0:2600;
var base=grill?{l:46,b:74}:{l:47,b:76};
var puffs=[];
[['steam_l',0,0],['steam_m',-5,.5],['steam_s',5,1]].forEach(function(p){var s=put('ckSteam'+(wait?'':' still'),'fx_'+p[0],'left:'+(base.l+p[1])+'%;bottom:'+base.b+'%;width:'+(p[0]==='steam_l'?8:p[0]==='steam_m'?6:4.5)+'%;animation-delay:'+p[2]+'s'); puffs.push(s);});
say(wait?R.cookTxt:'Ready. Cooked through and piping hot.', wait?'':'good');
function ready(){ if(finished)return; st.phase='plate'; setSteps(); puffs.forEach(function(p){p.classList.add('still');}); say('Cooked through. Now plate it.','good');
var pb=$('button','ckAct main wide','Plate it'); pb.type='button'; pb.id='ckPlate'; main.querySelector('.ckActions').appendChild(pb); pb.addEventListener('click',plate); focusNext(); }
if(wait)timers.push(setTimeout(ready,wait)); else ready();
if(!rm&&!noT)timers.push(setTimeout(function(){ if(finished||st.phase!=='plate')return; st.burnt=true;
if(R.burnt&&IM[R.burnt]&&wareImg)wareImg.src=IM[R.burnt]; else if(wareImg)wareImg.classList.add('ckBurnt');
put('ckSteam still','fx_smoke','left:'+(base.l-1)+'%;bottom:'+(base.b+6)+'%;width:6%');
say('It is getting dark. Take it off the heat and plate it. It is still fine to serve.','hint'); },(opts.burnMs||12000)+wait));
});
function plate(){
if(finished||st.phase!=='plate')return; st.phase='done';
var stars=st.mist===0?3:(st.mist<=2?2:1);
Array.prototype.slice.call(stage.querySelectorAll('img:not(.ckBg)')).forEach(function(i){i.style.display='none';});
var dimg=new Image(); dimg.src=IM[R.dish]; dimg.alt=R.name; dimg.className='ckPop'; dimg.style.cssText='left:42%;bottom:37%;width:16%;height:auto';
stage.appendChild(dimg);
if(!rm){var sp=new Image(); sp.src=IM.fx_sparkle; sp.alt=''; sp.className='ckPop'; sp.style.cssText='left:56%;bottom:62%;width:10%'; stage.appendChild(sp);}
var res=$('div','ckResult');
var sd=$('div','ckStars'); for(var i=0;i<3;i++){var s=new Image(); s.src=IM[i'+esc(R.name)+' '+stars+' of 3 stars');
res.appendChild(sd); res.appendChild(t);
if(st.safe)res.appendChild($('span','ckSafe','Safe cook: hands washed'));
res.appendChild($('p','ckTip','Kitchen tip: '+esc(R.tip)));
res.setAttribute('role','img'); res.setAttribute('aria-label',R.name+', '+stars+' of 3 stars');
main.querySelector('.ckL').insertBefore(res,picked);
var how=stars===3?'No wrong picks. Well done.':(stars===2?'Nearly perfect. A few wrong picks along the way.':'Cooked and served. Try again to get more stars.');
say(how,'good');
main.querySelector('.ckActions').innerHTML='';
var ok=$('button','ckAct main wide','Continue'); ok.type='button'; main.querySelector('.ckActions').appendChild(ok);
ok.addEventListener('click',function(){ finish({stars:stars,dish:id,mistakes:st.mist,safe:st.safe}); });
setSteps(); setTimeout(function(){try{ok.focus({preventScroll:true});}catch(e){}},30);
}
renderPicked(); setSteps(); say('Read the order card, then tap the ingredients you need.');
var first=ingBox.querySelector('button'); if(first)setTimeout(function(){try{first.focus({preventScroll:true});}catch(e){}},30);
}
}
return open;
})();
/* === COOKSTEP HOOK POINT ===
The Food-path shift calls this once per cooking round: foodCookRound(function(res){ ... }) res = {stars,dish,mistakes,safe} (or cancelled:true).
Nothing here awards money or blocks; the caller decides. */
function foodCookRound(cb,dishId){ cookStep({dish:dishId}, cb||function(){}); }
/* Temporary test button: only exists when localStorage mbCookTest=1 */
(function(){ try{ if(localStorage.getItem('mbCookTest')!=='1')return; }catch(e){return;}
function mk(){ var b=document.createElement('button'); b.id='ckTestBtn'; b.type='button'; b.textContent='Test: cooking step';
b.addEventListener('click',function(){ foodCookRound(function(r){ window.__ckLast=r; b.textContent='Test: cooking step (last: '+(r.cancelled?'left':r.stars+' stars, '+r.dish)+')'; }); });
document.body.appendChild(b); }
if(document.body)mk(); else document.addEventListener('DOMContentLoaded',mk); })();
})();