PageSourceSearch

https://smartmaths.ai/assets/main-Dt7Bgl8i.js

js smartmaths.ai collected 2026-10-05 15:05:57 UTC 3,186,358 bytes, 71 lines download raw bytes

1const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/AuthoringReviewScreen-LXcL6Gua.js","assets/curriculum-data-UeTaQjv3.js","assets/worked-examples-BExh4ce1.js","assets/CoverageDashboard-BL4576C7.js","assets/QaDashboard-UC3iQJlS.js","assets/reviewLabels-Svnz85TS.js","assets/QaDashboard-D3NP9HZo.css","assets/CalibrationPage-Ds5-qKgG.js","assets/CalibrationPage-DX2Heg1-.css","assets/QuestionPreview-DNfxQ1g7.js","assets/QuestionPreview-zn7Gc_t-.css","assets/WexReviewScreen-CCGVrCOJ.js","assets/WexReviewScreen-BcZqhEaj.css","assets/MiaLabScreen-soHhDFtK.js","assets/MiaLabScreen-BUAriQ8c.css"])))=>i.map(i=>d[i]);
2import{s as sessionPolicy,p as preAnswerPolicyRaw}from"./curriculum-data-UeTaQjv3.js";import{r as rpP3M04Raw,a as rpP3M03Raw,b as rpP3M02Raw,c as rpP3M01Raw,g as geoP6M06Raw,d as geoP6M05Raw,e as geoP6M04Raw,f as fdpP6M06Raw,h as fdpP6M05Raw,i as fdpP6M04Raw,j as dsP5M03Raw,k as dsP5M02Raw,l as dsP5M01Raw,m as mdP5M07Raw,n as mdP5M06Raw,o as mdP5M05Raw,p as asP6M10Raw,q as asP6M09Raw,s as asP6M08Raw,t as asP6M07Raw,u as meaP7M05Raw,v as npvP7M06Raw,w as npvP7M05Raw,x as npvP7M04Raw,y as npvP7M03Raw,z as rpP1M02Raw,A as rpP1M01Raw,B as npvP5M07Raw,C as npvP5M04Raw,D as npvP5M02Raw,E as meaP7M01Raw,F as meaP6M04Raw,G as meaP5M05Raw,H as meaP7M07Raw,I as meaP5M04Raw,J as meaP5M03Raw,K as mdP3M05Raw,L as mdP3M04Raw,M as mdP3M03Raw,N as mdP3M01Raw,O as geoP5M02Raw,P as geoP5M01Raw,Q as geoP4M02Raw,R as fdpP5M04Raw,S as fdpP6M07Raw,T as fdpP5M03Raw,U as fdpP5M02Raw,V as fdpP5M01Raw,W as fdpP3M04Raw,X as fdpP3M03Raw,Y as dsP3M01Raw,Z as dsP2M05Raw,_ as dsP2M04Raw,$ as dsP2M03Raw,a0 as asP5M02Raw,a1 as asP5M01Raw,a2 as npvP5M03Raw,a3 as rpP1M03Raw,a4 as dsP3M03Raw,a5 as dsP3M02Raw,a6 as meaP7M03Raw,a7 as meaP7M02Raw,a8 as geoP5M04Raw,a9 as geoP5M03Raw,aa as geoP4M05Raw,ab as geoP4M03Raw,ac as fdpP4M04Raw,ad as fdpP4M03Raw,ae as fdpP4M02Raw,af as fdpP4M01Raw,ag as mdP4M03Raw,ah as mdP4M02Raw,ai as mdP4M01Raw,aj as mdP3M02Raw,ak as asP5M05Raw,al as asP5M04Raw,am as asP5M03Raw,an as npvP6M04Raw,ao as npvP6M03Raw,ap as npvP6M02Raw,aq as npvP6M01Raw,ar as npvP5M06Raw,as as npvP5M05Raw,at as npvP4M05Raw,au as npvP4M04Raw,av as npvP4M03Raw,aw as npvP4M02Raw,ax as npvP4M01Raw,ay as meaP6M03Raw,az as meaP5M01Raw,aA as meaP4M05Raw,aB as meaP4M03Raw,aC as mdP2M05Raw,aD as mdP2M04Raw,aE as mdP2M03Raw,aF as mdP2M02Raw,aG as geoP4M01Raw,aH as geoP3M05Raw,aI as geoP3M03Raw,aJ as fdpP3M02Raw,aK as fdpP3M01Raw,aL as fdpP2M03Raw,aM as dsP2M02Raw,aN as asP4M07Raw,aO as asP4M06Raw,aP as asP4M05Raw,aQ as asP4M04Raw,aR as asP4M03Raw,aS as asP4M01Raw,aT as mdP2M01Raw,aU as fdpP2M04Raw,aV as dsP2M01Raw,aW as asP4M08Raw,aX as meaP3M03Raw,aY as meaP6M02Raw,aZ as meaP6M01Raw,a_ as meaP4M04Raw,a$ as meaP4M02Raw,b0 as meaP4M01Raw,b1 as geoP3M02Raw,b2 as geoP3M01Raw,b3 as fdpP2M02Raw,b4 as fdpP2M01Raw,b5 as fdpP1M03Raw,b6 as fdpP1M02Raw,b7 as fdpP1M01Raw,b8 as dsP1M02Raw,b9 as asP4M02Raw,ba as mdP1M03Raw,bb as mdP1M02Raw,bc as npvP3M03Raw,bd as npvP3M02Raw,be as npvP3M01Raw,bf as npvP2M04Raw,bg as npvP2M03Raw,bh as npvP2M02Raw,bi as npvP2M01Raw,bj as asP2M04Raw,bk as asP2M02Raw,bl as asP3M02Raw,bm as asP3M05Raw,bn as asP3M04Raw,bo as asP2M03Raw,bp as asP3M01Raw,bq as asP3M03Raw,br as rpP2M03Raw,bs as rpP2M02Raw,bt as rpP2M01Raw,bu as npvP7M02Raw,bv as npvP7M01Raw,bw as npvP6M05Raw,bx as meaP7M06Raw,by as meaP7M04Raw,bz as mdP5M04Raw,bA as mdP5M03Raw,bB as mdP5M02Raw,bC as mdP5M01Raw,bD as mdP4M05Raw,bE as mdP4M04Raw,bF as geoP6M03Raw,bG as geoP6M02Raw,bH as geoP6M01Raw,bI as fdpP6M03Raw,bJ as fdpP6M02Raw,bK as fdpP6M09Raw,bL as fdpP6M08Raw,bM as fdpP6M01Raw,bN as dsP4M03Raw,bO as dsP4M02Raw,bP as dsP4M01Raw,bQ as asP6M06Raw,bR as asP6M05Raw,bS as asP6M04Raw,bT as asP6M03Raw,bU as asP6M02Raw,bV as asP6M01Raw,bW as asP4M07MissingAddendRaw,bX as asP4M06NearDoubleRaw,bY as asP4M06MissingAddendRaw,bZ as asP4M05MissingAddendRaw,b_ as asP4M04MissingAddendRaw,b$ as asP4M02MissingAddendRaw,c0 as asP4M01MissingStartRaw,c1 as meaP5M04ComparisonStatementRaw,c2 as meaP5M03ComparisonStatementRaw,c3 as meaP5M03SymbolicChoiceRaw,c4 as meaP5M03ContextReasoningRaw,
vendor: 6,391 bytes, line 2
2c5 as fdpP4M02VariedSymbolicRaw,c6 as fdpP3M03EquationCompletionRaw,c7 as geoP4M01CompareRaw,c8 as dsP2M03ReadRaw,c9 as dsP2M02MostRaw,ca as geoP4M01DirRaw,cb as geoP3M03NameRaw,cc as mdP2M05EquationCompletionRaw,cd as mdP2M04FactFamilyRaw,ce as meaP5M01UnitsRaw,cf as npvP4M01BuildRaw,cg as npvP3M02TeenIdRaw,ch as npvP2M01CountRaw,ci as npvP2M01WordRaw,cj as asP3M03DoublingRaw,ck as asP3M01NearDoubleRaw,cl as meaP4M04ComparativeLengthRaw,cm as meaP4M02CoinTapRaw,cn as asP2M02DominoRaw,co as asP3M04DiffRaw,cp as mdP1M02CountGroupsRaw,cq as rpP2M02MisconceptionContrastProbeRaw,cr as rpP2M01RatioInterpretationRaw,cs as npvP7M02RoundingReverseIdentifyRaw,ct as npvP7M01SequenceContinueRaw,cu as npvP7M01OrderingLargeNumbersRaw,cv as npvP7M01DigitValueEntryRaw,cw as npvP7M01ComparisonRaw,cx as npvP6M05NumberLineIdentifyRaw,cy as meaP7M06TimeFindStartTimeRaw,cz as meaP7M06TimeFindEndTimeRaw,cA as meaP7M04InverseAreaMissingSideRaw,cB as mdP5M04CubeNumberEvaluateRaw,cC as mdP5M02MultipleFindRaw,cD as mdP5M02ClassifyRaw,cE as mdP4M05ContextReasoningRaw,cF as geoP6M03ImpossibleTriangleCheckRaw,cG as geoP6M02ComparisonStatementRaw,cH as fdpP6M03FractionIdentificationRaw,cI as fdpP6M08EquationCompletionRaw,cJ as dsP4M03PieChartFractionReadRaw,cK as dsP4M03ComparisonStatementRaw,cL as dsP4M02TrendShapeDescriptionRaw,cM as dsP4M02TrendPredictionRaw,cN as dsP4M02TrendDirectionReadRaw,cO as dsP4M01LineGraphDifferenceRaw,cP as asP6M06RoundingEntryRaw,cQ as asP6M05EstimateSufficiencyRaw,cR as asP6M05EstimateReasonablenessCheckRaw,cS as asP6M04EquationCompletionRaw,cT as asP6M03MissingAddendRaw,cU as asP6M01RoundingEntryRaw,cV as asP6M01MissingAddendRaw,cW as dsP5M03TrendPredictionRaw,cX as asP6M10MisconceptionContrastProbeRaw,cY as preQ102Raw,cZ as postQ102Raw}from"./worked-examples-BExh4ce1.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))s(o);new MutationObserver(o=>{for(const r of o)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&s(a)}).observe(document,{childList:!0,subtree:!0});function e(o){const r={};return o.integrity&&(r.integrity=o.integrity),o.referrerPolicy&&(r.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?r.credentials="include":o.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function s(o){if(o.ep)return;o.ep=!0;const r=e(o);fetch(o.href,r)}})();function getDefaultExportFromCjs(n){return n&&n.__esModule&&Object.prototype.hasOwnProperty.call(n,"default")?n.default:n}var jsxRuntime={exports:{}},reactJsxRuntime_production={};var hasRequiredReactJsxRuntime_production;function requireReactJsxRuntime_production(){if(hasRequiredReactJsxRuntime_production)return reactJsxRuntime_production;hasRequiredReactJsxRuntime_production=1;var n=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function e(s,o,r){var a=null;if(r!==void 0&&(a=""+r),o.key!==void 0&&(a=""+o.key),"key"in o){r={};for(var i in o)i!=="key"&&(r[i]=o[i])}else r=o;return o=r.ref,{$$typeof:n,type:s,key:a,ref:o!==void 0?o:null,props:r}}return reactJsxRuntime_production.Fragment=t,reactJsxRuntime_production.jsx=e,reactJsxRuntime_production.jsxs=e,reactJsxRuntime_production}var hasRequiredJsxRuntime;function requireJsxRuntime(){return hasRequiredJsxRuntime||(hasRequiredJsxRuntime=1,jsxRuntime.exports=requireReactJsxRuntime_production()),jsxRuntime.exports}var jsxRuntimeExports=requireJsxRuntime(),react={exports:{}},react_production={};var hasRequiredReact_production;function requireReact_production(){if(hasRequiredReact_production)return react_production;hasRequiredReact_production=1;var n=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),e=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),r=Symbol.for("react.consumer"),a=Symbol.for("react.context"),i=Symbol.for("react.forward_ref"),l=Symbol.for("react.suspense"),u=Symbol.for("react.memo"),d=Symbol.for("react.lazy"),m=Symbol.for("react.activity"),h=Symbol.iterator;function f(B){return B===null||typeof B!="object"?null:(B=h&&B[h]||B["@@iterator"],typeof B=="function"?B:null)}var g={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b=Object.assign,x={};function y(B,G,X){this.props=B,this.context=G,this.refs=x,this.updater=X||g}y.prototype.isReactComponent={},y.prototype.setState=function(B,G){if(typeof B!="object"&&typeof B!="function"&&B!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,B,G,"setState")},y.prototype.forceUpdate=function(B){this.updater.enqueueForceUpdate(this,B,"forceUpdate")};function E(){}E.prototype=y.prototype;function k(B,G,X){this.props=B,this.context=G,this.refs=x,this.updater=X||g}var T=k.prototype=new E;T.constructor=k,b(T,y.prototype),T.isPureReactComponent=!0;var A=Array.isArray;function N(){}var S={H:null,A:null,T:null,S:null},M=Object.prototype.hasOwnProperty;function L(B,G,X){var ne=X.ref;return{$$typeof:n,type:B,key:G,ref:ne!==void 0?ne:null,props:X}}function P(B,G){return L(B.type,G,B.props)}function F(B){return typeof B=="object"&&B!==null&&B.$$typeof===n}function D(B){var G={"=":"=0",":":"=2"};return"$"+B.replace(/[=:]/g,function(X){return G[X]})}var U=/\/+/g;function q(B,G){return typeof B=="object"&&B!==null&&B.key!=null?D(""+B.key):G.toString(36)}function V(B){switch(B.status){case"fulfilled":return B.value;case"rejected":throw B.reason;default:switch(typeof B.status=="string"?B.then(N,N):(B.status="pending",B.then(function(G){B.status==="pending"&&(B.status="fulfilled",B.value=G)},function(G){B.status==="pending"&&(B.status="rejected",B.reason=G)})),B.status){case"fulfilled":return B.value;case"rejected":throw B.reason}}throw B}function O(B,G,X,ne,se){var te=typeof B;(te==="undefined"||te==="boolean")&&(B=null);var oe=!1;if(B===null)oe=!0;else switch(te){case"bigint":case"string":case"number":oe=!0;break;case"object":switch(B.$$typeof){case n:case t:oe=!0;break;case d:return oe=B._init,O(oe(B._payload),G,X,ne,se)}}if(oe)return se=se(B),oe=ne===""?"."+q(B,0):ne,A(se)?(X="",oe!=null&&(X=oe.replace(U,"$&/")+"/"),O(se,G,X,"",function(ie){return ie}
vendor: 15,654 bytes, line 2
2)):se!=null&&(F(se)&&(se=P(se,X+(se.key==null||B&&B.key===se.key?"":(""+se.key).replace(U,"$&/")+"/")+oe)),G.push(se)),1;oe=0;var le=ne===""?".":ne+":";if(A(B))for(var ae=0;ae<B.length;ae++)ne=B[ae],te=le+q(ne,ae),oe+=O(ne,G,X,te,se);else if(ae=f(B),typeof ae=="function")for(B=ae.call(B),ae=0;!(ne=B.next()).done;)ne=ne.value,te=le+q(ne,ae++),oe+=O(ne,G,X,te,se);else if(te==="object"){if(typeof B.then=="function")return O(V(B),G,X,ne,se);throw G=String(B),Error("Objects are not valid as a React child (found: "+(G==="[object Object]"?"object with keys {"+Object.keys(B).join(", ")+"}":G)+"). If you meant to render a collection of children, use an array instead.")}return oe}function z(B,G,X){if(B==null)return B;var ne=[],se=0;return O(B,ne,"","",function(te){return G.call(X,te,se++)}),ne}function K(B){if(B._status===-1){var G=B._result;G=G(),G.then(function(X){(B._status===0||B._status===-1)&&(B._status=1,B._result=X)},function(X){(B._status===0||B._status===-1)&&(B._status=2,B._result=X)}),B._status===-1&&(B._status=0,B._result=G)}if(B._status===1)return B._result.default;throw B._result}var Z=typeof reportError=="function"?reportError:function(B){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var G=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof B=="object"&&B!==null&&typeof B.message=="string"?String(B.message):String(B),error:B});if(!window.dispatchEvent(G))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",B);return}console.error(B)},Q={map:z,forEach:function(B,G,X){z(B,function(){G.apply(this,arguments)},X)},count:function(B){var G=0;return z(B,function(){G++}),G},toArray:function(B){return z(B,function(G){return G})||[]},only:function(B){if(!F(B))throw Error("React.Children.only expected to receive a single React element child.");return B}};return react_production.Activity=m,react_production.Children=Q,react_production.Component=y,react_production.Fragment=e,react_production.Profiler=o,react_production.PureComponent=k,react_production.StrictMode=s,react_production.Suspense=l,react_production.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=S,react_production.__COMPILER_RUNTIME={__proto__:null,c:function(B){return S.H.useMemoCache(B)}},react_production.cache=function(B){return function(){return B.apply(null,arguments)}},react_production.cacheSignal=function(){return null},react_production.cloneElement=function(B,G,X){if(B==null)throw Error("The argument must be a React element, but you passed "+B+".");var ne=b({},B.props),se=B.key;if(G!=null)for(te in G.key!==void 0&&(se=""+G.key),G)!M.call(G,te)||te==="key"||te==="__self"||te==="__source"||te==="ref"&&G.ref===void 0||(ne[te]=G[te]);var te=arguments.length-2;if(te===1)ne.children=X;else if(1<te){for(var oe=Array(te),le=0;le<te;le++)oe[le]=arguments[le+2];ne.children=oe}return L(B.type,se,ne)},react_production.createContext=function(B){return B={$$typeof:a,_currentValue:B,_currentValue2:B,_threadCount:0,Provider:null,Consumer:null},B.Provider=B,B.Consumer={$$typeof:r,_context:B},B},react_production.createElement=function(B,G,X){var ne,se={},te=null;if(G!=null)for(ne in G.key!==void 0&&(te=""+G.key),G)M.call(G,ne)&&ne!=="key"&&ne!=="__self"&&ne!=="__source"&&(se[ne]=G[ne]);var oe=arguments.length-2;if(oe===1)se.children=X;else if(1<oe){for(var le=Array(oe),ae=0;ae<oe;ae++)le[ae]=arguments[ae+2];se.children=le}if(B&&B.defaultProps)for(ne in oe=B.defaultProps,oe)se[ne]===void 0&&(se[ne]=oe[ne]);return L(B,te,se)},react_production.createRef=function(){return{current:null}},react_production.forwardRef=function(B){return{$$typeof:i,render:B}},react_production.isValidElement=F,react_production.lazy=function(B){return{$$typeof:d,_payload:{_status:-1,_result:B},_init:K}},react_production.memo=function(B,G){return{$$typeof:u,type:B,compare:G===void 0?null:G}},react_production.startTransition=function(B){var G=S.T,X={};S.T=X;try{var ne=B(),se=S.S;se!==null&&se(X,ne),typeof ne=="object"&&ne!==null&&typeof ne.then=="function"&&ne.then(N,Z)}catch(te){Z(te)}finally{G!==null&&X.types!==null&&(G.types=X.types),S.T=G}},react_production.unstable_useCacheRefresh=function(){return S.H.useCacheRefresh()},react_production.use=function(B){return S.H.use(B)},react_production.useActionState=function(B,G,X){return S.H.useActionState(B,G,X)},react_production.useCallback=function(B,G){return S.H.useCallback(B,G)},react_production.useContext=function(B){return S.H.useContext(B)},react_production.useDebugValue=function(){},react_production.useDeferredValue=function(B,G){return S.H.useDeferredValue(B,G)},react_production.useEffect=function(B,G){return S.H.useEffect(B,G)},react_production.useEffectEvent=function(B){return S.H.useEffectEvent(B)},react_production.useId=function(){return S.H.useId()},react_production.useImperativeHandle=function(B,G,X){return S.H.useImperativeHandle(B,G,X)},react_production.useInsertionEffect=function(B,G){return S.H.useInsertionEffect(B,G)},react_production.useLayoutEffect=function(B,G){return S.H.useLayoutEffect(B,G)},react_production.useMemo=function(B,G){return S.H.useMemo(B,G)},react_production.useOptimistic=function(B,G){return S.H.useOptimistic(B,G)},react_production.useReducer=function(B,G,X){return S.H.useReducer(B,G,X)},react_production.useRef=function(B){return S.H.useRef(B)},react_production.useState=function(B){return S.H.useState(B)},react_production.useSyncExternalStore=function(B,G,X){return S.H.useSyncExternalStore(B,G,X)},react_production.useTransition=function(){return S.H.useTransition()},react_production.version="19.2.4",react_production}var hasRequiredReact;function requireReact(){return hasRequiredReact||(hasRequiredReact=1,react.exports=requireReact_production()),react.exports}var reactExports=requireReact();const React=getDefaultExportFromCjs(reactExports);var client={exports:{}},reactDomClient_production={},scheduler={exports:{}},scheduler_production={};var hasRequiredScheduler_production;function requireScheduler_production(){return hasRequiredScheduler_production||(hasRequiredScheduler_production=1,(function(n){function t(O,z){var K=O.length;O.push(z);e:for(;0<K;){var Z=K-1>>>1,Q=O[Z];if(0<o(Q,z))O[Z]=z,O[K]=Q,K=Z;else break e}}function e(O){return O.length===0?null:O[0]}function s(O){if(O.length===0)return null;var z=O[0],K=O.pop();if(K!==z){O[0]=K;e:for(var Z=0,Q=O.length,B=Q>>>1;Z<B;){var G=2*(Z+1)-1,X=O[G],ne=G+1,se=O[ne];if(0>o(X,K))ne<Q&&0>o(se,X)?(O[Z]=se,O[ne]=K,Z=ne):(O[Z]=X,O[G]=K,Z=G);else if(ne<Q&&0>o(se,K))O[Z]=se,O[ne]=K,Z=ne;else break e}}return z}function o(O,z){var K=O.sortIndex-z.sortIndex;return K!==0?K:O.id-z.id}if(n.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var r=performance;n.unstable_now=function(){return r.now()}}else{var a=Date,i=a.now();n.unstable_now=function(){return a.now()-i}}var l=[],u=[],d=1,m=null,h=3,f=!1,g=!1,b=!1,x=!1,y=typeof setTimeout=="function"?setTimeout:null,E=typeof clearTimeout=="function"?clearTimeout:null,k=typeof setImmediate<"u"?setImmediate:null;function T(O){for(var z=e(u);z!==null;){if(z.callback===null)s(u);else if(z.startTime<=O)s(u),z.sortIndex=z.expirationTime,t(l,z);else break;z=e(u)}}function A(O){if(b=!1,T(O),!g)if(e(l)!==null)g=!0,N||(N=!0,D());else{var z=e(u);z!==null&&V(A,z.startTime-O)}}var N=!1,S=-1,M=5,L=-1;function P(){return x?!0:!(n.unstable_now()-L<M)}function F(){if(x=!1,N){var O=n.unstable_now();L=O;var z=!0;try{e:{g=!1,b&&(b=!1,E(S),S=-1),f=!0;var K=h;try{t:{for(T(O),m=e(l);m!==null&&!(m.expirationTime>O&&P());){var Z=m.callback;if(typeof Z=="function"){m.callback=null,h=m.priorityLevel;var Q=Z(m.expirationTime<=O);if(O=n.unstable_now(),typeof Q=="function"){m.callback=Q,T(O),z=!0;break t}m===e(l)&&s(l),T(O)}else s(l);m=e(l)}if(m!==null)z=!0;else{var B=e(u);B!==null&&V(A,B.startTime-O),z=!1}}break e}finally{m=null,h=K,f=!1}z=void 0}}finally{z?D():N=!1}}}var D;if(typeof k=="function")D=function(){k(F)};else if(typeof MessageChannel<"u"){var U=new MessageChannel,q=U.port2;U.port1.onmessage=F,D=function(){q.postMessage(null)}}else D=function(){y(F,0)};function V(O,z){S=y(function(){O(n.unstable_now())},z)}n.unstable_IdlePriority=5,n.unstable_ImmediatePriority=1,n.unstable_LowPriority=4,n.unstable_NormalPriority=3,n.unstable_Profiling=null,n.unstable_UserBlockingPriority=2,n.unstable_cancelCallback=function(O){O.callback=null},n.unstable_forceFrameRate=function(O){0>O||125<O?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):M=0<O?Math.floor(1e3/O):5},n.unstable_getCurrentPriorityLevel=function(){return h},n.unstable_next=function(O){switch(h){case 1:case 2:case 3:var z=3;break;default:z=h}var K=h;h=z;try{return O()}finally{h=K}},n.unstable_requestPaint=function(){x=!0},n.unstable_runWithPriority=function(O,z){switch(O){case 1:case 2:case 3:case 4:case 5:break;default:O=3}var K=h;h=O;try{return z()}finally{h=K}},n.unstable_scheduleCallback=function(O,z,K){var Z=n.unstable_now();switch(typeof K=="object"&&K!==null?(K=K.delay,K=typeof K=="number"&&0<K?Z+K:Z):K=Z,O){case 1:var Q=-1;break;case 2:Q=250;break;case 5:Q=1073741823;break;case 4:Q=1e4;break;default:Q=5e3}return Q=K+Q,O={id:d++,callback:z,priorityLevel:O,startTime:K,expirationTime:Q,sortIndex:-1},K>Z?(O.sortIndex=K,t(u,O),e(l)===null&&O===e(u)&&(b?(E(S),S=-1):b=!0,V(A,K-Z))):(O.sortIndex=Q,t(l,O),g||f||(g=!0,N||(N=!0,D()))),O},n.unstable_shouldYield=P,n.unstable_wrapCallback=function(O){var z=h;return function(){var K=h;h=z;try{return O.apply(this,arguments)}finally{h=K}}}})(scheduler_production)),scheduler_production}var hasRequiredScheduler;function requireScheduler(){return hasRequiredScheduler||(hasRequiredScheduler=1,scheduler.exports=requireScheduler_production()),scheduler.exports}var reactDom={exports:{}},reactDom_production={};var hasRequiredReactDom_production;function requireReactDom_production(){if(hasRequiredReactDom_production)return reactDom_production;hasRequiredReactDom_production=1;var n=requireReact();function t(l){var u="https://react.dev/errors/"+l;if(1<arguments.length){u+="?args[]="+encodeURIComponent(arguments[1]);for(var d=2;d<arguments.length;d++)u+="&args[]="+encodeURIComponent(arguments[d])}return"Minified React error #"+l+"; visit "+u+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function e(){}var s={d:{f:e,r:function(){throw Error(t(522))},D:e,C:e,L:e,m:e,X:e,S:e,M:e},p:0,findDOMNode:null},o=Symbol.for("react.portal");function r(l,u,d){var m=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:o,key:m==null?null:""+m,children:l,containerInfo:u,implementation:d}}var a=n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function i(l,u){if(l==="font")return"";if(typeof u=="string")return u==="use-credentials"?u:""}return reactDom_production.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=s,reactDom_production.createPortal=function(l,u){var d=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!u||u.nodeType!==1&&u.nodeType!==9&&u.nodeType!==11)throw Error(t(299));return r(l,u,null,d)},reactDom_production.flushSync=function(l){var u=a.T,d=s.p;try{if(a.T=null,s.p=2,l)return l()}finally{a.T=u,s.p=d,s.d.f()}},reactDom_production.preconnect=function(l,u){typeof l=="string"&&(u?(u=u.crossOrigin,u=typeof u=="string"?u==="use-credentials"?u:"":void 0):u=null,s.d.C(l,u))},reactDom_production.prefetchDNS=function(l){typeof l=="string"&&s.d.D(l)},reactDom_production.preinit=function(l,u){if(typeof l=="string"&&u&&typeof u.as=="string"){var d=u.as,m=i(d,u.crossOrigin),h=typeof u.integrity=="string"?u.integrity:void 0,f=typeof u.fetchPriority=="string"?u.fetchPriority:void 0;d==="style"?s.d.S(l,typeof u.precedence=="string"?u.precedence:void 0,{crossOrigin:m,integrity:h,fetchPriority:f}):d==="script"&&s.d.X(l,{crossOrigin:m,integrity:h,fetchPriority:f,nonce:typeof u.nonce=="string"?u.nonce:void 0})}},reactDom_production.preinitModule=function(l,u){if(typeof l=="string")if(typeof u=="object"&&u!==null){if(u.as==null||u.as==="script"){var d=i(u.as,u.crossOrigin);s.d.M(l,{crossOrigin:d,integrity:typeof u.integrity=="string"?u.integrity:void 0,nonce:typeof u.nonce=="string"?u.nonce:void 0})}}else u==null&&s.d.M(l)},reactDom_production.preload=function(l,u){if(typeof l=="string"&&typeof u=="object"&&u!==null&&typeof u.as=="string"){var d=u.as,m=i(d,u.crossOrigin);s.d.L(l,d,{crossOrigin:m,integrity:typeof u.integrity=="string"?u.integrity:void 0,nonce:typeof u.nonce=="string"?u.nonce:void 0,type:typeof u.type=="string"?u.type:void 0,fetchPriority:typeof u.fetchPriority=="string"?u.fetchPriority:void 0,referrerPolicy:typeof u.referrerPolicy=="string"?u.referrerPolicy:void 0,imageSrcSet:typeof u.imageSrcSet=="string"?u.imageSrcSet:void 0,imageSizes:typeof u.imageSizes=="string"?u.imageSizes:void 0,media:typeof u.media=="string"?u.media:void 0})}},reactDom_production.preloadModule=function(l,u){if(typeof l=="string")if(u){var d=i(u.as,u.crossOrigin);s.d.m(l,{as:typeof u.as=="string"&&u.as!=="script"?u.as:void 0,crossOrigin:d,integrity:typeof u.integrity=="string"?u.integrity:void 0})}else s.d.m(l)},reactDom_production.requestFormReset=function(l){s.d.r(l)},reactDom_production.unstable_batchedUpdates=function(l,u){return l(u)},reactDom_production.useFormState=function(l,u,d){return a.H.useFormState(l,u,d)},reactDom_production.useFormStatus=function(){return a.H.useHostTransitionStatus()},reactDom_production.version="19.2.4",reactDom_production}var hasRequiredReactDom;function requireReactDom(){if(hasRequiredReactDom)return reactDom.exports;hasRequiredReactDom=1;function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(t){console.error(t)}}return n(),reactDom.exports=requireReactDom_production(),reactDom.exports}var hasRequiredReactDomClient_production;function requireReactDomClient_production(){if(hasRequiredReactDomClient_production)return reactDomClient_production;hasRequiredReactDomClient_production=1;var n=requireScheduler(),t=requireReact(),e=requireReactDom();function s(p){var $="https://react.dev/errors/"+p;if(1<arguments.length){$+="?args[]="+encodeURIComponent(arguments[1]);for(var _=2;_<arguments.length;_++)$+="&args[]="+encodeURIComponent(arguments[_])}return"Minified React error #"+p+"; visit "+$+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function o(p){return!(!p||p.nodeType!==1&&p.nodeType!==9&&p.nodeType!==11)}function r(p){var $=p,_=p;if(p.alternate)for(;$.return;)$=$.return;else{p=$;do $=p,($.flags&4098)!==0&&(_=$.return),p=$.return;while(p)}return $.tag===3?_:null}function a(p){if(p.tag===13){var $=p.memoizedState;if($===null&&(p=p.alternate,p!==null&&($=p.memoizedState)),$!==null)return $.dehydrated}return null}function i(p){if(p.tag===31){var $=p.memoizedState;if($===null&&(p=p.alternate,p!==null&&($=p.memoizedState)),$!==null)return $.dehydrated}return null}function l(p){if(r(p)!==p)throw Error(s(188))}function u(p){var $=p.alternate;if(!$){if($=r(p),$===null)throw Error(s(188));return $!==p?null:p}for(var _=p,v=$;;){var j=_.return;if(j===null)break;var I=j.alternate;if(I===null){if(v=j.return,v!==null){_=v;continue}break}if(j.child===I.child){for(I=j.child;I;){if(I===_)return l(j),p;if(I===v)return l(j),$;I=I.sibling}throw Error(s(188))}if(_.return!==v.return)_=j,v=I;else{for(var C=!1,Y=j.child;Y;){if(Y===_){C=!0,_=j,v=I;break}if(Y===v){C=!0,v=j,_=I;break}Y=Y.sibling}if(!C){for(Y=I.child;Y;
vendor: 12,066 bytes, lines 2-9
2){if(Y===_){C=!0,_=I,v=j;break}if(Y===v){C=!0,v=I,_=j;break}Y=Y.sibling}if(!C)throw Error(s(189))}}if(_.alternate!==v)throw Error(s(190))}if(_.tag!==3)throw Error(s(188));return _.stateNode.current===_?p:$}function d(p){var $=p.tag;if($===5||$===26||$===27||$===6)return p;for(p=p.child;p!==null;){if($=d(p),$!==null)return $;p=p.sibling}return null}var m=Object.assign,h=Symbol.for("react.element"),f=Symbol.for("react.transitional.element"),g=Symbol.for("react.portal"),b=Symbol.for("react.fragment"),x=Symbol.for("react.strict_mode"),y=Symbol.for("react.profiler"),E=Symbol.for("react.consumer"),k=Symbol.for("react.context"),T=Symbol.for("react.forward_ref"),A=Symbol.for("react.suspense"),N=Symbol.for("react.suspense_list"),S=Symbol.for("react.memo"),M=Symbol.for("react.lazy"),L=Symbol.for("react.activity"),P=Symbol.for("react.memo_cache_sentinel"),F=Symbol.iterator;function D(p){return p===null||typeof p!="object"?null:(p=F&&p[F]||p["@@iterator"],typeof p=="function"?p:null)}var U=Symbol.for("react.client.reference");function q(p){if(p==null)return null;if(typeof p=="function")return p.$$typeof===U?null:p.displayName||p.name||null;if(typeof p=="string")return p;switch(p){case b:return"Fragment";case y:return"Profiler";case x:return"StrictMode";case A:return"Suspense";case N:return"SuspenseList";case L:return"Activity"}if(typeof p=="object")switch(p.$$typeof){case g:return"Portal";case k:return p.displayName||"Context";case E:return(p._context.displayName||"Context")+".Consumer";case T:var $=p.render;return p=p.displayName,p||(p=$.displayName||$.name||"",p=p!==""?"ForwardRef("+p+")":"ForwardRef"),p;case S:return $=p.displayName||null,$!==null?$:q(p.type)||"Memo";case M:$=p._payload,p=p._init;try{return q(p($))}catch{}}return null}var V=Array.isArray,O=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,z=e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,K={pending:!1,data:null,method:null,action:null},Z=[],Q=-1;function B(p){return{current:p}}function G(p){0>Q||(p.current=Z[Q],Z[Q]=null,Q--)}function X(p,$){Q++,Z[Q]=p.current,p.current=$}var ne=B(null),se=B(null),te=B(null),oe=B(null);function le(p,$){switch(X(te,$),X(se,p),X(ne,null),$.nodeType){case 9:case 11:p=(p=$.documentElement)&&(p=p.namespaceURI)?dm(p):0;break;default:if(p=$.tagName,$=$.namespaceURI)$=dm($),p=mm($,p);else switch(p){case"svg":p=1;break;case"math":p=2;break;default:p=0}}G(ne),X(ne,p)}function ae(){G(ne),G(se),G(te)}function ie(p){p.memoizedState!==null&&X(oe,p);var $=ne.current,_=mm($,p.type);$!==_&&(X(se,p),X(ne,_))}function we(p){se.current===p&&(G(ne),G(se)),oe.current===p&&(G(oe),aa._currentValue=K)}var ke,Te;function Se(p){if(ke===void 0)try{throw Error()}catch(_){var $=_.stack.trim().match(/\n( *(at )?)/);ke=$&&$[1]||"",Te=-1<_.stack.indexOf(`
3    at`)?" (<anonymous>)":-1<_.stack.indexOf("@")?"@unknown:0:0":""}return`
4`+ke+p+Te}var $e=!1;function Re(p,$){if(!p||$e)return"";$e=!0;var _=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var v={DetermineComponentFrameRoot:function(){try{if($){var fe=function(){throw Error()};if(Object.defineProperty(fe.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(fe,[])}catch(me){var de=me}Reflect.construct(p,[],fe)}else{try{fe.call()}catch(me){de=me}p.call(fe.prototype)}}else{try{throw Error()}catch(me){de=me}(fe=p())&&typeof fe.catch=="function"&&fe.catch(function(){})}}catch(me){if(me&&de&&typeof me.stack=="string")return[me.stack,de.stack]}return[null,null]}};v.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var j=Object.getOwnPropertyDescriptor(v.DetermineComponentFrameRoot,"name");j&&j.configurable&&Object.defineProperty(v.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var I=v.DetermineComponentFrameRoot(),C=I[0],Y=I[1];if(C&&Y){var J=C.split(`
5`),ue=Y.split(`
6`);for(j=v=0;v<J.length&&!J[v].includes("DetermineComponentFrameRoot");)v++;for(;j<ue.length&&!ue[j].includes("DetermineComponentFrameRoot");)j++;if(v===J.length||j===ue.length)for(v=J.length-1,j=ue.length-1;1<=v&&0<=j&&J[v]!==ue[j];)j--;for(;1<=v&&0<=j;v--,j--)if(J[v]!==ue[j]){if(v!==1||j!==1)do if(v--,j--,0>j||J[v]!==ue[j]){var he=`
7`+J[v].replace(" at new "," at ");return p.displayName&&he.includes("<anonymous>")&&(he=he.replace("<anonymous>",p.displayName)),he}while(1<=v&&0<=j);break}}}finally{$e=!1,Error.prepareStackTrace=_}return(_=p?p.displayName||p.name:"")?Se(_):""}function ye(p,$){switch(p.tag){case 26:case 27:case 5:return Se(p.type);case 16:return Se("Lazy");case 13:return p.child!==$&&$!==null?Se("Suspense Fallback"):Se("Suspense");case 19:return Se("SuspenseList");case 0:case 15:return Re(p.type,!1);case 11:return Re(p.type.render,!1);case 1:return Re(p.type,!0);case 31:return Se("Activity");default:return""}}function Ne(p){try{var $="",_=null;do $+=ye(p,_),_=p,p=p.return;while(p);return $}catch(v){return`
8Error generating stack: `+v.message+`
9`+v.stack}}var Ye=Object.prototype.hasOwnProperty,Ge=n.unstable_scheduleCallback,Fe=n.unstable_cancelCallback,Xe=n.unstable_shouldYield,Rt=n.unstable_requestPaint,ge=n.unstable_now,Ie=n.unstable_getCurrentPriorityLevel,ft=n.unstable_ImmediatePriority,wt=n.unstable_UserBlockingPriority,mt=n.unstable_NormalPriority,et=n.unstable_LowPriority,Ke=n.unstable_IdlePriority,Pt=n.log,ht=n.unstable_setDisableYieldValue,lt=null,ct=null;function $t(p){if(typeof Pt=="function"&&ht(p),ct&&typeof ct.setStrictMode=="function")try{ct.setStrictMode(lt,p)}catch{}}var ot=Math.clz32?Math.clz32:ln,nn=Math.log,It=Math.LN2;function ln(p){return p>>>=0,p===0?32:31-(nn(p)/It|0)|0}var Et=256,Qe=262144,Ut=4194304;function Xt(p){var $=p&42;if($!==0)return $;switch(p&-p){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return p&261888;case 262144:case 524288:case 1048576:case 2097152:return p&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return p&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return p}}function Ce(p,$,_){var v=p.pendingLanes;if(v===0)return 0;var j=0,I=p.suspendedLanes,C=p.pingedLanes;p=p.warmLanes;var Y=v&134217727;return Y!==0?(v=Y&~I,v!==0?j=Xt(v):(C&=Y,C!==0?j=Xt(C):_||(_=Y&~p,_!==0&&(j=Xt(_))))):(Y=v&~I,Y!==0?j=Xt(Y):C!==0?j=Xt(C):_||(_=v&~p,_!==0&&(j=Xt(_)))),j===0?0:$!==0&&$!==j&&($&I)===0&&(I=j&-j,_=$&-$,I>=_||I===32&&(_&4194048)!==0)?$:j}function kt(p,$){return(p.pendingLanes&~(p.suspendedLanes&~p.pingedLanes)&$)===0}function sn(p,$){switch(p){case 1:case 2:case 4:case 8:case 64:return $+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return $+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function En(){var p=Ut;return Ut<<=1,(Ut&62914560)===0&&(Ut=4194304),p}function wn(p){for(var $=[],_=0;31>_;_++)$.push(p);return $}function Zt(p,$){p.pendingLanes|=$,$!==268435456&&(p.suspendedLanes=0,p.pingedLanes=0,p.warmLanes=0)}function $n(p,$,_,v,j,I){var C=p.pendingLanes;p.pendingLanes=_,p.suspendedLanes=0,p.pingedLanes=0,p.warmLanes=0,p.expiredLanes&=_,p.entangledLanes&=_,p.errorRecoveryDisabledLanes&=_,p.shellSuspendCounter=0;var Y=p.entanglements,J=p.expirationTimes,ue=p.hiddenUpdates;for(_=C&~_;0<_;){var he=31-ot(_),fe=1<<he;Y[he]=0,J[he]=-1;var de=ue[he];if(de!==null)for(ue[he]=null,he=0;he<de.length;he++){var me=de[he];me!==null&&(me.lane&=-536870913)}_&=~fe}v!==0&&cn(p,v,0),I!==0&&j===0&&p.tag!==0&&(p.suspendedLanes|=I&~(C&~$))}function cn(p,$,_){p.pendingLanes|=$,p.suspendedLanes&=~$;var v=31-ot($);p.entangledLanes|=$,p.entanglements[v]=p.entanglements[v]|1073741824|_&261930}function kn(p,$){var _=p.entangledLanes|=$;for(p=p.entanglements;_;){var v=31-ot(_),j=1<<v;j&$|p[v]&$&&(p[v]|=$),_&=~j}}function Tn(p,$){var _=$&-$;return _=(_&42)!==0?1:zn(_),(_&(p.suspendedLanes|$))!==0?0:_}function zn(p){switch(p){case 2:p=1;break;case 8:p=4;break;case 32:p=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:p=128;break;case 268435456:p=134217728;break;default:p=0}return p}function Sn(p){return p&=-p,2<p?8<p?(p&134217727)!==0?32:268435456:8:2}function Rn(){var p=z.p;return p!==0?p:(p=window.event,p===void 0?32:Lm(p.type))}function Yn(p,$){var _=z.p;try{return z.p=p,$()}finally{z.p=_}}var Gt=Math.random().toString(36).slice(2),rt="__reactFiber$"+Gt,At="__reactProps$"+Gt,un="__reactContainer$"+Gt,On="__reactEvents$"+Gt,as="__reactListeners$"+Gt,xe="__reactHandles$"+Gt,Ve="__reactResources$"+Gt,tt="__reactMarker$"+Gt;function Le(p){delete p[rt],delete p[At],delete p[On],delete p[as],delete p[xe]}function He(p){var $=p[rt];if($)return $;for(var _=p.parentNode;_;){if($=_[un]||_[rt]){if(_=$.alternate,$.child!==null||_!==null&&_.child!==null)for(p=_m(p);p!==null;){if(_=p[rt])return _;p=_m(p)}return $}p=_,_=p.parentNode}return null}function gt(p){if(p=p[rt]||p[un]){var $=p.tag;if($===5||$===6||$===13||$===31||$===26||$===27||$===3)return p}return null}function at(p){var $=p.tag;if($===5||$===26||$===27||$===6)return p.stateNode;throw Error(s(33))}function be(p){var $=p[Ve];return $||($=p[Ve]={hoistableStyles:new Map,hoistableScripts:new Map}),$}function We(p){p[tt]=!0}var De=new Set,An={};function Jt(p,$){Ct(p,$),Ct(p+"Capture",$)}function Ct(p,$){for(An[p]=$,p=0;p<$.length;p++)De.add($[p])}var Bs=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),xn={},Kn={};function Tt(p){return Ye.call(Kn,p)?!0:Ye.call(xn,p)?!1:Bs.test(p)?Kn[p]=!0:(xn[p]=!0,!1)}function xs(p,$,_){if(Tt($))if(_===null)p.removeAttribute($);else{switch(typeof _){case"undefined":case"function":case"symbol":p.removeAttribute($);return;case"boolean":var v=$.toLowerCase().slice(0,5);if(v!=="data-"&&v!=="aria-"){p.removeAttribute($);return}}p.setAttribute($,""+_)}}function qt(p,$,_){if(_===null)p.removeAttribute($);else{switch(typeof _){case"undefined":case"function":case"symbol":case"boolean":p.removeAttribute($);return}p.setAttribute($,""+_)}}function jt(p,$,_,v){if(v===null)p.removeAttribute(_);else{switch(typeof v){case"undefined":case"function":case"symbol":case"boolean":p.removeAttribute(_);return}p.setAttributeNS($,_,""+v)}}function on(p){switch(typeof p){case"bigint":case"boolean":case"number":case"string":case"undefined":return p;case"object":return p;default:return""}}function Us(p){var $=p.type;return(p=p.nodeName)&&p.toLowerCase()==="input"&&($==="checkbox"||$==="radio")}function xo(p,$,_){var v=Object.getOwnPropertyDescriptor(p.constructor.prototype,$);if(!p.hasOwnProperty($)&&typeof v<"u"&&typeof v.get=="function"&&typeof v.set=="function"){var j=v.get,I=v.set;return Object.defineProperty(p,$,{configurable:!0,get:function(){return j.call(this)},set:function(C){_=""+C,I.call(this,C)}}),Object.defineProperty(p,$,{enumerable:v.enumerable}),{getValue:function(){return _},setValue:function(C){_=""+C},stopTracking:function(){p._valueTracker=null,delete p[$]}}}}function Gs(p){if(!p._valueTracker){var $=Us(p)?"checked":"value";p._valueTracker=xo(p,$,""+p[$])}}function yn(p){if(!p)return!1;var $=p._valueTracker;if(!$)return!0;var _=$.getValue(),v="";return p&&(v=Us(p)?p.checked?"true":"false":p.value),p=v,p!==_?($.setValue(p),!0):!1}
vendor: 4,643 bytes, line 9
9function ps(p){if(p=p||(typeof document<"u"?document:void 0),typeof p>"u")return null;try{return p.activeElement||p.body}catch{return p.body}}var wr=/[\n"\\]/g;function gn(p){return p.replace(wr,function($){return"\\"+$.charCodeAt(0).toString(16)+" "})}function yo(p,$,_,v,j,I,C,Y){p.name="",C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"?p.type=C:p.removeAttribute("type"),$!=null?C==="number"?($===0&&p.value===""||p.value!=$)&&(p.value=""+on($)):p.value!==""+on($)&&(p.value=""+on($)):C!=="submit"&&C!=="reset"||p.removeAttribute("value"),$!=null?is(p,C,on($)):_!=null?is(p,C,on(_)):v!=null&&p.removeAttribute("value"),j==null&&I!=null&&(p.defaultChecked=!!I),j!=null&&(p.checked=j&&typeof j!="function"&&typeof j!="symbol"),Y!=null&&typeof Y!="function"&&typeof Y!="symbol"&&typeof Y!="boolean"?p.name=""+on(Y):p.removeAttribute("name")}function qs(p,$,_,v,j,I,C,Y){if(I!=null&&typeof I!="function"&&typeof I!="symbol"&&typeof I!="boolean"&&(p.type=I),$!=null||_!=null){if(!(I!=="submit"&&I!=="reset"||$!=null)){Gs(p);return}_=_!=null?""+on(_):"",$=$!=null?""+on($):_,Y||$===p.value||(p.value=$),p.defaultValue=$}v=v??j,v=typeof v!="function"&&typeof v!="symbol"&&!!v,p.checked=Y?p.checked:!!v,p.defaultChecked=!!v,C!=null&&typeof C!="function"&&typeof C!="symbol"&&typeof C!="boolean"&&(p.name=C),Gs(p)}function is(p,$,_){$==="number"&&ps(p.ownerDocument)===p||p.defaultValue===""+_||(p.defaultValue=""+_)}function Qn(p,$,_,v){if(p=p.options,$){$={};for(var j=0;j<_.length;j++)$["$"+_[j]]=!0;for(_=0;_<p.length;_++)j=$.hasOwnProperty("$"+p[_].value),p[_].selected!==j&&(p[_].selected=j),j&&v&&(p[_].defaultSelected=!0)}else{for(_=""+on(_),$=null,j=0;j<p.length;j++){if(p[j].value===_){p[j].selected=!0,v&&(p[j].defaultSelected=!0);return}$!==null||p[j].disabled||($=p[j])}$!==null&&($.selected=!0)}}function ys(p,$,_){if($!=null&&($=""+on($),$!==p.value&&(p.value=$),_==null)){p.defaultValue!==$&&(p.defaultValue=$);return}p.defaultValue=_!=null?""+on(_):""}function vs(p,$,_,v){if($==null){if(v!=null){if(_!=null)throw Error(s(92));if(V(v)){if(1<v.length)throw Error(s(93));v=v[0]}_=v}_==null&&(_=""),$=_}_=on($),p.defaultValue=_,v=p.textContent,v===_&&v!==""&&v!==null&&(p.value=v),Gs(p)}function $s(p,$){if($){var _=p.firstChild;if(_&&_===p.lastChild&&_.nodeType===3){_.nodeValue=$;return}}p.textContent=$}var vo=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function Fo(p,$,_){var v=$.indexOf("--")===0;_==null||typeof _=="boolean"||_===""?v?p.setProperty($,""):$==="float"?p.cssFloat="":p[$]="":v?p.setProperty($,_):typeof _!="number"||_===0||vo.has($)?$==="float"?p.cssFloat=_:p[$]=(""+_).trim():p[$]=_+"px"}function Vs(p,$,_){if($!=null&&typeof $!="object")throw Error(s(62));if(p=p.style,_!=null){for(var v in _)!_.hasOwnProperty(v)||$!=null&&$.hasOwnProperty(v)||(v.indexOf("--")===0?p.setProperty(v,""):v==="float"?p.cssFloat="":p[v]="");for(var j in $)v=$[j],$.hasOwnProperty(j)&&_[j]!==v&&Fo(p,j,v)}else for(var I in $)$.hasOwnProperty(I)&&Fo(p,I,$[I])}function ls(p){if(p.indexOf("-")===-1)return!1;switch(p){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var jn=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRen
9dering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),_e=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function Me(p){return _e.test(""+p)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":p}function Be(){}var Ft=null;function dn(p){return p=p.target||p.srcElement||window,p.correspondingUseElement&&(p=p.correspondingUseElement),p.nodeType===3?p.parentNode:p}var en=null,bn=null;function Ho(p){var $=gt(p);if($&&(p=$.stateNode)){var _=p[At]||null;e:switch(p=$.stateNode,$.type){case"input":if(yo(p,_.value,_.defaultValue,_.defaultValue,_.checked,_.defaultChecked,_.type,_.name),$=_.name,_.type==="radio"&&$!=null){for(_=p;_.parentNode;)_=_.parentNode;for(_=_.querySelectorAll('input[name="'+gn(""+$)+'"][type="radio"]'),$=0;$<_.length;$++){var v=_[$];if(v!==p&&v.form===p.form){var j=v[At]||null;if(!j)throw Error(s(90));yo(v,j.value,j.defaultValue,j.defaultValue,j.checked,j.defaultChecked,j.type,j.name)}}for($=0;$<_.length;$++)v=_[$],v.form===p.form&&yn(v)}break e;case"textarea":ys(p,_.value,_.defaultValue);break e;case"select":$=_.value,$!=null&&Qn(p,!!_.multiple,$,!1)}}}var Es=!1;function Mn(p,$,_){if(Es)return p($,_);Es=!0;try{var v=p($);return v}finally{if(Es=!1,(en!==null||bn!==null)&&(Ya(),en&&($=en,p=bn,bn=en=null,Ho($),p)))for($=0;$<p.length;$++)Ho(p[$])}}function _n(p,$){var _=p.stateNode;if(_===null)return null;var v=_[At]||null;if(v===null)return null;_=v[$];e:switch($){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(v=!v.disabled)||(p=p.type,v=!(p==="button"||p==="input"||p==="select"||p==="textarea")),p=!v;break e;default:p=!1}if(p)return null;if(_&&typeof _!="function")throw Error(s(231,$,typeof _));return _}var Vt=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),xr=!1;if(Vt)try{var zs={};Object.defineProperty(zs,"passive",{get:function(){xr=!0}}),window.addEventListener("test",zs,zs),window.removeEventListener("test",zs,zs)}catch{xr=!1}var cs=null,Ys=null,Ks=null;function yr(){if(Ks)return Ks;var p,$=Ys,_=$.length,v,j="value"in cs?cs.value:cs.textContent,I=j.length;for(p=0;p<_&&$[p]===j[p];p++);var C=_-p;for(v=1;v<=C&&$[_-v]===j[I-v];v++);return Ks=j.slice(p,1<v?1-v:void 0)}function ks(p){var $=p.keyCode;
vendor: 6,364 bytes, line 9
9return"charCode"in p?(p=p.charCode,p===0&&$===13&&(p=13)):p=$,p===10&&(p=13),32<=p||p===13?p:0}function Xn(){return!0}function ua(){return!1}function rn(p){function $(_,v,j,I,C){this._reactName=_,this._targetInst=j,this.type=v,this.nativeEvent=I,this.target=C,this.currentTarget=null;for(var Y in p)p.hasOwnProperty(Y)&&(_=p[Y],this[Y]=_?_(I):I[Y]);return this.isDefaultPrevented=(I.defaultPrevented!=null?I.defaultPrevented:I.returnValue===!1)?Xn:ua,this.isPropagationStopped=ua,this}return m($.prototype,{preventDefault:function(){this.defaultPrevented=!0;var _=this.nativeEvent;_&&(_.preventDefault?_.preventDefault():typeof _.returnValue!="unknown"&&(_.returnValue=!1),this.isDefaultPrevented=Xn)},stopPropagation:function(){var _=this.nativeEvent;_&&(_.stopPropagation?_.stopPropagation():typeof _.cancelBubble!="unknown"&&(_.cancelBubble=!0),this.isPropagationStopped=Xn)},persist:function(){},isPersistent:Xn}),$}var Ts={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(p){return p.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},Bo=rn(Ts),Dn=m({},Ts,{view:0,detail:0}),$i=rn(Dn),Eo,Uo,Qs,Go=m({},Dn,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:wi,button:0,buttons:0,relatedTarget:function(p){return p.relatedTarget===void 0?p.fromElement===p.srcElement?p.toElement:p.fromElement:p.relatedTarget},movementX:function(p){return"movementX"in p?p.movementX:(p!==Qs&&(Qs&&p.type==="mousemove"?(Eo=p.screenX-Qs.screenX,Uo=p.screenY-Qs.screenY):Uo=Eo=0,Qs=p),Eo)},movementY:function(p){return"movementY"in p?p.movementY:Uo}}),vr=rn(Go),da=m({},Go,{dataTransfer:0}),gi=rn(da),bi=m({},Dn,{relatedTarget:0}),Er=rn(bi),_i=m({},Ts,{animationName:0,elapsedTime:0,pseudoElement:0}),ve=rn(_i),Ee=m({},Ts,{clipboardData:function(p){return"clipboardData"in p?p.clipboardData:window.clipboardData}}),ze=rn(Ee),Mt=m({},Ts,{data:0}),Nn=rn(Mt),kr={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},us={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},ma={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function Tr(p){var $=this.nativeEvent;return $.getModifierState?$.getModifierState(p):(p=ma[p])?!!$[p]:!1}function wi(){return Tr}var Um=m({},Dn,{key:function(p){if(p.key){var $=kr[p.key]||p.key;if($!=="Unidentified")return $}return p.type==="keypress"?(p=ks(p),p===13?"Enter":String.fromCharCode(p)):p.type==="keydown"||p.type==="keyup"?us[p.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:wi,charCode:function(p){return p.type==="keypress"?ks(p):0},keyCode:function(p){return p.type==="keydown"||p.type==="keyup"?p.keyCode:0},which:function(p){return p.type==="keypress"?ks(p):p.type==="keydown"||p.type==="keyup"?p.keyCode:0}}),Gm=rn(Um),qm=m({},Go,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),fc=rn(qm),Vm=m({},Dn,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:wi}),zm=rn(Vm),Ym=m({},Ts,{propertyName:0,elapsedTime:0,pseudoElement:0}),Km=rn(Ym),Qm=m({},Go,{deltaX:function(p){return"deltaX"in p?p.deltaX:"wheelDeltaX"in p?-p.wheelDeltaX:0},deltaY:function(p){return"deltaY"in p?p.deltaY:"wheelDeltaY"in p?-p.wheelDeltaY:"wheelDelta"in p?-p.wheelDelta:0},deltaZ:0,deltaMode:0}),Xm=rn(Qm),Zm=m({},Ts,{newState:0,oldState:0}),Jm=rn(Zm),eh=[9,13,27,32],xi=Vt&&"CompositionEvent"in window,Sr=null;Vt&&"documentMode"in document&&(Sr=document.documentMode);var th=Vt&&"TextEvent"in window&&!Sr,$c=Vt&&(!xi||Sr&&8<Sr&&11>=Sr),gc=" ",bc=!1;function _c(p,$){switch(p){case"keyup":return eh.indexOf($.keyCode)!==-1;case"keydown":return $.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function wc(p){return p=p.detail,typeof p=="object"&&"data"in p?p.data:null}var qo=!1;function nh(p,$){switch(p){case"compositionend":return wc($);case"keypress":return $.which!==32?null:(bc=!0,gc);case"textInput":return p=$.data,p===gc&&bc?null:p;default:return null}}function sh(p,$){if(qo)return p==="compositionend"||!xi&&_c(p,$)?(p=yr(),Ks=Ys=cs=null,qo=!1,p):null;switch(p){case"paste":return null;case"keypress":if(!($.ctrlKey||$.altKey||$.metaKey)||$.ctrlKey&&$.altKey){if($.char&&1<$.char.length)return $.char;if($.which)return String.fromCharCode($.which)}return null;case"compositionend":return $c&&$.locale!=="ko"?null:$.data;default:return null}}var oh={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function xc(p){var $=p&&p.nodeName&&p.nodeName.toLowerCase();return $==="input"?!!oh[p.type]:$==="textarea"}function yc(p,$,_,v){en?bn?bn.push(v):bn=[v]:en=v,$=ti($,"onChange"),0<$.length&&(_=new Bo("onChange","change",null,_,v),p.push({event:_,listeners:$}))}var Rr=null,Ar=null;function rh(p){rm(p,0)}function ha(p){var $=at(p);if(yn($))return p}function vc(p,$){if(p==="change")return $}var Ec=!1;if(Vt){var yi;if(Vt){var vi="oninput"in document;if(!vi){var kc=document.createElement("div");kc.setAttribute("oninput","return;"),vi=typeof kc.oninput=="function"}yi=vi}else yi=!1;Ec=yi&&(!document.documentMode||9<document.documentMode)}function Tc(){Rr&&(Rr.detachEvent("onpropertychange",Sc),Ar=Rr=null)}function Sc(p){if(p.propertyName==="value"&&ha(Ar)){var $=[];yc($,Ar,p,dn(p)),Mn(rh,$)}}function ah(p,$,_){p==="focusin"?(Tc(),Rr=$,Ar=_,Rr.attachEvent("onpropertychange",Sc)):p==="focusout"&&Tc()}function ih(p){if(p==="selectionchange"||p==="keyup"||p==="keydown")return ha(Ar)}function lh(p,$){if(p==="click")return ha($)}function ch(p,$){if(p==="input"||p==="change")return ha($)}function uh(p,$){return p===$&&(p!==0||1/p===1/$)||p!==p&&$!==$}var Fn=typeof Object.is=="function"?Object.is:uh;function jr(p,$){if(Fn(p,$))return!0;
vendor: 17,769 bytes, line 9
9if(typeof p!="object"||p===null||typeof $!="object"||$===null)return!1;var _=Object.keys(p),v=Object.keys($);if(_.length!==v.length)return!1;for(v=0;v<_.length;v++){var j=_[v];if(!Ye.call($,j)||!Fn(p[j],$[j]))return!1}return!0}function Rc(p){for(;p&&p.firstChild;)p=p.firstChild;return p}function Ac(p,$){var _=Rc(p);p=0;for(var v;_;){if(_.nodeType===3){if(v=p+_.textContent.length,p<=$&&v>=$)return{node:_,offset:$-p};p=v}e:{for(;_;){if(_.nextSibling){_=_.nextSibling;break e}_=_.parentNode}_=void 0}_=Rc(_)}}function jc(p,$){return p&&$?p===$?!0:p&&p.nodeType===3?!1:$&&$.nodeType===3?jc(p,$.parentNode):"contains"in p?p.contains($):p.compareDocumentPosition?!!(p.compareDocumentPosition($)&16):!1:!1}function Mc(p){p=p!=null&&p.ownerDocument!=null&&p.ownerDocument.defaultView!=null?p.ownerDocument.defaultView:window;for(var $=ps(p.document);$ instanceof p.HTMLIFrameElement;){try{var _=typeof $.contentWindow.location.href=="string"}catch{_=!1}if(_)p=$.contentWindow;else break;$=ps(p.document)}return $}function Ei(p){var $=p&&p.nodeName&&p.nodeName.toLowerCase();return $&&($==="input"&&(p.type==="text"||p.type==="search"||p.type==="tel"||p.type==="url"||p.type==="password")||$==="textarea"||p.contentEditable==="true")}var dh=Vt&&"documentMode"in document&&11>=document.documentMode,Vo=null,ki=null,Mr=null,Ti=!1;function Nc(p,$,_){var v=_.window===_?_.document:_.nodeType===9?_:_.ownerDocument;Ti||Vo==null||Vo!==ps(v)||(v=Vo,"selectionStart"in v&&Ei(v)?v={start:v.selectionStart,end:v.selectionEnd}:(v=(v.ownerDocument&&v.ownerDocument.defaultView||window).getSelection(),v={anchorNode:v.anchorNode,anchorOffset:v.anchorOffset,focusNode:v.focusNode,focusOffset:v.focusOffset}),Mr&&jr(Mr,v)||(Mr=v,v=ti(ki,"onSelect"),0<v.length&&($=new Bo("onSelect","select",null,$,_),p.push({event:$,listeners:v}),$.target=Vo)))}function ko(p,$){var _={};return _[p.toLowerCase()]=$.toLowerCase(),_["Webkit"+p]="webkit"+$,_["Moz"+p]="moz"+$,_}var zo={animationend:ko("Animation","AnimationEnd"),animationiteration:ko("Animation","AnimationIteration"),animationstart:ko("Animation","AnimationStart"),transitionrun:ko("Transition","TransitionRun"),transitionstart:ko("Transition","TransitionStart"),transitioncancel:ko("Transition","TransitionCancel"),transitionend:ko("Transition","TransitionEnd")},Si={},Wc={};Vt&&(Wc=document.createElement("div").style,"AnimationEvent"in window||(delete zo.animationend.animation,delete zo.animationiteration.animation,delete zo.animationstart.animation),"TransitionEvent"in window||delete zo.transitionend.transition);function To(p){if(Si[p])return Si[p];if(!zo[p])return p;var $=zo[p],_;for(_ in $)if($.hasOwnProperty(_)&&_ in Wc)return Si[p]=$[_];return p}var Ic=To("animationend"),Lc=To("animationiteration"),Pc=To("animationstart"),mh=To("transitionrun"),hh=To("transitionstart"),ph=To("transitioncancel"),Cc=To("transitionend"),Oc=new Map,Ri="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");Ri.push("scrollEnd");function ds(p,$){Oc.set(p,$),Jt($,[p])}var pa=typeof reportError=="function"?reportError:function(p){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var $=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof p=="object"&&p!==null&&typeof p.message=="string"?String(p.message):String(p),error:p});if(!window.dispatchEvent($))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",p);return}console.error(p)},Zn=[],Yo=0,Ai=0;function fa(){for(var p=Yo,$=Ai=Yo=0;$<p;){var _=Zn[$];Zn[$++]=null;var v=Zn[$];Zn[$++]=null;var j=Zn[$];Zn[$++]=null;var I=Zn[$];if(Zn[$++]=null,v!==null&&j!==null){var C=v.pending;C===null?j.next=j:(j.next=C.next,C.next=j),v.pending=j}I!==0&&Dc(_,j,I)}}function $a(p,$,_,v){Zn[Yo++]=p,Zn[Yo++]=$,Zn[Yo++]=_,Zn[Yo++]=v,Ai|=v,p.lanes|=v,p=p.alternate,p!==null&&(p.lanes|=v)}function ji(p,$,_,v){return $a(p,$,_,v),ga(p)}function So(p,$){return $a(p,null,null,$),ga(p)}function Dc(p,$,_){p.lanes|=_;var v=p.alternate;v!==null&&(v.lanes|=_);for(var j=!1,I=p.return;I!==null;)I.childLanes|=_,v=I.alternate,v!==null&&(v.childLanes|=_),I.tag===22&&(p=I.stateNode,p===null||p._visibility&1||(j=!0)),p=I,I=I.return;return p.tag===3?(I=p.stateNode,j&&$!==null&&(j=31-ot(_),p=I.hiddenUpdates,v=p[j],v===null?p[j]=[$]:v.push($),$.lane=_|536870912),I):null}function ga(p){if(50<Jr)throw Jr=0,Dl=null,Error(s(185));for(var $=p.return;$!==null;)p=$,$=p.return;return p.tag===3?p.stateNode:null}var Ko={};function fh(p,$,_,v){this.tag=p,this.key=_,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=$,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=v,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Hn(p,$,_,v){return new fh(p,$,_,v)}function Mi(p){return p=p.prototype,!(!p||!p.isReactComponent)}function Ss(p,$){var _=p.alternate;return _===null?(_=Hn(p.tag,$,p.key,p.mode),_.elementType=p.elementType,_.type=p.type,_.stateNode=p.stateNode,_.alternate=p,p.alternate=_):(_.pendingProps=$,_.type=p.type,_.flags=0,_.subtreeFlags=0,_.deletions=null),_.flags=p.flags&65011712,_.childLanes=p.childLanes,_.lanes=p.lanes,_.child=p.child,_.memoizedProps=p.memoizedProps,_.memoizedState=p.memoizedState,_.updateQueue=p.updateQueue,$=p.dependencies,_.dependencies=$===null?null:{lanes:$.lanes,firstContext:$.firstContext},_.sibling=p.sibling,_.index=p.index,_.ref=p.ref,_.refCleanup=p.refCleanup,_}function Fc(p,$){p.flags&=65011714;var _=p.alternate;return _===null?(p.childLanes=0,p.lanes=$,p.child=null,p.subtreeFlags=0,p.memoizedProps=null,p.memoizedState=null,p.updateQueue=null,p.dependencies=null,p.stateNode=null):(p.childLanes=_.childLanes,p.lanes=_.lanes,p.child=_.child,p.subtreeFlags=0,p.deletions=null,p.memoizedProps=_.memoizedProps,p.memoizedState=_.memoizedState,p.updateQueue=_.updateQueue,p.type=_.type,$=_.dependencies,p.dependencies=$===null?null:{lanes:$.lanes,firstContext:$.firstContext}),p}function ba(p,$,_,v,j,I){var C=0;if(v=p,typeof p=="function")Mi(p)&&(C=1);else if(typeof p=="string")C=yp(p,_,ne.current)?26:p==="html"||p==="head"||p==="body"?27:5;else e:switch(p){case L:return p=Hn(31,_,$,j),p.elementType=L,p.lanes=I,p;case b:return Ro(_.children,j,I,$);case x:C=8,j|=24;break;case y:return p=Hn(12,_,$,j|2),p.elementType=y,p.lanes=I,p;case A:return p=Hn(13,_,$,j),p.elementType=A,p.lanes=I,p;case N:return p=Hn(19,_,$,j),p.elementType=N,p.lanes=I,p;default:if(typeof p=="object"&&p!==null)switch(p.$$typeof){case k:C=10;break e;case E:C=9;break e;case T:C=11;break e;case S:C=14;break e;case M:C=16,v=null;break e}C=29,_=Error(s(130,p===null?"null":typeof p,"")),v=null}return $=Hn(C,_,$,j),$.elementType=p,$.type=v,$.lanes=I,$}function Ro(p,$,_,v){return p=Hn(7,p,v,$),p.lanes=_,p}function Ni(p,$,_){return p=Hn(6,p,null,$),p.lanes=_,p}function Hc(p){var $=Hn(18,null,null,0);return $.stateNode=p,$}function Wi(p,$,_){return $=Hn(4,p.children!==null?p.children:[],p.key,$),$.lanes=_,$.stateNode={containerInfo:p.containerInfo,pendingChildren:null,implementation:p.implementation},$}var Bc=new WeakMap;function Jn(p,$){if(typeof p=="object"&&p!==null){var _=Bc.get(p);return _!==void 0?_:($={value:p,source:$,stack:Ne($)},Bc.set(p,$),$)}return{value:p,source:$,stack:Ne($)}}var Qo=[],Xo=0,_a=null,Nr=0,es=[],ts=0,Xs=null,gs=1,bs="";function Rs(p,$){Qo[Xo++]=Nr,Qo[Xo++]=_a,_a=p,Nr=$}function Uc(p,$,_){es[ts++]=gs,es[ts++]=bs,es[ts++]=Xs,Xs=p;var v=gs;p=bs;var j=32-ot(v)-1;v&=~(1<<j),_+=1;var I=32-ot($)+j;if(30<I){var C=j-j%5;I=(v&(1<<C)-1).toString(32),v>>=C,j-=C,gs=1<<32-ot($)+j|_<<j|v,bs=I+p}else gs=1<<I|_<<j|v,bs=p}function Ii(p){p.return!==null&&(Rs(p,1),Uc(p,1,0))}function Li(p){for(;p===_a;)_a=Qo[--Xo],Qo[Xo]=null,Nr=Qo[--Xo],Qo[Xo]=null;for(;p===Xs;)Xs=es[--ts],es[ts]=null,bs=es[--ts],es[ts]=null,gs=es[--ts],es[ts]=null}function Gc(p,$){es[ts++]=gs,es[ts++]=bs,es[ts++]=Xs,gs=$.id,bs=$.overflow,Xs=p}var mn=null,Nt=null,it=!1,Zs=null,ns=!1,Pi=Error(s(519));function Js(p){var $=Error(s(418,1<arguments.length&&arguments[1]!==void 0&&arguments[1]?"text":"HTML",""));throw Wr(Jn($,p)),Pi}function qc(p){var $=p.stateNode,_=p.type,v=p.memoizedProps;switch($[rt]=p,$[At]=v,_){case"dialog":Je("cancel",$),Je("close",$);break;case"iframe":case"object":case"embed":Je("load",$);break;case"video":case"audio":for(_=0;_<ta.length;_++)Je(ta[_],$);break;case"source":Je("error",$);break;case"img":case"image":case"link":Je("error",$),Je("load",$);break;case"details":Je("toggle",$);break;case"input":Je("invalid",$),qs($,v.value,v.defaultValue,v.checked,v.defaultChecked,v.type,v.name,!0);break;case"select":Je("invalid",$);break;case"textarea":Je("invalid",$),vs($,v.value,v.defaultValue,v.children)}_=v.children,typeof _!="string"&&typeof _!="number"&&typeof _!="bigint"||$.textContent===""+_||v.suppressHydrationWarning===!0||cm($.textContent,_)?(v.popover!=null&&(Je("beforetoggle",$),Je("toggle",$)),v.onScroll!=null&&Je("scroll",$),v.onScrollEnd!=null&&Je("scrollend",$),v.onClick!=null&&($.onclick=Be),$=!0):$=!1,$||Js(p,!0)}function Vc(p){for(mn=p.return;mn;)switch(mn.tag){case 5:case 31:case 13:ns=!1;return;case 27:case 3:ns=!0;return;default:mn=mn.return}}function Zo(p){if(p!==mn)return!1;if(!it)return Vc(p),it=!0,!1;var $=p.tag,_;if((_=$!==3&&$!==27)&&((_=$===5)&&(_=p.type,_=!(_!=="form"&&_!=="button")||ec(p.type,p.memoizedProps)),_=!_),_&&Nt&&Js(p),Vc(p),$===13){if(p=p.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(s(317));Nt=bm(p)}else if($===31){if(p=p.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(s(317));Nt=bm(p)}else $===27?($=Nt,fo(p.type)?(p=rc,rc=null,Nt=p):Nt=$):Nt=mn?os(p.stateNode.nextSibling):null;return!0}function Ao(){Nt=mn=null,it=!1}function Ci(){var p=Zs;return p!==null&&(Pn===null?Pn=p:Pn.push.apply(Pn,p),Zs=null),p}function Wr(p){Zs===null?Zs=[p]:Zs.push(p)}var Oi=B(null),jo=null,As=null;function eo(p,$,_){X(Oi,$._currentValue),$._currentValue=_}function js(p){p._currentValue=Oi.current,G(Oi)}function Di(p,$,_){for(;p!==null;){var v=p.alternate;if((p.childLanes&$)!==$?(p.childLanes|=$,v!==null&&(v.childLanes|=$)):v!==null&&(v.childLanes&$)!==$&&(v.childLanes|=$),p===_)break;p=p.return}}function Fi(p,$,_,v){var j=p.child;for(j!==null&&(j.return=p);j!==null;){var I=j.dependencies;if(I!==null){var C=j.child;I=I.firstContext;e:for(;I!==null;){var Y=I;I=j;for(var J=0;J<$.length;J++)if(Y.context===$[J]){I.lanes|=_,Y=I.alternate,Y!==null&&(Y.lanes|=_),Di(I.return,_,p),v||(C=null);break e}I=Y.next}}else if(j.tag===18){if(C=j.return,C===null)throw Error(s(341));C.lanes|=_,I=C.alternate,I!==null&&(I.lanes|=_),Di(C,_,p),C=null}else C=j.child;if(C!==null)C.return=j;else for(C=j;C!==null;){if(C===p){C=null;break}if(j=C.sibling,j!==null){j.return=C.return,C=j;break}C=C.return}j=C}}function Jo(p,$,_,v){p=null;for(var j=$,I=!1;j!==null;){if(!I){if((j.flags&524288)!==0)I=!0;else if((j.flags&262144)!==0)break}if(j.tag===10){var C=j.alternate;if(C===null)throw Error(s(387));if(C=C.memoizedProps,C!==null){var Y=j.type;Fn(j.pendingProps.value,C.value)||(p!==null?p.push(Y):p=[Y])}}else if(j===oe.current){if(C=j.alternate,C===null)throw Error(s(387));C.memoizedState.memoizedState!==j.memoizedState.memoizedState&&(p!==null?p.push(aa):p=[aa])}j=j.return}p!==null&&Fi($,p,_,v),$.flags|=262144}function wa(p){for(p=p.firstContext;p!==null;){if(!Fn(p.context._currentValue,p.memoizedValue))return!0;p=p.next}return!1}function Mo(p){jo=p,As=null,p=p.dependencies,p!==null&&(p.firstContext=null)}function hn(p){return zc(jo,p)}function xa(p,$){return jo===null&&Mo(p),zc(p,$)}function zc(p,$){var _=$._currentValue;if($={context:$,memoizedValue:_,next:null},As===null){if(p===null)throw Error(s(308));As=$,p.dependencies={lanes:0,firstContext:$},p.flags|=524288}else As=As.next=$;return _}var $h=typeof AbortController<"u"?AbortController:function(){var p=[],$=this.signal={aborted:!1,addEventListener:function(_,v){p.push(v)}};this.abort=function(){$.aborted=!0,p.forEach(function(_){return _()})}},gh=n.unstable_scheduleCallback,bh=n.unstable_NormalPriority,zt={$$typeof:k,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function Hi(){return{controller:new $h,data:new Map,refCount:0}}function Ir(p){p.refCount--,p.refCount===0&&gh(bh,function(){p.controller.abort()})}var Lr=null,Bi=0,er=0,tr=null;function _h(p,$){if(Lr===null){var _=Lr=[];Bi=0,er=ql(),tr={status:"pending",value:void 0,then:function(v){_.push(v)}}}return Bi++,$.then(Yc,Yc),$}function Yc(){if(--Bi===0&&Lr!==null){tr!==null&&(tr.status="fulfilled");var p=Lr;Lr=null,er=0,tr=null;for(var $=0;$<p.length;$++)(0,p[$])()}}function wh(p,$){var _=[],v={status:"pending",value:null,reason:null,then:function(j){_.push(j)}};return p.then(function(){v.status="fulfilled",v.value=$;for(var j=0;j<_.length;j++)(0,_[j])($)},function(j){for(v.status="rejected",v.reason=j,j=0;j<_.length;j++)(0,_[j])(void 0)}),v}var Kc=O.S;O.S=function(p,$){Wd=ge(),typeof $=="object"&&$!==null&&typeof $.then=="function"&&_h(p,$),Kc!==null&&Kc(p,$)};var No=B(null);function Ui(){var p=No.current;return p!==null?p:St.pooledCache}function ya(p,$){$===null?X(No,No.current):X(No,$.pool)}function Qc(){var p=Ui();return p===null?null:{parent:zt._currentValue,pool:p}}var nr=Error(s(460)),Gi=Error(s(474)),va=Error(s(542)),Ea={then:function(){}};function Xc(p){return p=p.status,p==="fulfilled"||p==="rejected"}function Zc(p,$,_){switch(_=p[_],_===void 0?p.push($):_!==$&&($.then(Be,Be),$=_),$.status){case"fulfilled":return $.value;case"rejected":throw p=$.reason,eu(p),p;default:if(typeof $.status=="string")$.then(Be,Be);else{if(p=St,p!==null&&100<p.shellSuspendCounter)throw Error(s(482));p=$,p.status="pending",p.then(function(v){if($.status==="pending"){var j=$;j.status="fulfilled",j.value=v}},function(v){if($.status==="pending"){var j=$;j.status="rejected",j.reason=v}})}switch($.status){case"fulfilled":return $.value;case"rejected":throw p=$.reason,eu(p),p}throw Io=$,nr}}function Wo(p){try{var $=p._init;return $(p._payload)}catch(_){throw _!==null&&typeof _=="object"&&typeof _.then=="function"?(Io=_,nr):_}}var Io=null;function Jc(){if(Io===null)throw Error(s(459));var p=Io;return Io=null,p}function eu(p){if(p===nr||p===va)throw Error(s(483))}var sr=null,Pr=0;function ka(p){var $=Pr;return Pr+=1,sr===null&&(sr=[]),Zc(sr,p,$)}function Cr(p,$){$=$.props.ref,p.ref=$!==void 0?$:null}function Ta(p,$){throw $.$$typeof===h?Error(s(525)):(p=Object.prototype.toString.call($),Error(s(31,p==="[object Object]"?"object with keys {"+Object.keys($).join(", ")+"}":p)))}function tu(p){function $(re,ee){if(p){var ce=re.deletions;ce===null?(re.deletions=[ee],re.flags|=16):ce.push(ee)}}function _(re,ee){if(!p)return null;for(;ee!==null;)$(re,ee),ee=ee.sibling;return null}function v(re){for(var ee=new Map;re!==null;)re.key!==null?ee.set(re.key,re):ee.set(re.index,re),re=re.sibling;return ee}function j(re,ee){return re=Ss(re,ee),re.index=0,re.sibling=null,re}function I(re,ee,ce){return re.index=ce,p?(ce=re.alternate,ce!==null?(ce=ce.index,ce<ee?(re.flags|=67108866,ee):ce):(re.flags|=67108866,ee)):(re.flags|=1048576,ee)}function C(re){return p&&re.alternate===null&&(re.flags|=67108866),re}function Y(re,ee,ce,pe){return ee===null||ee.tag!==6?(ee=Ni(ce,re.mode,pe),ee.return=re,ee):(ee=j(ee,ce),ee.return=re,ee)}function J(re,ee,ce,pe){var Pe=ce.type;return Pe===b?he(re,ee,ce.props.children,pe,ce.key):ee!==null&&(ee.elementType===Pe||typeof Pe=="object"&&Pe!==null&&Pe.$$typeof===M&&Wo(Pe)===ee.type)?(ee=j(ee,ce.props),Cr(ee,ce),ee.return=re,ee):(ee=ba(ce.type,ce.key,ce.props,null,re.mode,pe),Cr(ee,ce),ee.return=re,ee)}function ue(re,ee,ce,pe){return ee===null||ee.tag!==4||ee.stateNode.containerInfo!==ce.containerInfo||ee.stateNode.implementation!==ce.implementation?(ee=Wi(ce,re.mode,pe),ee.return=re,ee):(ee=j(ee,ce.children||[]),ee.return=re,ee)}function he(re,ee,ce,pe,Pe){return ee===null||ee.tag!==7?(ee=Ro(ce,re.mode,pe,Pe),ee.return=re,ee):(ee=j(ee,ce),ee.return=re,ee)}function fe(re,ee,ce){if(typeof ee=="string"&&ee!==""||typeof ee=="number"||typeof ee=="bigint")return ee=Ni(""+ee,re.mode,ce),ee.return=re,ee;if(typeof ee=="object"&&ee!==null){switch(ee.$$typeof){case f:return ce=ba(ee.type,ee.key,ee.props,null,re.mode,ce),Cr(ce,ee),ce.return=re,ce;case g:return ee=Wi(ee,re.mode,ce),ee.return=re,ee;case M:return ee=Wo(ee),fe(re,ee,ce)}if(V(ee)||D(ee))return ee=Ro(ee,re.mode,ce,null),ee.return=re,ee;if(typeof ee.then=="function")return fe(re,ka(ee),ce);if(ee.$$typeof===k)return fe(re,xa(re,ee),ce);Ta(re,ee)}return null}function de(re,ee,ce,pe){var Pe=ee!==null?ee.key:null;if(typeof ce=="string"&&ce!==""||typeof ce=="number"||typeof ce=="bigint")return Pe!==null?null:Y(re,ee,""+ce,pe);if(typeof ce=="object"&&ce!==null){switch(ce.$$typeof){case f:return ce.key===Pe?J(re,ee,ce,pe):null;case g:return ce.key===Pe?ue(re,ee,ce,pe):null;case M:return ce=Wo(ce),de(re,ee,ce,pe)}if(V(ce)||D(ce))return Pe!==null?null:he(re,ee,ce,pe,null);if(typeof ce.then=="function")return de(re,ee,ka(ce),pe);if(ce.$$typeof===k)return de(re,ee,xa(re,ce),pe);Ta(re,ce)}return null}function me(re,ee,ce,pe,Pe){if(typeof pe=="string"&&pe!==""||typeof pe
vendor: 5,137 bytes, line 9
9=="number"||typeof pe=="bigint")return re=re.get(ce)||null,Y(ee,re,""+pe,Pe);if(typeof pe=="object"&&pe!==null){switch(pe.$$typeof){case f:return re=re.get(pe.key===null?ce:pe.key)||null,J(ee,re,pe,Pe);case g:return re=re.get(pe.key===null?ce:pe.key)||null,ue(ee,re,pe,Pe);case M:return pe=Wo(pe),me(re,ee,ce,pe,Pe)}if(V(pe)||D(pe))return re=re.get(ce)||null,he(ee,re,pe,Pe,null);if(typeof pe.then=="function")return me(re,ee,ce,ka(pe),Pe);if(pe.$$typeof===k)return me(re,ee,ce,xa(ee,pe),Pe);Ta(ee,pe)}return null}function Ae(re,ee,ce,pe){for(var Pe=null,ut=null,je=ee,qe=ee=0,st=null;je!==null&&qe<ce.length;qe++){je.index>qe?(st=je,je=null):st=je.sibling;var dt=de(re,je,ce[qe],pe);if(dt===null){je===null&&(je=st);break}p&&je&&dt.alternate===null&&$(re,je),ee=I(dt,ee,qe),ut===null?Pe=dt:ut.sibling=dt,ut=dt,je=st}if(qe===ce.length)return _(re,je),it&&Rs(re,qe),Pe;if(je===null){for(;qe<ce.length;qe++)je=fe(re,ce[qe],pe),je!==null&&(ee=I(je,ee,qe),ut===null?Pe=je:ut.sibling=je,ut=je);return it&&Rs(re,qe),Pe}for(je=v(je);qe<ce.length;qe++)st=me(je,re,qe,ce[qe],pe),st!==null&&(p&&st.alternate!==null&&je.delete(st.key===null?qe:st.key),ee=I(st,ee,qe),ut===null?Pe=st:ut.sibling=st,ut=st);return p&&je.forEach(function(wo){return $(re,wo)}),it&&Rs(re,qe),Pe}function Oe(re,ee,ce,pe){if(ce==null)throw Error(s(151));for(var Pe=null,ut=null,je=ee,qe=ee=0,st=null,dt=ce.next();je!==null&&!dt.done;qe++,dt=ce.next()){je.index>qe?(st=je,je=null):st=je.sibling;var wo=de(re,je,dt.value,pe);if(wo===null){je===null&&(je=st);break}p&&je&&wo.alternate===null&&$(re,je),ee=I(wo,ee,qe),ut===null?Pe=wo:ut.sibling=wo,ut=wo,je=st}if(dt.done)return _(re,je),it&&Rs(re,qe),Pe;if(je===null){for(;!dt.done;qe++,dt=ce.next())dt=fe(re,dt.value,pe),dt!==null&&(ee=I(dt,ee,qe),ut===null?Pe=dt:ut.sibling=dt,ut=dt);return it&&Rs(re,qe),Pe}for(je=v(je);!dt.done;qe++,dt=ce.next())dt=me(je,re,qe,dt.value,pe),dt!==null&&(p&&dt.alternate!==null&&je.delete(dt.key===null?qe:dt.key),ee=I(dt,ee,qe),ut===null?Pe=dt:ut.sibling=dt,ut=dt);return p&&je.forEach(function(Wp){return $(re,Wp)}),it&&Rs(re,qe),Pe}function vt(re,ee,ce,pe){if(typeof ce=="object"&&ce!==null&&ce.type===b&&ce.key===null&&(ce=ce.props.children),typeof ce=="object"&&ce!==null){switch(ce.$$typeof){case f:e:{for(var Pe=ce.key;ee!==null;){if(ee.key===Pe){if(Pe=ce.type,Pe===b){if(ee.tag===7){_(re,ee.sibling),pe=j(ee,ce.props.children),pe.return=re,re=pe;break e}}else if(ee.elementType===Pe||typeof Pe=="object"&&Pe!==null&&Pe.$$typeof===M&&Wo(Pe)===ee.type){_(re,ee.sibling),pe=j(ee,ce.props),Cr(pe,ce),pe.return=re,re=pe;break e}_(re,ee);break}else $(re,ee);ee=ee.sibling}ce.type===b?(pe=Ro(ce.props.children,re.mode,pe,ce.key),pe.return=re,re=pe):(pe=ba(ce.type,ce.key,ce.props,null,re.mode,pe),Cr(pe,ce),pe.return=re,re=pe)}return C(re);case g:e:{for(Pe=ce.key;ee!==null;){if(ee.key===Pe)if(ee.tag===4&&ee.stateNode.containerInfo===ce.containerInfo&&ee.stateNode.implementation===ce.implementation){_(re,ee.sibling),pe=j(ee,ce.children||[]),pe.return=re,re=pe;break e}else{_(re,ee);break}else $(re,ee);ee=ee.sibling}pe=Wi(ce,re.mode,pe),pe.return=re,re=pe}return C(re);case M:return ce=Wo(ce),vt(re,ee,ce,pe)}if(V(ce))return Ae(re,ee,ce,pe);if(D(ce)){if(Pe=D(ce),typeof Pe!="function")throw Error(s(150));return ce=Pe.call(ce),Oe(re,ee,ce,pe)}if(typeof ce.then=="function")return vt(re,ee,ka(ce),pe);if(ce.$$typeof===k)return vt(re,ee,xa(re,ce),pe);Ta(re,ce)}return typeof ce=="string"&&ce!==""||typeof ce=="number"||typeof ce=="bigint"?(ce=""+ce,ee!==null&&ee.tag===6?(_(re,ee.sibling),pe=j(ee,ce),pe.return=re,re=pe):(_(re,ee),pe=Ni(ce,re.mode,pe),pe.return=re,re=pe),C(re)):_(re,ee)}return function(re,ee,ce,pe){try{Pr=0;var Pe=vt(re,ee,ce,pe);return sr=null,Pe}catch(je){if(je===nr||je===va)throw je;var ut=Hn(29,je,null,re.mode);return ut.lanes=pe,ut.return=re,ut}}}var Lo=tu(!0),nu=tu(!1),no=!1;function qi(p){p.updateQueue={baseState:p.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Vi(p,$){p=p.updateQueue,$.updateQueue===p&&($.updateQueue={baseState:p.baseState,firstBaseUpdate:p.firstBaseUpdate,lastBaseUpdate:p.lastBaseUpdate,shared:p.shared,callbacks:null})}function so(p){return{lane:p,tag:0,payload:null,callback:null,next:null}}function oo(p,$,_){var v=p.updateQueue;if(v===null)return null;if(v=v.shared,(pt&2)!==0){var j=v.pending;return j===null?$.next=$:($.next=j.next,j.next=$),v.pending=$,$=ga(p),Dc(p,null,_),$}return $a(p,v,$,_),ga(p)}function Or(p,$,_){if($=$.updateQueue,$!==null&&($=$.shared,(_&4194048)!==0)){var v=$.lanes;v&=p.pendingLanes,_|=v,$.lanes=_,kn(p,_)}}function zi(p,$){var _=p.updateQueue,v=p.alternate;if(v!==null&&(v=v.updateQueue,_===v)){var j=null,I=null;if(_=_.firstBaseUpdate,_!==null){do{var C={lane:_.lane,tag:_.tag,payload:_.payload,callback:null,next:null};I===null?j=I=C:I=I.next=C,_=_.next}while(_!==null);I===null?j=I=$:I=I.next=$}else j=I=$;_={baseState:v.baseState,firstBaseUpdate:j,lastBaseUpdate:I,shared:v.shared,callbacks:v.callbacks},p.updateQueue=_;return}p=_.lastBaseUpdate,p===null?_.firstBaseUpdate=$:p.next=$,_.lastBaseUpdate=$}var Yi=!1;
vendor: 6,568 bytes, line 9
9function Dr(){if(Yi){var p=tr;if(p!==null)throw p}}function Fr(p,$,_,v){Yi=!1;var j=p.updateQueue;no=!1;var I=j.firstBaseUpdate,C=j.lastBaseUpdate,Y=j.shared.pending;if(Y!==null){j.shared.pending=null;var J=Y,ue=J.next;J.next=null,C===null?I=ue:C.next=ue,C=J;var he=p.alternate;he!==null&&(he=he.updateQueue,Y=he.lastBaseUpdate,Y!==C&&(Y===null?he.firstBaseUpdate=ue:Y.next=ue,he.lastBaseUpdate=J))}if(I!==null){var fe=j.baseState;C=0,he=ue=J=null,Y=I;do{var de=Y.lane&-536870913,me=de!==Y.lane;if(me?(nt&de)===de:(v&de)===de){de!==0&&de===er&&(Yi=!0),he!==null&&(he=he.next={lane:0,tag:Y.tag,payload:Y.payload,callback:null,next:null});e:{var Ae=p,Oe=Y;de=$;var vt=_;switch(Oe.tag){case 1:if(Ae=Oe.payload,typeof Ae=="function"){fe=Ae.call(vt,fe,de);break e}fe=Ae;break e;case 3:Ae.flags=Ae.flags&-65537|128;case 0:if(Ae=Oe.payload,de=typeof Ae=="function"?Ae.call(vt,fe,de):Ae,de==null)break e;fe=m({},fe,de);break e;case 2:no=!0}}de=Y.callback,de!==null&&(p.flags|=64,me&&(p.flags|=8192),me=j.callbacks,me===null?j.callbacks=[de]:me.push(de))}else me={lane:de,tag:Y.tag,payload:Y.payload,callback:Y.callback,next:null},he===null?(ue=he=me,J=fe):he=he.next=me,C|=de;if(Y=Y.next,Y===null){if(Y=j.shared.pending,Y===null)break;me=Y,Y=me.next,me.next=null,j.lastBaseUpdate=me,j.shared.pending=null}}while(!0);he===null&&(J=fe),j.baseState=J,j.firstBaseUpdate=ue,j.lastBaseUpdate=he,I===null&&(j.shared.lanes=0),co|=C,p.lanes=C,p.memoizedState=fe}}function su(p,$){if(typeof p!="function")throw Error(s(191,p));p.call($)}function ou(p,$){var _=p.callbacks;if(_!==null)for(p.callbacks=null,p=0;p<_.length;p++)su(_[p],$)}var or=B(null),Sa=B(0);function ru(p,$){p=Ds,X(Sa,p),X(or,$),Ds=p|$.baseLanes}function Ki(){X(Sa,Ds),X(or,or.current)}function Qi(){Ds=Sa.current,G(or),G(Sa)}var Bn=B(null),ss=null;function ro(p){var $=p.alternate;X(Ht,Ht.current&1),X(Bn,p),ss===null&&($===null||or.current!==null||$.memoizedState!==null)&&(ss=p)}function Xi(p){X(Ht,Ht.current),X(Bn,p),ss===null&&(ss=p)}function au(p){p.tag===22?(X(Ht,Ht.current),X(Bn,p),ss===null&&(ss=p)):ao()}function ao(){X(Ht,Ht.current),X(Bn,Bn.current)}function Un(p){G(Bn),ss===p&&(ss=null),G(Ht)}var Ht=B(0);function Ra(p){for(var $=p;$!==null;){if($.tag===13){var _=$.memoizedState;if(_!==null&&(_=_.dehydrated,_===null||sc(_)||oc(_)))return $}else if($.tag===19&&($.memoizedProps.revealOrder==="forwards"||$.memoizedProps.revealOrder==="backwards"||$.memoizedProps.revealOrder==="unstable_legacy-backwards"||$.memoizedProps.revealOrder==="together")){if(($.flags&128)!==0)return $}else if($.child!==null){$.child.return=$,$=$.child;continue}if($===p)break;for(;$.sibling===null;){if($.return===null||$.return===p)return null;$=$.return}$.sibling.return=$.return,$=$.sibling}return null}var Ms=0,Ue=null,xt=null,Yt=null,Aa=!1,rr=!1,Po=!1,ja=0,Hr=0,ar=null,xh=0;function Ot(){throw Error(s(321))}function Zi(p,$){if($===null)return!1;for(var _=0;_<$.length&&_<p.length;_++)if(!Fn(p[_],$[_]))return!1;return!0}function Ji(p,$,_,v,j,I){return Ms=I,Ue=$,$.memoizedState=null,$.updateQueue=null,$.lanes=0,O.H=p===null||p.memoizedState===null?Gu:pl,Po=!1,I=_(v,j),Po=!1,rr&&(I=lu($,_,v,j)),iu(p),I}function iu(p){O.H=Gr;var $=xt!==null&&xt.next!==null;if(Ms=0,Yt=xt=Ue=null,Aa=!1,Hr=0,ar=null,$)throw Error(s(300));p===null||Kt||(p=p.dependencies,p!==null&&wa(p)&&(Kt=!0))}function lu(p,$,_,v){Ue=p;var j=0;do{if(rr&&(ar=null),Hr=0,rr=!1,25<=j)throw Error(s(301));if(j+=1,Yt=xt=null,p.updateQueue!=null){var I=p.updateQueue;I.lastEffect=null,I.events=null,I.stores=null,I.memoCache!=null&&(I.memoCache.index=0)}O.H=qu,I=$(_,v)}while(rr);return I}function yh(){var p=O.H,$=p.useState()[0];return $=typeof $.then=="function"?Br($):$,p=p.useState()[0],(xt!==null?xt.memoizedState:null)!==p&&(Ue.flags|=1024),$}function el(){var p=ja!==0;return ja=0,p}function tl(p,$,_){$.updateQueue=p.updateQueue,$.flags&=-2053,p.lanes&=~_}function nl(p){if(Aa){for(p=p.memoizedState;p!==null;){var $=p.queue;$!==null&&($.pending=null),p=p.next}Aa=!1}Ms=0,Yt=xt=Ue=null,rr=!1,Hr=ja=0,ar=null}function vn(){var p={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return Yt===null?Ue.memoizedState=Yt=p:Yt=Yt.next=p,Yt}function Bt(){if(xt===null){var p=Ue.alternate;p=p!==null?p.memoizedState:null}else p=xt.next;var $=Yt===null?Ue.memoizedState:Yt.next;if($!==null)Yt=$,xt=p;else{if(p===null)throw Ue.alternate===null?Error(s(467)):Error(s(310));xt=p,p={memoizedState:xt.memoizedState,baseState:xt.baseState,baseQueue:xt.baseQueue,queue:xt.queue,next:null},Yt===null?Ue.memoizedState=Yt=p:Yt=Yt.next=p}return Yt}function Ma(){return{lastEffect:null,events:null,stores:null,memoCache:null}}function Br(p){var $=Hr;return Hr+=1,ar===null&&(ar=[]),p=Zc(ar,p,$),$=Ue,(Yt===null?$.memoizedState:Yt.next)===null&&($=$.alternate,O.H=$===null||$.memoizedState===null?Gu:pl),p}function Na(p){if(p!==null&&typeof p=="object"){if(typeof p.then=="function")return Br(p);if(p.$$typeof===k)return hn(p)}throw Error(s(438,String(p)))}function sl(p){var $=null,_=Ue.updateQueue;if(_!==null&&($=_.memoCache),$==null){var v=Ue.alternate;v!==null&&(v=v.updateQueue,v!==null&&(v=v.memoCache,v!=null&&($={data:v.data.map(function(j){return j.slice()}),index:0})))}if($==null&&($={data:[],index:0}),_===null&&(_=Ma(),Ue.updateQueue=_),_.memoCache=$,_=$.data[$.index],_===void 0)for(_=$.data[$.index]=Array(p),v=0;v<p;v++)_[v]=P;return $.index++,_}function Ns(p,$){return typeof $=="function"?$(p):$}function Wa(p){var $=Bt();return ol($,xt,p)}function ol(p,$,_){var v=p.queue;if(v===null)throw Error(s(311));v.lastRenderedReducer=_;var j=p.baseQueue,I=v.pending;if(I!==null){if(j!==null){var C=j.next;j.next=I.next,I.next=C}$.baseQueue=j=I,v.pending=null}if(I=p.baseState,j===null)p.memoizedState=I;else{$=j.next;var Y=C=null,J=null,ue=$,he=!1;do{var fe=ue.lane&-536870913;if(fe!==ue.lane?(nt&fe)===fe:(Ms&fe)===fe){var de=ue.revertLane;if(de===0)J!==null&&(J=J.next={lane:0,revertLane:0,gesture:null,action:ue.action,hasEagerState:ue.hasEagerState,eagerState:ue.eagerState,next:null}),fe===er&&(he=!0);else if((Ms&de)===de){ue=ue.next,de===er&&(he=!0);continue}else fe={lane:0,revertLane:ue.revertLane,gesture:null,action:ue.action,hasEagerState:ue.hasEagerState,eagerState:ue.eagerState,next:null},J===null?(Y=J=fe,C=I):J=J.next=fe,Ue.lanes|=de,co|=de;fe=ue.action,Po&&_(I,fe),I=ue.hasEagerState?ue.eagerState:_(I,fe)}else de={lane:fe,revertLane:ue.revertLane,gesture:ue.gesture,action:ue.action,hasEagerState:ue.hasEagerState,eagerState:ue.eagerState,next:null},J===null?(Y=J=de,C=I):J=J.next=de,Ue.lanes|=fe,co|=fe;ue=ue.next}
vendor: 9,125 bytes, line 9
9while(ue!==null&&ue!==$);if(J===null?C=I:J.next=Y,!Fn(I,p.memoizedState)&&(Kt=!0,he&&(_=tr,_!==null)))throw _;p.memoizedState=I,p.baseState=C,p.baseQueue=J,v.lastRenderedState=I}return j===null&&(v.lanes=0),[p.memoizedState,v.dispatch]}function rl(p){var $=Bt(),_=$.queue;if(_===null)throw Error(s(311));_.lastRenderedReducer=p;var v=_.dispatch,j=_.pending,I=$.memoizedState;if(j!==null){_.pending=null;var C=j=j.next;do I=p(I,C.action),C=C.next;while(C!==j);Fn(I,$.memoizedState)||(Kt=!0),$.memoizedState=I,$.baseQueue===null&&($.baseState=I),_.lastRenderedState=I}return[I,v]}function cu(p,$,_){var v=Ue,j=Bt(),I=it;if(I){if(_===void 0)throw Error(s(407));_=_()}else _=$();var C=!Fn((xt||j).memoizedState,_);if(C&&(j.memoizedState=_,Kt=!0),j=j.queue,ll(mu.bind(null,v,j,p),[p]),j.getSnapshot!==$||C||Yt!==null&&Yt.memoizedState.tag&1){if(v.flags|=2048,ir(9,{destroy:void 0},du.bind(null,v,j,_,$),null),St===null)throw Error(s(349));I||(Ms&127)!==0||uu(v,$,_)}return _}function uu(p,$,_){p.flags|=16384,p={getSnapshot:$,value:_},$=Ue.updateQueue,$===null?($=Ma(),Ue.updateQueue=$,$.stores=[p]):(_=$.stores,_===null?$.stores=[p]:_.push(p))}function du(p,$,_,v){$.value=_,$.getSnapshot=v,hu($)&&pu(p)}function mu(p,$,_){return _(function(){hu($)&&pu(p)})}function hu(p){var $=p.getSnapshot;p=p.value;try{var _=$();return!Fn(p,_)}catch{return!0}}function pu(p){var $=So(p,2);$!==null&&Cn($,p,2)}function al(p){var $=vn();if(typeof p=="function"){var _=p;if(p=_(),Po){$t(!0);try{_()}finally{$t(!1)}}}return $.memoizedState=$.baseState=p,$.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ns,lastRenderedState:p},$}function fu(p,$,_,v){return p.baseState=_,ol(p,xt,typeof v=="function"?v:Ns)}function vh(p,$,_,v,j){if(Pa(p))throw Error(s(485));if(p=$.action,p!==null){var I={payload:j,action:p,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(C){I.listeners.push(C)}};O.T!==null?_(!0):I.isTransition=!1,v(I),_=$.pending,_===null?(I.next=$.pending=I,$u($,I)):(I.next=_.next,$.pending=_.next=I)}}function $u(p,$){var _=$.action,v=$.payload,j=p.state;if($.isTransition){var I=O.T,C={};O.T=C;try{var Y=_(j,v),J=O.S;J!==null&&J(C,Y),gu(p,$,Y)}catch(ue){il(p,$,ue)}finally{I!==null&&C.types!==null&&(I.types=C.types),O.T=I}}else try{I=_(j,v),gu(p,$,I)}catch(ue){il(p,$,ue)}}function gu(p,$,_){_!==null&&typeof _=="object"&&typeof _.then=="function"?_.then(function(v){bu(p,$,v)},function(v){return il(p,$,v)}):bu(p,$,_)}function bu(p,$,_){$.status="fulfilled",$.value=_,_u($),p.state=_,$=p.pending,$!==null&&(_=$.next,_===$?p.pending=null:(_=_.next,$.next=_,$u(p,_)))}function il(p,$,_){var v=p.pending;if(p.pending=null,v!==null){v=v.next;do $.status="rejected",$.reason=_,_u($),$=$.next;while($!==v)}p.action=null}function _u(p){p=p.listeners;for(var $=0;$<p.length;$++)(0,p[$])()}function xu(p,$){return $}function yu(p,$){if(it){var _=St.formState;if(_!==null){e:{var v=Ue;if(it){if(Nt){t:{for(var j=Nt,I=ns;j.nodeType!==8;){if(!I){j=null;break t}if(j=os(j.nextSibling),j===null){j=null;break t}}I=j.data,j=I==="F!"||I==="F"?j:null}if(j){Nt=os(j.nextSibling),v=j.data==="F!";break e}}Js(v)}v=!1}v&&($=_[0])}}return _=vn(),_.memoizedState=_.baseState=$,v={pending:null,lanes:0,dispatch:null,lastRenderedReducer:xu,lastRenderedState:$},_.queue=v,_=Hu.bind(null,Ue,v),v.dispatch=_,v=al(!1),I=hl.bind(null,Ue,!1,v.queue),v=vn(),j={state:$,dispatch:null,action:p,pending:null},v.queue=j,_=vh.bind(null,Ue,j,I,_),j.dispatch=_,v.memoizedState=p,[$,_,!1]}function vu(p){var $=Bt();return Eu($,xt,p)}function Eu(p,$,_){if($=ol(p,$,xu)[0],p=Wa(Ns)[0],typeof $=="object"&&$!==null&&typeof $.then=="function")try{var v=Br($)}catch(C){throw C===nr?va:C}else v=$;$=Bt();var j=$.queue,I=j.dispatch;return _!==$.memoizedState&&(Ue.flags|=2048,ir(9,{destroy:void 0},Eh.bind(null,j,_),null)),[v,I,p]}function Eh(p,$){p.action=$}function ku(p){var $=Bt(),_=xt;if(_!==null)return Eu($,_,p);Bt(),$=$.memoizedState,_=Bt();var v=_.queue.dispatch;return _.memoizedState=p,[$,v,!1]}function ir(p,$,_,v){return p={tag:p,create:_,deps:v,inst:$,next:null},$=Ue.updateQueue,$===null&&($=Ma(),Ue.updateQueue=$),_=$.lastEffect,_===null?$.lastEffect=p.next=p:(v=_.next,_.next=p,p.next=v,$.lastEffect=p),p}function Tu(){return Bt().memoizedState}function Ia(p,$,_,v){var j=vn();Ue.flags|=p,j.memoizedState=ir(1|$,{destroy:void 0},_,v===void 0?null:v)}function La(p,$,_,v){var j=Bt();v=v===void 0?null:v;var I=j.memoizedState.inst;xt!==null&&v!==null&&Zi(v,xt.memoizedState.deps)?j.memoizedState=ir($,I,_,v):(Ue.flags|=p,j.memoizedState=ir(1|$,I,_,v))}function Su(p,$){Ia(8390656,8,p,$)}function ll(p,$){La(2048,8,p,$)}function kh(p){Ue.flags|=4;var $=Ue.updateQueue;if($===null)$=Ma(),Ue.updateQueue=$,$.events=[p];else{var _=$.events;_===null?$.events=[p]:_.push(p)}}function Ru(p){var $=Bt().memoizedState;return kh({ref:$,nextImpl:p}),function(){if((pt&2)!==0)throw Error(s(440));return $.impl.apply(void 0,arguments)}}function Au(p,$){return La(4,2,p,$)}function ju(p,$){return La(4,4,p,$)}function Mu(p,$){if(typeof $=="function"){p=p();var _=$(p);return function(){typeof _=="function"?_():$(null)}}if($!=null)return p=p(),$.current=p,function(){$.current=null}}function Nu(p,$,_){_=_!=null?_.concat([p]):null,La(4,4,Mu.bind(null,$,p),_)}function cl(){}function Wu(p,$){var _=Bt();$=$===void 0?null:$;var v=_.memoizedState;return $!==null&&Zi($,v[1])?v[0]:(_.memoizedState=[p,$],p)}function Iu(p,$){var _=Bt();$=$===void 0?null:$;var v=_.memoizedState;if($!==null&&Zi($,v[1]))return v[0];if(v=p(),Po){$t(!0);try{p()}finally{$t(!1)}}return _.memoizedState=[v,$],v}function ul(p,$,_){return _===void 0||(Ms&1073741824)!==0&&(nt&261930)===0?p.memoizedState=$:(p.memoizedState=_,p=Ld(),Ue.lanes|=p,co|=p,_)}function Lu(p,$,_,v){return Fn(_,$)?_:or.current!==null?(p=ul(p,_,v),Fn(p,$)||(Kt=!0),p):(Ms&42)===0||(Ms&1073741824)!==0&&(nt&261930)===0?(Kt=!0,p.memoizedState=_):(p=Ld(),Ue.lanes|=p,co|=p,$)}function Pu(p,$,_,v,j){var I=z.p;z.p=I!==0&&8>I?I:8;var C=O.T,Y={};O.T=Y,hl(p,!1,$,_);try{var J=j(),ue=O.S;if(ue!==null&&ue(Y,J),J!==null&&typeof J=="object"&&typeof J.then=="function"){var he=wh(J,v);Ur(p,$,he,Vn(p))}else Ur(p,$,v,Vn(p))}catch(fe){Ur(p,$,{then:function(){},status:"rejected",reason:fe},Vn())}finally{z.p=I,C!==null&&Y.types!==null&&(C.types=Y.types),O.T=C}}function Th(){}function dl(p,$,_,v){if(p.tag!==5)throw Error(s(476));var j=Cu(p).queue;Pu(p,j,$,K,_===null?Th:function(){return Ou(p),_(v)})}function Cu(p){var $=p.memoizedState;if($!==null)return $;$={memoizedState:K,baseState:K,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ns,lastRenderedState:K},next:null};var _={};return $.next={memoizedState:_,baseState:_,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ns,lastRenderedState:_},next:null},p.memoizedState=$,p=p.alternate,p!==null&&(p.memoizedState=$),$}function Ou(p){var $=Cu(p);$.next===null&&($=p.alternate.memoizedState),Ur(p,$.next.queue,{},Vn())}function ml(){return hn(aa)}function Du(){return Bt().memoizedState}function Fu(){return Bt().memoizedState}function Sh(p){for(var $=p.return;$!==null;){switch($.tag){case 24:case 3:var _=Vn();p=so(_);var v=oo($,p,_);v!==null&&(Cn(v,$,_),Or(v,$,_)),$={cache:Hi()},p.payload=$;return}$=$.return}}function Rh(p,$,_){var v=Vn();_={lane:v,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null},Pa(p)?Bu($,_):(_=ji(p,$,_,v),_!==null&&(Cn(_,p,v),Uu(_,$,v)))}function Hu(p,$,_){var v=Vn();Ur(p,$,_,v)}function Ur(p,$,_,v){var j={lane:v,revertLane:0,gesture:null,action:_,hasEagerState:!1,eagerState:null,next:null};if(Pa(p))Bu($,j);else{var I=p.alternate;if(p.lanes===0&&(I===null||I.lanes===0)&&(I=$.lastRenderedReducer,I!==null))try{var C=$.lastRenderedState,Y=I(C,_);if(j.hasEagerState=!0,j.eagerState=Y,Fn(Y,C))return $a(p,$,j,0),St===null&&fa(),!1}catch{}if(_=ji(p,$,j,v),_!==null)return Cn(_,p,v),Uu(_,$,v),!0}return!1}function hl(p,$,_,v){if(v={lane:2,revertLane:ql(),gesture:null,action:v,hasEagerState:!1,eagerState:null,next:null},Pa(p)){if($)throw Error(s(479))}else $=ji(p,_,v,2),$!==null&&Cn($,p,2)}function Pa(p){var $=p.alternate;return p===Ue||$!==null&&$===Ue}function Bu(p,$){rr=Aa=!0;var _=p.pending;_===null?$.next=$:($.next=_.next,_.next=$),p.pending=$}function Uu(p,$,_){if((_&4194048)!==0){var v=$.lanes;v&=p.pendingLanes,_|=v,$.lanes=_,kn(p,_)}}var Gr={readContext:hn,use:Na,useCallback:Ot,useContext:Ot,useEffect:Ot,useImperativeHandle:Ot,useLayoutEffect:Ot,useInsertionEffect:Ot,useMemo:Ot,useReducer:Ot,useRef:Ot,useState:Ot,useDebugValue:Ot,useDeferredValue:Ot,useTransition:Ot,useSyncExternalStore:Ot,useId:Ot,useHostTransitionStatus:Ot,useFormState:Ot,useActionState:Ot,useOptimistic:Ot,useMemoCache:Ot,useCacheRefresh:Ot};Gr.useEffectEvent=Ot;var Gu={readContext:hn,use:Na,useCallback:function(p,$){return vn().memoizedState=[p,$===void 0?null:$],p},useContext:hn,useEffect:Su,useImperativeHandle:function(p,$,_){_=_!=null?_.concat([p]):null,Ia(4194308,4,Mu.bind(null,$,p),_)},useLayoutEffect:function(p,$){return Ia(4194308,4,p,$)},useInsertionEffect:function(p,$){Ia(4,2,p,$)},useMemo:function(p,$){var _=vn();
vendor: 7,484 bytes, line 9
9$=$===void 0?null:$;var v=p();if(Po){$t(!0);try{p()}finally{$t(!1)}}return _.memoizedState=[v,$],v},useReducer:function(p,$,_){var v=vn();if(_!==void 0){var j=_($);if(Po){$t(!0);try{_($)}finally{$t(!1)}}}else j=$;return v.memoizedState=v.baseState=j,p={pending:null,lanes:0,dispatch:null,lastRenderedReducer:p,lastRenderedState:j},v.queue=p,p=p.dispatch=Rh.bind(null,Ue,p),[v.memoizedState,p]},useRef:function(p){var $=vn();return p={current:p},$.memoizedState=p},useState:function(p){p=al(p);var $=p.queue,_=Hu.bind(null,Ue,$);return $.dispatch=_,[p.memoizedState,_]},useDebugValue:cl,useDeferredValue:function(p,$){var _=vn();return ul(_,p,$)},useTransition:function(){var p=al(!1);return p=Pu.bind(null,Ue,p.queue,!0,!1),vn().memoizedState=p,[!1,p]},useSyncExternalStore:function(p,$,_){var v=Ue,j=vn();if(it){if(_===void 0)throw Error(s(407));_=_()}else{if(_=$(),St===null)throw Error(s(349));(nt&127)!==0||uu(v,$,_)}j.memoizedState=_;var I={value:_,getSnapshot:$};return j.queue=I,Su(mu.bind(null,v,I,p),[p]),v.flags|=2048,ir(9,{destroy:void 0},du.bind(null,v,I,_,$),null),_},useId:function(){var p=vn(),$=St.identifierPrefix;if(it){var _=bs,v=gs;_=(v&~(1<<32-ot(v)-1)).toString(32)+_,$="_"+$+"R_"+_,_=ja++,0<_&&($+="H"+_.toString(32)),$+="_"}else _=xh++,$="_"+$+"r_"+_.toString(32)+"_";return p.memoizedState=$},useHostTransitionStatus:ml,useFormState:yu,useActionState:yu,useOptimistic:function(p){var $=vn();$.memoizedState=$.baseState=p;var _={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return $.queue=_,$=hl.bind(null,Ue,!0,_),_.dispatch=$,[p,$]},useMemoCache:sl,useCacheRefresh:function(){return vn().memoizedState=Sh.bind(null,Ue)},useEffectEvent:function(p){var $=vn(),_={impl:p};return $.memoizedState=_,function(){if((pt&2)!==0)throw Error(s(440));return _.impl.apply(void 0,arguments)}}},pl={readContext:hn,use:Na,useCallback:Wu,useContext:hn,useEffect:ll,useImperativeHandle:Nu,useInsertionEffect:Au,useLayoutEffect:ju,useMemo:Iu,useReducer:Wa,useRef:Tu,useState:function(){return Wa(Ns)},useDebugValue:cl,useDeferredValue:function(p,$){var _=Bt();return Lu(_,xt.memoizedState,p,$)},useTransition:function(){var p=Wa(Ns)[0],$=Bt().memoizedState;return[typeof p=="boolean"?p:Br(p),$]},useSyncExternalStore:cu,useId:Du,useHostTransitionStatus:ml,useFormState:vu,useActionState:vu,useOptimistic:function(p,$){var _=Bt();return fu(_,xt,p,$)},useMemoCache:sl,useCacheRefresh:Fu};pl.useEffectEvent=Ru;var qu={readContext:hn,use:Na,useCallback:Wu,useContext:hn,useEffect:ll,useImperativeHandle:Nu,useInsertionEffect:Au,useLayoutEffect:ju,useMemo:Iu,useReducer:rl,useRef:Tu,useState:function(){return rl(Ns)},useDebugValue:cl,useDeferredValue:function(p,$){var _=Bt();return xt===null?ul(_,p,$):Lu(_,xt.memoizedState,p,$)},useTransition:function(){var p=rl(Ns)[0],$=Bt().memoizedState;return[typeof p=="boolean"?p:Br(p),$]},useSyncExternalStore:cu,useId:Du,useHostTransitionStatus:ml,useFormState:ku,useActionState:ku,useOptimistic:function(p,$){var _=Bt();return xt!==null?fu(_,xt,p,$):(_.baseState=p,[p,_.queue.dispatch])},useMemoCache:sl,useCacheRefresh:Fu};qu.useEffectEvent=Ru;function fl(p,$,_,v){$=p.memoizedState,_=_(v,$),_=_==null?$:m({},$,_),p.memoizedState=_,p.lanes===0&&(p.updateQueue.baseState=_)}var $l={enqueueSetState:function(p,$,_){p=p._reactInternals;var v=Vn(),j=so(v);j.payload=$,_!=null&&(j.callback=_),$=oo(p,j,v),$!==null&&(Cn($,p,v),Or($,p,v))},enqueueReplaceState:function(p,$,_){p=p._reactInternals;var v=Vn(),j=so(v);j.tag=1,j.payload=$,_!=null&&(j.callback=_),$=oo(p,j,v),$!==null&&(Cn($,p,v),Or($,p,v))},enqueueForceUpdate:function(p,$){p=p._reactInternals;var _=Vn(),v=so(_);v.tag=2,$!=null&&(v.callback=$),$=oo(p,v,_),$!==null&&(Cn($,p,_),Or($,p,_))}};function Vu(p,$,_,v,j,I,C){return p=p.stateNode,typeof p.shouldComponentUpdate=="function"?p.shouldComponentUpdate(v,I,C):$.prototype&&$.prototype.isPureReactComponent?!jr(_,v)||!jr(j,I):!0}function zu(p,$,_,v){p=$.state,typeof $.componentWillReceiveProps=="function"&&$.componentWillReceiveProps(_,v),typeof $.UNSAFE_componentWillReceiveProps=="function"&&$.UNSAFE_componentWillReceiveProps(_,v),$.state!==p&&$l.enqueueReplaceState($,$.state,null)}function Co(p,$){var _=$;if("ref"in $){_={};for(var v in $)v!=="ref"&&(_[v]=$[v])}if(p=p.defaultProps){_===$&&(_=m({},_));for(var j in p)_[j]===void 0&&(_[j]=p[j])}return _}function Yu(p){pa(p)}function Ku(p){console.error(p)}function Qu(p){pa(p)}function Ca(p,$){try{var _=p.onUncaughtError;_($.value,{componentStack:$.stack})}catch(v){setTimeout(function(){throw v})}}function Xu(p,$,_){try{var v=p.onCaughtError;v(_.value,{componentStack:_.stack,errorBoundary:$.tag===1?$.stateNode:null})}catch(j){setTimeout(function(){throw j})}}function gl(p,$,_){return _=so(_),_.tag=3,_.payload={element:null},_.callback=function(){Ca(p,$)},_}function Zu(p){return p=so(p),p.tag=3,p}function Ju(p,$,_,v){var j=_.type.getDerivedStateFromError;if(typeof j=="function"){var I=v.value;p.payload=function(){return j(I)},p.callback=function(){Xu($,_,v)}}var C=_.stateNode;C!==null&&typeof C.componentDidCatch=="function"&&(p.callback=function(){Xu($,_,v),typeof j!="function"&&(uo===null?uo=new Set([this]):uo.add(this));var Y=v.stack;this.componentDidCatch(v.value,{componentStack:Y!==null?Y:""})})}function Ah(p,$,_,v,j){if(_.flags|=32768,v!==null&&typeof v=="object"&&typeof v.then=="function"){if($=_.alternate,$!==null&&Jo($,_,j,!0),_=Bn.current,_!==null){switch(_.tag){case 31:case 13:return ss===null?Ka():_.alternate===null&&Dt===0&&(Dt=3),_.flags&=-257,_.flags|=65536,_.lanes=j,v===Ea?_.flags|=16384:($=_.updateQueue,$===null?_.updateQueue=new Set([v]):$.add(v),Bl(p,v,j)),!1;case 22:return _.flags|=65536,v===Ea?_.flags|=16384:($=_.updateQueue,$===null?($={transitions:null,markerInstances:null,retryQueue:new Set([v])},_.updateQueue=$):(_=$.retryQueue,_===null?$.retryQueue=new Set([v]):_.add(v)),Bl(p,v,j)),!1}throw Error(s(435,_.tag))}return Bl(p,v,j),Ka(),!1}if(it)return $=Bn.current,$!==null?(($.flags&65536)===0&&($.flags|=256),$.flags|=65536,$.lanes=j,v!==Pi&&(p=Error(s(422),{cause:v}),Wr(Jn(p,_)))):(v!==Pi&&($=Error(s(423),{cause:v}),Wr(Jn($,_))),p=p.current.alternate,p.flags|=65536,j&=-j,p.lanes|=j,v=Jn(v,_),j=gl(p.stateNode,v,j),zi(p,j),Dt!==4&&(Dt=2)),!1;var I=Error(s(520),{cause:v});if(I=Jn(I,_),Zr===null?Zr=[I]:Zr.push(I),Dt!==4&&(Dt=2),$===null)return!0;v=Jn(v,_),_=$;do{switch(_.tag){case 3:return _.flags|=65536,p=j&-j,_.lanes|=p,p=gl(_.stateNode,v,p),zi(_,p),!1;case 1:if($=_.type,I=_.stateNode,(_.flags&128)===0&&(typeof $.getDerivedStateFromError=="function"||I!==null&&typeof I.componentDidCatch=="function"&&(uo===null||!uo.has(I))))return _.flags|=65536,j&=-j,_.lanes|=j,j=Zu(j),Ju(j,p,_,v),zi(_,j),!1}_=_.return}while(_!==null);return!1}var bl=Error(s(461)),Kt=!1;function pn(p,$,_,v){$.child=p===null?nu($,null,_,v):Lo($,p.child,_,v)}function ed(p,$,_,v,j){_=_.render;var I=$.ref;if("ref"in v){var C={};for(var Y in v)Y!=="ref"&&(C[Y]=v[Y])}else C=v;return Mo($),v=Ji(p,$,_,C,I,j),Y=el(),p!==null&&!Kt?(tl(p,$,j),Ws(p,$,j)):(it&&Y&&Ii($),$.flags|=1,pn(p,$,v,j),$.child)}function td(p,$,_,v,j){if(p===null){var I=_.type;return typeof I=="function"&&!Mi(I)&&I.defaultProps===void 0&&_.compare===null?($.tag=15,$.type=I,nd(p,$,I,v,j)):(p=ba(_.type,null,v,$,$.mode,j),p.ref=$.ref,p.return=$,$.child=p)}if(I=p.child,!Tl(p,j)){var C=I.memoizedProps;if(_=_.compare,_=_!==null?_:jr,_(C,v)&&p.ref===$.ref)return Ws(p,$,j)}return $.flags|=1,p=Ss(I,v),p.ref=$.ref,p.return=$,$.child=p}
vendor: 13,658 bytes, line 9
9function nd(p,$,_,v,j){if(p!==null){var I=p.memoizedProps;if(jr(I,v)&&p.ref===$.ref)if(Kt=!1,$.pendingProps=v=I,Tl(p,j))(p.flags&131072)!==0&&(Kt=!0);else return $.lanes=p.lanes,Ws(p,$,j)}return _l(p,$,_,v,j)}function sd(p,$,_,v){var j=v.children,I=p!==null?p.memoizedState:null;if(p===null&&$.stateNode===null&&($.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),v.mode==="hidden"){if(($.flags&128)!==0){if(I=I!==null?I.baseLanes|_:_,p!==null){for(v=$.child=p.child,j=0;v!==null;)j=j|v.lanes|v.childLanes,v=v.sibling;v=j&~I}else v=0,$.child=null;return od(p,$,I,_,v)}if((_&536870912)!==0)$.memoizedState={baseLanes:0,cachePool:null},p!==null&&ya($,I!==null?I.cachePool:null),I!==null?ru($,I):Ki(),au($);else return v=$.lanes=536870912,od(p,$,I!==null?I.baseLanes|_:_,_,v)}else I!==null?(ya($,I.cachePool),ru($,I),ao(),$.memoizedState=null):(p!==null&&ya($,null),Ki(),ao());return pn(p,$,j,_),$.child}function qr(p,$){return p!==null&&p.tag===22||$.stateNode!==null||($.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),$.sibling}function od(p,$,_,v,j){var I=Ui();return I=I===null?null:{parent:zt._currentValue,pool:I},$.memoizedState={baseLanes:_,cachePool:I},p!==null&&ya($,null),Ki(),au($),p!==null&&Jo(p,$,v,!0),$.childLanes=j,null}function Oa(p,$){return $=Fa({mode:$.mode,children:$.children},p.mode),$.ref=p.ref,p.child=$,$.return=p,$}function rd(p,$,_){return Lo($,p.child,null,_),p=Oa($,$.pendingProps),p.flags|=2,Un($),$.memoizedState=null,p}function jh(p,$,_){var v=$.pendingProps,j=($.flags&128)!==0;if($.flags&=-129,p===null){if(it){if(v.mode==="hidden")return p=Oa($,v),$.lanes=536870912,qr(null,p);if(Xi($),(p=Nt)?(p=gm(p,ns),p=p!==null&&p.data==="&"?p:null,p!==null&&($.memoizedState={dehydrated:p,treeContext:Xs!==null?{id:gs,overflow:bs}:null,retryLane:536870912,hydrationErrors:null},_=Hc(p),_.return=$,$.child=_,mn=$,Nt=null)):p=null,p===null)throw Js($);return $.lanes=536870912,null}return Oa($,v)}var I=p.memoizedState;if(I!==null){var C=I.dehydrated;if(Xi($),j)if($.flags&256)$.flags&=-257,$=rd(p,$,_);else if($.memoizedState!==null)$.child=p.child,$.flags|=128,$=null;else throw Error(s(558));else if(Kt||Jo(p,$,_,!1),j=(_&p.childLanes)!==0,Kt||j){if(v=St,v!==null&&(C=Tn(v,_),C!==0&&C!==I.retryLane))throw I.retryLane=C,So(p,C),Cn(v,p,C),bl;Ka(),$=rd(p,$,_)}else p=I.treeContext,Nt=os(C.nextSibling),mn=$,it=!0,Zs=null,ns=!1,p!==null&&Gc($,p),$=Oa($,v),$.flags|=4096;return $}return p=Ss(p.child,{mode:v.mode,children:v.children}),p.ref=$.ref,$.child=p,p.return=$,p}function Da(p,$){var _=$.ref;if(_===null)p!==null&&p.ref!==null&&($.flags|=4194816);else{if(typeof _!="function"&&typeof _!="object")throw Error(s(284));(p===null||p.ref!==_)&&($.flags|=4194816)}}function _l(p,$,_,v,j){return Mo($),_=Ji(p,$,_,v,void 0,j),v=el(),p!==null&&!Kt?(tl(p,$,j),Ws(p,$,j)):(it&&v&&Ii($),$.flags|=1,pn(p,$,_,j),$.child)}function ad(p,$,_,v,j,I){return Mo($),$.updateQueue=null,_=lu($,v,_,j),iu(p),v=el(),p!==null&&!Kt?(tl(p,$,I),Ws(p,$,I)):(it&&v&&Ii($),$.flags|=1,pn(p,$,_,I),$.child)}function id(p,$,_,v,j){if(Mo($),$.stateNode===null){var I=Ko,C=_.contextType;typeof C=="object"&&C!==null&&(I=hn(C)),I=new _(v,I),$.memoizedState=I.state!==null&&I.state!==void 0?I.state:null,I.updater=$l,$.stateNode=I,I._reactInternals=$,I=$.stateNode,I.props=v,I.state=$.memoizedState,I.refs={},qi($),C=_.contextType,I.context=typeof C=="object"&&C!==null?hn(C):Ko,I.state=$.memoizedState,C=_.getDerivedStateFromProps,typeof C=="function"&&(fl($,_,C,v),I.state=$.memoizedState),typeof _.getDerivedStateFromProps=="function"||typeof I.getSnapshotBeforeUpdate=="function"||typeof I.UNSAFE_componentWillMount!="function"&&typeof I.componentWillMount!="function"||(C=I.state,typeof I.componentWillMount=="function"&&I.componentWillMount(),typeof I.UNSAFE_componentWillMount=="function"&&I.UNSAFE_componentWillMount(),C!==I.state&&$l.enqueueReplaceState(I,I.state,null),Fr($,v,I,j),Dr(),I.state=$.memoizedState),typeof I.componentDidMount=="function"&&($.flags|=4194308),v=!0}else if(p===null){I=$.stateNode;var Y=$.memoizedProps,J=Co(_,Y);I.props=J;var ue=I.context,he=_.contextType;C=Ko,typeof he=="object"&&he!==null&&(C=hn(he));var fe=_.getDerivedStateFromProps;he=typeof fe=="function"||typeof I.getSnapshotBeforeUpdate=="function",Y=$.pendingProps!==Y,he||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(Y||ue!==C)&&zu($,I,v,C),no=!1;var de=$.memoizedState;I.state=de,Fr($,v,I,j),Dr(),ue=$.memoizedState,Y||de!==ue||no?(typeof fe=="function"&&(fl($,_,fe,v),ue=$.memoizedState),(J=no||Vu($,_,J,v,de,ue,C))?(he||typeof I.UNSAFE_componentWillMount!="function"&&typeof I.componentWillMount!="function"||(typeof I.componentWillMount=="function"&&I.componentWillMount(),typeof I.UNSAFE_componentWillMount=="function"&&I.UNSAFE_componentWillMount()),typeof I.componentDidMount=="function"&&($.flags|=4194308)):(typeof I.componentDidMount=="function"&&($.flags|=4194308),$.memoizedProps=v,$.memoizedState=ue),I.props=v,I.state=ue,I.context=C,v=J):(typeof I.componentDidMount=="function"&&($.flags|=4194308),v=!1)}else{I=$.stateNode,Vi(p,$),C=$.memoizedProps,he=Co(_,C),I.props=he,fe=$.pendingProps,de=I.context,ue=_.contextType,J=Ko,typeof ue=="object"&&ue!==null&&(J=hn(ue)),Y=_.getDerivedStateFromProps,(ue=typeof Y=="function"||typeof I.getSnapshotBeforeUpdate=="function")||typeof I.UNSAFE_componentWillReceiveProps!="function"&&typeof I.componentWillReceiveProps!="function"||(C!==fe||de!==J)&&zu($,I,v,J),no=!1,de=$.memoizedState,I.state=de,Fr($,v,I,j),Dr();var me=$.memoizedState;C!==fe||de!==me||no||p!==null&&p.dependencies!==null&&wa(p.dependencies)?(typeof Y=="function"&&(fl($,_,Y,v),me=$.memoizedState),(he=no||Vu($,_,he,v,de,me,J)||p!==null&&p.dependencies!==null&&wa(p.dependencies))?(ue||typeof I.UNSAFE_componentWillUpdate!="function"&&typeof I.componentWillUpdate!="function"||(typeof I.componentWillUpdate=="function"&&I.componentWillUpdate(v,me,J),typeof I.UNSAFE_componentWillUpdate=="function"&&I.UNSAFE_componentWillUpdate(v,me,J)),typeof I.componentDidUpdate=="function"&&($.flags|=4),typeof I.getSnapshotBeforeUpdate=="function"&&($.flags|=1024)):(typeof I.componentDidUpdate!="function"||C===p.memoizedProps&&de===p.memoizedState||($.flags|=4),typeof I.getSnapshotBeforeUpdate!="function"||C===p.memoizedProps&&de===p.memoizedState||($.flags|=1024),$.memoizedProps=v,$.memoizedState=me),I.props=v,I.state=me,I.context=J,v=he):(typeof I.componentDidUpdate!="function"||C===p.memoizedProps&&de===p.memoizedState||($.flags|=4),typeof I.getSnapshotBeforeUpdate!="function"||C===p.memoizedProps&&de===p.memoizedState||($.flags|=1024),v=!1)}return I=v,Da(p,$),v=($.flags&128)!==0,I||v?(I=$.stateNode,_=v&&typeof _.getDerivedStateFromError!="function"?null:I.render(),$.flags|=1,p!==null&&v?($.child=Lo($,p.child,null,j),$.child=Lo($,null,_,j)):pn(p,$,_,j),$.memoizedState=I.state,p=$.child):p=Ws(p,$,j),p}function ld(p,$,_,v){return Ao(),$.flags|=256,pn(p,$,_,v),$.child}var wl={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function xl(p){return{baseLanes:p,cachePool:Qc()}}function yl(p,$,_){return p=p!==null?p.childLanes&~_:0,$&&(p|=qn),p}function cd(p,$,_){var v=$.pendingProps,j=!1,I=($.flags&128)!==0,C;if((C=I)||(C=p!==null&&p.memoizedState===null?!1:(Ht.current&2)!==0),C&&(j=!0,$.flags&=-129),C=($.flags&32)!==0,$.flags&=-33,p===null){if(it){if(j?ro($):ao(),(p=Nt)?(p=gm(p,ns),p=p!==null&&p.data!=="&"?p:null,p!==null&&($.memoizedState={dehydrated:p,treeContext:Xs!==null?{id:gs,overflow:bs}:null,retryLane:536870912,hydrationErrors:null},_=Hc(p),_.return=$,$.child=_,mn=$,Nt=null)):p=null,p===null)throw Js($);return oc(p)?$.lanes=32:$.lanes=536870912,null}var Y=v.children;return v=v.fallback,j?(ao(),j=$.mode,Y=Fa({mode:"hidden",children:Y},j),v=Ro(v,j,_,null),Y.return=$,v.return=$,Y.sibling=v,$.child=Y,v=$.child,v.memoizedState=xl(_),v.childLanes=yl(p,C,_),$.memoizedState=wl,qr(null,v)):(ro($),vl($,Y))}var J=p.memoizedState;if(J!==null&&(Y=J.dehydrated,Y!==null)){if(I)$.flags&256?(ro($),$.flags&=-257,$=El(p,$,_)):$.memoizedState!==null?(ao(),$.child=p.child,$.flags|=128,$=null):(ao(),Y=v.fallback,j=$.mode,v=Fa({mode:"visible",children:v.children},j),Y=Ro(Y,j,_,null),Y.flags|=2,v.return=$,Y.return=$,v.sibling=Y,$.child=v,Lo($,p.child,null,_),v=$.child,v.memoizedState=xl(_),v.childLanes=yl(p,C,_),$.memoizedState=wl,$=qr(null,v));else if(ro($),oc(Y)){if(C=Y.nextSibling&&Y.nextSibling.dataset,C)var ue=C.dgst;C=ue,v=Error(s(419)),v.stack="",v.digest=C,Wr({value:v,source:null,stack:null}),$=El(p,$,_)}else if(Kt||Jo(p,$,_,!1),C=(_&p.childLanes)!==0,Kt||C){if(C=St,C!==null&&(v=Tn(C,_),v!==0&&v!==J.retryLane))throw J.retryLane=v,So(p,v),Cn(C,p,v),bl;sc(Y)||Ka(),$=El(p,$,_)}else sc(Y)?($.flags|=192,$.child=p.child,$=null):(p=J.treeContext,Nt=os(Y.nextSibling),mn=$,it=!0,Zs=null,ns=!1,p!==null&&Gc($,p),$=vl($,v.children),$.flags|=4096);return $}return j?(ao(),Y=v.fallback,j=$.mode,J=p.child,ue=J.sibling,v=Ss(J,{mode:"hidden",children:v.children}),v.subtreeFlags=J.subtreeFlags&65011712,ue!==null?Y=Ss(ue,Y):(Y=Ro(Y,j,_,null),Y.flags|=2),Y.return=$,v.return=$,v.sibling=Y,$.child=v,qr(null,v),v=$.child,Y=p.child.memoizedState,Y===null?Y=xl(_):(j=Y.cachePool,j!==null?(J=zt._currentValue,j=j.parent!==J?{parent:J,pool:J}:j):j=Qc(),Y={baseLanes:Y.baseLanes|_,cachePool:j}),v.memoizedState=Y,v.childLanes=yl(p,C,_),$.memoizedState=wl,qr(p.child,v)):(ro($),_=p.child,p=_.sibling,_=Ss(_,{mode:"visible",children:v.children}),_.return=$,_.sibling=null,p!==null&&(C=$.deletions,C===null?($.deletions=[p],$.flags|=16):C.push(p)),$.child=_,$.memoizedState=null,_)}function vl(p,$){return $=Fa({mode:"visible",children:$},p.mode),$.return=p,p.child=$}function Fa(p,$){return p=Hn(22,p,null,$),p.lanes=0,p}function El(p,$,_){return Lo($,p.child,null,_),p=vl($,$.pendingProps.children),p.flags|=2,$.memoizedState=null,p}function ud(p,$,_){p.lanes|=$;var v=p.alternate;v!==null&&(v.lanes|=$),Di(p.return,$,_)}function kl(p,$,_,v,j,I){var C=p.memoizedState;C===null?p.memoizedState={isBackwards:$,rendering:null,renderingStartTime:0,last:v,tail:_,tailMode:j,treeForkCount:I}:(C.isBackwards=$,C.rendering=null,C.renderingStartTime=0,C.last=v,C.tail=_,C.tailMode=j,C.treeForkCount=I)}function dd(p,$,_){var v=$.pendingProps,j=v.revealOrder,I=v.tail;v=v.children;var C=Ht.current,Y=(C&2)!==0;if(Y?(C=C&1|2,$.flags|=128):C&=1,X(Ht,C),pn(p,$,v,_),v=it?Nr:0,!Y&&p!==null&&(p.flags&128)!==0)e:for(p=$.child;p!==null;){if(p.tag===13)p.memoizedState!==null&&ud(p,_,$);else if(p.tag===19)ud(p,_,$);else if(p.child!==null){p.child.return=p,p=p.child;continue}if(p===$)break e;for(;p.sibling===null;){if(p.return===null||p.return===$)break e;p=p.return}p.sibling.return=p.return,p=p.sibling}switch(j){case"forwards":for(_=$.child,j=null;_!==null;)p=_.alternate,p!==null&&Ra(p)===null&&(j=_),_=_.sibling;_=j,_===null?(j=$.child,$.child=null):(j=_.sibling,_.sibling=null),kl($,!1,j,_,I,v);break;case"backwards":case"unstable_legacy-backwards":for(_=null,j=$.child,$.child=null;j!==null;){if(p=j.alternate,p!==null&&Ra(p)===null){$.child=j;break}p=j.sibling,j.sibling=_,_=j,j=p}kl($,!0,_,null,I,v);break;case"together":kl($,!1,null,null,void 0,v);break;default:$.memoizedState=null}return $.child}function Ws(p,$,_){if(p!==null&&($.dependencies=p.dependencies),co|=$.lanes,(_&$.childLanes)===0)if(p!==null){if(Jo(p,$,_,!1),(_&$.childLanes)===0)return null}else return null;if(p!==null&&$.child!==p.child)throw Error(s(153));if($.child!==null){for(p=$.child,_=Ss(p,p.pendingProps),$.child=_,_.return=$;p.sibling!==null;)p=p.sibling,_=_.sibling=Ss(p,p.pendingProps),_.return=$;_.sibling=null}return $.child}function Tl(p,$){return(p.lanes&$)!==0?!0:(p=p.dependencies,!!(p!==null&&wa(p)))}function Mh(p,$,_){switch($.tag){case 3:le($,$.stateNode.containerInfo),eo($,zt,p.memoizedState.cache),Ao();break;case 27:case 5:ie($);break;case 4:le($,$.stateNode.containerInfo);break;case 10:eo($,$.type,$.memoizedProps.value);break;case 31:if($.memoizedState!==null)return $.flags|=128,Xi($),null;break;case 13:var v=$.memoizedState;if(v!==null)return v.dehydrated!==null?(ro($),$.flags|=128,null):(_&$.child.childLanes)!==0?cd(p,$,_):(ro($),p=Ws(p,$,_),p!==null?p.sibling:null);ro($);break;case 19:var j=(p.flags&128)!==0;if(v=(_&$.childLanes)!==0,v||(Jo(p,$,_,!1),v=(_&$.childLanes)!==0),j){if(v)return dd(p,$,_);$.flags|=128}if(j=$.memoizedState,j!==null&&(j.rendering=null,j.tail=null,j.lastEffect=null),X(Ht,Ht.current),v)break;return null;case 22:return $.lanes=0,sd(p,$,_,$.pendingProps);case 24:eo($,zt,p.memoizedState.cache)}return Ws(p,$,_)}function md(p,$,_){if(p!==null)if(p.memoizedProps!==$.pendingProps)Kt=!0;else{if(!Tl(p,_)&&($.flags&128)===0)return Kt=!1,Mh(p,$,_);Kt=(p.flags&131072)!==0}else Kt=!1,it&&($.flags&1048576)!==0&&Uc($,Nr,$.index);switch($.lanes=0,$.tag){case 16:e:{var v=$.pendingProps;if(p=Wo($.elementType),$.type=p,typeof p=="function")Mi(p)?(v=Co(p,v),$.tag=1,$=id(null,$,p,v,_)):($.tag=0,$=_l(null,$,p,v,_));else{if(p!=null){var j=p.$$typeof;if(j===T){$.tag=11,$=ed(null,$,p,v,_);break e}else if(j===S){$.tag=14,$=td(null,$,p,v,_);break e}}throw $=q(p)||p,Error(s(306,$,""))}}return $;case 0:return _l(p,$,$.type,$.pendingProps,_);case 1:return v=$.type,j=Co(v,$.pendingProps),id(p,$,v,j,_);case 3:e:{if(le($,$.stateNode.containerInfo),p===null)throw Error(s(387));v=$.pendingProps;var I=$.memoizedState;j=I.element,Vi(p,$),Fr($,v,null,_);var C=$.memoizedState;if(v=C.cache,eo($,zt,v),v!==I.cache&&Fi($,[zt],_,!0),Dr(),v=C.element,I.isDehydrated)if(I={element:v,isDehydrated:!1,cache:C.cache},$.updateQueue.baseState=I,$.memoizedState=I,$.flags&256){$=ld(p,$,v,_);break e}
vendor: 5,421 bytes, line 9
9else if(v!==j){j=Jn(Error(s(424)),$),Wr(j),$=ld(p,$,v,_);break e}else for(p=$.stateNode.containerInfo,p.nodeType===9?p=p.body:p=p.nodeName==="HTML"?p.ownerDocument.body:p,Nt=os(p.firstChild),mn=$,it=!0,Zs=null,ns=!0,_=nu($,null,v,_),$.child=_;_;)_.flags=_.flags&-3|4096,_=_.sibling;else{if(Ao(),v===j){$=Ws(p,$,_);break e}pn(p,$,v,_)}$=$.child}return $;case 26:return Da(p,$),p===null?(_=vm($.type,null,$.pendingProps,null))?$.memoizedState=_:it||(_=$.type,p=$.pendingProps,v=ni(te.current).createElement(_),v[rt]=$,v[At]=p,fn(v,_,p),We(v),$.stateNode=v):$.memoizedState=vm($.type,p.memoizedProps,$.pendingProps,p.memoizedState),null;case 27:return ie($),p===null&&it&&(v=$.stateNode=wm($.type,$.pendingProps,te.current),mn=$,ns=!0,j=Nt,fo($.type)?(rc=j,Nt=os(v.firstChild)):Nt=j),pn(p,$,$.pendingProps.children,_),Da(p,$),p===null&&($.flags|=4194304),$.child;case 5:return p===null&&it&&((j=v=Nt)&&(v=lp(v,$.type,$.pendingProps,ns),v!==null?($.stateNode=v,mn=$,Nt=os(v.firstChild),ns=!1,j=!0):j=!1),j||Js($)),ie($),j=$.type,I=$.pendingProps,C=p!==null?p.memoizedProps:null,v=I.children,ec(j,I)?v=null:C!==null&&ec(j,C)&&($.flags|=32),$.memoizedState!==null&&(j=Ji(p,$,yh,null,null,_),aa._currentValue=j),Da(p,$),pn(p,$,v,_),$.child;case 6:return p===null&&it&&((p=_=Nt)&&(_=cp(_,$.pendingProps,ns),_!==null?($.stateNode=_,mn=$,Nt=null,p=!0):p=!1),p||Js($)),null;case 13:return cd(p,$,_);case 4:return le($,$.stateNode.containerInfo),v=$.pendingProps,p===null?$.child=Lo($,null,v,_):pn(p,$,v,_),$.child;case 11:return ed(p,$,$.type,$.pendingProps,_);case 7:return pn(p,$,$.pendingProps,_),$.child;case 8:return pn(p,$,$.pendingProps.children,_),$.child;case 12:return pn(p,$,$.pendingProps.children,_),$.child;case 10:return v=$.pendingProps,eo($,$.type,v.value),pn(p,$,v.children,_),$.child;case 9:return j=$.type._context,v=$.pendingProps.children,Mo($),j=hn(j),v=v(j),$.flags|=1,pn(p,$,v,_),$.child;case 14:return td(p,$,$.type,$.pendingProps,_);case 15:return nd(p,$,$.type,$.pendingProps,_);case 19:return dd(p,$,_);case 31:return jh(p,$,_);case 22:return sd(p,$,_,$.pendingProps);case 24:return Mo($),v=hn(zt),p===null?(j=Ui(),j===null&&(j=St,I=Hi(),j.pooledCache=I,I.refCount++,I!==null&&(j.pooledCacheLanes|=_),j=I),$.memoizedState={parent:v,cache:j},qi($),eo($,zt,j)):((p.lanes&_)!==0&&(Vi(p,$),Fr($,null,null,_),Dr()),j=p.memoizedState,I=$.memoizedState,j.parent!==v?(j={parent:v,cache:v},$.memoizedState=j,$.lanes===0&&($.memoizedState=$.updateQueue.baseState=j),eo($,zt,v)):(v=I.cache,eo($,zt,v),v!==j.cache&&Fi($,[zt],_,!0))),pn(p,$,$.pendingProps.children,_),$.child;case 29:throw $.pendingProps}throw Error(s(156,$.tag))}function Is(p){p.flags|=4}function Sl(p,$,_,v,j){if(($=(p.mode&32)!==0)&&($=!1),$){if(p.flags|=16777216,(j&335544128)===j)if(p.stateNode.complete)p.flags|=8192;else if(Dd())p.flags|=8192;else throw Io=Ea,Gi}else p.flags&=-16777217}function hd(p,$){if($.type!=="stylesheet"||($.state.loading&4)!==0)p.flags&=-16777217;else if(p.flags|=16777216,!Rm($))if(Dd())p.flags|=8192;else throw Io=Ea,Gi}function Ha(p,$){$!==null&&(p.flags|=4),p.flags&16384&&($=p.tag!==22?En():536870912,p.lanes|=$,dr|=$)}function Vr(p,$){if(!it)switch(p.tailMode){case"hidden":$=p.tail;for(var _=null;$!==null;)$.alternate!==null&&(_=$),$=$.sibling;_===null?p.tail=null:_.sibling=null;break;case"collapsed":_=p.tail;for(var v=null;_!==null;)_.alternate!==null&&(v=_),_=_.sibling;v===null?$||p.tail===null?p.tail=null:p.tail.sibling=null:v.sibling=null}}function Wt(p){var $=p.alternate!==null&&p.alternate.child===p.child,_=0,v=0;if($)for(var j=p.child;j!==null;)_|=j.lanes|j.childLanes,v|=j.subtreeFlags&65011712,v|=j.flags&65011712,j.return=p,j=j.sibling;else for(j=p.child;j!==null;)_|=j.lanes|j.childLanes,v|=j.subtreeFlags,v|=j.flags,j.return=p,j=j.sibling;return p.subtreeFlags|=v,p.childLanes=_,$}function Nh(p,$,_){var v=$.pendingProps;switch(Li($),$.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Wt($),null;case 1:return Wt($),null;case 3:return _=$.stateNode,v=null,p!==null&&(v=p.memoizedState.cache),$.memoizedState.cache!==v&&($.flags|=2048),js(zt),ae(),_.pendingContext&&(_.context=_.pendingContext,_.pendingContext=null),(p===null||p.child===null)&&(Zo($)?Is($):p===null||p.memoizedState.isDehydrated&&($.flags&256)===0||($.flags|=1024,Ci())),Wt($),null;case 26:var j=$.type,I=$.memoizedState;return p===null?(Is($),I!==null?(Wt($),hd($,I)):(Wt($),Sl($,j,null,v,_))):I?I!==p.memoizedState?(Is($),Wt($),hd($,I)):(Wt($),$.flags&=-16777217):(p=p.memoizedProps,p!==v&&Is($),Wt($),Sl($,j,p,v,_)),null;case 27:if(we($),_=te.current,j=$.type,p!==null&&$.stateNode!=null)p.memoizedProps!==v&&Is($);else{if(!v){if($.stateNode===null)throw Error(s(166));return Wt($),null}p=ne.current,Zo($)?qc($):(p=wm(j,v,_),$.stateNode=p,Is($))}return Wt($),null;case 5:if(we($),j=$.type,p!==null&&$.stateNode!=null)p.memoizedProps!==v&&Is($);else{if(!v){if($.stateNode===null)throw Error(s(166));return Wt($),null}if(I=ne.current,Zo($))qc($);else{var C=ni(te.current);switch(I){case 1:I=C.createElementNS("http://www.w3.org/2000/svg",j);break;case 2:I=C.createElementNS("http://www.w3.org/1998/Math/MathML",j);break;default:switch(j){case"svg":I=C.createElementNS("http://www.w3.org/2000/svg",j);break;case"math":I=C.createElementNS("http://www.w3.org/1998/Math/MathML",j);break;case"script":I=C.createElement("div"),I.innerHTML="<script><\/script>",I=I.removeChild(I.firstChild);break;
vendor: 26,853 bytes, line 9
9case"select":I=typeof v.is=="string"?C.createElement("select",{is:v.is}):C.createElement("select"),v.multiple?I.multiple=!0:v.size&&(I.size=v.size);break;default:I=typeof v.is=="string"?C.createElement(j,{is:v.is}):C.createElement(j)}}I[rt]=$,I[At]=v;e:for(C=$.child;C!==null;){if(C.tag===5||C.tag===6)I.appendChild(C.stateNode);else if(C.tag!==4&&C.tag!==27&&C.child!==null){C.child.return=C,C=C.child;continue}if(C===$)break e;for(;C.sibling===null;){if(C.return===null||C.return===$)break e;C=C.return}C.sibling.return=C.return,C=C.sibling}$.stateNode=I;e:switch(fn(I,j,v),j){case"button":case"input":case"select":case"textarea":v=!!v.autoFocus;break e;case"img":v=!0;break e;default:v=!1}v&&Is($)}}return Wt($),Sl($,$.type,p===null?null:p.memoizedProps,$.pendingProps,_),null;case 6:if(p&&$.stateNode!=null)p.memoizedProps!==v&&Is($);else{if(typeof v!="string"&&$.stateNode===null)throw Error(s(166));if(p=te.current,Zo($)){if(p=$.stateNode,_=$.memoizedProps,v=null,j=mn,j!==null)switch(j.tag){case 27:case 5:v=j.memoizedProps}p[rt]=$,p=!!(p.nodeValue===_||v!==null&&v.suppressHydrationWarning===!0||cm(p.nodeValue,_)),p||Js($,!0)}else p=ni(p).createTextNode(v),p[rt]=$,$.stateNode=p}return Wt($),null;case 31:if(_=$.memoizedState,p===null||p.memoizedState!==null){if(v=Zo($),_!==null){if(p===null){if(!v)throw Error(s(318));if(p=$.memoizedState,p=p!==null?p.dehydrated:null,!p)throw Error(s(557));p[rt]=$}else Ao(),($.flags&128)===0&&($.memoizedState=null),$.flags|=4;Wt($),p=!1}else _=Ci(),p!==null&&p.memoizedState!==null&&(p.memoizedState.hydrationErrors=_),p=!0;if(!p)return $.flags&256?(Un($),$):(Un($),null);if(($.flags&128)!==0)throw Error(s(558))}return Wt($),null;case 13:if(v=$.memoizedState,p===null||p.memoizedState!==null&&p.memoizedState.dehydrated!==null){if(j=Zo($),v!==null&&v.dehydrated!==null){if(p===null){if(!j)throw Error(s(318));if(j=$.memoizedState,j=j!==null?j.dehydrated:null,!j)throw Error(s(317));j[rt]=$}else Ao(),($.flags&128)===0&&($.memoizedState=null),$.flags|=4;Wt($),j=!1}else j=Ci(),p!==null&&p.memoizedState!==null&&(p.memoizedState.hydrationErrors=j),j=!0;if(!j)return $.flags&256?(Un($),$):(Un($),null)}return Un($),($.flags&128)!==0?($.lanes=_,$):(_=v!==null,p=p!==null&&p.memoizedState!==null,_&&(v=$.child,j=null,v.alternate!==null&&v.alternate.memoizedState!==null&&v.alternate.memoizedState.cachePool!==null&&(j=v.alternate.memoizedState.cachePool.pool),I=null,v.memoizedState!==null&&v.memoizedState.cachePool!==null&&(I=v.memoizedState.cachePool.pool),I!==j&&(v.flags|=2048)),_!==p&&_&&($.child.flags|=8192),Ha($,$.updateQueue),Wt($),null);case 4:return ae(),p===null&&Kl($.stateNode.containerInfo),Wt($),null;case 10:return js($.type),Wt($),null;case 19:if(G(Ht),v=$.memoizedState,v===null)return Wt($),null;if(j=($.flags&128)!==0,I=v.rendering,I===null)if(j)Vr(v,!1);else{if(Dt!==0||p!==null&&(p.flags&128)!==0)for(p=$.child;p!==null;){if(I=Ra(p),I!==null){for($.flags|=128,Vr(v,!1),p=I.updateQueue,$.updateQueue=p,Ha($,p),$.subtreeFlags=0,p=_,_=$.child;_!==null;)Fc(_,p),_=_.sibling;return X(Ht,Ht.current&1|2),it&&Rs($,v.treeForkCount),$.child}p=p.sibling}v.tail!==null&&ge()>Va&&($.flags|=128,j=!0,Vr(v,!1),$.lanes=4194304)}else{if(!j)if(p=Ra(I),p!==null){if($.flags|=128,j=!0,p=p.updateQueue,$.updateQueue=p,Ha($,p),Vr(v,!0),v.tail===null&&v.tailMode==="hidden"&&!I.alternate&&!it)return Wt($),null}else 2*ge()-v.renderingStartTime>Va&&_!==536870912&&($.flags|=128,j=!0,Vr(v,!1),$.lanes=4194304);v.isBackwards?(I.sibling=$.child,$.child=I):(p=v.last,p!==null?p.sibling=I:$.child=I,v.last=I)}return v.tail!==null?(p=v.tail,v.rendering=p,v.tail=p.sibling,v.renderingStartTime=ge(),p.sibling=null,_=Ht.current,X(Ht,j?_&1|2:_&1),it&&Rs($,v.treeForkCount),p):(Wt($),null);case 22:case 23:return Un($),Qi(),v=$.memoizedState!==null,p!==null?p.memoizedState!==null!==v&&($.flags|=8192):v&&($.flags|=8192),v?(_&536870912)!==0&&($.flags&128)===0&&(Wt($),$.subtreeFlags&6&&($.flags|=8192)):Wt($),_=$.updateQueue,_!==null&&Ha($,_.retryQueue),_=null,p!==null&&p.memoizedState!==null&&p.memoizedState.cachePool!==null&&(_=p.memoizedState.cachePool.pool),v=null,$.memoizedState!==null&&$.memoizedState.cachePool!==null&&(v=$.memoizedState.cachePool.pool),v!==_&&($.flags|=2048),p!==null&&G(No),null;case 24:return _=null,p!==null&&(_=p.memoizedState.cache),$.memoizedState.cache!==_&&($.flags|=2048),js(zt),Wt($),null;case 25:return null;case 30:return null}throw Error(s(156,$.tag))}function Wh(p,$){switch(Li($),$.tag){case 1:return p=$.flags,p&65536?($.flags=p&-65537|128,$):null;case 3:return js(zt),ae(),p=$.flags,(p&65536)!==0&&(p&128)===0?($.flags=p&-65537|128,$):null;case 26:case 27:case 5:return we($),null;case 31:if($.memoizedState!==null){if(Un($),$.alternate===null)throw Error(s(340));Ao()}return p=$.flags,p&65536?($.flags=p&-65537|128,$):null;case 13:if(Un($),p=$.memoizedState,p!==null&&p.dehydrated!==null){if($.alternate===null)throw Error(s(340));Ao()}return p=$.flags,p&65536?($.flags=p&-65537|128,$):null;case 19:return G(Ht),null;case 4:return ae(),null;case 10:return js($.type),null;case 22:case 23:return Un($),Qi(),p!==null&&G(No),p=$.flags,p&65536?($.flags=p&-65537|128,$):null;case 24:return js(zt),null;case 25:return null;default:return null}}function pd(p,$){switch(Li($),$.tag){case 3:js(zt),ae();break;case 26:case 27:case 5:we($);break;case 4:ae();break;case 31:$.memoizedState!==null&&Un($);break;case 13:Un($);break;case 19:G(Ht);break;case 10:js($.type);break;case 22:case 23:Un($),Qi(),p!==null&&G(No);break;case 24:js(zt)}}function zr(p,$){try{var _=$.updateQueue,v=_!==null?_.lastEffect:null;if(v!==null){var j=v.next;_=j;do{if((_.tag&p)===p){v=void 0;var I=_.create,C=_.inst;v=I(),C.destroy=v}_=_.next}while(_!==j)}}catch(Y){_t($,$.return,Y)}}function io(p,$,_){try{var v=$.updateQueue,j=v!==null?v.lastEffect:null;if(j!==null){var I=j.next;v=I;do{if((v.tag&p)===p){var C=v.inst,Y=C.destroy;if(Y!==void 0){C.destroy=void 0,j=$;var J=_,ue=Y;try{ue()}catch(he){_t(j,J,he)}}}v=v.next}while(v!==I)}}catch(he){_t($,$.return,he)}}function fd(p){var $=p.updateQueue;if($!==null){var _=p.stateNode;try{ou($,_)}catch(v){_t(p,p.return,v)}}}function $d(p,$,_){_.props=Co(p.type,p.memoizedProps),_.state=p.memoizedState;try{_.componentWillUnmount()}catch(v){_t(p,$,v)}}function Yr(p,$){try{var _=p.ref;if(_!==null){switch(p.tag){case 26:case 27:case 5:var v=p.stateNode;break;case 30:v=p.stateNode;break;default:v=p.stateNode}typeof _=="function"?p.refCleanup=_(v):_.current=v}}catch(j){_t(p,$,j)}}function _s(p,$){var _=p.ref,v=p.refCleanup;if(_!==null)if(typeof v=="function")try{v()}catch(j){_t(p,$,j)}finally{p.refCleanup=null,p=p.alternate,p!=null&&(p.refCleanup=null)}else if(typeof _=="function")try{_(null)}catch(j){_t(p,$,j)}else _.current=null}function gd(p){var $=p.type,_=p.memoizedProps,v=p.stateNode;try{e:switch($){case"button":case"input":case"select":case"textarea":_.autoFocus&&v.focus();break e;case"img":_.src?v.src=_.src:_.srcSet&&(v.srcset=_.srcSet)}}catch(j){_t(p,p.return,j)}}function Rl(p,$,_){try{var v=p.stateNode;tp(v,p.type,_,$),v[At]=$}catch(j){_t(p,p.return,j)}}function bd(p){return p.tag===5||p.tag===3||p.tag===26||p.tag===27&&fo(p.type)||p.tag===4}function Al(p){e:for(;;){for(;p.sibling===null;){if(p.return===null||bd(p.return))return null;p=p.return}for(p.sibling.return=p.return,p=p.sibling;p.tag!==5&&p.tag!==6&&p.tag!==18;){if(p.tag===27&&fo(p.type)||p.flags&2||p.child===null||p.tag===4)continue e;p.child.return=p,p=p.child}if(!(p.flags&2))return p.stateNode}}function jl(p,$,_){var v=p.tag;if(v===5||v===6)p=p.stateNode,$?(_.nodeType===9?_.body:_.nodeName==="HTML"?_.ownerDocument.body:_).insertBefore(p,$):($=_.nodeType===9?_.body:_.nodeName==="HTML"?_.ownerDocument.body:_,$.appendChild(p),_=_._reactRootContainer,_!=null||$.onclick!==null||($.onclick=Be));else if(v!==4&&(v===27&&fo(p.type)&&(_=p.stateNode,$=null),p=p.child,p!==null))for(jl(p,$,_),p=p.sibling;p!==null;)jl(p,$,_),p=p.sibling}function Ba(p,$,_){var v=p.tag;if(v===5||v===6)p=p.stateNode,$?_.insertBefore(p,$):_.appendChild(p);else if(v!==4&&(v===27&&fo(p.type)&&(_=p.stateNode),p=p.child,p!==null))for(Ba(p,$,_),p=p.sibling;p!==null;)Ba(p,$,_),p=p.sibling}function _d(p){var $=p.stateNode,_=p.memoizedProps;try{for(var v=p.type,j=$.attributes;j.length;)$.removeAttributeNode(j[0]);fn($,v,_),$[rt]=p,$[At]=_}catch(I){_t(p,p.return,I)}}var Ls=!1,Qt=!1,Ml=!1,wd=typeof WeakSet=="function"?WeakSet:Set,an=null;function Ih(p,$){if(p=p.containerInfo,Zl=ci,p=Mc(p),Ei(p)){if("selectionStart"in p)var _={start:p.selectionStart,end:p.selectionEnd};else e:{_=(_=p.ownerDocument)&&_.defaultView||window;var v=_.getSelection&&_.getSelection();if(v&&v.rangeCount!==0){_=v.anchorNode;var j=v.anchorOffset,I=v.focusNode;v=v.focusOffset;try{_.nodeType,I.nodeType}catch{_=null;break e}var C=0,Y=-1,J=-1,ue=0,he=0,fe=p,de=null;t:for(;;){for(var me;fe!==_||j!==0&&fe.nodeType!==3||(Y=C+j),fe!==I||v!==0&&fe.nodeType!==3||(J=C+v),fe.nodeType===3&&(C+=fe.nodeValue.length),(me=fe.firstChild)!==null;)de=fe,fe=me;for(;;){if(fe===p)break t;if(de===_&&++ue===j&&(Y=C),de===I&&++he===v&&(J=C),(me=fe.nextSibling)!==null)break;fe=de,de=fe.parentNode}fe=me}_=Y===-1||J===-1?null:{start:Y,end:J}}else _=null}_=_||{start:0,end:0}}else _=null;for(Jl={focusedElem:p,selectionRange:_},ci=!1,an=$;an!==null;)if($=an,p=$.child,($.subtreeFlags&1028)!==0&&p!==null)p.return=$,an=p;else for(;an!==null;){switch($=an,I=$.alternate,p=$.flags,$.tag){case 0:if((p&4)!==0&&(p=$.updateQueue,p=p!==null?p.events:null,p!==null))for(_=0;_<p.length;_++)j=p[_],j.ref.impl=j.nextImpl;break;case 11:case 15:break;case 1:if((p&1024)!==0&&I!==null){p=void 0,_=$,j=I.memoizedProps,I=I.memoizedState,v=_.stateNode;try{var Ae=Co(_.type,j);p=v.getSnapshotBeforeUpdate(Ae,I),v.__reactInternalSnapshotBeforeUpdate=p}catch(Oe){_t(_,_.return,Oe)}}break;case 3:if((p&1024)!==0){if(p=$.stateNode.containerInfo,_=p.nodeType,_===9)nc(p);else if(_===1)switch(p.nodeName){case"HEAD":case"HTML":case"BODY":nc(p);break;default:p.textContent=""}}break;case 5:case 26:case 27:case 6:case 4:case 17:break;default:if((p&1024)!==0)throw Error(s(163))}if(p=$.sibling,p!==null){p.return=$.return,an=p;break}an=$.return}}function xd(p,$,_){var v=_.flags;switch(_.tag){case 0:case 11:case 15:Cs(p,_),v&4&&zr(5,_);break;case 1:if(Cs(p,_),v&4)if(p=_.stateNode,$===null)try{p.componentDidMount()}catch(C){_t(_,_.return,C)}else{var j=Co(_.type,$.memoizedProps);$=$.memoizedState;try{p.componentDidUpdate(j,$,p.__reactInternalSnapshotBeforeUpdate)}catch(C){_t(_,_.return,C)}}v&64&&fd(_),v&512&&Yr(_,_.return);break;case 3:if(Cs(p,_),v&64&&(p=_.updateQueue,p!==null)){if($=null,_.child!==null)switch(_.child.tag){case 27:case 5:$=_.child.stateNode;break;case 1:$=_.child.stateNode}try{ou(p,$)}catch(C){_t(_,_.return,C)}}break;case 27:$===null&&v&4&&_d(_);case 26:case 5:Cs(p,_),$===null&&v&4&&gd(_),v&512&&Yr(_,_.return);break;case 12:Cs(p,_);break;case 31:Cs(p,_),v&4&&Ed(p,_);break;case 13:Cs(p,_),v&4&&kd(p,_),v&64&&(p=_.memoizedState,p!==null&&(p=p.dehydrated,p!==null&&(_=Uh.bind(null,_),up(p,_))));break;case 22:if(v=_.memoizedState!==null||Ls,!v){$=$!==null&&$.memoizedState!==null||Qt,j=Ls;var I=Qt;Ls=v,(Qt=$)&&!I?Os(p,_,(_.subtreeFlags&8772)!==0):Cs(p,_),Ls=j,Qt=I}break;case 30:break;default:Cs(p,_)}}function yd(p){var $=p.alternate;$!==null&&(p.alternate=null,yd($)),p.child=null,p.deletions=null,p.sibling=null,p.tag===5&&($=p.stateNode,$!==null&&Le($)),p.stateNode=null,p.return=null,p.dependencies=null,p.memoizedProps=null,p.memoizedState=null,p.pendingProps=null,p.stateNode=null,p.updateQueue=null}var Lt=null,Wn=!1;function Ps(p,$,_){for(_=_.child;_!==null;)vd(p,$,_),_=_.sibling}function vd(p,$,_){if(ct&&typeof ct.onCommitFiberUnmount=="function")try{ct.onCommitFiberUnmount(lt,_)}catch{}switch(_.tag){case 26:Qt||_s(_,$),Ps(p,$,_),_.memoizedState?_.memoizedState.count--:_.stateNode&&(_=_.stateNode,_.parentNode.removeChild(_));break;case 27:Qt||_s(_,$);var v=Lt,j=Wn;fo(_.type)&&(Lt=_.stateNode,Wn=!1),Ps(p,$,_),sa(_.stateNode),Lt=v,Wn=j;break;case 5:Qt||_s(_,$);case 6:if(v=Lt,j=Wn,Lt=null,Ps(p,$,_),Lt=v,Wn=j,Lt!==null)if(Wn)try{(Lt.nodeType===9?Lt.body:Lt.nodeName==="HTML"?Lt.ownerDocument.body:Lt).removeChild(_.stateNode)}catch(I){_t(_,$,I)}else try{Lt.removeChild(_.stateNode)}catch(I){_t(_,$,I)}break;case 18:Lt!==null&&(Wn?(p=Lt,fm(p.nodeType===9?p.body:p.nodeName==="HTML"?p.ownerDocument.body:p,_.stateNode),_r(p)):fm(Lt,_.stateNode));break;case 4:v=Lt,j=Wn,Lt=_.stateNode.containerInfo,Wn=!0,Ps(p,$,_),Lt=v,Wn=j;break;case 0:case 11:case 14:case 15:io(2,_,$),Qt||io(4,_,$),Ps(p,$,_);break;case 1:Qt||(_s(_,$),v=_.stateNode,typeof v.componentWillUnmount=="function"&&$d(_,$,v)),Ps(p,$,_);break;case 21:Ps(p,$,_);break;case 22:Qt=(v=Qt)||_.memoizedState!==null,Ps(p,$,_),Qt=v;break;default:Ps(p,$,_)}}function Ed(p,$){if($.memoizedState===null&&(p=$.alternate,p!==null&&(p=p.memoizedState,p!==null))){p=p.dehydrated;try{_r(p)}catch(_){_t($,$.return,_)}}}function kd(p,$){if($.memoizedState===null&&(p=$.alternate,p!==null&&(p=p.memoizedState,p!==null&&(p=p.dehydrated,p!==null))))try{_r(p)}catch(_){_t($,$.return,_)}}function Lh(p){switch(p.tag){case 31:case 13:case 19:var $=p.stateNode;return $===null&&($=p.stateNode=new wd),$;case 22:return p=p.stateNode,$=p._retryCache,$===null&&($=p._retryCache=new wd),$;default:throw Error(s(435,p.tag))}}function Ua(p,$){var _=Lh(p);$.forEach(function(v){if(!_.has(v)){_.add(v);var j=Gh.bind(null,p,v);v.then(j,j)}})}function In(p,$){var _=$.deletions;if(_!==null)for(var v=0;v<_.length;v++){var j=_[v],I=p,C=$,Y=C;e:for(;Y!==null;){switch(Y.tag){case 27:if(fo(Y.type)){Lt=Y.stateNode,Wn=!1;break e}break;case 5:Lt=Y.stateNode,Wn=!1;break e;case 3:case 4:Lt=Y.stateNode.containerInfo,Wn=!0;break e}Y=Y.return}if(Lt===null)throw Error(s(160));vd(I,C,j),Lt=null,Wn=!1,I=j.alternate,I!==null&&(I.return=null),j.return=null}if($.subtreeFlags&13886)for($=$.child;$!==null;)Td($,p),$=$.sibling}var ms=null;function Td(p,$){var _=p.alternate,v=p.flags;switch(p.tag){case 0:case 11:case 14:case 15:In($,p),Ln(p),v&4&&(io(3,p,p.return),zr(3,p),io(5,p,p.return));break;case 1:In($,p),Ln(p),v&512&&(Qt||_===null||_s(_,_.return)),v&64&&Ls&&(p=p.updateQueue,p!==null&&(v=p.callbacks,v!==null&&(_=p.shared.hiddenCallbacks,p.shared.hiddenCallbacks=_===null?v:_.concat(v))));break;case 26:var j=ms;if(In($,p),Ln(p),v&512&&(Qt||_===null||_s(_,_.return)),v&4){var I=_!==null?_.memoizedState:null;if(v=p.memoizedState,_===null)if(v===null)if(p.stateNode===null){e:{v=p.type,_=p.memoizedProps,j=j.ownerDocument||j;t:switch(v){case"title":I=j.getElementsByTagName("title")[0],(!I||I[tt]||I[rt]||I.namespaceURI==="http://www.w3.org/2000/svg"||I.hasAttribute("itemprop"))&&(I=j.createElement(v),j.head.insertBefore(I,j.querySelector("head > title"))),fn(I,v,_),I[rt]=p,We(I),v=I;break e;case"link":var C=Tm("link","href",j).get(v+(_.href||""));if(C){for(var Y=0;Y<C.length;Y++)if(I=C[Y],I.getAttribute("href")===(_.href==null||_.href===""?null:_.href)&&I.getAttribute("rel")===(_.rel==null?null:_.rel)&&I.getAttribute("title")===(_.title==null?null:_.title)&&I.getAttribute("crossorigin")===(_.crossOrigin==null?null:_.crossOrigin)){C.splice(Y,1);break t}}I=j.createElement(v),fn(I,v,_),j.head.appendChild(I);break;case"meta":if(C=Tm("meta","content",j).get(v+(_.content||""))){for(Y=0;Y<C.length;Y++)if(I=C[Y],I.getAttribute("content")===(_.content==null?null:""+_.content)&&I.getAttribute("name")===(_.name==null?null:_.name)&&I.getAttribute("property")===(_.property==null?null:_.property)&&I.getAttribute("http-equiv")===(_.httpEquiv==null?null:_.httpEquiv)&&I.getAttribute("charset")===(_.charSet==null?null:_.charSet)){C.splice(Y,1);break t}}I=j.createElement(v),fn(I,v,_),j.head.appendChild(I);break;default:throw Error(s(468,v))}I[rt]=p,We(I),v=I}p.stateNode=v}else Sm(j,p.type,p.stateNode);else p.stateNode=km(j,v,p.memoizedProps);else I!==v?(I===null?_.stateNode!==null&&(_=_.stateNode,_.parentNode.removeChild(_)):I.count--,v===null?Sm(j,p.type,p.stateNode):km(j,v,p.memoizedProps)):v===null&&p.stateNode!==null&&Rl(p,p.memoizedProps,_.memoizedProps)}break;case 27:In($,p),Ln(p),v&512&&(Qt||_===null||_s(_,_.return)),_!==null&&v&4&&Rl(p,p.memoizedProps,_.memoizedProps);break;case 5:if(In($,p),Ln(p),v&512&&(Qt||_===null||_s(_,_.return)),p.flags&32){j=p.stateNode;try{$s(j,"")}catch(Ae){_t(p,p.return,Ae)}}v&4&&p.stateNode!=null&&(j=p.memoizedProps,Rl(p,j,_!==null?_.memoizedProps:j)),v&1024&&(Ml=!0);break;case 6:if(In($,p),Ln(p),v&4){if(p.stateNode===null)throw Error(s(162));v=p.memoizedProps,_=p.stateNode;try{_.nodeValue=v}catch(Ae){_t(p,p.return,Ae)}}break;case 3:if(ri=null,j=ms,ms=si($.containerInfo),In($,p),ms=j,Ln(p),v&4&&_!==null&&_.memoizedState.isDehydrated)try{_r($.containerInfo)}catch(Ae){_t(p,p.return,Ae)}Ml&&(Ml=!1,Sd(p));break;case 4:v=ms,ms=si(p.stateNode.containerInfo),In($,p),Ln(p),ms=v;break;case 12:In($,p),Ln(p);break;case 31:In($,p),Ln(p),v&4&&(v=p.updateQueue,v!==null&&(p.updateQueue=null,Ua(p,v)));break;case 13:In($,p),Ln(p),p.child.flags&8192&&p.memoizedState!==null!=(_!==null&&_.memoizedState!==null)&&(qa=ge()),v&4&&(v=p.updateQueue,v!==null&&(p.updateQueue=null,Ua(p,v)));break;case 22:j=p.memoizedState!==null;var J=_!==null&&_.memoizedState!==null,ue=Ls,he=Qt;if(Ls=ue||j,Qt=he||J,In($,p),Qt=he,Ls=ue,Ln(p),v&8192)e:for($=p.stateNode,$._visibility=j?$._visibility&-2:$._visibility|1,j&&(_===null||J||Ls||Qt||Oo(p)),_=null,$=p;;){if($.tag===5||$.tag===26){if(_===null){J=_=$;try{if(I=J.stateNode,j)C=I.style,typeof C.setProperty=="function"?C.setProperty("display","none","important"):C.display="none";else{Y=J.stateNode;var fe=J.memoizedProps.style,de=fe!=null&&fe.hasOwnProperty("display")?fe.display:null;Y.style.display=de==null||typeof de=="boolean"?"":(""+de).trim()}}catch(Ae){_t(J,J.return,Ae)}}}else if($.tag===6){if(_===null){J=$;try{J.stateNode.nodeValue=j?"":J.memoizedProps}catch(Ae){_t(J,J.return,Ae)}}}else if($.tag===18){if(_===null){J=$;try{var me=J.stateNode;j?$m(me,!0):$m(J.stateNode,!1)}catch(Ae){_t(J,J.return,Ae)}}}else if(($.tag!==22&&$.tag!==23||$.memoizedState===null||$===p)&&$.child!==null){$.child.return=$,$=$.child;continue}if($===p)break e;for(;$.sibling===null;){if($.return===null||$.return===p)break e;_===$&&(_=null),$=$.return}_===$&&(_=null),$.sibling.return=$.return,$=$.sibling}v&4&&(v=p.updateQueue,v!==null&&(_=v.retryQueue,_!==null&&(v.retryQueue=null,Ua(p,_))));break;case 19:In($,p),Ln(p),v&4&&(v=p.updateQueue,v!==null&&(p.updateQueue=null,Ua(p,v)));break;case 30:break;case 21:break;default:In($,p),Ln(p)}}function Ln(p){var $=p.flags;if($&2){try{for(var _,v=p.return;v!==null;){if(bd(v)){_=v;break}v=v.return}if(_==null)throw Error(s(160));switch(_.tag){case 27:var j=_.stateNode,I=Al(p);Ba(p,I,j);break;case 5:var C=_.stateNode;_.flags&32&&($s(C,""),_.flags&=-33);var Y=Al(p);Ba(p,Y,C);break;case 3:case 4:var J=_.stateNode.containerInfo,ue=Al(p);jl(p,ue,J);break;default:throw Error(s(161))}}catch(he){_t(p,p.return,he)}p.flags&=-3}$&4096&&(p.flags&=-4097)}function Sd(p){if(p.subtreeFlags&1024)for(p=p.child;p!==null;){var $=p;Sd($),$.tag===5&&$.flags&1024&&$.stateNode.reset(),p=p.sibling}}function Cs(p,$){if($.subtreeFlags&8772)for($=$.child;$!==null;)xd(p,$.alternate,$),$=$.sibling}function Oo(p){for(p=p.child;p!==null;){var $=p;switch($.tag){case 0:case 11:case 14:case 15:io(4,$,$.return),Oo($);break;case 1:_s($,$.return);var _=$.stateNode;typeof _.componentWillUnmount=="function"&&$d($,$.return,_),Oo($);break;case 27:sa($.stateNode);case 26:case 5:_s($,$.return),Oo($);break;case 22:$.memoizedState===null&&Oo($);break;case 30:Oo($);break;default:Oo($)}p=p.sibling}}function Os(p,$,_){for(_=_&&($.subtreeFlags&8772)!==0,$=$.child;$!==null;){var v=$.alternate,j=p,I=$,C=I.flags;switch(I.tag){case 0:case 11:case 15:Os(j,I,_),zr(4,I);break;case 1:if(Os(j,I,_),v=I,j=v.stateNode,typeof j.componentDidMount=="function")try{j.componentDidMount()}catch(ue){_t(v,v.return,ue)}if(v=I,j=v.updateQueue,j!==null){var Y=v.stateNode;try{var J=j.shared.hiddenCallbacks;if(J!==null)for(j.shared.hiddenCallbacks=null,j=0;j<J.length;j++)su(J[j],Y)}catch(ue){_t(v,v.return,ue)}}_&&C&64&&fd(I),Yr(I,I.return);break;case 27:_d(I);case 26:case 5:Os(j,I,_),_&&v===null&&C&4&&gd(I),Yr(I,I.return);break;case 12:Os(j,I,_);break;case 31:Os(j,I,_),_&&C&4&&Ed(j,I);break;case 13:Os(j,I,_),_&&C&4&&kd(j,I);break;case 22:I.memoizedState===null&&Os(j,I,_),Yr(I,I.return);break;case 30:break;default:Os(j,I,_)}$=$.sibling}}function Nl(p,$){var _=null;p!==null&&p.memoizedState!==null&&p.memoizedState.cachePool!==null&&(_=p.memoizedState.cachePool.pool),p=null,$.memoizedState!==null&&$.memoizedState.cachePool!==null&&(p=$.memoizedState.cachePool.pool),p!==_&&(p!=null&&p.refCount++,_!=null&&Ir(_))}function Wl(p,$){p=null,$.alternate!==null&&(p=$.alternate.memoizedState.cache),$=$.memoizedState.cache,$!==p&&($.refCount++,p!=null&&Ir(p))}function hs(p,$,_,v){if($.subtreeFlags&10256)for($=$.child;$!==null;)Rd(p,$,_,v),$=$.sibling}function Rd(p,$,_,v){var j=$.flags;switch($.tag){case 0:case 11:case 15:hs(p,$,_,v),j&2048&&zr(9,$);break;case 1:hs(p,$,_,v);break;case 3:hs(p,$,_,v),j&2048&&(p=null,$.alternate!==null&&(p=$.alternate.memoizedState.cache),$=$.memoizedState.cache,$!==p&&($.refCount++,p!=null&&Ir(p)));break;case 12:if(j&2048){hs(p,$,_,v),p=$.stateNode;try{var I=$.memoizedProps,C=I.id,Y=I.onPostCommit;typeof Y=="function"&&Y(C,$.alternate===null?"mount":"update",p.passiveEffectDuration,-0)}catch(J){_t($,$.return,J)}}else hs(p,$,_,v);break;case 31:hs(p,$,_,v);break;case 13:hs(p,$,_,v);break;case 23:break;case 22:I=$.stateNode,C=$.alternate,$.memoizedState!==null?I._visibility&2?hs(p,$,_,v):Kr(p,$):I._visibility&2?hs(p,$,_,v):(I._visibility|=2,lr(p,$,_,v,($.subtreeFlags&10256)!==0||!1)),j&2048&&Nl(C,$);break;case 24:hs(p,$,_,v),j&2048&&Wl($.alternate,$);break;default:hs(p,$,_,v)}}function lr(p,$,_,v,j){for(j=j&&(($.subtreeFlags&10256)!==0||!1),$=$.child;$!==null;){var I=p,C=$,Y=_,J=v,ue=C.flags;switch(C.tag){case 0:case 11:case 15:lr(I,C,Y,J,j),zr(8,C);break;case 23:break;case 22:var he=C.stateNode;C.memoizedState!==null?he._visibility&2?lr(I,C,Y,J,j):Kr(I,C):(he._visibility|=2,lr(I,C,Y,J,j)),j&&ue&2048&&Nl(C.alternate,C);break;case 24:lr(I,C,Y,J,j),j&&ue&2048&&Wl(C.alternate,C);break;default:lr(I,C,Y,J,j)}$=$.sibling}}function Kr(p,$){if($.subtreeFlags&10256)for($=$.child;$!==null;){var _=p,v=$,j=v.flags;switch(v.tag){case 22:Kr(_,v),j&2048&&Nl(v.alternate,v);break;case 24:Kr(_,v),j&2048&&Wl(v.alternate,v);break;default:Kr(_,v)}$=$.sibling}}var Qr=8192;function cr(p,$,_){if(p.subtreeFlags&Qr)for(p=p.child;p!==null;)Ad(p,$,_),p=p.sibling}function Ad(p,$,_){switch(p.tag){case 26:cr(p,$,_),p.flags&Qr&&p.memoizedState!==null&&vp(_,ms,p.memoizedState,p.memoizedProps);break;case 5:cr(p,$,_);break;case 3:case 4:var v=ms;ms=si(p.stateNode.containerInfo),cr(p,$,_),ms=v;break;case 22:p.memoizedState===null&&(v=p.alternate,v!==null&&v.memoizedState!==null?(v=Qr,Qr=16777216,cr(p,$,_),Qr=v):cr(p,$,_));break;default:cr(p,$,_)}}function jd(p){var $=p.alternate;if($!==null&&(p=$.child,p!==null)){$.child=null;do $=p.sibling,p.sibling=null,p=$;while(p!==null)}}function Xr(p){var $=p.deletions;if((p.flags&16)!==0){if($!==null)for(var _=0;_<$.length;_++){var v=$[_];an=v,Nd(v,p)}jd(p)}if(p.subtreeFlags&10256)for(p=p.child;p!==null;)Md(p),p=p.sibling}function Md(p){switch(p.tag){case 0:case 11:case 15:Xr(p),p.flags&2048&&io(9,p,p.return);break;case 3:Xr(p);break;case 12:Xr(p);break;case 22:var $=p.stateNode;p.memoizedState!==null&&$._visibility&2&&(p.return===null||p.return.tag!==13)?($._visibility&=-3,Ga(p)):Xr(p);break;default:Xr(p)}}function Ga(p){var $=p.deletions;if((p.flags&16)!==0){if($!==null)for(var _=0;_<$.length;_++){var v=$[_];an=v,Nd(v,p)}jd(p)}for(p=p.child;p!==null;){switch($=p,$.tag){case 0:case 11:case 15:io(8,$,$.return),Ga($);break;case 22:_=$.stateNode,_._visibility&2&&(_._visibility&=-3,Ga($));break;default:Ga($)}p=p.sibling}}function Nd(p,$){for(;an!==null;){var _=an;switch(_.tag){case 0:case 11:case 15:io(8,_,$);break;case 23:case 22:if(_.memoizedState!==null&&_.memoizedState.cachePool!==null){var v=_.memoizedState.cachePool.pool;v!=null&&v.refCount++}break;case 24:Ir(_.memoizedState.cache)}if(v=_.child,v!==null)v.return=_,an=v;else e:for(_=p;an!==null;){v=an;var j=v.sibling,I=v.return;if(yd(v),v===_){an=null;break e}if(j!==null){j.return=I,an=j;break e}an=I}}}var Ph={getCacheForType:function(p){var $=hn(zt),_=$.data.get(p);return _===void 0&&(_=p(),$.data.set(p,_)),_},cacheSignal:function(){return hn(zt).controller.signal}},Ch=typeof WeakMap=="function"?WeakMap:Map,pt=0,St=null,Ze=null,nt=0,bt=0,Gn=null,lo=!1,ur=!1,Il=!1,Ds=0,Dt=0,co=0,Do=0,Ll=0,qn=0,dr=0,Zr=null,Pn=null,Pl=!1,qa=0,Wd=0,Va=1/0,za=null,uo=null,tn=0,mo=null,mr=null,Fs=0,Cl=0,Ol=null,Id=null,Jr=0,Dl=null;function Vn(){return(pt&2)!==0&&nt!==0?nt&-nt:O.T!==null?ql():Rn()}function Ld(){if(qn===0)if((nt&536870912)===0||it){var p=Qe;Qe<<=1,(Qe&3932160)===0&&(Qe=262144),qn=p}else qn=536870912;return p=Bn.current,p!==null&&(p.flags|=32),qn}function Cn(p,$,_){(p===St&&(bt===2||bt===9)||p.cancelPendingCommit!==null)&&(hr(p,0),ho(p,nt,qn,!1)),Zt(p,_),((pt&2)===0||p!==St)&&(p===St&&((pt&2)===0&&(Do|=_),Dt===4&&ho(p,nt,qn,!1)),ws(p))}function Pd(p,$,_){if((pt&6)!==0)throw Error(s(327));var v=!_&&($&127)===0&&($&p.expiredLanes)===0||kt(p,$),j=v?Fh(p,$):Hl(p,$,!0),I=v;do{if(j===0){ur&&!v&&ho(p,$,0,!1);break}else{if(_=p.current.alternate,I&&!Oh(_)){j=Hl(p,$,!1),I=!1;continue}if(j===2){if(I=$,p.errorRecoveryDisabledLanes&I)var C=0;else C=p.pendingLanes&-536870913,C=C!==0?C:C&536870912?536870912:0;if(C!==0){$=C;e:{var Y=p;j=Zr;var J=Y.current.memoizedState.isDehydrated;if(J&&(hr(Y,C).flags|=256),C=Hl(Y,C,!1),C!==2){if(Il&&!J){Y.errorRecoveryDisabledLanes|=I,Do|=I,j=4;break e}I=Pn,Pn=j,I!==null&&(Pn===null?Pn=I:Pn.push.apply(Pn,I))}j=C}if(I=!1,j!==2)continue}}if(j===1){hr(p,0),ho(p,$,0,!0);break}e:{switch(v=p,I=j,I){case 0:case 1:throw Error(s(345));case 4:if(($&4194048)!==$)break;case 6:ho(v,$,qn,!lo);break e;case 2:Pn=null;break;case 3:case 5:break;default:throw Error(s(329))}if(($&62914560)===$&&(j=qa+300-ge(),10<j)){if(ho(v,$,qn,!lo),Ce(v,0,!0)!==0)break e;Fs=$,v.timeoutHandle=hm(Cd.bind(null,v,_,Pn,za,Pl,$,qn,Do,dr,lo,I,"Throttled",-0,0),j);break e}Cd(v,_,Pn,za,Pl,$,qn,Do,dr,lo,I,null,-0,0)}}break}while(!0);ws(p)}function Cd(p,$,_,v,j,I,C,Y,J,ue,he,fe,de,me){if(p.timeoutHandle=-1,fe=$.subtreeFlags,fe&8192||(fe&16785408)===16785408){fe={stylesheets:null,count:0,imgCount:0,imgBytes:0,suspenseyImages:[],waitingForImages:!0,waitingForViewTransition:!1,unsuspend:Be},Ad($,I,fe);var Ae=(I&62914560)===I?qa-ge():(I&4194048)===I?Wd-ge():0;if(Ae=Ep(fe,Ae),Ae!==null){Fs=I,p.cancelPendingCommit=Ae(qd.bind(null,p,$,I,_,v,j,C,Y,J,he,fe,null,de,me)),ho(p,I,C,!ue);return}}qd(p,$,I,_,v,j,C,Y,J)}function Oh(p){for(var $=p;;){var _=$.tag;if((_===0||_===11||_===15)&&$.flags&16384&&(_=$.updateQueue,_!==null&&(_=_.stores,_!==null)))for(var v=0;v<_.length;
vendor: 18,899 bytes, line 9
9v++){var j=_[v],I=j.getSnapshot;j=j.value;try{if(!Fn(I(),j))return!1}catch{return!1}}if(_=$.child,$.subtreeFlags&16384&&_!==null)_.return=$,$=_;else{if($===p)break;for(;$.sibling===null;){if($.return===null||$.return===p)return!0;$=$.return}$.sibling.return=$.return,$=$.sibling}}return!0}function ho(p,$,_,v){$&=~Ll,$&=~Do,p.suspendedLanes|=$,p.pingedLanes&=~$,v&&(p.warmLanes|=$),v=p.expirationTimes;for(var j=$;0<j;){var I=31-ot(j),C=1<<I;v[I]=-1,j&=~C}_!==0&&cn(p,_,$)}function Ya(){return(pt&6)===0?(ea(0),!1):!0}function Fl(){if(Ze!==null){if(bt===0)var p=Ze.return;else p=Ze,As=jo=null,nl(p),sr=null,Pr=0,p=Ze;for(;p!==null;)pd(p.alternate,p),p=p.return;Ze=null}}function hr(p,$){var _=p.timeoutHandle;_!==-1&&(p.timeoutHandle=-1,rp(_)),_=p.cancelPendingCommit,_!==null&&(p.cancelPendingCommit=null,_()),Fs=0,Fl(),St=p,Ze=_=Ss(p.current,null),nt=$,bt=0,Gn=null,lo=!1,ur=kt(p,$),Il=!1,dr=qn=Ll=Do=co=Dt=0,Pn=Zr=null,Pl=!1,($&8)!==0&&($|=$&32);var v=p.entangledLanes;if(v!==0)for(p=p.entanglements,v&=$;0<v;){var j=31-ot(v),I=1<<j;$|=p[j],v&=~I}return Ds=$,fa(),_}function Od(p,$){Ue=null,O.H=Gr,$===nr||$===va?($=Jc(),bt=3):$===Gi?($=Jc(),bt=4):bt=$===bl?8:$!==null&&typeof $=="object"&&typeof $.then=="function"?6:1,Gn=$,Ze===null&&(Dt=1,Ca(p,Jn($,p.current)))}function Dd(){var p=Bn.current;return p===null?!0:(nt&4194048)===nt?ss===null:(nt&62914560)===nt||(nt&536870912)!==0?p===ss:!1}function Fd(){var p=O.H;return O.H=Gr,p===null?Gr:p}function Hd(){var p=O.A;return O.A=Ph,p}function Ka(){Dt=4,lo||(nt&4194048)!==nt&&Bn.current!==null||(ur=!0),(co&134217727)===0&&(Do&134217727)===0||St===null||ho(St,nt,qn,!1)}function Hl(p,$,_){var v=pt;pt|=2;var j=Fd(),I=Hd();(St!==p||nt!==$)&&(za=null,hr(p,$)),$=!1;var C=Dt;e:do try{if(bt!==0&&Ze!==null){var Y=Ze,J=Gn;switch(bt){case 8:Fl(),C=6;break e;case 3:case 2:case 9:case 6:Bn.current===null&&($=!0);var ue=bt;if(bt=0,Gn=null,pr(p,Y,J,ue),_&&ur){C=0;break e}break;default:ue=bt,bt=0,Gn=null,pr(p,Y,J,ue)}}Dh(),C=Dt;break}catch(he){Od(p,he)}while(!0);return $&&p.shellSuspendCounter++,As=jo=null,pt=v,O.H=j,O.A=I,Ze===null&&(St=null,nt=0,fa()),C}function Dh(){for(;Ze!==null;)Bd(Ze)}function Fh(p,$){var _=pt;pt|=2;var v=Fd(),j=Hd();St!==p||nt!==$?(za=null,Va=ge()+500,hr(p,$)):ur=kt(p,$);e:do try{if(bt!==0&&Ze!==null){$=Ze;var I=Gn;t:switch(bt){case 1:bt=0,Gn=null,pr(p,$,I,1);break;case 2:case 9:if(Xc(I)){bt=0,Gn=null,Ud($);break}$=function(){bt!==2&&bt!==9||St!==p||(bt=7),ws(p)},I.then($,$);break e;case 3:bt=7;break e;case 4:bt=5;break e;case 7:Xc(I)?(bt=0,Gn=null,Ud($)):(bt=0,Gn=null,pr(p,$,I,7));break;case 5:var C=null;switch(Ze.tag){case 26:C=Ze.memoizedState;case 5:case 27:var Y=Ze;if(C?Rm(C):Y.stateNode.complete){bt=0,Gn=null;var J=Y.sibling;if(J!==null)Ze=J;else{var ue=Y.return;ue!==null?(Ze=ue,Qa(ue)):Ze=null}break t}}bt=0,Gn=null,pr(p,$,I,5);break;case 6:bt=0,Gn=null,pr(p,$,I,6);break;case 8:Fl(),Dt=6;break e;default:throw Error(s(462))}}Hh();break}catch(he){Od(p,he)}while(!0);return As=jo=null,O.H=v,O.A=j,pt=_,Ze!==null?0:(St=null,nt=0,fa(),Dt)}function Hh(){for(;Ze!==null&&!Xe();)Bd(Ze)}function Bd(p){var $=md(p.alternate,p,Ds);p.memoizedProps=p.pendingProps,$===null?Qa(p):Ze=$}function Ud(p){var $=p,_=$.alternate;switch($.tag){case 15:case 0:$=ad(_,$,$.pendingProps,$.type,void 0,nt);break;case 11:$=ad(_,$,$.pendingProps,$.type.render,$.ref,nt);break;case 5:nl($);default:pd(_,$),$=Ze=Fc($,Ds),$=md(_,$,Ds)}p.memoizedProps=p.pendingProps,$===null?Qa(p):Ze=$}function pr(p,$,_,v){As=jo=null,nl($),sr=null,Pr=0;var j=$.return;try{if(Ah(p,j,$,_,nt)){Dt=1,Ca(p,Jn(_,p.current)),Ze=null;return}}catch(I){if(j!==null)throw Ze=j,I;Dt=1,Ca(p,Jn(_,p.current)),Ze=null;return}$.flags&32768?(it||v===1?p=!0:ur||(nt&536870912)!==0?p=!1:(lo=p=!0,(v===2||v===9||v===3||v===6)&&(v=Bn.current,v!==null&&v.tag===13&&(v.flags|=16384))),Gd($,p)):Qa($)}function Qa(p){var $=p;do{if(($.flags&32768)!==0){Gd($,lo);return}p=$.return;var _=Nh($.alternate,$,Ds);if(_!==null){Ze=_;return}if($=$.sibling,$!==null){Ze=$;return}Ze=$=p}while($!==null);Dt===0&&(Dt=5)}function Gd(p,$){do{var _=Wh(p.alternate,p);if(_!==null){_.flags&=32767,Ze=_;return}if(_=p.return,_!==null&&(_.flags|=32768,_.subtreeFlags=0,_.deletions=null),!$&&(p=p.sibling,p!==null)){Ze=p;return}Ze=p=_}while(p!==null);Dt=6,Ze=null}function qd(p,$,_,v,j,I,C,Y,J){p.cancelPendingCommit=null;do Xa();while(tn!==0);if((pt&6)!==0)throw Error(s(327));if($!==null){if($===p.current)throw Error(s(177));if(I=$.lanes|$.childLanes,I|=Ai,$n(p,_,I,C,Y,J),p===St&&(Ze=St=null,nt=0),mr=$,mo=p,Fs=_,Cl=I,Ol=j,Id=v,($.subtreeFlags&10256)!==0||($.flags&10256)!==0?(p.callbackNode=null,p.callbackPriority=0,qh(mt,function(){return Qd(),null})):(p.callbackNode=null,p.callbackPriority=0),v=($.flags&13878)!==0,($.subtreeFlags&13878)!==0||v){v=O.T,O.T=null,j=z.p,z.p=2,C=pt,pt|=4;try{Ih(p,$,_)}finally{pt=C,z.p=j,O.T=v}}tn=1,Vd(),zd(),Yd()}}function Vd(){if(tn===1){tn=0;var p=mo,$=mr,_=($.flags&13878)!==0;if(($.subtreeFlags&13878)!==0||_){_=O.T,O.T=null;var v=z.p;z.p=2;var j=pt;pt|=4;try{Td($,p);var I=Jl,C=Mc(p.containerInfo),Y=I.focusedElem,J=I.selectionRange;if(C!==Y&&Y&&Y.ownerDocument&&jc(Y.ownerDocument.documentElement,Y)){if(J!==null&&Ei(Y)){var ue=J.start,he=J.end;if(he===void 0&&(he=ue),"selectionStart"in Y)Y.selectionStart=ue,Y.selectionEnd=Math.min(he,Y.value.length);else{var fe=Y.ownerDocument||document,de=fe&&fe.defaultView||window;if(de.getSelection){var me=de.getSelection(),Ae=Y.textContent.length,Oe=Math.min(J.start,Ae),vt=J.end===void 0?Oe:Math.min(J.end,Ae);!me.extend&&Oe>vt&&(C=vt,vt=Oe,Oe=C);var re=Ac(Y,Oe),ee=Ac(Y,vt);if(re&&ee&&(me.rangeCount!==1||me.anchorNode!==re.node||me.anchorOffset!==re.offset||me.focusNode!==ee.node||me.focusOffset!==ee.offset)){var ce=fe.createRange();ce.setStart(re.node,re.offset),me.removeAllRanges(),Oe>vt?(me.addRange(ce),me.extend(ee.node,ee.offset)):(ce.setEnd(ee.node,ee.offset),me.addRange(ce))}}}}for(fe=[],me=Y;me=me.parentNode;)me.nodeType===1&&fe.push({element:me,left:me.scrollLeft,top:me.scrollTop});for(typeof Y.focus=="function"&&Y.focus(),Y=0;Y<fe.length;Y++){var pe=fe[Y];pe.element.scrollLeft=pe.left,pe.element.scrollTop=pe.top}}ci=!!Zl,Jl=Zl=null}finally{pt=j,z.p=v,O.T=_}}p.current=$,tn=2}}function zd(){if(tn===2){tn=0;var p=mo,$=mr,_=($.flags&8772)!==0;if(($.subtreeFlags&8772)!==0||_){_=O.T,O.T=null;var v=z.p;z.p=2;var j=pt;pt|=4;try{xd(p,$.alternate,$)}finally{pt=j,z.p=v,O.T=_}}tn=3}}function Yd(){if(tn===4||tn===3){tn=0,Rt();var p=mo,$=mr,_=Fs,v=Id;($.subtreeFlags&10256)!==0||($.flags&10256)!==0?tn=5:(tn=0,mr=mo=null,Kd(p,p.pendingLanes));var j=p.pendingLanes;if(j===0&&(uo=null),Sn(_),$=$.stateNode,ct&&typeof ct.onCommitFiberRoot=="function")try{ct.onCommitFiberRoot(lt,$,void 0,($.current.flags&128)===128)}catch{}if(v!==null){$=O.T,j=z.p,z.p=2,O.T=null;try{for(var I=p.onRecoverableError,C=0;C<v.length;C++){var Y=v[C];I(Y.value,{componentStack:Y.stack})}}finally{O.T=$,z.p=j}}(Fs&3)!==0&&Xa(),ws(p),j=p.pendingLanes,(_&261930)!==0&&(j&42)!==0?p===Dl?Jr++:(Jr=0,Dl=p):Jr=0,ea(0)}}function Kd(p,$){(p.pooledCacheLanes&=$)===0&&($=p.pooledCache,$!=null&&(p.pooledCache=null,Ir($)))}function Xa(){return Vd(),zd(),Yd(),Qd()}function Qd(){if(tn!==5)return!1;var p=mo,$=Cl;Cl=0;var _=Sn(Fs),v=O.T,j=z.p;try{z.p=32>_?32:_,O.T=null,_=Ol,Ol=null;var I=mo,C=Fs;if(tn=0,mr=mo=null,Fs=0,(pt&6)!==0)throw Error(s(331));var Y=pt;if(pt|=4,Md(I.current),Rd(I,I.current,C,_),pt=Y,ea(0,!1),ct&&typeof ct.onPostCommitFiberRoot=="function")try{ct.onPostCommitFiberRoot(lt,I)}catch{}return!0}finally{z.p=j,O.T=v,Kd(p,$)}}function Xd(p,$,_){$=Jn(_,$),$=gl(p.stateNode,$,2),p=oo(p,$,2),p!==null&&(Zt(p,2),ws(p))}function _t(p,$,_){if(p.tag===3)Xd(p,p,_);else for(;$!==null;){if($.tag===3){Xd($,p,_);break}else if($.tag===1){var v=$.stateNode;if(typeof $.type.getDerivedStateFromError=="function"||typeof v.componentDidCatch=="function"&&(uo===null||!uo.has(v))){p=Jn(_,p),_=Zu(2),v=oo($,_,2),v!==null&&(Ju(_,v,$,p),Zt(v,2),ws(v));break}}$=$.return}}function Bl(p,$,_){var v=p.pingCache;if(v===null){v=p.pingCache=new Ch;var j=new Set;v.set($,j)}else j=v.get($),j===void 0&&(j=new Set,v.set($,j));j.has(_)||(Il=!0,j.add(_),p=Bh.bind(null,p,$,_),$.then(p,p))}function Bh(p,$,_){var v=p.pingCache;v!==null&&v.delete($),p.pingedLanes|=p.suspendedLanes&_,p.warmLanes&=~_,St===p&&(nt&_)===_&&(Dt===4||Dt===3&&(nt&62914560)===nt&&300>ge()-qa?(pt&2)===0&&hr(p,0):Ll|=_,dr===nt&&(dr=0)),ws(p)}function Zd(p,$){$===0&&($=En()),p=So(p,$),p!==null&&(Zt(p,$),ws(p))}function Uh(p){var $=p.memoizedState,_=0;$!==null&&(_=$.retryLane),Zd(p,_)}function Gh(p,$){var _=0;switch(p.tag){case 31:case 13:var v=p.stateNode,j=p.memoizedState;j!==null&&(_=j.retryLane);break;case 19:v=p.stateNode;break;case 22:v=p.stateNode._retryCache;break;default:throw Error(s(314))}v!==null&&v.delete($),Zd(p,_)}function qh(p,$){return Ge(p,$)}var Za=null,fr=null,Ul=!1,Ja=!1,Gl=!1,po=0;function ws(p){p!==fr&&p.next===null&&(fr===null?Za=fr=p:fr=fr.next=p),Ja=!0,Ul||(Ul=!0,zh())}function ea(p,$){if(!Gl&&Ja){Gl=!0;do for(var _=!1,v=Za;v!==null;){if(p!==0){var j=v.pendingLanes;if(j===0)var I=0;else{var C=v.suspendedLanes,Y=v.pingedLanes;I=(1<<31-ot(42|p)+1)-1,I&=j&~(C&~Y),I=I&201326741?I&201326741|1:I?I|2:0}I!==0&&(_=!0,nm(v,I))}else I=nt,I=Ce(v,v===St?I:0,v.cancelPendingCommit!==null||v.timeoutHandle!==-1),(I&3)===0||kt(v,I)||(_=!0,nm(v,I));v=v.next}while(_);Gl=!1}}function Vh(){Jd()}function Jd(){Ja=Ul=!1;var p=0;po!==0&&op()&&(p=po);for(var $=ge(),_=null,v=Za;v!==null;){var j=v.next,I=em(v,$);I===0?(v.next=null,_===null?Za=j:_.next=j,j===null&&(fr=_)):(_=v,(p!==0||(I&3)!==0)&&(Ja=!0)),v=j}tn!==0&&tn!==5||ea(p),po!==0&&(po=0)}function em(p,$){for(var _=p.suspendedLanes,v=p.pingedLanes,j=p.expirationTimes,I=p.pendingLanes&-62914561;0<I;){var C=31-ot(I),Y=1<<C,J=j[C];J===-1?((Y&_)===0||(Y&v)!==0)&&(j[C]=sn(Y,$)):J<=$&&(p.expiredLanes|=Y),I&=~Y}if($=St,_=nt,_=Ce(p,p===$?_:0,p.cancelPendingCommit!==null||p.timeoutHandle!==-1),v=p.callbackNode,_===0||p===$&&(bt===2||bt===9)||p.cancelPendingCommit!==null)return v!==null&&v!==null&&Fe(v),p.callbackNode=null,p.callbackPriority=0;if((_&3)===0||kt(p,_)){if($=_&-_,$===p.callbackPriority)return $;switch(v!==null&&Fe(v),Sn(_)){case 2:case 8:_=wt;break;case 32:_=mt;break;case 268435456:_=Ke;break;default:_=mt}return v=tm.bind(null,p),_=Ge(_,v),p.callbackPriority=$,p.callbackNode=_,$}return v!==null&&v!==null&&Fe(v),p.callbackPriority=2,p.callbackNode=null,2}function tm(p,$){if(tn!==0&&tn!==5)return p.callbackNode=null,p.callbackPriority=0,null;var _=p.callbackNode;if(Xa()&&p.callbackNode!==_)return null;var v=nt;return v=Ce(p,p===St?v:0,p.cancelPendingCommit!==null||p.timeoutHandle!==-1),v===0?null:(Pd(p,v,$),em(p,ge()),p.callbackNode!=null&&p.callbackNode===_?tm.bind(null,p):null)}function nm(p,$){if(Xa())return null;Pd(p,$,!0)}function zh(){ap(function(){(pt&6)!==0?Ge(ft,Vh):Jd()})}function ql(){if(po===0){var p=er;p===0&&(p=Et,Et<<=1,(Et&261888)===0&&(Et=256)),po=p}return po}function sm(p){return p==null||typeof p=="symbol"||typeof p=="boolean"?null:typeof p=="function"?p:Me(""+p)}function om(p,$){var _=$.ownerDocument.createElement("input");return _.name=$.name,_.value=$.value,p.id&&_.setAttribute("form",p.id),$.parentNode.insertBefore(_,$),p=new FormData(p),_.parentNode.removeChild(_),p}function Yh(p,$,_,v,j){if($==="submit"&&_&&_.stateNode===j){var I=sm((j[At]||null).action),C=v.submitter;C&&($=($=C[At]||null)?sm($.formAction):C.getAttribute("formAction"),$!==null&&(I=$,C=null));var Y=new Bo("action","action",null,v,j);p.push({event:Y,listeners:[{instance:null,listener:function(){if(v.defaultPrevented){if(po!==0){var J=C?om(j,C):new FormData(j);dl(_,{pending:!0,data:J,method:j.method,action:I},null,J)}}else typeof I=="function"&&(Y.preventDefault(),J=C?om(j,C):new FormData(j),dl(_,{pending:!0,data:J,method:j.method,action:I},I,J))},currentTarget:j}]})}}for(var Vl=0;Vl<Ri.length;Vl++){var zl=Ri[Vl],Kh=zl.toLowerCase(),Qh=zl[0].toUpperCase()+zl.slice(1);ds(Kh,"on"+Qh)}ds(Ic,"onAnimationEnd"),ds(Lc,"onAnimationIteration"),ds(Pc,"onAnimationStart"),ds("dblclick","onDoubleClick"),ds("focusin","onFocus"),ds("focusout","onBlur"),ds(mh,"onTransitionRun"),ds(hh,"onTransitionStart"),ds(ph,"onTransitionCancel"),ds(Cc,"onTransitionEnd"),Ct("onMouseEnter",["mouseout","mouseover"]),Ct("onMouseLeave",["mouseout","mouseover"]),Ct("onPointerEnter",["pointerout","pointerover"]),Ct("onPointerLeave",["pointerout","pointerover"]),Jt("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),Jt("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),Jt("onBeforeInput",["compositionend","keypress","textInput","paste"]),Jt("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),Jt("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),Jt("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var ta="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Xh=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(ta));function rm(p,$){$=($&4)!==0;for(var _=0;_<p.length;_++){var v=p[_],j=v.event;v=v.listeners;e:{var I=void 0;if($)for(var C=v.length-1;0<=C;C--){var Y=v[C],J=Y.instance,ue=Y.currentTarget;if(Y=Y.listener,J!==I&&j.isPropagationStopped())break e;I=Y,j.currentTarget=ue;try{I(j)}catch(he){pa(he)}j.currentTarget=null,I=J}else for(C=0;C<v.length;C++){if(Y=v[C],J=Y.instance,ue=Y.currentTarget,Y=Y.listener,J!==I&&j.isPropagationStopped())break e;I=Y,j.currentTarget=ue;try{I(j)}catch(he){pa(he)}j.currentTarget=null,I=J}}}}function Je(p,$){var _=$[On];_===void 0&&(_=$[On]=new Set);var v=p+"__bubble";_.has(v)||(am($,p,2,!1),_.add(v))}function Yl(p,$,_){var v=0;$&&(v|=4),am(_,p,v,$)}var ei="_reactListening"+Math.random().toString(36).slice(2);function Kl(p){if(!p[ei]){p[ei]=!0,De.forEach(function(_){_!=="selectionchange"&&(Xh.has(_)||Yl(_,!1,p),Yl(_,!0,p))});var $=p.nodeType===9?p:p.ownerDocument;$===null||$[ei]||($[ei]=!0,Yl("selectionchange",!1,$))}}function am(p,$,_,v){switch(Lm($)){case 2:var j=Sp;break;case 8:j=Rp;break;default:j=uc}_=j.bind(null,$,_,p),j=void 0,!xr||$!=="touchstart"&&$!=="touchmove"&&$!=="wheel"||(j=!0),v?j!==void 0?p.addEventListener($,_,{capture:!0,passive:j}):p.addEventListener($,_,!0):j!==void 0?p.addEventListener($,_,{passive:j}):p.addEventListener($,_,!1)}function Ql(p,$,_,v,j){var I=v;if(($&1)===0&&($&2)===0&&v!==null)e:for(;;){if(v===null)return;var C=v.tag;if(C===3||C===4){var Y=v.stateNode.containerInfo;if(Y===j)break;if(C===4)for(C=v.return;C!==null;){var J=C.tag;if((J===3||J===4)&&C.stateNode.containerInfo===j)return;C=C.return}for(;Y!==null;){if(C=He(Y),C===null)return;if(J=C.tag,J===5||J===6||J===26||J===27){v=I=C;continue e}Y=Y.parentNode}}v=v.return}Mn(function(){var ue=I,he=dn(_),fe=[];e:{var de=Oc.get(p);if(de!==void 0){var me=Bo,Ae=p;switch(p){case"keypress":if(ks(_)===0)break e;case"keydown":case"keyup":me=Gm;break;case"focusin":Ae="focus",me=Er;break;case"focusout":Ae="blur",me=Er;break;case"beforeblur":case"afterblur":me=Er;break;case"click":if(_.button===2)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":me=vr;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":me=gi;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":me=zm;break;case Ic:case Lc:case Pc:me=ve;break;case Cc:me=Km;break;case"scroll":case"scrollend":me=$i;break;case"wheel":me=Xm;break;case"copy":case"cut":case"paste":me=ze;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":me=fc;break;case"toggle":case"beforetoggle":me=Jm}var Oe=($&4)!==0,vt=!Oe&&(p==="scroll"||p==="scrollend"),re=Oe?de!==null?de+"Capture":null:de;Oe=[];for(var ee=ue,ce;ee!==null;){var pe=ee;if(ce=pe.stateNode,pe=pe.tag,pe!==5&&pe!==26&&pe!==27||ce===null||re===null||(pe=_n(ee,re),pe!=null&&Oe.push(na(ee,pe,ce))),vt)break;ee=ee.return}0<Oe.length&&(de=new me(de,Ae,null,_,he),fe.push({event:de,listeners:Oe}))}}if(($&7)===0){e:{if(de=p==="mouseover"||p==="pointerover",me=p==="mouseout"||p==="pointerout",de&&_!==Ft&&(Ae=_.relatedTarget||_.fromElement)&&(He(Ae)||Ae[un]))break e;if((me||de)&&(de=he.window===he?he:(de=he.ownerDocument)?de.defaultView||de.parentWindow:window,me?(Ae=_.relatedTarget||_.toElement,me=ue,Ae=Ae?He(Ae):null,Ae!==null&&(vt=r(Ae),Oe=Ae.tag,Ae!==vt||Oe!==5&&Oe!==27&&Oe!==6)&&(Ae=null)):(me=null,Ae=ue),me!==Ae)){if(Oe=vr,pe="onMouseLeave",re="onMouseEnter",ee="mouse",(p==="pointerout"||p==="pointerover")&&(Oe=fc,pe="onPointerLeave",re="onPointerEnter",ee="pointer"),vt=me==null?de:at(me),ce=Ae==null?de:at(Ae),de=new Oe(pe,ee+"leave",me,_,he),de.target=vt,de.relatedTarget=ce,pe=null,He(he)===ue&&(Oe=new Oe(re,ee+"enter",Ae,_,he),Oe.target=ce,Oe.relatedTarget=vt,pe=Oe),vt=pe,me&&Ae)t:{for(Oe=Zh,re=me,ee=Ae,ce=0,pe=re;pe;pe=Oe(pe))ce++;pe=0;for(var Pe=ee;Pe;Pe=Oe(Pe))pe++;for(;0<ce-pe;)re=Oe(re),ce--;for(;0<pe-ce;)ee=Oe(ee),pe--;for(;ce--;){if(re===ee||ee!==null&&re===ee.alternate){Oe=re;break t}re=Oe(re),ee=Oe(ee)}Oe=null}else Oe=null;me!==null&&im(fe,de,me,Oe,!1),Ae!==null&&vt!==null&&im(fe,vt,Ae,Oe,!0)}}e:{if(de=ue?at(ue):window,me=de.nodeName&&de.nodeName.toLowerCase(),me==="select"||me==="input"&&de.type==="file")var ut=vc;else if(xc(de))if(Ec)ut=ch;else{ut=ih;var je=ah}else me=de.nodeName,!me||me.toLowerCase()!=="input"||de.type!=="checkbox"&&de.type!=="radio"?ue&&ls(ue.elementType)&&(ut=vc):ut=lh;if(ut&&(ut=ut(p,ue))){yc(fe,ut,_,he);break e}je&&je(p,de,ue),p==="focusout"&&ue&&de.type==="number"&&ue.memoizedProps.value!=null&&is(de,"number",de.value)}switch(je=ue?at(ue):window,p){case"focusin":(xc(je)||je.contentEditable==="true")&&(Vo=je,ki=ue,Mr=null);break;case"focusout":Mr=ki=Vo=null;break;case"mousedown":Ti=!0;break;case"contextmenu":case"mouseup":case"dragend":Ti=!1,Nc(fe,_,he);break;case"selectionchange":if(dh)break;case"keydown":case"keyup":Nc(fe,_,he)}var qe;if(xi)e:{switch(p){case"compositionstart":var st="onCompositionStart";break e;case"compositionend":st="onCompositionEnd";break e;case"compositionupdate":st="onCompositionUpdate";break e}st=void 0}else qo?_c(p,_)&&(st="onCompositionEnd"):p==="keydown"&&_.keyCode===229&&(st="onCompositionStart");st&&($c&&_.locale!=="ko"&&(qo||st!=="onCompositionStart"?st==="onCompositionEnd"&&qo&&(qe=yr()):(cs=he,Ys="value"in cs?cs.value:cs.textContent,qo=!0)),je=ti(ue,st),0<je.length&&(st=new Nn(st,p,null,_,he),fe.push({event:st,listeners:je}),qe?st.data=qe:(qe=wc(_),qe!==null&&(st.data=qe)))),(qe=th?nh(p,_):sh(
vendor: 13,469 bytes, lines 9-10
9p,_))&&(st=ti(ue,"onBeforeInput"),0<st.length&&(je=new Nn("onBeforeInput","beforeinput",null,_,he),fe.push({event:je,listeners:st}),je.data=qe)),Yh(fe,p,ue,_,he)}rm(fe,$)})}function na(p,$,_){return{instance:p,listener:$,currentTarget:_}}function ti(p,$){for(var _=$+"Capture",v=[];p!==null;){var j=p,I=j.stateNode;if(j=j.tag,j!==5&&j!==26&&j!==27||I===null||(j=_n(p,_),j!=null&&v.unshift(na(p,j,I)),j=_n(p,$),j!=null&&v.push(na(p,j,I))),p.tag===3)return v;p=p.return}return[]}function Zh(p){if(p===null)return null;do p=p.return;while(p&&p.tag!==5&&p.tag!==27);return p||null}function im(p,$,_,v,j){for(var I=$._reactName,C=[];_!==null&&_!==v;){var Y=_,J=Y.alternate,ue=Y.stateNode;if(Y=Y.tag,J!==null&&J===v)break;Y!==5&&Y!==26&&Y!==27||ue===null||(J=ue,j?(ue=_n(_,I),ue!=null&&C.unshift(na(_,ue,J))):j||(ue=_n(_,I),ue!=null&&C.push(na(_,ue,J)))),_=_.return}C.length!==0&&p.push({event:$,listeners:C})}var Jh=/\r\n?/g,ep=/\u0000|\uFFFD/g;function lm(p){return(typeof p=="string"?p:""+p).replace(Jh,`
10`).replace(ep,"")}function cm(p,$){return $=lm($),lm(p)===$}function yt(p,$,_,v,j,I){switch(_){case"children":typeof v=="string"?$==="body"||$==="textarea"&&v===""||$s(p,v):(typeof v=="number"||typeof v=="bigint")&&$!=="body"&&$s(p,""+v);break;case"className":qt(p,"class",v);break;case"tabIndex":qt(p,"tabindex",v);break;case"dir":case"role":case"viewBox":case"width":case"height":qt(p,_,v);break;case"style":Vs(p,v,I);break;case"data":if($!=="object"){qt(p,"data",v);break}case"src":case"href":if(v===""&&($!=="a"||_!=="href")){p.removeAttribute(_);break}if(v==null||typeof v=="function"||typeof v=="symbol"||typeof v=="boolean"){p.removeAttribute(_);break}v=Me(""+v),p.setAttribute(_,v);break;case"action":case"formAction":if(typeof v=="function"){p.setAttribute(_,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof I=="function"&&(_==="formAction"?($!=="input"&&yt(p,$,"name",j.name,j,null),yt(p,$,"formEncType",j.formEncType,j,null),yt(p,$,"formMethod",j.formMethod,j,null),yt(p,$,"formTarget",j.formTarget,j,null)):(yt(p,$,"encType",j.encType,j,null),yt(p,$,"method",j.method,j,null),yt(p,$,"target",j.target,j,null)));if(v==null||typeof v=="symbol"||typeof v=="boolean"){p.removeAttribute(_);break}v=Me(""+v),p.setAttribute(_,v);break;case"onClick":v!=null&&(p.onclick=Be);break;case"onScroll":v!=null&&Je("scroll",p);break;case"onScrollEnd":v!=null&&Je("scrollend",p);break;case"dangerouslySetInnerHTML":if(v!=null){if(typeof v!="object"||!("__html"in v))throw Error(s(61));if(_=v.__html,_!=null){if(j.children!=null)throw Error(s(60));p.innerHTML=_}}break;case"multiple":p.multiple=v&&typeof v!="function"&&typeof v!="symbol";break;case"muted":p.muted=v&&typeof v!="function"&&typeof v!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(v==null||typeof v=="function"||typeof v=="boolean"||typeof v=="symbol"){p.removeAttribute("xlink:href");break}_=Me(""+v),p.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",_);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":v!=null&&typeof v!="function"&&typeof v!="symbol"?p.setAttribute(_,""+v):p.removeAttribute(_);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":v&&typeof v!="function"&&typeof v!="symbol"?p.setAttribute(_,""):p.removeAttribute(_);break;case"capture":case"download":v===!0?p.setAttribute(_,""):v!==!1&&v!=null&&typeof v!="function"&&typeof v!="symbol"?p.setAttribute(_,v):p.removeAttribute(_);break;case"cols":case"rows":case"size":case"span":v!=null&&typeof v!="function"&&typeof v!="symbol"&&!isNaN(v)&&1<=v?p.setAttribute(_,v):p.removeAttribute(_);break;case"rowSpan":case"start":v==null||typeof v=="function"||typeof v=="symbol"||isNaN(v)?p.removeAttribute(_):p.setAttribute(_,v);break;case"popover":Je("beforetoggle",p),Je("toggle",p),xs(p,"popover",v);break;case"xlinkActuate":jt(p,"http://www.w3.org/1999/xlink","xlink:actuate",v);break;case"xlinkArcrole":jt(p,"http://www.w3.org/1999/xlink","xlink:arcrole",v);break;case"xlinkRole":jt(p,"http://www.w3.org/1999/xlink","xlink:role",v);break;case"xlinkShow":jt(p,"http://www.w3.org/1999/xlink","xlink:show",v);break;case"xlinkTitle":jt(p,"http://www.w3.org/1999/xlink","xlink:title",v);break;case"xlinkType":jt(p,"http://www.w3.org/1999/xlink","xlink:type",v);break;case"xmlBase":jt(p,"http://www.w3.org/XML/1998/namespace","xml:base",v);break;case"xmlLang":jt(p,"http://www.w3.org/XML/1998/namespace","xml:lang",v);break;case"xmlSpace":jt(p,"http://www.w3.org/XML/1998/namespace","xml:space",v);break;case"is":xs(p,"is",v);break;case"innerText":case"textContent":break;default:(!(2<_.length)||_[0]!=="o"&&_[0]!=="O"||_[1]!=="n"&&_[1]!=="N")&&(_=jn.get(_)||_,xs(p,_,v))}}function Xl(p,$,_,v,j,I){switch(_){case"style":Vs(p,v,I);break;case"dangerouslySetInnerHTML":if(v!=null){if(typeof v!="object"||!("__html"in v))throw Error(s(61));if(_=v.__html,_!=null){if(j.children!=null)throw Error(s(60));p.innerHTML=_}}break;case"children":typeof v=="string"?$s(p,v):(typeof v=="number"||typeof v=="bigint")&&$s(p,""+v);break;case"onScroll":v!=null&&Je("scroll",p);break;case"onScrollEnd":v!=null&&Je("scrollend",p);break;case"onClick":v!=null&&(p.onclick=Be);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":break;case"innerText":case"textContent":break;default:if(!An.hasOwnProperty(_))e:{if(_[0]==="o"&&_[1]==="n"&&(j=_.endsWith("Capture"),$=_.slice(2,j?_.length-7:void 0),I=p[At]||null,I=I!=null?I[_]:null,typeof I=="function"&&p.removeEventListener($,I,j),typeof v=="function")){typeof I!="function"&&I!==null&&(_ in p?p[_]=null:p.hasAttribute(_)&&p.removeAttribute(_)),p.addEventListener($,v,j);break e}_ in p?p[_]=v:v===!0?p.setAttribute(_,""):xs(p,_,v)}}}function fn(p,$,_){switch($){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":Je("error",p),Je("load",p);var v=!1,j=!1,I;for(I in _)if(_.hasOwnProperty(I)){var C=_[I];if(C!=null)switch(I){case"src":v=!0;break;case"srcSet":j=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(s(137,$));default:yt(p,$,I,C,_,null)}}j&&yt(p,$,"srcSet",_.srcSet,_,null),v&&yt(p,$,"src",_.src,_,null);return;case"input":Je("invalid",p);var Y=I=C=j=null,J=null,ue=null;for(v in _)if(_.hasOwnProperty(v)){var he=_[v];if(he!=null)switch(v){case"name":j=he;break;case"type":C=he;break;case"checked":J=he;break;case"defaultChecked":ue=he;break;case"value":I=he;break;case"defaultValue":Y=he;break;case"children":case"dangerouslySetInnerHTML":if(he!=null)throw Error(s(137,$));break;default:yt(p,$,v,he,_,null)}}qs(p,I,Y,J,ue,C,j,!1);return;case"select":Je("invalid",p),v=C=I=null;for(j in _)if(_.hasOwnProperty(j)&&(Y=_[j],Y!=null))switch(j){case"value":I=Y;break;case"defaultValue":C=Y;break;case"multiple":v=Y;default:yt(p,$,j,Y,_,null)}$=I,_=C,p.multiple=!!v,$!=null?Qn(p,!!v,$,!1):_!=null&&Qn(p,!!v,_,!0);return;case"textarea":Je("invalid",p),I=j=v=null;for(C in _)if(_.hasOwnProperty(C)&&(Y=_[C],Y!=null))switch(C){case"value":v=Y;break;case"defaultValue":j=Y;break;case"children":I=Y;break;case"dangerouslySetInnerHTML":if(Y!=null)throw Error(s(91));break;default:yt(p,$,C,Y,_,null)}vs(p,v,j,I);return;case"option":for(J in _)_.hasOwnProperty(J)&&(v=_[J],v!=null)&&(J==="selected"?p.selected=v&&typeof v!="function"&&typeof v!="symbol":yt(p,$,J,v,_,null));return;case"dialog":Je("beforetoggle",p),Je("toggle",p),Je("cancel",p),Je("close",p);break;case"iframe":case"object":Je("load",p);break;case"video":case"audio":for(v=0;v<ta.length;v++)Je(ta[v],p);break;case"image":Je("error",p),Je("load",p);break;case"details":Je("toggle",p);break;case"embed":case"source":case"link":Je("error",p),Je("load",p);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(ue in _)if(_.hasOwnProperty(ue)&&(v=_[ue],v!=null))switch(ue){case"children":case"dangerouslySetInnerHTML":throw Error(s(137,$));default:yt(p,$,ue,v,_,null)}return;default:if(ls($)){for(he in _)_.hasOwnProperty(he)&&(v=_[he],v!==void 0&&Xl(p,$,he,v,_,void 0));return}}for(Y in _)_.hasOwnProperty(Y)&&(v=_[Y],v!=null&&yt(p,$,Y,v,_,null))}function tp(p,$,_,v){switch($){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var j=null,I=null,C=null,Y=null,J=null,ue=null,he=null;for(me in _){var fe=_[me];if(_.hasOwnProperty(me)&&fe!=null)switch(me){case"checked":break;case"value":break;case"defaultValue":J=fe;default:v.hasOwnProperty(me)||yt(p,$,me,null,v,fe)}}for(var de in v){var me=v[de];if(fe=_[de],v.hasOwnProperty(de)&&(me!=null||fe!=null))switch(de){case"type":I=me;break;case"name":j=me;break;case"checked":ue=me;break;case"defaultChecked":he=me;break;case"value":C=me;break;case"defaultValue":Y=me;break;case"children":case"dangerouslySetInnerHTML":if(me!=null)throw Error(s(137,$));break;default:me!==fe&&yt(p,$,de,me,v,fe)}}yo(p,C,Y,J,ue,he,I,j);return;case"select":me=C=Y=de=null;for(I in _)if(J=_[I],_.hasOwnProperty(I)&&J!=null)switch(I){case"value":break;case"multiple":me=J;default:v.hasOwnProperty(I)||yt(p,$,I,null,v,J)}for(j in v)if(I=v[j],J=_[j],v.hasOwnProperty(j)&&(I!=null||J!=null))switch(j){case"value":de=I;break;case"defaultValue":Y=I;break;case"multiple":C=I;default:I!==J&&yt(p,$,j,I,v,J)}$=Y,_=C,v=me,de!=null?Qn(p,!!_,de,!1):!!v!=!!_&&($!=null?Qn(p,!!_,$,!0):Qn(p,!!_,_?[]:"",!1));return;case"textarea":me=de=null;for(Y in _)if(j=_[Y],_.hasOwnProperty(Y)&&j!=null&&!v.hasOwnProperty(Y))switch(Y){case"value":break;case"children":break;default:yt(p,$,Y,null,v,j)}for(C in v)if(j=v[C],I=_[C],v.hasOwnProperty(C)&&(j!=null||I!=null))switch(C){case"value":de=j;break;case"defaultValue":me=j;break;case"children":break;case"dangerouslySetInnerHTML":if(j!=null)throw Error(s(91));break;default:j!==I&&yt(p,$,C,j,v,I)}ys(p,de,me);return;case"option":for(var Ae in _)de=_[Ae],_.hasOwnProperty(Ae)&&de!=null&&!v.hasOwnProperty(Ae)&&(Ae==="selected"?p.selected=!1:yt(p,$,Ae,null,v,de));for(J in v)de=v[J],me=_[J],v.hasOwnProperty(J)&&de!==me&&(de!=null||me!=null)&&(J==="selected"?p.selected=de&&typeof de!="function"&&typeof de!="symbol":yt(p,$,J,de,v,me));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var Oe in _)de=_[Oe],_.hasOwnProperty(Oe)&&de!=null&&!v.hasOwnProperty(Oe)&&yt(p,$,Oe,null,v,de);for(ue in v)if(de=v[ue],me=_[ue],v.hasOwnProperty(ue)&&de!==me&&(de!=null||me!=null))switch(ue){case"children":case"dangerouslySetInnerHTML":if(de!=null)throw Error(s(137,$));break;default:yt(p,$,ue,de,v,me)}return;default:if(ls($)){for(var vt in _)de=_[vt],_.hasOwnProperty(vt)&&de!==void 0&&!v.hasOwnProperty(vt)&&Xl(p,$,vt,void 0,v,de);for(he in v)de=v[he],me=_[he],!v.hasOwnProperty(he)||de===me||de===void 0&&me===void 0||Xl(p,$,he,de,v,me);return}}for(var re in _)de=_[re],_.hasOwnProperty(re)&&de!=null&&!v.hasOwnProperty(re)&&yt(p,$,re,null,v,de);for(fe in v)de=v[fe],me=_[fe],!v.hasOwnProperty(fe)||de===me||de==null&&me==null||yt(p,$,fe,de,v,me)}function um(p){switch(p){case"css":case"script":case"font":case"img":case"image":case"input":case"link":return!0;default:return!1}}function sp(){if(typeof performance.getEntriesByType=="function"){for(var p=0,$=0,_=performance.getEntriesByType("resource"),v=0;v<_.length;v++){var j=_[v],I=j.transferSize,C=j.initiatorType,Y=j.duration;if(I&&Y&&um(C)){for(C=0,Y=j.responseEnd,v+=1;v<_.length;v++){var J=_[v],ue=J.startTime;if(ue>Y)break;var he=J.transferSize,fe=J.initiatorType;he&&um(fe)&&(J=J.responseEnd,C+=he*(J<Y?1:(Y-ue)/(J-ue)))}if(--v,$+=8*(I+C)/(j.duration/1e3),p++,10<p)break}}if(0<p)return $/p/1e6}return navigator.connection&&(p=navigator.connection.downlink,typeof p=="number")?p:5}var Zl=null,Jl=null;function ni(p){return p.nodeType===9?p:p.ownerDocument}function dm(p){switch(p){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function mm(p,$){if(p===0)switch($){case"svg":return 1;case"math":return 2;default:return 0}return p===1&&$==="foreignObject"?0:p}function ec(p,$){return p==="textarea"||p==="noscript"||typeof $.children=="string"||typeof $.children=="number"||typeof $.children=="bigint"||typeof $.dangerouslySetInnerHTML=="object"&&$.dangerouslySetInnerHTML!==null&&$.dangerouslySetInnerHTML.__html!=null}var tc=null;function op(){var p=window.event;return p&&p.type==="popstate"?p===tc?!1:(tc=p,!0):(tc=null,!1)}var hm=typeof setTimeout=="function"?setTimeout:void 0,rp=typeof clearTimeout=="function"?clearTimeout:void 0,pm=typeof Promise=="function"?Promise:void 0,ap=typeof queueMicrotask=="function"?queueMicrotask:typeof pm<"u"?function(p){return pm.resolve(null).then(p).catch(ip)}:hm;
vendor: 11,319 bytes, line 10
10function ip(p){setTimeout(function(){throw p})}function fo(p){return p==="head"}function fm(p,$){var _=$,v=0;do{var j=_.nextSibling;if(p.removeChild(_),j&&j.nodeType===8)if(_=j.data,_==="/$"||_==="/&"){if(v===0){p.removeChild(j),_r($);return}v--}else if(_==="$"||_==="$?"||_==="$~"||_==="$!"||_==="&")v++;else if(_==="html")sa(p.ownerDocument.documentElement);else if(_==="head"){_=p.ownerDocument.head,sa(_);for(var I=_.firstChild;I;){var C=I.nextSibling,Y=I.nodeName;I[tt]||Y==="SCRIPT"||Y==="STYLE"||Y==="LINK"&&I.rel.toLowerCase()==="stylesheet"||_.removeChild(I),I=C}}else _==="body"&&sa(p.ownerDocument.body);_=j}while(_);_r($)}function $m(p,$){var _=p;p=0;do{var v=_.nextSibling;if(_.nodeType===1?$?(_._stashedDisplay=_.style.display,_.style.display="none"):(_.style.display=_._stashedDisplay||"",_.getAttribute("style")===""&&_.removeAttribute("style")):_.nodeType===3&&($?(_._stashedText=_.nodeValue,_.nodeValue=""):_.nodeValue=_._stashedText||""),v&&v.nodeType===8)if(_=v.data,_==="/$"){if(p===0)break;p--}else _!=="$"&&_!=="$?"&&_!=="$~"&&_!=="$!"||p++;_=v}while(_)}function nc(p){var $=p.firstChild;for($&&$.nodeType===10&&($=$.nextSibling);$;){var _=$;switch($=$.nextSibling,_.nodeName){case"HTML":case"HEAD":case"BODY":nc(_),Le(_);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if(_.rel.toLowerCase()==="stylesheet")continue}p.removeChild(_)}}function lp(p,$,_,v){for(;p.nodeType===1;){var j=_;if(p.nodeName.toLowerCase()!==$.toLowerCase()){if(!v&&(p.nodeName!=="INPUT"||p.type!=="hidden"))break}else if(v){if(!p[tt])switch($){case"meta":if(!p.hasAttribute("itemprop"))break;return p;case"link":if(I=p.getAttribute("rel"),I==="stylesheet"&&p.hasAttribute("data-precedence"))break;if(I!==j.rel||p.getAttribute("href")!==(j.href==null||j.href===""?null:j.href)||p.getAttribute("crossorigin")!==(j.crossOrigin==null?null:j.crossOrigin)||p.getAttribute("title")!==(j.title==null?null:j.title))break;return p;case"style":if(p.hasAttribute("data-precedence"))break;return p;case"script":if(I=p.getAttribute("src"),(I!==(j.src==null?null:j.src)||p.getAttribute("type")!==(j.type==null?null:j.type)||p.getAttribute("crossorigin")!==(j.crossOrigin==null?null:j.crossOrigin))&&I&&p.hasAttribute("async")&&!p.hasAttribute("itemprop"))break;return p;default:return p}}else if($==="input"&&p.type==="hidden"){var I=j.name==null?null:""+j.name;if(j.type==="hidden"&&p.getAttribute("name")===I)return p}else return p;if(p=os(p.nextSibling),p===null)break}return null}function cp(p,$,_){if($==="")return null;for(;p.nodeType!==3;)if((p.nodeType!==1||p.nodeName!=="INPUT"||p.type!=="hidden")&&!_||(p=os(p.nextSibling),p===null))return null;return p}function gm(p,$){for(;p.nodeType!==8;)if((p.nodeType!==1||p.nodeName!=="INPUT"||p.type!=="hidden")&&!$||(p=os(p.nextSibling),p===null))return null;return p}function sc(p){return p.data==="$?"||p.data==="$~"}function oc(p){return p.data==="$!"||p.data==="$?"&&p.ownerDocument.readyState!=="loading"}function up(p,$){var _=p.ownerDocument;if(p.data==="$~")p._reactRetry=$;else if(p.data!=="$?"||_.readyState!=="loading")$();else{var v=function(){$(),_.removeEventListener("DOMContentLoaded",v)};_.addEventListener("DOMContentLoaded",v),p._reactRetry=v}}function os(p){for(;p!=null;p=p.nextSibling){var $=p.nodeType;if($===1||$===3)break;if($===8){if($=p.data,$==="$"||$==="$!"||$==="$?"||$==="$~"||$==="&"||$==="F!"||$==="F")break;if($==="/$"||$==="/&")return null}}return p}var rc=null;function bm(p){p=p.nextSibling;for(var $=0;p;){if(p.nodeType===8){var _=p.data;if(_==="/$"||_==="/&"){if($===0)return os(p.nextSibling);$--}else _!=="$"&&_!=="$!"&&_!=="$?"&&_!=="$~"&&_!=="&"||$++}p=p.nextSibling}return null}function _m(p){p=p.previousSibling;for(var $=0;p;){if(p.nodeType===8){var _=p.data;if(_==="$"||_==="$!"||_==="$?"||_==="$~"||_==="&"){if($===0)return p;$--}else _!=="/$"&&_!=="/&"||$++}p=p.previousSibling}return null}function wm(p,$,_){switch($=ni(_),p){case"html":if(p=$.documentElement,!p)throw Error(s(452));return p;case"head":if(p=$.head,!p)throw Error(s(453));return p;case"body":if(p=$.body,!p)throw Error(s(454));return p;default:throw Error(s(451))}}function sa(p){for(var $=p.attributes;$.length;)p.removeAttributeNode($[0]);Le(p)}var rs=new Map,xm=new Set;function si(p){return typeof p.getRootNode=="function"?p.getRootNode():p.nodeType===9?p:p.ownerDocument}var Hs=z.d;z.d={f:mp,r:hp,D:pp,C:fp,L:$p,m:gp,X:_p,S:bp,M:wp};function mp(){var p=Hs.f(),$=Ya();return p||$}function hp(p){var $=gt(p);$!==null&&$.tag===5&&$.type==="form"?Ou($):Hs.r(p)}var $r=typeof document>"u"?null:document;function ym(p,$,_){var v=$r;if(v&&typeof $=="string"&&$){var j=gn($);j='link[rel="'+p+'"][href="'+j+'"]',typeof _=="string"&&(j+='[crossorigin="'+_+'"]'),xm.has(j)||(xm.add(j),p={rel:p,crossOrigin:_,href:$},v.querySelector(j)===null&&($=v.createElement("link"),fn($,"link",p),We($),v.head.appendChild($)))}}function pp(p){Hs.D(p),ym("dns-prefetch",p,null)}function fp(p,$){Hs.C(p,$),ym("preconnect",p,$)}function $p(p,$,_){Hs.L(p,$,_);var v=$r;if(v&&p&&$){var j='link[rel="preload"][as="'+gn($)+'"]';$==="image"&&_&&_.imageSrcSet?(j+='[imagesrcset="'+gn(_.imageSrcSet)+'"]',typeof _.imageSizes=="string"&&(j+='[imagesizes="'+gn(_.imageSizes)+'"]')):j+='[href="'+gn(p)+'"]';var I=j;switch($){case"style":I=gr(p);break;case"script":I=br(p)}rs.has(I)||(p=m({rel:"preload",href:$==="image"&&_&&_.imageSrcSet?void 0:p,as:$},_),rs.set(I,p),v.querySelector(j)!==null||$==="style"&&v.querySelector(oa(I))||$==="script"&&v.querySelector(ra(I))||($=v.createElement("link"),fn($,"link",p),We($),v.head.appendChild($)))}}function gp(p,$){Hs.m(p,$);var _=$r;if(_&&p){var v=$&&typeof $.as=="string"?$.as:"script",j='link[rel="modulepreload"][as="'+gn(v)+'"][href="'+gn(p)+'"]',I=j;switch(v){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":I=br(p)}if(!rs.has(I)&&(p=m({rel:"modulepreload",href:p},$),rs.set(I,p),_.querySelector(j)===null)){switch(v){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(_.querySelector(ra(I)))return}v=_.createElement("link"),fn(v,"link",p),We(v),_.head.appendChild(v)}}}function bp(p,$,_){Hs.S(p,$,_);var v=$r;if(v&&p){var j=be(v).hoistableStyles,I=gr(p);$=$||"default";var C=j.get(I);if(!C){var Y={loading:0,preload:null};if(C=v.querySelector(oa(I)))Y.loading=5;else{p=m({rel:"stylesheet",href:p,"data-precedence":$},_),(_=rs.get(I))&&ac(p,_);var J=C=v.createElement("link");We(J),fn(J,"link",p),J._p=new Promise(function(ue,he){J.onload=ue,J.onerror=he}),J.addEventListener("load",function(){Y.loading|=1}),J.addEventListener("error",function(){Y.loading|=2}),Y.loading|=4,oi(C,$,v)}C={type:"stylesheet",instance:C,count:1,state:Y},j.set(I,C)}}}function _p(p,$){Hs.X(p,$);var _=$r;if(_&&p){var v=be(_).hoistableScripts,j=br(p),I=v.get(j);I||(I=_.querySelector(ra(j)),I||(p=m({src:p,async:!0},$),($=rs.get(j))&&ic(p,$),I=_.createElement("script"),We(I),fn(I,"link",p),_.head.appendChild(I)),I={type:"script",instance:I,count:1,state:null},v.set(j,I))}}function wp(p,$){Hs.M(p,$);var _=$r;if(_&&p){var v=be(_).hoistableScripts,j=br(p),I=v.get(j);I||(I=_.querySelector(ra(j)),I||(p=m({src:p,async:!0,type:"module"},$),($=rs.get(j))&&ic(p,$),I=_.createElement("script"),We(I),fn(I,"link",p),_.head.appendChild(I)),I={type:"script",instance:I,count:1,state:null},v.set(j,I))}}function vm(p,$,_,v){var j=(j=te.current)?si(j):null;if(!j)throw Error(s(446));switch(p){case"meta":case"title":return null;case"style":return typeof _.precedence=="string"&&typeof _.href=="string"?($=gr(_.href),_=be(j).hoistableStyles,v=_.get($),v||(v={type:"style",instance:null,count:0,state:null},_.set($,v)),v):{type:"void",instance:null,count:0,state:null};case"link":if(_.rel==="stylesheet"&&typeof _.href=="string"&&typeof _.precedence=="string"){p=gr(_.href);var I=be(j).hoistableStyles,C=I.get(p);if(C||(j=j.ownerDocument||j,C={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},I.set(p,C),(I=j.querySelector(oa(p)))&&!I._p&&(C.instance=I,C.state.loading=5),rs.has(p)||(_={rel:"preload",as:"style",href:_.href,crossOrigin:_.crossOrigin,integrity:_.integrity,media:_.media,hrefLang:_.hrefLang,referrerPolicy:_.referrerPolicy},rs.set(p,_),I||xp(j,p,_,C.state))),$&&v===null)throw Error(s(528,""));return C}if($&&v!==null)throw Error(s(529,""));return null;case"script":return $=_.async,_=_.src,typeof _=="string"&&$&&typeof $!="function"&&typeof $!="symbol"?($=br(_),_=be(j).hoistableScripts,v=_.get($),v||(v={type:"script",instance:null,count:0,state:null},_.set($,v)),v):{type:"void",instance:null,count:0,state:null};default:throw Error(s(444,p))}}function gr(p){return'href="'+gn(p)+'"'}function oa(p){return'link[rel="stylesheet"]['+p+"]"}function Em(p){return m({},p,{"data-precedence":p.precedence,precedence:null})}function xp(p,$,_,v){p.querySelector('link[rel="preload"][as="style"]['+$+"]")?v.loading=1:($=p.createElement("link"),v.preload=$,$.addEventListener("load",function(){return v.loading|=1}),$.addEventListener("error",function(){return v.loading|=2}),fn($,"link",_),We($),p.head.appendChild($))}function br(p){return'[src="'+gn(p)+'"]'}function ra(p){return"script[async]"+p}function km(p,$,_){if($.count++,$.instance===null)switch($.type){case"style":var v=p.querySelector('style[data-href~="'+gn(_.href)+'"]');if(v)return $.instance=v,We(v),v;var j=m({},_,{"data-href":_.href,"data-precedence":_.precedence,href:null,precedence:null});return v=(p.ownerDocument||p).createElement("style"),We(v),fn(v,"style",j),oi(v,_.precedence,p),$.instance=v;case"stylesheet":j=gr(_.href);var I=p.querySelector(oa(j));if(I)return $.state.loading|=4,$.instance=I,We(I),I;v=Em(_),(j=rs.get(j))&&ac(v,j),I=(p.ownerDocument||p).createElement("link"),We(I);var C=I;return C._p=new Promise(function(Y,J){C.onload=Y,C.onerror=J}),fn(I,"link",v),$.state.loading|=4,oi(I,_.precedence,p),$.instance=I;case"script":return I=br(_.src),(j=p.querySelector(ra(I)))?($.instance=j,We(j),j):(v=_,(j=rs.get(I))&&(v=m({},_),ic(v,j)),p=p.ownerDocument||p,j=p.createElement("script"),We(j),fn(j,"link",v),p.head.appendChild(j),$.instance=j);case"void":return null;default:throw Error(s(443,$.type))}else $.type==="stylesheet"&&($.state.loading&4)===0&&(v=$.instance,$.state.loading|=4,oi(v,_.precedence,p));return $.instance}function oi(p,$,_){for(var v=_.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),j=v.length?v[v.length-1]:null,I=j,C=0;C<v.length;C++){var Y=v[C];if(Y.dataset.precedence===$)I=Y;else if(I!==j)break}I?I.parentNode.insertBefore(p,I.nextSibling):($=_.nodeType===9?_.head:_,$.insertBefore(p,$.firstChild))}function ac(p,$){p.crossOrigin==null&&(p.crossOrigin=$.crossOrigin),p.referrerPolicy==null&&(p.referrerPolicy=$.referrerPolicy),p.title==null&&(p.title=$.title)}function ic(p,$){p.crossOrigin==null&&(p.crossOrigin=$.crossOrigin),p.referrerPolicy==null&&(p.referrerPolicy=$.referrerPolicy),p.integrity==null&&(p.integrity=$.integrity)}var ri=null;function Tm(p,$,_){if(ri===null){var v=new Map,j=ri=new Map;j.set(_,v)}else j=ri,v=j.get(_),v||(v=new Map,j.set(_,v));if(v.has(p))return v;for(v.set(p,null),_=_.getElementsByTagName(p),j=0;j<_.length;j++){var I=_[j];if(!(I[tt]||I[rt]||p==="link"&&I.getAttribute("rel")==="stylesheet")&&I.namespaceURI!=="http://www.w3.org/2000/svg"){var 
vendor: 5,865 bytes, line 10
10C=I.getAttribute($)||"";C=p+C;var Y=v.get(C);Y?Y.push(I):v.set(C,[I])}}return v}function Sm(p,$,_){p=p.ownerDocument||p,p.head.insertBefore(_,$==="title"?p.querySelector("head > title"):null)}function yp(p,$,_){if(_===1||$.itemProp!=null)return!1;switch(p){case"meta":case"title":return!0;case"style":if(typeof $.precedence!="string"||typeof $.href!="string"||$.href==="")break;return!0;case"link":if(typeof $.rel!="string"||typeof $.href!="string"||$.href===""||$.onLoad||$.onError)break;return $.rel==="stylesheet"?(p=$.disabled,typeof $.precedence=="string"&&p==null):!0;case"script":if($.async&&typeof $.async!="function"&&typeof $.async!="symbol"&&!$.onLoad&&!$.onError&&$.src&&typeof $.src=="string")return!0}return!1}function Rm(p){return!(p.type==="stylesheet"&&(p.state.loading&3)===0)}function vp(p,$,_,v){if(_.type==="stylesheet"&&(typeof v.media!="string"||matchMedia(v.media).matches!==!1)&&(_.state.loading&4)===0){if(_.instance===null){var j=gr(v.href),I=$.querySelector(oa(j));if(I){$=I._p,$!==null&&typeof $=="object"&&typeof $.then=="function"&&(p.count++,p=ai.bind(p),$.then(p,p)),_.state.loading|=4,_.instance=I,We(I);return}I=$.ownerDocument||$,v=Em(v),(j=rs.get(j))&&ac(v,j),I=I.createElement("link"),We(I);var C=I;C._p=new Promise(function(Y,J){C.onload=Y,C.onerror=J}),fn(I,"link",v),_.instance=I}p.stylesheets===null&&(p.stylesheets=new Map),p.stylesheets.set(_,$),($=_.state.preload)&&(_.state.loading&3)===0&&(p.count++,_=ai.bind(p),$.addEventListener("load",_),$.addEventListener("error",_))}}var lc=0;function Ep(p,$){return p.stylesheets&&p.count===0&&li(p,p.stylesheets),0<p.count||0<p.imgCount?function(_){var v=setTimeout(function(){if(p.stylesheets&&li(p,p.stylesheets),p.unsuspend){var I=p.unsuspend;p.unsuspend=null,I()}},6e4+$);0<p.imgBytes&&lc===0&&(lc=62500*sp());var j=setTimeout(function(){if(p.waitingForImages=!1,p.count===0&&(p.stylesheets&&li(p,p.stylesheets),p.unsuspend)){var I=p.unsuspend;p.unsuspend=null,I()}},(p.imgBytes>lc?50:800)+$);return p.unsuspend=_,function(){p.unsuspend=null,clearTimeout(v),clearTimeout(j)}}:null}function ai(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)li(this,this.stylesheets);else if(this.unsuspend){var p=this.unsuspend;this.unsuspend=null,p()}}}var ii=null;function li(p,$){p.stylesheets=null,p.unsuspend!==null&&(p.count++,ii=new Map,$.forEach(kp,p),ii=null,ai.call(p))}function kp(p,$){if(!($.state.loading&4)){var _=ii.get(p);if(_)var v=_.get(null);else{_=new Map,ii.set(p,_);for(var j=p.querySelectorAll("link[data-precedence],style[data-precedence]"),I=0;I<j.length;I++){var C=j[I];(C.nodeName==="LINK"||C.getAttribute("media")!=="not all")&&(_.set(C.dataset.precedence,C),v=C)}v&&_.set(null,v)}j=$.instance,C=j.getAttribute("data-precedence"),I=_.get(C)||v,I===v&&_.set(null,j),_.set(C,j),this.count++,v=ai.bind(this),j.addEventListener("load",v),j.addEventListener("error",v),I?I.parentNode.insertBefore(j,I.nextSibling):(p=p.nodeType===9?p.head:p,p.insertBefore(j,p.firstChild)),$.state.loading|=4}}var aa={$$typeof:k,Provider:null,Consumer:null,_currentValue:K,_currentValue2:K,_threadCount:0};function Tp(p,$,_,v,j,I,C,Y,J){this.tag=1,this.containerInfo=p,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=wn(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=wn(0),this.hiddenUpdates=wn(null),this.identifierPrefix=v,this.onUncaughtError=j,this.onCaughtError=I,this.onRecoverableError=C,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=J,this.incompleteTransitions=new Map}function Am(p,$,_,v,j,I,C,Y,J,ue,he,fe){return p=new Tp(p,$,_,C,J,ue,he,fe,Y),$=1,I===!0&&($|=24),I=Hn(3,null,null,$),p.current=I,I.stateNode=p,$=Hi(),$.refCount++,p.pooledCache=$,$.refCount++,I.memoizedState={element:v,isDehydrated:_,cache:$},qi(I),p}function jm(p){return p?(p=Ko,p):Ko}function Mm(p,$,_,v,j,I){j=jm(j),v.context===null?v.context=j:v.pendingContext=j,v=so($),v.payload={element:_},I=I===void 0?null:I,I!==null&&(v.callback=I),_=oo(p,v,$),_!==null&&(Cn(_,p,$),Or(_,p,$))}function Nm(p,$){if(p=p.memoizedState,p!==null&&p.dehydrated!==null){var _=p.retryLane;p.retryLane=_!==0&&_<$?_:$}}function cc(p,$){Nm(p,$),(p=p.alternate)&&Nm(p,$)}function Wm(p){if(p.tag===13||p.tag===31){var $=So(p,67108864);$!==null&&Cn($,p,67108864),cc(p,67108864)}}function Im(p){if(p.tag===13||p.tag===31){var $=Vn();$=zn($);var _=So(p,$);_!==null&&Cn(_,p,$),cc(p,$)}}var ci=!0;function Sp(p,$,_,v){var j=O.T;O.T=null;var I=z.p;try{z.p=2,uc(p,$,_,v)}finally{z.p=I,O.T=j}}function Rp(p,$,_,v){var j=O.T;O.T=null;var I=z.p;try{z.p=8,uc(p,$,_,v)}finally{z.p=I,O.T=j}}function uc(p,$,_,v){if(ci){var j=dc(v);if(j===null)Ql(p,$,v,ui,_),Pm(p,v);else if(jp(j,p,$,_,v))v.stopPropagation();else if(Pm(p,v),$&4&&-1<Ap.indexOf(p)){for(;j!==null;){var I=gt(j);if(I!==null)switch(I.tag){case 3:if(I=I.stateNode,I.current.memoizedState.isDehydrated){var C=Xt(I.pendingLanes);if(C!==0){var Y=I;for(Y.pendingLanes|=2,Y.entangledLanes|=2;C;){var J=1<<31-ot(C);Y.entanglements[1]|=J,C&=~J}ws(I),(pt&6)===0&&(Va=ge()+500,ea(0))}}break;case 31:case 13:Y=So(I,2),Y!==null&&Cn(Y,I,2),Ya(),cc(I,2)}if(I=dc(v),I===null&&Ql(p,$,v,ui,_),I===j)break;j=I}j!==null&&v.stopPropagation()}else Ql(p,$,v,null,_)}}function dc(p){return p=dn(p),mc(p)}var ui=null;function mc(p){if(ui=null,p=He(p),p!==null){var $=r(p);if($===null)p=null;else{var _=$.tag;if(_===13){if(p=a($),p!==null)return p;p=null}else if(_===31){if(p=i($),p!==null)return p;p=null}else if(_===3){if($.stateNode.current.memoizedState.isDehydrated)return $.tag===3?$.stateNode.containerInfo:null;p=null}
vendor: 4,260 bytes, line 10
10else $!==p&&(p=null)}}return ui=p,null}function Lm(p){switch(p){case"beforetoggle":case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(Ie()){case ft:return 2;case wt:return 8;case mt:case et:return 32;case Ke:return 268435456;default:return 32}default:return 32}}var hc=!1,$o=null,go=null,bo=null,ia=new Map,la=new Map,_o=[],Ap="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function Pm(p,$){switch(p){case"focusin":case"focusout":$o=null;break;case"dragenter":case"dragleave":go=null;break;case"mouseover":case"mouseout":bo=null;break;case"pointerover":case"pointerout":ia.delete($.pointerId);break;case"gotpointercapture":case"lostpointercapture":la.delete($.pointerId)}}function ca(p,$,_,v,j,I){return p===null||p.nativeEvent!==I?(p={blockedOn:$,domEventName:_,eventSystemFlags:v,nativeEvent:I,targetContainers:[j]},$!==null&&($=gt($),$!==null&&Wm($)),p):(p.eventSystemFlags|=v,$=p.targetContainers,j!==null&&$.indexOf(j)===-1&&$.push(j),p)}function jp(p,$,_,v,j){switch($){case"focusin":return $o=ca($o,p,$,_,v,j),!0;case"dragenter":return go=ca(go,p,$,_,v,j),!0;case"mouseover":return bo=ca(bo,p,$,_,v,j),!0;case"pointerover":var I=j.pointerId;return ia.set(I,ca(ia.get(I)||null,p,$,_,v,j)),!0;case"gotpointercapture":return I=j.pointerId,la.set(I,ca(la.get(I)||null,p,$,_,v,j)),!0}return!1}function Cm(p){var $=He(p.target);if($!==null){var _=r($);if(_!==null){if($=_.tag,$===13){if($=a(_),$!==null){p.blockedOn=$,Yn(p.priority,function(){Im(_)});return}}else if($===31){if($=i(_),$!==null){p.blockedOn=$,Yn(p.priority,function(){Im(_)});return}}else if($===3&&_.stateNode.current.memoizedState.isDehydrated){p.blockedOn=_.tag===3?_.stateNode.containerInfo:null;return}}}p.blockedOn=null}function di(p){if(p.blockedOn!==null)return!1;for(var $=p.targetContainers;0<$.length;){var _=dc(p.nativeEvent);if(_===null){_=p.nativeEvent;var v=new _.constructor(_.type,_);Ft=v,_.target.dispatchEvent(v),Ft=null}else return $=gt(_),$!==null&&Wm($),p.blockedOn=_,!1;$.shift()}return!0}function Om(p,$,_){di(p)&&_.delete($)}function Mp(){hc=!1,$o!==null&&di($o)&&($o=null),go!==null&&di(go)&&(go=null),bo!==null&&di(bo)&&(bo=null),ia.forEach(Om),la.forEach(Om)}function mi(p,$){p.blockedOn===$&&(p.blockedOn=null,hc||(hc=!0,n.unstable_scheduleCallback(n.unstable_NormalPriority,Mp)))}var hi=null;function Dm(p){hi!==p&&(hi=p,n.unstable_scheduleCallback(n.unstable_NormalPriority,function(){hi===p&&(hi=null);for(var $=0;$<p.length;$+=3){var _=p[$],v=p[$+1],j=p[$+2];if(typeof v!="function"){if(mc(v||_)===null)continue;break}var I=gt(_);I!==null&&(p.splice($,3),$-=3,dl(I,{pending:!0,data:j,method:_.method,action:v},v,j))}}))}function _r(p){function $(J){return mi(J,p)}$o!==null&&mi($o,p),go!==null&&mi(go,p),bo!==null&&mi(bo,p),ia.forEach($),la.forEach($);for(var _=0;_<_o.length;_++){var v=_o[_];v.blockedOn===p&&(v.blockedOn=null)}for(;0<_o.length&&(_=_o[0],_.blockedOn===null);)Cm(_),_.blockedOn===null&&_o.shift();if(_=(p.ownerDocument||p).$$reactFormReplay,_!=null)for(v=0;v<_.length;
10v+=3){var j=_[v],I=_[v+1],C=j[At]||null;if(typeof I=="function")C||Dm(_);else if(C){var Y=null;if(I&&I.hasAttribute("formAction")){if(j=I,C=I[At]||null)Y=C.formAction;else if(mc(j)!==null)continue}else Y=C.action;typeof Y=="function"?_[v+1]=Y:(_.splice(v,3),v-=3),Dm(_)}}}function Fm(){function p(I){I.canIntercept&&I.info==="react-transition"&&I.intercept({handler:function(){return new Promise(function(C){return j=C})},focusReset:"manual",scroll:"manual"})}function $(){j!==null&&(j(),j=null),v||setTimeout(_,20)}function _(){if(!v&&!navigation.transition){var I=navigation.currentEntry;I&&I.url!=null&&navigation.navigate(I.url,{state:I.getState(),info:"react-transition",history:"replace"})}}if(typeof navigation=="object"){var v=!1,j=null;return navigation.addEventListener("navigate",p),navigation.addEventListener("navigatesuccess",$),navigation.addEventListener("navigateerror",$),setTimeout(_,100),function(){v=!0,navigation.removeEventListener("navigate",p),navigation.removeEventListener("navigatesuccess",$),navigation.removeEventListener("navigateerror",$),j!==null&&(j(),j=null)}}}function pc(p){this._internalRoot=p}pi.prototype.render=pc.prototype.render=function(p){var $=this._internalRoot;if($===null)throw Error(s(409));var _=$.current,v=Vn();Mm(_,v,p,$,null,null)},pi.prototype.unmount=pc.prototype.unmount=function(){var p=this._internalRoot;if(p!==null){this._internalRoot=null;var $=p.containerInfo;Mm(p.current,2,null,p,null,null),Ya(),$[un]=null}};function pi(p){this._internalRoot=p}pi.prototype.unstable_scheduleHydration=function(p){if(p){var $=Rn();p={blockedOn:null,target:p,priority:$};for(var _=0;_<_o.length&&$!==0&&$<_o[_].priority;_++);_o.splice(_,0,p),_===0&&Cm(p)}};var Hm=t.version;if(Hm!=="19.2.4")throw Error(s(527,Hm,"19.2.4"));z.findDOMNode=function(p){var $=p._reactInternals;if($===void 0)throw typeof p.render=="function"?Error(s(188)):(p=Object.keys(p).join(","),Error(s(268,p)));return p=u($),p=p!==null?d(p):null,p=p===null?null:p.stateNode,p};var Np={bundleType:0,version:"19.2.4",rendererPackageName:"react-dom",currentDispatcherRef:O,reconcilerVersion:"19.2.4"};if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"){var fi=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!fi.isDisabled&&fi.supportsFiber)try{lt=fi.inject(Np),ct=fi}catch{}}return reactDomClient_production.createRoot=function(p,$){if(!o(p))throw Error(s(299));var _=!1,v="",j=Yu,I=Ku,C=Qu;return $!=null&&($.unstable_strictMode===!0&&(_=!0),$.identifierPrefix!==void 0&&(v=$.identifierPrefix),$.onUncaughtError!==void 0&&(j=$.onUncaughtError),$.onCaughtError!==void 0&&(I=$.onCaughtError),$.onRecoverableError!==void 0&&(C=$.onRecoverableError)),$=Am(p,1,!1,null,null,_,v,null,j,I,C,Fm),p[un]=$.current,Kl(p),new pc($)},reactDomClient_production.hydrateRoot=function(p,$,_){if(!o(p))throw Error(s(299));var v=!1,j="",I=Yu,C=Ku,Y=Qu,J=null;return _!=null&&(_.unstable_strictMode===!0&&(v=!0),_.identifierPrefix!==void 0&&(j=_.identifierPrefix),_.onUncaughtError!==void 0&&(I=_.onUncaughtError),_.onCaughtError!==void 0&&(C=_.onCaughtError),_.onRecoverableError!==void 0&&(Y=_.onRecoverableError),_.formState!==void 0&&(J=_.formState)),$=Am(p,1,!0,$,_??null,v,j,J,I,C,Y,Fm),$.context=jm(null),_=$.current,v=Vn(),v=zn(v),j=so(v),j.callback=null,oo(_,j,v),_=v,$.current.lanes=_,Zt($,_),ws($),p[un]=$.current,Kl(p),new pi($)},reactDomClient_production.version="19.2.4",reactDomClient_production}var hasRequiredClient;function requireClient(){if(hasRequiredClient)return client.exports;hasRequiredClient=1;function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(t){console.error(t)}}return n(),client.exports=requireReactDomClient_production(),client.exports}var clientExports=requireClient();const ReactDOM=getDefaultExportFromCjs(clientExports);var define_process_env_default={},initQueue=()=>{window.va||(window.va=function(...t){window.vaq||(window.vaq=[]),window.vaq.push(t)})},name$1="@vercel/analytics",version$4="2.0.1";function isBrowser$2(){return typeof window<"u"}function detectEnvironment$1(){try{const n="production"}catch{}return"production"}function setMode(n="auto"){if(n==="auto"){window.vam=detectEnvironment$1();return}window.vam=n}function getMode$1(){return(isBrowser$2()?window.vam:detectEnvironment$1())||"production"}function isDevelopment$1(){return getMode$1()==="development"}function getScriptSrc(n){return n.scriptSrc?makeAbsolute(n.scriptSrc):isDevelopment$1()?"https://va.vercel-scripts.com/v1/script.debug.js":n.basePath?makeAbsolute(`${n.basePath}
10/insights/script.js`):"/_vercel/insights/script.js"}function loadProps(n,t){var e;let s=n;if(t)try{s={...(e=JSON.parse(t))==null?void 0:e.analytics,...n}}catch{}setMode(s.mode);const o={sdkn:name$1+(s.framework?`/${s.framework}`:""),sdkv:version$4};return s.disableAutoTrack&&(o.disableAutoTrack="1"),s.viewEndpoint&&(o.viewEndpoint=makeAbsolute(s.viewEndpoint)),s.eventEndpoint&&(o.eventEndpoint=makeAbsolute(s.eventEndpoint)),s.sessionEndpoint&&(o.sessionEndpoint=makeAbsolute(s.sessionEndpoint)),isDevelopment$1()&&s.debug===!1&&(o.debug="false"),s.dsn&&(o.dsn=s.dsn),s.endpoint?o.endpoint=s.endpoint:s.basePath&&(o.endpoint=makeAbsolute(`${s.basePath}/insights`)),{beforeSend:s.beforeSend,src:getScriptSrc(s),dataset:o}}function makeAbsolute(n){return n.startsWith("http://")||n.startsWith("https://")||n.startsWith("/")?n:`/${n}`}function inject(n={debug:!0},t){var e;if(!isBrowser$2())return;const{beforeSend:s,src:o,dataset:r}=loadProps(n,t);if(initQueue(),s&&((e=window.va)==null||e.call(window,"beforeSend",s)),document.head.querySelector(`script[src*="${o}"]`))return;const a=document.createElement("script");a.src=o;for(const[i,l]of Object.entries(r))a.dataset[i]=l;a.defer=!0,a.onerror=()=>{const i=isDevelopment$1()?"Please check if any ad blockers are enabled and try again.":"Be sure to enable Web Analytics for your project and deploy again. See https://vercel.com/docs/analytics/quickstart for more information.";console.log(`[Vercel Web Analytics] Failed to load script from ${o}. ${i}`)},document.head.appendChild(a)}function pageview({route:n,path:t}){var e;(e=window.va)==null||e.call(window,"pageview",{route:n,path:t})}function getBasePath(){if(!(typeof process>"u"||typeof define_process_env_default>"u"))return define_process_env_default.REACT_APP_VERCEL_OBSERVABILITY_BASEPATH}function getConfigString(){if(!(typeof process>"u"||typeof define_process_env_default>"u"))return define_process_env_default.REACT_APP_VERCEL_OBSERVABILITY_CLIENT_CONFIG}function Analytics(n){return reactExports.useEffect(()=>{var t;n.beforeSend&&((t=window.va)==null||t.call(window,"beforeSend",n.beforeSend))},[n.beforeSend]),reactExports.useEffect(()=>{inject({framework:n.framework||"react",basePath:n.basePath??getBasePath(),...n.route!==void 0&&{disableAutoTrack:!0},...n},n.configString??getConfigString())},[]),reactExports.useEffect(()=>{n.route&&n.path&&pageview({route:n.route,path:n.path})},[n.route,n.path]),null}function __rest(n,t){var e={};for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&t.indexOf(s)<0&&(e[s]=n[s]);if(n!=null&&typeof Object.getOwnPropertySymbols=="function")for(var o=0,s=Object.getOwnPropertySymbols(n);o<s.length;o++)t.indexOf(s[o])<0&&Object.prototype.propertyIsEnumerable.call(n,s[o])&&(e[s[o]]=n[s[o]]);return e}function __awaiter(n,t,e,s){function o(r){return r instanceof e?r:new e(function(a){a(r)})}return new(e||(e=Promise))(function(r,a){function i(d){try{u(s.next(d))}catch(m){a(m)}}function l(d){try{u(s.throw(d))}catch(m){a(m)}}function u(d){d.done?r(d.value):o(d.value).then(i,l)}u((s=s.apply(n,t||[])).next())})}typeof SuppressedError=="function"&&SuppressedError;const resolveFetch$3=n=>n?(...t)=>n(...t):(...t)=>fetch(...t);class FunctionsError extends Error{constructor(t,e="FunctionsError",s){super(t),this.name=e,this.context=s}}class FunctionsFetchError extends FunctionsError{constructor(t){super("Failed to send a request to the Edge Function","FunctionsFetchError",t)}}class FunctionsRelayError extends FunctionsError{constructor(t){super("Relay Error invoking the Edge Function","FunctionsRelayError",t)}}class FunctionsHttpError extends FunctionsError{constructor(t){super("Edge Function returned a non-2xx status code","FunctionsHttpError",t)}}var FunctionRegion;(function(n){n.Any="any",n.ApNortheast1="ap-northeast-1",n.ApNortheast2="ap-northeast-2",n.ApSouth1="ap-south-1",n.ApSoutheast1="ap-southeast-1",n.ApSoutheast2="ap-southeast-2",n.CaCentral1="ca-central-1",n.EuCentral1="eu-central-1",n.EuWest1="eu-west-1",n.EuWest2="eu-west-2",n.EuWest3="eu-west-3",n.SaEast1="sa-east-1",n.UsEast1="us-east-1",n.UsWest1="us-west-1",n.UsWest2="us-west-2"})(FunctionRegion||(FunctionRegion={}));class FunctionsClient{constructor(t,{headers:e={},customFetch:s,region:o=FunctionRegion.Any}={}){this.url=t,this.headers=e,this.region=o,this.fetch=resolveFetch$3(s)}setAuth(t){this.headers.Authorization=`Bearer ${t}`}invoke(t){return __awaiter(this,arguments,void 0,function*(e,s={}){var o;let r,a;try{const{headers:i,method:l,body:u,signal:d,timeout:m}=s;let h={},{region:f}=s;f||(f=this.region);const g=new URL(`${this.url}/${e}`);f&&f!=="any"&&(h["x-region"]=f,g.searchParams.set("forceFunctionRegion",f));let b;u&&(i&&!Object.prototype.hasOwnProperty.call(i,"Content-Type")||!i)?typeof Blob<"u"&&u instanceof Blob||u instanceof ArrayBuffer?(h["Content-Type"]="application/octet-stream",b=u):typeof u=="string"?(h["Content-Type"]="text/plain",b
10=u):typeof FormData<"u"&&u instanceof FormData?b=u:(h["Content-Type"]="application/json",b=JSON.stringify(u)):u&&typeof u!="string"&&!(typeof Blob<"u"&&u instanceof Blob)&&!(u instanceof ArrayBuffer)&&!(typeof FormData<"u"&&u instanceof FormData)?b=JSON.stringify(u):b=u;let x=d;m&&(a=new AbortController,r=setTimeout(()=>a.abort(),m),d?(x=a.signal,d.addEventListener("abort",()=>a.abort())):x=a.signal);const y=yield this.fetch(g.toString(),{method:l||"POST",headers:Object.assign(Object.assign(Object.assign({},h),this.headers),i),body:b,signal:x}).catch(A=>{throw new FunctionsFetchError(A)}),E=y.headers.get("x-relay-error");if(E&&E==="true")throw new FunctionsRelayError(y);if(!y.ok)throw new FunctionsHttpError(y);let k=((o=y.headers.get("Content-Type"))!==null&&o!==void 0?o:"text/plain").split(";")[0].trim(),T;return k==="application/json"?T=yield y.json():k==="application/octet-stream"||k==="application/pdf"?T=yield y.blob():k==="text/event-stream"?T=y:k==="multipart/form-data"?T=yield y.formData():T=yield y.text(),{data:T,error:null,response:y}}catch(i){return{data:null,error:i,response:i instanceof FunctionsHttpError||i instanceof FunctionsRelayError?i.context:void 0}}finally{r&&clearTimeout(r)}})}}var PostgrestError=class extends Error{constructor(n){super(n.message),this.name="PostgrestError",this.details=n.details,this.hint=n.hint,this.co
10de=n.code}},PostgrestBuilder=class{constructor(n){var t,e,s;this.shouldThrowOnError=!1,this.method=n.method,this.url=n.url,this.headers=new Headers(n.headers),this.schema=n.schema,this.body=n.body,this.shouldThrowOnError=(t=n.shouldThrowOnError)!==null&&t!==void 0?t:!1,this.signal=n.signal,this.isMaybeSingle=(e=n.isMaybeSingle)!==null&&e!==void 0?e:!1,this.urlLengthLimit=(s=n.urlLengthLimit)!==null&&s!==void 0?s:8e3,n.fetch?this.fetch=n.fetch:this.fetch=fetch}throwOnError(){return this.shouldThrowOnError=!0,this}setHeader(n,t){return this.headers=new Headers(this.headers),this.headers.set(n,t),this}then(n,t){var e=this;this.schema===void 0||(["GET","HEAD"].includes(this.method)?this.headers.set("Accept-Profile",this.schema):this.headers.set("Content-Profile",this.schema)),this.method!=="GET"&&this.method!=="HEAD"&&this.headers.set("Content-Type","application/json");const s=this.fetch;let o=s(this.url.toString(),{method:this.method,headers:this.headers,body:JSON.stringify(this.body),signal:this.signal}).then(async r=>{let a=null,i=null,l=null,u=r.status,d=r.statusText;if(r.ok){var m,h;if(e.method!=="HEAD"){var f;const y=await r.text();y===""||(e.headers.get("Accept")==="text/csv"||e.headers.get("Accept")&&(!((f=e.headers.get("Accept"))===null||f===void 0)&&f.includes("application/vnd.pgrst.plan+text"))?i=y:i=JSON.parse(y))}const b=(m=e.headers.get("Prefer"))===null||m===void 0?void 0:m.match(/count=(exact|planned|estimated)/),x=(h=r.headers.get("content-range"))===null||h===void 0?void 0:h.split("/");b&&x&&x.length>1&&(l=parseInt(x[1])),e.isMaybeSingle&&e.method==="GET"&&Array.isArray(i)&&(i.length>1?(a={code:"PGRST116",details:`Results contain ${i.length} rows, application/vnd.pgrst.object+json requires 1 row`,hint:null,message:"JSON object requested, multiple (or no) rows returned"},i=null,l=null,u=406,d="Not Acceptable"):i.length===1?i=i[0]:i=null)}else{var g;const b=await r.text();try{a=JSON.parse(b),Array.isArray(a)&&r.status===404&&(i=[],a=null,u=200,d="OK")}catch{r.status===404&&b===""?(u=204,d="No Content"):a={message:b}}if(a&&e.isMaybeSingle&&(!(a==null||(g=a.details)===null||g===void 0)&&g.includes("0 rows"))&&(a=null,u=200,d="OK"),a&&e.shouldThrowOnError)throw new PostgrestError(a)}return{error:a,data:i,count:l,status:u,statusText:d}});return this.shouldThrowOnError||(o=o.catch(r=>{var a;let i="",l="",u="";const d=r?.cause;if(d){var m,h,f,g;const y=(m=d?.message)!==null&&m!==void 0?m:"",E=(h=d?.code)!==null&&h!==void 0?h:"";i=`${(f=r?.name)!==null&&f!==void 0?f:"FetchError"}: ${r?.message}`,i+=`
11
12Caused by: ${(g=d?.name)!==null&&g!==void 0?g:"Error"}: ${y}`,E&&(i+=` (${E})`),d?.stack&&(i+=`
13${d.stack}`)}else{var b;i=(b=r?.stack)!==null&&b!==void 0?b:""}const x=this.url.toString().length;return r?.name==="AbortError"||r?.code==="ABORT_ERR"?(u="",l="Request was aborted (timeout or manual cancellation)",x>this.urlLengthLimit&&(l+=`. Note: Your request URL is ${x} characters, which may exceed server limits. If selecting many fields, consider using views. If filtering with large arrays (e.g., .in('id', [many IDs])), consider using an RPC function to pass values server-side.`)):(d?.name==="HeadersOverflowError"||d?.code==="UND_ERR_HEADERS_OVERFLOW")&&(u="",l="HTTP headers exceeded server limits (typically 16KB)",x>this.urlLengthLimit&&(l+=`. Your request URL is ${x} characters. If selecting many fields, consider using views. If filtering with large arrays (e.g., .in('id', [200+ IDs])), consider using an RPC function instead.`)),{error:{message:`${(a=r?.name)!==null&&a!==void 0?a:"FetchError"}: ${r?.message}`,details:i,hint:l,code:u},data:null,count:null,status:0,statusText:""}})),o.then(n,t)}returns(){return this}overrideTypes(){return this}},PostgrestTransformBuilder=class extends PostgrestBuilder{select(n){let t=!1;const e=(n??"*").split("").map(s=>/\s/.test(s)&&!t?"":(s==='"'&&(t=!t),s)).join("");return this.url.searchParams.set("select",e),this.headers.append("Prefer","return=representation"),this}order(n,{ascending:t=!0,nullsFirst:e,foreignTable:s,referencedTable:o=s}={}){const r=o?`${o}.order`:"order",a=this.url.searchParams.get(r);return this.url.searchParams.set(r,`${a?`${a},`:""}${n}.${t?"asc":"desc"}${e===void 0?"":e?".nullsfirst":".nullslast"}`),this}limit(n,{foreignTable:t,referencedTable:e=t}={}){const s=typeof e>"u"?"limit":`${e}.limit`;return this.url.searchParams.set(s,`${n}`),this}range(n,t,{foreignTable:e,referencedTable:s=e}={}){const o=typeof s>"u"?"offset":`${s}.offset`,r=typeof s>"u"?"limit":`${s}.limit`;return this.url.searchParams.set(o,`${n}`),this.url.searchParams.set(r,`${t-n+1}`),this}abortSignal(n){return this.signal=n,this}single(){return this.headers.set("Accept","application/vnd.pgrst.object+json"),this}maybeSingle(){return this.method==="GET"?this.headers.set("Accept","application/json"):this.headers.set("Accept","application/vnd.pgrst.object+json"),this.isMaybeSingle=!0,this}csv(){return this.headers.set("Accept","text/csv"),this}geojson(){return this.headers.set("Accept","application/geo+json"),this}
13explain({analyze:n=!1,verbose:t=!1,settings:e=!1,buffers:s=!1,wal:o=!1,format:r="text"}={}){var a;const i=[n?"analyze":null,t?"verbose":null,e?"settings":null,s?"buffers":null,o?"wal":null].filter(Boolean).join("|"),l=(a=this.headers.get("Accept"))!==null&&a!==void 0?a:"application/json";return this.headers.set("Accept",`application/vnd.pgrst.plan+${r}; for="${l}"; options=${i};`),r==="json"?this:this}rollback(){return this.headers.append("Prefer","tx=rollback"),this}returns(){return this}maxAffected(n){return this.headers.append("Prefer","handling=strict"),this.headers.append("Prefer",`max-affected=${n}`),this}};const PostgrestReservedCharsRegexp=new RegExp("[,()]");var PostgrestFilterBuilder=class extends PostgrestTransformBuilder{eq(n,t){return this.url.searchParams.append(n,`eq.${t}`),this}neq(n,t){return this.url.searchParams.append(n,`neq.${t}`),this}gt(n,t){return this.url.searchParams.append(n,`gt.${t}`),this}gte(n,t){return this.url.searchParams.append(n,`gte.${t}`),this}lt(n,t){return this.url.searchParams.append(n,`lt.${t}`),this}lte(n,t){return this.url.searchParams.append(n,`lte.${t}`),this}like(n,t){return this.url.searchParams.append(n,`like.${t}`),this}likeAllOf(n,t){return this.url.searchParams.append(n,`like(all).{${t.join(",")}}`),this}likeAnyOf(n,t){return this.url.searchParams.append(n,`like(any).{${t.join(",")}}`),this}ilike(n,t){return this.url.searchParams.append(n,`ilike.${t}`),this}ilikeAllOf(n,t){return this.url.searchParams.append(n,`ilike(all).{${t.join(",")}}`),this}ilikeAnyOf(n,t){return this.url.searchParams.append(n,`ilike(any).{${t.join(",")}}`),this}regexMatch(n,t){return this.url.searchParams.append(n,`match.${t}`),this}regexIMatch(n,t){return this.url.searchParams.append(n,`imatch.${t}`),this}is(n,t){return this.url.searchParams.append(n,`is.${t}`),this}isDistinct(n,t){return this.url.searchParams.append(n,`isdistinct.${t}`),this}in(n,t){const e=Array.from(new Set(t)).map(s=>typeof s=="string"&&PostgrestReservedCharsRegexp.test(s)?`"${s}"`:`${s}`).join(",");return this.url.searchParams.append(n,`in.(${e})`),this}notIn(n,t){const e=Array.from(new Set(t)).map(s=>typeof s=="string"&&PostgrestReservedCharsRegexp.test(s)?`"${s}"`:`${s}`).join(",");return this.url.searchParams.append(n,`not.in.(${e})`),this}contains(n,t){return typeof t=="string"?this.url.searchParams.append(n,`cs.${t}`):Array.isArray(t)?this.url.searchParams.append(n,`cs.{${t.join(",")}}`):this.url.searchParams.append(n,`cs.${JSON.stringify(t)}`),this}containedBy(n,t){return typeof t=="string"?this.url.searchParams.append(n,`cd.${t}`):Array.isArray(t)?this.url.searchParams.append(n,`cd.{${t.join(",")}}`):this.url.searchParams.append(n,`cd.${JSON.stringify(t)}`),this}rangeGt(n,t){return this.url.searchParams.append(n,`sr.${t}`),this}rangeGte(n,t){return this.url.searchParams.append(n,`nxl.${t}`),this}rangeLt(n,t){return this.url.searchParams.append(n,`sl.${t}`),this}rangeLte(n,t){return this.url.searchParams.append(n,`nxr.${t}`),this}rangeAdjacent(n,t){return this.url.searchParams.append(n,`adj.${t}`),this}overlaps(n,t){return typeof t=="string"?this.url.searchParams.append(n,`ov.${t}`):this.url.searchParams.append(n,`ov.{${t.join(",")}}`),this}textSearch(n,t,{config:e,type:s}={}){let o="";s==="plain"?o="pl":s==="phrase"?o="ph":s==="websearch"&&(o="w");const r=e===void 0?"":`(${e})`;return this.url.searchParams.append(n,`${o}fts${r}.${t}`),this}match(n){return Object.entries(n).forEach(([t,e])=>{this.url.searchParams.append(t,`eq.${e}`)}),this}not(n,t,e){return this.url.searchParams.append(n,`not.${t}.${e}`),this}or(n,{foreignTable:t,referencedTable:e=t}={}){const s=e?`${e}.or`:"or";return this.url.searchParams.append(s,`(${n})`),this}filter(n,t,e){return this.url.searchParams.append(n,`${t}.${e}`),this}},PostgrestQueryBuilder=class{constructor(n,{headers:t={},schema:e,fetch:s,urlLengthLimit:o=8e3}){this.url=n,this.headers=new Headers(t),this.schema=e,this.fetch=s,this.urlLengthLimit=o}cloneRequestState(){return{url:new URL(this.url.toString()),headers:new Headers(this.headers)}}select(n,t){const{head:e=!1,count:s}=t??{},o=e?"HEAD":"GET";let r=!1;const a=(n??"*").split("").map(u=>/\s/.test(u)&&!r?"":(u==='"'&&(r=!r),u)).join(""),{url:i,headers:l}=this.cloneRequestState();return i.searchParams.set("select",a),s&&l.append("Prefer",`count=${s}`),new PostgrestFilterBuilder({method:o,url:i,headers:l,schema:this.schema,fetch:this.fetch,urlLengthLimit:this.urlLengthLimit})}insert(n,{count:t,defaultToNull:e=!0}={}){var s;const o="POST",{url:r,headers:a}=this.cloneRequestState();if(t&&a.append("Prefer",`count=${t}`),e||a.append("Prefer","missing=default"),Array.isArray(n)){const i=n.reduce((l,u)=>l.concat(Object.keys(u)),[]);if(i.length>0){const l=[...new Set(i)].map(u=>`"${u}"`);r.searchParams.set("columns",l.join(","))}}return new PostgrestFilterBuilder({method:o,url:r,headers:a,schema:this.schema,body:n,fetch:(s=this.fetch)!==null&&s!==void 0?s:fetch,urlLengthLimit:this.urlLengthLimit})}upsert(n,{onConflict:t,ignoreDuplicates:e=!1,count:s,defaultToNull:o=!0}={}){var r;const a="POST",{url:i,headers:l}=this.cloneRequestState();if(l.append("Prefer",`resolution=${e?"ignore":"merge"}-duplicates`),t!==void 0&&i.searchParams.set("on_conflict",t),s&&l.append("Prefer",`count=${s}
13`),o||l.append("Prefer","missing=default"),Array.isArray(n)){const u=n.reduce((d,m)=>d.concat(Object.keys(m)),[]);if(u.length>0){const d=[...new Set(u)].map(m=>`"${m}"`);i.searchParams.set("columns",d.join(","))}}return new PostgrestFilterBuilder({method:a,url:i,headers:l,schema:this.schema,body:n,fetch:(r=this.fetch)!==null&&r!==void 0?r:fetch,urlLengthLimit:this.urlLengthLimit})}update(n,{count:t}={}){var e;const s="PATCH",{url:o,headers:r}=this.cloneRequestState();return t&&r.append("Prefer",`count=${t}`),new PostgrestFilterBuilder({method:s,url:o,headers:r,schema:this.schema,body:n,fetch:(e=this.fetch)!==null&&e!==void 0?e:fetch,urlLengthLimit:this.urlLengthLimit})}delete({count:n}={}){var t;const e="DELETE",{url:s,headers:o}=this.cloneRequestState();return n&&o.append("Prefer",`count=${n}`),new PostgrestFilterBuilder({method:e,url:s,headers:o,schema:this.schema,fetch:(t=this.fetch)!==null&&t!==void 0?t:fetch,urlLengthLimit:this.urlLengthLimit})}};function _typeof$2(n){"@babel/helpers - typeof";return _typeof$2=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$2(n)}function toPrimitive$2(n,t){if(_typeof$2(n)!="object"||!n)return n;var e=n[Symbol.toPrimitive];if(e!==void 0){var s=e.call(n,t);if(_typeof$2(s)!="object")return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(n)}function toPropertyKey$2(n){var t=toPrimitive$2(n,"string");return _typeof$2(t)=="symbol"?t:t+""}function _defineProperty$2(n,t,e){return(t=toPropertyKey$2(t))in n?Object.defineProperty(n,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):n[t]=e,n}function ownKeys$2(n,t){var e=Object.keys(n);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(n);t&&(s=s.filter(function(o){return Object.getOwnPropertyDescriptor(n,o).enumerable})),e.push.apply(e,s)}return e}function _objectSpread2$2(n){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?arguments[t]:{};t%2?ownKeys$2(Object(e),!0).forEach(function(s){_defineProperty$2(n,s,e[s])}):Object.getOwnPropertyDescriptors?Object.defineProperties(n,Object.getOwnPropertyDescriptors(e)):ownKeys$2(Object(e)).forEac
13h(function(s){Object.defineProperty(n,s,Object.getOwnPropertyDescriptor(e,s))})}return n}var PostgrestClient=class Bm{constructor(t,{headers:e={},schema:s,fetch:o,timeout:r,urlLengthLimit:a=8e3}={}){this.url=t,this.headers=new Headers(e),this.schemaName=s,this.urlLengthLimit=a;const i=o??globalThis.fetch;r!==void 0&&r>0?this.fetch=(l,u)=>{const d=new AbortController,m=setTimeout(()=>d.abort(),r),h=u?.signal;if(h){if(h.aborted)return clearTimeout(m),i(l,u);const f=()=>{clearTimeout(m),d.abort()};return h.addEventListener("abort",f,{once:!0}),i(l,_objectSpread2$2(_objectSpread2$2({},u),{},{signal:d.signal})).finally(()=>{clearTimeout(m),h.removeEventListener("abort",f)})}return i(l,_objectSpread2$2(_objectSpread2$2({},u),{},{signal:d.signal})).finally(()=>clearTimeout(m))}:this.fetch=i}from(t){if(!t||typeof t!="string"||t.trim()==="")throw new Error("Invalid relation name: relation must be a non-empty string.");return new PostgrestQueryBuilder(new URL(`${this.url}/${t}`),{headers:new Headers(this.headers),schema:this.schemaName,fetch:this.fetch,urlLengthLimit:this.urlLengthLimit})}schema(t){return new Bm(this.url,{headers:this.headers,schema:t,fetch:this.fetch,urlLengthLimit:this.urlLengthLimit})}rpc(t,e={},{head:s=!1,get:o=!1,count:r}={}){var a;let i;const l=new URL(`${this.url}/rpc/${t}`);let u;const d=f=>f!==null&&typeof f=="object"&&(!Array.isArray(f)||f.some(d)),m=s&&Object.values(e).some(d);m?(i="POST",u=e):s||o?(i=s?"HEAD":"GET",Object.entries(e).filter(([f,g])=>g!==void 0).map(([f,g])=>[f,Array.isArray(g)?`{${g.join(",")}}`:`${g}`]).forEach(([f,g])=>{l.searchParams.append(f,g)})):(i="POST",u=e);const h=new Headers(this.headers);return m?h.set("Prefer",r?`count=${r},return=minimal`:"return=minimal"):r&&h.set("Prefer",`count=${r}`),new PostgrestFilterBuilder({method:i,url:l,headers:h,schema:this.schemaName,body:u,fetch:(a=this.fetch)!==null&&a!==void 0?a:fetch,urlLengthLimit:this.urlLengthLimit})}};class WebSocketFactory{constructor(){}static detectEnvironment(){var t;if(typeof WebSocket<"u")return{type:"native",constructor:WebSocket};if(typeof globalThis<"u"&&typeof globalThis.WebSocket<"u")return{type:"native",constructor:globalThis.WebSocket};if(typeof global<"u"&&typeof global.WebSocket<"u")return{type:"native",constructor:global.WebSocket};if(typeof globalThis<"u"&&typeof globalThis.WebSocketPair<"u"&&typeof globalThis.WebSocket>"u")return{type:"cloudflare",error:"Cloudflare Workers detected. WebSocket clients are not supported in Cloudflare Workers.",workaround:"Use Cloudflare Workers WebSocket API for server-side WebSocket handling, or deploy to a different runtime."};if(typeof globalThis<"u"&&globalThis.EdgeRuntime||typeof navigator<"u"&&(!((t=navigator.userAgent)===null||t===void 0)&&t.includes("Vercel-Edge")))return{type:"unsupported",error:"Edge runtime detected (Vercel Edge/Netlify Edge). WebSockets are not supported in edge functions.",workaround:"Use serverless functions or a different deployment target for WebSocket functionality."};const e=globalThis.process;if(e){const s=e.versions;if(s&&s.node){const o=s.node,r=parseInt(o.replace(/^v/,"").split(".")[0]);return r>=22?typeof globalThis.WebSocket<"u"?{type:"native",constructor:globalThis.WebSocket}:{type:"unsupported",error:`Node.js ${r} detected but native WebSocket not found.`,workaround:"Provide a WebSocket implementation via the transport option."}:{type:"unsupported",error:`Node.js ${r} detected without native WebSocket support.`,workaround:`For Node.js < 22, install "ws" package and provide it via the transport option:
14import ws from "ws"
15new RealtimeClient(url, { transport: ws })`}}}return{type:"unsupported",error:"Unknown JavaScript runtime without WebSocket support.",workaround:"Ensure you're running in a supported environment (browser, Node.js, Deno) or provide a custom WebSocket implementation."}}static getWebSocketConstructor(){const t=this.detectEnvironment();if(t.constructor)return t.constructor;let e=t.error||"WebSocket not supported in this environment.";throw t.workaround&&(e+=`
16
17Suggested solution: ${t.workaround}`),new Error(e)}static createWebSocket(t,e){const s=this.getWebSocketConstructor();return new s(t,e)}static isWebSocketSupported(){try{const t=this.detectEnvironment();return t.type==="native"||t.type==="ws"}catch{return!1}}}const version$3="2.98.0",DEFAULT_VERSION=`realtime-js/${version$3}`,VSN_1_0_0="1.0.0",VSN_2_0_0="2.0.0",DEFAULT_VSN=VSN_2_0_0,DEFAULT_TIMEOUT=1e4,WS_CLOSE_NORMAL=1e3,MAX_PUSH_BUFFER_SIZE=100;var SOCKET_STATES;(function(n){n[n.connecting=0]="connecting",n[n.open=1]="open",n[n.closing=2]="closing",n[n.closed=3]="closed"})(SOCKET_STATES||(SOCKET_STATES={}));var CHANNEL_STATES;(function(n){n.closed="closed",n.errored="errored",n.joined="joined",n.joining="joining",n.leaving="leaving"})(CHANNEL_STATES||(CHANNEL_STATES={}));var CHANNEL_EVENTS;(function(n){n.close="phx_close",n.error="phx_error",n.join="phx_join",n.reply="phx_reply",n.leave="phx_leave",n.access_token="access_token"})(CHANNEL_EVENTS||(CHANNEL_EVENTS={}));var TRANSPORTS;(function(n){n.websocket="websocket"}
17)(TRANSPORTS||(TRANSPORTS={}));var CONNECTION_STATE;(function(n){n.Connecting="connecting",n.Open="open",n.Closing="closing",n.Closed="closed"})(CONNECTION_STATE||(CONNECTION_STATE={}));class Serializer{constructor(t){this.HEADER_LENGTH=1,this.USER_BROADCAST_PUSH_META_LENGTH=6,this.KINDS={userBroadcastPush:3,userBroadcast:4},this.BINARY_ENCODING=0,this.JSON_ENCODING=1,this.BROADCAST_EVENT="broadcast",this.allowedMetadataKeys=[],this.allowedMetadataKeys=t??[]}encode(t,e){if(t.event===this.BROADCAST_EVENT&&!(t.payload instanceof ArrayBuffer)&&typeof t.payload.event=="string")return e(this._binaryEncodeUserBroadcastPush(t));let s=[t.join_ref,t.ref,t.topic,t.event,t.payload];return e(JSON.stringify(s))}_binaryEncodeUserBroadcastPush(t){var e;return this._isArrayBuffer((e=t.payload)===null||e===void 0?void 0:e.payload)?this._encodeBinaryUserBroadcastPush(t):this._encodeJsonUserBroadcastPush(t)}_encodeBinaryUserBroadcastPush(t){var e,s;const o=(s=(e=t.payload)===null||e===void 0?void 0:e.payload)!==null&&s!==void 0?s:new ArrayBuffer(0);return this._encodeUserBroadcastPush(t,this.BINARY_ENCODING,o)}_encodeJsonUserBroadcastPush(t){var e,s;const o=(s=(e=t.payload)===null||e===void 0?void 0:e.payload)!==null&&s!==void 0?s:{},a=new TextEncoder().encode(JSON.stringify(o)).buffer;return this._encodeUserBroadcastPush(t,this.JSON_ENCODING,a)}_encodeUserBroadcastPush(t,e,s){var o,r;const a=t.topic,i=(o=t.ref)!==null&&o!==void 0?o:"",l=(r=t.join_ref)!==null&&r!==void 0?r:"",u=t.payload.event,d=this.allowedMetadataKeys?this._pick(t.payload,this.allowedMetadataKeys):{},m=Object.keys(d).length===0?"":JSON.stringify(d);if(l.length>255)throw new Error(`joinRef length ${l.length} exceeds maximum of 255`);if(i.length>255)throw new Error(`ref length ${i.length} exceeds maximum of 255`);if(a.length>255)throw new Error(`topic length ${a.length} exceeds maximum of 255`);if(u.length>255)throw new Error(`userEvent length ${u.length} exceeds maximum of 255`);if(m.length>255)throw new Error(`metadata length ${m.length} exceeds maximum of 255`);const h=this.USER_BROADCAST_PUSH_META_LENGTH+l.length+i.length+a.length+u.length+m.length,f=new ArrayBuffer(this.HEADER_LENGTH+h);let g=new DataView(f),b=0;g.setUint8(b++,this.KINDS.userBroadcastPush),g.setUint8(b++,l.length),g.setUint8(b++,i.length),g.setUint8(b++,a.length),g.setUint8(b++,u.length),g.setUint8(b++,m.length),g.setUint8(b++,e),Array.from(l,y=>g.setUint8(b++,y.charCodeAt(0))),Array.from(i,y=>g.setUint8(b++,y.charCodeAt(0))),Array.from(a,y=>g.setUint8(b++,y.charCodeAt(0))),Array.from(u,y=>g.setUint8(b++,y.charCodeAt(0))),Array.from(m,y=>g.setUint8(b++,y.charCodeAt(0)));var x=new Uint8Array(f.byteLength+s.byteLength);return x.set(new Uint8Array(f),0),x.set(new Uint8Array(s),f.byteLength),x.buffer}decode(t,e){if(this._isArrayBuffer(t)){let s=this._binaryDecode(t);return e(s)}if(typeof t=="string"){const s=JSON.parse(t),[o,r,a,i,l]=s;return e({join_ref:o,ref:r,topic:a,event:i,payload:l})}return e({})}_binaryDecode(t){const e=new DataView(t),s=e.getUint8(0),o=new TextDecoder;if(s===this.KINDS.userBroadcast)return this._decodeUserBroadcast(t,e,o)}_decodeUserBroadcast(t,e,s){const o=e.getUint8(1),r=e.getUint8(2),a=e.getUint8(3),i=e.getUint8(4);let l=this.HEADER_LENGTH+4;const u=s.decode(t.slice(l,l+o));l=l+o;const d=s.decode(t.slice(l,l+r));l=l+r;const m=s.decode(t.slice(l,l+a));l=l+a;const h=t.slice(l,t.byteLength),f=i===this.JSON_ENCODING?JSON.parse(s.decode(h)):h,g={type:this.BROADCAST_EVENT,event:d,payload:f};return a>0&&(g.meta=JSON.parse(m)),{join_ref:null,ref:null,topic:u,event:this.BROADCAST_EVENT,payload:g}}_isArrayBuffer(t){var e;return t instanceof ArrayBuffer||((e=t?.constructor)===null||e===void 0?void 0:e.name)==="ArrayBuffer"}_pick(t,e){return!t||typeof t!="object"?{}:Object.fromEntries(Object.entries(t).filter(([s])=>e.includes(s)))}}class Timer{constructor(t,e){this.callback=t,this.timerCalc=e,this.timer=void 0,this.tries=0,this.callback=t,this.timerCalc=e}reset(){this.tries=0,clearTimeout(this.timer),this.timer=void 0}scheduleTimeout(){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.tries=this.tries+1,this.callback()},this.timerCalc(this.tries+1))}}var PostgresTypes;(function(n){n.abstime="abstime",n.bool="bool",n.date="date",n.daterange="daterange",n.float4="float4",n.float8="float8",n.int2="int2",n.int4="int4",n.int4range="int4range",n.int8="int8",n.int8range="int8range",n.json="json",n.jsonb="jsonb",n.money="money",n.numeric="numeric",n.oid="oid",n.reltime="reltime",n.text="text",n.time="time",n.timestamp="timestamp",n.timestamptz="timestamptz",n.timetz="timetz",n.tsrange="tsrange",n.tstzrange="tstzrange"}
17)(PostgresTypes||(PostgresTypes={}));const convertChangeData=(n,t,e={})=>{var s;const o=(s=e.skipTypes)!==null&&s!==void 0?s:[];return t?Object.keys(t).reduce((r,a)=>(r[a]=convertColumn(a,n,t,o),r),{}):{}},convertColumn=(n,t,e,s)=>{const o=t.find(i=>i.name===n),r=o?.type,a=e[n];return r&&!s.includes(r)?convertCell(r,a):noop$1(a)},convertCell=(n,t)=>{if(n.charAt(0)==="_"){const e=n.slice(1,n.length);return toArray(t,e)}switch(n){case PostgresTypes.bool:return toBoolean(t);case PostgresTypes.float4:case PostgresTypes.float8:case PostgresTypes.int2:case PostgresTypes.int4:case PostgresTypes.int8:case PostgresTypes.numeric:case PostgresTypes.oid:return toNumber(t);case PostgresTypes.json:case PostgresTypes.jsonb:return toJson(t);case PostgresTypes.timestamp:return toTimestampString(t);case PostgresTypes.abstime:case PostgresTypes.date:case PostgresTypes.daterange:case PostgresTypes.int4range:case PostgresTypes.int8range:case PostgresTypes.money:case PostgresTypes.reltime:case PostgresTypes.text:case PostgresTypes.time:case PostgresTypes.timestamptz:case PostgresTypes.timetz:case PostgresTypes.tsrange:case PostgresTypes.tstzrange:return noop$1(t);default:return noop$1(t)}},noop$1=n=>n,toBoolean=n=>{switch(n){case"t":return!0;case"f":return!1;default:return n}},toNumber=n=>{if(typeof n=="string"){const t=parseFloat(n);if(!Number.isNaN(t))return t}return n},toJson=n=>{if(typeof n=="string")try{return JSON.parse(n)}catch{return n}return n},toArray=(n,t)=>{if(typeof n!="string")return n;const e=n.length-1,s=n[e];if(n[0]==="{"&&s==="}"){let r;const a=n.slice(1,e);try{r=JSON.parse("["+a+"]")}catch{r=a?a.split(","):[]}return r.map(i=>convertCell(t,i))}return n},toTimestampString=n=>typeof n=="string"?n.replace(" ","T"):n,httpEndpointURL=n=>{const t=new URL(n);return t.protocol=t.protocol.replace(/^ws/i,"http"),t.pathname=t.pathname.replace(/\/+$/,"").replace(/\/socket\/websocket$/i,"").replace(/\/socket$/i,"").replace(/\/websocket$/i,""),t.pathname===""||t.pathname==="/"?t.pathname="/api/broadcast":t.pathname=t.pathname+"/api/broadcast",t.href};class Push{constructor(t,e,s={},o=DEFAULT_TIMEOUT){this.channel=t,this.event=e,this.payload=s,this.timeout=o,this.sent=!1,this.timeoutTimer=void 0,this.ref="",this.receivedResp=null,this.recHooks=[],this.refEvent=null}resend(t){this.timeout=t,this._cancelRefEvent(),this.ref="",this.refEvent=null,this.receivedResp=null,this.sent=!1,this.send()}send(){this._hasReceived("timeout")||(this.startTimeout(),this.sent=!0,this.channel.socket.push({topic:this.channel.topic,event:this.event,payload:this.payload,ref:this.ref,join_ref:this.channel._joinRef()}))}updatePayload(t){this.payload=Object.assign(Object.assign({},this.payload),t)}receive(t,e){var s;return this._hasReceived(t)&&e((s=this.receivedResp)===null||s===void 0?void 0:s.response),this.recHooks.push({status:t,callback:e}),this}startTimeout(){if(this.timeoutTimer)return;this.ref=this.channel.socket._makeRef(),this.refEvent=this.channel._replyEventName(this.ref);const t=e=>{this._cancelRefEvent(),this._cancelTimeout(),this.receivedResp=e,this._matchReceive(e)};this.channel._on(this.refEvent,{},t),this.timeoutTimer=setTimeout(()=>{this.trigger("timeout",{})},this.timeout)}trigger(t,e){this.refEvent&&this.channel._trigger(this.refEvent,{status:t,response:e})}destroy(){this._cancelRefEvent(),this._cancelTimeout()}_cancelRefEvent(){this.refEvent&&this.channel._off(this.refEvent,{})}_cancelTimeout(){clearTimeout(this.timeoutTimer),this.timeoutTimer=void 0}_matchReceive({status:t,response:e}){this.recHooks.filter(s=>s.status===t).forEach(s=>s.callback(e))}_hasReceived(t){return this.receivedResp&&this.receivedResp.status===t}}var REALTIME_PRESENCE_LISTEN_EVENTS;(function(n){n.SYNC="sync",n.JOIN="join",n.LEAVE="leave"})(REALTIME_PRESENCE_LISTEN_EVENTS||(REALTIME_PRESENCE_LISTEN_EVENTS={}));class RealtimePresence{constructor(t,e){this.channel=t,this.state={},this.pendingDiffs=[],this.joinRef=null,this.enabled=!1,this.caller={onJoin:()=>{},onLeave:()=>{},onSync:()=>{}};const s=e?.events||{state:"presence_state",diff:"presence_diff"};this.channel._on(s.state,{},o=>{const{onJoin:r,onLeave:a,onSync:i}=this.caller;this.joinRef=this.channel._joinRef(),this.state=RealtimePresence.syncState(this.state,o,r,a),this.pendingDiffs.forEac
17h(l=>{this.state=RealtimePresence.syncDiff(this.state,l,r,a)}),this.pendingDiffs=[],i()}),this.channel._on(s.diff,{},o=>{const{onJoin:r,onLeave:a,onSync:i}=this.caller;this.inPendingSyncState()?this.pendingDiffs.push(o):(this.state=RealtimePresence.syncDiff(this.state,o,r,a),i())}),this.onJoin((o,r,a)=>{this.channel._trigger("presence",{event:"join",key:o,currentPresences:r,newPresences:a})}),this.onLeave((o,r,a)=>{this.channel._trigger("presence",{event:"leave",key:o,currentPresences:r,leftPresences:a})}),this.onSync(()=>{this.channel._trigger("presence",{event:"sync"})})}static syncState(t,e,s,o){const r=this.cloneDeep(t),a=this.transformState(e),i={},l={};return this.map(r,(u,d)=>{a[u]||(l[u]=d)}),this.map(a,(u,d)=>{const m=r[u];if(m){const h=d.map(x=>x.presence_ref),f=m.map(x=>x.presence_ref),g=d.filter(x=>f.indexOf(x.presence_ref)<0),b=m.filter(x=>h.indexOf(x.presence_ref)<0);g.length>0&&(i[u]=g),b.length>0&&(l[u]=b)}else i[u]=d}),this.syncDiff(r,{joins:i,leaves:l},s,o)}static syncDiff(t,e,s,o){const{joins:r,leaves:a}={joins:this.transformState(e.joins),leaves:this.transformState(e.leaves)};return s||(s=()=>{}),o||(o=()=>{}),this.map(r,(i,l)=>{var u;const d=(u=t[i])!==null&&u!==void 0?u:[];if(t[i]=this.cloneDeep(l),d.length>0){const m=t[i].map(f=>f.presence_ref),h=d.filter(f=>m.indexOf(f.presence_ref)<0);t[i].unshift(...h)}s(i,d,l)}),this.map(a,(i,l)=>{let u=t[i];if(!u)return;const d=l.map(m=>m.presence_ref);u=u.filter(m=>d.indexOf(m.presence_ref)<0),t[i]=u,o(i,u,l),u.length===0&&delete t[i]}),t}static map(t,e){return Object.getOwnPropertyNames(t).map(s=>e(s,t[s]))}static transformState(t){return t=this.cloneDeep(t),Object.getOwnPropertyNames(t).reduce((e,s)=>{const o=t[s];return"metas"in o?e[s]=o.metas.map(r=>(r.presence_ref=r.phx_ref,delete r.phx_ref,delete r.phx_ref_prev,r)):e[s]=o,e},{})}static cloneDeep(t){return JSON.parse(JSON.stringify(t))}onJoin(t){this.caller.onJoin=t}onLeave(t){this.caller.onLeave=t}onSync(t){this.caller.onSync=t}inPendingSyncState(){return!this.joinRef||this.joinRef!==this.channel._joinRef()}}var REALTIME_POSTGRES_CHANGES_LISTEN_EVENT;(function(n){n.ALL="*",n.INSERT="INSERT",n.UPDATE="UPDATE",n.DELETE="DELETE"})(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT||(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT={}));var REALTIME_LISTEN_TYPES;(function(n){n.BROADCAST="broadcast",n.PRESENCE="presence",n.POSTGRES_CHANGES="postgres_changes",n.SYSTEM="system"})(REALTIME_LISTEN_TYPES||(REALTIME_LISTEN_TYPES={}));var REALTIME_SUBSCRIBE_STATES;(function(n){n.SUBSCRIBED="SUBSCRIBED",n.TIMED_OUT="TIMED_OUT",n.CLOSED="CLOSED",n.CHANNEL_ERROR="CHANNEL_ERROR"})(REALTIME_SUBSCRIBE_STATES||(REALTIME_SUBSCRIBE_STATES={}));class RealtimeChannel{constructor(t,e={config:{}},s){var o,r;if(this.topic=t,this.params=e,this.socket=s,this.bindings={},this.state=CHANNEL_STATES.closed,this.joinedOnce=!1,this.pushBuffer=[],this.subTopic=t.replace(/^realtime:/i,""),this.params.config=Object.assign({broadcast:{ack:!1,self:!1},presence:{key:"",enabled:!1},private:!1},e.config),this.timeout=this.socket.timeout,this.joinPush=new Push(this,CHANNEL_EVENTS.join,this.params,this.timeout),this.rejoinTimer=new Timer(()=>this._rejoinUntilConnected(),this.socket.reconnectAfterMs),this.joinPush.receive("ok",()=>{this.state=CHANNEL_STATES.joined,this.rejoinTimer.reset(),this.pushBuffer.forEach(a=>a.send()),this.pushBuffer=[]}),this._onClose(()=>{this.rejoinTimer.reset(),this.socket.log("channel",`close ${this.topic} ${this._joinRef()}`),this.state=CHANNEL_STATES.closed,this.socket._remove(this)}),this._onError(a=>{this._isLeaving()||this._isClosed()||(this.socket.log("channel",`error ${this.topic}`,a),this.state=CHANNEL_STATES.errored,this.rejoinTimer.scheduleTimeout())}),this.joinPush.receive("timeout",()=>{this._isJoining()&&(this.socket.log("channel",`timeout ${this.topic}`,this.joinPush.timeout),this.state=CHANNEL_STATES.errored,this.rejoinTimer.scheduleTimeout())}),this.joinPush.receive("error",a=>{this._isLeaving()||this._isClosed()||(this.socket.log("channel",`error ${this.topic}`,a),this.state=CHANNEL_STATES.errored,this.rejoinTimer.scheduleTimeout())}),this._on(CHANNEL_EVENTS.reply,{},(a,i)=>{this._trigger(this._replyEventName(i),a)}),this.presence=new RealtimePresence(this),this.broadcastEndpointURL=httpEndpointURL(this.socket.endPoint),this.private=this.params.config.private||!1,!this.private&&(!((r=(o=this.params.config)===null||o===void 0?void 0:o.broadcast)===null||r===void 0)&&r.replay))throw`tried to use replay on public channel '${this.topic}'. It must be a private channel.`}subscribe(t,e=this.timeout){var s,o,r;if(this.socket.isConnected()||this.socket.connect(),this.state==CHANNEL_STATES.closed){const{config:{broadcast:a,presence:i,private:l}}=this.params,u=(o=(s=this.bindings.postgres_changes)===null||s===void 0?void 0:s.map(f=>f.filter))!==null&&o!==void 0?o:[],d=!!this.bindings[REALTIME_LISTEN_TYPES.PRESENCE]&&this.bindings[REALTIME_LISTEN_TYPES.PRESENCE].length>0||((r=this.params.config.presence)===null||r===void 0?void 0:r.enabled)===!0,m={},h={broadcast:a,presence:Object.assign(Object.assign({},i),{enabled:d}),postgres_changes:u,private:l};this.socket.accessTokenValue&&(m.access_token=this.socket.accessTokenValue),this._onError(f=>t?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,f)),this._onClose(()=>t?.(REALTIME_SUBSCRIBE_STATES.CLOSED)),this.updateJoinPayload(Object.assign({config:h},m)),this.joinedOnce=!0,this._rejoin(e),this.joinPush.receive("ok",async({postgres_changes:f})=>{var g;if(this.socket._isManualToken()||this.socket.setAuth(),f===void 0){t?.(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);return}else{const b=this.bindings.postgres_changes,x=(g=b?.length)!==null&&g!==void 0?g:0,y=[];for(let E=0;E<x;E++){const k=b[E],{filter:{event:T,schema:A,table:N,filter:S}}=k,M=f&&f[E];
17if(M&&M.event===T&&RealtimeChannel.isFilterValueEqual(M.schema,A)&&RealtimeChannel.isFilterValueEqual(M.table,N)&&RealtimeChannel.isFilterValueEqual(M.filter,S))y.push(Object.assign(Object.assign({},k),{id:M.id}));else{this.unsubscribe(),this.state=CHANNEL_STATES.errored,t?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,new Error("mismatch between server and client bindings for postgres changes"));return}}this.bindings.postgres_changes=y,t&&t(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);return}}).receive("error",f=>{this.state=CHANNEL_STATES.errored,t?.(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR,new Error(JSON.stringify(Object.values(f).join(", ")||"error")))}).receive("timeout",()=>{t?.(REALTIME_SUBSCRIBE_STATES.TIMED_OUT)})}return this}presenceState(){return this.presence.state}async track(t,e={}){return await this.send({type:"presence",event:"track",payload:t},e.timeout||this.timeout)}async untrack(t={}){return await this.send({type:"presence",event:"untrack"},t)}on(t,e,s){return this.state===CHANNEL_STATES.joined&&t===REALTIME_LISTEN_TYPES.PRESENCE&&(this.socket.log("channel",`resubscribe to ${this.topic} due to change in presence callbacks on joined channel`),this.unsubscribe().then(async()=>await this.subscribe())),this._on(t,e,s)}async httpSend(t,e,s={}){var o;if(e==null)return Promise.reject("Payload is required for httpSend()");const r={apikey:this.socket.apiKey?this.socket.apiKey:"","Content-Type":"application/json"};this.socket.accessTokenValue&&(r.Authorization=`Bearer ${this.socket.accessTokenValue}`);const a={method:"POST",headers:r,body:JSON.stringify({messages:[{topic:this.subTopic,event:t,payload:e,private:this.private}]})},i=await this._fetchWithTimeout(this.broadcastEndpointURL,a,(o=s.timeout)!==null&&o!==void 0?o:this.timeout);if(i.status===202)return{success:!0};let l=i.statusText;try{const u=await i.json();l=u.error||u.message||l}catch{}return Promise.reject(new Error(l))}async send(t,e={}){var s,o;if(!this._canPush()&&t.type==="broadcast"){console.warn("Realtime send() is automatically falling back to REST API. This behavior will be deprecated 
17in the future. Please use httpSend() explicitly for REST delivery.");const{event:r,payload:a}=t,i={apikey:this.socket.apiKey?this.socket.apiKey:"","Content-Type":"application/json"};this.socket.accessTokenValue&&(i.Authorization=`Bearer ${this.socket.accessTokenValue}`);const l={method:"POST",headers:i,body:JSON.stringify({messages:[{topic:this.subTopic,event:r,payload:a,private:this.private}]})};try{const u=await this._fetchWithTimeout(this.broadcastEndpointURL,l,(s=e.timeout)!==null&&s!==void 0?s:this.timeout);return await((o=u.body)===null||o===void 0?void 0:o.cancel()),u.ok?"ok":"error"}catch(u){return u.name==="AbortError"?"timed out":"error"}}else return new Promise(r=>{var a,i,l;const u=this._push(t.type,t,e.timeout||this.timeout);t.type==="broadcast"&&!(!((l=(i=(a=this.params)===null||a===void 0?void 0:a.config)===null||i===void 0?void 0:i.broadcast)===null||l===void 0)&&l.ack)&&r("ok"),u.receive("ok",()=>r("ok")),u.receive("error",()=>r("error")),u.receive("timeout",()=>r("timed out"))})}updateJoinPayload(t){this.joinPush.updatePayload(t)}unsubscribe(t=this.timeout){this.state=CHANNEL_STATES.leaving;const e=()=>{this.socket.log("channel",`leave ${this.topic}`),this._trigger(CHANNEL_EVENTS.close,"leave",this._joinRef())};this.joinPush.destroy();let s=null;return new Promise(o=>{s=new Push(this,CHANNEL_EVENTS.leave,{},t),s.receive("ok",()=>{e(),o("ok")}).receive("timeout",()=>{e(),o("timed out")}).receive("error",()=>{o("error")}),s.send(),this._canPush()||s.trigger("ok",{})}).finally(()=>{s?.destroy()})}teardown(){this.pushBuffer.forEach(t=>t.destroy()),this.pushBuffer=[],this.rejoinTimer.reset(),this.joinPush.destroy(),this.state=CHANNEL_STATES.closed,this.bindings={}}async _fetchWithTimeout(t,e,s){const o=new AbortController,r=setTimeout(()=>o.abort(),s),a=await this.socket.fetch(t,Object.assign(Object.assign({},e),{signal:o.signal}));return clearTimeout(r),a}_push(t,e,s=this.timeout){if(!this.joinedOnce)throw`tried to push '${t}' to '${this.topic}' before joining. Use channel.subscribe() before pushing events`;let o=new Push(this,t,e,s);return this._canPush()?o.send():this._addToPushBuffer(o),o}_addToPushBuffer(t){if(t.startTimeout(),this.pushBuffer.push(t),this.pushBuffer.length>MAX_PUSH_BUFFER_SIZE){const e=this.pushBuffer.shift();e&&(e.destroy(),this.socket.log("channel",`discarded push due to buffer overflow: ${e.event}`,e.payload))}}_onMessage(t,e,s){return e}_isMember(t){return this.topic===t}_joinRef(){return this.joinPush.ref}_trigger(t,e,s){var o,r;const a=t.toLocaleLowerCase(),{close:i,error:l,leave:u,join:d}=CHANNEL_EVENTS;if(s&&[i,l,u,d].indexOf(a)>=0&&s!==this._joinRef())return;let h=this._onMessage(a,e,s);if(e&&!h)throw"channel onMessage callbacks must return the payload, modified or unmodified";["insert","update","delete"].includes(a)?(o=this.bindings.postgres_changes)===null||o===void 0||o.filter(f=>{var g,b,x;return((g=f.filter)===null||g===void 0?void 0:g.event)==="*"||((x=(b=f.filter)===null||b===void 0?void 0:b.event)===null||x===void 0?void 0:x.toLocaleLowerCase())===a}).map(f=>f.callback(h,s)):(r=this.bindings[a])===null||r===void 0||r.filter(f=>{var g,b,x,y,E,k;if(["broadcast","presence","postgres_changes"].includes(a))if("id"in f){const T=f.id,A=(g=f.filter)===null||g===void 0?void 0:g.event;return T&&((b=e.ids)===null||b===void 0?void 0:b.includes(T))&&(A==="*"||A?.toLocaleLowerCase()===((x=e.data)===null||x===void 0?void 0:x.type.toLocaleLowerCase()))}else{const T=(E=(y=f?.filter)===null||y===void 0?void 0:y.event)===null||E===void 0?void 0:E.toLocaleLowerCase();return T==="*"||T===((k=e?.event)===null||k===void 0?void 0:k.toLocaleLowerCase())}else return f.type.toLocaleLowerCase()===a}).map(f=>{if(typeof h=="object"&&"ids"in h){const g=h.data,{schema:b,table:x,commit_timestamp:y,type:E,errors:k}=g;h=Object.assign(Object.assign({},{schema:b,table:x,commit_timestamp:y,eventType:E,new:{},old:{},errors:k}),this._getPayloadRecords(g))}f.callback(h,s)})}_isClosed(){return this.state===CHANNEL_STATES.closed}_isJoined(){return this.state===CHANNEL_STATES.joined}_isJoining(){return this.state===CHANNEL_STATES.joining}_isLeaving(){return this.state===CHANNEL_STATES.leaving}_replyEventName(t){return`chan_reply_${t}`}_on(t,e,s){const o=t.toLocaleLowerCase(),r={type:o,filter:e,callback:s};return this.bindings[o]?this.bindings[o].push(r):this.bindings[o]=[r],this}_off(t,e){const s=t.toLocaleLowerCase();return this.bindings[s]&&(this.bindings[s]=this.bindings[s].filter(o=>{var r;return!(((r=o.type)===null||r===void 0?void 0:r.toLocaleLowerCase())===s&&RealtimeChannel.isEqual(o.filter,e))})),this}static isEqual(t,e){if(Object.keys(t).length!==Object.keys(e).length)return!1;for(const s in t)if(t[s]!==e[s])return!1;return!0}static isFilterValueEqual(t,e){return(t??void 0)===(e??void 0)}_rejoinUntilConnected(){this.rejoinTimer.scheduleTimeout(),this.socket.isConnected()&&this._rejoin()}_onClose(t){this._on(CHANNEL_EVENTS.close,{},t)}_onError(t){this._on(CHANNEL_EVENTS.error,{},e=>t(e))}_canPush(){return this.socket.isConnected()&&this._isJoined()}_rejoin(t=this.timeout){this._isLeaving()||(this.socket._leaveOpenTopic(this.topic),this.state=CHANNEL_STATES.joining,this.joinPush.resend(t))}_getPayloadRecords(t){const e={new:{},old:{}};return(t.type==="INSERT"||t.type==="UPDATE")&&(e.new=convertChangeData(t.columns,t.record)),(t.type==="UPDATE"||t.type==="DELETE")&&(e.old=convertChangeData(t.columns,t.old_record)),e}}const noop=()=>{},CONNECTION_TIMEOUTS={HEARTBEAT_INTERVAL:25e3,RECONNECT_DELAY:10,HEARTBEAT_TIMEOUT_FALLBACK:100},RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4],DEFAULT_RECONNECT_FALLBACK=1e4,WORKER_SCRIPT=`
18  addEventListener("message", (e) => {
19    if (e.data.event === "start") {
20      setInterval(() => postMessage({ event: "keepAlive" }), e.data.interval);
21    }
22  });`;class RealtimeClient{constructor(t,e){var s;if(this.accessTokenValue=null,this.apiKey=null,this._manuallySetToken=!1,this.channels=new Array,this.endPoint="",this.httpEndpoint="",this.headers={},this.params={},this.timeout=DEFAULT_TIMEOUT,this.transport=null,this.heartbeatIntervalMs=CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL,this.heartbeatTimer=void 0,this.pendingHeartbeatRef=null,this.heartbeatCallback=noop,this.ref=0,this.reconnectTimer=null,this.vsn=DEFAULT_
22VSN,this.logger=noop,this.conn=null,this.sendBuffer=[],this.serializer=new Serializer,this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.accessToken=null,this._connectionState="disconnected",this._wasManualDisconnect=!1,this._authPromise=null,this._heartbeatSentAt=null,this._resolveFetch=o=>o?(...r)=>o(...r):(...r)=>fetch(...r),!(!((s=e?.params)===null||s===void 0)&&s.apikey))throw new Error("API key is required to connect to Realtime");this.apiKey=e.params.apikey,this.endPoint=`${t}/${TRANSPORTS.websocket}`,this.httpEndpoint=httpEndpointURL(t),this._initializeOptions(e),this._setupReconnectionTimer(),this.fetch=this._resolveFetch(e?.fetch)}connect(){if(!(this.isConnecting()||this.isDisconnecting()||this.conn!==null&&this.isConnected())){if(this._setConnectionState("connecting"),this.accessToken&&!this._authPromise&&this._setAuthSafely("connect"),this.transport)this.conn=new this.transport(this.endpointURL());else try{this.conn=WebSocketFactory.createWebSocket(this.endpointURL())}catch(t){this._setConnectionState("disconnected");const e=t.message;throw e.includes("Node.js")?new Error(`${e}
23
24To use Realtime in Node.js, you need to provide a WebSocket implementation:
25
26Option 1: Use Node.js 22+ which has native WebSocket support
27Option 2: Install and provide the "ws" package:
28
29  npm install ws
30
31  import ws from "ws"
32  const client = new RealtimeClient(url, {
33    ...options,
34    transport: ws
35  })`):new Error(`WebSocket not available: ${e}`)}this._setupConnectionHandlers()}}endpointURL(){return this._appendParams(this.endPoint,Object.assign({},this.params,{vsn:this.vsn}))}disconnect(t,e){if(!this.isDisconnecting())if(this._setConnectionState("disconnecting",!0),this.conn){const s=setTimeout(()=>{this._setConnectionState("disconnected")},100);this.conn.onclose=()=>{clearTimeout(s),this._setConnectionState("disconnected")},typeof this.conn.close=="function"&&(t?this.conn.close(t,e??""):this.conn.close()),this._teardownConnection()}else this._setConnectionState("disconnected")}getChannels(){return this.channels}async removeChannel(t){const e=await t.unsubscribe();return this.channels.length===0&&this.disconnect(),e}async removeAllChannels(){const t=await Promise.all(this.channels.map(e=>e.unsubscribe()));return this.channels=[],this.disconnect(),t}log(t,e,s){this.logger(t,e,s)}connectionState(){switch(this.conn&&this.conn.readyState){case SOCKET_STATES.connecting:return CONNECTION_STATE.Connecting;case SOCKET_STATES.open:return CONNECTION_STATE.Open;case SOCKET_STATES.closing:return CONNECTION_STATE.Closing;default:return CONNECTION_STATE.Closed}}isConnected(){return this.connectionState()===CONNECTION_STATE.Open}isConnecting(){return this._connectionState==="connecting"}isDisconnecting(){return this._connectionState==="disconnecting"}channel(t,e={config:{}}){const s=`realtime:${t}`,o=this.getChannels().find(r=>r.topic===s);if(o)return o;{const r=new RealtimeChannel(`realtime:${t}`,e,this);return this.channels.push(r),r}}push(t){const{topic:e,event:s,payload:o,ref:r}=t,a=()=>{this.encode(t,i=>{var l;(l=this.conn)===null||l===void 0||l.send(i)})};this.log("push",`${e} ${s} (${r})`,o),this.isConnected()?a():this.sendBuffer.push(a)}async setAuth(t=null){this._authPromise=this._performAuth(t);try{await this._authPromise}finally{this._authPromise=null}}_isManualToken(){return this._manuallySetToken}async sendHeartbeat(){var t;if(!this.isConnected()){try{this.heartbeatCallback("disconnected")}catch(e){this.log("error","error in heartbeat callback",e)}return}if(this.pendingHeartbeatRef){this.pendingHeartbeatRef=null,this._heartbeatSentAt=null,this.log("transport","heartbeat timeout. Attempting to re-establish connection");try{this.heartbeatCallback("timeout")}catch(e){this.log("error","error in heartbeat callback",e)}this._wasManualDisconnect=!1,(t=this.conn)===null||t===void 0||t.close(WS_CLOSE_NORMAL,"heartbeat timeout"),setTimeout(()=>{var e;this.isConnected()||(e=this.reconnectTimer)===null||e===void 0||e.scheduleTimeout()},CONNECTION_TIMEOUTS.HEARTBEAT_TIMEOUT_FALLBACK);return}this._heartbeatSentAt=Date.now(),this.pendingHeartbeatRef=this._makeRef(),this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:this.pendingHeartbeatRef});try{this.heartbeatCallback("sent")}catch(e){this.log("error","error in heartbeat callback",e)}this._setAuthSafely("heartbeat")}onHeartbeat(t){this.heartbeatCallback=t}flushSendBuffer(){this.isConnected()&&this.sendBuffer.length>0&&(this.sendBuffer.forEach(t=>t()),this.sendBuffer=[])}_makeRef(){let t=this.ref+1;return t===this.ref?this.ref=0:this.ref=t,this.ref.toString()}_leaveOpenTopic(t){let e=this.channels.find(s=>s.topic===t&&(s._isJoined()||s._isJoining()));e&&(this.log("transport",`leaving duplicate topic "${t}"`),e.unsubscribe())}_remove(t){this.channels=this.channels.filter(e=>e.topic!==t.topic)}_onConnMessage(t){this.decode(t.data,e=>{if(e.topic==="phoenix"&&e.event==="phx_reply"&&e.ref&&e.ref===this.pendingHeartbeatRef){const u=this._heartbeatSentAt?Date.now()-this._heartbeatSentAt:void 0;try{this.heartbeatCallback(e.payload.status==="ok"?"ok":"error",u)}catch(d){this.log("error","error in heartbeat callback",d)}this._heartbeatSentAt=null,this.pendingHeartbeatRef=null}const{topic:s,event:o,payload:r,ref:a}=e,i=a?`(${a})`:"",l=r.status||"";this.log("receive",`${l} ${s} ${o} ${i}`.trim(),r),this.channels.filter(u=>
35u._isMember(s)).forEach(u=>u._trigger(o,r,a)),this._triggerStateCallbacks("message",e)})}_clearTimer(t){var e;t==="heartbeat"&&this.heartbeatTimer?(clearInterval(this.heartbeatTimer),this.heartbeatTimer=void 0):t==="reconnect"&&((e=this.reconnectTimer)===null||e===void 0||e.reset())}_clearAllTimers(){this._clearTimer("heartbeat"),this._clearTimer("reconnect")}_setupConnectionHandlers(){this.conn&&("binaryType"in this.conn&&(this.conn.binaryType="arraybuffer"),this.conn.onopen=()=>this._onConnOpen(),this.conn.onerror=t=>this._onConnError(t),this.conn.onmessage=t=>this._onConnMessage(t),this.conn.onclose=t=>this._onConnClose(t),this.conn.readyState===SOCKET_STATES.open&&this._onConnOpen())}_teardownConnection(){if(this.conn){if(this.conn.readyState===SOCKET_STATES.open||this.conn.readyState===SOCKET_STATES.connecting)try{this.conn.close()}catch(t){this.log("error","Error closing connection",t)}this.conn.onopen=null,this.conn.onerror=null,this.conn.onmessage=null,this.conn.onclose=null,this.conn=null}this._clearAllTimers(),this._terminateWorker(),this.channels.forEach(t=>t.teardown())}_onConnOpen(){this._setConnectionState("connected"),this.log("transport",`connected to ${this.endpointURL()}`),(this._authPromise||(this.accessToken&&!this.accessTokenValue?this.setAuth():Promise.resolve())).then(()=>{this.accessTokenValue&&(this.channels.forEach(e=>{e.updateJoinPayload({access_token:this.accessTokenValue})}),this.sendBuffer=[],this.channels.forEach(e=>{e._isJoining()&&(e.joinPush.sent=!1,e.joinPush.send())})),this.flushSendBuffer()}).catch(e=>{this.log("error","error waiting for auth on connect",e),this.flushSendBuffer()}),this._clearTimer("reconnect"),this.worker?this.workerRef||this._startWorkerHeartbeat():this._startHeartbeat(),this._triggerStateCallbacks("open")}_startHeartbeat(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),this.heartbeatIntervalMs)}_startWorkerHeartbeat(){this.workerUrl?this.log("worker",`starting worker for from ${this.workerUrl}`):this.log("worker","starting default worker");const t=this._workerObjectUrl(this.workerUrl);this.workerRef=new Worker(t),this.workerRef.onerror=e=>{this.log("worker","worker error",e.message),this._terminateWorker()},this.workerRef.onmessage=e=>{e.data.event==="keepAlive"&&this.sendHeartbeat()},this.workerRef.postMessage({event:"start",interval:this.heartbeatIntervalMs})}_terminateWorker(){this.workerRef&&(this.log("worker","terminating worker"),this.workerRef.terminate(),this.workerRef=void 0)}_onConnClose(t){var e;this._setConnectionState("disconnected"),this.log("transport","close",t),this._triggerChanError(),this._clearTimer("heartbeat"),this._wasManualDisconnect||(e=this.reconnectTimer)===null||e===void 0||e.scheduleTimeout(),this._triggerStateCallbacks("close",t)}_onConnError(t){this._setConnectionState("disconnected"),this.log("transport",`${t}`),this._triggerChanError(),this._triggerStateCallbacks("error",t);try{this.heartbeatCallback("error")}catch(e){this.log("error","error in heartbeat callback",e)}}_triggerChanError(){this.channels.forEach(t=>t._trigger(CHANNEL_EVENTS.error))}_appendParams(t,e){if(Object.keys(e).length===0)return t;const s=t.match(/\?/)?"&":"?",o=new URLSearchParams(e);return`${t}${s}${o}`}_workerObjectUrl(t){let e;if(t)e=t;else{const s=new Blob([WORKER_SCRIPT],{type:"application/javascript"});e=URL.createObjectURL(s)}return e}_setConnectionState(t,e=!1){this._connectionState=t,t==="connecting"?this._wasManualDisconnect=!1:t==="disconnecting"&&(this._wasManualDisconnect=e)}async _performAuth(t=null){let e,s=!1;if(t)e=t,s=!0;else if(this.accessToken)try{e=await this.accessToken()}catch(o){this.log("error","Error fetching access token from callback",o),e=this.accessTokenValue}else e=this.accessTokenValue;s?this._manuallySetToken=!0:this.accessToken&&(this._manuallySetToken=!1),this.accessTokenValue!=e&&(this.accessTokenValue=e,this.channels.forEach(o=>{const r={access_token:e,version:DEFAULT_VERSION};e&&o.updateJoinPayload(r),o.joinedOnce&&o._isJoined()&&o._push(CHANNEL_EVENTS.access_token,{access_token:e})}))}async _waitForAuthIfNeeded(){this._authPromise&&await this._authPromise}_setAuthSafely(t="general"){this._isManualToken()||this.setAuth().catch(e=>{this.log("error",`Error setting auth in ${t}`,e)})}_triggerStateCallbacks(t,e){try{this.stateChangeCallbacks[t].forEach(s=>{try{s(e)}catch(o){this.log("error",`error in ${t} callback`,o)}})}catch(s){this.log("error",`error triggering ${t} callbacks`,s)}}_setupReconnectionTimer(){this.reconnectTimer=new Timer(async()=>{setTimeout(async()=>{await this._waitForAuthIfNeeded(),this.isConnected()||this.connect()},CONNECTION_TIMEOUTS.RECONNECT_DELAY)},this.reconnectAfterMs)}_initializeOptions(t){var e,s,o,r,a,i,l,u,d,m,h,f;switch(this.transport=(e=t?.transport)!==null&&e!==void 0?e:null,this.timeout=(s=t?.timeout)!==null&&s!==void 0?s:DEFAULT_TIMEOUT,this.heartbeatIntervalMs=(o=t?.heartbeatIntervalMs)!==null&&o!==void 0?o:CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL,this.worker=(r=t?.worker)!==null&&r!==void 0?r:!1,this.accessToken=(a=t?.accessToken)!==null&&a!==void 0?a:null,this.heartbeatCallback=(i=t?.heartbeatCallback)!==null&&i!==void 0?i:noop,this.vsn=(l=t?.vsn)!==null&&l!==void 0?l:DEFAULT_
35VSN,t?.params&&(this.params=t.params),t?.logger&&(this.logger=t.logger),(t?.logLevel||t?.log_level)&&(this.logLevel=t.logLevel||t.log_level,this.params=Object.assign(Object.assign({},this.params),{log_level:this.logLevel})),this.reconnectAfterMs=(u=t?.reconnectAfterMs)!==null&&u!==void 0?u:(g=>RECONNECT_INTERVALS[g-1]||DEFAULT_RECONNECT_FALLBACK),this.vsn){case VSN_1_0_0:this.encode=(d=t?.encode)!==null&&d!==void 0?d:((g,b)=>b(JSON.stringify(g))),this.decode=(m=t?.decode)!==null&&m!==void 0?m:((g,b)=>b(JSON.parse(g)));break;case VSN_2_0_0:this.encode=(h=t?.encode)!==null&&h!==void 0?h:this.serializer.encode.bind(this.serializer),this.decode=(f=t?.decode)!==null&&f!==void 0?f:this.serializer.decode.bind(this.serializer);break;default:throw new Error(`Unsupported serializer version: ${this.vsn}`)}if(this.worker){if(typeof window<"u"&&!window.Worker)throw new Error("Web Worker is not supported");this.workerUrl=t?.workerUrl}}}var IcebergError=class extends Error{constructor(n,t){super(n),this.name="IcebergError",this.status=t.status,this.icebergType=t.icebergType,this.icebergCode=t.icebergCode,this.details=t.details,this.isCommitStateUnknown=t.icebergType==="CommitStateUnknownException"||[500,502,504].includes(t.status)&&t.icebergType?.includes("CommitState")===!0}isNotFound(){return this.status===404}isConflict(){return this.status===409}isAuthenticationTimeout(){return this.status===419}};function buildUrl(n,t,e){const s=new URL(t,n);if(e)for(const[o,r]of Object.entries(e))r!==void 0&&s.searchParams.set(o,r);return s.toString()}async function buildAuthHeaders(n){return!n||n.type==="none"?{}:n.type==="bearer"?{Authorization:`Bearer ${n.token}`}:n.type==="header"?{[n.name]:n.value}:n.type==="custom"?await n.getHeaders():{}}function createFetchClient(n){const t=n.fetchImpl??globalThis.fetch;return{async request({method:e,path:s,query:o,body:r,headers:a}){const i=buildUrl(n.baseUrl,s,o),l=await buildAuthHeaders(n.auth),u=await t(i,{method:e,headers:{...r?{"Content-Type":"application/json"}:{},...l,...a},body:r?JSON.stringify(r):void 0}),d=await u.text(),m=(u.headers.get("content-type")||"").includes("application/json"),h=m&&d?JSON.parse(d):d;if(!u.ok){const f=m?h:void 0,g=f?.error;throw new IcebergError(g?.message??`Request failed with status ${u.status}`,{status:u.status,icebergType:g?.type,icebergCode:g?.code,details:f})}return{status:u.status,headers:u.headers,data:h}}}}function namespaceToPath(n){return n.join("�")}var NamespaceOperations=class{constructor(n,t=""){this.client=n,this.prefix=t}async listNamespaces(n){const t=n?{parent:namespaceToPath(n.namespace)}:void 0;return(await this.client.request({method:"GET",path:`${this.prefix}/namespaces`,query:t})).data.namespaces.map(s=>({namespace:s}))}async createNamespace(n,t){const e={namespace:n.namespace,properties:t?.properties};return(await this.client.request({method:"POST",path:`${this.prefix}/namespaces`,body:e})).data}async dropNamespace(n){await this.client.request({method:"DELETE",path:`${this.prefix}/namespaces/${namespaceToPath(n.namespace)}`})}async loadNamespaceMetadata(n){return{properties:(await this.client.request({method:"GET",path:`${this.prefix}/namespaces/${namespaceToPath(n.namespace)}`})).data.properties}}async namespaceExists(n){try{return await this.client.request({method:"HEAD",path:`${this.prefix}/namespaces/${namespaceToPath(n.namespace)}`}),!0}catch(t){if(t instanceof IcebergError&&t.status===404)return!1;throw t}}async createNamespaceIfNotExists(n,t){try{return await this.createNamespace(n,t)}catch(e){if(e instanceof IcebergError&&e.status===409)return;throw e}}};function namespaceToPath2(n){return n.join("�")}var TableOperations=class{constructor(n,t="",e){this.client=n,this.prefix=t,this.accessDelegation=e}async listTables(n){return(await this.client.request({method:"GET",path:`${this.prefix}/namespaces/${namespaceToPath2(n.namespace)}/tables`})).data.identifiers}async createTable(n,t){const e={};return this.accessDelegation&&(e["X-Iceberg-Access-Delegation"]=this.accessDelegation),(await this.client.request({method:"POST",path:`${this.prefix}/namespaces/${namespaceToPath2(n.namespace)}/tables`,body:t,headers:e})).data.metadata}async updateTable(n,t){const e=await this.client.request({method:"POST",path:`${this.prefix}/namespaces/${namespaceToPath2(n.namespace)}/tables/${n.name}`,body:t});return{"metadata-location":e.data["metadata-location"],metadata:e.data.metadata}}async dropTable(n,t){await this.client.request({method:"DELETE",path:`${this.prefix}/namespaces/${namespaceToPath2(n.namespace)}/tables/${n.name}`,query:{purgeRequested:String(t?.purge??!1)}})}async loadTable(n){const t={};return this.accessDelegation&&(t["X-Iceberg-Access-Delegation"]=this.accessDelegation),(await this.client.request({method:"GET",path:`${this.prefix}/namespaces/${namespaceToPath2(n.namespace)}/tables/${n.name}`,headers:t})).data.metadata}async tableExists(n){const t={};this.accessDelegation&&(t["X-Iceberg-Access-Delegation"]=this.accessDelegation);try{return await this.client.request({method:"HEAD",path:`${this.prefix}/namespaces/${namespaceToPath2(n.namespace)}/tables/${n.name}`,headers:t}),!0}catch(e){if(e instanceof IcebergError&&e.status===404)return!1;throw e}}async createTableIfNotExists(n,t){try{return await this.createTable(n,t)}catch(e){if(e instanceof IcebergError&&e.status===409)return await this.loadTable({namespace:n.namespace,name:t.name});throw e}}},IcebergRestCatalog=class{constructor(n){let t="v1";n.catalogName&&(t+=`/${n.catalogName}`);const e=n.baseUrl.endsWith("/")?n.baseUrl:`${n.baseUrl}/`;this.client=createFetchClient({baseUrl:e,auth:n.auth,fetchImpl:n.fetch}),this.accessDelegation=n.accessDelegation?.join(","),this.namespaceOps=new NamespaceOperations(this.client,t),this.tableOps=new TableOperations(this.client,t,this.accessDelegation)}async listNamespaces(n){return this.namespaceOps.listNamespaces(n)}async createNamespace(n,t){return this.namespaceOps.createNamespace(n,t)}async dropNamespace(n){await this.namespaceOps.dropNamespace(n)}async loadNamespaceMetadata(n){return this.namespaceOps.loadNamespaceMetadata(n)}async listTables(n){return this.tableOps.listTables(n)}
35async createTable(n,t){return this.tableOps.createTable(n,t)}async updateTable(n,t){return this.tableOps.updateTable(n,t)}async dropTable(n,t){await this.tableOps.dropTable(n,t)}async loadTable(n){return this.tableOps.loadTable(n)}async namespaceExists(n){return this.namespaceOps.namespaceExists(n)}async tableExists(n){return this.tableOps.tableExists(n)}async createNamespaceIfNotExists(n,t){return this.namespaceOps.createNamespaceIfNotExists(n,t)}async createTableIfNotExists(n,t){return this.tableOps.createTableIfNotExists(n,t)}},StorageError=class extends Error{constructor(n,t="storage",e,s){super(n),this.__isStorageError=!0,this.namespace=t,this.name=t==="vectors"?"StorageVectorsError":"StorageError",this.status=e,this.statusCode=s}};function isStorageError(n){return typeof n=="object"&&n!==null&&"__isStorageError"in n}var StorageApiError=class extends StorageError{constructor(n,t,e,s="storage"){super(n,s,t,e),this.name=s==="vectors"?"StorageVectorsApiError":"StorageApiError",this.status=t,this.statusCode=e}toJSON(){return{name:this.name,message:this.message,status:this.status,statusCode:this.statusCode}}},StorageUnknownError=class extends StorageError{constructor(n,t,e="storage"){super(n,e),this.name=e==="vectors"?"StorageVectorsUnknownError":"StorageUnknownError",this.originalError=t}};const resolveFetch$2=n=>n?(...t)=>n(...t):(...t)=>fetch(...t),isPlainObject=n=>{if(typeof n!="object"||n===null)return!1;const t=Object.getPrototypeOf(n);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in n)&&!(Symbol.iterator in n)},recursiveToCamel=n=>{if(Array.isArray(n))return n.map(e=>recursiveToCamel(e));if(typeof n=="function"||n!==Object(n))return n;const t={};return Object.entries(n).forEach(([e,s])=>{const o=e.replace(/([-_][a-z])/gi,r=>r.toUpperCase().replace(/[-_]/g,""));t[o]=recursiveToCamel(s)}),t},isValidBucketName=n=>!n||typeof n!="string"||n.length===0||n.length>100||n.trim()!==n||n.includes("/")||n.includes("\\")?!1:/^[\w!.\*'() &$@=;:+,?-]+$/.test(n);function _typeof$1(n){"@babel/helpers - typeof";return _typeof$1=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof$1(n)}function toPrimitive$1(n,t){if(_typeof$1(n)!="object"||!n)return n;var e=n[Symbol.toPrimitive];if(e!==void 0){var s=e.call(n,t);if(_typeof$1(s)!="object")return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(n)}function toPropertyKey$1(n){var t=toPrimitive$1(n,"string");return _typeof$1(t)=="symbol"?t:t+""}function _defineProperty$1(n,t,e){return(t=toPropertyKey$1(t))in n?Object.defineProperty(n,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):n[t]=e,n}function ownKeys$1(n,t){var e=Object.keys(n);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(n);t&&(s=s.filter(function(o){return Object.getOwnPropertyDescriptor(n,o).enumerable})),e.push.apply(e,s)}return e}function _objectSpread2$1(n){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?arguments[t]:{};t%2?ownKeys$1(Object(e),!0).forEach(function(s){_defineProperty$1(n,s,e[s])}):Object.getOwnPropertyDescriptors?Object.defineProperties(n,Object.getOwnPropertyDescriptors(e)):ownKeys$1(Object(e)).forEac
35h(function(s){Object.defineProperty(n,s,Object.getOwnPropertyDescriptor(e,s))})}return n}const _getErrorMessage$1=n=>{var t;return n.msg||n.message||n.error_description||(typeof n.error=="string"?n.error:(t=n.error)===null||t===void 0?void 0:t.message)||JSON.stringify(n)},handleError$1=async(n,t,e,s)=>{if(n&&typeof n=="object"&&"status"in n&&"ok"in n&&typeof n.status=="number"&&!e?.noResolveJson){const o=n,r=o.status||500;if(typeof o.json=="function")o.json().then(a=>{const i=a?.statusCode||a?.code||r+"";t(new StorageApiError(_getErrorMessage$1(a),r,i,s))}).catch(()=>{if(s==="vectors"){const a=r+"";t(new StorageApiError(o.statusText||`HTTP ${r} error`,r,a,s))}else{const a=r+"";t(new StorageApiError(o.statusText||`HTTP ${r} error`,r,a,s))}});else{const a=r+"";t(new StorageApiError(o.statusText||`HTTP ${r} error`,r,a,s))}}else t(new StorageUnknownError(_getErrorMessage$1(n),n,s))},_getRequestParams$1=(n,t,e,s)=>{const o={method:n,headers:t?.headers||{}};return n==="GET"||n==="HEAD"||!s?_objectSpread2$1(_objectSpread2$1({},o),e):(isPlainObject(s)?(o.headers=_objectSpread2$1({"Content-Type":"application/json"},t?.headers),o.body=JSON.stringify(s)):o.body=s,t?.duplex&&(o.duplex=t.duplex),_objectSpread2$1(_objectSpread2$1({},o),e))};async function _handleRequest$1(n,t,e,s,o,r,a){return new Promise((i,l)=>
35{n(e,_getRequestParams$1(t,s,o,r)).then(u=>{if(!u.ok)throw u;if(s?.noResolveJson)return u;if(a==="vectors"){const d=u.headers.get("content-type");if(u.headers.get("content-length")==="0"||u.status===204)return{};if(!d||!d.includes("application/json"))return{}}return u.json()}).then(u=>i(u)).catch(u=>handleError$1(u,l,s,a))})}function createFetchApi(n="storage"){return{get:async(t,e,s,o)=>_handleRequest$1(t,"GET",e,s,o,void 0,n),post:async(t,e,s,o,r)=>_handleRequest$1(t,"POST",e,o,r,s,n),put:async(t,e,s,o,r)=>_handleRequest$1(t,"PUT",e,o,r,s,n),head:async(t,e,s,o)=>_handleRequest$1(t,"HEAD",e,_objectSpread2$1(_objectSpread2$1({},s),{},{noResolveJson:!0}),o,void 0,n),remove:async(t,e,s,o,r)=>_handleRequest$1(t,"DELETE",e,o,r,s,n)}}const defaultApi=createFetchApi("storage"),{get,post,put,head,remove}=defaultApi,vectorsApi=createFetchApi("vectors");var BaseApiClient=class{constructor(n,t={},e,s="storage"){this.shouldThrowOnError=!1,this.url=n,this.headers=t,this.fetch=resolveFetch$2(e),this.namespace=s}throwOnError(){return this.shouldThrowOnError=!0,this}setHeader(n,t){return this.headers=_objectSpread2$1(_objectSpread2$1({},this.headers),{},{[n]:t}),this}async handleOperation(n){var t=this;try{return{data:await n(),error:null}}catch(e){if(t.shouldThrowOnError)throw e;if(isStorageError(e))return{data:null,error:e};throw e}}},StreamDownloadBuilder=class{constructor(n,t){this.downloadFn=n,this.shouldThrowOnError=t}then(n,t){return this.execute().then(n,t)}async execute(){var n=this;try{return{data:(await n.downloadFn()).body,error:null}}catch(t){if(n.shouldThrowOnError)throw t;if(isStorageError(t))return{data:null,error:t};throw t}}};let _Symbol$toStringTag;_Symbol$toStringTag=Symbol.toStringTag;var BlobDownloadBuilder=class{constructor(n,t){this.downloadFn=n,this.shouldThrowOnError=t,this[_Symbol$toStringTag]="BlobDownloadBuilder",this.promise=null}asStream(){return new StreamDownloadBuilder(this.downloadFn,this.shouldThrowOnError)}then(n,t){return this.getPromise().then(n,t)}catch(n){return this.getPromise().catch(n)}finally(n){return this.getPromise().finally(n)}getPromise(){return this.promise||(this.promise=this.execute()),this.promise}async execute(){var n=this;try{return{data:await(await n.downloadFn()).blob(),error:null}}catch(t){if(n.shouldThrowOnError)throw t;if(isStorageError(t))return{data:null,error:t};throw t}}};const DEFAULT_SEARCH_OPTIONS={limit:100,offset:0,sortBy:{column:"name",order:"asc"}},DEFAULT_FILE_OPTIONS={cacheControl:"3600",contentType:"text/plain;charset=UTF-8",upsert:!1};var StorageFileApi=class extends BaseApiClient{constructor(n,t={},e,s){super(n,t,s,"storage"),this.bucketId=e}async uploadOrUpdate(n,t,e,s){var o=this;return o.handleOperation(async()=>{let r;const a=_objectSpread2$1(_objectSpread2$1({},DEFAULT_FILE_OPTIONS),s);let i=_objectSpread2$1(_objectSpread2$1({},o.headers),n==="POST"&&{"x-upsert":String(a.upsert)});const l=a.metadata;typeof Blob<"u"&&e instanceof Blob?(r=new FormData,r.append("cacheControl",a.cacheControl),l&&r.append("metadata",o.encodeMetadata(l)),r.append("",e)):typeof FormData<"u"&&e instanceof FormData?(r=e,r.has("cacheControl")||r.append("cacheControl",a.cacheControl),l&&!r.has("metadata")&&r.append("metadata",o.encodeMetadata(l))):(r=e,i["cache-control"]=`max-age=${a.cacheControl}`,i["content-type"]=a.contentType,l&&(i["x-metadata"]=o.toBase64(o.encodeMetadata(l))),(typeof ReadableStream<"u"&&r instanceof ReadableStream||r&&typeof r=="object"&&"pipe"in r&&typeof r.pipe=="function")&&!a.duplex&&(a.duplex="half")),s?.headers&&(i=_objectSpread2$1(_objectSpread2$1({},i),s.headers));const u=o._removeEmptyFolders(t),d=o._getFinalPath(u),m=await(n=="PUT"?put:post)(o.fetch,`${o.url}/object/${d}`,r,_objectSpread2$1({headers:i},a?.duplex?{duplex:a.duplex}:{}));return{path:u,id:m.Id,fullPath:m.Key}})}async upload(n,t,e){return this.uploadOrUpdate("POST",n,t,e)}
35async uploadToSignedUrl(n,t,e,s){var o=this;const r=o._removeEmptyFolders(n),a=o._getFinalPath(r),i=new URL(o.url+`/object/upload/sign/${a}`);return i.searchParams.set("token",t),o.handleOperation(async()=>{let l;const u=_objectSpread2$1({upsert:DEFAULT_FILE_OPTIONS.upsert},s),d=_objectSpread2$1(_objectSpread2$1({},o.headers),{"x-upsert":String(u.upsert)});return typeof Blob<"u"&&e instanceof Blob?(l=new FormData,l.append("cacheControl",u.cacheControl),l.append("",e)):typeof FormData<"u"&&e instanceof FormData?(l=e,l.append("cacheControl",u.cacheControl)):(l=e,d["cache-control"]=`max-age=${u.cacheControl}`,d["content-type"]=u.contentType),{path:r,fullPath:(await put(o.fetch,i.toString(),l,{headers:d})).Key}})}async createSignedUploadUrl(n,t){var e=this;return e.handleOperation(async()=>{let s=e._getFinalPath(n);const o=_objectSpread2$1({},e.headers);t?.upsert&&(o["x-upsert"]="true");const r=await post(e.fetch,`${e.url}/object/upload/sign/${s}`,{},{headers:o}),a=new URL(e.url+r.url),i=a.searchParams.get("token");if(!i)throw new StorageError("No token returned by API");return{signedUrl:a.toString(),path:n,token:i}})}async update(n,t,e){return this.uploadOrUpdate("PUT",n,t,e)}
35async move(n,t,e){var s=this;return s.handleOperation(async()=>await post(s.fetch,`${s.url}/object/move`,{bucketId:s.bucketId,sourceKey:n,destinationKey:t,destinationBucket:e?.destinationBucket},{headers:s.headers}))}async copy(n,t,e){var s=this;return s.handleOperation(async()=>({path:(await post(s.fetch,`${s.url}/object/copy`,{bucketId:s.bucketId,sourceKey:n,destinationKey:t,destinationBucket:e?.destinationBucket},{headers:s.headers})).Key}))}async createSignedUrl(n,t,e){var s=this;return s.handleOperation(async()=>{let o=s._getFinalPath(n),r=await post(s.fetch,`${s.url}/object/sign/${o}`,_objectSpread2$1({expiresIn:t},e?.transform?{transform:e.transform}:{}),{headers:s.headers});const a=e?.download?`&download=${e.download===!0?"":e.download}`:"";return{signedUrl:encodeURI(`${s.url}${r.signedURL}${a}`)}})}async createSignedUrls(n,t,e){var s=this;return s.handleOperation(async()=>{const o=await post(s.fetch,`${s.url}/object/sign/${s.bucketId}`,{expiresIn:t,paths:n},{headers:s.headers}),r=e?.download?`&download=${e.download===!0?"":e.download}`:"";return o.map(a=>_objectSpread2$1(_objectSpread2$1({},a),{},{signedUrl:a.signedURL?encodeURI(`${s.url}${a.signedURL}${r}`):null}))})}download(n,t,e){const s=typeof t?.transform<"u"?"render/image/authenticated":"object",o=this.transformOptsToQueryString(t?.transform||{}),r=o?`?${o}`:"",a=this._getFinalPath(n),i=()=>get(this.fetch,`${this.url}/${s}/${a}${r}`,{headers:this.headers,noResolveJson:!0},e);return new BlobDownloadBuilder(i,this.shouldThrowOnError)}async info(n){var t=this;const e=t._getFinalPath(n);return t.handleOperation(async()=>recursiveToCamel(await get(t.fetch,`${t.url}/object/info/${e}`,{headers:t.headers})))}async exists(n){var t=this;const e=t._getFinalPath(n);try{return await head(t.fetch,`${t.url}/object/${e}`,{headers:t.headers}),{data:!0,error:null}}catch(s){if(t.shouldThrowOnError)throw s;if(isStorageError(s)&&s instanceof StorageUnknownError){const o=s.originalError;if([400,404].includes(o?.status))return{data:!1,error:s}}throw s}}getPublicUrl(n,t){const e=this._getFinalPath(n),s=[],o=t?.download?`download=${t.download===!0?"":t.download}`:"";o!==""&&s.push(o);const r=typeof t?.transform<"u"?"render/image":"object",a=this.transformOptsToQueryString(t?.transform||{});a!==""&&s.push(a);let i=s.join("&");return i!==""&&(i=`?${i}`),{data:{publicUrl:encodeURI(`${this.url}/${r}/public/${e}${i}`)}}}async remove(n){var t=this;return t.handleOperation(async()=>await remove(t.fetch,`${t.url}/object/${t.bucketId}`,{prefixes:n},{headers:t.headers}))}async list(n,t,e){var s=this;return s.handleOperation(async()=>{const o=_objectSpread2$1(_objectSpread2$1(_objectSpread2$1({},DEFAULT_SEARCH_OPTIONS),t),{},{prefix:n||""});return await post(s.fetch,`${s.url}/object/list/${s.bucketId}`,o,{headers:s.headers},e)})}async listV2(n,t){var e=this;return e.handleOperation(async()=>{const s=_objectSpread2$1({},n);return await post(e.fetch,`${e.url}/object/list-v2/${e.bucketId}`,s,{headers:e.headers},t)})}encodeMetadata(n){return JSON.stringify(n)}toBase64(n){return typeof Buffer<"u"?Buffer.from(n).toString("base64"):btoa(n)}_getFinalPath(n){return`${this.bucketId}/${n.replace(/^\/+/,"")}`}_removeEmptyFolders(n){return n.replace(/^\/|\/$/g,"").replace(/\/+/g,"/")}transformOptsToQueryString(n){const t=[];return n.width&&t.push(`width=${n.width}`),n.height&&t.push(`height=${n.height}`),n.resize&&t.push(`resize=${n.resize}`),n.format&&t.push(`format=${n.format}`),n.quality&&t.push(`quality=${n.quality}`),t.join("&")}};const version$2="2.98.0",DEFAULT_HEADERS$2={"X-Client-Info":`storage-js/${version$2}`};var StorageBucketApi=class extends BaseApiClient{constructor(n,t={},e,s){const o=new URL(n);s?.useNewHostname&&/supabase\.(co|in|red)$/.test(o.hostname)&&!o.hostname.includes("storage.supabase.")&&(o.hostname=o.hostname.replace("supabase.","storage.supabase."));const r=o.href.replace(/\/$/,""),a=_objectSpread2$1(_objectSpread2$1({},DEFAULT_HEADERS$2),t);super(r,a,e,"storage")}async listBuckets(n){var t=this;return t.handleOperation(async()=>{const e=t.listBucketOptionsToQueryString(n);return await get(t.fetch,`${t.url}/bucket${e}`,{headers:t.headers})})}async getBucket(n){var t=this;return t.handleOperation(async()=>await get(t.fetch,`${t.url}/bucket/${n}`,{headers:t.headers}))}async createBucket(n,t={public:!1}){var e=this;return e.handleOperation(async()=>await post(e.fetch,`${e.url}/bucket`,{id:n,name:n,type:t.type,public:t.public,file_size_limit:t.fileSizeLimit,allowed_mime_types:t.allowedMimeTypes},{headers:e.headers}))}async updateBucket(n,t){var e=this;return e.handleOperation(async()=>await put(e.fetch,`${e.url}/bucket/${n}`,{id:n,name:n,public:t.public,file_size_limit:t.fileSizeLimit,allowed_mime_types:t.allowedMimeTypes},{headers:e.headers}))}async emptyBucket(n){var t=this;return t.handleOperation(async()=>await post(t.fetch,`${t.url}/bucket/${n}/empty`,{},{headers:t.headers}))}async deleteBucket(n){var t=this;return t.handleOperation(async()=>await remove(t.fetch,`${t.url}/bucket/${n}`,{},{headers:t.headers}))}listBucketOptionsToQueryString(n){const t={};return n&&("limit"in n&&(t.limit=String(n.limit)),"offset"in n&&(t.offset=String(n.offset)),n.search&&(t.search=n.search),n.sortColumn&&(t.sortColumn=n.sortColumn),n.sortOrder&&(t.sortOrder=n.sortOrder)),Object.keys(t).length>0?"?"+new URLSearchParams(t).toString():""}},StorageAnalyticsClient=class extends BaseApiClient{constructor(n,t={},e){const s=n.replace(/\/$/,""),o=_objectSpread2$1(_objectSpread2$1({},DEFAULT_HEADERS$2),t);super(s,o,e,"storage")}async createBucket(n){var t=this;return t.handleOperation(async()=>await post(t.fetch,`${t.url}/bucket`,{name:n},{headers:t.headers}))}async listBuckets(n){var t=this;return t.handleOperation(async()=>{const e=new URLSearchParams;n?.limit!==void 0&&e.set("limit",n.limit.toString()),n?.offset!==void 0&&e.set("offset",n.offset.toString()),n?.sortColumn&&e.set("sortColumn",n.sortColumn),n?.sortOrder&&e.set("sortOrder",n.sortOrder),n?.search&&e.set("search",n.search);const s=e.toString(),o=s?`${t.url}/bucket?${s}`:`${t.url}/bucket`;return await get(t.fetch,o,{headers:t.headers})})}async deleteBucket(n){var t=this;return t.handleOperation(async()=>await remove(t.fetch,`${t.url}/bucket/${n}`,{},{headers:t.headers}))}from(n){var t=this;if(!isValidBucketName(n))throw new StorageError("Invalid bucket name: File, folder, and bucket names must follow AWS object key naming guidelines and should avoid the use of any other characters.");const e=new IcebergRestCatalog({baseUrl:this.url,catalogName:n,auth:{type:"custom",getHeaders:async()=>t.headers},fetch:this.fetch}),s=this.shouldThrowOnError;return new Proxy(e,{get(o,r){const a=o[r];return typeof a!="function"?a:async(...i)=>{try{return{data:await a.apply(o,i),error:null}}catch(l){if(s)throw l;return{data:null,error:l}}}}})}},VectorIndexApi=class extends BaseApiClient{constructor(n,t={},e){const s=n.replace(/\/$/,""),o=_objectSpread2$1(_objectSpread2$1({},DEFAULT_HEADERS$2),{},{"Content-Type":"application/json"},t);super(s,o,e,"vectors")}async createIndex(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/CreateIndex`,n,{headers:t.headers})||{})}async getIndex(n,t){var e=this;return e.handleOperation(async()=>await vectorsApi.post(e.fetch,`${e.url}/GetIndex`,{vectorBucketName:n,indexName:t},{headers:e.headers}))}async listIndexes(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/ListIndexes`,n,{headers:t.headers}))}async deleteIndex(n,t){var e=this;return e.handleOperation(async()=>await vectorsApi.post(e.fetch,`${e.url}/DeleteIndex`,{vectorBucketName:n,indexName:t},{headers:e.headers})||{})}},VectorDataApi=class extends BaseApiClient{constructor(n,t={},e){const s=n.replace(/\/$/,""),o=_objectSpread2$1(_objectSpread2$1({},DEFAULT_HEADERS$2),{},{"Content-Type":"application/json"},t);super(s,o,e,"vectors")}async putVectors(n){var t=this;if(n.vectors.length<1||n.vectors.length>500)throw new Error("Vector batch size must be between 1 and 500 items");return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/PutVectors`,n,{headers:t.headers})||{})}async getVectors(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/GetVectors`,n,{headers:t.headers}))}async listVectors(n){var t=this;if(n.segmentCount!==void 0){if(n.segmentCount<1||n.segmentCount>16)throw new Error("segmentCount must be between 1 and 16");
35if(n.segmentIndex!==void 0&&(n.segmentIndex<0||n.segmentIndex>=n.segmentCount))throw new Error(`segmentIndex must be between 0 and ${n.segmentCount-1}`)}return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/ListVectors`,n,{headers:t.headers}))}async queryVectors(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/QueryVectors`,n,{headers:t.headers}))}async deleteVectors(n){var t=this;if(n.keys.length<1||n.keys.length>500)throw new Error("Keys batch size must be between 1 and 500 items");return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/DeleteVectors`,n,{headers:t.headers})||{})}},VectorBucketApi=class extends BaseApiClient{constructor(n,t={},e){const s=n.replace(/\/$/,""),o=_objectSpread2$1(_objectSpread2$1({},DEFAULT_HEADERS$2),{},{"Content-Type":"application/json"},t);super(s,o,e,"vectors")}async createBucket(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/CreateVectorBucket`,{vectorBucketName:n},{headers:t.headers})||{})}async getBucket(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/GetVectorBucket`,{vectorBucketName:n},{headers:t.headers}))}async listBuckets(n={}){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/ListVectorBuckets`,n,{headers:t.headers}))}async deleteBucket(n){var t=this;return t.handleOperation(async()=>await vectorsApi.post(t.fetch,`${t.url}/DeleteVectorBucket`,{vectorBucketName:n},{headers:t.headers})||{})}},StorageVectorsClient=class extends VectorBucketApi{constructor(n,t={}){super(n,t.headers||{},t.fetch)}from(n){return new VectorBucketScope(this.url,this.headers,n,this.fetch)}async createBucket(n){var t=()=>super.createBucket,e=this;return t().call(e,n)}async getBucket(n){var t=()=>super.getBucket,e=this;return t().call(e,n)}async listBuckets(n={}){var t=()=>super.listBuckets,e=this;return t().call(e,n)}async deleteBucket(n){var t=()=>super.deleteBucket,e=this;return t().call(e,n)}},VectorBucketScope=class extends VectorIndexApi{constructor(n,t,e,s){super(n,t,s),this.vectorBucketName=e}async createIndex(n){var t=()=>super.createIndex,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName}))}async listIndexes(n={}){var t=()=>super.listIndexes,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName}))}async getIndex(n){var t=()=>super.getIndex,e=this;return t().call(e,e.vectorBucketName,n)}async deleteIndex(n){var t=()=>super.deleteIndex,e=this;return t().call(e,e.vectorBucketName,n)}index(n){return new VectorIndexScope(this.url,this.headers,this.vectorBucketName,n,this.fetch)}},VectorIndexScope=class extends VectorDataApi{constructor(n,t,e,s,o){super(n,t,o),this.vectorBucketName=e,this.indexName=s}async putVectors(n){var t=()=>super.putVectors,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName,indexName:e.indexName}))}async getVectors(n){var t=()=>super.getVectors,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName,indexName:e.indexName}))}async listVectors(n={}){var t=()=>super.listVectors,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName,indexName:e.indexName}))}async queryVectors(n){var t=()=>super.queryVectors,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName,indexName:e.indexName}))}async deleteVectors(n){var t=()=>super.deleteVectors,e=this;return t().call(e,_objectSpread2$1(_objectSpread2$1({},n),{},{vectorBucketName:e.vectorBucketName,indexName:e.indexName}))}},StorageClient=class extends StorageBucketApi{constructor(n,t={},e,s){super(n,t,e,s)}from(n){return new StorageFileApi(this.url,this.headers,n,this.fetch)}get vectors(){return new StorageVectorsClient(this.url+"/vector",{headers:this.headers,fetch:this.fetch})}get analytics(){return new StorageAnalyticsClient(this.url+"/iceberg",this.headers,this.fetch)}};const version$1="2.98.0",AUTO_REFRESH_TICK_DURATION_MS=30*1e3,AUTO_REFRESH_TICK_THRESHOLD=3,EXPIRY_MARGIN_MS=AUTO_REFRESH_TICK_THRESHOLD*AUTO_REFRESH_TICK_DURATION_MS,GOTRUE_URL="http://localhost:9999",STORAGE_KEY$2="supabase.auth.token",DEFAULT_HEADERS$1={"X-Client-Info":`gotrue-js/${version$1}`},API_VERSION_HEADER_NAME="X-Supabase-Api-Version",API_VERSIONS={"2024-01-01":{timestamp:Date.parse("2024-01-01T00:00:00.0Z"),name:"2024-01-01"}},BASE64URL_REGEX=/^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$/i,JWKS_TTL=600*1e3;
35class AuthError extends Error{constructor(t,e,s){super(t),this.__isAuthError=!0,this.name="AuthError",this.status=e,this.code=s}}function isAuthError(n){return typeof n=="object"&&n!==null&&"__isAuthError"in n}class AuthApiError extends AuthError{constructor(t,e,s){super(t,e,s),this.name="AuthApiError",this.status=e,this.code=s}}function isAuthApiError(n){return isAuthError(n)&&n.name==="AuthApiError"}class AuthUnknownError extends AuthError{constructor(t,e){super(t),this.name="AuthUnknownError",this.originalError=e}}class CustomAuthError extends AuthError{constructor(t,e,s,o){super(t,s,o),this.name=e,this.status=s}}class AuthSessionMissingError extends CustomAuthError{constructor(){super("Auth session missing!","AuthSessionMissingError",400,void 0)}}function isAuthSessionMissingError(n){return isAuthError(n)&&n.name==="AuthSessionMissingError"}class AuthInvalidTokenResponseError extends CustomAuthError{constructor(){super("Auth session or user missing","AuthInvalidTokenResponseError",500,void 0)}}class AuthInvalidCredentialsError extends CustomAuthError{constructor(t){super(t,"AuthInvalidCredentialsError",400,void 0)}}class AuthImplicitGrantRedirectError extends CustomAuthError{constructor(t,e=null){super(t,"AuthImplicitGrantRedirectError",500,void 0),this.details=null,this.details=e}toJSON(){return{name:this.name,message:this.message,status:this.status,details:this.details}}}function isAuthImplicitGrantRedirectError(n){return isAuthError(n)&&n.name==="AuthImplicitGrantRedirectError"}class AuthPKCEGrantCodeExchangeError extends CustomAuthError{constructor(t,e=null){super(t,"AuthPKCEGrantCodeExchangeError",500,void 0),this.details=null,this.details=e}toJSON(){return{name:this.name,message:this.message,status:this.status,details:this.details}}}class AuthPKCECodeVerifierMissingError extends CustomAuthError{constructor(){super("PKCE code verifier not found in storage. This can happen if the auth flow was initiated in a different browser or device, or if the storage was cleared. For SSR frameworks (Next.js, SvelteKit, etc.), use @supabase/ssr on both the server and client to store the code verifier in cookies.","AuthPKCECodeVerifierMissingError",400,"pkce_code_verifier_not_found")}}class AuthRetryableFetchError extends CustomAuthError{constructor(t,e){super(t,"AuthRetryableFetchError",e,void 0)}}function isAuthRetryableFetchError(n){return isAuthError(n)&&n.name==="AuthRetryableFetchError"}class AuthWeakPasswordError extends CustomAuthError{constructor(t,e,s){super(t,"AuthWeakPasswordError",e,"weak_password"),this.reasons=s}}class AuthInvalidJwtError extends CustomAuthError{constructor(t){super(t,"AuthInvalidJwtError",400,"invalid_jwt")}}const TO_BASE64URL="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".split(""),IGNORE_BASE64URL=` 	
36\r=`.split(""),FROM_BASE64URL=(()=>{const n=new Array(128);for(let t=0;t<n.length;t+=1)n[t]=-1;for(let t=0;t<IGNORE_BASE64URL.length;t+=1)n[IGNORE_BASE64URL[t].charCodeAt(0)]=-2;for(let t=0;t<TO_BASE64URL.length;t+=1)n[TO_BASE64URL[t].charCodeAt(0)]=t;return n})();function byteToBase64URL(n,t,e){if(n!==null)for(t.queue=t.queue<<8|n,t.queuedBits+=8;t.queuedBits>=6;){const s=t.queue>>t.queuedBits-6&63;e(TO_BASE64URL[s]),t.queuedBits-=6}else if(t.queuedBits>0)for(t.queue=t.queue<<6-t.queuedBits,t.queuedBits=6;t.queuedBits>=6;){const s=t.queue>>t.queuedBits-6&63;e(TO_BASE64URL[s]),t.queuedBits-=6}}function byteFromBase64URL(n,t,e){const s=FROM_BASE64URL[n];if(s>-1)for(t.queue=t.queue<<6|s,t.queuedBits+=6;t.queuedBits>=8;)e(t.queue>>t.queuedBits-8&255),t.queuedBits-=8;else{if(s===-2)return;throw new Error(`Invalid Base64-URL character "${String.fromCharCode(n)}"`)}}function stringFromBase64URL(n){const t=[],e=a=>{t.push(String.fromCodePoint(a))},s={utf8seq:0,codepoint:0},o={queue:0,queuedBits:0},r=a=>{stringFromUTF8(a,s,e)};for(let a=0;a<n.length;a+=1)byteFromBase64URL(n.charCodeAt(a),o,r);return t.join("")}function codepointToUTF8(n,t){if(n<=127){t(n);return}else if(n<=2047){t(192|n>>6),t(128|n&63);return}else if(n<=65535){t(224|n>>12),t(128|n>>6&63),t(128|n&63);return}else if(n<=1114111){t(240|n>>18),t(128|n>>12&63),t(128|n>>6&63),t(128|n&63);return}throw new Error(`Unrecognized Unicode codepoint: ${n.toString(16)}`)}function stringToUTF8(n,t){for(let e=0;e<n.length;e+=1){let s=n.charCodeAt(e);if(s>55295&&s<=56319){const o=(s-55296)*1024&65535;s=(n.charCodeAt(e+1)-56320&65535|o)+65536,e+=1}codepointToUTF8(s,t)}}function stringFromUTF8(n,t,e){if(t.utf8seq===0){if(n<=127){e(n);return}for(let s=1;s<6;s+=1)if((n>>7-s&1)===0){t.utf8seq=s;break}if(t.utf8seq===2)t.codepoint=n&31;else if(t.utf8seq===3)t.codepoint=n&15;else if(t.utf8seq===4)t.codepoint=n&7;else throw new Error("Invalid UTF-8 sequence");t.utf8seq-=1}else if(t.utf8seq>0){if(n<=127)throw new Error("Invalid UTF-8 sequence");t.codepoint=t.codepoint<<6|n&63,t.utf8seq-=1,t.utf8seq===0&&e(t.codepoint)}}function base64UrlToUint8Array(n){const t=[],e={queue:0,queuedBits:0},s=o=>{t.push(o)};for(let o=0;o<n.length;o+=1)byteFromBase64URL(n.charCodeAt(o),e,s);return new Uint8Array(t)}function stringToUint8Array(n){const t=[];return stringToUTF8(n,e=>t.push(e)),new Uint8Array(t)}function bytesToBase64URL(n){const t=[],e={queue:0,queuedBits:0},s=o=>{t.push(o)};
36return n.forEach(o=>byteToBase64URL(o,e,s)),byteToBase64URL(null,e,s),t.join("")}function expiresAt(n){return Math.round(Date.now()/1e3)+n}function generateCallbackId(){return Symbol("auth-callback")}const isBrowser$1=()=>typeof window<"u"&&typeof document<"u",localStorageWriteTests={tested:!1,writable:!1},supportsLocalStorage=()=>{if(!isBrowser$1())return!1;try{if(typeof globalThis.localStorage!="object")return!1}catch{return!1}if(localStorageWriteTests.tested)return localStorageWriteTests.writable;const n=`lswt-${Math.random()}${Math.random()}`;try{globalThis.localStorage.setItem(n,n),globalThis.localStorage.removeItem(n),localStorageWriteTests.tested=!0,localStorageWriteTests.writable=!0}catch{localStorageWriteTests.tested=!0,localStorageWriteTests.writable=!1}return localStorageWriteTests.writable};function parseParametersFromURL(n){const t={},e=new URL(n);if(e.hash&&e.hash[0]==="#")try{new URLSearchParams(e.hash.substring(1)).forEach((o,r)=>{t[r]=o})}catch{}return e.searchParams.forEach((s,o)=>{t[o]=s}),t}const resolveFetch$1=n=>n?(...t)=>n(...t):(...t)=>fetch(...t),looksLikeFetchResponse=n=>typeof n=="object"&&n!==null&&"status"in n&&"ok"in n&&"json"in n&&typeof n.json=="function",setItemAsync=async(n,t,e)=>{await n.setItem(t,JSON.stringify(e))},getItemAsync=async(n,t)=>{const e=await n.getItem(t);if(!e)return null;try{return JSON.parse(e)}catch{return e}},removeItemAsync=async(n,t)=>{await n.removeItem(t)};class Deferred{constructor(){this.promise=new Deferred.promiseConstructor((t,e)=>{this.resolve=t,this.reject=e})}}Deferred.promiseConstructor=Promise;function decodeJWT(n){const t=n.split(".");if(t.length!==3)throw new AuthInvalidJwtError("Invalid JWT structure");for(let s=0;s<t.length;s++)if(!BASE64URL_REGEX.test(t[s]))throw new AuthInvalidJwtError("JWT not in base64url format");return{header:JSON.parse(stringFromBase64URL(t[0])),payload:JSON.parse(stringFromBase64URL(t[1])),signature:base64UrlToUint8Array(t[2]),raw:{header:t[0],payload:t[1]}}}async function sleep(n){return await new Promise(t=>{setTimeout(()=>t(null),n)})}function retryable(n,t){return new Promise((s,o)=>{(async()=>{for(let r=0;r<1/0;r++)try{const a=await n(r);if(!t(r,null,a)){s(a);return}}catch(a){if(!t(r,a)){o(a);return}}})()})}function dec2hex(n){return("0"+n.toString(16)).substr(-2)}function generatePKCEVerifier(){const t=new Uint32Array(56);if(typeof crypto>"u"){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",s=e.length;let o="";for(let r=0;r<56;r++)o+=e.charAt(Math.floor(Math.random()*s));return o}return crypto.getRandomValues(t),Array.from(t,dec2hex).join("")}async function sha256(n){const e=new TextEncoder().encode(n),s=await crypto.subtle.digest("SHA-256",e),o=new Uint8Array(s);return Array.from(o).map(r=>String.fromCharCode(r)).join("")}async function generatePKCEChallenge(n){if(!(typeof crypto<"u"&&typeof crypto.subtle<"u"&&typeof TextEncoder<"u"))return console.warn("WebCrypto API is not supported. Code challenge method will default to use plain instead of sha256."),n;const e=await sha256(n);return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}async function getCodeChallengeAndMethod(n,t,e=!1){const s=generatePKCEVerifier();let o=s;e&&(o+="/PASSWORD_RECOVERY"),await setItemAsync(n,`${t}-code-verifier`,o);const r=await generatePKCEChallenge(s);return[r,s===r?"plain":"s256"]}const API_VERSION_REGEX=/^2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1])$/i;function parseResponseAPIVersion(n){const t=n.headers.get(API_VERSION_HEADER_NAME);if(!t||!t.match(API_VERSION_REGEX))return null;try{return new Date(`${t}T00:00:00.0Z`)}catch{return null}}function validateExp(n){if(!n)throw new Error("Missing exp claim");const t=Math.floor(Date.now()/1e3);if(n<=t)throw new Error("JWT has expired")}function getAlgorithm(n){switch(n){case"RS256":return{name:"RSASSA-PKCS1-v1_5",hash:{name:"SHA-256"}};case"ES256":return{name:"ECDSA",namedCurve:"P-256",hash:{name:"SHA-256"}};default:throw new Error("Invalid alg claim")}}const UUID_REGEX=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
36function validateUUID(n){if(!UUID_REGEX.test(n))throw new Error("@supabase/auth-js: Expected parameter to be UUID but is not")}function userNotAvailableProxy(){const n={};return new Proxy(n,{get:(t,e)=>{if(e==="__isUserNotAvailableProxy")return!0;if(typeof e=="symbol"){const s=e.toString();if(s==="Symbol(Symbol.toPrimitive)"||s==="Symbol(Symbol.toStringTag)"||s==="Symbol(util.inspect.custom)")return}throw new Error(`@supabase/auth-js: client was created with userStorage option and there was no user stored in the user storage. Accessing the "${e}" property of the session object is not supported. Please use getUser() instead.`)},set:(t,e)=>{throw new Error(`@supabase/auth-js: client was created with userStorage option and there was no user stored in the user storage. Setting the "${e}" property of the session object is not supported. Please use getUser() to fetch a user object you can manipulate.`)},deleteProperty:(t,e)=>{throw new Error(`@supabase/auth-js: client was created with userStorage option and there was no user stored in the user storage. Deleting the "${e}" property of the session object is not supported. Please use getUser() to fetch a user object you can manipulate.`)}})}function insecureUserWarningProxy(n,t){return new Proxy(n,{get:(e,s,o)=>{if(s==="__isInsecureUserWarningProxy")return!0;if(typeof s=="symbol"){const r=s.toString();if(r==="Symbol(Symbol.toPrimitive)"||r==="Symbol(Symbol.toStringTag)"||r==="Symbol(util.inspect.custom)"||r==="Symbol(nodejs.util.inspect.custom)")return Reflect.get(e,s,o)}return!t.value&&typeof s=="string"&&(console.warn("Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and may not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server."),t.value=!0),Reflect.get(e,s,o)}})}function deepClone(n){return JSON.parse(JSON.stringify(n))}const _getErrorMessage=n=>n.msg||n.message||n.error_description||n.error||JSON.stringify(n),NETWORK_ERROR_CODES=[502,503,504];async function handleError(n){var t;if(!looksLikeFetchResponse(n))throw new AuthRetryableFetchError(_getErrorMessage(n),0);if(NETWORK_ERROR_CODES.includes(n.status))throw new AuthRetryableFetchError(_getErrorMessage(n),n.status);let e;try{e=await n.json()}catch(r){throw new AuthUnknownError(_getErrorMessage(r),r)}let s;const o=parseResponseAPIVersion(n);if(o&&o.getTime()>=API_VERSIONS["2024-01-01"].timestamp&&typeof e=="object"&&e&&typeof e.code=="string"?s=e.code:typeof e=="object"&&e&&typeof e.error_code=="string"&&(s=e.error_code),s){if(s==="weak_password")throw new AuthWeakPasswordError(_getErrorMessage(e),n.status,((t=e.weak_password)===null||t===void 0?void 0:t.reasons)||[]);if(s==="session_not_found")throw new AuthSessionMissingError}else if(typeof e=="object"&&e&&typeof e.weak_password=="object"&&e.weak_password&&Array.isArray(e.weak_password.reasons)&&e.weak_password.reasons.length&&e.weak_password.reasons.reduce((r,a)=>r&&typeof a=="string",!0))throw new AuthWeakPasswordError(_getErrorMessage(e),n.status,e.weak_password.reasons);throw new AuthApiError(_getErrorMessage(e),n.status||500,s)}
36const _getRequestParams=(n,t,e,s)=>{const o={method:n,headers:t?.headers||{}};return n==="GET"?o:(o.headers=Object.assign({"Content-Type":"application/json;charset=UTF-8"},t?.headers),o.body=JSON.stringify(s),Object.assign(Object.assign({},o),e))};async function _request(n,t,e,s){var o;const r=Object.assign({},s?.headers);r[API_VERSION_HEADER_NAME]||(r[API_VERSION_HEADER_NAME]=API_VERSIONS["2024-01-01"].name),s?.jwt&&(r.Authorization=`Bearer ${s.jwt}`);const a=(o=s?.query)!==null&&o!==void 0?o:{};s?.redirectTo&&(a.redirect_to=s.redirectTo);const i=Object.keys(a).length?"?"+new URLSearchParams(a).toString():"",l=await _handleRequest(n,t,e+i,{headers:r,noResolveJson:s?.noResolveJson},{},s?.body);return s?.xform?s?.xform(l):{data:Object.assign({},l),error:null}}async function _handleRequest(n,t,e,s,o,r){const a=_getRequestParams(t,s,o,r);let i;try{i=await n(e,Object.assign({},a))}catch(l){throw console.error(l),new AuthRetryableFetchError(_getErrorMessage(l),0)}if(i.ok||await handleError(i),s?.noResolveJson)return i;try{return await i.json()}catch(l){await handleError(l)}}function _sessionResponse(n){var t;let e=null;hasSession(n)&&(e=Object.assign({},n),n.expires_at||(e.expires_at=expiresAt(n.expires_in)));const s=(t=n.user)!==null&&t!==void 0?t:n;return{data:{session:e,user:s},error:null}}function _sessionResponsePassword(n){const t=_sessionResponse(n);return!t.error&&n.weak_password&&typeof n.weak_password=="object"&&Array.isArray(n.weak_password.reasons)&&n.weak_password.reasons.length&&n.weak_password.message&&typeof n.weak_password.message=="string"&&n.weak_password.reasons.reduce((e,s)=>e&&typeof s=="string",!0)&&(t.data.weak_password=n.weak_password),t}function _userResponse(n){var t;return{data:{user:(t=n.user)!==null&&t!==void 0?t:n},error:null}}function _ssoResponse(n){return{data:n,error:null}}function _generateLinkResponse(n){const{action_link:t,email_otp:e,hashed_token:s,redirect_to:o,verification_type:r}=n,a=__rest(n,["action_link","email_otp","hashed_token","redirect_to","verification_type"]),i={action_link:t,email_otp:e,hashed_token:s,redirect_to:o,verification_type:r},l=Object.assign({},a);return{data:{properties:i,user:l},error:null}}function _noResolveJsonResponse(n){return n}function hasSession(n){return n.access_token&&n.refresh_token&&n.expires_in}const SIGN_OUT_SCOPES=["global","local","others"];class GoTrueAdminApi{constructor({url:t="",headers:e={},fetch:s}){this.url=t,this.headers=e,this.fetch=resolveFetch$1(s),this.mfa={listFactors:this._listFactors.bind(this),deleteFactor:this._deleteFactor.bind(this)},this.oauth={listClients:this._listOAuthClients.bind(this),createClient:this._createOAuthClient.bind(this),getClient:this._getOAuthClient.bind(this),updateClient:this._updateOAuthClient.bind(this),deleteClient:this._deleteOAuthClient.bind(this),regenerateClientSecret:this._regenerateOAuthClientSecret.bind(this)}}async signOut(t,e=SIGN_OUT_SCOPES[0]){if(SIGN_OUT_SCOPES.indexOf(e)<0)throw new Error(`@supabase/auth-js: Parameter scope must be one of ${SIGN_OUT_SCOPES.join(", ")}`);try{return await _request(this.fetch,"POST",`${this.url}/logout?scope=${e}`,{headers:this.headers,jwt:t,noResolveJson:!0}),{data:null,error:null}}catch(s){if(isAuthError(s))return{data:null,error:s};throw s}}async inviteUserByEmail(t,e={}){try{return await _request(this.fetch,"POST",`${this.url}/invite`,{body:{email:t,data:e.data},headers:this.headers,redirectTo:e.redirectTo,xform:_userResponse})}catch(s){if(isAuthError(s))return{data:{user:null},error:s};throw s}}async generateLink(t){try{const{options:e}=t,s=__rest(t,["options"]),o=Object.assign(Object.assign({},s),e);return"newEmail"in s&&(o.new_email=s?.newEmail,delete o.newEmail),await _request(this.fetch,"POST",`${this.url}/admin/generate_link`,{body:o,headers:this.headers,xform:_generateLinkResponse,redirectTo:e?.redirectTo})}catch(e){if(isAuthError(e))return{data:{properties:null,user:null},error:e};throw e}}async createUser(t){try{return await _request(this.fetch,"POST",`${this.url}/admin/users`,{body:t,headers:this.headers,xform:_userResponse})}catch(e){if(isAuthError(e))return{data:{user:null},error:e};throw e}}async listUsers(t){var e,s,o,r,a,i,l;try{const u={nextPage:null,lastPage:0,total:0},d=await _request(this.fetch,"GET",`${this.url}/admin/users`,{headers:this.headers,noResolveJson:!0,query:{page:(s=(e=t?.page)===null||e===void 0?void 0:e.toString())!==null&&s!==void 0?s:"",per_page:(r=(o=t?.perPage)===null||o===void 0?void 0:o.toString())!==null&&r!==void 0?r:""}
36,xform:_noResolveJsonResponse});if(d.error)throw d.error;const m=await d.json(),h=(a=d.headers.get("x-total-count"))!==null&&a!==void 0?a:0,f=(l=(i=d.headers.get("link"))===null||i===void 0?void 0:i.split(","))!==null&&l!==void 0?l:[];return f.length>0&&(f.forEach(g=>{const b=parseInt(g.split(";")[0].split("=")[1].substring(0,1)),x=JSON.parse(g.split(";")[1].split("=")[1]);u[`${x}Page`]=b}),u.total=parseInt(h)),{data:Object.assign(Object.assign({},m),u),error:null}}catch(u){if(isAuthError(u))return{data:{users:[]},error:u};throw u}}async getUserById(t){validateUUID(t);try{return await _request(this.fetch,"GET",`${this.url}/admin/users/${t}`,{headers:this.headers,xform:_userResponse})}catch(e){if(isAuthError(e))return{data:{user:null},error:e};throw e}}async updateUserById(t,e){validateUUID(t);try{return await _request(this.fetch,"PUT",`${this.url}/admin/users/${t}`,{body:e,headers:this.headers,xform:_userResponse})}catch(s){if(isAuthError(s))return{data:{user:null},error:s};throw s}}async deleteUser(t,e=!1){validateUUID(t);try{return await _request(this.fetch,"DELETE",`${this.url}/admin/users/${t}`,{headers:this.headers,body:{should_soft_delete:e},xform:_userResponse})}catch(s){if(isAuthError(s))return{data:{user:null},error:s};throw s}}async _listFactors(t){validateUUID(t.userId);try{const{data:e,error:s}=await _request(this.fetch,"GET",`${this.url}/admin/users/${t.userId}/factors`,{headers:this.headers,xform:o=>({data:{factors:o},error:null})});return{data:e,error:s}}catch(e){if(isAuthError(e))return{data:null,error:e};throw e}}async _deleteFactor(t){validateUUID(t.userId),validateUUID(t.id);try{return{data:await _request(this.fetch,"DELETE",`${this.url}/admin/users/${t.userId}/factors/${t.id}`,{headers:this.headers}),error:null}}catch(e){if(isAuthError(e))return{data:null,error:e};throw e}}async _listOAuthClients(t){var e,s,o,r,a,i,l;try{const u={nextPage:null,lastPage:0,total:0},d=await _request(this.fetch,"GET",`${this.url}/admin/oauth/clients`,{headers:this.headers,noResolveJson:!0,query:{page:(s=(e=t?.page)===null||e===void 0?void 0:e.toString())!==null&&s!==void 0?s:"",per_page:(r=(o=t?.perPage)===null||o===void 0?void 0:o.toString())!==null&&r!==void 0?r:""}
36,xform:_noResolveJsonResponse});if(d.error)throw d.error;const m=await d.json(),h=(a=d.headers.get("x-total-count"))!==null&&a!==void 0?a:0,f=(l=(i=d.headers.get("link"))===null||i===void 0?void 0:i.split(","))!==null&&l!==void 0?l:[];return f.length>0&&(f.forEach(g=>{const b=parseInt(g.split(";")[0].split("=")[1].substring(0,1)),x=JSON.parse(g.split(";")[1].split("=")[1]);u[`${x}Page`]=b}),u.total=parseInt(h)),{data:Object.assign(Object.assign({},m),u),error:null}}catch(u){if(isAuthError(u))return{data:{clients:[]},error:u};throw u}}async _createOAuthClient(t){try{return await _request(this.fetch,"POST",`${this.url}/admin/oauth/clients`,{body:t,headers:this.headers,xform:e=>({data:e,error:null})})}catch(e){if(isAuthError(e))return{data:null,error:e};throw e}}async _getOAuthClient(t){try{return await _request(this.fetch,"GET",`${this.url}/admin/oauth/clients/${t}`,{headers:this.headers,xform:e=>({data:e,error:null})})}catch(e){if(isAuthError(e))return{data:null,error:e};throw e}}async _updateOAuthClient(t,e){try{return await _request(this.fetch,"PUT",`${this.url}/admin/oauth/clients/${t}`,{body:e,headers:this.headers,xform:s=>({data:s,error:null})})}catch(s){if(isAuthError(s))return{data:null,error:s};throw s}}async _deleteOAuthClient(t){try{return await _request(this.fetch,"DELETE",`${this.url}/admin/oauth/clients/${t}`,{headers:this.headers,noResolveJson:!0}),{data:null,error:null}}catch(e){if(isAuthError(e))return{data:null,error:e};throw e}}async _regenerateOAuthClientSecret(t){try{return await _request(this.fetch,"POST",`${this.url}/admin/oauth/clients/${t}/regenerate_secret`,{headers:this.headers,xform:e=>({data:e,error:null})})}catch(e){if(isAuthError(e))return{data:null,error:e};throw e}}}function memoryLocalStorageAdapter(n={}){return{getItem:t=>n[t]||null,setItem:(t,e)=>{n[t]=e},removeItem:t=>{delete n[t]}}}const internals={debug:!!(globalThis&&supportsLocalStorage()&&globalThis.localStorage&&globalThis.localStorage.getItem("supabase.gotrue-js.locks.debug")==="true")};class LockAcquireTimeoutError extends Error{constructor(t){super(t),this.isAcquireTimeout=!0}}class NavigatorLockAcquireTimeoutError extends LockAcquireTimeoutError{}async function navigatorLock(n,t,e){internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: acquire lock",n,t);const s=new globalThis.AbortController;t>0&&setTimeout(()=>{s.abort(),internals.debug&&console.log("@supabase/gotrue-js: navigatorLock acquire timed out",n)},t),await Promise.resolve();try{return await globalThis.navigator.locks.request(n,t===0?{mode:"exclusive",ifAvailable:!0}:{mode:"exclusive",signal:s.signal},async o=>{if(o){internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: acquired",n,o.name);try{return await e()}finally{internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: released",n,o.name)}}else{if(t===0)throw internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: not immediately available",n),new NavigatorLockAcquireTimeoutError(`Acquiring an exclusive Navigator LockManager lock "${n}" immediately failed`);if(internals.debug)try{const r=await globalThis.navigator.locks.query();console.log("@supabase/gotrue-js: Navigator LockManager state",JSON.stringify(r,null,"  "))}catch(r){console.warn("@supabase/gotrue-js: Error when querying Navigator LockManager state",r)}return console.warn("@supabase/gotrue-js: Navigator LockManager returned a null lock when using #request without ifAvailable set to true, it appears this browser is not following the LockManager spec https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request"),await e()}})}catch(o){if(o?.name==="AbortError"&&t>0)return internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: acquire timeout, recovering by stealing lock",n),console.warn(`@supabase/gotrue-js: Lock "${n}" was not released within ${t}ms. This may indicate an orphaned lock from a component unmount (e.g., React Strict Mode). Forcefully acquiring the lock to recover.`),await Promise.resolve().then(()=>globalThis.navigator.locks.request(n,{mode:"exclusive",steal:!0},async r=>{if(r){internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: recovered (stolen)",n,r.name);try{return await e()}finally{internals.debug&&console.log("@supabase/gotrue-js: navigatorLock: released (stolen)",n,r.name)}}else return console.warn("@supabase/gotrue-js: Navigator LockManager returned null lock even with steal: true"),await e()}));throw o}}function polyfillGlobalThis(){if(typeof globalThis!="object")try{Object.defineProperty(Object.prototype,"__magic__",{get:function(){return this},configurable:!0}),__magic__.globalThis=__magic__,delete Object.prototype.__magic__}catch{typeof self<"u"&&(self.globalThis=self)}}function getAddress(n){if(!/^0x[a-fA-F0-9]{40}$/.test(n))throw new Error(`@supabase/auth-js: Address "${n}
36" is invalid.`);return n.toLowerCase()}function fromHex(n){return parseInt(n,16)}function toHex(n){const t=new TextEncoder().encode(n);return"0x"+Array.from(t,s=>s.toString(16).padStart(2,"0")).join("")}function createSiweMessage(n){var t;const{chainId:e,domain:s,expirationTime:o,issuedAt:r=new Date,nonce:a,notBefore:i,requestId:l,resources:u,scheme:d,uri:m,version:h}=n;{if(!Number.isInteger(e))throw new Error(`@supabase/auth-js: Invalid SIWE message field "chainId". Chain ID must be a EIP-155 chain ID. Provided value: ${e}`);if(!s)throw new Error('@supabase/auth-js: Invalid SIWE message field "domain". Domain must be provided.');if(a&&a.length<8)throw new Error(`@supabase/auth-js: Invalid SIWE message field "nonce". Nonce must be at least 8 characters. Provided value: ${a}`);if(!m)throw new Error('@supabase/auth-js: Invalid SIWE message field "uri". URI must be provided.');if(h!=="1")throw new Error(`@supabase/auth-js: Invalid SIWE message field "version". Version must be '1'. Provided value: ${h}`);if(!((t=n.statement)===null||t===void 0)&&t.includes(`
37`))throw new Error(`@supabase/auth-js: Invalid SIWE message field "statement". Statement must not include '\\n'. Provided value: ${n.statement}`)}const f=getAddress(n.address),g=d?`${d}://${s}`:s,b=n.statement?`${n.statement}
38`:"",x=`${g} wants you to sign in with your Ethereum account:
39${f}
40
41${b}`;let y=`URI: ${m}
42Version: ${h}
43Chain ID: ${e}${a?`
44Nonce: ${a}`:""}
45Issued At: ${r.toISOString()}`;if(o&&(y+=`
46Expiration Time: ${o.toISOString()}`),i&&(y+=`
47Not Before: ${i.toISOString()}`),l&&(y+=`
48Request ID: ${l}`),u){let E=`
49Resources:`;for(const k of u){if(!k||typeof k!="string")throw new Error(`@supabase/auth-js: Invalid SIWE message field "resources". Every resource must be a valid string. Provided value: ${k}`);E+=`
50- ${k}`}y+=E}return`${x}
51${y}`}class WebAuthnError extends Error{constructor({message:t,code:e,cause:s,name:o}){var r;super(t,{cause:s}),this.__isWebAuthnError=!0,this.name=(r=o??(s instanceof Error?s.name:void 0))!==null&&r!==void 0?r:"Unknown Error",this.code=e}}class WebAuthnUnknownError extends WebAuthnError{constructor(t,e){super({code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:e,message:t}),this.name="WebAuthnUnknownError",this.originalError=e}}function identifyRegistrationError({error:n,options:t}){var e,s,o;const{publicKey:r}=t;if(!r)throw Error("options was missing required publicKey property");if(n.name==="AbortError"){if(t.signal instanceof AbortSignal)return new WebAuthnError({message:"Registration ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:n})}else if(n.name==="ConstraintError"){if(((e=r.authenticatorSelection)===null||e===void 0?void 0:e.requireResidentKey)===!0)return new WebAuthnError({message:"Discoverable credentials were required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",cause:n});if(t.mediation==="conditional"&&((s=r.authenticatorSelection)===null||s===void 0?void 0:s.userVerification)==="required")return new WebAuthnError({message:"User verification was required during automatic registration but it could not be performed",code:"ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE",cause:n});if(((o=r.authenticatorSelection)===null||o===void 0?void 0:o.userVerification)==="required")return new WebAuthnError({message:"User verification was required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",cause:n})}else{if(n.name==="InvalidStateError")return new WebAuthnError({message:"The authenticator was previously registered",code:"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",cause:n});if(n.name==="NotAllowedError")return new WebAuthnError({message:n.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:n});if(n.name==="NotSupportedError")return r.pubKeyCredParams.filter(i=>i.type==="public-key").length===0?new WebAuthnError({message:'No entry in pubKeyCredParams was of type "public-key"',code:"ERROR_MALFORMED_PUBKEYCREDPARAMS",cause:n}):new WebAuthnError({message:"No available authenticator supported any of the specified pubKeyCredParams algorithms",code:"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",cause:n});if(n.name==="SecurityError"){const a=window.location.hostname;if(isValidDomain(a)){if(r.rp.id!==a)return new WebAuthnError({message:`The RP ID "${r.rp.id}
51" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:n})}else return new WebAuthnError({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:n})}else if(n.name==="TypeError"){if(r.user.id.byteLength<1||r.user.id.byteLength>64)return new WebAuthnError({message:"User ID was not between 1 and 64 characters",code:"ERROR_INVALID_USER_ID_LENGTH",cause:n})}else if(n.name==="UnknownError")return new WebAuthnError({message:"The authenticator was unable to process the specified options, or could not create a new credential",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:n})}return new WebAuthnError({message:"a Non-Webauthn related error has occurred",code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:n})}function identifyAuthenticationError({error:n,options:t}){const{publicKey:e}=t;if(!e)throw Error("options was missing required publicKey property");if(n.name==="AbortError"){if(t.signal instanceof AbortSignal)return new WebAuthnError({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:n})}else{if(n.name==="NotAllowedError")return new WebAuthnError({message:n.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:n});if(n.name==="SecurityError"){const s=window.location.hostname;if(isValidDomain(s)){if(e.rpId!==s)return new WebAuthnError({message:`The RP ID "${e.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:n})}else return new WebAuthnError({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:n})}else if(n.name==="UnknownError")return new WebAuthnError({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:n})}return new WebAuthnError({message:"a Non-Webauthn related error has occurred",code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:n})}class WebAuthnAbortService{createNewAbortSignal(){if(this.controller){const e=new Error("Cancelling existing WebAuthn API call for new one");e.name="AbortError",this.controller.abort(e)}const t=new AbortController;return this.controller=t,t.signal}cancelCeremony(){if(this.controller){const t=new Error("Manually cancelling existing WebAuthn API call");t.name="AbortError",this.controller.abort(t),this.controller=void 0}}}const webAuthnAbortService=new WebAuthnAbortService;function deserializeCredentialCreationOptions(n){if(!n)throw new Error("Credential creation options are required");if(typeof PublicKeyCredential<"u"&&"parseCreationOptionsFromJSON"in PublicKeyCredential&&typeof PublicKeyCredential.parseCreationOptionsFromJSON=="function")return PublicKeyCredential.parseCreationOptionsFromJSON(n);const{challenge:t,user:e,excludeCredentials:s}=n,o=__rest(n,["challenge","user","excludeCredentials"]),r=base64UrlToUint8Array(t).buffer,a=Object.assign(Object.assign({},e),{id:base64UrlToUint8Array(e.id).buffer}),i=Object.assign(Object.assign({},o),{challenge:r,user:a});if(s&&s.length>0){i.excludeCredentials=new Array(s.length);for(let l=0;l<s.length;l++){const u=s[l];i.excludeCredentials[l]=Object.assign(Object.assign({},u),{id:base64UrlToUint8Array(u.id).buffer,type:u.type||"public-key",transports:u.transports})}}return i}function deserializeCredentialRequestOptions(n){if(!n)throw new Error("Credential request options are required");if(typeof PublicKeyCredential<"u"&&"parseRequestOptionsFromJSON"in PublicKeyCredential&&typeof PublicKeyCredential.parseRequestOptionsFromJSON=="function")return PublicKeyCredential.parseRequestOptionsFromJSON(n);const{challenge:t,allowCredentials:e}=n,s=__rest(n,["challenge","allowCredentials"]),o=base64UrlToUint8Array(t).buffer,r=Object.assign(Object.assign({},s),{challenge:o});if(e&&e.length>0){r.allowCredentials=new Array(e.length);for(let a=0;a<e.length;a++){const i=e[a];r.allowCredentials[a]=Object.assign(Object.assign({},i),{id:base64UrlToUint8Array(i.id).buffer,type:i.type||"public-key",transports:i.transports})}}return r}function serializeCredentialCreationResponse(n){var t;if("toJSON"in n&&typeof n.toJSON=="function")return n.toJSON();const e=n;return{id:n.id,rawId:n.id,response:{attestationObject:bytesToBase64URL(new Uint8Array(n.response.attestationObject)),clientDataJSON:bytesToBase64URL(new Uint8Array(n.response.clientDataJSON))},type:"public-key",clientExtensionResults:n.getClientExtensionResults(),authenticatorAttachment:(t=e.authenticatorAttachment)!==null&&t!==void 0?t:void 0}}function serializeCredentialRequestResponse(n){var t;if("toJSON"in n&&typeof n.toJSON=="function")return n.toJSON();const e=n,s=n.getClientExtensionResults(),o=n.response;return{id:n.id,rawId:n.id,response:{authenticatorData:bytesToBase64URL(new Uint8Array(o.authenticatorData)),clientDataJSON:bytesToBase64URL(new Uint8Array(o.clientDataJSON)),signature:bytesToBase64URL(new Uint8Array(o.signature)),userHandle:o.userHandle?bytesToBase64URL(new Uint8Array(o.userHandle)):void 0},type:"public-key",clientExtensionResults:s,authenticatorAttachment:(t=e.authenticatorAttachment)!==null&&t!==void 0?t:void 0}}function isValidDomain(n){return n==="localhost"||/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(n)}function browserSupportsWebAuthn(){var n,t;return!!(isBrowser$1()&&"PublicKeyCredential"in window&&window.PublicKeyCredential&&"credentials"in navigator&&typeof((n=navigator?.credentials)===null||n===void 0?void 0:n.create)=="function"&&typeof((t=navigator?.credentials)===null||t===void 0?void 0:t.get)=="function")}async function createCredential(n){try{const t=await navigator.credentials.create(n);return t?t instanceof PublicKeyCredential?{data:t,error:null}:{data:null,error:new WebAuthnUnknownError("Browser returned unexpected credential type",t)}:{data:null,error:new WebAuthnUnknownError("Empty credential response",t)}}catch(t){return{data:null,error:identifyRegistrationError({error:t,o
51ptions:n})}}}async function getCredential(n){try{const t=await navigator.credentials.get(n);return t?t instanceof PublicKeyCredential?{data:t,error:null}:{data:null,error:new WebAuthnUnknownError("Browser returned unexpected credential type",t)}:{data:null,error:new WebAuthnUnknownError("Empty credential response",t)}}catch(t){return{data:null,error:identifyAuthenticationError({error:t,options:n})}}}const DEFAULT_CREATION_OPTIONS={hints:["security-key"],authenticatorSelection:{authenticatorAttachment:"cross-platform",requireResidentKey:!1,userVerification:"preferred",residentKey:"discouraged"},attestation:"direct"},DEFAULT_REQUEST_OPTIONS={userVerification:"preferred",hints:["security-key"],attestation:"direct"};function deepMerge(...n){const t=o=>o!==null&&typeof o=="object"&&!Array.isArray(o),e=o=>o instanceof ArrayBuffer||ArrayBuffer.isView(o),s={};for(const o of n)if(o)for(const r in o){const a=o[r];if(a!==void 0)if(Array.isArray(a))s[r]=a;else if(e(a))s[r]=a;else if(t(a)){const i=s[r];t(i)?s[r]=deepMerge(i,a):s[r]=deepMerge(a)}else s[r]=a}return s}function mergeCredentialCreationOptions(n,t){return deepMerge(DEFAULT_CREATION_OPTIONS,n,t||{})}function mergeCredentialRequestOptions(n,t){return deepMerge(DEFAULT_REQUEST_OPTIONS,n,t||{})}class WebAuthnApi{constructor(t){this.client=t,this.enroll=this._enroll.bind(this),this.challenge=this._challenge.bind(this),this.verify=this._verify.bind(this),this.authenticate=this._authenticate.bind(this),this.register=this._register.bind(this)}async _enroll(t){return this.client.mfa.enroll(Object.assign(Object.assign({},t),{factorType:"webauthn"}))}async _challenge({factorId:t,webauthn:e,friendlyName:s,signal:o},r){var a;try{const{data:i,error:l}=await this.client.mfa.challenge({factorId:t,webauthn:e});if(!i)return{data:null,error:l};const u=o??webAuthnAbortService.createNewAbortSignal();if(i.webauthn.type==="create"){const{user:d}=i.webauthn.credential_options.publicKey;if(!d.name){const m=s;if(m)d.name=`${d.id}:${m}`;else{const f=(await this.client.getUser()).data.user,g=((a=f?.user_metadata)===null||a===void 0?void 0:a.name)||f?.email||f?.id||"User";d.name=`${d.id}:${g}`}}d.displayName||(d.displayName=d.name)}switch(i.webauthn.type){case"create":{const d=mergeCredentialCreationOptions(i.webauthn.credential_options.publicKey,r?.create),{data:m,error:h}=await createCredential({publicKey:d,signal:u});return m?{data:{factorId:t,challengeId:i.id,webauthn:{type:i.webauthn.type,credential_response:m}},error:null}:{data:null,error:h}}case"request":{const d=mergeCredentialRequestOptions(i.webauthn.credential_options.publicKey,r?.request),{data:m,error:h}=await getCredential(Object.assign(Object.assign({},i.webauthn.credential_options),{publicKey:d,signal:u}));return m?{data:{factorId:t,challengeId:i.id,webauthn:{type:i.webauthn.type,credential_response:m}},error:null}:{data:null,error:h}}}}catch(i){return isAuthError(i)?{data:null,error:i}:{data:null,error:new AuthUnknownError("Unexpected error in challenge",i)}}}async _verify({challengeId:t,factorId:e,webauthn:s}){return this.client.mfa.verify({factorId:e,challengeId:t,webauthn:s})}async _authenticate({factorId:t,webauthn:{rpId:e=typeof window<"u"?window.location.hostname:void 0,rpOrigins:s=typeof window<"u"?[window.location.origin]:void 0,signal:o}={}},r){if(!e)return{data:null,error:new AuthError("rpId is required for WebAuthn authentication")};try{if(!browserSupportsWebAuthn())return{data:null,error:new AuthUnknownError("Browser does not support WebAuthn",null)};const{data:a,error:i}=await this.challenge({factorId:t,webauthn:{rpId:e,rpOrigins:s},signal:o},{request:r});if(!a)return{data:null,error:i};const{webauthn:l}=a;return this._verify({factorId:t,challengeId:a.challengeId,webauthn:{type:l.type,rpId:e,rpOrigins:s,credential_response:l.credential_response}})}catch(a){return isAuthError(a)?{data:null,error:a}:{data:null,error:new AuthUnknownError("Unexpected error in authenticate",a)}}}async _register({friendlyName:t,webauthn:{rpId:e=typeof window<"u"?window.location.hostname:void 0,rpOrigins:s=typeof window<"u"?[window.location.origin]:void 0,signal:o}={}},r){if(!e)return{data:null,error:new AuthError("rpId is required for WebAuthn registration")};try{if(!browserSupportsWebAuthn())return{data:null,error:new AuthUnknownError("Browser does not support WebAuthn",null)};const{data:a,error:i}=await this._enroll({friendlyName:t});if(!a)return await this.client.mfa.listFactors().then(d=>{var m;return(m=d.data)===null||m===void 0?void 0:m.all.find(h=>h.factor_type==="webauthn"&&h.friendly_name===t&&h.status!=="unverified")}).then(d=>d?this.client.mfa.unenroll({factorId:d?.id}):void 0),{data:null,error:i};const{data:l,error:u}=await this._challenge({factorId:a.id,friendlyName:a.friendly_name,webauthn:{rpId:e,rpOrigins:s},signal:o},{create:r});return l?this._verify({factorId:a.id,challengeId:l.challengeId,webauthn:{rpId:e,rpOrigins:s,type:l.webauthn.type,credential_response:l.webauthn.credential_response}}):{data:null,error:u}}catch(a){return isAuthError(a)?{data:null,error:a}:{data:null,error:new AuthUnknownError("Unexpected error in register",a)}}}}polyfillGlobalThis();const DEFAULT_OPTIONS={url:GOTRUE_URL,storageKey:STORAGE_KEY$2,autoRefreshToken:!0,persistSession:!0,detectSessionInUrl:!0,headers:DEFAULT_HEADERS$1,flowType:"implicit",debug:!1,hasCustomAuthorizationHeader:!1,throwOnError:!1,lockAcquireTimeout:5e3,skipAutoInitialize:!1};async function lockNoOp(n,t,e){return await e()}const GLOBAL_JWKS={};class GoTrueClient{get jwks(){var t,e;return(e=(t=GLOBAL_JWKS[this.storageKey])===null||t===void 0?void 0:t.jwks)!==null&&e!==void 0?e:{keys:[]}}set jwks(t){GLOBAL_JWKS[this.storageKey]=Object.assign(Object.assign({},GLOBAL_JWKS[this.storageKey]),{jwks:t})}get jwks_cached_at(){var t,e;return(e=(t=GLOBAL_JWKS[this.storageKey])===null||t===void 0?void 0:t.cachedAt)!==null&&e!==void 0?e:Number.MIN_SAFE_INTEGER}set jwks_cached_at(t){GLOBAL_JWKS[this.storageKey]=Object.assign(Object.assign({},GLOBAL_JWKS[this.storageKey]),{cachedAt:t})}constructor(t){var e,s,o;this.userStorage=null,this.memoryStorage=null,this.stateChangeEmitters=new Map,this.autoRefreshTicker=null,this.autoRefreshTickTimeout=null,this.visibilityChangedCallback=null,this.refreshingDeferred=null,this.initializePromise=null,this.detectSessionInUrl=!0,this.hasCustomAuthorizationHeader=!1,this.suppressGetSessionWarning=!1,this.lockAcquired=!1,this.pendingInLock=[],this.broadcastChannel=null,this.logger=console.log;const r=Object.assign(Object.assign({},DEFAULT_OPTIONS),t);
51if(this.storageKey=r.storageKey,this.instanceID=(e=GoTrueClient.nextInstanceID[this.storageKey])!==null&&e!==void 0?e:0,GoTrueClient.nextInstanceID[this.storageKey]=this.instanceID+1,this.logDebugMessages=!!r.debug,typeof r.debug=="function"&&(this.logger=r.debug),this.instanceID>0&&isBrowser$1()){const a=`${this._logPrefix()} Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key.`;console.warn(a),this.logDebugMessages&&console.trace(a)}if(this.persistSession=r.persistSession,this.autoRefreshToken=r.autoRefreshToken,this.admin=new GoTrueAdminApi({url:r.url,headers:r.headers,fetch:r.fetch}),this.url=r.url,this.headers=r.headers,this.fetch=resolveFetch$1(r.fetch),this.lock=r.lock||lockNoOp,this.detectSessionInUrl=r.detectSessionInUrl,this.flowType=r.flowType,this.hasCustomAuthorizationHeader=r.hasCustomAuthorizationHeader,this.throwOnError=r.throwOnError,this.lockAcquireTimeout=r.lockAcquireTimeout,r.lock?this.lock=r.lock:this.persistSession&&isBrowser$1()&&(!((s=globalThis?.navigator)===null||s===void 0)&&s.locks)?this.lock=navigatorLock:this.lock=lockNoOp,this.jwks||(this.jwks={keys:[]},this.jwks_cached_at=Number.MIN_SAFE_INTEGER),this.mfa={verify:this._verify.bind(this),enroll:this._enroll.bind(this),unenroll:this._unenroll.bind(this),challenge:this._challenge.bind(this),listFactors:this._listFactors.bind(this),challengeAndVerify:this._challengeAndVerify.bind(this),getAuthenticatorAssuranceLevel:this._getAuthenticatorAssuranceLevel.bind(this),webauthn:new WebAuthnApi(this)},this.oauth={getAuthorizationDetails:this._getAuthorizationDetails.bind(this),approveAuthorization:this._approveAuthorization.bind(this),denyAuthorization:this._denyAuthorization.bind(this),listGrants:this._listOAuthGrants.bind(this),revokeGrant:this._revokeOAuthGrant.bind(this)},this.persistSession?(r.storage?this.storage=r.storage:supportsLocalStorage()?this.storage=globalThis.localStorage:(this.memoryStorage={},this.storage=memoryLocalStorageAdapter(this.memoryStorage)),r.userStorage&&(this.userStorage=r.userStorage)):(this.memoryStorage={},this.storage=memoryLocalStorageAdapter(this.memoryStorage)),isBrowser$1()&&globalThis.BroadcastChannel&&this.persistSession&&this.storageKey){try{this.broadcastChannel=new globalThis.BroadcastChannel(this.storageKey)}catch(a){console.error("Failed to create a new BroadcastChannel, multi-tab state changes will not be available",a)}(o=this.broadcastChannel)===null||o===void 0||o.addEventListener("message",async a=>{this._debug("received broadcast notification from other tab or client",a);try{await this._notifyAllSubscribers(a.data.event,a.data.session,!1)}catch(i){this._debug("#broadcastChannel","error",i)}})}r.skipAutoInitialize||this.initialize().catch(a=>{this._debug("#initialize()","error",a)})}isThrowOnErrorEnabled(){return this.throwOnError}_returnResult(t){if(this.throwOnError&&t&&t.error)throw t.error;return t}_logPrefix(){return`GoTrueClient@${this.storageKey}:${this.instanceID} (${version$1}) ${new Date().toISOString()}`}_debug(...t){return this.logDebugMessages&&this.logger(this._logPrefix(),...t),this}async initialize(){return this.initializePromise?await this.initializePromise:(this.initializePromise=(async()=>await this._acquireLock(this.lockAcquireTimeout,async()=>await this._initialize()))(),await this.initializePromise)}async _initialize(){var t;try{let e={},s="none";if(isBrowser$1()&&(e=parseParametersFromURL(window.location.href),this._isImplicitGrantCallback(e)?s="implicit":await this._isPKCECallback(e)&&(s="pkce")),isBrowser$1()&&this.detectSessionInUrl&&s!=="none"){const{data:o,error:r}=await this._getSessionFromURL(e,s);if(r){if(this._debug("#_initialize()","error detecting session from URL",r),isAuthImplicitGrantRedirectError(r)){const l=(t=r.details)===null||t===void 0?void 0:t.code;if(l==="identity_already_exists"||l==="identity_not_found"||l==="single_identity_not_deletable")return{error:r}}return{error:r}}const{session:a,redirectType:i}=o;return this._debug("#_initialize()","detected session in URL",a,"redirect type",i),await this._saveSession(a),setTimeout(async()=>{i==="recovery"?await this._notifyAllSubscribers("PASSWORD_RECOVERY",a):await this._notifyAllSubscribers("SIGNED_IN",a)},0),{error:null}}return await this._recoverAndRefresh(),{error:null}}catch(e){return isAuthError(e)?this._returnResult({error:e}):this._returnResult({error:new AuthUnknownError("Unexpected error during initialization",e)})}finally{await this._handleVisibilityChange(),this._debug("#_initialize()","end")}}async signInAnonymously(t){var e,s,o;try{const r=await _request(this.fetch,"POST",`${this.url}/signup`,{headers:this.headers,body:{data:(s=(e=t?.options)===null||e===void 0?void 0:e.data)!==null&&s!==void 0?s:{}
51,gotrue_meta_security:{captcha_token:(o=t?.options)===null||o===void 0?void 0:o.captchaToken}},xform:_sessionResponse}),{data:a,error:i}=r;if(i||!a)return this._returnResult({data:{user:null,session:null},error:i});const l=a.session,u=a.user;return a.session&&(await this._saveSession(a.session),await this._notifyAllSubscribers("SIGNED_IN",l)),this._returnResult({data:{user:u,session:l},error:null})}catch(r){if(isAuthError(r))return this._returnResult({data:{user:null,session:null},error:r});throw r}}async signUp(t){var e,s,o;try{let r;if("email"in t){const{email:d,password:m,options:h}=t;let f=null,g=null;this.flowType==="pkce"&&([f,g]=await getCodeChallengeAndMethod(this.storage,this.storageKey)),r=await _request(this.fetch,"POST",`${this.url}/signup`,{headers:this.headers,redirectTo:h?.emailRedirectTo,body:{email:d,password:m,data:(e=h?.data)!==null&&e!==void 0?e:{},gotrue_meta_security:{captcha_token:h?.captchaToken},code_challenge:f,code_challenge_method:g},xform:_sessionResponse})}else if("phone"in t){const{phone:d,password:m,options:h}=t;r=await _request(this.fetch,"POST",`${this.url}/signup`,{headers:this.headers,body:{phone:d,password:m,data:(s=h?.data)!==null&&s!==void 0?s:{},channel:(o=h?.channel)!==null&&o!==void 0?o:"sms",gotrue_meta_security:{captcha_token:h?.captchaToken}},xform:_sessionResponse})}else throw new AuthInvalidCredentialsError("You must provide either an email or phone number and a password");const{data:a,error:i}=r;if(i||!a)return await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),this._returnResult({data:{user:null,session:null},error:i});const l=a.session,u=a.user;return a.session&&(await this._saveSession(a.session),await this._notifyAllSubscribers("SIGNED_IN",l)),this._returnResult({data:{user:u,session:l},error:null})}catch(r){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(r))return this._returnResult({data:{user:null,session:null},error:r});throw r}}async signInWithPassword(t){try{let e;if("email"in t){const{email:r,password:a,options:i}=t;e=await _request(this.fetch,"POST",`${this.url}/token?grant_type=password`,{headers:this.headers,body:{email:r,password:a,gotrue_meta_security:{captcha_token:i?.captchaToken}},xform:_sessionResponsePassword})}else if("phone"in t){const{phone:r,password:a,options:i}=t;e=await _request(this.fetch,"POST",`${this.url}/token?grant_type=password`,{headers:this.headers,body:{phone:r,password:a,gotrue_meta_security:{captcha_token:i?.captchaToken}},xform:_sessionResponsePassword})}else throw new AuthInvalidCredentialsError("You must provide either an email or phone number and a password");const{data:s,error:o}=e;if(o)return this._returnResult({data:{user:null,session:null},error:o});
51if(!s||!s.session||!s.user){const r=new AuthInvalidTokenResponseError;return this._returnResult({data:{user:null,session:null},error:r})}return s.session&&(await this._saveSession(s.session),await this._notifyAllSubscribers("SIGNED_IN",s.session)),this._returnResult({data:Object.assign({user:s.user,session:s.session},s.weak_password?{weakPassword:s.weak_password}:null),error:o})}catch(e){if(isAuthError(e))return this._returnResult({data:{user:null,session:null},error:e});throw e}}async signInWithOAuth(t){var e,s,o,r;return await this._handleProviderSignIn(t.provider,{redirectTo:(e=t.options)===null||e===void 0?void 0:e.redirectTo,scopes:(s=t.options)===null||s===void 0?void 0:s.scopes,queryParams:(o=t.options)===null||o===void 0?void 0:o.queryParams,skipBrowserRedirect:(r=t.options)===null||r===void 0?void 0:r.skipBrowserRedirect})}async exchangeCodeForSession(t){return await this.initializePromise,this._acquireLock(this.lockAcquireTimeout,async()=>this._exchangeCodeForSession(t))}async signInWithWeb3(t){const{chain:e}=t;switch(e){case"ethereum":return await this.signInWithEthereum(t);case"solana":return await this.signInWithSolana(t);default:throw new Error(`@supabase/auth-js: Unsupported chain "${e}"`)}}async signInWithEthereum(t){var e,s,o,r,a,i,l,u,d,m,h;let f,g;if("message"in t)f=t.message,g=t.signature;else{const{chain:b,wallet:x,statement:y,options:E}=t;let k;if(isBrowser$1())if(typeof x=="object")k=x;else{const L=window;if("ethereum"in L&&typeof L.ethereum=="object"&&"request"in L.ethereum&&typeof L.ethereum.request=="function")k=L.ethereum;else throw new Error("@supabase/auth-js: No compatible Ethereum wallet interface on the window object (window.ethereum) detected. Make sure the user already has a wallet installed and connected for this app. Prefer passing the wallet interface object directly to signInWithWeb3({ chain: 'ethereum', wallet: resolvedUserWallet }) instead.")}else{if(typeof x!="object"||!E?.url)throw new Error("@supabase/auth-js: Both wallet and url must be specified in non-browser environments.");k=x}const T=new URL((e=E?.url)!==null&&e!==void 0?e:window.location.href),A=await k.request({method:"eth_requestAccounts"}).then(L=>L).catch(()=>{throw new Error("@supabase/auth-js: Wallet method eth_requestAccounts is missing or invalid")});if(!A||A.length===0)throw new Error("@supabase/auth-js: No accounts available. Please ensure the wallet is connected.");const N=getAddress(A[0]);let S=(s=E?.signInWithEthereum)===null||s===void 0?void 0:s.chainId;if(!S){const L=await k.request({method:"eth_chainId"});S=fromHex(L)}const M={domain:T.host,address:N,statement:y,uri:T.href,version:"1",chainId:S,nonce:(o=E?.signInWithEthereum)===null||o===void 0?void 0:o.nonce,issuedAt:(a=(r=E?.signInWithEthereum)===null||r===void 0?void 0:r.issuedAt)!==null&&a!==void 0?a:new Date,expirationTime:(i=E?.signInWithEthereum)===null||i===void 0?void 0:i.expirationTime,notBefore:(l=E?.signInWithEthereum)===null||l===void 0?void 0:l.notBefore,requestId:(u=E?.signInWithEthereum)===null||u===void 0?void 0:u.requestId,resources:(d=E?.signInWithEthereum)===null||d===void 0?void 0:d.resources};f=createSiweMessage(M),g=await k.request({method:"personal_sign",params:[toHex(f),N]})}try{const{data:b,error:x}=await _request(this.fetch,"POST",`${this.url}/token?grant_type=web3`,{headers:this.headers,body:Object.assign({chain:"ethereum",message:f,signature:g},!((m=t.options)===null||m===void 0)&&m.captchaToken?{gotrue_meta_security:{captcha_token:(h=t.options)===null||h===void 0?void 0:h.captchaToken}}:null),xform:_sessionResponse});if(x)throw x;
51if(!b||!b.session||!b.user){const y=new AuthInvalidTokenResponseError;return this._returnResult({data:{user:null,session:null},error:y})}return b.session&&(await this._saveSession(b.session),await this._notifyAllSubscribers("SIGNED_IN",b.session)),this._returnResult({data:Object.assign({},b),error:x})}catch(b){if(isAuthError(b))return this._returnResult({data:{user:null,session:null},error:b});throw b}}async signInWithSolana(t){var e,s,o,r,a,i,l,u,d,m,h,f;let g,b;if("message"in t)g=t.message,b=t.signature;else{const{chain:x,wallet:y,statement:E,options:k}=t;let T;if(isBrowser$1())if(typeof y=="object")T=y;else{const N=window;if("solana"in N&&typeof N.solana=="object"&&("signIn"in N.solana&&typeof N.solana.signIn=="function"||"signMessage"in N.solana&&typeof N.solana.signMessage=="function"))T=N.solana;else throw new Error("@supabase/auth-js: No compatible Solana wallet interface on the window object (window.solana) detected. Make sure the user already has a wallet installed and connected for this app. Prefer passing the wallet interface object directly to signInWithWeb3({ chain: 'solana', wallet: resolvedUserWallet }) instead.")}else{if(typeof y!="object"||!k?.url)throw new Error("@supabase/auth-js: Both wallet and url must be specified in non-browser environments.");T=y}const A=new URL((e=k?.url)!==null&&e!==void 0?e:window.location.href);if("signIn"in T&&T.signIn){const N=await T.signIn(Object.assign(Object.assign(Object.assign({issuedAt:new Date().toISOString()},k?.signInWithSolana),{version:"1",domain:A.host,uri:A.href}),E?{statement:E}:null));let S;if(Array.isArray(N)&&N[0]&&typeof N[0]=="object")S=N[0];else if(N&&typeof N=="object"&&"signedMessage"in N&&"signature"in N)S=N;else throw new Error("@supabase/auth-js: Wallet method signIn() returned unrecognized value");if("signedMessage"in S&&"signature"in S&&(typeof S.signedMessage=="string"||S.signedMessage instanceof Uint8Array)&&S.signature instanceof Uint8Array)g=typeof S.signedMessage=="string"?S.signedMessage:new TextDecoder().decode(S.signedMessage),b=S.signature;else throw new Error("@supabase/auth-js: Wallet method signIn() API returned object without signedMessage and signature fields")}else{if(!("signMessage"in T)||typeof T.signMessage!="function"||!("publicKey"in T)||typeof T!="object"||!T.publicKey||!("toBase58"in T.publicKey)||typeof T.publicKey.toBase58!="function")throw new Error("@supabase/auth-js: Wallet does not have a compatible signMessage() and publicKey.toBase58() API");g=[`${A.host} wants you to sign in with your Solana account:`,T.publicKey.toBase58(),...E?["",E,""]:[""],"Version: 1",`URI: ${A.href}`,`Issued At: ${(o=(s=k?.signInWithSolana)===null||s===void 0?void 0:s.issuedAt)!==null&&o!==void 0?o:new Date().toISOString()}`,...!((r=k?.signInWithSolana)===null||r===void 0)&&r.notBefore?[`Not Before: ${k.signInWithSolana.notBefore}`]:[],...!((a=k?.signInWithSolana)===null||a===void 0)&&a.expirationTime?[`Expiration Time: ${k.signInWithSolana.expirationTime}`]:[],...!((i=k?.signInWithSolana)===null||i===void 0)&&i.chainId?[`Chain ID: ${k.signInWithSolana.chainId}`]:[],...!((l=k?.signInWithSolana)===null||l===void 0)&&l.nonce?[`Nonce: ${k.signInWithSolana.nonce}`]:[],...!((u=k?.signInWithSolana)===null||u===void 0)&&u.requestId?[`Request ID: ${k.signInWithSolana.requestId}`]:[],...!((m=(d=k?.signInWithSolana)===null||d===void 0?void 0:d.resources)===null||m===void 0)&&m.length?["Resources",...k.signInWithSolana.resources.map(S=>`- ${S}`)]:[]].join(`
52`);const N=await T.signMessage(new TextEncoder().encode(g),"utf8");if(!N||!(N instanceof Uint8Array))throw new Error("@supabase/auth-js: Wallet signMessage() API returned an recognized value");b=N}}try{const{data:x,error:y}=await _request(this.fetch,"POST",`${this.url}/token?grant_type=web3`,{headers:this.headers,body:Object.assign({chain:"solana",message:g,signature:bytesToBase64URL(b)},!((h=t.options)===null||h===void 0)&&h.captchaToken?{gotrue_meta_security:{captcha_token:(f=t.options)===null||f===void 0?void 0:f.captchaToken}}:null),xform:_sessionResponse});if(y)throw y;
52if(!x||!x.session||!x.user){const E=new AuthInvalidTokenResponseError;return this._returnResult({data:{user:null,session:null},error:E})}return x.session&&(await this._saveSession(x.session),await this._notifyAllSubscribers("SIGNED_IN",x.session)),this._returnResult({data:Object.assign({},x),error:y})}catch(x){if(isAuthError(x))return this._returnResult({data:{user:null,session:null},error:x});throw x}}async _exchangeCodeForSession(t){const e=await getItemAsync(this.storage,`${this.storageKey}-code-verifier`),[s,o]=(e??"").split("/");try{if(!s&&this.flowType==="pkce")throw new AuthPKCECodeVerifierMissingError;const{data:r,error:a}=await _request(this.fetch,"POST",`${this.url}/token?grant_type=pkce`,{headers:this.headers,body:{auth_code:t,code_verifier:s},xform:_sessionResponse});if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),a)throw a;if(!r||!r.session||!r.user){const i=new AuthInvalidTokenResponseError;return this._returnResult({data:{user:null,session:null,redirectType:null},error:i}
52)}return r.session&&(await this._saveSession(r.session),await this._notifyAllSubscribers("SIGNED_IN",r.session)),this._returnResult({data:Object.assign(Object.assign({},r),{redirectType:o??null}),error:a})}catch(r){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(r))return this._returnResult({data:{user:null,session:null,redirectType:null},error:r});throw r}}async signInWithIdToken(t){try{const{options:e,provider:s,token:o,access_token:r,nonce:a}=t,i=await _request(this.fetch,"POST",`${this.url}/token?grant_type=id_token`,{headers:this.headers,body:{provider:s,id_token:o,access_token:r,nonce:a,gotrue_meta_security:{captcha_token:e?.captchaToken}},xform:_sessionResponse}),{data:l,error:u}=i;if(u)return this._returnResult({data:{user:null,session:null},error:u});if(!l||!l.session||!l.user){const d=new AuthInvalidTokenResponseError;return this._returnResult({data:{user:null,session:null},error:d})}return l.session&&(await this._saveSession(l.session),await this._notifyAllSubscribers("SIGNED_IN",l.session)),this._returnResult({data:l,error:u})}catch(e){if(isAuthError(e))return this._returnResult({data:{user:null,session:null},error:e});throw e}}async signInWithOtp(t){var e,s,o,r,a;try{if("email"in t){const{email:i,options:l}=t;let u=null,d=null;this.flowType==="pkce"&&([u,d]=await getCodeChallengeAndMethod(this.storage,this.storageKey));const{error:m}=await _request(this.fetch,"POST",`${this.url}/otp`,{headers:this.headers,body:{email:i,data:(e=l?.data)!==null&&e!==void 0?e:{},create_user:(s=l?.shouldCreateUser)!==null&&s!==void 0?s:!0,gotrue_meta_security:{captcha_token:l?.captchaToken},code_challenge:u,code_challenge_method:d},redirectTo:l?.emailRedirectTo});return this._returnResult({data:{user:null,session:null},error:m})}if("phone"in t){const{phone:i,options:l}=t,{data:u,error:d}=await _request(this.fetch,"POST",`${this.url}/otp`,{headers:this.headers,body:{phone:i,data:(o=l?.data)!==null&&o!==void 0?o:{},create_user:(r=l?.shouldCreateUser)!==null&&r!==void 0?r:!0,gotrue_meta_security:{captcha_token:l?.captchaToken},channel:(a=l?.channel)!==null&&a!==void 0?a:"sms"}});return this._returnResult({data:{user:null,session:null,messageId:u?.message_id},error:d})}throw new AuthInvalidCredentialsError("You must provide either an email or phone number.")}catch(i){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(i))return this._returnResult({data:{user:null,session:null},error:i});throw i}}async verifyOtp(t){var e,s;try{let o,r;"options"in t&&(o=(e=t.options)===null||e===void 0?void 0:e.redirectTo,r=(s=t.options)===null||s===void 0?void 0:s.captchaToken);const{data:a,error:i}=await _request(this.fetch,"POST",`${this.url}/verify`,{headers:this.headers,body:Object.assign(Object.assign({},t),{gotrue_meta_security:{captcha_token:r}}),redirectTo:o,xform:_sessionResponse});if(i)throw i;if(!a)throw new Error("An error occurred on token verification.");const l=a.session,u=a.user;return l?.access_token&&(await this._saveSession(l),await this._notifyAllSubscribers(t.type=="recovery"?"PASSWORD_RECOVERY":"SIGNED_IN",l)),this._returnResult({data:{user:u,session:l},error:null})}catch(o){if(isAuthError(o))return this._returnResult({data:{user:null,session:null},error:o});throw o}}async signInWithSSO(t){var e,s,o,r,a;try{let i=null,l=null;this.flowType==="pkce"&&([i,l]=await getCodeChallengeAndMethod(this.storage,this.storageKey));const u=await _request(this.fetch,"POST",`${this.url}/sso`,{body:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},"providerId"in t?{provider_id:t.providerId}:null),"domain"in t?{domain:t.domain}:null),{redirect_to:(s=(e=t.options)===null||e===void 0?void 0:e.redirectTo)!==null&&s!==void 0?s:void 0}),!((o=t?.options)===null||o===void 0)&&o.captchaToken?{gotrue_meta_security:{captcha_token:t.options.captchaToken}}:null),{skip_http_redirect:!0,code_challenge:i,code_challenge_method:l}),headers:this.headers,xform:_ssoResponse});return!((r=u.data)===null||r===void 0)&&r.url&&isBrowser$1()&&!(!((a=t.options)===null||a===void 0)&&a.skipBrowserRedirect)&&window.location.assign(u.data.url),this._returnResult(u)}catch(i){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(i))return this._returnResult({data:null,error:i});throw i}}async reauthenticate(){return await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>await this._reauthenticate())}async _reauthenticate(){try{return await this._useSession(async t=>{const{data:{session:e},error:s}=t;if(s)throw s;if(!e)throw new AuthSessionMissingError;
52const{error:o}=await _request(this.fetch,"GET",`${this.url}/reauthenticate`,{headers:this.headers,jwt:e.access_token});return this._returnResult({data:{user:null,session:null},error:o})})}catch(t){if(isAuthError(t))return this._returnResult({data:{user:null,session:null},error:t});throw t}}async resend(t){try{const e=`${this.url}/resend`;if("email"in t){const{email:s,type:o,options:r}=t,{error:a}=await _request(this.fetch,"POST",e,{headers:this.headers,body:{email:s,type:o,gotrue_meta_security:{captcha_token:r?.captchaToken}},redirectTo:r?.emailRedirectTo});return this._returnResult({data:{user:null,session:null},error:a})}else if("phone"in t){const{phone:s,type:o,options:r}=t,{data:a,error:i}=await _request(this.fetch,"POST",e,{headers:this.headers,body:{phone:s,type:o,gotrue_meta_security:{captcha_token:r?.captchaToken}}});return this._returnResult({data:{user:null,session:null,messageId:a?.message_id},error:i})}throw new AuthInvalidCredentialsError("You must provide either an email or phone number and a type")}catch(e){if(isAuthError(e))return this._returnResult({data:{user:null,session:null},error:e});throw e}}async getSession(){return await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>this._useSession(async e=>e))}async _acquireLock(t,e){this._debug("#_acquireLock","begin",t);try{if(this.lockAcquired){const s=this.pendingInLock.length?this.pendingInLock[this.pendingInLock.length-1]:Promise.resolve(),o=(async()=>(await s,await e()))();return this.pendingInLock.push((async()=>{try{await o}catch{}})()),o}return await this.lock(`lock:${this.storageKey}`,t,async()=>{this._debug("#_acquireLock","lock acquired for storage key",this.storageKey);try{this.lockAcquired=!0;const s=e();for(this.pendingInLock.push((async()=>{try{await s}catch{}})()),await s;this.pendingInLock.length;){const o=[...this.pendingInLock];await Promise.all(o),this.pendingInLock.splice(0,o.length)}return await s}finally{this._debug("#_acquireLock","lock released for storage key",this.storageKey),this.lockAcquired=!1}})}finally{this._debug("#_acquireLock","end")}}async _useSession(t){this._debug("#_useSession","begin");try{const e=await this.__loadSession();return await t(e)}finally{this._debug("#_useSession","end")}}async __loadSession(){this._debug("#__loadSession()","begin"),this.lockAcquired||this._debug("#__loadSession()","used outside of an acquired lock!",new Error().stack);try{let t=null;const e=await getItemAsync(this.storage,this.storageKey);if(this._debug("#getSession()","session from storage",e),e!==null&&(this._isValidSession(e)?t=e:(this._debug("#getSession()","session from storage is not valid"),await this._removeSession())),!t)return{data:{session:null},error:null};const s=t.expires_at?t.expires_at*1e3-Date.now()<EXPIRY_MARGIN_MS:!1;if(this._debug("#__loadSession()",`session has${s?"":" not"} expired`,"expires_at",t.expires_at),!s){if(this.userStorage){const a=await getItemAsync(this.userStorage,this.storageKey+"-user");a?.user?t.user=a.user:t.user=userNotAvailableProxy()}if(this.storage.isServer&&t.user&&!t.user.__isUserNotAvailableProxy){const a={value:this.suppressGetSessionWarning};t.user=insecureUserWarningProxy(t.user,a),a.value&&(this.suppressGetSessionWarning=!0)}return{data:{session:t},error:null}}const{data:o,error:r}=await this._callRefreshToken(t.refresh_token);return r?this._returnResult({data:{session:null},error:r}):this._returnResult({data:{session:o},error:null})}finally{this._debug("#__loadSession()","end")}}async getUser(t){if(t)return await this._getUser(t);await this.initializePromise;const e=await this._acquireLock(this.lockAcquireTimeout,async()=>await this._getUser());return e.data.user&&(this.suppressGetSessionWarning=!0),e}async _getUser(t){try{return t?await _request(this.fetch,"GET",`${this.url}/user`,{headers:this.headers,jwt:t,xform:_userResponse}):await this._useSession(async e=>{var s,o,r;const{data:a,error:i}=e;if(i)throw i;return!(!((s=a.session)===null||s===void 0)&&s.access_token)&&!this.hasCustomAuthorizationHeader?{data:{user:null},error:new AuthSessionMissingError}:await _request(this.fetch,"GET",`${this.url}/user`,{headers:this.headers,jwt:(r=(o=a.session)===null||o===void 0?void 0:o.access_token)!==null&&r!==void 0?r:void 0,xform:_userResponse})})}catch(e){if(isAuthError(e))return isAuthSessionMissingError(e)&&(await this._removeSession(),await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`)),this._returnResult({data:{user:null},error:e});throw e}}async updateUser(t,e={}){return await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>await this._updateUser(t,e))}async _updateUser(t,e={}){try{return await this._useSession(async s=>{const{data:o,error:r}=s;if(r)throw r;if(!o.session)throw new AuthSessionMissingError;
52const a=o.session;let i=null,l=null;this.flowType==="pkce"&&t.email!=null&&([i,l]=await getCodeChallengeAndMethod(this.storage,this.storageKey));const{data:u,error:d}=await _request(this.fetch,"PUT",`${this.url}/user`,{headers:this.headers,redirectTo:e?.emailRedirectTo,body:Object.assign(Object.assign({},t),{code_challenge:i,code_challenge_method:l}),jwt:a.access_token,xform:_userResponse});if(d)throw d;return a.user=u.user,await this._saveSession(a),await this._notifyAllSubscribers("USER_UPDATED",a),this._returnResult({data:{user:a.user},error:null})})}catch(s){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(s))return this._returnResult({data:{user:null},error:s});throw s}}async setSession(t){return await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>await this._setSession(t))}async _setSession(t){try{if(!t.access_token||!t.refresh_token)throw new AuthSessionMissingError;const e=Date.now()/1e3;let s=e,o=!0,r=null;const{payload:a}=decodeJWT(t.access_token);if(a.exp&&(s=a.exp,o=s<=e),o){const{data:i,error:l}=await this._callRefreshToken(t.refresh_token);if(l)return this._returnResult({data:{user:null,session:null},error:l});if(!i)return{data:{user:null,session:null},error:null};r=i}else{const{data:i,error:l}=await this._getUser(t.access_token);if(l)return this._returnResult({data:{user:null,session:null},error:l});r={access_token:t.access_token,refresh_token:t.refresh_token,user:i.user,token_type:"bearer",expires_in:s-e,expires_at:s},await this._saveSession(r),await this._notifyAllSubscribers("SIGNED_IN",r)}return this._returnResult({data:{user:r.user,session:r},error:null})}catch(e){if(isAuthError(e))return this._returnResult({data:{session:null,user:null},error:e});throw e}}async refreshSession(t){return await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>await this._refreshSession(t))}async _refreshSession(t){try{return await this._useSession(async e=>{var s;if(!t){const{data:a,error:i}=e;if(i)throw i;t=(s=a.session)!==null&&s!==void 0?s:void 0}if(!t?.refresh_token)throw new AuthSessionMissingError;const{data:o,error:r}=await this._callRefreshToken(t.refresh_token);return r?this._returnResult({data:{user:null,session:null},error:r}):o?this._returnResult({data:{user:o.user,session:o},error:null}):this._returnResult({data:{user:null,session:null},error:null})})}catch(e){if(isAuthError(e))return this._returnResult({data:{user:null,session:null},error:e});throw e}}async _getSessionFromURL(t,e){try{if(!isBrowser$1())throw new AuthImplicitGrantRedirectError("No browser detected.");if(t.error||t.error_description||t.error_code)throw new AuthImplicitGrantRedirectError(t.error_description||"Error in URL with unspecified error_description",{error:t.error||"unspecified_error",code:t.error_code||"unspecified_code"});switch(e){case"implicit":if(this.flowType==="pkce")throw new AuthPKCEGrantCodeExchangeError("Not a valid PKCE flow url.");break;case"pkce":if(this.flowType==="implicit")throw new AuthImplicitGrantRedirectError("Not a valid implicit grant flow url.");break;default:}if(e==="pkce"){if(this._debug("#_initialize()","begin","is PKCE flow",!0),!t.code)throw new AuthPKCEGrantCodeExchangeError("No code detected.");const{data:E,error:k}=await this._exchangeCodeForSession(t.code);if(k)throw k;const T=new URL(window.location.href);return T.searchParams.delete("code"),window.history.replaceState(window.history.state,"",T.toString()),{data:{session:E.session,redirectType:null},error:null}}const{provider_token:s,provider_refresh_token:o,access_token:r,refresh_token:a,expires_in:i,expires_at:l,token_type:u}=t;if(!r||!i||!a||!u)throw new AuthImplicitGrantRedirectError("No session defined in URL");const d=Math.round(Date.now()/1e3),m=parseInt(i);let h=d+m;l&&(h=parseInt(l));const f=h-d;f*1e3<=AUTO_REFRESH_TICK_DURATION_MS&&console.warn(`@supabase/gotrue-js: Session as retrieved from URL expires in ${f}s, should have been closer to ${m}s`);const g=h-m;d-g>=120?console.warn("@supabase/gotrue-js: Session as retrieved from URL was issued over 120s ago, URL could be stale",g,h,d):d-g<0&&console.warn("@supabase/gotrue-js: Session as retrieved from URL was issued in the future? Check the device clock for skew",g,h,d);const{data:b,error:x}=await this._getUser(r);if(x)throw x;const y={provider_token:s,provider_refresh_token:o,access_token:r,expires_in:m,expires_at:h,refresh_token:a,token_type:u,user:b.user};return window.location.hash="",this._debug("#_getSessionFromURL()","clearing window.location.hash"),this._returnResult({data:{session:y,redirectType:t.type},error:null})}catch(s){if(isAuthError(s))return this._returnResult({data:{session:null,redirectType:null},error:s});throw s}}_isImplicitGrantCallback(t){return typeof this.detectSessionInUrl=="function"?this.detectSessionInUrl(new URL(window.location.href),t):!!(t.access_token||t.error_description)}async _isPKCECallback(t){const e=await getItemAsync(this.storage,`${this.storageKey}-code-verifier`);return!!(t.code&&e)}async signOut(t={scope:"global"}){return await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>await this._signOut(t))}async _signOut({scope:t}={scope:"global"}){return await this._useSession(async e=>{var s;const{data:o,error:r}=e;if(r&&!isAuthSessionMissingError(r))return this._returnResult({error:r});const a=(s=o.session)===null||s===void 0?void 0:s.access_token;if(a){const{error:i}=await this.admin.signOut(a,t);if(i&&!(isAuthApiError(i)&&(i.status===404||i.status===401||i.status===403)||isAuthSessionMissingError(i)))return this._returnResult({error:i})}return t!=="others"&&(await this._removeSession(),await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`)),this._returnResult({error:null})})}onAuthStateChange(t){const e=generateCallbackId(),s={id:e,callback:t,unsubscribe:()=>{this._debug("#unsubscribe()","state change callback with id removed",e),this.stateChangeEmitters.delete(e)}};return this._debug("#onAuthStateChange()","registered callback with id",e),this.stateChangeEmitters.set(e,s),(async()=>(await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>{this._emitInitialSession(e)})))(),{data:{subscription:s}}}async _emitInitialSession(t){return await this._useSession(async e=>{var s,o;try{const{data:{session:r},error:a}=e;if(a)throw a;await((s=this.stateChangeEmitters.get(t))===null||s===void 0?void 0:s.callback("INITIAL_SESSION",r)),this._debug("INITIAL_SESSION","callback id",t,"session",r)}catch(r){await((o=this.stateChangeEmitters.get(t))===null||o===void 0?void 0:o.callback("INITIAL_SESSION",null)),this._debug("INITIAL_SESSION","callback id",t,"error",r),console.error(r)}})}async resetPasswordForEmail(t,e={}){let s=null,o=null;this.flowType==="pkce"&&([s,o]=await getCodeChallengeAndMethod(this.storage,this.storageKey,!0));try{return await _request(this.fetch,"POST",`${this.url}/recover`,{body:{email:t,code_challenge:s,code_challenge_method:o,gotrue_meta_security:{captcha_token:e.captchaToken}},headers:this.headers,redirectTo:e.redirectTo})}catch(r){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(r))return this._returnResult({data:null,error:r});throw r}}async getUserIdentities(){var t;try{const{data:e,error:s}=await this.getUser();if(s)throw s;return this._returnResult({data:{identities:(t=e.user.identities)!==null&&t!==void 0?t:[]},error:null})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}}async linkIdentity(t){return"token"in t?this.linkIdentityIdToken(t):this.linkIdentityOAuth(t)}async linkIdentityOAuth(t){var e;try{const{data:s,error:o}=await this._useSession(async r=>{var a,i,l,u,d;const{data:m,error:h}=r;if(h)throw h;const f=await this._getUrlForProvider(`${this.url}/user/identities/authorize`,t.provider,{redirectTo:(a=t.options)===null||a===void 0?void 0:a.redirectTo,scopes:(i=t.options)===null||i===void 0?void 0:i.scopes,queryParams:(l=t.options)===null||l===void 0?void 0:l.queryParams,skipBrowserRedirect:!0});return await _request(this.fetch,"GET",f,{headers:this.headers,jwt:(d=(u=m.session)===null||u===void 0?void 0:u.access_token)!==null&&d!==void 0?d:void 0})});if(o)throw o;return isBrowser$1()&&!(!((e=t.options)===null||e===void 0)&&e.skipBrowserRedirect)&&window.location.assign(s?.url),this._returnResult({data:{provider:t.provider,url:s?.url},error:null})}catch(s){if(isAuthError(s))return this._returnResult({data:{provider:t.provider,url:null},error:s});throw s}}async linkIdentityIdToken(t){return await this._useSession(async e=>{var s;try{const{error:o,data:{session:r}}=e;if(o)throw o;const{options:a,provider:i,token:l,access_token:u,nonce:d}=t,m=await _request(this.fetch,"POST",`${this.url}/token?grant_type=id_token`,{headers:this.headers,jwt:(s=r?.access_token)!==null&&s!==void 0?s:void 0,body:{provider:i,id_token:l,access_token:u,nonce:d,link_identity:!0,gotrue_meta_security:{captcha_token:a?.captchaToken}},xform:_sessionResponse}),{data:h,error:f}=m;return f?this._returnResult({data:{user:null,session:null},error:f}):!h||!h.session||!h.user?this._returnResult({data:{user:null,session:null}
52,error:new AuthInvalidTokenResponseError}):(h.session&&(await this._saveSession(h.session),await this._notifyAllSubscribers("USER_UPDATED",h.session)),this._returnResult({data:h,error:f}))}catch(o){if(await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`),isAuthError(o))return this._returnResult({data:{user:null,session:null},error:o});throw o}})}async unlinkIdentity(t){try{return await this._useSession(async e=>{var s,o;const{data:r,error:a}=e;if(a)throw a;return await _request(this.fetch,"DELETE",`${this.url}/user/identities/${t.identity_id}`,{headers:this.headers,jwt:(o=(s=r.session)===null||s===void 0?void 0:s.access_token)!==null&&o!==void 0?o:void 0})})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}}async _refreshAccessToken(t){const e=`#_refreshAccessToken(${t.substring(0,5)}...)`;this._debug(e,"begin");try{const s=Date.now();return await retryable(async o=>(o>0&&await sleep(200*Math.pow(2,o-1)),this._debug(e,"refreshing attempt",o),await _request(this.fetch,"POST",`${this.url}/token?grant_type=refresh_token`,{body:{refresh_token:t},headers:this.headers,xform:_sessionResponse})),(o,r)=>{const a=200*Math.pow(2,o);return r&&isAuthRetryableFetchError(r)&&Date.now()+a-s<AUTO_REFRESH_TICK_DURATION_MS})}catch(s){if(this._debug(e,"error",s),isAuthError(s))return this._returnResult({data:{session:null,user:null},error:s});throw s}finally{this._debug(e,"end")}}_isValidSession(t){return typeof t=="object"&&t!==null&&"access_token"in t&&"refresh_token"in t&&"expires_at"in t}async _handleProviderSignIn(t,e){const s=await this._getUrlForProvider(`${this.url}/authorize`,t,{redirectTo:e.redirectTo,scopes:e.scopes,queryParams:e.queryParams});return this._debug("#_handleProviderSignIn()","provider",t,"options",e,"url",s),isBrowser$1()&&!e.skipBrowserRedirect&&window.location.assign(s),{data:{provider:t,url:s},error:null}}async _recoverAndRefresh(){var t,e;const s="#_recoverAndRefresh()";this._debug(s,"begin");try{const o=await getItemAsync(this.storage,this.storageKey);if(o&&this.userStorage){let a=await getItemAsync(this.userStorage,this.storageKey+"-user");!this.storage.isServer&&Object.is(this.storage,this.userStorage)&&!a&&(a={user:o.user},await setItemAsync(this.userStorage,this.storageKey+"-user",a)),o.user=(t=a?.user)!==null&&t!==void 0?t:userNotAvailableProxy()}else if(o&&!o.user&&!o.user){const a=await getItemAsync(this.storage,this.storageKey+"-user");a&&a?.user?(o.user=a.user,await removeItemAsync(this.storage,this.storageKey+"-user"),await setItemAsync(this.storage,this.storageKey,o)):o.user=userNotAvailableProxy()}if(this._debug(s,"session from storage",o),!this._isValidSession(o)){this._debug(s,"session is not valid"),o!==null&&await this._removeSession();return}const r=((e=o.expires_at)!==null&&e!==void 0?e:1/0)*1e3-Date.now()<EXPIRY_MARGIN_MS;if(this._debug(s,`session has${r?"":" not"} expired with margin of ${EXPIRY_MARGIN_MS}s`),r){if(this.autoRefreshToken&&o.refresh_token){const{error:a}=await this._callRefreshToken(o.refresh_token);a&&(console.error(a),isAuthRetryableFetchError(a)||(this._debug(s,"refresh failed with a non-retryable error, removing the session",a),await this._removeSession()))}}else if(o.user&&o.user.__isUserNotAvailableProxy===!0)try{const{data:a,error:i}=await this._getUser(o.access_token);!i&&a?.user?(o.user=a.user,await this._saveSession(o),await this._notifyAllSubscribers("SIGNED_IN",o)):this._debug(s,"could not get user data, skipping SIGNED_IN notification")}catch(a){console.error("Error getting user data:",a),this._debug(s,"error getting user data, skipping SIGNED_IN notification",a)}else await this._notifyAllSubscribers("SIGNED_IN",o)}catch(o){this._debug(s,"error",o),console.error(o);return}finally{this._debug(s,"end")}}async _callRefreshToken(t){var e,s;if(!t)throw new AuthSessionMissingError;if(this.refreshingDeferred)return this.refreshingDeferred.promise;const o=`#_callRefreshToken(${t.substring(0,5)}...)`;this._debug(o,"begin");try{this.refreshingDeferred=new Deferred;const{data:r,error:a}=await this._refreshAccessToken(t);if(a)throw a;if(!r.session)throw new AuthSessionMissingError;await this._saveSession(r.session),await this._notifyAllSubscribers("TOKEN_REFRESHED",r.session);const i={data:r.session,error:null};return this.refreshingDeferred.resolve(i),i}catch(r){if(this._debug(o,"error",r),isAuthError(r)){const a={data:null,error:r};return isAuthRetryableFetchError(r)||await this._removeSession(),(e=this.refreshingDeferred)===null||e===void 0||e.resolve(a),a}throw(s=this.refreshingDeferred)===null||s===void 0||s.reject(r),r}finally{this.refreshingDeferred=null,this._debug(o,"end")}}async _notifyAllSubscribers(t,e,s=!0){const o=`#_notifyAllSubscribers(${t})`;this._debug(o,"begin",e,`broadcast = ${s}`);try{this.broadcastChannel&&s&&this.broadcastChannel.postMessage({event:t,session:e});const r=[],a=Array.from(this.stateChangeEmitters.values()).map(async i=>{try{await i.callback(t,e)}catch(l){r.push(l)}});if(await Promise.all(a),r.length>0){for(let i=0;i<r.length;i+=1)console.error(r[i]);throw r[0]}}finally{this._debug(o,"end")}}async _saveSession(t){this._debug("#_saveSession()",t),this.suppressGetSessionWarning=!0,await removeItemAsync(this.storage,`${this.storageKey}-code-verifier`);const e=Object.assign({},t),s=e.user&&e.user.__isUserNotAvailableProxy===!0;if(this.userStorage){!s&&e.user&&await setItemAsync(this.userStorage,this.storageKey+"-user",{user:e.user});const o=Object.assign({},e);delete o.user;const r=deepClone(o);await setItemAsync(this.storage,this.storageKey,r)}else{const o=deepClone(e);await setItemAsync(this.storage,this.storageKey,o)}}async _removeSession(){this._debug("#_removeSession()"),this.suppressGetSessionWarning=!1,await removeItemAsync(this.storage,this.storageKey),await removeItemAsync(this.storage,this.storageKey+"-code-verifier"),await removeItemAsync(this.storage,this.storageKey+"-user"),this.userStorage&&await removeItemAsync(this.userStorage,this.storageKey+"-user"),await this._notifyAllSubscribers("SIGNED_OUT",null)}_removeVisibilityChangedCallback(){this._debug("#_removeVisibilityChangedCallback()");const t=this.visibilityChangedCallback;this.visibilityChangedCallback=null;try{t&&isBrowser$1()&&window?.removeEventListener&&window.removeEventListener("visibilitychange",t)}
52catch(e){console.error("removing visibilitychange callback failed",e)}}async _startAutoRefresh(){await this._stopAutoRefresh(),this._debug("#_startAutoRefresh()");const t=setInterval(()=>this._autoRefreshTokenTick(),AUTO_REFRESH_TICK_DURATION_MS);this.autoRefreshTicker=t,t&&typeof t=="object"&&typeof t.unref=="function"?t.unref():typeof Deno<"u"&&typeof Deno.unrefTimer=="function"&&Deno.unrefTimer(t);const e=setTimeout(async()=>{await this.initializePromise,await this._autoRefreshTokenTick()},0);this.autoRefreshTickTimeout=e,e&&typeof e=="object"&&typeof e.unref=="function"?e.unref():typeof Deno<"u"&&typeof Deno.unrefTimer=="function"&&Deno.unrefTimer(e)}async _stopAutoRefresh(){this._debug("#_stopAutoRefresh()");const t=this.autoRefreshTicker;this.autoRefreshTicker=null,t&&clearInterval(t);const e=this.autoRefreshTickTimeout;this.autoRefreshTickTimeout=null,e&&clearTimeout(e)}async startAutoRefresh(){this._removeVisibilityChangedCallback(),await this._startAutoRefresh()}async stopAutoRefresh(){this._removeVisibilityChangedCallback(),await this._stopAutoRefresh()}async _autoRefreshTokenTick(){this._debug("#_autoRefreshTokenTick()","begin");try{await this._acquireLock(0,async()=>{try{const t=Date.now();try{return await this._useSession(async e=>{const{data:{session:s}}=e;if(!s||!s.refresh_token||!s.expires_at){this._debug("#_autoRefreshTokenTick()","no session");return}const o=Math.floor((s.expires_at*1e3-t)/AUTO_REFRESH_TICK_DURATION_MS);this._debug("#_autoRefreshTokenTick()",`access token expires in ${o} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`),o<=AUTO_REFRESH_TICK_THRESHOLD&&await this._callRefreshToken(s.refresh_token)})}catch(e){console.error("Auto refresh tick failed with error. This is likely a transient error.",e)}}finally{this._debug("#_autoRefreshTokenTick()","end")}})}catch(t){if(t.isAcquireTimeout||t instanceof LockAcquireTimeoutError)this._debug("auto refresh token tick lock not available");else throw t}}async _handleVisibilityChange(){if(this._debug("#_handleVisibilityChange()"),!isBrowser$1()||!window?.addEventListener)return this.autoRefreshToken&&this.startAutoRefresh(),!1;try{this.visibilityChangedCallback=async()=>{try{await this._onVisibilityChanged(!1)}catch(t){this._debug("#visibilityChangedCallback","error",t)}},window?.addEventListener("visibilitychange",this.visibilityChangedCallback),await this._onVisibilityChanged(!0)}catch(t){console.error("_handleVisibilityChange",t)}}async _onVisibilityChanged(t){const e=`#_onVisibilityChanged(${t})`;this._debug(e,"visibilityState",document.visibilityState),document.visibilityState==="visible"?(this.autoRefreshToken&&this._startAutoRefresh(),t||(await this.initializePromise,await this._acquireLock(this.lockAcquireTimeout,async()=>{if(document.visibilityState!=="visible"){this._debug(e,"acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting");return}await this._recoverAndRefresh()}))):document.visibilityState==="hidden"&&this.autoRefreshToken&&this._stopAutoRefresh()}async _getUrlForProvider(t,e,s){const o=[`provider=${encodeURIComponent(e)}`];if(s?.redirectTo&&o.push(`redirect_to=${encodeURIComponent(s.redirectTo)}`),s?.scopes&&o.push(`scopes=${encodeURIComponent(s.scopes)}`),this.flowType==="pkce"){const[r,a]=await getCodeChallengeAndMethod(this.storage,this.storageKey),i=new URLSearchParams({code_challenge:`${encodeURIComponent(r)}`,code_challenge_method:`${encodeURIComponent(a)}`});o.push(i.toString())}if(s?.queryParams){const r=new URLSearchParams(s.queryParams);o.push(r.toString())}return s?.skipBrowserRedirect&&o.push(`skip_http_redirect=${s.skipBrowserRedirect}`),`${t}?${o.join("&")}`}async _unenroll(t){try{return await this._useSession(async e=>{var s;const{data:o,error:r}=e;return r?this._returnResult({data:null,error:r}):await _request(this.fetch,"DELETE",`${this.url}/factors/${t.factorId}`,{headers:this.headers,jwt:(s=o?.session)===null||s===void 0?void 0:s.access_token})})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}}async _enroll(t){try{return await this._useSession(async e=>{var s,o;const{data:r,error:a}=e;if(a)return this._returnResult({data:null,error:a});const i=Object.assign({friendly_name:t.friendlyName,factor_type:t.factorType},t.factorType==="phone"?{phone:t.phone}:t.factorType==="totp"?{issuer:t.issuer}:{}),{data:l,error:u}=await _request(this.fetch,"POST",`${this.url}/factors`,{body:i,headers:this.headers,jwt:(s=r?.session)===null||s===void 0?void 0:s.access_token});return u?this._returnResult({data:null,error:u}):(t.factorType==="totp"&&l.type==="totp"&&(!((o=l?.totp)===null||o===void 0)&&o.qr_code)&&(l.totp.qr_code=`data:image/svg+xml;utf-8,${l.totp.qr_code}`),this._returnResult({data:l,error:null}))})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}}async _verify(t){return this._acquireLock(this.lockAcquireTimeout,async()=>{try{return await this._useSession(async e=>{var s;const{data:o,error:r}=e;if(r)return this._returnResult({data:null,error:r});const a=Object.assign({challenge_id:t.challengeId},"webauthn"in t?{webauthn:Object.assign(Object.assign({},t.webauthn),{credential_response:t.webauthn.type==="create"?serializeCredentialCreationResponse(t.webauthn.credential_response):serializeCredentialRequestResponse(t.webauthn.credential_response)})}:{code:t.code}),{data:i,error:l}=await _request(this.fetch,"POST",`${this.url}/factors/${t.factorId}/verify`,{body:a,headers:this.headers,jwt:(s=o?.session)===null||s===void 0?void 0:s.access_token});return l?this._returnResult({data:null,error:l}):(await this._saveSession(Object.assign({expires_at:Math.round(Date.now()/1e3)+i.expires_in},i)),await this._notifyAllSubscribers("MFA_CHALLENGE_VERIFIED",i),this._returnResult({data:i,error:l}))})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}})}async _challenge(t){return this._acquireLock(this.lockAcquireTimeout,async()=>{try{return await this._useSession(async e=>{var s;const{data:o,error:r}=e;if(r)return this._returnResult({data:null,error:r});const a=await _request(this.fetch,"POST",`${this.url}/factors/${t.factorId}/challenge`,{body:t,headers:this.headers,jwt:(s=o?.session)===null||s===void 0?void 0:s.access_token});if(a.error)return a;const{data:i}=a;if(i.type!=="webauthn")return{data:i,error:null};switch(i.webauthn.type){case"create":return{data:Object.assign(Object.assign({},i),{webauthn:Object.assign(Object.assign({},i.webauthn),{credential_options:Object.assign(Object.assign({},i.webauthn.credential_options),{publicKey:deserializeCredentialCreationOptions(i.webauthn.credential_options.publicKey)})})}),error:null};case"request":return{data:Object.assign(Object.assign({},i),{webauthn:Object.assign(Object.assign({},i.webauthn),{credential_options:Object.assign(Object.assign({},i.webauthn.credential_options),{publicKey:deserializeCredentialRequestOptions(i.webauthn.credential_options.publicKey)})})}),error:null}}})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}})}async _challengeAndVerify(t){const{data:e,error:s}=await this._challenge({factorId:t.factorId});return s?this._returnResult({data:null,error:s}):await this._verify({factorId:t.factorId,challengeId:e.id,code:t.code})}async _listFactors(){var t;const{data:{user:e},error:s}=await this.getUser();if(s)return{data:null,error:s};const o={all:[],phone:[],totp:[],webauthn:[]};for(const r of(t=e?.factors)!==null&&t!==void 0?t:[])o.all.push(r),r.status==="verified"&&o[r.factor_type].push(r);return{data:o,error:null}}async _getAuthenticatorAssuranceLevel(t){var e,s,o,r;if(t)try{const{payload:f}=decodeJWT(t);let g=null;f.aal&&(g=f.aal);let b=g;
52const{data:{user:x},error:y}=await this.getUser(t);if(y)return this._returnResult({data:null,error:y});((s=(e=x?.factors)===null||e===void 0?void 0:e.filter(T=>T.status==="verified"))!==null&&s!==void 0?s:[]).length>0&&(b="aal2");const k=f.amr||[];return{data:{currentLevel:g,nextLevel:b,currentAuthenticationMethods:k},error:null}}catch(f){if(isAuthError(f))return this._returnResult({data:null,error:f});throw f}const{data:{session:a},error:i}=await this.getSession();if(i)return this._returnResult({data:null,error:i});if(!a)return{data:{currentLevel:null,nextLevel:null,currentAuthenticationMethods:[]},error:null};const{payload:l}=decodeJWT(a.access_token);let u=null;l.aal&&(u=l.aal);let d=u;((r=(o=a.user.factors)===null||o===void 0?void 0:o.filter(f=>f.status==="verified"))!==null&&r!==void 0?r:[]).length>0&&(d="aal2");const h=l.amr||[];return{data:{currentLevel:u,nextLevel:d,currentAuthenticationMethods:h},error:null}}async _getAuthorizationDetails(t){try{return await this._useSession(async e=>{const{data:{session:s},error:o}=e;return o?this._returnResult({data:null,error:o}):s?await _request(this.fetch,"GET",`${this.url}/oauth/authorizations/${t}`,{headers:this.headers,jwt:s.access_token,xform:r=>({data:r,error:null})}):this._returnResult({data:null,error:new AuthSessionMissingError})})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}}async _approveAuthorization(t,e){try{return await this._useSession(async s=>{const{data:{session:o},error:r}=s;if(r)return this._returnResult({data:null,error:r});if(!o)return this._returnResult({data:null,error:new AuthSessionMissingError});const a=await _request(this.fetch,"POST",`${this.url}/oauth/authorizations/${t}/consent`,{headers:this.headers,jwt:o.access_token,body:{action:"approve"},xform:i=>({data:i,error:null})});return a.data&&a.data.redirect_url&&isBrowser$1()&&!e?.skipBrowserRedirect&&window.location.assign(a.data.redirect_url),a})}catch(s){if(isAuthError(s))return this._returnResult({data:null,error:s});throw s}}async _denyAuthorization(t,e){try{return await this._useSession(async s=>{const{data:{session:o},error:r}=s;if(r)return this._returnResult({data:null,error:r});if(!o)return this._returnResult({data:null,error:new AuthSessionMissingError});const a=await _request(this.fetch,"POST",`${this.url}/oauth/authorizations/${t}/consent`,{headers:this.headers,jwt:o.access_token,body:{action:"deny"},xform:i=>({data:i,error:null})});return a.data&&a.data.redirect_url&&isBrowser$1()&&!e?.skipBrowserRedirect&&window.location.assign(a.data.redirect_url),a})}catch(s){if(isAuthError(s))return this._returnResult({data:null,error:s});throw s}}async _listOAuthGrants(){try{return await this._useSession(async t=>{const{data:{session:e},error:s}=t;return s?this._returnResult({data:null,error:s}):e?await _request(this.fetch,"GET",`${this.url}/user/oauth/grants`,{headers:this.headers,jwt:e.access_token,xform:o=>({data:o,error:null})}):this._returnResult({data:null,error:new AuthSessionMissingError})})}catch(t){if(isAuthError(t))return this._returnResult({data:null,error:t});throw t}}async _revokeOAuthGrant(t){try{return await this._useSession(async e=>{const{data:{session:s},error:o}=e;return o?this._returnResult({data:null,error:o}):s?(await _request(this.fetch,"DELETE",`${this.url}/user/oauth/grants`,{headers:this.headers,jwt:s.access_token,query:{client_id:t.clientId},noResolveJson:!0}),{data:{},error:null}):this._returnResult({data:null,error:new AuthSessionMissingError})})}catch(e){if(isAuthError(e))return this._returnResult({data:null,error:e});throw e}}async fetchJwk(t,e={keys:[]}){let s=e.keys.find(i=>i.kid===t);if(s)return s;const o=Date.now();if(s=this.jwks.keys.find(i=>i.kid===t),s&&this.jwks_cached_at+JWKS_TTL>o)return s;const{data:r,error:a}=await _request(this.fetch,"GET",`${this.url}/.well-known/jwks.json`,{headers:this.headers});if(a)throw a;return!r.keys||r.keys.length===0||(this.jwks=r,this.jwks_cached_at=o,s=r.keys.find(i=>i.kid===t),!s)?null:s}async getClaims(t,e={}){try{let s=t;if(!s){const{data:f,error:g}
52=await this.getSession();if(g||!f.session)return this._returnResult({data:null,error:g});s=f.session.access_token}const{header:o,payload:r,signature:a,raw:{header:i,payload:l}}=decodeJWT(s);e?.allowExpired||validateExp(r.exp);const u=!o.alg||o.alg.startsWith("HS")||!o.kid||!("crypto"in globalThis&&"subtle"in globalThis.crypto)?null:await this.fetchJwk(o.kid,e?.keys?{keys:e.keys}:e?.jwks);if(!u){const{error:f}=await this.getUser(s);if(f)throw f;return{data:{claims:r,header:o,signature:a},error:null}}const d=getAlgorithm(o.alg),m=await crypto.subtle.importKey("jwk",u,d,!0,["verify"]);if(!await crypto.subtle.verify(d,m,a,stringToUint8Array(`${i}.${l}`)))throw new AuthInvalidJwtError("Invalid JWT signature");return{data:{claims:r,header:o,signature:a},error:null}}catch(s){if(isAuthError(s))return this._returnResult({data:null,error:s});throw s}}}GoTrueClient.nextInstanceID={};const AuthClient=GoTrueClient,version="2.98.0";let JS_ENV="";typeof Deno<"u"?JS_ENV="deno":typeof document<"u"?JS_ENV="web":typeof navigator<"u"&&navigator.product==="ReactNative"?JS_ENV="react-native":JS_ENV="node";const DEFAULT_HEADERS={"X-Client-Info":`supabase-js-${JS_ENV}/${version}`},DEFAULT_GLOBAL_OPTIONS={headers:DEFAULT_HEADERS},DEFAULT_DB_OPTIONS={schema:"public"},DEFAULT_AUTH_OPTIONS={autoRefreshToken:!0,persistSession:!0,detectSessionInUrl:!0,flowType:"implicit"},DEFAULT_REALTIME_OPTIONS={};function _typeof(n){"@babel/helpers - typeof";return _typeof=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},_typeof(n)}function toPrimitive(n,t){if(_typeof(n)!="object"||!n)return n;var e=n[Symbol.toPrimitive];if(e!==void 0){var s=e.call(n,t);if(_typeof(s)!="object")return s;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(n)}function toPropertyKey(n){var t=toPrimitive(n,"string");return _typeof(t)=="symbol"?t:t+""}function _defineProperty(n,t,e){return(t=toPropertyKey(t))in n?Object.defineProperty(n,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):n[t]=e,n}function ownKeys(n,t){var e=Object.keys(n);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(n);t&&(s=s.filter(function(o){return Object.getOwnPropertyDescriptor(n,o).enumerable})),e.push.apply(e,s)}return e}function _objectSpread2(n){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?arguments[t]:{};t%2?ownKeys(Object(e),!0).forEach(function(s){_defineProperty(n,s,e[s])}):Object.getOwnPropertyDescriptors?Object.defineProperties(n,Object.getOwnPropertyDescriptors(e)):ownKeys(Object(e)).forEac
52h(function(s){Object.defineProperty(n,s,Object.getOwnPropertyDescriptor(e,s))})}return n}const resolveFetch=n=>n?(...t)=>n(...t):(...t)=>fetch(...t),resolveHeadersConstructor=()=>Headers,fetchWithAuth=(n,t,e)=>{const s=resolveFetch(e),o=resolveHeadersConstructor();return async(r,a)=>{var i;const l=(i=await t())!==null&&i!==void 0?i:n;let u=new o(a?.headers);return u.has("apikey")||u.set("apikey",n),u.has("Authorization")||u.set("Authorization",`Bearer ${l}`),s(r,_objectSpread2(_objectSpread2({},a),{},{headers:u}))}};function ensureTrailingSlash(n){return n.endsWith("/")?n:n+"/"}function applySettingDefaults(n,t){var e,s;const{db:o,auth:r,realtime:a,global:i}=n,{db:l,auth:u,realtime:d,global:m}=t,h={db:_objectSpread2(_objectSpread2({},l),o),auth:_objectSpread2(_objectSpread2({},u),r),realtime:_objectSpread2(_objectSpread2({},d),a),storage:{},global:_objectSpread2(_objectSpread2(_objectSpread2({},m),i),{},{headers:_objectSpread2(_objectSpread2({},(e=m?.headers)!==null&&e!==void 0?e:{}),(s=i?.headers)!==null&&s!==void 0?s:{})}),accessToken:async()=>""};return n.accessToken?h.accessToken=n.accessToken:delete h.accessToken,h}function validateSupabaseUrl(n){const t=n?.trim();if(!t)throw new Error("supabaseUrl is required.");if(!t.match(/^https?:\/\//i))throw new Error("Invalid supabaseUrl: Must be a valid HTTP or HTTPS URL.");try{return new URL(ensureTrailingSlash(t))}catch{throw Error("Invalid supabaseUrl: Provided URL is malformed.")}}var SupabaseAuthClient=class extends AuthClient{constructor(n){super(n)}},SupabaseClient=class{constructor(n,t,e){var s,o;this.supabaseUrl=n,this.supabaseKey=t;const r=validateSupabaseUrl(n);if(!t)throw new Error("supabaseKey is required.");this.realtimeUrl=new URL("realtime/v1",r),this.realtimeUrl.protocol=this.realtimeUrl.protocol.replace("http","ws"),this.authUrl=new URL("auth/v1",r),this.storageUrl=new URL("storage/v1",r),this.functionsUrl=new URL("functions/v1",r);const a=`sb-${r.hostname.split(".")[0]}-auth-token`,i={db:DEFAULT_DB_OPTIONS,realtime:DEFAULT_REALTIME_OPTIONS,auth:_objectSpread2(_objectSpread2({},DEFAULT_AUTH_OPTIONS),{},{storageKey:a}),global:DEFAULT_GLOBAL_OPTIONS},l=applySettingDefaults(e??{},i);if(this.storageKey=(s=l.auth.storageKey)!==null&&s!==void 0?s:"",this.headers=(o=l.global.headers)!==null&&o!==void 0?o:{},l.accessToken)this.accessToken=l.accessToken,this.auth=new Proxy({},{get:(d,m)=>{throw new Error(`@supabase/supabase-js: Supabase Client is configured with the accessToken option, accessing supabase.auth.${String(m)} is not possible`)}});else{var u;this.auth=this._initSupabaseAuthClient((u=l.auth)!==null&&u!==void 0?u:{},this.headers,l.global.fetch)}this.fetch=fetchWithAuth(t,this._getAccessToken.bind(this),l.global.fetch),this.realtime=this._initRealtimeClient(_objectSpread2({headers:this.headers,accessToken:this._getAccessToken.bind(this)},l.realtime)),this.accessToken&&Promise.resolve(this.accessToken()).then(d=>this.realtime.setAuth(d)).catch(d=>console.warn("Failed to set initial Realtime auth token:",d)),this.rest=new PostgrestClient(new URL("rest/v1",r).href,{headers:this.headers,schema:l.db.schema,fetch:this.fetch,timeout:l.db.timeout,urlLengthLimit:l.db.urlLengthLimit}),this.storage=new StorageClient(this.storageUrl.href,this.headers,this.fetch,e?.storage),l.accessToken||this._listenForAuthEvents()}get functions(){return new FunctionsClient(this.functionsUrl.href,{headers:this.headers,customFetch:this.fetch})}from(n){return this.rest.from(n)}schema(n){return this.rest.schema(n)}rpc(n,t={},e={head:!1,get:!1,count:void 0}){return this.rest.rpc(n,t,e)}channel(n,t={config:{}}){return this.realtime.channel(n,t)}getChannels(){return this.realtime.getChannels()}removeChannel(n){return this.realtime.removeChannel(n)}removeAllChannels(){return this.realtime.removeAllChannels()}async _getAccessToken(){var n=this,t,e;if(n.accessToken)return await n.accessToken();const{data:s}=await n.auth.getSession();return(t=(e=s.session)===null||e===void 0?void 0:e.access_token)!==null&&t!==void 0?t:n.supabaseKey}_initSupabaseAuthClient({autoRefreshToken:n,persistSession:t,detectSessionInUrl:e,storage:s,userStorage:o,storageKey:r,flowType:a,lock:i,debug:l,throwOnError:u},d,m){const h={Authorization:`Bearer ${this.supabaseKey}`,apikey:`${this.supabaseKey}`};return new SupabaseAuthClient({url:this.authUrl.href,headers:_objectSpread2
52(_objectSpread2({},h),d),storageKey:r,autoRefreshToken:n,persistSession:t,detectSessionInUrl:e,storage:s,userStorage:o,flowType:a,lock:i,debug:l,throwOnError:u,fetch:m,hasCustomAuthorizationHeader:Object.keys(this.headers).some(f=>f.toLowerCase()==="authorization")})}_initRealtimeClient(n){return new RealtimeClient(this.realtimeUrl.href,_objectSpread2(_objectSpread2({},n),{},{params:_objectSpread2(_objectSpread2({},{apikey:this.supabaseKey}),n?.params)}))}_listenForAuthEvents(){return this.auth.onAuthStateChange((n,t)=>{this._handleTokenChanged(n,"CLIENT",t?.access_token)})}_handleTokenChanged(n,t,e){(n==="TOKEN_REFRESHED"||n==="SIGNED_IN")&&this.changedAccessToken!==e?(this.changedAccessToken=e,this.realtime.setAuth(e)):n==="SIGNED_OUT"&&(this.realtime.setAuth(),t=="STORAGE"&&this.auth.signOut(),this.changedAccessToken=void 0)}};const createClient=(n,t,e)=>new SupabaseClient(n,t,e);function shouldShowDeprecationWarning(){if(typeof window<"u")return!1;const n=globalThis.process;if(!n)return!1;const t=n.version;if(t==null)return!1;const e=t.match(/^v(\d+)\./);return e?parseInt(e[1],10)<=18:!1}shouldShowDeprecationWarning()&&console.warn("⚠️  Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");const url="https://ixnpvbwrjbkvxuekoojl.supabase.co",key="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Iml4bnB2YndyamJrdnh1ZWtvb2psIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI3NjY4NjEsImV4cCI6MjA4ODM0Mjg2MX0.SlzTOMB4QlaiNNJ9YhaSkUvUvcoaxpTvFiDpqHD6akE",noOpLock=async(n,t,e)=>e(),supabase=createClient(url,key,{auth:{lock:noOpLock}});function resolveAnimatedMia(n,t,e){return n==="off"?{enabled:!1,storage:"set-off"}:n==="on"||n==="animated"?{enabled:e,storage:"clear"}:t?{enabled:!1,storage:"keep"}:{enabled:e,storage:"keep"}}const DEV_PIN="555",ACCEPTED_DEV_PINS=new Set([DEV_PIN,"2718"]),WEX_FORCE_STORAGE_KEY="smartmaths_wex_force",ADMIN_STORAGE_KEY="smartmaths_admin",IS_LOCAL_DEV=typeof window<"u"&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1");function checkAdminAndStore(){if(typeof window>"u")return!1;const t=new URLSearchParams(window.location.search).get("admin");return t==="off"?(localStorage.removeItem(ADMIN_STORAGE_KEY),!1):IS_LOCAL_DEV&&t!==null&&ACCEPTED_DEV_PINS.has(t)?(localStorage.setItem(ADMIN_STORAGE_KEY,"1"),!0):localStorage.getItem(ADMIN_STORAGE_KEY)==="1"}function checkWexForceAndStore(){return typeof window>"u"?!1:IS_LOCAL_DEV?new URLSearchParams(window.location.search).get("wex")==="force"?(sessionStorage.setItem(WEX_FORCE_STORAGE_KEY,"1"),!0):sessionStorage.getItem(WEX_FORCE_STORAGE_KEY)==="1":(sessionStorage.removeItem(WEX_FORCE_STORAGE_KEY),!1)}const EXPLAIN_ALT_OFF_KEY="smartmaths_explain_alt_off";function checkExplainAltAndStore(){if(typeof window>"u")return!1;const t=new URLSearchParams(window.location.search).get("explain-alt");return t==="off"?(localStorage.setItem(EXPLAIN_ALT_OFF_KEY,"1"),!1):t==="1"?(localStorage.removeItem(EXPLAIN_ALT_OFF_KEY),!0):localStorage.getItem(EXPLAIN_ALT_OFF_KEY)!=="1"}const MICRO_FORCE_STORAGE_KEY="smartmaths_micro_force";function checkMicroForceAndStore(){if(typeof window>"u")return null;const t=new URLSearchParams(window.location.search).get("micro");return t==="off"?(sessionStorage.removeItem(MICRO_FORCE_STORAGE_KEY),null):t&&/^[A-Z]+\.P\d+\.M\d+$/.test(t)?(sessionStorage.setItem(MICRO_FORCE_STORAGE_KEY,t),t):sessionStorage.getItem(MICRO_FORCE_STORAGE_KEY)}const QUESTION_FORCE_STORAGE_KEY="smartmaths_question_force";function checkQuestionForceAndStore(){if(typeof window>"u")return null;const t=new URLSearchParams(window.location.search).get("q");if(t==="off")return sessionStorage.removeItem(QUESTION_FORCE_STORAGE_KEY),null;if(t&&/^[A-Z]+\.P\d+\.M\d+\.Q\d+$/i.test(t)){const e=t.toUpperCase();return sessionStorage.setItem(QUESTION_FORCE_STORAGE_KEY,e),e}return sessionStorage.getItem(QUESTION_FORCE_STORAGE_KEY)}const ANIMATED_MIA_DEFAULT=!0,MIA_STATIC_STORAGE_KEY="smartmaths_mia_static";function safeStorageGet(n){try{return localStorage.getItem(n)}catch{return null}}function safeStorageSet(n,t){try{localStorage.setItem(n,t)}catch{}}function safeStorageRemove(n){try{localStorage.removeItem(n)}catch{}}function checkMiaAnimatedAndStore(){if(typeof window>"u")return ANIMATED_MIA_DEFAULT;const n=new URLSearchParams(window.location.search),t=resolveAnimatedMia(n.get("mia"),safeStorageGet(MIA_STATIC_STORAGE_KEY)==="1",ANIMATED_MIA_DEFAULT);return t.storage==="set-off"?safeStorageSet(MIA_STATIC_STORAGE_KEY,"1"):t.storage==="clear"&&safeStorageRemove(MIA_STATIC_STORAGE_KEY),t.enabled}let adminMode=checkAdminAndStore();function setAdminFromAuth(n){if(typeof window>"u")return;if(n){adminMode=!0,localStorage.setItem(ADMIN_STORAGE_KEY,"1");return}const e=new URLSearchParams(window.location.search).get("admin");IS_LOCAL_DEV&&e!==null&&ACCEPTED_DEV_PINS.has(e)||(adminMode=!1,localStorage.removeItem(ADMIN_STORAGE_KEY))}const wexForce=checkWexForceAndStore(),explainAltMode=checkExplainAltAndStore(),microForce=checkMicroForceAndStore(),questionForce=checkQuestionForceAndStore(),miaAnimated=checkMiaAnimatedAndStore();function isAdminMode(){return adminMode}function isForceWorkedExample(){return wexForce}function isExplainAltEnabled(){return explainAltMode}function getForcedMicroSkill(){return microForce}function getForcedQuestionId(){return questionForce}function isAnimatedMiaEnabled(){return miaAnimated}const STORAGE_KEY$1="mathsquest:selectedChildId";function readSelectedChildId(){if(typeof window>"u")return null;try{return window.localStorage.getItem(STORAGE_KEY$1)}catch{return null}}function writeSelectedChildId(n){if(!(typeof window>"u"))try{n?window.localStorage.setItem(STORAGE_KEY$1,n):window.localStorage.removeItem(STORAGE_KEY$1)}catch{}}function pickInitialChildId(n,t){return n.length===0?null:t&&n.some(e=>e.id===t)?t:n[0].id}const AuthContext=reactExports.createContext({session:null,user:null,parentFirstName:null,children:[],archivedChildren:[],selectedChild:null,isLoading:!0,isAdmin:!1,childrenError:!1,signOut:async()=>{},selectChild:()=>{},addChild:async()=>({child:null,error:"Auth not initialised"}),renameChild:async()=>({error:"Auth not initialised"}),updateChildYearGroup:async()=>({error:"Auth not initialised"}),archiveChild:async()=>({error:"Auth not initialised"}),restoreChild:async()=>({error:"Auth not initialised"}),refreshChildren:async()=>{},refresh:async()=>{},requestPasswordReset:async()=>({error:"Auth not initialised"}),resendConfirmation:async()=>({error:"Auth not initialised"})});function useAuth(){return reactExports.useContext(AuthContext)}async function loadParentRow(n){if(!supabase)return null;const{data:t,error:e}=await supabase.from("profiles").select("parent_first_name, first_name, year_group_label").eq("id",n).maybeSingle();return e||!t?null:t}async function withTimeout(n,t,e=8e3){const s=new Promise((o,r)=>setTimeout(()=>r(new Error(`${t} timed out after ${e}ms — please try again.`)),e));return Promise.race([Promise.resolve(n),s])}async function loadChildren(n){if(!supabase)return{active:[],archived:[],ok:!0};let t=[];const e=await supabase.from("children").select("id, first_name, year_group_label, created_at, archived_at").eq("parent_id",n).order("created_at",{ascending:!0});if(e.error){const r=await supabase.from("children").select("id, first_name, year_group_label, created_at").eq("parent_id",n).order("created_at",{ascending:!0});if(r.error||!r.data)return{active:[],archived:[],ok:!1};t=r.data.map(a=>({...a,archived_at:null}))}else e.data&&(t=e.data);const s=[],o=[];for(const r of t){const a={id:r.id,firstName:r.first_name,yearGroupLabel:r.year_group_label};r.archived_at?o.push(a):s.push(a)}return{active:s,archived:o,ok:!0}}function AuthProvider({children:n}){const[t,e]=reactExports.useState(null),[s,o]=reactExports.useState(null),[r,a]=reactExports.useState(null),[i,l]=reactExports.useState([]),[u,d]=reactExports.useState([]),[m,h]=reactExports.useState(null),[f,g]=reactExports.useState(!0),[b,x]=reactExports.useState(!1),[y,E]=reactExports.useState(!1),k=reactExports.useCallback(async K=>{let Z=!1;if(K&&supabase)try{const{data:Q}=await supabase.from("admin_users").select("user_id").eq("user_id",K).maybeSingle();Z=!!Q}catch{Z=!1}x(Z),setAdminFromAuth(Z)},[]),T=reactExports.useCallback(async K=>{const[Z,Q]=await Promise.all([loadParentRow(K),loadChildren(K)]);if(Z&&a(Z.parent_first_name??null),!Q.ok){E(!0);return}E(!1),l(Q.active),d(Q.archived),h(B=>{const G=B??readSelectedChildId(),X=pickInitialChildId(Q.active,G);return writeSelectedChildId(X),X})},[]);reactExports.useEffect(()=>{if(!supabase){g(!1);return}let K=!1;(async()=>{const Q={data:{session:null}};let B=null;const G=new Promise(X=>{B=setTimeout(()=>{console.warn("[auth] getSession timed out after 4s — proceeding without a session"),X(Q)},4e3)});try{const ne=(await Promise.race([supabase.auth.getSession(),G])).data.session;if(K)return;e(ne),o(ne?.user??null),k(ne?.user?.id??null),ne?.user&&await withTimeout(T(ne.user.id),"Loading your account")}catch(X){console.error("[auth] init failed:",X)}finally{B!==null&&clearTimeout(B),K||g(!1)}})();const{data:{subscription:Z}}=supabase.auth.onAuthStateChange(async(Q,B)=>{try{e(B),o(B?.user??null),k(B?.user?.id??null),B?.user?await withTimeout(T(B.user.id),"Loading your account"):(a(null),l([]),d([]),h(null),writeSelectedChildId(null))}catch(G){console.error("Auth state change handler failed:",G)}});return()=>{K=!0,Z.unsubscribe()}},[T,k]);const A=reactExports.useCallback(async()=>{supabase&&await supabase.auth.signOut()},[]),N=reactExports.useCallback(K=>{h(Z=>Z===K?Z:(writeSelectedChildId(K),K))},[]),S=reactExports.useCallback(async()=>
52{if(!s)return;const K=await loadChildren(s.id);if(!K.ok){E(!0);return}E(!1),l(K.active),d(K.archived),h(Z=>{const Q=pickInitialChildId(K.active,Z);return writeSelectedChildId(Q),Q})},[s]),M=reactExports.useCallback(async K=>{const Z=K??s?.id;Z&&await T(Z)},[s,T]),L=reactExports.useCallback(async K=>{if(!supabase||!s)return{child:null,error:"Sign in first."};const Z=K.firstName.trim();if(!Z)return{child:null,error:"Please enter the child's name."};const{data:Q,error:B}=await supabase.from("children").insert({parent_id:s.id,first_name:Z,year_group_label:K.yearGroupLabel}).select("id, first_name, year_group_label, created_at").single();if(B||!Q)return{child:null,error:B?.message??"Could not add child."};const G={id:Q.id,firstName:Q.first_name,yearGroupLabel:Q.year_group_label};return l(X=>[...X,G]),h(X=>X||(writeSelectedChildId(G.id),G.id)),{child:G,error:null}},[s]),P=reactExports.useCallback(async(K,Z)=>{if(!supabase||!s)return{error:"Sign in first."};const Q=Z.trim();if(!Q)return{error:"Please enter a name."};try{const{error:G}=await withTimeout(supabase.from("children").update({first_name:Q}).eq("id",K).eq("parent_id",s.id),"Rename");if(G)return{error:G.message}}catch(G){return{error:G instanceof Error?G.message:"Rename failed."}}const B=G=>G.map(X=>X.id===K?{...X,firstName:Q}:X);return l(B),d(B),{error:null}},[s]),F=reactExports.useCallback(async(K,Z)=>{if(!supabase||!s)return{error:"Sign in first."};try{const{error:B}=await withTimeout(supabase.from("children").update({year_group_label:Z}).eq("id",K).eq("parent_id",s.id),"Year-group update");if(B)return{error:B.message}}catch(B){return{error:B instanceof Error?B.message:"Year-group update failed."}}const Q=B=>B.map(G=>G.id===K?{...G,yearGroupLabel:Z}:G);return l(Q),d(Q),{error:null}},[s]),D=reactExports.useCallback(async K=>{if(!supabase||!s)return{error:"Sign in first."};try{const{error:Q}=await withTimeout(supabase.from("children").update({archived_at:new Date().toISOString()}).eq("id",K).eq("parent_id",s.id),"Archive");if(Q)return{error:Q.message}}catch(Q){return{error:Q instanceof Error?Q.message:"Archive failed."}}let Z=null;return l(Q=>(Z=Q.find(B=>B.id===K)??null,Q.filter(B=>B.id!==K))),Z&&d(Q=>[...Q,Z]),h(Q=>{if(Q!==K)return Q;const B=i.filter(X=>X.id!==K),G=pickInitialChildId(B,null);return writeSelectedChildId(G),G}),{error:null}},[s,i]),U=reactExports.useCallback(async K=>{if(!supabase||!s)return{error:"Sign in first."};try{const{error:Q}=await withTimeout(supabase.from("children").update({archived_at:null}).eq("id",K).eq("parent_id",s.id),"Restore");if(Q)return{error:Q.message}}catch(Q){return{error:Q instanceof Error?Q.message:"Restore failed."}}let Z=null;return d(Q=>(Z=Q.find(B=>B.id===K)??null,Q.filter(B=>B.id!==K))),Z&&l(Q=>[...Q,Z]),{error:null}},[s]),q=reactExports.useCallback(async K=>{if(!supabase)return{error:"Auth not configured."};const Z=K.trim();if(!Z)return{error:"Please enter your email."};const Q=typeof window<"u"?`${window.location.origin}${window.location.pathname}${window.location.search}#reset-password`:void 0,{error:B}=await supabase.auth.resetPasswordForEmail(Z,{redirectTo:Q});return{error:B?.message??null}},[]),V=reactExports.useCallback(async K=>{if(!supabase)return{error:"Auth not configured."};const Z=K.trim();if(!Z)return{error:"Please enter your email."};const Q=typeof window<"u"?`${window.location.origin}${window.location.pathname}${window.location.search}`:void 0,{error:B}=await supabase.auth.resend({type:"signup",email:Z,options:{emailRedirectTo:Q}});return{error:B?.message??null}},[]),O=reactExports.useMemo(()=>m?i.find(K=>K.id===m)??null:null,[i,m]),z=reactExports.useMemo(()=>({session:t,user:s,parentFirstName:r,children:i,archivedChildren:u,selectedChild:O,isLoading:f,isAdmin:b,childrenError:y,signOut:A,selectChild:N,addChild:L,renameChild:P,updateChildYearGroup:F,archiveChild:D,restoreChild:U,refreshChildren:S,refresh:M,requestPasswordReset:q,resendConfirmation:V}),[t,s,r,i,u,O,f,b,y,A,N,L,P,F,D,U,S,M,q,V]);return jsxRuntimeExports.jsx(AuthContext.Provider,{value:z,children:n})}const scriptRel="modulepreload",assetsURL=function(n){return"/"+n},seen$1={}
52,__vitePreload=function(t,e,s){let o=Promise.resolve();if(e&&e.length>0){let l=function(u){return Promise.all(u.map(d=>Promise.resolve(d).then(m=>({status:"fulfilled",value:m}),m=>({status:"rejected",reason:m}))))};document.getElementsByTagName("link");const a=document.querySelector("meta[property=csp-nonce]"),i=a?.nonce||a?.getAttribute("nonce");o=l(e.map(u=>{if(u=assetsURL(u),u in seen$1)return;seen$1[u]=!0;const d=u.endsWith(".css"),m=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${u}"]${m}`))return;const h=document.createElement("link");if(h.rel=d?"stylesheet":scriptRel,d||(h.as="script"),h.crossOrigin="",h.href=u,i&&h.setAttribute("nonce",i),document.head.appendChild(h),d)return new Promise((f,g)=>{h.addEventListener("load",f),h.addEventListener("error",()=>g(new Error(`Unable to preload CSS for ${u}`)))})}))}function r(a){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=a,window.dispatchEvent(i),!i.defaultPrevented)throw a}return o.then(a=>{for(const i of a||[])i.status==="rejected"&&r(i.reason);return t().catch(r)})},sample$1="_sample_1qy3r_36 _palette_1qy3r_3",member="_member_1qy3r_137",publicFlowStyles={sample:sample$1,member},STRANDS=[{name:"Number & Place Value",iconKind:"number",defaultProgress:72,tone:"blue"},{name:"Addition & Subtraction",iconKind:"addSub",defaultProgress:64,tone:"green"},{name:"Multiplication & Division",iconKind:"multDiv",defaultProgress:45,tone:"green"},{name:"Fractions",iconKind:"fractions",defaultProgress:48,tone:"purple"},{name:"Money & Time",iconKind:"moneyTime",defaultProgress:56,tone:"orange"},{name:"Shape & Space",iconKind:"shapeSpace",defaultProgress:61,tone:"blue"},{name:"Ratio & Proportion",iconKind:"ratio",defaultProgress:40,tone:"purple"},{name:"Data & Statistics",iconKind:"data",defaultProgress:42,tone:"orange"}],STORAGE_KEYS={selectedStrand:"mathsquest:selectedStrand",lastSessionResult:"mathsquest:lastSessionResult",strandProgress:"mathsquest:strandProgress",sessionHistory:"mathsquest:sessionHistory",abandonedSessions:"mathsquest:abandonedSessions",authoringReviewBatch:"mathsquest:authoringReviewBatch"};function strandProgressKey(n){return n?`mathsquest:strandProgress:${n}`:STORAGE_KEYS.strandProgress}function sessionHistoryKey(n){return n?`mathsquest:sessionHistory:${n}`:STORAGE_KEYS.sessionHistory}const DEFAULT_CHILD_PROFILE={id:"sample-demo",firstName:"",yearGroupLabel:"Year 1"},UI_TO_ENGINE_STRANDS={"Number & Place Value":["Number & Place Value"],"Addition & Subtraction":["Addition & Subtraction"],"Multiplication & Division":["Multiplication & Division"],Fractions:["Fractions"],"Money & Time":["Measurement"],"Shape & Space":["Geometry","Geometry (Position & Direction)"],"Ratio & Proportion":["Ratio & Proportion"],"Data & Statistics":["Data & Statistics"]};function buildRecommendation(n,t){return t>=80?`Great progress in ${n}. Try a mixed round next.`:t>=60?`Keep going in ${n}. One more round should lock this in.`:`Let's repeat ${n} with hints on so confidence builds.`}function buildSessionLabel(n){return n.length===0?"Mixed Maths":n.length===1?n[0]:`Mixed (${n.length} topics)`}function clampProgress(n){return Math.max(0,Math.min(100,Math.round(n)))}function getDefaultStrandProgress(){const n={};for(const t of STRANDS)n[t.name]=t.defaultProgress;return n}function getBlankStrandProgress(){const n={};for(const t of STRANDS)n[t.name]=0;return n}const YEAR_GROUP_OPTIONS=["Year 1","Year 2","Year 3","Year 4","Year 5","Year 6"];function yearGroupOptionsFor(n){const t=[...YEAR_GROUP_OPTIONS];return t.includes(n)&&n?t:[n,...t].filter(Boolean)}function readStorageString(n){return typeof window>"u"?null:window.localStorage.getItem(n)}function readStorageJson(n){const t=readStorageString(n);if(!t)return null;try{return JSON.parse(t)}catch{return null}}const slotsPerSession=sessionPolicy.session_length.slots_per_session,FALLBACK_BAND="year_1";function yearBandIdForLabel(n){const t=n.trim().toLowerCase();if(t==="reception")return"reception";const e=t.match(/([1-6])/);return e?`year_${e[1]}`:FALLBACK_BAND}function questionCountForYearGroup(n){return slotsPerSession[yearBandIdForLabel(n)]??slotsPerSession[FALLBACK_BAND]}function effectiveQuestionCount(n){return n.targetQuestionId?1:n.curatedQuestionIds&&n.curatedQuestionIds.length>0?n.curatedQuestionIds.length:n.questionCount}const SAMPLE_SESSION_IDS={"Year 1":["MEA.P6.M01.Q26","AS.P3.M03.Q12","MEA.P4.M02.Q47","AS.P2.M02.Q34","NPV.P3.M03.Q66"],"Year 2":["MD.P2.M02.Q116","AS.P4.M04.Q51","FDP.P3.M02.Q128","AS.P4.M05.Q108","MEA.P4.M03.Q201"],"Year 3":["MD.P3.M01.Q104","FDP.P6.M01.Q304","AS.P5.M01.Q106","MEA.P5.M05.Q102","DS.P2.M05.Q301"],"Year 4":["DS.P3.M02.Q200","MD.P3.M02.Q200","FDP.P4.M04.Q223","MD.P4.M02.Q100","GEO.P4.M02.Q302"],"Year 5":["FDP.P6.M02.Q102","MD.P4.M05.Q102","MD.P4.M04.Q106","GEO.P5.M04.Q101","NPV.P6.M05.Q114"],"Year 6":["DS.P4.M03.Q113","GEO.P6.M03.Q109","RP.P3.M02.Q114","FDP.P6.M06.Q101","MEA.P7.M05.Q110"]},SAMPLE_HELP_PLAN=["none","example","demo","example","none"];function sampleHelpModeAt(n){return SAMPLE_HELP_PLAN[n]??"none"}function sampleSessionIdsFor(n){return SAMPLE_SESSION_IDS[n]}const SAMPLE_SESSION_STRANDS={"Year 1":["Money & Time","Addition & Subtraction","Number & Place Value"],"Year 2":["Multiplication & Division","Addition & Subtraction","Fractions","Money & Time"],"Year 3":["Multiplication & Division","Fractions","Addition & Subtraction","Money & Time","Data & Statistics"],"Year 4":["Data & Statistics","Multiplication & Division","Fractions","Shape & Space"],"Year 5":["Fractions","Multiplication & Division","Shape & Space","Number & Place Value"],"Year 6":["Data & Statistics","Shape & Space","Ratio & Proportion","Fractions","Money & Time"]};function sampleSessionStrandsFor(n){return SAMPLE_SESSION_STRANDS[n]}function cloneJson(n){return JSON.parse(JSON.stringify(n))}function sortedUnique(n){return[...new Set(n)].sort()}function isObject(n){return typeof n=="object"&&n!==null}const reviewStatuses=new Set(["pending","approved","approved_with_edits","rejected"]),exportStatuses=new Set(["not_exported","exported"]),batchStatuses=new Set(["drafted","in_review","ready_for_export","exported"]),reviewActions=new Set(["generated","edited","approved","approved_with_edits","rejected","exported"]);function validateCandidateQuestionShape(n,t){const e=[];
52if(!isObject(n))return[`${t} must be an object.`];const s=["id","micro_skill_id","strand_id","phase_id","typical_year_band_id","status","source","representation","interaction_type","difficulty_tier","mathematical_form","question_family","form_variant","scaffold_level"];for(const o of s)(typeof n[o]!="string"||n[o].length===0)&&e.push(`${t}.${o} must be a non-empty string.`);return(typeof n.difficulty_rank!="number"||!Number.isFinite(n.difficulty_rank))&&e.push(`${t}.difficulty_rank must be a number.`),typeof n.unscaffolded_eligible!="boolean"&&e.push(`${t}.unscaffolded_eligible must be a boolean.`),(!isObject(n.prompt)||typeof n.prompt.stem!="string"||n.prompt.stem.length===0)&&e.push(`${t}.prompt.stem must be present.`),(!isObject(n.answer_spec)||typeof n.answer_spec.kind!="string")&&e.push(`${t}.answer_spec.kind must be present.`),Array.isArray(n.misconception_tags)||e.push(`${t}.misconception_tags must be an array.`),isObject(n.suitability)||e.push(`${t}.suitability must be an object.`),e}function summarizeReviewBatch(n){return n.drafts.reduce((t,e)=>(t.total+=1,t[e.review.status]+=1,e.review.export_status==="exported"&&(t.exported+=1),t),{total:0,pending:0,approved:0,approved_with_edits:0,rejected:0,exported:0})}function deriveBatchStatus(n){const t=summarizeReviewBatch(n);return t.exported>0&&t.exported===t.approved+t.approved_with_edits?"exported":t.pending===0&&t.approved+t.approved_with_edits>0?"ready_for_export":t.pending<t.total?"in_review":"drafted"}function getApprovedDrafts(n){return n.drafts.filter(t=>t.review.status==="approved"||t.review.status==="approved_with_edits")}function findEditedFields(n,t){const e=[],s=(o,r,a)=>{if(Array.isArray(o)||Array.isArray(r)){JSON.stringify(o)!==JSON.stringify(r)&&e.push(a);return}if(isObject(o)&&isObject(r)){const i=new Set([...Object.keys(o),...Object.keys(r)]);for(const l of i)s(o[l],r[l],a?`${a}.${l}`:l);return}o!==r&&e.push(a)};return s(n,t,""),sortedUnique(e.filter(Boolean))}function mergeAuditTrail(n,t){return[...n,t].sort((e,s)=>e.at.localeCompare(s.at))}function applyDraftReviewDecision(n,t){const e=cloneJson(n),s=t.candidate_question?cloneJson(t.candidate_question):e.candidate_question,o=findEditedFields(e.original_question,s);s.status=t.status==="rejected"?"draft":"approved",e.candidate_question=s,e.review={status:t.status,reviewer_id:t.reviewer_id,reviewed_at:t.reviewed_at,edited_fields:o,rejection_reason:t.status==="rejected"?t.rejection_reason:void 0,decision_notes:t.decision_notes,export_status:e.review.export_status,exported_at:e.review.exported_at};const r=t.status==="approved"?"approved":t.status==="approved_with_edits"?"approved_with_edits":"rejected",a=t.status==="rejected"?t.rejection_reason||"Rejected draft.":o.length>0?`Approved with edits to ${o.join(", ")}.`:"Approved draft.";return e.audit_trail=mergeAuditTrail(e.audit_trail,{at:t.reviewed_at,action:r,actor:t.reviewer_id,summary:a}),e}function buildApprovedExportBundle(n,t){const e=getApprovedDrafts(n),s=new Map;for(const r of e){const a=r.candidate_question.strand_id,i=s.get(a)??{strand_id:a,authored_questions:[],micro_skill_question_map_updates:[]};i.authored_questions.push(cloneJson(r.candidate_question)),s.set(a,i)}for(const r of s.values()){const a=new Map;for(const i of e.filter(l=>l.candidate_question.strand_id===r.strand_id)){const l=a.get(i.candidate_question.micro_skill_id)??[];l.push(i),a.set(i.candidate_question.micro_skill_id,l)}r.micro_skill_question_map_updates=[...a.entries()].map(([i,l])=>({micro_skill_id:i,add_authored_question_ids:l.map(u=>u.candidate_question.id).sort(),question_families:sortedUnique(l.map(u=>u.candidate_question.question_family)),representations:sortedUnique(l.map(u=>u.candidate_question.representation)),scaffold_levels:sortedUnique(l.map(u=>u.candidate_question.scaffold_level)),supports:{teaching:l.some(u=>u.candidate_question.suitability.teaching),diagnostic:l.some(u=>u.candidate_question.suitability.diagnostic),review:l.some(u=>u.candidate_question.suitability.review),mastery_evidence:l.some(u=>u.candidate_question.suitability.mastery_evidence),support:l.some(u=>u.candidate_question.suitability.support),stretch:l.some(u=>u.candidate_question.suitability.stretch)}}))}const o=summarizeReviewBatch(n);return{schema_version:"smartmaths_authoring_export_v1",metadata:cloneJson(n.metadata),export_id:`${n.batch_id}.export`,source_batch_id:n.batch_id,exported_at:t,strands:[...s.values()].sort((r,a)=>r.strand_id.localeCompare(a.strand_id)),summary:{approved_count:o.approved,approved_with_edits_count:o.approved_with_edits,rejected_count:o.rejected,exported_count:e.length}}}function markBatchExported(n,t,e){const s=cloneJson(n);return s.drafts=s.drafts.map(o=>o.review.status==="pending"||o.review.status==="rejected"?o:{...o,review:{...o.review,export_status:"exported",exported_at:e},audit_trail:mergeAuditTrail(o.audit_trail,{at:e,action:"exported",actor:t,summary:`Exported ${o.candidate_question.id} into approved bundle.`})}),s.status=deriveBatchStatus(s),s}function validateReviewBatchShape(n){const t=[];if(!isObject(n))return["Batch is not an object."];if((typeof n.batch_id!="string"||n.batch_id.length===0)&&t.push("batch_id must be a non-empty string."),(typeof n.status!="str
52ing"||!batchStatuses.has(n.status))&&t.push("status must be one of drafted, in_review, ready_for_export, or exported."),Array.isArray(n.requests)||t.push("requests must be an array."),!Array.isArray(n.drafts))return t.push("drafts must be an array."),t;for(const[e,s]of n.drafts.entries()){if(!isObject(s)){t.push(`drafts[${e}] must be an object.`);continue}if((typeof s.draft_id!="string"||s.draft_id.length===0)&&t.push(`drafts[${e}].draft_id must be a non-empty string.`),t.push(...validateCandidateQuestionShape(s.candidate_question,`drafts[${e}].candidate_question`)),t.push(...validateCandidateQuestionShape(s.original_question,`drafts[${e}].original_question`)),!isObject(s.review)||typeof s.review.status!="string")t.push(`drafts[${e}].review.status must be present.`);else{const o=s.review;reviewStatuses.has(o.status)||t.push(`drafts[${e}].review.status is invalid.`),Array.isArray(o.edited_fields)||t.push(`drafts[${e}].review.edited_fields must be an array.`),(typeof o.export_status!="string"||!exportStatuses.has(o.export_status))&&t.push(`drafts[${e}
52].review.export_status is invalid.`),o.status==="rejected"&&(typeof o.rejection_reason!="string"||o.rejection_reason.length===0)&&t.push(`drafts[${e}].review.rejection_reason is required for rejected drafts.`),o.status==="approved_with_edits"&&Array.isArray(o.edited_fields)&&o.edited_fields.length===0&&t.push(`drafts[${e}].review.edited_fields must list at least one change for approved_with_edits.`),o.export_status==="exported"&&typeof o.exported_at!="string"&&t.push(`drafts[${e}].review.exported_at is required when export_status is exported.`)}if(!Array.isArray(s.audit_trail))t.push(`drafts[${e}].audit_trail must be an array.`);else for(const[o,r]of s.audit_trail.entries()){if(!isObject(r)){t.push(`drafts[${e}].audit_trail[${o}] must be an object.`);continue}(typeof r.action!="string"||!reviewActions.has(r.action))&&t.push(`drafts[${e}].audit_trail[${o}].action is invalid.`)}}return t}const VALID_OUTCOMES=new Set(["first","retry","miss","skipped"]);function parseSessionResult(n){if(!n||typeof n!="object")return null;const t=n;if(typeof t.total!="number"||typeof t.correct!="number")return null;const e=Array.isArray(t.strands)?t.strands.filter(l=>typeof l=="string"&&l.trim().length>0):typeof t.strand=="string"?[t.strand]:[];if(e.length===0)return null;const s=typeof t.sessionLabel=="string"&&t.sessionLabel.trim().length>0?t.sessionLabel:buildSessionLabel(e),o={},r={};if(t.wrongByTheme&&typeof t.wrongByTheme=="object")for(const[l,u]of Object.entries(t.wrongByTheme))typeof u=="number"&&Number.isFinite(u)&&u>0&&(o[l]=Math.round(u));if(t.wrongExamplesByTheme&&typeof t.wrongExamplesByTheme=="object")for(const[l,u]of Object.entries(t.wrongExamplesByTheme)){if(!Array.isArray(u))continue;const d=u.filter(m=>typeof m=="string"&&m.trim().length>0).slice(0,5);d.length>0&&(r[l]=d)}let a;if(Array.isArray(t.perQuestion)){const l=[];let u=!0;for(const d of t.perQuestion)if(typeof d=="string"&&VALID_OUTCOMES.has(d))l.push(d);else{u=!1;break}u&&(a=l)}let i;if(Array.isArray(t.strandDelta)){const l=[];for(const u of t.strandDelta){if(!u||typeof u!="object")continue;const d=u;typeof d.strand!="string"||d.strand.trim().length===0||typeof d.before!="number"||!Number.isFinite(d.before)||typeof d.after!="number"||!Number.isFinite(d.after)||l.push({strand:d.strand,before:clampProgress(d.before),after:clampProgress(d.after)})}l.length>0&&(i=l)}return{total:t.total,correct:t.correct,strands:e,sessionLabel:s,wrongByTheme:o,wrongExamplesByTheme:r,...a?{perQuestion:a}:{},...i?{strandDelta:i}:{}}}function parseStrandProgress(n){if(!n||typeof n!="object")return null;const e={...getDefaultStrandProgress()};for(const s of STRANDS){const o=n[s.name];typeof o=="number"&&Number.isFinite(o)&&(e[s.name]=clampProgress(o))}return e}function parseAuthoringReviewBatch(n){return validateReviewBatchShape(n).length>0?null:n}function registerSW(n={}){const{immediate:t=!1,onNeedRefresh:e,onOfflineReady:s,onRegistered:o,onRegisteredSW:r,onRegisterError:a}=n;let i,l,u;const d=async(h=!0)=>{await l,u?.()};async function m(){if("serviceWorker"in navigator){if(i=await __vitePreload(async()=>{const{Workbox:h}=await import("./workbox-window.prod.es5-BIl4cyR9.js");return{Workbox:h}},[]).then(({Workbox:h})=>new h("/sw.js",{scope:"/",type:"classic"})).catch(h=>{a?.(h)}),!i)return;u=()=>{i?.messageSkipWaiting()};{let h=!1;const f=()=>{h=!0,i?.addEventListener("controlling",g=>{g.isUpdate&&window.location.reload()}),e?.()};i.addEventListener("installed",g=>{typeof g.isUpdate>"u"?typeof g.isExternal<"u"&&g.isExternal?f():!h&&s?.():g.isUpdate||s?.()}),i.addEventListener("waiting",f)}i.register({immediate:t}).then(h=>{r?r("/sw.js",h):o?.(h)}).catch(h=>{a?.(h)})}}return l=m(),d}let updateReady=!1,triggerReload=null,registered=!1;const listeners=new Set;function notify(){for(const n of listeners)n()}function isUpdateReady(){return updateReady}function subscribeToSwUpdate(n){return listeners.add(n),()=>{listeners.delete(n)}}function applyWaitingUpdate(){if(!updateReady||!triggerReload)return;const n=triggerReload;updateReady=!1,triggerReload=null,n()}function registerServiceWorker(){if(registered)return;registered=!0;const n=registerSW({onNeedRefresh(){updateReady=!0,triggerReload=()=>n(!0),notify()}})}const PROGRESS_CONFIG={settling:{sessions:3},stuck:{misconceptionRepeats:3,latencyIncreaseFactor:1.5,sessionAccuracyFloor:.3}};function isUnknownColumnError(n,t){if(!n)return!1;if(n.code==="PGRST204")return!0;const e=(n.message??"").toLowerCase();return e.includes(t.toLowerCase())&&e.includes("column")}async function insertClientErrorRow(n,t){const{error:e}=await n.from("client_errors").insert(t);if(e&&isUnknownColumnError(e,"app_version")){const{app_version:s,...o}=t;await n.from("client_errors").insert(o)}}const APP_VERSION="8a0778e",MAX_PER_LOAD=20;let sent=0;const seen=new Set,LOCAL_HOSTNAMES=new Set(["localhost","127.0.0.1","[::1]","0.0.0.0"]);function isSuppressedLocalOrigin(){if(typeof window>"u"||!LOCAL_HOSTNAMES.has(window.location.hostname))return!1;try{return window.sessionStorage.getItem("opslog")!=="force"}catch{return!0}}function scrubStack(n){return n?n.split(`
53`).map(t=>t.trim()).filter(t=>/^at\s|https?:\/\/|\.tsx?:\d|\.js:\d/.test(t)).map(t=>t.replace(/[?#].*$/,"").slice(0,200)).slice(0,4).join(" | "):""}function hashCode(n){let t=0;for(let e=0;e<n.length;e++)t=Math.imul(31,t)+n.charCodeAt(e)|0;return(t>>>0).toString(36)}function errorName(n){return n instanceof Error?n.name||"Error":typeof n=="string"?"StringError":"UnknownError"}function logClientError(n,t,e){try{if(!supabase||sent>=MAX_PER_LOAD||isSuppressedLocalOrigin())return;const s=errorName(t),o=t instanceof Error?scrubStack(t.stack):"",r=typeof window<"u"?window.location.pathname:"",a=hashCode(`${n}|${s}|${e?.tag??""}|${e?.status??""}|${o.slice(0,80)}`);if(seen.has(a))return;seen.add(a),sent++,insertClientErrorRow(supabase,{kind:n,name:s.slice(0,80),code:a,route:r.slice(0,120),stack_head:o,app_version:APP_VERSION,user_agent:typeof navigator<"u"?navigator.userAgent.
53slice(0,200):null}).catch(()=>{})}catch{}}function reportCloudWriteFailure(n,t,e){const s=n.split("?")[0];if(t.kind==="http"){const o=new Error(`cloud write failed: ${s} ${t.status}`);o.name=`CloudWrite${t.status}:${s}`,e("cloud_write_failed",o,{tag:s,status:t.status})}else{const o=t.error,r=new Error("cloud write threw");r.name=`CloudWriteThrow:${s}`,o instanceof Error&&o.stack&&(r.stack=o.stack),e("cloud_write_failed",r,{tag:s})}}class RequiredCloudWriteError extends Error{constructor(t,e){super("Progress saving is unavailable right now."),this.route=t,this.reason=e,this.name="RequiredCloudWriteError"}reported=!0}function isRequiredCloudWriteError(n){return n instanceof RequiredCloudWriteError&&n.reported}async function postRequiredJson(n,t,e){const{getAccessToken:s,reportFailure:o,fetchImpl:r=fetch,timeoutMs:a=8e3}=e;let i;try{i=await s()}catch(m){throw o({kind:"throw",error:m}),new RequiredCloudWriteError(n,"missing_token")}if(!i){const m=new Error("Authenticated write has no access token");throw m.name="CloudWriteMissingToken",o({kind:"throw",error:m}),new RequiredCloudWriteError(n,"missing_token")}const l=new AbortController,u=setTimeout(()=>l.abort(),a);let d;try{d=await r(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify(t),signal:l.signal})}catch(m){throw o({kind:"throw",error:m}),new RequiredCloudWriteError(n,"network")}finally{clearTimeout(u)}if(!d.ok)throw o({kind:"http",status:d.status}),new RequiredCloudWriteError(n,"http");try{return await d.json()}catch(m){throw o({kind:"throw",error:m}),new RequiredCloudWriteError(n,"invalid_response")}}function useCloudSync(n){const{userId:t}=n,e=reactExports.useCallback(async()=>{if(!supabase)return null;const{data:M,error:L}=await supabase.auth.getSession();if(L)throw L;return M.session?.access_token??null},[]),s=reactExports.useCallback(async(M,L)=>{if(!supabase)return null;const{data:P}=await supabase.auth.getSession(),F=P.session?.access_token??null;if(!F)return null;let D;try{D=await fetch(M,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${F}`},body:JSON.stringify(L)})}catch(U){return reportCloudWriteFailure(M,{kind:"throw",error:U},logClientError),null}return D.ok?await D.json():(reportCloudWriteFailure(M,{kind:"http",status:D.status},logClientError),null)},[]),o=reactExports.useCallback(async(M,L)=>await postRequiredJson(M,L,{getAccessToken:async()=>t?await e():null,reportFailure:P=>reportCloudWriteFailure(M,P,logClientError),timeoutMs:8e3}),[e,t]),r=reactExports.useCallback(async(M,L)=>{if(!supabase)throw new Error("Please sign in before sending feedback.");const{data:P}=await supabase.auth.getSession(),F=P.session?.access_token??null;if(!F)throw new Error("Please sign in before sending feedback.");const D=await fetch(M,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${F}`},body:JSON.stringify(L)});let U=null;try{U=await D.json()}catch{U=null}if(!D.ok)throw new Error(U?.message??"Request failed");return U},[]),a=reactExports.useCallback(async M=>{if(!supabase)return null;const{data:L}=await supabase.auth.getSession(),P=L.session?.access_token??null;if(!P)return null;const F=new AbortController,D=setTimeout(()=>F.abort(),12e3);let U;try{U=await fetch(M,{headers:{Authorization:`Bearer ${P}`},signal:F.signal})}catch(q){return reportCloudWriteFailure(M,{kind:"throw",error:q},logClientError),null}finally{clearTimeout(D)}return U.ok?await U.json():(reportCloudWriteFailure(M,{kind:"http",status:U.status},logClientError),null)},[]),i=reactExports.useCallback(async()=>{if(!supabase||!t)return{strandProgress:null,sessionHistory:null};const[M,L]=await Promise.all([supabase.from("strand_progress").select("strand, mastery_percent").eq("user_id",t),supabase.from("session_results").select("strand_label, strands, total, correct_points, wrong_by_theme, wrong_examples_by_theme, played_at").eq("user_id",t).order("played_at",{ascending:!1}).limit(5)]);let P=null;if(M.data&&M.data.length>0){P={};for(const D of M.data)P[D.strand]=D.mastery_percent}let F=null;return L.data&&L.data.length>0&&(F=L.data.map(D=>({sessionLabel:D.strand_label,strands:D.strands,total:D.total,correct:D.correct_points,wrongByTheme:D.wrong_by_theme??{},wrongExamplesByTheme:D.wrong_examples_by_theme??{},playedAtIso:D.played_at}
53))),{strandProgress:P,sessionHistory:F}},[t]),l=reactExports.useCallback(async M=>{!supabase||!t||await supabase.from("session_results").insert({user_id:t,strand_label:M.sessionLabel,strands:M.strands,total:M.total,correct_points:M.correct,wrong_by_theme:M.wrongByTheme,wrong_examples_by_theme:M.wrongExamplesByTheme})},[t]),u=reactExports.useCallback(async M=>await o("/api/sessions/start",{childId:M.childId,sessionMode:"practice",sessionConfig:M.sessionConfig,sessionId:M.sessionId,clientDevice:typeof navigator<"u"?navigator.userAgent:null}),[o]),d=reactExports.useCallback(async M=>{if(!supabase||!t)return{};const{data:L}=await supabase.from("pupil_skill_states").select("skill_id, state").eq("child_id",M);return L?Object.fromEntries(L.map(P=>[P.skill_id,P.state])):{}},[t]),m=reactExports.useCallback(async M=>{if(!supabase||!t)return{};const{data:L}=await supabase.from("pupil_skill_states").select("skill_id, last_attempt_at").eq("child_id",M);if(!L)return{};const P=Date.now(),F={};for(const D of L){const U=D.last_attempt_at,q=U?new Date(U).getTime():NaN;Number.isFinite(q)&&(F[D.skill_id]=Math.max(0,(P-q)/864e5))}return F},[t]),h=reactExports.useCallback(async M=>{if(!supabase||!t)return{};const{data:L}=await supabase.from("pupil_skill_states").select("skill_id, recent_accuracy").eq("child_id",M);if(!L)return{};const P={};for(const F of L){const D=Number(F.recent_accuracy);Number.isFinite(D)&&(P[F.skill_id]=D)}return P},[t]),f=reactExports.useCallback(async M=>!supabase||!t?null:(await s("/api/attempts",M))?.skillState??null,[t]),g=reactExports.useCallback(M=>{!supabase||!t||s("/api/wex-events",M).catch(()=>{})},[t]),b=reactExports.useCallback(async(M,L)=>{if(!supabase||!t)return!1;const{count:P,error:F}=await supabase.from("attempt_events").select("id",{count:"exact",head:!0}).eq("child_id",M).like("question_uid",`cb-${L}.%`);return F?(console.warn("[wex] attempt_events lookup failed:",F.message),!1):(P??0)>0},[t]),x=reactExports.useCallback(async M=>{!supabase||!t||await s("/api/question-flags",M)},[t]),y=reactExports.useCallback(async M=>{if(!supabase||!t)throw new Error("Please sign in before sending feedback.");await r("/api/feedback",M)},[t]),E=reactExports.useCallback(async M=>{!supabase||!t||await s("/api/sessions/complete",M)},[t]),k=reactExports.useCallback(async M=>!supabase||!t?null:await a(`/api/parent-snapshot?childId=${encodeURIComponent(M)}`),[t]),T=reactExports.useCallback(async M=>!supabase||!t?[]:(await a(`/api/question-flags?childId=${encodeURIComponent(M)}`))?.flags??[],[t]),A=reactExports.useCallback(async M=>{if(!supabase||!t)return null;const{data:L,error:P}=await supabase.from("children").select("sessions_completed, settled_difficulty_step").eq("id",M).maybeSingle();return P||!L?null:{sessionsCompleted:L.sessions_completed??0,lastEndStep:typeof L.settled_difficulty_step=="number"?L.settled_difficulty_step:null}},[t]),N=reactExports.useCallback(async(M,L)=>{if(!supabase||!t)return;const{data:P}=await supabase.from("children").select("sessions_completed, placement_state").eq("id",M).maybeSingle();if(!P)return;const F=(P.sessions_completed??0)+1,D=P.placement_state!=="calibrated"&&F>=PROGRESS_CONFIG.settling.sessions,U={sessions_completed:F};D&&(U.placement_state="calibrated"),typeof L=="number"&&(U.settled_difficulty_step=L),await supabase.from("children").update(U).eq("id",M)},[t]),S=reactExports.useCallback(async M=>{if(!supabase||!t)return;const L=Object.entries(M).map(([P,F])=>({user_id:t,strand:P,mastery_percent:F,updated_at:new Date().toISOString()}));L.length!==0&&await supabase.from("strand_progress").upsert(L,{onConflict:"user_id,strand"})},[t]);return reactExports.useMemo(()=>({loadInitialData:i,getParentSnapshot:k,getQuestionFlags:T,loadSkillStates:d,loadSkillRecency:m,loadSkillAccuracy:h,hasAttemptedMicroSkill:b,startBackendSession:u,recordAttemptEvent:f,recordWexEvent:g,flagQuestion:x,submitParentFeedback:y,completeBackendSession:E,syncSessionResult:l,syncStrandProgress:S,loadDifficultyCarry:A,bumpSessionsCompleted:N,isSyncing:!1}),[i,k,T,d,m,h,b,u,f,x,y,E,l,S,A,N])}let provider=null;function registerApiAuthProvider(n){provider=n}async function buildApiAuthHeaders(){if(!provider)return{};try{return await provider()}catch{return{}}}const BILLING_TIMEOUT_MS=1e4;async function fetchBillingStatus(n){const t=new AbortController,e=setTimeout(()=>t.abort(),BILLING_TIMEOUT_MS),s=`/api/billing${n?`?sessionId=${encodeURIComponent(n)}`:""}`;try{const o=await buildApiAuthHeaders(),r=await fetch(s,{headers:o,signal:t.signal});return r.ok?await r.json():(reportCloudWriteFailure("/api/billing",{kind:"http",status:r.status},logClientError),null)}catch(o){return reportCloudWriteFailure("/api/billing",{kind:"throw",error:o},logClientError),null}finally{clearTimeout(e)}}async function postBillingAction(n){try{const t=await buildApiAuthHeaders(),e=await fetch("/api/billing",{method:"POST",headers:{"Content-Type":"application/json",...t},body:JSON.stringify(n)});let s=null;try{s=await e.json()}catch{s=null}return s&&(s.url||s.error)?s:(reportCloudWriteFailure("/api/billing",{kind:"http",status:e.status},logClientError),{error:"network_error"})}catch(t){return reportCloudWriteFailure("/api/billing",{kind:"throw",error:t},logClientError),{error:"network_error"}}}function createCheckoutSession(n,t){return postBillingAction({action:"create-checkout-session",priceKey:n,...t?{referralCode:t}:{}})}function createPortalSession(){return postBillingAction({action:"create-portal-session"})}async function fetchReferralInfo(){try{const n=await buildApiAuthHeaders(),t=await fetch("/api/billing",{method:"POST",headers:{"Content-Type":"application/json",...n},body:JSON.stringify({action:"referral-info"})});if(!t.ok)return null;const e=await t.json();return typeof e.code=="string"?e:null}catch(n){return reportCloudWriteFailure("/api/billing",{kind:"throw",error:n},logClientError),null}}function computeEntitlement({isAdmin:n,loading:t,raw:e}){return n||t?!0:e?.entitled??!0}function formatDate$1(n){if(!n)return"";const t=new Date(n);return Number.isNaN(t.getTime())?"":t.toLocaleDateString("en-GB",{day:"numeric",month:"short",year:"numeric"})}function derivePlanCopy(n,t,e,s){return n==="trialing"?{line:`Free trial until ${formatDate$1(t)}`,tone:"normal"}:n==="active"&&s?{line:`Ends ${formatDate$1(e)}`,tone:"normal"}:n==="active"?{line:`Renews ${formatDate$1(e)}`,tone:"normal"}:n==="past_due"?{line:"There's a problem with your payment — update your card",tone:"warning"}:{line:"No active subscription",tone:"normal"}}function useSubscriptionStatus(){const{session:n,isAdmin:t}=useAuth(),e=n?.user?.id??null,[s,o]=reactExports.useState(null),[r,a]=reactExports.useState(!1),i=reactExports.useCallback(async d=>{if(!e)return;a(!0);const m=await fetchBillingStatus(d);o(m),a(!1)},[e]);reactExports.useEffect(()=>{if(!e){o(null),a(!1);return}i()},[e]);const l=reactExports.useCallback(async d=>{e&&await i(d)},[e,i]),u=reactExports.useMemo(()=>computeEntitlement({isAdmin:t,loading:r,raw:s}),[t,r,s]);
53return{status:s?.status??null,entitled:u,loading:r,foundingMember:s?.foundingMember??!1,foundingAvailable:s?.foundingAvailable??!1,trialEnd:s?.trialEnd??null,currentPeriodEnd:s?.currentPeriodEnd??null,cancelAtPeriodEnd:s?.cancelAtPeriodEnd??!1,refresh:l}}const DUPLICATE_EMAIL_MESSAGE="That email already has an account — try signing in instead.";function friendlyAuthError(n){if(!n)return"Something went wrong. Please try again.";const t=n.toLowerCase();return t.includes("invalid login")||t.includes("invalid credentials")?"That email or password doesn't look right. Please check and try again.":t.includes("already registered")||t.includes("already exists")||t.includes("already been registered")?DUPLICATE_EMAIL_MESSAGE:t.includes("rate limit")||t.includes("too many")?"Too many tries just now — please wait a minute and try again.":t.includes("row-level security")||t.includes("violates row-level")||t.includes("rls")?"We couldn't finish setting up your account. Please try again in a moment, or contact us if it keeps happening.":t.includes("password")&&(t.includes("at least")||t.includes("should be")||t.includes("characters"))?"Please choose a password with at least 6 characters.":t.includes("failed to fetch")||t.includes("network")||t.includes("load failed")?"Couldn't reach the server — check your connection and try again.":t.includes("email")&&t.includes("invalid")?"That email address doesn't look right. Please check it and try again.":t.includes("not confirmed")||t.includes("email not confirmed")?"You haven't confirmed your email yet — check your inbox and spam folder for the confirmation link. You can resend it below.":n}const PARENT_UNLOCK_IDLE_MS=300*1e3,PARENT_FRESH_AUTH_GRACE_MS=300*1e3;function isProtectedParentScreen(n){return n==="parent"||n==="account"||n==="pricing"}function useParentGate(){const{session:n,requestPasswordReset:t}=useAuth(),[e,s]=reactExports.useState(!1),[o,r]=reactExports.useState(null),a=reactExports.useRef(!1),i=reactExports.useRef(0),l=reactExports.useRef(null),u=reactExports.useRef(null),d=reactExports.useRef(0),m=reactExports.useRef(n?.user.id??null),h=reactExports.useCallback(()=>{l.current!==null&&(clearTimeout(l.current),l.current=null)},[]),f=reactExports.useCallback(()=>{h(),a.current=!1,i.current=0,s(!1)},[h]),g=reactExports.useCallback(()=>{h(),l.current=setTimeout(f,PARENT_UNLOCK_IDLE_MS)},[h,f]),b=reactExports.useCallback(()=>{a.current=!0,i.current=Date.now(),s(!0),g()},[g]),x=reactExports.useCallback(A=>{const N=u.current;u.current=null,r(null),N?.(A)},[]),y=reactExports.useCallback((A={kind:"parent-area"},N={})=>{if(!n)return Promise.resolve(!0);const S=Date.now()-i.current<PARENT_FRESH_AUTH_GRACE_MS;return a.current&&(!N.fresh||S)?(g(),Promise.resolve(!0)):(u.current?.(!1),new Promise(M=>{u.current=M,d.current+=1,r({id:d.current,reason:A})}))},[g,n]),E=reactExports.useCallback(()=>{x(!1)},[x]),k=reactExports.useCallback(async A=>{const N=n?.user.email;if(!supabase||!n||!N)return{ok:!1,error:"We couldn't verify this account. Try signing in again or email [email protected]."};if(!A)return{ok:!1,error:"Enter the parent account password."};try{const{error:S}=await supabase.auth.signInWithPassword({email:N,password:A});return S?{ok:!1,error:friendlyAuthError(S.message)}:(b(),x(!0),{ok:!0,error:null})}catch(S){return{ok:!1,error:friendlyAuthError(S instanceof Error?S.message:null)}}},[x,n,b]),T=reactExports.useCallback(async()=>{const A=n?.user.email;if(!A)return{ok:!1,error:"We couldn't find the account email. Sign in again or email [email protected]."};const{error:N}=await t(A);return N?{ok:!1,error:friendlyAuthError(N)}:{ok:!0,error:null}},[t,n?.user.email]);return reactExports.useEffect(()=>{if(!e)return;const A=()=>g();return window.addEventListener("pointerdown",A),window.addEventListener("keydown",A),()=>{window.removeEventListener("pointerdown",A),window.removeEventListener("keydown",A)}},[g,e]),reactExports.useEffect(()=>{const A=n?.user.id??null,N=m.current;m.current=A,(A===null||N!==null&&N!==A)&&(f(),x(!1))},[x,f,n?.user.id]),reactExports.useEffect(()=>()=>{h(),u.current?.(!1)}
53,[h]),{isUnlocked:e,prompt:o,accountEmail:n?.user.email??null,requestAccess:y,verifyPassword:k,sendPasswordReset:T,cancelRequest:E,lock:f,unlock:b}}function carryKey(n){return`mathsquest:difficultyCarry:${n}`}function loadLocalDifficultyCarry(n){const t=readStorageJson(carryKey(n));return!t||typeof t.sessionsCompleted!="number"?{sessionsCompleted:0,lastEndStep:null}:{sessionsCompleted:t.sessionsCompleted,lastEndStep:typeof t.lastEndStep=="number"?t.lastEndStep:null}}function bumpLocalDifficultyCarry(n,t){if(typeof window>"u")return;const e=readStorageJson(carryKey(n)),s=(e?.sessionsCompleted??0)+1,o={sessionsCompleted:s,lastEndStep:typeof t=="number"?t:e?.lastEndStep??null,placementState:e?.placementState==="calibrated"||s>=PROGRESS_CONFIG.settling.sessions?"calibrated":"bootstrapping"};try{window.localStorage.setItem(carryKey(n),JSON.stringify(o))}catch{}}const auth="_auth_1ppfa_4 _palette_1qy3r_3",styles$8={auth};function LoginScreen({onSwitchToSignUp:n,onBack:t,onSignedIn:e}){const{requestPasswordReset:s,resendConfirmation:o}=useAuth(),[r,a]=reactExports.useState("signin"),[i,l]=reactExports.useState(""),[u,d]=reactExports.useState(""),[m,h]=reactExports.useState(null),[f,g]=reactExports.useState(null),[b,x]=reactExports.useState(!1),[y,E]=reactExports.useState(!1),k=S=>{a(S),h(null),g(null),E(!1)},T=async S=>{if(S.preventDefault(),!supabase)return;h(null),g(null),E(!1),x(!0);const{error:M}=await supabase.auth.signInWithPassword({email:i,password:u});M?(h(friendlyAuthError(M.message)),/not confirmed/i.test(M.message)&&E(!0)):e(),x(!1)},A=async()=>{h(null),g(null),x(!0);const{error:S}=await o(i);S?h(friendlyAuthError(S)):(g("Confirmation email sent. It can take a minute — check your inbox and spam folder."),E(!1)),x(!1)},N=async S=>{S.preventDefault(),h(null),g(null),x(!0);const{error:M}=await s(i);M?h(friendlyAuthError(M)):g("Check your email for a link to reset your password. It can take a minute — please check your spam folder, and message us if it doesn't arrive."),x(!1)};return jsxRuntimeExports.jsx("div",{className:`${styles$8.auth} screen screen-enter`,children:jsxRuntimeExports.jsxs("div",{className:"card auth-card",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact auth-back",type:"button",onClick:t,children:"← Back"}),jsxRuntimeExports.jsxs("div",{className:"sm-logo sm-logo-auth","aria-label":"SmartMaths",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),r==="signin"?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("h1",{children:"Welcome back!"}),jsxRuntimeExports.jsx("p",{className:"auth-note",children:"Sign in to access your child's saved progress and parent tools."}),jsxRuntimeExports.jsxs("form",{className:"auth-form",onSubmit:S=>{T(S)},children:[jsxRuntimeExports.jsxs("label",{children:["Email",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"email",autoComplete:"email",value:i,onChange:S=>l(S.target.value),required:!0})]}),jsxRuntimeExports.jsxs("label",{children:["Password",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"password",autoComplete:"current-password",value:u,onChange:S=>d(S.target.value),required:!0})]}),m&&jsxRuntimeExports.jsx("p",{className:"auth-error",children:m}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"submit",disabled:b,children:b?"Signing in...":"Sign in"})]}),f&&jsxRuntimeExports.jsx("p",{className:"auth-info",children:f}),y&&jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-full",type:"button",onClick:()=>{A()},disabled:b,children:b?"Sending...":"Resend confirmation email"}),jsxRuntimeExports.jsx("p",{className:"auth-switch",children:jsxRuntimeExports.jsx("button",{className:"btn-link",type:"button",onClick:()=>k("forgot"),children:"Forgot password?"})}),jsxRuntimeExports.jsxs("p",{className:"auth-switch",children:["No account?"," ",jsxRuntimeExports.jsx("button",{className:"btn-link",type:"button",onClick:n,children:"Sign up"})]})]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("h1",{children:"Reset your password"}),jsxRuntimeExports.jsx("p",{className:"auth-note",children:"Enter your email and we'll send you a link to set a new password."}),jsxRuntimeExports.jsxs("form",{className:"auth-form",onSubmit:S=>{N(S)},children:[jsxRuntimeExports.jsxs("label",{children:["Email",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"email",autoComplete:"email",value:i,onChange:S=>l(S.target.value),required:!0})]}),m&&jsxRuntimeExports.jsx("p",{className:"auth-error",children:m}),f&&jsxRuntimeExports.jsx("p",{className:"auth-info",children:f}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"submit",disabled:b,children:b?"Sending...":"Send reset link"})]}),jsxRuntimeExports.jsx("p",{className:"auth-switch",children:jsxRuntimeExports.jsx("button",{className:"btn-link",type:"button",onClick:()=>k("signin"),children:"Back to sign in"})})]})]})})}const LEGAL_PUBLIC_COPY_READY=!0,LEGAL_DRAFT_DATE="17 August 2026",ICO_REGISTRATION={reference:"ZC207505",publicRegisterUrl:"https://ico.org.uk/ESDWebPages/Entry/ZC207505"},LEGAL_OWNER_FACTS={legalIdentity:"Fourteen Change Consulting Limited",tradingName:"SmartMaths",publishableAddress:"128 City Road, London, EC1V 2NX",addressLabel:"Business correspondence address",companyNumber:"16353916",registeredIn:"England and Wales",icoRegistration:ICO_REGISTRATION.reference}
53,LEGAL_CONTACTS={contactEmail:"[email protected]",supportEmail:"[email protected]",privacyEmail:"[email protected]",inboxStatus:"TESTED AND MONITORED"},LEGAL_DOCUMENTS={terms:{kind:"terms",key:"terms:terms-2026-08-17-1",version:"terms-2026-08-17-1",title:"Terms for families"},privacy:{kind:"privacy",key:"parent_privacy:privacy-2026-08-17-1",version:"privacy-2026-08-17-1",title:"Privacy notice for parents"},childPrivacy:{kind:"child_privacy",key:"child_privacy:child-privacy-2026-08-17-1",version:"child-privacy-2026-08-17-1",title:"Your privacy at SmartMaths"},checkoutSummary:{kind:"checkout_summary",key:"checkout_summary:checkout-summary-2026-08-17-1",version:"checkout-summary-2026-08-17-1",title:"Subscription checkout summary"},foundingOffer:{kind:"founding_offer",key:"founding_offer:founding-50-2026-08-17-1",version:"founding-50-2026-08-17-1",title:"Founding family offer"},signupDeclaration:{kind:"signup_declaration",key:"signup_declaration:signup-declaration-2026-08-17-1",version:"signup-declaration-2026-08-17-1",title:"Adult and parental-authority declarations"}},SMARTMATHS_PLANS={monthly:{key:"monthly",name:"Monthly",pricePence:1199,currency:"gbp",interval:"month",intervalLabel:"month"},annual:{key:"annual",name:"Annual",pricePence:9900,currency:"gbp",interval:"year",intervalLabel:"year"}},SMARTMATHS_TRIAL_DAYS=14,FOUNDING_OFFER={stripeCouponPercent:50,pricePence:{monthly:599,annual:4950},publicLabel:"£5.99 a month or £49.50 a year while your subscription stays active",rules:{eligibility:"The first 50 new family accounts that choose the Founding price for their first SmartMaths subscription qualify. Starting the free trial uses a place.",plans:"The fixed Founding price is £5.99 a month or £49.50 a year. Switching between those plans without cancelling keeps the Founding price.",futurePrices:"These Founding prices stay fixed while the same subscription remains active, even if the standard price changes later.",cancellation:"If the subscription actually ends, including during the trial, the offer ends. Reversing a scheduled cancellation before the subscription ends keeps it; starting a new subscription later does not restore it.",account:"The offer is limited to one parent account and cannot be transferred to another account."}},REFERRAL_OFFER={discountPence:1199,foundingDiscountPence:600,rules:{eligibility:"Referral codes are for parents joining SmartMaths for the first time. A parent cannot use their own code.",familyDiscount:"A valid code takes £11.99 off the first standard-price subscription charge due after the free trial. With the Founding price, it takes £5.99 off the first monthly charge (reducing £5.99 to £0) or £6 off the first annual charge (reducing £49.50 to £43.50). The discount cannot be greater than that charge.",referrerReward:"The referring parent receives a credit worth one month at their own effective subscription rate only after the new family makes its first positive payment, and only if the referring parent's subscription is then active, trialling or temporarily past due.",stacking:"A valid referral can be used with the Founding family offer. Stripe may show only the Founding discount; a valid referral is attached during the trial and appears on the first post-trial invoice.",cap:"A parent can receive up to 6 referral rewards in any rolling 365-day period. The cap and subscription condition are checked when each reward becomes due, not when the code is shared.",credit:"Referral rewards are account credit for later SmartMaths bills. They are not cash, cannot be transferred and have no cash value."}},LEGAL_RELEASE_CHECKS={finalCopyApproved:!0},REQUIRED_DOCUMENT_KEY_PREFIXES=["terms:","parent_privacy:","child_privacy:","signup_declaration:","checkout_summary:","founding_offer:"],DRAFT_MARKER=/(^|[-:])draft($|[-:])/i;function evaluateLegalPublicationReadiness({releaseReady:n,ownerFacts:t,inboxStatus:e,documents:s,releaseChecks:o}){const r=REQUIRED_DOCUMENT_KEY_PREFIXES.every(a=>s.some(i=>i.key.startsWith(a)&&!DRAFT_MARKER.test(i.key)&&!DRAFT_MARKER.test(i.version)));return t.length>0&&t.every(a=>a.trim().length>0&&!a.includes("OWNER TO CONFIRM"))&&e==="TESTED AND MONITORED"&&r&&o.length>0&&o.every(Boolean)}function isPublicLegalCopyReady(){return evaluateLegalPublicationReadiness({releaseReady:LEGAL_PUBLIC_COPY_READY,ownerFacts:Object.values(LEGAL_OWNER_FACTS),inboxStatus:LEGAL_CONTACTS.inboxStatus,documents:Object.values(LEGAL_DOCUMENTS),releaseChecks:[LEGAL_RELEASE_CHECKS.finalCopyApproved]})}function formatGbp(n){const t=n/100;return t%1===0?`£${t.toFixed(0)}`:`£${t.toFixed(2)}`}function foundingPricePence(n){return FOUNDING_OFFER.pricePence[n.key]}function referralDiscountPence(n,t){const e=t==="founding"?foundingPricePence(n):n.pricePence,s=t==="founding"?REFERRAL_OFFER.foundingDiscountPence:REFERRAL_OFFER.discountPence;return Math.min(e,s)}function referralFirstPaymentPence(n,t){return(t==="founding"?foundingPricePence(n):n.pricePence)-referralDiscountPence(n,t)}function referralFirstPaymentText(n,t){const e=t==="founding"?foundingPricePence(n):n.pricePence,s=referralFirstPaymentPence(n,t),o=referralDiscountPence(n,t);return`With the ${t==="founding"?"Founding price":"standard price"}, a valid referral reduces the first post-trial ${n.intervalLabel} charge from ${formatGbp(e)} to ${formatGbp(s)} (${formatGbp(o)} off).`}function checkoutSummaryText(n,t){const e=n.pricePence;return[`${SMARTMATHS_TRIAL_DAYS} days free. No charge today.`,`Then ${formatGbp(e)} a ${n.intervalLabel}, starting ${SMARTMATHS_TRIAL_DAYS} days after you subscribe, until you cancel.`].join(" ")}const SIGNUP_DECLARATIONS={adult:"I am 18 or over.",authority:"I am this child's parent or guardian, or I otherwise have authority to add them to SmartMaths.",childUse:"I authorise this child to use SmartMaths."},privacyMailto=`mailto:${LEGAL_CONTACTS.privacyEmail}`,supportMailto=`mailto:${LEGAL_CONTACTS.supportEmail}`,contactMailto=`mailto:${LEGAL_CONTACTS.contactEmail}`,showDraftScaffolding=!isPublicLegalCopyReady(),refundMailto=`${supportMailto}?subject=${encodeURIComponent("Cancel my SmartMaths subscription")}&body=${encodeURIComponent(`I give notice that I cancel my SmartMaths subscription. Please confirm the cancellation, whether renewal is stopped, and any refund due under my statutory rights or the SmartMaths terms.
54
55Account email:
56Subscription date:
57
58Please do not include a child's name.`)}
58`,SUPPLIERS=[{name:"Supabase",purpose:"Secure login, database and file storage",childData:"Parent account, child profile and learning records. SmartMaths does not use Supabase to store newly synthesised read-aloud audio."},{name:"Vercel",purpose:"Hosting, security, delivery and cookieless aggregate Web Analytics",childData:"Normal web traffic and technical logs; no child profile in analytics"},{name:"OpenAI",purpose:"Writing some hints after a wrong answer",childData:"Question, typed answer, correct answer and year group. SmartMaths does not add the child's profile name or account ID, but anything typed in an answer is sent as entered."},{name:"Microsoft Azure",purpose:"Reading maths text aloud",childData:"Displayed text sent for speech, including any typed answer as entered. SmartMaths does not add the child's profile name or account ID. Audio is returned privately to the signed-in browser and is not persisted by SmartMaths."},{name:"Stripe",purpose:"Subscription checkout, payments, invoices and cancellations",childData:"Parent contact, subscription and payment details; no child profile or learning record"},{name:"GitHub",purpose:"Encrypted backups, private code/test artefacts, and 14-day private operations-report artefacts",childData:"Encrypted backups and operations reports can contain child names, year groups and learning/feedback records; reports can also contain parent contact details"},{name:"Resend",purpose:"Delivery of account, password-reset, feedback and operational email",childData:"Parent email and message content. Feedback can include a child reference when the parent attaches it to a child, and subscriber alerts can include limited parent and billing references. These emails are not intended to contain a child's name or learning record."}];function DraftNotice(){return jsxRuntimeExports.jsxs("aside",{className:"sm-legal-draft-warning",role:"note","aria-label":"Legal draft warning",children:[jsxRuntimeExports.jsx("strong",{children:"Review draft — do not publish"}),jsxRuntimeExports.jsxs("p",{children:["Supplier account evidence, the additional-child authority record, migration rehearsal, durable contract-confirmation delivery evidence, final Stripe test-mode spot checks and final approvals remain open. The first-release route for existing families to receive and, where required, accept the current legal information also remains open. The legacy read-aloud cache must be made private, purged through the Storage API and proved empty. A tested periodic review and deletion procedure is a publication gate; the notices do not promise an unproved timetable. Draft date: ",LEGAL_DRAFT_DATE,"."]})]})}function DocumentMeta({version:n}){return jsxRuntimeExports.jsxs("p",{className:"sm-legal-eyebrow",children:[showDraftScaffolding&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:["Review draft · ",LEGAL_DRAFT_DATE," · "]}),"Version ",n]})}function PrivacyNotice(){return jsxRuntimeExports.jsxs("section",{id:"legal-privacy",className:"sm-legal-doc",children:[jsxRuntimeExports.jsx(DocumentMeta,{version:LEGAL_DOCUMENTS.privacy.version}),jsxRuntimeExports.jsx("h2",{children:"Privacy notice for parents"}),jsxRuntimeExports.jsx("p",{children:"SmartMaths is a maths practice service for families with children in Year 1 to Year 6. Its topics are guided by England's national curriculum, but it does not promise full or term-by-term curriculum coverage. This notice explains what information the service uses, why it uses it, and the choices your family has."}),jsxRuntimeExports.jsx("h3",{children:"Who is responsible"}),jsxRuntimeExports.jsxs("dl",{className:"sm-legal-facts",children:[jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("dt",{children:"Legal identity"}),jsxRuntimeExports.jsxs("dd",{children:[LEGAL_OWNER_FACTS.legalIdentity,", trading as ",LEGAL_OWNER_FACTS.tradingName]})]}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("dt",{children:LEGAL_OWNER_FACTS.addressLabel}),jsxRuntimeExports.jsx("dd",{children:LEGAL_OWNER_FACTS.publishableAddress})]}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("dt",{children:"Company number"}),jsxRuntimeExports.jsx("dd",{children:LEGAL_OWNER_FACTS.companyNumber})]}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("dt",{children:"Registered in"}),jsxRuntimeExports.jsx("dd",{children:LEGAL_OWNER_FACTS.registeredIn})]}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("dt",{children:"ICO registration"}),jsxRuntimeExports.jsx("dd",{children:jsxRuntimeExports.jsx("a",{href:ICO_REGISTRATION.publicRegisterUrl,children:LEGAL_OWNER_FACTS.icoRegistration})})]})]}),jsxRuntimeExports.jsxs("p",{children:["This company is the data controller. For privacy questions, rights requests or a data protection complaint, email"," ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail}),".",showDraftScaffolding&&jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:" All three SmartMaths email routes are recorded as tested and monitored."})]}),jsxRuntimeExports.jsxs("p",{children:["For general enquiries, email"," ",jsxRuntimeExports.jsx("a",{href:contactMailto,children:LEGAL_CONTACTS.contactEmail}),". For account, subscription or service help, email ",jsxRuntimeExports.jsx("a",{href:supportMailto,children:LEGAL_CONTACTS.supportEmail}),"."]}),jsxRuntimeExports.jsx("h3",{children:"What we collect"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Parent account:"})," email address, first name, login and account-security records. SmartMaths does not see the password itself."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Child profile:"}
58)," a first name or nickname and year group. We do not ask for a surname, date of birth, home address, photo, phone number or the child's email."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Learning records:"})," questions shown, answers, correct or incorrect outcomes, hints, read-aloud and worked examples used, time taken, practice sessions and progress."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"AI and voice records:"})," the maths question, typed answer, correct answer, generated hint, what happened next, and text sent to the read-aloud service. SmartMaths does not add the child's profile name or account ID to AI or voice requests, but anything typed in an answer is sent as entered. Read-aloud audio is returned privately and held only temporarily in the open page; SmartMaths does not persist it."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Billing records:"})," Stripe customer and subscription references, plan status, invoices held by Stripe, referral promotion and credit-ledger references, and the plan and offer used at checkout."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Messages and technical records:"})," feedback, support messages, reply address, security/error logs, IP address and device/browser details needed to keep the service working."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Browser storage:"})," the selected child, display and sound preferences, short-lived sign-in and screen state, and practice/progress state needed to continue a round safely. Signing out removes the sign-in token and selected-child link. Preferences and per-child practice/progress values can remain on that device after sign-out so the app can resume consistently; clearing SmartMaths site data removes them. Session-only values disappear when the browser session ends."]}),jsxRuntimeExports.jsxs("li",{children:[jsxRuntimeExports.jsx("strong",{children:"Aggregate website analytics:"})," page view, referring site, broad country and device/browser type, plus a count of how far visitors get through the free sample round (started, each question answered right or wrong, whether Mia's examples were watched or skipped, finished, sign-up button pressed and sign-up completed — with the year group only, never the answer typed). Vercel Web Analytics is cookieless, stores no identifier on the device, and receives URLs only after SmartMaths removes query strings and hashes that can contain referral, checkout or sign-in information."]})]}),jsxRuntimeExports.jsx("p",{children:"The browser storage used for sign-in, security, preferences and an active practice round is necessary to provide the service the family asks for. It is not used for advertising or to follow a family across other websites. Vercel Web Analytics uses no cookie or device-stored identifier; SmartMaths uses its aggregate reports under its legitimate interest in understanding whether the public site works. Blocking analytics does not stop practice."}),jsxRuntimeExports.jsx("h3",{children:"What you need to give us"}),jsxRuntimeExports.jsx("p",{children:"To create and use a family account, the parent must provide their first name, email address and a password, plus the first child's first name or nickname and year group. The parent must also make the sign-up declarations shown on the form. Without those details and declarations, SmartMaths cannot create the account or provide personalised practice. A payment method is required only when the parent chooses to start a paid trial; Stripe collects it and SmartMaths does not receive the full card number."}),jsxRuntimeExports.jsx("p",{children:"Support messages, feedback and an alternative reply address are optional. Not providing them does not stop the family using the core service. Profile details come from the parent; learning records are created as the child uses SmartMaths; technical records come from the browser, device and service; and subscription status and payment references come from Stripe."}),jsxRuntimeExports.jsx("h3",{children:"Why we use it"}),jsxRuntimeExports.jsx("div",{className:"sm-legal-table-wrap",children:jsxRuntimeExports.jsxs("table",{className:"sm-legal-table",children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("th",{scope:"col",children:"Purpose"}),jsxRuntimeExports.jsx("th",{scope:"col",children:"Why the law allows it"})]})}),jsxRuntimeExports.jsxs("tbody",{children:[jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Create and run the parent account and paid family service"}),jsxRuntimeExports.jsx("td",{children:"To perform the contract with the parent"})]}),jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Provide and gently adapt a child's maths practice"}),jsxRuntimeExports.jsx("td",{children:"Our legitimate interests in providing the service the parent requested, balanced carefully against the child's rights"})]}),jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Keep accounts secure, diagnose faults and prevent misuse"}),jsxRuntimeExports.jsx("td",{children:"Our legitimate interests in operating a safe and reliable service"})]}),jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Run referrals and apply subscription credits"}),jsxRuntimeExports.jsx("td",{children:"To perform the offer made to the participating parents and prevent misuse"})]}),jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Understand aggregate public-site use without cookies or device identifiers"}),jsxRuntimeExports.jsx("td",{children:"Our legitimate interests in improving the service and public information"})]}),jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Keep tax, payment, complaint and legal evidence"}),jsxRuntimeExports.jsx("td",{children:"Legal obligations and establishing, exercising or defending legal claims"})]}),jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:"Send optional marketing"}),jsxRuntimeExports.jsx("td",{children:"Consent, collected separately and never assumed from creating an account"})]})]})]})}),jsxRuntimeExports.jsx("p",{children:"We do not describe reading this notice as consent. If SmartMaths ever needs consent for an optional use, it will ask separately and make refusal as easy as agreement."}),jsxRuntimeExports.jsx("h3",{children:"How SmartMaths is designed for children"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:"No adverts, in-app purchases, public profiles or chat."}),jsxRuntimeExports.jsx("li",{children:"No precise location tracking or location history and no sale or rental of family information. Aggregate website analytics can report a broad country."}),jsxRuntimeExports.jsx("li",{children:"High-privacy defaults: a parent creates and controls the account."}),jsxRuntimeExports.jsx("li",{children:"Only a first name or nickname and 
58year group are requested for a child."}),jsxRuntimeExports.jsx("li",{children:"Parents can see progress and ask for records to be corrected, exported or deleted."})]}),jsxRuntimeExports.jsx("h3",{children:"Adaptation and AI"}),jsxRuntimeExports.jsx("p",{children:"SmartMaths uses answers and progress to choose a suitable next question. This is automated personalisation for learning; it does not make a legal or similarly important decision about a child. A parent can ask for an explanation or human review."}),jsxRuntimeExports.jsx("p",{children:"Some hints are written by OpenAI after a wrong answer. The prompt can include the question, the child's typed answer as entered, the correct answer and year group. SmartMaths does not add the child's profile name or account ID, but a child could type personal information in an answer. AI hints can be wrong, so they are support rather than a formal assessment. Read-aloud can send the displayed maths text, including a typed answer as entered, to Microsoft Azure; SmartMaths does not add the child's profile name or account ID to that request."}),jsxRuntimeExports.jsx("h3",{children:"Companies that help us"}),jsxRuntimeExports.jsx("p",{children:"SmartMaths uses these companies for the jobs described below. The table explains what each company does and what information can be involved. You can ask SmartMaths which company is used for a particular job and for more information about how it protects personal data."}),jsxRuntimeExports.jsx("div",{className:"sm-legal-table-wrap",children:jsxRuntimeExports.jsxs("table",{className:"sm-legal-table sm-legal-supplier-table",children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("th",{scope:"col",children:"Supplier"}),jsxRuntimeExports.jsx("th",{scope:"col",children:"What it does"}),jsxRuntimeExports.jsx("th",{scope:"col",children:"Information involved"})]})}),jsxRuntimeExports.jsx("tbody",{children:SUPPLIERS.map(n=>jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("th",{scope:"row",children:n.name}),jsxRuntimeExports.jsx("td",{children:n.purpose}),jsxRuntimeExports.jsx("td",{children:n.childData})]},n.name))})]})}),jsxRuntimeExports.jsx("h3",{children:"Where information is processed"}),jsxRuntimeExports.jsx("p",{children:"Some suppliers can process information outside the UK, including in the United States. When UK law requires an extra safeguard, the transfer must be covered by a recognised protection, such as the UK International Data Transfer Agreement, the UK Addendum to approved standard clauses, or an applicable UK adequacy regulation. You can ask which location and safeguard apply and request a copy of the relevant safeguard."}),jsxRuntimeExports.jsx("h3",{children:"How long we keep it"}),jsxRuntimeExports.jsx("p",{children:"Parent account, login, child profile and learning records are kept while the family uses SmartMaths. Ending a subscription or stopping practice does not itself delete the account or those records. We review personal information and keep it only for as long as it is needed to provide and protect the service, respond to requests, and meet accounting or legal duties."}),jsxRuntimeExports.jsxs("p",{children:["To ask for a copy, correction or deletion, email"," ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail}),". We will confirm the request and may need to verify the requester's identity and, if someone is acting for a child, their authority. We will explain what we can delete and any narrow records the law requires us to keep. Technical and security records, supplier backups, routine support messages and feedback are removed or anonymised when they are no longer needed. We do not keep a child's answers or learning history as long-term payment, contract or complaint evidence."]}),jsxRuntimeExports.jsx("p",{children:"Browser-held sign-in and selected-child values are removed on sign-out, and session-only values end with the browser session. Device preferences and local practice/progress values can remain after sign-out until the family clears SmartMaths site data; the parent controls that device copy."}),showDraftScaffolding&&jsxRuntimeExports.jsx("p",{className:"sm-legal-owner-decision",children:"The published notice intentionally makes no fixed deletion-timetable promise. Before publication, implement and prove the peri
58odic manual or automated review/deletion controls, scheduled cleanups and restore-safe erasure procedure recorded in the DPIA. Also re-confirm that the monitored privacy inbox, signed-in privacy feedback route and manual erasure runbook are ready for individual requests."}),jsxRuntimeExports.jsx("h3",{children:"Your family's rights"}),jsxRuntimeExports.jsx("p",{children:"Rights in a child's personal information belong to the child, not automatically to the parent account holder. A parent or another authorised adult can often exercise them for the child, and parents also have rights in their own personal information. When deciding how to respond, SmartMaths considers the requester's identity and authority and the child's age, understanding, wishes and best interests."}),jsxRuntimeExports.jsxs("p",{children:["Depending on the circumstances, the person whose information is involved can ask for access, correction, deletion, restriction or portability, and can withdraw any consent used for an optional purpose. There is no charge in the usual case. We normally respond within one month. If the law allows more time for a complex request, we will tell you and explain why. Email ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail}),"."]}),jsxRuntimeExports.jsx("h3",{children:"Your right to object"}),jsxRuntimeExports.jsxs("p",{children:["You or your child can object when SmartMaths relies on legitimate interests, including for personalised practice, security or aggregate analytics. Email"," ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail})," or choose “Privacy or my data” in the signed-in feedback form. Tell us which use concerns you. We normally respond within one month and will stop that use unless there are compelling legitimate grounds to continue or the information is needed for a legal claim. We will explain the decision in plain language."]}),jsxRuntimeExports.jsx("h3",{children:"Data protection complaints"}),jsxRuntimeExports.jsxs("p",{children:["You can complain through ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail}),". We will acknowledge a data protection complaint within 30 days, investigate it without undue delay, keep you informed where appropriate, and explain the outcome. You can also complain to the Information Commissioner's Office at"," ",jsxRuntimeExports.jsx("a",{href:"https://ico.org.uk/make-a-complaint/",target:"_blank",rel:"noreferrer",children:"ico.org.uk/make-a-complaint"})," ","or 0303 123 1113."]}),jsxRuntimeExports.jsx("h3",{children:"Changes to this notice"}),jsxRuntimeExports.jsx("p",{children:"The version is shown at the top. If a change materially affects how a child's information is used, SmartMaths will explain it clearly and ask the parent to review it before the new use begins where required."})]})}function TermsNotice(){const n=SMARTMATHS_PLANS.monthly,t=SMARTMATHS_PLANS.annual;return jsxRuntimeExports.jsxs("section",{id:"legal-terms",className:"sm-legal-doc",children:[jsxRuntimeExports.jsx(DocumentMeta,{version:LEGAL_DOCUMENTS.terms.version}),jsxRuntimeExports.jsx("h2",{children:"Terms for families"}),jsxRuntimeExports.jsxs("p",{children:["These are the rules for using SmartMaths. They are between the parent account holder and"," ",jsxRuntimeExports.jsx("strong",{children:LEGAL_OWNER_FACTS.legalIdentity}),", trading as SmartMaths, at"," ",LEGAL_OWNER_FACTS.publishableAddress,". They use plain language on purpose."]}),jsxRuntimeExports.jsx("h3",{children:"Who can create an account"}),jsxRuntimeExports.jsx("p",{children:"The account holder must be 18 or over and be the child's parent or guardian, or have the authority needed to add the child. The adult controls the account and is responsible for supervising the child's use. A child must not create their own parent account. The account holder must have the same parental authority and authorisation for every child they add to SmartMaths."}),jsxRuntimeExports.jsx("p",{children:"A family plan is sold directly to the adult account holder for personal use with children in their family or household. These Terms do not provide a school, tutoring-business or other organisation licence, and account access must not be shared outside the family or household."}),jsxRuntimeExports.jsx("p",{children:"At sign-up, the account holder confirms in one statement that:"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:SIGNUP_DECLARATIONS.adult}),jsxRuntimeExports.jsx("li",{children:SIGNUP_DECLARATIONS.authority}),jsxRuntimeExports.jsx("li",{children:SIGNUP_DECLARATIONS.childUse}),jsxRuntimeExports.jsx("li",{children:"They agree to the Terms and privacy notice available at sign-up."})]}),jsxRuntimeExports.jsx("h3",{children:"What SmartMaths provides"}),jsxRuntimeExports.jsx("p",{children:"SmartMaths provides online maths practice, progress information, worked examples and optional AI-written hints and read-aloud support. It is inspired and guided by England's national curriculum for Year 1 to Year 6, but it is not a school, formal assessment or replacement for a teacher. It does not guarantee full curriculum al
58ignment, a particular result or uninterrupted availability."}),jsxRuntimeExports.jsx("p",{children:"SmartMaths will provide the service with reasonable care and skill. Nothing in these terms reduces the consumer rights the law gives you if the service is not provided properly."}),jsxRuntimeExports.jsx("h3",{children:"What you need to use it"}),jsxRuntimeExports.jsxs("p",{children:["SmartMaths needs an internet connection, a parent account with a working email address, and a current version of a mainstream browser on a supported phone, tablet or computer. It is an online service and does not promise offline access. Read-aloud depends on the browser, device and voice service, so it may not be available everywhere; the maths practice remains usable without it. If an important compatibility limit affects your family, contact"," ",jsxRuntimeExports.jsx("a",{href:supportMailto,children:LEGAL_CONTACTS.supportEmail})," before subscribing."]}),jsxRuntimeExports.jsx("h3",{children:"When the agreements begin"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:"The account-use terms apply when you create the parent account."}),jsxRuntimeExports.jsx("li",{children:"A paid subscription contract starts when Stripe confirms that you completed the subscription checkout."}),jsxRuntimeExports.jsx("li",{children:"Before checkout, SmartMaths must show the plan, recurring total, billing interval, trial, renewal, cancellation terms and any discount. Stripe must show the final order details again before you subscribe."})]}),jsxRuntimeExports.jsx("h3",{children:"Plans, trial and renewal"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsxs("li",{children:["Monthly: ",formatGbp(n.pricePence)," a month after a ",SMARTMATHS_TRIAL_DAYS,"-day free trial."]}),jsxRuntimeExports.jsxs("li",{children:["Annual: ",formatGbp(t.pricePence)," a year after a ",SMARTMATHS_TRIAL_DAYS,"-day free trial."]}),jsxRuntimeExports.jsx("li",{children:"No charge is taken when the trial starts, but Stripe asks for a payment method."}),jsxRuntimeExports.jsx("li",{children:"Unless you cancel, the plan renews automatically at the interval shown at checkout. SmartMaths may change the standard renewal price after giving advance notice. Cancel during the trial and no subscription payment is taken."}),jsxRuntimeExports.jsx("li",{children:"The checkout total is the full amount payable. SmartMaths will not add an extra fee or tax at checkout."})]}),jsxRuntimeExports.jsx("h3",{children:"Founding family offer"}),jsxRuntimeExports.jsxs("p",{children:["Where the live checkout confirms a place is available, the offer is"," ",jsxRuntimeExports.jsx("strong",{children:FOUNDING_OFFER.publicLabel}),". The exact Founding price must be shown before checkout and on Stripe."]}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.eligibility}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.plans}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.futurePrices}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.cancellation}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.account})]}),jsxRuntimeExports.jsx("p",{children:"Founding availability can change between the SmartMaths plan page and Stripe Checkout. If the discount is unavailable when checkout is created, Stripe may show the standard price instead. Check the price shown by Stripe before you subscribe; if it is not the price you want, leave checkout and no subscription will be created."}),jsxRuntimeExports.jsx("h3",{children:"Parent referral offer"}),jsxRuntimeExports.jsx("p",{children:"Referral codes are a parent-to-parent offer. The referral is governed by the Terms and plan facts available when the subscription starts. Stripe shows any discount applied at checkout; when a referral is combined with Founding, the referral adjustment may instead appear on the first post-trial invoice."}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.eligibility}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.familyDiscount}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(SMARTMATHS_PLANS.monthly,"standard")}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(SMARTMATHS_PLANS.annual,"standard")}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(SMARTMATHS_PLANS.monthly,"founding")}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(SMARTMATHS_PLANS.annual,"founding")}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.referrerReward}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.stacking}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.cap}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.credit})]}),jsxRuntimeExports.jsx("h3",{children:"Your legal 14-day cancellation period"}),jsxRuntimeExports.jsx("p",{children:"The legal cancellation period starts when the subscription contract is made — not when the first payment is taken — and ends at the end of the 14th calendar day after the contract day, using UK time. The free-trial days are part of that period; they do not restart the legal clock."}),jsxRuntimeExports.jsx("p",{children:"Access to SmartMaths can begin as soon as your account is created, including during the legal cancellation period. If you cancel before the Stripe trial ends, SmartMaths will take no subscription payment. If Stripe takes the first charge earlier on the statutory deadline date, SmartMaths will honour a clear cancellation sent before that legal deadline and refund that charge. Starting to practise does not waive your statutory rights."}),jsxRuntimeExports.jsx("h3",{children:"How to cancel"}),jsxRuntimeExports.jsxs("p",{children:["You can stop the next renewal through Account → Plan & billing → Manage subscription, or email ",jsxRuntimeExports.jsx("a",{href:supportMailto,children:LEGAL_CONTACTS.supportEmail}),". Stopping renewal does not delete the family account, and deleting the account does not by itself cancel Stripe. The separate cancellation page explains refund requests and includes a model notice."]}),jsxRuntimeExports.jsx("h3",{children:"Fair use and account security"}),jsxRuntimeExports.jsx("p",{children:"Keep the parent password private and tell us promptly if the account may have been accessed without permission. Do not try to bypass security, interfere with the service, scrape or republish the question bank, misuse the AI or voice tools, or enter unlawful, offensive or unnecessarily sensitive information."}),jsxRuntimeExports.jsx("h3",{children:"Changes, suspension and ending the service"}),jsxRuntimeExports.jsx("p",{children:"SmartMaths may make reasonable changes to improve learning or accessibility, protect security, meet the law, keep technology working, or reflect a clearly notified price or supplier change. For a material change to a paid service or price, we will give clear advance notice, explain the reason and tell you how to cancel. If a materially adverse change leads you to cancel before it takes effect, SmartMaths will refund the proportion of any advance payment for access you will not use. This is especially relevant to an annual plan."}),jsxRuntimeExports.jsx("p",{children:"We may suspend an account immediately for a serious security or safety reason; otherwise we will give reasonable notice and an opportunity to put a problem right. If SmartMaths ends paid access for a reason that is not your fault, it will refund the appropriate amount for paid time you cannot use."}),jsxRuntimeExports.jsx("h3",{children:"Responsibility when something goes wrong"}),jsxRuntimeExports.jsx("p",{children:"Nothing in these terms excludes responsibility that cannot legally be excluded, including for death or personal injury caused by negligence, fraud, or your statutory consumer rights. SmartMaths is responsible for loss that was a foreseeable result of breaking the contract or failing to use reasonable care and skill. It is not responsible for loss that was not foreseeable. SmartMaths is for personal family use, so it is not responsible for business losses."}),jsxRuntimeExports.jsx("h3",{children:"Questions, complaints and the law"}),jsxRuntimeExports.jsxs("p",{children:["Contact ",jsxRuntimeExports.jsx("a",{href:contactMailto,children:LEGAL_CONTACTS.contactEmail})," for general enquiries,"," ",jsxRuntimeExports.jsx("a",{href:supportMailto,children:LEGAL_CONTACTS.supportEmail})," for service or billing complaints, and ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail})," for data protection complaints. These terms are governed by the law of England and Wales. If you live elsewhere in the UK, you keep any mandatory protections and court rights that apply where you live."]}),jsxRuntimeExports.jsx("p",{children:"SmartMaths will acknowledge a service or billing complaint promptly, investigate it fairly, keep you informed where appropriate and explain the outcome in 
58writing."})]})}function ChildPrivacyNotice(){return jsxRuntimeExports.jsxs("section",{id:"legal-child",className:"sm-legal-doc sm-legal-child-page",children:[jsxRuntimeExports.jsx(DocumentMeta,{version:LEGAL_DOCUMENTS.childPrivacy.version}),jsxRuntimeExports.jsx("h2",{children:"Your privacy at SmartMaths"}),jsxRuntimeExports.jsx("p",{className:"sm-legal-child-lead",children:"This page is for you. It tells you what SmartMaths remembers, why it uses that information and what you can ask us to do."}),jsxRuntimeExports.jsx("h3",{children:"Who is in charge?"}),jsxRuntimeExports.jsxs("p",{children:[jsxRuntimeExports.jsx("strong",{children:LEGAL_OWNER_FACTS.legalIdentity}),", trading as"," ",LEGAL_OWNER_FACTS.tradingName,", is the data controller. That means it is responsible for deciding how SmartMaths uses and protects your information. You can contact SmartMaths yourself at ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail}),", or ask a parent, carer, teacher or another adult you trust to help."]}),jsxRuntimeExports.jsx("h3",{children:"What do we remember, and why?"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:"Your first name or nickname, so the app can say hello."}),jsxRuntimeExports.jsx("li",{children:"Your year group, so the maths starts in a sensible place."}),jsxRuntimeExports.jsx("li",{children:"Your answers, hints, practice time and progress, so the next questions can suit you."})]}),jsxRuntimeExports.jsx("p",{children:"SmartMaths uses this information to run your practice, choose helpful next questions, show progress to your grown-up, help when something goes wrong and keep the service safe. It does not use it to show you adverts."}),jsxRuntimeExports.jsx("h3",{children:"Who can see it, and who helps?"}),jsxRuntimeExports.jsx("p",{children:"Your grown-up can see your profile and progress. A small SmartMaths team can look when it needs to help, fix a problem or keep the app safe. Trusted technology companies help store the records, run the website, read words aloud and sometimes write a hint."}),jsxRuntimeExports.jsx("p",{children:"Some helpers may process information outside the UK, including in the United States. UK data-protection rules still apply. When an extra legal protection is required, SmartMaths must use it before information is sent. You or an adult you trust can ask which country and protection apply."}),jsxRuntimeExports.jsx("p",{children:"When a helper computer writes a hint, we send the maths question, your answer, the right answer and your year group. SmartMaths does not add the name from your profile, but it sends your answer as you typed it, so do not put personal details in an answer. Helper computers can make mistakes, so you can always ask a grown-up if a hint does not look right."}),jsxRuntimeExports.jsx("p",{children:"When SmartMaths reads words aloud, it can send the words on screen, including an answer as you typed it. The sound comes back privately and is kept only for a short time while the page is open; SmartMaths does not save it."}),jsxRuntimeExports.jsx("h3",{children:"Things SmartMaths does not do"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:"No adverts or in-app shopping."}),jsxRuntimeExports.jsx("li",{children:"No public profile and no chat with strangers."}),jsxRuntimeExports.jsx("li",{children:"No selling your information."}),jsxRuntimeExports.jsx("li",{children:"No tricks to make you stay longer or feel bad about stopping."}),jsxRuntimeExports.jsx("li",{children:"No asking for your surname, birthday, address, photo, phone number or email."})]}),jsxRuntimeExports.jsx("h3",{children:"How long do we remember it?"}),jsxRuntimeExports.jsx("p",{children:"We remember your profile and maths progress while your family uses SmartMaths. Stopping practice does not erase it by itself. We remove or make information anonymous when it is no longer needed. If you ask us to delete it, we will explain what we can remove and whether the law means we must keep a small part of the record."}),jsxRuntimeExports.jsx("h3",{children:"Your information, your rights"}),jsxRuntimeExports.jsx("p",{children:"Your privacy rights belong to you. You can ask SmartMaths to show you your information and give you a copy, correct it, delete it when the law allows, limit how it is used, move it to another service where that right applies, or object to some uses. If we ever ask permission for an optional use, that permission can be taken back. We will listen to what you want and consider your age, understanding and best interests when someone helps you use these rights."}),jsxRuntimeExports.jsx("h3",{children:"How to ask a question or complain"}),jsxRuntimeExports.jsxs("p",{children:["You can contact SmartMaths yourself by emailing"," ",jsxRuntimeExports.jsx("a",{href:privacyMailto,children:LEGAL_CONTACTS.privacyEmail}),". You can also ask a parent, carer, teacher or another adult you trust to help. A trusted adult with an account can choose “Privacy or my data” in the signed-in feedback form. SmartMaths will say that it received the request, keep your grown-up updated when needed, and explain what it does."]}),jsxRuntimeExports.jsxs("p",{children:["If you are unhappy with our answer, you or an adult you trust can complain to the Information Commissioner's Office (ICO), an independent organisation that protects information rights. Visit"," ",jsxRuntimeExports.jsx("a",{href:"https://ico.org.uk/make-a-complaint/",target:"_blank",rel:"noreferrer",children:"ico.org.uk/make-a-complaint"})," ","or call 0303 123 1113."]})]})}function PlanFactsNotice(){const n=SMARTMATHS_PLANS.monthly,t=SMARTMATHS_PLANS.annual;return jsxRuntimeExports.jsxs("section",{id:"legal-plans",className:"sm-legal-doc",children:[jsxRuntimeExports.jsx(DocumentMeta,{version:LEGAL_DOCUMENTS.checkoutSummary.version}),jsxRuntimeExports.jsx("h2",{children:"Subscription plan facts"}),jsxRuntimeExports.jsx("p",{children:"This page keeps the price, trial and renewal facts in a form a parent can return to. Stripe must show the exact final total and first charge date again before the subscription button."}),jsxRuntimeExports.jsx("div",{className:"sm-legal-table-wrap",children:jsxRuntimeExports.jsxs("table",{className:"sm-legal-table",children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("th",{scope:"col",children:"Plan"}),jsxRuntimeExports.jsx("th",{scope:"col",children:"Standard price"}),jsxRuntimeExports.jsx("th",{scope:"col",children:"Founding price"})]})}),jsxRuntimeExports.jsx("tbody",{children:[n,t].map(e=>jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("th",{scope:"row",children:e.name}),jsxRuntimeExports.jsxs("td",{children:[formatGbp(e.pricePence)," a ",e.intervalLabel]}),jsxRuntimeExports.jsxs("td",{children:[formatGbp(foundingPricePence(e))," a ",e.intervalLabel]})]},e.key))})]})}),jsxRuntimeExports.jsx("h3",{children:"What every checkout summary must say"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:checkoutSummaryText(n)}),jsxRuntimeExports.jsx("li",{children:checkoutSummaryText(t)}),jsxRuntimeExports.jsx("li",{children:"No charge today; Stripe asks for a payment method."}),jsxRuntimeExports.jsx("li",{children:"Cancel during the trial and n
58o subscription payment is taken."}),jsxRuntimeExports.jsx("li",{children:"After the trial, the selected plan renews automatically at the shown interval until cancelled."})]}),jsxRuntimeExports.jsx("h3",{children:"Founding offer"}),jsxRuntimeExports.jsxs("p",{children:["When Stripe shows the Founding discount, the offer is"," ",jsxRuntimeExports.jsx("strong",{children:FOUNDING_OFFER.publicLabel}),". SmartMaths shows the standard and Founding price together."]}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.eligibility}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.plans}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.futurePrices}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.cancellation}),jsxRuntimeExports.jsx("li",{children:FOUNDING_OFFER.rules.account})]}),jsxRuntimeExports.jsx("p",{children:"Founding availability can change before Stripe creates checkout. If the discount is unavailable, Stripe may show the standard price. Review the price in Stripe and continue only if you want that plan."}),jsxRuntimeExports.jsx("h3",{children:"Referral offer"}),jsxRuntimeExports.jsxs("ul",{children:[jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.eligibility}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(n,"standard")}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(t,"standard")}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(n,"founding")}),jsxRuntimeExports.jsx("li",{children:referralFirstPaymentText(t,"founding")}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.referrerReward}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.stacking}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.cap}),jsxRuntimeExports.jsx("li",{children:REFERRAL_OFFER.rules.credit})]}),jsxRuntimeExports.jsx("p",{children:"Stripe records the promotion reference used for a referral. For a standard plan, Stripe normally shows the referral at checkout. When Founding and referral are combined, Stripe shows the Founding price first; SmartMaths then applies the referral during the trial, and the first post-trial invoice shows the adjusted amount."}),jsxRuntimeExports.jsx("h3",{children:"Cancellation and refunds"}),jsxRuntimeExports.jsx("p",{children:"The statutory cancellation period starts when the subscription contract is made, not at the first payment, and ends at the end of the 14th calendar day after the contract day. Cancel before the Stripe trial ends and no subscription payment is taken. If Stripe takes the first charge earlier on that deadline date, a clear cancellation sent before the legal deadline will be honoured and that charge refunded. After that legal period, cancelling normally stops the next renewal and keeps access open until the paid period ends; there is no separate first-payment change-of-mind refund. Your statutory rights are unaffected. See the Cancel & refunds page for the full instructions and model form."})]})}function CancellationNotice(){return jsxRuntimeExports.jsxs("section",{id:"legal-cancellation",className:"sm-legal-doc",children:[jsxRuntimeExports.jsx(DocumentMeta,{version:LEGAL_DOCUMENTS.terms.version}),jsxRuntimeExports.jsx("h2",{children:"Cancel or ask for a refund"}),jsxRuntimeExports.jsxs("p",{children:["This cancellation information forms part of Terms version"," ",LEGAL_DOCUMENTS.terms.version,"."]}),jsxRuntimeExports.jsx("p",{children:"Cancelling a renewal, using your legal cancellation right and asking for a refund that is due under the law or these Terms are different. Choose the one that matches what you want."}),jsxRuntimeExports.jsxs("div",{className:"sm-cancellation-grid",children:[jsxRuntimeExports.jsxs("article",{className:"sm-cancellation-option",children:[jsxRuntimeExports.jsx("p",{className:"sm-legal-eyebrow",children:"Stop the next renewal"}),jsxRuntimeExports.jsx("h3",{children:"Keep access to the paid end date"}),jsxRuntimeExports.jsx("p",{children:"Sign in, open Account → Plan & billing, and choose Manage subscription. Stripe lets you stop renewal. Your current access normally continues until the date shown in Account or Stripe."})]}),jsxRuntimeExports.jsxs("article",{className:"sm-cancellation-option",children:[jsxRuntimeExports.jsx("p",{className:"sm-legal-eyebrow",children:"Cancel and ask about a refund"}),jsxRuntimeExports.jsx("h3",{children:"Send a clear written request"}),jsxRuntimeExports.jsxs("p",{children:["Use the prepared ",jsxRuntimeExports.jsx("a",{href:refundMailto,children:"email"})," or send any other clear statement to"," ",jsxRuntimeExports.jsx("a",{href:supportMailto,children:LEGAL_CONTACTS.supportEmail}),". Using the account email helps SmartMaths find the subscription, but it is not a condition of cancelling. Do not include a child's name."]})]})]}),jsxRuntimeExports.jsx("h3",{children:"The legal cancellation period"}),jsxRuntimeExports.jsxs("p",{children:["The legal 14-day period starts when Stripe confirms the subscription contract, even if the first ",SMARTMATHS_TRIAL_DAYS," days are free, and ends at the end of the 14th calendar day after the contract day, using UK time. If you cancel before the Stripe trial ends, SmartMaths takes no subscription payment. If Stripe takes the first charge earlier on the statutory deadline date, SmartMaths will honour a clear cancellation sent before the legal deadline and refund that charge. Starting access immediately does not, under these terms, remove that legal r
58ight. It is enough to send your cancellation statement before the deadline ends; SmartMaths does not have to receive or answer it before then."]}),jsxRuntimeExports.jsx("h3",{children:"After the legal cancellation period"}),jsxRuntimeExports.jsxs("p",{children:["Once the legal cancellation period has ended, cancelling normally stops the next renewal and keeps access open until the paid period ends. SmartMaths does not offer a separate change-of-mind refund after the first charge, whether full or part-period. Email"," ",jsxRuntimeExports.jsx("a",{href:supportMailto,children:LEGAL_CONTACTS.supportEmail})," if a charge is duplicated or incorrect, or if you believe the service was not provided properly. Your statutory rights remain unchanged. The Terms also explain the proportionate refund available if a materially adverse change leads you to cancel before it takes effect, or if SmartMaths ends paid access for a reason that is not your fault."]}),jsxRuntimeExports.jsx("h3",{children:"Model cancellation notice"}),jsxRuntimeExports.jsx("p",{children:"You may use this form, but you do not have to. Any clear statement that you are cancelling is enough."}),jsxRuntimeExports.jsxs("div",{className:"sm-cancellation-model",children:[jsxRuntimeExports.jsxs("p",{children:["To: ",LEGAL_OWNER_FACTS.legalIdentity,", ",LEGAL_OWNER_FACTS.publishableAddress,","," ",LEGAL_CONTACTS.supportEmail]}),jsxRuntimeExports.jsx("p",{children:"I give notice that I cancel my contract for the SmartMaths subscription service."}),jsxRuntimeExports.jsx("p",{children:"Subscription ordered on: ______."}),jsxRuntimeExports.jsx("p",{children:"Name of consumer: ______."}),jsxRuntimeExports.jsx("p",{children:"Address of consumer: ______."}),jsxRuntimeExports.jsx("p",{children:"Signature of consumer (only if this form is sent on paper): ______."}),jsxRuntimeExports.jsx("p",{children:"Date: ______."}),jsxRuntimeExports.jsx("p",{children:"Optional account email or Stripe reference, to help find the subscription: ______."})]}),jsxRuntimeExports.jsx("h3",{children:"What happens next"}),jsxRuntimeExports.jsx("p",{children:"SmartMaths should acknowledge the request, confirm whether renewal is stopped, explain any refund and send the outcome in a form you can keep. A refund that is due should be returned without undue delay and no later than 14 days after SmartMaths is told of the cancellation, using the same payment method unless you expressly agree otherwise, and without a fee. Closing a SmartMaths account is a separate step: do not assume it has cancelled the Stripe subscription."}),showDraftScaffolding&&jsxRuntimeExports.jsxs("p",{className:"sm-legal-owner-decision",children:["Release blocker: run one end-to-end Stripe test-mode cancellation and, where a refund is due, refund flow; document who responds through ",LEGAL_CONTACTS.supportEmail,"."]})]})}function LegalContent({view:n="all"}){const t=o=>n==="all"||n===o,s=["privacy","child","terms","plans","cancellation"].filter(t);return jsxRuntimeExports.jsxs("div",{className:"sm-legal",children:[showDraftScaffolding&&jsxRuntimeExports.jsx(DraftNotice,{}),s.map((o,r)=>jsxRuntimeExports.jsxs("div",{children:[r>0&&jsxRuntimeExports.jsx("hr",{className:"sm-legal-divider"}),o==="privacy"&&jsxRuntimeExports.jsx(PrivacyNotice,{}),o==="terms"&&jsxRuntimeExports.jsx(TermsNotice,{}),o==="child"&&jsxRuntimeExports.jsx(ChildPrivacyNotice,{}),o==="plans"&&jsxRuntimeExports.jsx(PlanFactsNotice,{}),o==="cancellation"&&jsxRuntimeExports.jsx(CancellationNotice,{})]},o))]})}function SignUpScreen({onSwitchToLogin:n,onBack:t,onSignedUp:e,initialYearGroup:s,onAccountCreated:o}){const{refresh:r}=useAuth(),[a,i]=reactExports.useState(""),[l,u]=reactExports.useState(""),[d,m]=reactExports.useState(""),[h,f]=reactExports.useState(""),[g,b]=reactExports.useState(()=>s&&YEAR_GROUP_OPTIONS.includes(s)?s:"Year 1"),[x,y]=reactExports.useState(null),[E,k]=reactExports.useState(!1),[T,A]=reactExports.useState(!1),[N,S]=reactExports.useState(null);reactExports.useEffect(()=>{if(!N)return;const L=F=>{F.key==="Escape"&&S(null)};window.addEventListener("keydown",L);const P=document.getElementById(`legal-${N}`);return P&&P.scrollIntoView({block:"start"}),()=>window.removeEventListener("keydown",L)},[N]);const M=async L=>{if(L.preventDefault(),!supabase||!T)return;y(null),k(!0);const P=typeof window<"u"?`${window.location.origin}${window.location.pathname}${window.location.search}`:void 0,{data:F,error:D}=await supabase.auth.signUp({email:a,password:l,options:{emailRedirectTo:P}});if(D){y(friendlyAuthError(D.message)),k(!1);return}if(F.user&&Array.isArray(F.user.identities)&&F.user.identities.length===0){y(DUPLICATE_EMAIL_MESSAGE),k(!1);return}const U=F.user?.id;if(!U){y("Account created — check your email to confirm (including your spam folder), then sign in. If it doesn't arrive, you can resend it from the sign in screen."),k(!1);return}const q=d.trim(),V=h.trim(),{error:O}=await supabase.from("profiles").insert({id:U,parent_first_name:q,first_name:V,year_group_label:g});if(O){y("Your account was created, but setting up your child didn't finish. Please sign in — if your child isn't there, add them in Account, or message us."),k(!1);return}const{error:z}=await supabase.from("children").insert({parent_id:U,first_name:V,year_group_label:g});if(z){y("Your account was created, but adding your child didn't finish. Please sign in and add them in Account, or message us if it keeps happening."),k(!1);return}o?.(),await r(U),F.session&&e(),k(!1)};return jsxRuntimeExports.jsxs("div",{className:`${styles$8.auth} screen screen-enter`,children:[jsxRuntimeExports.jsxs("div",{className:"card auth-card",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact auth-back",type:"button",onClick:t,children:"← Back"}),jsxRuntimeExports.jsxs("div",{className:"sm-logo sm-logo-auth","aria-label":"SmartMaths",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),jsxRuntimeExports.jsx("h1",{children:"Create account"}),jsxRuntimeExports.jsx("p",{className:"auth-note",children:"You're the account holder. You can add more children once you're signed in."}),jsxRuntimeExports.jsxs("form",{className:"auth-form",onSubmit:L=>{M(L)},children:[jsxRuntimeExports.jsxs("label",{children:["Your first name",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"text",autoComplete:"given-name",value:d,onChange:L=>m(L.target.value),required:!0})]}),jsxRuntimeExports.jsxs("label",{children:["Child's first name",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"text",autoComplete:"off",value:h,onChange:L=>
58f(L.target.value),required:!0})]}),jsxRuntimeExports.jsxs("label",{children:["Year group",jsxRuntimeExports.jsx("select",{className:"auth-input",value:g,onChange:L=>b(L.target.value),children:YEAR_GROUP_OPTIONS.map(L=>jsxRuntimeExports.jsx("option",{value:L,children:L},L))})]}),jsxRuntimeExports.jsxs("label",{children:["Email",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"email",autoComplete:"email",value:a,onChange:L=>i(L.target.value),required:!0})]}),jsxRuntimeExports.jsxs("label",{children:["Password",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"password",autoComplete:"new-password",value:l,onChange:L=>u(L.target.value),required:!0,minLength:6})]}),jsxRuntimeExports.jsxs("div",{className:"auth-consent",children:[jsxRuntimeExports.jsx("input",{id:"consent-check",type:"checkbox",checked:T,onChange:L=>A(L.target.checked)}),jsxRuntimeExports.jsx("label",{htmlFor:"consent-check",className:"auth-consent-text",children:"I'm this child's parent or guardian and I'm 18 or over. I agree to the SmartMaths terms and privacy notice, and I consent to my child using SmartMaths."})]}),jsxRuntimeExports.jsxs("p",{className:"auth-consent-help subtle",children:["Please read our"," ",jsxRuntimeExports.jsx("button",{type:"button",className:"btn-link",onClick:()=>S("privacy"),children:"privacy notice"})," ","and"," ",jsxRuntimeExports.jsx("button",{type:"button",className:"btn-link",onClick:()=>S("terms"),children:"Terms"}),". No adverts, ever, and we never sell your child's data — you can withdraw consent and ask to delete your child's data at"," ",jsxRuntimeExports.jsx("a",{href:"mailto:[email protected]",children:"[email protected]"}),"."]}),x&&jsxRuntimeExports.jsx("p",{className:"auth-error",children:x}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"submit",disabled:E||!T,children:E?"Creating account...":"Create account"})]}),jsxRuntimeExports.jsxs("p",{className:"auth-switch",children:["Already have an account?"," ",jsxRuntimeExports.jsx("button",{className:"btn-link",type:"button",onClick:n,children:"Sign in"})]})]}),N&&jsxRuntimeExports.jsx("div",{className:"sm-legal-modal-overlay",role:"dialog","aria-modal":"true","aria-label":"Privacy and terms",onClick:()=>S(null),children:jsxRuntimeExports.jsxs("div",{className:"sm-legal-modal-card",onClick:L=>L.stopPropagation(),children:[jsxRuntimeExports.jsx("div",{className:"sm-legal-modal-head",children:jsxRuntimeExports.jsx("button",{type:"button",className:"sm-legal-close","aria-label":"Close",onClick:()=>S(null),children:"✕"})}),jsxRuntimeExports.jsx("div",{className:"sm-legal-modal-scroll",children:jsxRuntimeExports.jsx(LegalContent,{view:N})})]})})]})}function tokenHashFromLocation(){if(typeof window>"u")return null;const n=/[#&]token_hash=([^&#]+)/.exec(window.location.hash);try{return n?decodeURIComponent(n[1]):null}catch{return null}}const EXPIRED_MESSAGE="This reset link has expired or already been used. Request a new one from the sign in screen.";function ResetPasswordScreen({onDone:n}){const[t,e]=reactExports.useState(""),[s,o]=reactExports.useState(""),[r,a]=reactExports.useState("checking"),[i,l]=reactExports.useState(null),u=reactExports.useRef(tokenHashFromLocation()),d=reactExports.useRef(!1);reactExports.useEffect(()=>{if(!supabase){a("error"),l("Password reset isn't available — Supabase not configured.");return}if(u.current){a("ready");return}a("no_session"),l(EXPIRED_MESSAGE)},[]);const m=async h=>{if(h.preventDefault(),!supabase||r==="saving")return;if(!u.current){a("no_session"),l(EXPIRED_MESSAGE);return}if(l(null),t.length<6){l("Password must be at least 6 characters."),a("error");return}if(t!==s){l("Passwords don't match."),a("error");return}if(a("saving"),!d.current){const{data:g,error:b}=await supabase.auth.verifyOtp({type:"recovery",token_hash:u.current});if(b||!g.session?.user){a("no_session"),l(EXPIRED_MESSAGE);return}d.current=!0}const{error:f}=await supabase.auth.updateUser({password:t});if(f){a("error"),l(f.message);return}a("saved"),l("Password updated. You're signed in.")};return jsxRuntimeExports.jsx("div",{className:`${styles$8.auth} screen screen-enter`,children:jsxRuntimeExports.jsxs("div",{className:"card auth-card",children:[jsxRuntimeExports.jsxs("div",{className:"sm-logo sm-logo-auth","aria-label":"SmartMaths",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),jsxRuntimeExports.jsx("h1",{children:"Set a new password"}),r==="checking"&&jsxRuntimeExports.jsx("p",{className:"auth-note",children:"Checking your reset link…"}),r==="no_session"&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("p",{className:"auth-error",children:i}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",onClick:()=>n(!1),children:"Back to sign in"})]}),r!=="checking"&&r!=="no_session"&&jsxRuntimeExports.jsxs("form",{className:"auth-form",onSubmit:h=>{m(h)},children:[jsxRuntimeExports.jsxs("label",{children:["New password",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"password",autoComplete:"new-password",value:t,onChange:h=>e(h.target.value),minLength:6,required:!0})]}),jsxRuntimeExports.jsxs("label",{children:["Confirm new password",jsxRuntimeExports.jsx("input",{className:"auth-input",type:"password",autoComplete:"new-password",value:s,onChange:h=>o(h.target.value),minLength:6,required:!0})]}),r==="error"&&i&&jsxRuntimeExports.jsx("p",{className:"auth-error",children:i}),r==="saved"&&i&&jsxRuntimeExports.jsx("p",{className:"auth-info",children:i}),r==="saved"?jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",onClick:()=>n(!0),children:"Continue"}):jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"submit",disabled:r==="saving",children:r==="saving"?"Saving…":"Save new password"})]})]})})}function SampleSignUpBar({onSignUp:n,variant:t="sticky",hideWhileVisible:e}){const s=useOnScreen(e);return t==="inline"?jsxRuntimeExports.jsxs("div",{className:"sm-signup-inline",children:[jsxRuntimeExports.jsx("span",{children:"Like what you see?"}),jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-signup-inline-btn",onClick:n,children:["Start your free trial ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]})]}):jsxRuntimeExports.jsxs("div",{className:"sm-signup-bar","data-visible":!s,"aria-hidden":s,children:[jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-btn sm-btn-primary sm-signup-bar-btn",tabIndex:s?-1:0,onClick:n,children:["Start free trial ",jsxRuntimeExports.jsx("span",{className:"arrow","aria-hidden":"true",children:"→"})]}),jsxRuntimeExports.jsx("span",{className:"sm-signup-bar-note",children:"14 days free · cancel anytime"})]})}function useOnScreen(n){const[t,e]=reactExports.useState(!1);return reactExports.useEffect(()=>{const s=n?.current;if(!s||typeof IntersectionObserver>"u")return;const o=new IntersectionObserver(([r])=>e(r.isIntersecting));return o.observe(s),()=>o.disconnect()},[n]),t}const NEUTRAL_POSE={bob:0,tilt:0,sx:1,sy:1,eyeOpenL:1,eyeOpenR:1,eyeHappy:0,gazeX:0,gazeY:0,lashRaise:0,lashTiltL:0,lashTiltR:0,mouthW:52,mouthCurve:.6,mouthOpen:0,mouthShift:0,blush:.7,tuft:0},POSE_KEYS=Object.keys(NEUTRAL_POSE),EXPRESSION_POSES={neutral:{},listen:{tilt:-4,lashRaise:.25,mouthW:48,mouthCurve:.5}
58,thinking:{tilt:5,gazeX:4.5,gazeY:-5,lashTiltL:10,lashTiltR:-6,lashRaise:.15,mouthW:22,mouthCurve:.05,mouthShift:9,blush:.5},idea:{eyeOpenL:1.2,eyeOpenR:1.2,lashRaise:.9,mouthW:44,mouthCurve:.85,mouthOpen:.25,blush:.9},encourage:{tilt:7,eyeOpenL:.82,eyeOpenR:.82,lashTiltL:7,lashTiltR:7,lashRaise:.2,mouthW:46,mouthCurve:.55,blush:1},cheer:{eyeHappy:1,mouthW:60,mouthCurve:1,mouthOpen:.4,blush:1,sy:1.02},wow:{eyeOpenL:1.3,eyeOpenR:1.3,lashRaise:1,mouthW:30,mouthCurve:.25,mouthOpen:.9,blush:1,gazeY:-1},greet:{eyeHappy:1,mouthW:56,mouthCurve:.95,mouthOpen:.15,blush:1},sleepy:{tilt:9,eyeOpenL:.3,eyeOpenR:.3,gazeY:4,lashRaise:-.4,mouthW:26,mouthCurve:.35,blush:.5}},EXPRESSION_ENTRY={cheer:{gesture:"bounce",fx:"sparkles"},wow:{gesture:"hop",fx:"sparkles"},idea:{gesture:"hop",fx:"idea"},greet:{gesture:"greet"},encourage:{gesture:"nod"},listen:{gesture:"glance"}},FX_DURATION_MS={sparkles:1e3,idea:1200},WIDE={eyeOpenL:1.28,eyeOpenR:1.28},GESTURES={bounce:{durationMs:1100,frames:[[0,{sy:.88,sx:1.1}],[.12,{bob:-16,sy:1.12,sx:.92}],[.4,{bob:0,sy:.94,sx:1.06}],[.55,{bob:-9,sy:1.06,sx:.96}],[.8,{}]]},wiggle:{durationMs:1400,frames:[[0,{tilt:-10}],[.22,{tilt:10}],[.44,{tilt:-7}],[.66,{tilt:5}],[.85,{}]]},nod:{durationMs:900,frames:[[0,{bob:5,sy:.97}],[.3,{bob:-3}],[.55,{bob:4,sy:.98}],[.8,{}]]},hop:{durationMs:700,frames:[[0,{sy:.92,sx:1.06}],[.15,{bob:-11,sy:1.07,sx:.96}],[.5,{}]]},shake:{durationMs:650,frames:[[0,{tilt:-7,...WIDE}],[.25,{tilt:7,...WIDE}],[.5,{tilt:-4}],[.75,{tilt:2}],[.9,{}]]},wink:{durationMs:750,frames:[[0,{eyeOpenR:0,mouthShift:-4,mouthCurve:.95,tilt:-3}],[.7,{}]]},glance:{durationMs:1300,frames:[[0,{gazeX:-6,gazeY:2,tilt:-3}],[.45,{gazeX:-6,gazeY:2,bob:3}],[.7,{gazeX:-6,gazeY:2}],[.9,{}]]},wow:{durationMs:1300,fx:"sparkles",frames:[[0,{...WIDE,eyeHappy:0,lashRaise:1,mouthW:30,mouthCurve:.25,mouthOpen:.9,bob:-4}],[.55,{}]]},greet:{durationMs:1500,frames:[[0,{tilt:-10,eyeHappy:1,mouthCurve:.95,mouthW:56}],[.22,{tilt:10,eyeHappy:1,mouthCurve:.95,mouthW:56}],[.44,{tilt:-7,eyeHappy:1,mouthCurve:.95,mouthW:56}],[.66,{tilt:5,eyeHappy:1,mouthCurve:.95,mouthW:56}],[.85,{}]]},smile:{durationMs:1e3,frames:[[0,{mouthCurve:.9,mouthW:57,blush:1}],[.6,{}]]}},spring=(n,t)=>({kind:"spring",stiffness:n,damping:t}),ease=n=>({kind:"ease",tau:n}),CHANNEL_DYNAMICS={bob:spring(260,16),tilt:spring(170,15),sx:spring(300,16),sy:spring(300,16),eyeOpenL:ease(.045),eyeOpenR:ease(.045),eyeHappy:ease(.1),gazeX:ease(.11),gazeY:ease(.11),lashRaise:ease(.12),lashTiltL:ease(.14),lashTiltR:ease(.14),mouthW:ease(.12),mouthCurve:ease(.14),mouthOpen:ease(.045),mouthShift:ease(.14),blush:ease(.5),tuft:spring(200,8)},REDUCED_MOTION_DYNAMICS=ease(.16);function composePose(...n){const t={...NEUTRAL_POSE};for(const e of n)for(const s of POSE_KEYS){const o=e[s];o!==void 0&&(t[s]=o)}return t}function gestureFrameAt(n,t){let e={};for(const[s,o]of n.frames)if(t>=s)e=o;else break;return e}class BlinkScheduler{constructor(t){this.rnd=t}nextAt=-1;closedUntil=0;lastWasDouble=!1;update(t,e){return this.nextAt<0&&(this.nextAt=t+900+this.rnd()*2500),t<this.closedUntil?!0:t<this.nextAt?!1:(this.closedUntil=t+110,!this.lastWasDouble&&this.rnd()<.12?(this.lastWasDouble=!0,this.nextAt=t+280):(this.lastWasDouble=!1,this.nextAt=t+(e?3500+this.rnd()*4500:2500+this.rnd()*4e3)),!0)}}class GazeWander{constructor(t){this.rnd=t}nextAt=-1;target={x:0,y:0};update(t,e,s){if(t>=this.nextAt){const o=this.rnd(),r=a=>(this.rnd()-.5)*2*a;s?this.target=e&&o>.8?{x:e.x*4+r(.8),y:e.y*4+r(.8)}:{x:r(.9),y:r(.7)}:e&&o<.5?this.target={x:e.x*5.5+r(1.2),y:e.y*5.5+r(1.2)}:o<.82?this.target={x:r(1),y:r(.8)}:this.target={x:r(4),y:r(3)},this.nextAt=t+1200+this.rnd()*2600}return this.target}}class SpeechCadence{constructor(t){this.rnd=t}phaseUntil=-1;open=0;untilPause=0;startedAt=-1;reset(){this.phaseUntil=-1,this.open=0,this.untilPause=0,this.startedAt=-1}update(t,e){this.startedAt<0&&(this.startedAt=t);const s=t-this.startedAt;if(e!==null){const o=clamp01((e-.05)*1.7);return{open:o*.62,widen:o*6,sinceStart:s}}return t>=this.phaseUntil&&(this.open>0?(this.open=0,this.untilPause<=0?(this.untilPause=4+Math.floor(this.rnd()*5),this.phaseUntil=t+160+this.rnd()*240):(this.untilPause-=1,this.phaseUntil=t+45+this.rnd()*80)):(this.open=(.35+this.rnd()*.65)*.62,this.phaseUntil=t+70+this.rnd()*90)),{open:this.open,widen:this.open*9,sinceStart:s}}}class IdleLife{constructor(t){this.rnd=t}nextAt=-1;nextDriftAt=-1;drift=0;headDrift(t){return t>=this.nextDriftAt&&(this.drift=(this.rnd()-.5)*5,this.nextDriftAt=t+4e3+this.rnd()*4e3),this.drift}nextBusiness(t,e){return this.nextAt<0&&(this.nextAt=t+5e3+this.rnd()*6e3),t<this.nextAt?null:(this.nextAt=t+7e3+this.rnd()*7e3,e&&this.rnd()<.4?"glance":"smile")}}function clamp01(n){return n<0?0:n>1?1:n}
58const HEAD_KEYS=["bob","tilt","sx","sy"],scale=(n,t)=>({x:n.x*t,y:n.y*t}),BREATH_PERIOD_MS=3800;class MiaAnimator{rnd;reduced;autoSleepMs;cur={...NEUTRAL_POSE};vel=Object.fromEntries(POSE_KEYS.map(t=>[t,0]));expression="neutral";speaking=!1;amplitude=null;lookAt=null;gazeLock=null;gesture=null;pendingFx=[];lastNow=0;firstNow=-1;lastActivityAt=0;dozing=!1;blink;gaze;speech;idle;constructor(t={}){this.rnd=t.random??Math.random,this.reduced=t.reducedMotion??!1,this.autoSleepMs=t.autoSleepMs===void 0?6e4:t.autoSleepMs,this.blink=new BlinkScheduler(this.rnd),this.gaze=new GazeWander(this.rnd),this.speech=new SpeechCadence(this.rnd),this.idle=new IdleLife(this.rnd)}getExpression(){return this.expression}isDozing(){return this.dozing}isReducedMotion(){return this.reduced}setReducedMotion(t){if(t!==this.reduced&&(this.reduced=t,!!t)){for(const e of HEAD_KEYS)this.cur[e]=NEUTRAL_POSE[e],this.vel[e]=0;this.cur.tuft=0,this.vel.tuft=0}}setAutoSleepMs(t){this.autoSleepMs=t,this.lastActivityAt=this.lastNow}setExpression(t){if(this.wake(),t===this.expression)return;this.expression=t;const e=EXPRESSION_ENTRY[t];e?.gesture&&this.playGesture(e.gesture),e?.fx&&this.pushFx(e.fx)}setSpeaking(t){t&&this.wake(),t!==this.speaking&&(this.speaking=t,this.speech.reset())}setAmplitude(t){this.amplitude=t}setLookAt(t){this.lookAt=t}setGazeLock(t){this.gazeLock=t}playGesture(t){const e=GESTURES[t];this.gesture={spec:e,startedAt:this.firstNow<0?-1:this.lastNow},e.fx&&this.pushFx(e.fx)}notifyActivity(){this.wake()}tick(t){this.firstNow<0&&(this.firstNow=t,this.lastNow=t,this.lastActivityAt=t);const e=Math.min(.05,Math.max(0,(t-this.lastNow)/1e3));this.lastNow=t;const s=this.expression==="neutral"||this.expression==="listen";this.autoSleepMs!==null&&!this.dozing&&!this.speaking&&s&&t-this.lastActivityAt>this.autoSleepMs&&(this.dozing=!0);const o=this.dozing?"sleepy":this.expression,r=EXPRESSION_POSES[o],a=composePose(r);if(this.reduced)a.bob=0,a.tilt=0,a.sx=1,a.sy=1;else{const l=2*Math.PI*(t-this.firstNow)/(this.dozing?BREATH_PERIOD_MS*1.3:BREATH_PERIOD_MS),u=this.dozing?2:1;a.bob+=1.2*u*Math.sin(l),a.sy+=.012*u*Math.sin(l),a.tilt+=this.idle.headDrift(t)}if(r.gazeX===void 0&&r.gazeY===void 0){const l=this.gazeLock??scale(this.gaze.update(t,this.lookAt,this.speaking),1);a.gazeX=this.gazeLock?l.x*6:l.x,a.gazeY=this.gazeLock?l.y*6:l.y}if(this.speaking){const l=this.speech.update(t,this.amplitude?this.amplitude():null);a.mouthOpen=Math.max(a.mouthOpen*.3,l.open),a.mouthW+=l.widen,a.mouthCurve*=.85,this.reduced||(a.bob-=1.6*l.open),l.sinceStart<350&&(a.lashRaise+=.15)}else if(s&&!this.dozing&&!this.gesture){const l=this.idle.nextBusiness(t,this.lookAt!==null);l&&this.playGesture(l)}if(this.blink.update(t,this.dozing)&&a.eyeHappy<.5&&(a.eyeOpenL=0,a.eyeOpenR=0),this.gesture){this.gesture.startedAt<0&&(this.gesture.startedAt=t);const l=(t-this.gesture.startedAt)/this.gesture.spec.durationMs;if(l>=1)this.gesture=null;else{const u=gestureFrameAt(this.gesture.spec,l);for(const d of POSE_KEYS){const m=u[d];m!==void 0&&(this.reduced&&HEAD_KEYS.includes(d)||(a[d]=m))}}}a.tuft=Math.max(-25,Math.min(25,-this.vel.tilt*.06-this.vel.bob*.5)),this.integrate(a,e);const i=this.pendingFx;return this.pendingFx=[],{pose:{...this.cur},expression:o,fx:i,speaking:this.speaking}}integrate(t,e){if(e!==0)for(const s of POSE_KEYS){const o=this.reduced&&s!=="tuft"?REDUCED_MOTION_DYNAMICS:CHANNEL_DYNAMICS[s],r=this.cur[s],a=t[s];if(o.kind==="spring"){const i=o.stiffness*(a-r)-o.damping*this.vel[s];this.vel[s]+=i*e,this.cur[s]=r+this.vel[s]*e}else{const i=(s==="eyeOpenL"||s==="eyeOpenR")&&a<r?.028:o.tau,l=r+(a-r)*(1-Math.exp(-e/i));this.vel[s]=(l-r)/e,this.cur[s]=l}}}wake(){this.lastActivityAt=this.lastNow,this.dozing&&(this.dozing=!1,this.playGesture("shake"))}pushFx(t){this.pendingFx.push({name:t,durationMs:FX_DURATION_MS[t]})}}const MIA_INK="#2E2A4D",MIA_FACE="#9B82FA",HONEY="#F4C542",TONGUE="#F49AB0",f1=n=>(Math.round(n*10)/10).toString(),f2=n=>(Math.round(n*100)/100).toString(),f3=n=>(Math.round(n*1e3)/1e3).toString();function mouthGeometry(n,t,e){const s=116-6*t,o=s+44*t,r=o+e*(30+8*t);return{cornerY:s,upperCtrlY:o,lowerCtrlY:r,lowerMidY:.5*s+.5*r}}function mouthPath(n,t,e){const s=mouthGeometry(n,t,e),o=f1(100-n/2),r=f1(100+n/2);return`M ${o} ${f1(s.cornerY)} Q 100 ${f1(s.upperCtrlY)} ${r} ${f1(s.cornerY)} Q 100 ${f1(s.lowerCtrlY)} ${o} ${f1(s.cornerY)} Z`}function computeMiaAttrs(n){const t=Math.max(.05,Math.min(1.35,n.eyeOpenL)),e=Math.max(.05,Math.min(1.35,n.eyeOpenR)),s=clamp01(n.eyeHappy),o=clamp01(n.mouthOpen),r=mouthGeometry(n.mouthW,n.mouthCurve,o),a=f1(-6*n.lashRaise),i=u
58=>({transform:`scale(${f3(1+(u-1)*.35)} ${f3(u)})`,opacity:f2(1-s)}),l=u=>({opacity:f2(clamp01((u-.5)*2)*(1-s))});return{head:{transform:`translate(0 ${f1(n.bob)}) translate(100 100) rotate(${f1(n.tilt)}) scale(${f3(n.sx)} ${f3(n.sy)}) translate(-100 -100)`},tuft:{transform:`rotate(${f1(n.tuft)} 100 12)`},eyeL:{transform:`translate(${f1(70+n.gazeX)} ${f1(78+n.gazeY)})`},eyeR:{transform:`translate(${f1(130+n.gazeX)} ${f1(78+n.gazeY)})`},pupilL:i(t),pupilR:i(e),arcL:{opacity:f2(s)},arcR:{opacity:f2(s)},shineL:l(t),shineR:l(e),lashL:{transform:`translate(0 ${a}) rotate(${f1(-n.lashTiltL)} 70 78)`},lashR:{transform:`translate(0 ${a}) rotate(${f1(n.lashTiltR)} 130 78)`},mouth:{d:mouthPath(n.mouthW,n.mouthCurve,o),transform:`translate(${f1(n.mouthShift)} 0)`},tongue:{transform:`translate(${f1(n.mouthShift)} 0)`,cy:f1(r.lowerMidY-3*o),rx:f1(n.mouthW*.3),ry:f1(4+9*o),opacity:f2(clamp01((o-.2)*2.5))},blushL:{opacity:f2(clamp01(n.blush))},blushR:{opacity:f2(clamp01(n.blush))}}}function serialize(n){return Object.entries(n).map(([t,e])=>`${t}="${e}"`).join(" ")}const STAR="M0 -9 L2.4 -2.4 L9 0 L2.4 2.4 L0 9 L-2.4 2.4 L-9 0 L-2.4 -2.4 Z",SPARKLE_SPOTS=[[16,34,HONEY],[184,30,MIA_FACE],[2,118,HONEY],[198,124,HONEY],[100,-8,MIA_FACE]],LASHES_L=["M56 68 L50 60","M65 64 L62 55","M75 64 L74 54"],LASHES_R=["M125 64 L126 54","M135 64 L138 55","M144 68 L150 60"];function buildMiaSvgMarkup(n,t=NEUTRAL_POSE){const e=computeMiaAttrs(t),s=n.idPrefix,o=(h,f,g,b="")=>`<${h} data-mia="${f}" ${serialize({...g,...e[f]??{}})}>${b}</${h}>`,r=h=>o("g",`eye${h}`,{},o("ellipse",`pupil${h}`,{rx:"9",ry:"13",fill:MIA_INK})+o("path",`arc${h}`,{d:"M -14 2 A 15 15 0 0 1 14 2",stroke:MIA_INK,"stroke-width":"8","stroke-linecap":"round",fill:"none"})+o("circle",`shine${h}`,{cx:"3",cy:"-6",r:"3.5",fill:"#fff"})),a=(h,f)=>o("g",`lash${h}`,{stroke:MIA_INK,"stroke-width":"4","stroke-linecap":"round",fill:"none"},f.map(g=>`<path d="${g}"/>`).join("")),i=SPARKLE_SPOTS.map(([h,f,g])=>`<g transform="translate(${h} ${f}) scale(1.6)"><path class="sm-mia-star" d="${STAR}" fill="${g}" stroke="${g===HONEY?"#E8A23D":"#5B3FD4"}" stroke-width="0.8"/></g>`).join(""),l=[[160,44,3],[172,30,4],[186,14,5]].map(([h,f,g])=>`<circle cx="${h}" cy="${f}" r="${g}" fill="${MIA_FACE}" stroke="#fff" stroke-width="2"/>`).join(""),u=[[152,38,14],[166,22,18],[182,6,22]].map(([h,f,g])=>`<text x="${h}" y="${f}" font-size="${g}" font-weight="900" font-family="Nunito, system-ui, sans-serif" fill="${MIA_INK}">z</text>`).join(""),d=`<g class="sm-mia-idea"><g transform="translate(172 24) scale(1.5)"><path class="sm-mia-idea-star" d="${STAR}" fill="${HONEY}" stroke="#E8A23D" stroke-width="1"/></g><g class="sm-mia-idea-rays" stroke="${HONEY}" stroke-width="3" stroke-linecap="round"><path d="M150 20 L142 18"/><path d="M158 6 L152 0"/><path d="M186 4 L190 -3"/></g></g>`,m=n.tuft?o("g","tuft",{},`<path d="M96 10 C 90 0 100 -12 112 -4 C 118 0 112 9 105 8" fill="${MIA_FACE}" stroke="#8A6FF5" stroke-width="2" stroke-linejoin="round"/>`):"";return`<svg viewBox="0 0 200 200" width="100%" height="100%" class="sm-mia-svg ${n.className??""}" focusable="false" aria-hidden="true"><defs><radialGradient id="${s}-blush"><stop offset="0%" stop-color="rgba(255,170,195,.9)"/><stop offset="60%" stop-color="rgba(255,170,195,.55)"/><stop offset="100%" stop-color="rgba(255,170,195,0)"/></radialGradient><clipPath id="${s}-mouthclip"><use href="#${s}-mouth"/></clipPath></defs><g class="sm-mia-fx sm-mia-fx-think" data-mia="fx-think">${l}</g><g class="sm-mia-fx sm-mia-fx-zzz" data-mia="fx-zzz">${u}</g>`+o("g","head",{},`<circle cx="100" cy="100" r="95" fill="${MIA_FACE}"/>`+m+o("circle","blushL",{cx:"44",cy:"106",r:"14",fill:`url(#${s}-blush)`})+o("circle","blushR",{cx:"156",cy:"106",r:"14",fill:`url(#${s}-blush)`})+r("L")+r("R")+a("L",LASHES_L)+a("R",LASHES_R)+o("path","mouth",{id:`${s}-mouth`,fill:MIA_INK,stroke:MIA_INK,"stroke-width":"8","stroke-linejoin":"round","stroke-linecap":"round"})+o("ellipse","tongue",{cx:"100",fill:TONGUE,"clip-path":`url(#${s}-mouthclip)`}))+`<g class="sm-mia-fx sm-mia-fx-sparkles-g" data-mia="fx-sparkles">${i}</g><g class="sm-mia-fx sm-mia-fx-idea-g" data-mia="fx-idea">${d}</g></svg>`}function bindMiaSvg(n,t={set:(e,s)=>setTimeout(e,s),clear:e=>clearTimeout(e)}){const e=new Map;for(const i of n.querySelectorAll("[data-mia]")){const l=i.getAttribute("data-mia");l&&e.set(l,i)}const s={};let o="",r=!1;const a=new Map;return{apply(i){const l=computeMiaAttrs(i.pose);for(const[d,m]of Object.entries(l)){const h=e.get(d);if(!h)continue;const f=s[d]??(s[d]={});for(const[g,b]of Object.entries(m))f[g]!==b&&(f[g]=b,h.setAttribute(g,b))}const u=`sm-mia-x-${i.expression}`;u!==o&&(o&&n.classList.remove(o),n.classList.add(u),o=u),i.speaking!==r&&(r=i.speaking,r?n.classList.add("sm
58-mia-talking"):n.classList.remove("sm-mia-talking"));for(const{name:d,durationMs:m}of i.fx){const h=`sm-mia-fx-${d}`,f=a.get(d);f!==void 0&&(t.clear(f),n.classList.remove(h),n.getBoundingClientRect?.()),n.classList.add(h),a.set(d,t.set(()=>{n.classList.remove(h),a.delete(d)},m))}},destroy(){for(const[i,l]of a)t.clear(l),n.classList.remove(`sm-mia-fx-${i}`);a.clear(),o&&n.classList.remove(o),r&&n.classList.remove("sm-mia-talking"),o="",r=!1}}}function expressionForMood(n){switch(n){case"happy":return"cheer";case"encouraging":return"encourage";case"thinking":return"thinking";case"hint":return"idea";default:return"neutral"}}function gestureForTutorEvent(n){return n==="session_start"?"greet":n==="question_saved"?"nod":null}const subscribers=new Set;let rafHandle=0,hiddenListenerAttached=!1;function loop(n){if(rafHandle=0,!(typeof document<"u"&&document.hidden)){for(const t of Array.from(subscribers))try{t.active()&&t.frame(n)}catch(e){subscribers.delete(t),typeof console<"u"&&console.warn("[mia] animation stopped for one face after an error",e)}subscribers.size>0&&(rafHandle=requestAnimationFrame(loop))}}function ensureRunning(){rafHandle!==0||subscribers.size===0||typeof requestAnimationFrame>"u"||(rafHandle=requestAnimationFrame(loop),!hiddenListenerAttached&&typeof document<"u"&&(hiddenListenerAttached=!0,document.addEventListener("visibilitychange",()=>{document.hidden||ensureRunning()})))}function subscribeTicker(n){return subscribers.add(n),ensureRunning(),()=>{subscribers.delete(n),subscribers.size===0&&rafHandle!==0&&(cancelAnimationFrame(rafHandle),rafHandle=0)}}const CUE_MAX_AGE_MS=2500,REDUCED_MOTION_QUERY="(prefers-reduced-motion: reduce)";function prefersReducedMotion$2(){return typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia(REDUCED_MOTION_QUERY).matches}function useReducedMotionPreference(n){const[t,e]=reactExports.useState(prefersReducedMotion$2);return reactExports.useEffect(()=>{if(!n||typeof window>"u"||typeof window.matchMedia!="function")return;const s=window.matchMedia(REDUCED_MOTION_QUERY),o=r=>e(r.matches);return e(s.matches),s.addEventListener("change",o),()=>s.removeEventListener("change",o)},[n]),t}function MiaAnimated({size:n=80,mood:t="idle",expression:e,speaking:s=!1,cue:o=null,lookAt:r=null,variant:a,autoSleepMs:i=6e4,amplitude:l=null,gazeLock:u=null,reducedMotion:d,className:m=""}){const h=reactExports.useRef(null),f=reactExports.useRef(null),g=`mia-${reactExports.useId().replace(/[^a-zA-Z0-9_-]/g,"")}`,b=!!a?.tuft,x=reactExports.useMemo(()=>buildMiaSvgMarkup({idPrefix:g,tuft:b}),[g,b]),y=reactExports.useMemo(()=>({__html:x}),[x]),E=e??expressionForMood(t),k=useReducedMotionPreference(d===void 0),T=d??k;return reactExports.useEffect(()=>{const A=h.current,N=A?.querySelector("svg");if(!A||!N)return;const S=new MiaAnimator({reducedMotion:prefersReducedMotion$2(),autoSleepMs:6e4});f.current=S;const M=bindMiaSvg(N);let L=!0;const P=typeof IntersectionObserver<"u"?new IntersectionObserver(U=>{L=U.some(q=>q.isIntersecting)}):null;P?.observe(A);const F=subscribeTicker({active:()=>L,frame:U=>M.apply(S.tick(U))}),D=()=>S.notifyActivity();return window.addEventListener("pointerdown",D,{passive:!0}),window.addEventListener("keydown",D,{passive:!0}),()=>{F(),P?.disconnect(),M.destroy(),window.removeEventListener("pointerdown",D),window.removeEventListener("keydown",D),f.current=null}},[x]),reactExports.useEffect(()=>{f.current?.setReducedMotion(T)},[T,x]),reactExports.useEffect(()=>{f.current?.setAutoSleepMs(i)},[i,x]),reactExports.useEffect(()=>{f.current?.setExpression(E)},[E,x]),reactExports.useEffect(()=>{f.current?.setSpeaking(s)},[s,x]),reactExports.useEffect(()=>{f.current?.setAmplitude(l)},[l,x]),reactExports.useEffect(()=>{f.current?.setLookAt(r)},[r?.x,r?.y,x]),reactExports.useEffect(()=>{f.current?.setGazeLock(u)},[u?.x,u?.y,x]),reactExports.useEffect(()=>{o&&Date.now()-o.at<CUE_MAX_AGE_MS&&f.current?.playGesture(o.gesture)},[o?.at,o?.gesture,x]),jsxRuntimeExports.jsx("span",{ref:h,className:`sm-mia sm-mia-anim sm-mia-${t}${s?" sm-mia-speaking":""}${d?" sm-mia-reduced":""}${m?` ${m}`:""}`,style:{width:n,height:n},"aria-hidden":"true",dangerouslySetInnerHTML:y})}const FACE_BY_MOOD={happy:"cheer",encouraging:"think",thinking:"think",hint:"happy",idle:"happy"},INK$3="#2E2A4D",FACE="#9B82FA";function Mia({animated:n,cue:t,lookAt:e,variant:s,gazeLock:o,reducedMotion:r,autoSleepMs:a,...i}){return n??isAnimatedMiaEnabled()?jsxRuntimeExports.jsx(MiaAnimated,{...i,cue:t,lookAt:e,variant:s,gazeLock:o,reducedMotion:r,autoSleepMs:a}):jsxRuntimeExports.jsx(MiaStatic,{...i})}function MiaStatic({size:n=80,mood:t="idle",speaking:e=!1}){const s=FACE_BY_MOOD[t]??"happy",o=`mia-blush-${reactExports.useId().replace(/[^a-zA-Z0-9_-]/g,"")}`;return jsxRuntimeExports.jsx("span",{className:`sm-mia sm-mia-${t}${e?" sm-mia-speaking":""}`,style:{width:n,height:n},"aria-hidden":"true",children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 200 200",width:n,height:n,
58children:[jsxRuntimeExports.jsx("defs",{children:jsxRuntimeExports.jsxs("radialGradient",{id:o,children:[jsxRuntimeExports.jsx("stop",{offset:"0%",stopColor:"rgba(255,170,195,.9)"}),jsxRuntimeExports.jsx("stop",{offset:"60%",stopColor:"rgba(255,170,195,.55)"}),jsxRuntimeExports.jsx("stop",{offset:"100%",stopColor:"rgba(255,170,195,0)"})]})}),jsxRuntimeExports.jsx("circle",{cx:"100",cy:"100",r:"95",fill:FACE}),s==="cheer"?jsxRuntimeExports.jsxs("g",{stroke:INK$3,strokeWidth:"8",strokeLinecap:"round",fill:"none",children:[jsxRuntimeExports.jsx("path",{d:"M56 80 A 15 15 0 0 1 84 80"}),jsxRuntimeExports.jsx("path",{d:"M116 80 A 15 15 0 0 1 144 80"})]}):jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("ellipse",{cx:"70",cy:"78",rx:"9",ry:"13",fill:INK$3}),jsxRuntimeExports.jsx("ellipse",{cx:"130",cy:"78",rx:"9",ry:"13",fill:INK$3}),jsxRuntimeExports.jsx("circle",{cx:"73",cy:"72",r:"3.5",fill:"#fff"}),jsxRuntimeExports.jsx("circle",{cx:"133",cy:"72",r:"3.5",fill:"#fff"})]}),jsxRuntimeExports.jsxs("g",{stroke:INK$3,strokeWidth:"4",strokeLinecap:"round",children:[jsxRuntimeExports.jsx("path",{d:"M56 68 L50 60"}),jsxRuntimeExports.jsx("path",{d:"M65 64 L62 55"}),jsxRuntimeExports.jsx("path",{d:"M75 64 L74 54"}),jsxRuntimeExports.jsx("path",{d:"M125 64 L126 54"}),jsxRuntimeExports.jsx("path",{d:"M135 64 L138 55"}),jsxRuntimeExports.jsx("path",{d:"M144 68 L150 60"})]}),jsxRuntimeExports.jsx("circle",{cx:"44",cy:"106",r:"14",fill:`url(#${o})`}),jsxRuntimeExports.jsx("circle",{cx:"156",cy:"106",r:"14",fill:`url(#${o})`}),s==="cheer"?jsxRuntimeExports.jsx("path",{d:"M74 116 A 32 32 0 0 0 126 116",stroke:INK$3,strokeWidth:"9",strokeLinecap:"round",fill:"none"}):s==="think"?jsxRuntimeExports.jsx("path",{d:"M82 116 L118 116",stroke:INK$3,strokeWidth:"9",strokeLinecap:"round",fill:"none"}):jsxRuntimeExports.jsx("path",{d:"M74 118 A 32 32 0 0 0 126 118",stroke:INK$3,strokeWidth:"9",strokeLinecap:"round",fill:"none"})]})})}function bubbleTone(n){return n==="happy"?"happy":n==="encouraging"?"gentle":n==="hint"?"hint":"neutral"}function TutorAvatar({message:n,mood:t,tutorName:e="Mia",size:s=64,isSpeaking:o=!1,cue:r=null,lookAt:a=null}){if(!n)return null;const i=bubbleTone(t);return jsxRuntimeExports.jsxs("div",{className:"sm-tutor-block",children:[jsxRuntimeExports.jsxs("div",{className:"sm-tutor-head",children:[jsxRuntimeExports.jsx(Mia,{size:s,mood:t,speaking:o,cue:r,lookAt:a}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("b",{children:e}),jsxRuntimeExports.jsx("span",{children:"Your tutor"})]})]}),jsxRuntimeExports.jsx("div",{className:`sm-tutor-msg tone-${i}`,role:"status","aria-live":"polite","aria-atomic":"true","aria-label":`${e}: ${t==="thinking"?"thinking":n}`,children:t==="thinking"?jsxRuntimeExports.jsxs("span",{className:"sm-tutor-thinking","aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{}),jsxRuntimeExports.jsx("span",{}),jsxRuntimeExports.jsx("span",{})]}):n})]})}function ClueIcon({className:n="sm-help-ico"}){return jsxRuntimeExports.jsxs("svg",{className:n,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[jsxRuntimeExports.jsx("path",{d:"M9.5 18h5"}),jsxRuntimeExports.jsx("path",{d:"M10.5 21h3"}),jsxRuntimeExports.jsx("path",{d:"M12 3a6 6 0 0 0-3.4 10.9c.9.7 1.4 1.3 1.4 2.1h4c0-.8.5-1.4 1.4-2.1A6 6 0 0 0 12 3z"})]})}function SpeakerIcon$1({className:n="sm-help-ico"}){return jsxRuntimeExports.jsxs("svg",{className:n,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[jsxRuntimeExports.jsx("path",{d:"M4 9.5v5h3.5L12 18.5v-13L7.5 9.5H4z",className:"sm-help-ico-fill"}),jsxRuntimeExports.jsx("path",{d:"M15.5 9a4 4 0 0 1 0 6"}),jsxRuntimeExports.jsx("path",{d:"M18 6.5a7.5 7.5 0 0 1 0 11"})]})}function PlayIcon({className:n="sm-help-ico"}){return jsxRuntimeExports.jsxs("svg",{className:n,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[jsxRuntimeExports.jsx("circle",{cx:"12",cy:"12",r:"9.5"}),jsxRuntimeExports.jsx("path",{d:"M10 8.3v7.4l6-3.7z",className:"sm-help-ico-fill"})]})}function ComeBackIcon({className:n="sm-help-ico"}){return jsxRuntimeExports.jsxs("svg",{className:n,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[jsxRuntimeExports.jsx("path",{d:"M5 12a7 7 0 1 0 2.1-5"}),jsxRuntimeExports.jsx("path",{d:"M4 4v4.5h4.5"})]})}function MiaHelpPromise(){return jsxRuntimeExports.jsxs("div",{className:"sm-help-promise",children:[jsxRuntimeExports.jsxs("ul",{className:"sm-help-promise-list",children:[jsxRuntimeExports.jsxs("li",{className:"sm-help-promise-clue",children:[jsxRuntimeExports.jsx(ClueIcon,{})," Stuck? I give a clue, not the answer"]}),jsxRuntimeExports.jsxs("li",{className:"sm-help-promise-read",children:[jsxRuntimeExports.jsx(SpeakerIcon$1,{})," I can read each question out loud"]}),jsxRuntimeExports.jsxs("li",{className:"sm-help-promise-watch",children:[jsxRuntimeExports.jsx(PlayIcon,{})," Missed one? I explain it step by step"]})]}),jsxRuntimeExports.jsxs("p",{className:"sm-help-promise-grownup",children:[jsxRuntimeExports.jsx("b",{children:"For grown-ups:"})," sit with your child and try Mia's help on question 1."]})]})}const minutesFor=n=>Math.max(3,Math.ceil(n*.6)),WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Frid
58ay","Saturday"],STRAND_STYLE={"Number & Place Value":{glyph:"N",color:"var(--sm-accent)",soft:"var(--sm-accent-soft)"},"Addition & Subtraction":{glyph:"+",color:"var(--sm-primary-deep)",soft:"var(--sm-primary-soft)"},"Multiplication & Division":{glyph:"×",color:"var(--sm-accent)",soft:"var(--sm-accent-soft)"},Fractions:{glyph:"½",color:"var(--sm-primary-deep)",soft:"var(--sm-primary-soft)"},"Shape & Space":{glyph:"◇",color:"var(--sm-accent)",soft:"var(--sm-accent-soft)"},"Money & Time":{glyph:"£",color:"var(--sm-primary-deep)",soft:"var(--sm-primary-soft)"},"Ratio & Proportion":{glyph:"⅔",color:"var(--sm-accent)",soft:"var(--sm-accent-soft)"},"Data & Statistics":{glyph:"▤",color:"var(--sm-primary-deep)",soft:"var(--sm-primary-soft)"}},DEFAULT_STRAND_STYLE={glyph:"•",color:"var(--sm-ink-2)",soft:"var(--sm-bg-2)"},styleFor=n=>STRAND_STYLE[n]??DEFAULT_STRAND_STYLE,CHILD_STRAND_LABEL={"Number & Place Value":"Numbers","Addition & Subtraction":"Adding & taking away","Multiplication & Division":"Times & sharing",Fractions:"Fractions","Shape & Space":"Shapes","Money & Time":"Money & time","Ratio & Proportion":"Comparing amounts","Data & Statistics":"Charts & tallies"},childLabelFor=n=>CHILD_STRAND_LABEL[n]??n;function levelFor(n){return n>=65?{label:"Strong",cls:"strong"}:n>=25?{label:"Growing",cls:"growing"}:{label:"New",cls:"new"}}function todayEyebrow(){return`${WEEKDAYS[new Date().getDay()]??"Today"} · today's practice`}function buildStreak(n){const t=new Date,e=t.getDay(),s=new Date(t);s.setHours(0,0,0,0),s.setDate(t.getDate()-e);const o=new Set;for(const r of n){const a=new Date(r.playedAtIso);a>=s&&a<=t&&o.add(a.getDay())}return WEEKDAYS.map((r,a)=>a===e?"today":o.has(a)?"done":"")}function HomeScreen({profile:n,onStartSession:t,onOpenGrownUps:e,strandProgress:s,recentSessions:o,sessionConfig:r,onUpdateSessionConfig:a,onChangeYearGroup:i,onStartSampleYear:l,onOpenSampleSignUp:u,onOpenQaDashboard:d,onOpenCalibration:m,isAuthenticated:h,onOpenLogin:f,onBackHome:g}){const b=h?void 0:sampleSessionIdsFor(n.yearGroupLabel),x=b?.length??r.questionCount,y=minutesFor(x),E=b?sampleSessionStrandsFor(n.yearGroupLabel):void 0,[k,T]=reactExports.useState(()=>getForcedQuestionId()??""),A=()=>{const V=k.trim().toUpperCase();if(!V)return;const O=new URL(window.location.href);O.searchParams.set("q",V),window.history.replaceState(null,"",O),t({...r,targetQuestionId:V})},N=(E&&E.length>0?E:r.strands.length>0?r.strands:Object.keys(STRAND_STYLE)).slice(0,3),S=Object.keys(STRAND_STYLE).filter(V=>s[V]!==void 0||r.strands.includes(V)).slice(0,5),M=buildStreak(o),L=M.filter(V=>V==="done").length,P=o.length===0,F=!!i&&!h,[D]=reactExports.useState(()=>({gesture:"greet",at:Date.now()})),U=!!i&&isAdminMode(),q=!h&&!!l;return jsxRuntimeExports.jsxs("div",{className:"sm-app",children:[jsxRuntimeExports.jsx("div",{className:"sm-grain","aria-hidden":"true"}),jsxRuntimeExports.jsxs("div",{className:"sm-screen",children:[jsxRuntimeExports.jsxs("header",{className:"sm-topbar",children:[jsxRuntimeExports.jsxs("div",{className:"sm-logo",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),jsxRuntimeExports.jsxs("div",{className:"sm-topbar-right",children:[!h&&g&&jsxRuntimeExports.jsx("button",{className:"sm-link",type:"button",onClick:g,children:"← Home"}),!q&&jsxRuntimeExports.jsxs("span",{className:"sm-chip",children:[n.firstName&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("strong",{children:n.firstName}),jsxRuntimeExports.jsx("span",{children:"·"})]}),U?jsxRuntimeExports.jsx("select",{value:n.yearGroupLabel,onChange:V=>i?.(V.target.value),className:"sm-chip-select","aria-label":"Year group",children:yearGroupOptionsFor(n.yearGroupLabel).map(V=>jsxRuntimeExports.jsx("option",{children:V},V))}):jsxRuntimeExports.jsx("span",{children:n.yearGroupLabel})]}),h&&jsxRuntimeExports.jsx("button",{className:"sm-grownups-btn",type:"button",onClick:e,children:"Grown-ups"}),!h&&jsxRuntimeExports.jsx("button",{className:"sm-signin-link",type:"button",onClick:f,children:"Sign in"})]})]}),jsxRuntimeExports.jsx("main",{className:"sm-home",children:q?jsxRuntimeExports.jsxs("section",{className:"sm-try-pick","aria-labelledby":"sm-try-pick-title",children:[jsxRuntimeExports.jsx("p",{className:"sm-eyebrow",children:"Free sample · no sign-up"}),jsxRuntimeExports.jsx("h1",{id:"sm-try-pick-title",children:"Which year is your child in?"}),jsxRuntimeExports.jsxs("p",{className:"sm-try-pick-sub",children:["Tap it to start ",x," real questions, about ",y," minutes. See how Mia helps when someone gets stuck."]}),jsxRuntimeExports.jsx("div",{className:"sm-try-years",children:yearGroupOptionsFor(n.yearGroupLabel).map(V=>jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-try-year",onClick:()=>l?.(V),children:[jsxRuntimeExports.jsx("span",{className:"sm-try-year-word",children:"Year"}),jsxRuntimeExports.jsx("span",{className:"sm-try-year-num",children:V.replace("Year ","")})]},V))})]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsxs("div",{className:"sm-hero",children:[jsxRuntimeExports.jsxs("div",{className:"sm-greeting",children:[jsxRuntimeExports.jsx("p",{className:"sm-eyebrow",children:h?todayEyebrow():"Free sample · no account needed"}),jsxRuntimeExports.jsx("h1",{children:n.firstName?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:["Ready when you are,",jsxRuntimeExports.jsx("br",{}),jsxRuntimeExports.jsxs("span",{className:"accent",children:[n.firstName,"."]})]}):jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:"Try a sample roun
58d."})}),jsxRuntimeExports.jsx("p",{children:h?"I picked today's questions for you. We'll work on a little of everything — short and focused.":"Choose your child’s year, then try five real questions. If they get stuck, Mia helps."})]}),jsxRuntimeExports.jsxs("div",{className:"sm-start-card",children:[jsxRuntimeExports.jsxs("div",{className:"sm-start-meta",children:[jsxRuntimeExports.jsx("span",{className:"sm-eyebrow",children:h?"Today's round":"Sample round"}),jsxRuntimeExports.jsxs("h2",{children:[jsxRuntimeExports.jsxs("span",{className:"sm-nowrap",children:[x," questions"]})," ",jsxRuntimeExports.jsxs("span",{className:"sm-nowrap",children:["· about ",y," min"]})]}),jsxRuntimeExports.jsx("div",{className:"sm-start-detail","aria-label":"Topics in today's round",children:N.map(V=>jsxRuntimeExports.jsx("span",{children:childLabelFor(V)},V))}),F&&jsxRuntimeExports.jsxs("div",{className:"sm-year-pick",role:"radiogroup","aria-label":"Choose your year group",children:[jsxRuntimeExports.jsx("span",{className:"sm-year-pick-label",children:"Pick your year, then press Start:"}),jsxRuntimeExports.jsx("div",{className:"sm-year-chips",children:yearGroupOptionsFor(n.yearGroupLabel).map(V=>jsxRuntimeExports.jsx("button",{type:"button",role:"radio","aria-checked":V===n.yearGroupLabel,className:`sm-year-chip${V===n.yearGroupLabel?" selected":""}`,onClick:()=>i?.(V),children:V.replace("Year ","Y")},V))})]})]}),jsxRuntimeExports.jsxs("button",{className:"sm-btn sm-btn-primary sm-btn-large",type:"button",onClick:()=>t({...r,targetQuestionId:void 0}),children:[h?"Start":"Start sample"," ",jsxRuntimeExports.jsx("span",{className:"arrow",children:"→"})]})]}),h&&jsxRuntimeExports.jsxs("section",{className:"sm-where","aria-label":"Where you are today",children:[jsxRuntimeExports.jsxs("div",{className:"sm-where-head",children:[jsxRuntimeExports.jsx("h3",{children:"Where you are today"}),jsxRuntimeExports.jsx("span",{className:"sm-where-link",children:P?"Fills in after your first round":"Updated after every round"})]}),S.map(V=>{const O=styleFor(V),z=Math.max(0,Math.min(100,s[V]??0));return jsxRuntimeExports.jsxs("div",{className:"sm-strand-row",children:[jsxRuntimeExports.jsx("span",{className:"sm-strand-glyph",style:{background:O.soft,color:O.color},"aria-hidden":"true",children:O.glyph}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("div",{className:"sm-strand-label",children:childLabelFor(V)}),jsxRuntimeExports.jsx("div",{className:"sm-strand-bar",style:{"--p":`${z}%`,"--strand-c":O.color}})]}),jsxRuntimeExports.jsxs("div",{className:"sm-strand-meta",children:[jsxRuntimeExports.jsx("span",{className:`sm-level-pill ${levelFor(z).cls}`,children:levelFor(z).label}),jsxRuntimeExports.jsxs("span",{className:"sm-sr-only",children:[z,"% mastered"]})]})]},V)})]})]}),jsxRuntimeExports.jsxs("div",{className:"sm-tutor-col",children:[jsxRuntimeExports.jsxs("div",{className:"sm-tutor-card",children:[jsxRuntimeExports.jsx("span",{className:"sm-tutor-name",children:"Hi, I'm Mia · your tutor"}),jsxRuntimeExports.jsx(Mia,{size:150,mood:"idle",cue:D}),jsxRuntimeExports.jsx("p",{className:"sm-tutor-line",children:h?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[`"Let's start with the `,jsxRuntimeExports.jsx("em",{children:"easy ones"}),' and warm up the brain."']}):"I'll help if a question gets tricky."}),h?jsxRuntimeExports.jsx("p",{className:"sm-tutor-sub",children:"I'll be here while you practise. Stuck? Ask me for a clue any time."}):jsxRuntimeExports.jsx(MiaHelpPromise,{})]}),h&&jsxRuntimeExports.jsxs("div",{className:"sm-streak",children:[jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("h3",{children:"This week"}),jsxRuntimeExports.jsx("p",{children:L===0?"Start a round today to begin the streak.":`${L} short ${L===1?"round":"rounds"} done. Keep the streak going.`})]}),jsxRuntimeExports.jsx("div",{className:"sm-streak-dots","aria-hidden":"true",children:M.map((V,O)=>jsxRuntimeExports.jsxs("span",{className:"sm-streak-day",children:[jsxRuntimeExports.jsx("span",{className:"sm-streak-letter",children:WEEKDAYS[O][0]}),jsxRuntimeExports.jsx("span",{className:`sm-streak-dot${V?` ${V}`:""}`})]},O))})]}),P&&h&&jsxRuntimeExports.jsxs("div",{className:"sm-recent",children:[jsxRuntimeExports.jsx("div",{className:"sm-recent-head",children:jsxRuntimeExports.jsx("span",{className:"sm-eyebrow",children:h?"First roun
58d":"Your sample"})}),jsxRuntimeExports.jsx("div",{className:"sm-recent-row",children:jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("b",{children:"Everything's set up for you"}),jsxRuntimeExports.jsx("div",{className:"sub",children:h?"Press Start and Mia will guide you — nothing to lose, lots to find out. The first few rounds warm up gently and may feel easy on purpose while Mia finds the right level.":"Try five questions with hints and worked examples to explore the maths. You can play the same sample again when you finish."})]})})]}),o.length>0&&jsxRuntimeExports.jsxs("div",{className:"sm-recent",children:[jsxRuntimeExports.jsxs("div",{className:"sm-recent-head",children:[jsxRuntimeExports.jsx("span",{className:"sm-eyebrow",children:"Recent"}),jsxRuntimeExports.jsxs("span",{className:"sm-eyebrow",children:[o.length," ",o.length===1?"round":"rounds"]})]}),o.slice(0,3).map(V=>{const O=V.total>0?Math.min(100,Math.round(V.correct/V.total*100)):0;return jsxRuntimeExports.jsxs("div",{className:"sm-recent-row",children:[jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("b",{children:V.sessionLabel}),jsxRuntimeExports.jsxs("div",{className:"sub",children:[V.correct," / ",V.total," questions"]})]}),jsxRuntimeExports.jsxs("span",{className:"sm-recent-score",children:[O,"%"]})]},`${V.playedAtIso}-${V.sessionLabel}`)})]})]})]})}),q&&u&&jsxRuntimeExports.jsx(SampleSignUpBar,{onSignUp:u}),isAdminMode()&&jsxRuntimeExports.jsxs("aside",{className:"sm-dev-panel","aria-label":"Developer controls",children:[jsxRuntimeExports.jsx("span",{className:"sm-dev-panel-title",children:"Dev mode"}),jsxRuntimeExports.jsx("input",{type:"text",placeholder:"Target skill id e.g. AS.P2.M04",value:r.targetMicroSkillId??"",onChange:V=>a({...r,targetMicroSkillId:V.target.value.trim()||void 0})}),jsxRuntimeExports.jsx("input",{type:"text",placeholder:"One question id e.g. AS.P2.M04.Q26",value:k,onChange:V=>T(V.target.value),onKeyDown:V=>{V.key==="Enter"&&A()}}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-dev-show-q",disabled:!k.trim(),onClick:A,children:"Show question →"}),d&&jsxRuntimeExports.jsx("button",{type:"button",onClick:d,children:"Open QA dashboard"}),m&&jsxRuntimeExports.jsx("button",{type:"button",onClick:m,children:"Difficulty calibration"}),jsxRuntimeExports.jsx("button",{type:"button",onClick:()=>{window.location.hash="mia-lab"},children:"Mia lab (animated tutor)"})]})]})]})}const page$1="_page_1qjdd_19",heroShell="_heroShell_1qjdd_43",container="_container_1qjdd_49",eyebrow$1="_eyebrow_1qjdd_50",button="_button_1qjdd_51",accentButton="_accentButton_1qjdd_59",navigation$1="_navigation_1qjdd_61",hero="_hero_1qjdd_43",heroVideo="_heroVideo_1qjdd_83",heroCopy="_heroCopy_1qjdd_84",heroEyebrow="_heroEyebrow_1qjdd_86",tutorEmphasis="_tutorEmphasis_1qjdd_88",heroDescription="_heroDescription_1qjdd_90",actions="_actions_1qjdd_91",sampleLink="_sampleLink_1qjdd_92",purposeSection="_purposeSection_1qjdd_95",purposeIntro="_purposeIntro_1qjdd_96",threeCards="_threeCards_1qjdd_99",purposeCard="_purposeCard_1qjdd_100",cardNumber="_cardNumber_1qjdd_102",howSection="_howSection_1qjdd_105",sectionHead="_sectionHead_1qjdd_106",steps="_steps_1qjdd_109",step="_step_1qjdd_109",parentsSection="_parentsSection_1qjdd_114",parentSplit="_parentSplit_1qjdd_115",parentsCopy="_parentsCopy_1qjdd_116",updateFigure="_updateFigure_1qjdd_118",reassurances="_reassurances_1qjdd_121",reassurance="_reassurance_1qjdd_121",plansSection="_plansSection_1qjdd_126",plansLayout="_plansLayout_1qjdd_127",plansCopy="_plansCopy_1qjdd_128",familyMia="_familyMia_1qjdd_131",planCard="_planCard_1qjdd_132",offerLabel="_offerLabel_1qjdd_134",billingToggle="_billingToggle_1qjdd_135",price="_price_1qjdd_138",standardPrice="_standardPrice_1qjdd_141",priceCaption="_priceCaption_1qjdd_142",planFeatures="_planFeatures_1qjdd_143",trialButton="_trialButton_1qjdd_146",offerTerms="_offerTerms_1qjdd_147",companyNotice="_companyNotice_1qjdd_160",soundToggle="_soundToggle_1qjdd_184",soundHint="_soundHint_1qjdd_193",srOnly$1="_srOnly_1qjdd_195",tryNote="_tryNote_1qjdd_196",miaSection="_miaSection_1qjdd_199",miaIntro="_miaIntro_1qjdd_200",miaFace="_miaFace_1q
58jdd_201",miaCopy="_miaCopy_1qjdd_204",miaChips="_miaChips_1qjdd_205",miaApproach="_miaApproach_1qjdd_208",swipe="_swipe_1qjdd_209",miaTrack="_miaTrack_1qjdd_211",miaSlide="_miaSlide_1qjdd_212",stickyTry="_stickyTry_1qjdd_217",swipeOnPhone="_swipeOnPhone_1qjdd_303",styles$7={page:page$1,heroShell,container,eyebrow:eyebrow$1,button,accentButton,navigation:navigation$1,hero,heroVideo,heroCopy,heroEyebrow,tutorEmphasis,heroDescription,actions,sampleLink,purposeSection,purposeIntro,threeCards,purposeCard,cardNumber,howSection,sectionHead,steps,step,parentsSection,parentSplit,parentsCopy,updateFigure,reassurances,reassurance,plansSection,plansLayout,plansCopy,familyMia,planCard,offerLabel,billingToggle,price,standardPrice,priceCaption,planFeatures,trialButton,offerTerms,companyNotice,soundToggle,soundHint,srOnly:srOnly$1,tryNote,miaSection,miaIntro,miaFace,miaCopy,miaChips,miaApproach,swipe,miaTrack,miaSlide,stickyTry,swipeOnPhone},LOOP="/media/home/hero-mia-loop.mp4",VOICED="/media/home/hero-mia-voiced.mp4",POSTER="/media/home/hero-mia-poster.jpg",prefersReducedMotion$1=()=>typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;function SpeakerIcon({on:n}){return jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 24 24",width:"20",height:"20","aria-hidden":"true",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[jsxRuntimeExports.jsx("path",{d:"M4 9h4l5-4v14l-5-4H4z",fill:"currentColor"}),n?jsxRuntimeExports.jsx("path",{d:"M16.5 8.5a5 5 0 0 1 0 7M19 6a8.5 8.5 0 0 1 0 12"}):jsxRuntimeExports.jsx("path",{d:"m17 9 5 6M22 9l-5 6"})]})}function HeroVideo(){const n=reactExports.useRef(null),t=reactExports.useRef(!1),[e,s]=reactExports.useState(!1),[o,r]=reactExports.useState(!0),a=reactExports.useId();function i(){const d=n.current;d&&(t.current=!1,s(!1),d.muted=!0,d.defaultMuted=!0,d.loop=!0,d.currentSrc.endsWith(LOOP)||(d.src=LOOP),prefersReducedMotion$1()||d.play().catch(()=>{}))}function l(){const d=n.current;d&&(t.current=!0,s(!0),d.loop=!1,d.muted=!1,d.src=VOICED,d.currentTime=0,d.play().catch(i))}function u(){const d=n.current;if(d){if(!t.current){l();return}d.muted=!d.muted,s(!d.muted)}}return reactExports.useEffect(()=>{const d=n.current;if(!d)return;d.setAttribute("muted",""),i();const m=new IntersectionObserver(([f])=>{t.current||prefersReducedMotion$1()||(f.isIntersecting?d.play().catch(()=>{}):d.pause())},{threshold:.1});m.observe(d);const h=window.setTimeout(()=>r(!1),4e3);return()=>{m.disconnect(),window.clearTimeout(h)}},[]),jsxRuntimeExports.jsxs("figure",{className:styles$7.heroVideo,children:[jsxRuntimeExports.jsx("video",{ref:n,poster:POSTER,playsInline:!0,preload:"auto",width:720,height:900,"aria-label":"Animation: Mia shows a child why one third is bigger than one eighth.","aria-describedby":a,onEnded:()=>{t.current&&i()}}),jsxRuntimeExports.jsxs("button",{type:"button",className:styles$7.soundToggle,"aria-pressed":e,"aria-label":e?"Turn sound off":"Turn sound on and hear Mia",onClick:u,children:[jsxRuntimeExports.jsx(SpeakerIcon,{on:e}),jsxRuntimeExports.jsx("span",{className:styles$7.soundHint,"data-show":o&&!e,"aria-hidden":"true",children:"Tap for sound"})]}),jsxRuntimeExports.jsx("p",{id:a,className:styles$7.srOnly,children:"Most maths apps tell your child “wrong”. Never why. Which one is bigger, one third or one eighth? A really common mix-up. Let’s see why. Cut one whole into three equal parts. Now the same whole, cut into eight. More parts, smaller pieces. Almost three eighths fit. So one third is bigger. You worked it out. SmartMaths explains it the way a tutor would. Very affordable tutor-style maths."})]})}const FRACTION_DURATION=32e3,FRACTION_STEPS=[{phase:"intro",name:"Introduction",at:0,stillAt:0},{phase:"attempt",name:"First attempt",at:3500,stillAt:3500},{phase:"whole",name:"Start with twelve",at:9e3,stillAt:9e3},{phase:"groups",name:"Four equal groups",at:12e3,stillAt:16400},{phase:"choose",name:"Take three groups",at:2e4,stillAt:23e3},{phase:"result",name:"The answer",at:26e3,stillAt:26e3}
58,{phase:"complete",name:"Try a sample round",at:32e3,stillAt:32e3}],FRACTION_LINES=["Hi, I’m Mia.","Let’s look together at what three quarters means.","First, start with all twelve counters.","The bottom number, four, tells us to make four equal groups.","The top number, three, means take three of those groups.","Share into four groups. Take three. Three groups of three make nine."],FRACTION_CAPTIONS=["","Start with the whole amount: 12.","Twelve counters. One whole amount.","4 equal groups · 3 in each","Take 3 of the 4 groups.","3 groups of 3 = 9"],FRACTION_DESCRIPTIONS=["","Twelve counters. The child’s answer is not quite right.","All twelve counters are shown.","Twelve counters shared into four equal groups, with three in each.","Three of the four groups are highlighted. Each has three counters.","Nine highlighted counters in three groups of three. The fourth group of three is unselected. Three quarters of twelve is nine."],smooth$1=n=>{const t=Math.max(0,Math.min(1,n));return t*t*(3-2*t)},timing=[[0,0],[5500,3200],[8500,5600],[16500,11200],[22500,15800],[28500,2e4]];function poseTime(n){const t=Math.max(0,Math.min(28500,n-3500));for(let e=1;e<timing.length;e++){const[s,o]=timing[e],[r,a]=timing[e-1];if(t<=s)return a+(o-a)*(t-r)/(s-r)}return 2e4}function fractionFrame(n){const t=poseTime(n),e=smooth$1((t-5600)/2600);return{groups:Array.from({length:4},(s,o)=>{const r=o<3&&smooth$1((t-11200-o*600)/650)>.5,a=o===3?1-.55*smooth$1((t-11200)/1300):1;return{selected:r,opacity:smooth$1((t-5200)/900)*a,labelOpacity:smooth$1((t-7900)/500)*a}}),counters:Array.from({length:12},(s,o)=>{const r=Math.floor(o/3),a=175+o%6*26,i=o<6?54:88,l=48+r*113+o%3*22;return{x:a+(l-a)*e,y:i+(64-i)*e,selected:r<3&&smooth$1((t-11200-r*600)/650)>.5,opacity:r===3?1-.55*smooth$1((t-11200)/1300):1}})}}const film="_film_b5dar_2",meta="_meta_b5dar_5",outro="_outro_b5dar_6",body="_body_b5dar_8",scene="_scene_b5dar_9",reserve="_reserve_b5dar_9",question="_question_b5dar_12",fraction="_fraction_b5dar_13",emphasis="_emphasis_b5dar_16",guidance="_guidance_b5dar_17",context$7="_context_b5dar_18",tutor="_tutor_b5dar_19",avatar="_avatar_b5dar_20",bubble="_bubble_b5dar_24",tutorCopy="_tutorCopy_b5dar_25",teachingReserve="_teachingReserve_b5dar_27",diagram="_diagram_b5dar_28",caption="_caption_b5dar_29",intro="_intro_b5dar_30",introCopy="_introCopy_b5dar_32",endActions="_endActions_b5dar_41",sample="_sample_b5dar_42",stillActions="_stillActions_b5dar_47",footer="_footer_b5dar_48",controls="_controls_b5dar_49",stepArrow="_stepArrow_b5dar_53",srOnly="_srOnly_b5dar_59",progress="_progress_b5dar_60",numberScene="_numberScene_b5dar_99",numberVisual="_numberVisual_b5dar_102",qmark="_qmark_b5dar_127",styles$6={film,meta,outro,body,scene,reserve,question,fraction,emphasis,guidance,context:context$7,tutor,avatar,bubble,tutorCopy,teachingReserve,diagram,caption,intro,introCopy,endActions,sample,stillActions,footer,controls,stepArrow,srOnly,progress,numberScene,numberVisual,qmark},FractionDemoDiagram=reactExports.forwardRef(function({description:t},e){return jsxRuntimeExports.jsx("svg",{ref:e,className:styles$6.diagram,viewBox:"0 24 480 96",role:"img","aria-label":t,children:jsxRuntimeExports.jsxs("g",{"aria-hidden":"true",children:[Array.from({length:4},(s,o)=>jsxRuntimeExports.jsx("rect",{"data-group":o,x:20+o*113,y:30,width:101,height:83,rx:9,fill:"#f2f8f4",stroke:"#dce7e0",strokeWidth:1.5,opacity:0},o)),Array.from({length:12},(s,o)=>jsxRuntimeExports.jsx("circle",{"data-counter":o,r:8,cx:175+o%6*26,cy:o<6?54:88,fill:"#002640"},o)),Array.from({length:4},(s,o)=>jsxRuntimeExports.jsx("text",{"data-count":o,x:70.5+o*113,y:99,textAnchor:"middle",fontSize:15,fontWeight:700,fill:"#002640",opacity:0,children:"3"},o))]})})});function bindFractionDiagram(n){const t=[...n.querySelectorAll("[data-group]")],e=[...n.querySelectorAll("[data-counter]")],s=[...n.querySelectorAll("[data-count]")];function o(r,a,i){const l=String(i);r.getAttribute(a)!==l&&r.setAttribute(a,l)}return r=>{const a=fractionFrame(r);a.groups.forEach((i,l)=>{o(t[l],"opacity",i.opacity),o(t[l],"stroke",i.selected?"#ff4b44":"#dce7e0"),o(t[l],"fill",i.selected?"#fff1ed":"#f2f8f4"),o(s[l],"opacity",i.labelOpacity),o(s[l],"fill",i.selected?"#b83b34":"#002640")}
58),a.counters.forEach((i,l)=>{o(e[l],"cx",i.x.toFixed(2)),o(e[l],"cy",i.y.toFixed(2)),o(e[l],"fill",i.selected?"#ff4b44":"#002640"),o(e[l],"opacity",i.opacity)})}}const navy="#002640",coral="#ff4b44",mint="#f2f8f4",line$4="#dce7e0";function Hundreds(){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[Array.from({length:6},(n,t)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsxs("g",{"data-hundred":t,children:[jsxRuntimeExports.jsx("rect",{x:-52,y:-43,width:104,height:111,rx:10,fill:mint,stroke:line$4}),Array.from({length:99},(e,s)=>jsxRuntimeExports.jsx("rect",{"data-unit":!0,x:-34+s%10*7,y:-34+Math.floor(s/10)*7,width:5.7,height:5.7,rx:.5,fill:navy},s)),jsxRuntimeExports.jsx("rect",{x:29,y:29,width:5.7,height:5.7,fill:"none",stroke:coral,strokeDasharray:"1 1"}),jsxRuntimeExports.jsx("text",{"data-count":!0,x:0,y:59,textAnchor:"middle",fontSize:28,fontWeight:700,fill:navy,children:"99"})]}),jsxRuntimeExports.jsx("g",{"data-extra":t,children:jsxRuntimeExports.jsx("rect",{x:-3,y:-3,width:6,height:6,rx:.6,fill:coral})})]},t)),jsxRuntimeExports.jsxs("g",{"data-extra-hint":!0,children:[jsxRuntimeExports.jsx("path",{d:"M339 119 L299 154",stroke:coral,strokeWidth:1.7,fill:"none"}),jsxRuntimeExports.jsx("text",{x:336,y:108,fontSize:21,fontWeight:700,fill:navy,children:"1 extra"})]})]})}function TenFrames(){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[Array.from({length:2},(n,t)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:26+t*234,y:61,width:195,height:91,rx:11,fill:mint,stroke:line$4,strokeWidth:1.5}),Array.from({length:10},(e,s)=>jsxRuntimeExports.jsx("rect",{"data-gap":t===0&&s>=7?"":void 0,x:33+t*234+s%5*36,y:69+Math.floor(s/5)*41,width:31,height:31,rx:6,fill:"#fff",stroke:line$4,strokeWidth:1.2},s))]},t)),jsxRuntimeExports.jsx("text",{x:240,y:116,textAnchor:"middle",fontSize:28,fontWeight:700,fill:navy,children:"+"}),Array.from({length:15},(n,t)=>{const e=t<7,s=e?t:t-7;return jsxRuntimeExports.jsx("circle",{"data-counter":t,cx:48+(e?0:234)+s%5*36,cy:84+Math.floor(s/5)*41,r:11.5,fill:e?navy:coral},t)}),jsxRuntimeExports.jsx("text",{"data-left":!0,x:123,y:213,textAnchor:"middle",fontSize:27,fontWeight:700,fill:navy,children:"7"}),jsxRuntimeExports.jsx("text",{"data-right":!0,x:358,y:213,textAnchor:"middle",fontSize:27,fontWeight:700,fill:navy,children:"8"}),jsxRuntimeExports.jsx("text",{"data-gap-label":!0,x:240,y:36,textAnchor:"middle",fontSize:21,fontWeight:700,fill:navy,children:"3 empty spaces"})]})}const NumberDemoDiagram=reactExports.forwardRef(function({example:t,description:e},s){return jsxRuntimeExports.jsx("svg",{ref:s,className:styles$6.diagram,viewBox:t==="multiply"?"0 0 480 255":"0 0 480 240",role:"img","aria-label":e,children:jsxRuntimeExports.jsx("g",{"aria-hidden":"true",children:t==="multiply"?jsxRuntimeExports.jsx(Hundreds,{}):jsxRuntimeExports.jsx(TenFrames,{})})})}),NUMBER_DEMOS={multiply:{stem:"6 × 99",answer:594,year:5,end:39e3,bounds:[0,3500,9e3,15e3,22e3,31e3,39e3],stops:[0,3500,12600,19400,28300,31e3,39e3],names:["Introduction","A tricky multiplication","One extra makes a hundred","Six hundreds","One extra in each group","The answer","Replay or try a sample"],lines:["Hi, I’m Mia.","Let’s look together for an easier way to work out six times ninety-nine.","Ninety-nine is one less than one hundred. Add one for now to make an easier number.","Six groups of one hundred make six hundred.","There’s one extra in every group. Six groups means six extras. Let’s take them away.","Six hundred minus six is five hundred and ninety-four. That’s six groups of ninety-nine."],captions:["","99 is just one less than 100.","99 + 1 = 100","6 × 100 = 600","Take away the 6 extras.","600 − 6 = 594"],descriptions:["","One group of ninety-nine, shown as a hundred grid with one space empty.","One extra unit completes a hundred grid. Ninety-nine plus one equals one hundred.","Six hundred grids, each with one extra unit highlighted. Six times one hundred equals six hundred.","One highlighted unit leaves each of the six hundred grids. The six extras are set aside; ninety-nine remain in each group.","Six groups of ninety-nine remain. Six hundred minus six equals five hundred and ninety-four."]}
58,addition:{stem:"7 + 8",answer:15,year:1,end:35e3,bounds:[0,3500,9e3,14500,21500,27500,35e3],stops:[0,3500,11200,18400,24400,27500,35e3],names:["Introduction","A tricky addition","Find the gap to ten","Move three counters","Ten and five","The answer","Replay or try a sample"],lines:["Hi, I’m Mia.","Let’s look together for an easier way to add seven and eight.","Seven needs three more to make ten. Can you see the three empty spaces?","Take three of the eight and move them into those spaces. We still have the same counters.","Now we have a full ten, and five more. We’ve only moved the counters.","Ten and five make fifteen. So seven plus eight is fifteen. Making ten helps us add."],captions:["","7 counters + 8 counters","7 needs 3 more to make 10.","Split the 8 into 3 and 5.","10 + 5","10 + 5 = 15"],descriptions:["","Seven navy counters and eight coral counters in two ten frames. Fifteen counters altogether.","Three empty spaces are highlighted beside the seven navy counters. Eight coral counters remain in the other frame.","Three of the eight coral counters move into the first frame. Ten counters are now on the left and five on the right; none are added or removed.","A full ten frame contains seven navy and three coral counters. Five coral counters remain on the right.","Ten counters and five counters make fifteen. Seven plus eight equals fifteen."]}},smooth=n=>{const t=Math.max(0,Math.min(1,n));return t*t*(3-2*t)},mix=(n,t,e)=>n+(t-n)*e;function multiplicationFrame(n){const t=smooth((n-15800)/2200),e=smooth((n-10100)/1300);return{groups:Array.from({length:6},(s,o)=>{const r=mix(240,83+o%3*157,t),a=mix(103,o<3?48:164,t),i=mix(1.6,1,t),l=smooth((n-23200-o*430)/1100);return{x:r,y:a,scale:i,opacity:o===0?1:t,count:n<10500||l>.98?99:100,extra:{x:mix(r+32*i,175+o*26,l),y:mix(a+32*i,244,l),scale:mix(i,1.8,l),opacity:e*(o===0?1:t),removed:l===1}}}),hintOpacity:e*(1-t)}}function additionFrame(n){const t=Array.from({length:3},(s,o)=>{const r=smooth((n-15400-o*450)/1500);return{x:mix(282+o*36,120+o*36,r),y:125+Math.sin(r*Math.PI)*56,moved:r===1}}),e=t.every(s=>s.moved);return{moving:t,gap:n>=9e3&&n<18200,left:e?10:7,right:e?5:8,labelOpacity:n>=15400&&!e?0:1}}const numberLesson=n=>{const t=NUMBER_DEMOS[n];return{...t,label:n==="multiply"?"six times ninety-nine":"seven plus eight",duration:t.end,steps:t.bounds.map((e,s)=>({at:e,stillAt:t.stops[s],name:t.names[s],phase:["intro","attempt","near","move","understand","result","complete"][s]}))}},TUTOR_DEMOS={fraction:{label:"three quarters of twelve",stem:"¾ of 12",answer:9,year:3,duration:FRACTION_DURATION,steps:FRACTION_STEPS,lines:FRACTION_LINES,captions:FRACTION_CAPTIONS,descriptions:FRACTION_DESCRIPTIONS},multiply:numberLesson("multiply"),addition:numberLesson("addition")};function tutorDemoStepAt(n,t){for(let e=n.steps.length-1;e>=0;e--)if(t>=n.steps[e].at)return e;return 0}const set=(n,t,e)=>{const s=String(e);n.getAttribute(t)!==s&&n.setAttribute(t,s)},text=(n,t)=>{n.textContent!==String(t)&&(n.textContent=String(t))},transform=(n,t,e=1)=>`translate(${n.toFixed(2)} ${t.toFixed(2)}) scale(${e.toFixed(2)})`;function bindNumberDiagram(n,t){if(t==="multiply"){const i=[...n.querySelectorAll("[data-hundred]")],l=[...n.querySelectorAll("[data-extra]")],u=[...n.querySelectorAll("[data-count]")],d=n.querySelector("[data-extra-hint]");return m=>{const h=multiplicationFrame(m);h.groups.forEach((f,g)=>{set(i[g],"transform",transform(f.x,f.y,f.scale)),set(i[g],"opacity",f.opacity),text(u[g],f.count),set(l[g],"transform",transform(f.extra.x,f.extra.y,f.extra.scale)),set(l[g],"opacity",f.extra.opacity),set(l[g],"data-removed",f.extra.removed)}),set(d,"opacity",h.hintOpacity)}}const e=[...n.querySelectorAll("[data-gap]")],s=[...n.querySelectorAll("[data-counter]")].slice(12),o=n.querySelector("[data-left]"),r=n.querySelector("[data-right]"),a=n.querySelector("[data-gap-label]");return i=>{const l=additionFrame(i);e.forEach(u=>{set(u,"stroke",l.gap?"#ff4b44":"#dce7e0"),set(u,"stroke-width",l.gap?2:1.2),set(u,"fill",l.gap?"#fff1ed":"#fff")}),l.moving.forEach((u,d)=>{set(s[d],"cx",u.x.toFixed(2)),set(s[d],"cy",u.y.toFixed(2)),set(s[d],"data-moved",u.moved)}),text(o,l.left),text(r,l.right),set(o,"opacity",l.labelOpacity),set(r,"opacity",l.labelOpacity),set(a,"opacity",l.gap?1:0)}}const prefersReducedMotion=()=>typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches;function useTutorDemo(n,t=0,e=1,s=e){const o=TUTOR_DEMOS[n],r=o.steps[t].at,a=o.steps[t+1].at,i=reactExports.useRef(null),l=reactExports.useRef(null),u=reactExports.useRef(null),d=reactExports.useRef(null),[m,h]=reactExports.useState(()=>({step:prefersReducedMotion()?5:t,reduced:prefersReducedMotion(),paused:!1,manual:!1,running:!1}));return reactExports.useEffect(()=>{const f=i.current,g=l.current;if(!f||!g)return;const b=window.matchMedia("(prefers-reduced-motion: reduce)"),x=n==="fraction"?bindFractionDiagram(g):bindNumberDiagram(g,n);let y=b.matches,E=y?o.duration:r,k=y?5:t,T=!1,A=!1,N=!1,S=!0,M=0,L=null,P="";const F=()=>N&&S&&!document.hidden&&!T&&E<o.duration&&!y;function D(){k=y&&!A?5:tutorDemoStepAt(o,E),x(E),u.current?.setAttribute("width",String(E/o.duration*100));const Q=F(),B=`${k}:${T}:${y}:${A}:${Q}`;B!==P&&(P=B,h({step:k,paused:T,reduced:y,manual:A,running:Q}))}function U(Q){if(M=0,b.matches!==y){O();return}if(!F()){L=null;return}L!==null&&(E=Math.min(o.duration,E+Math.min(100,Q-L)*(E<a?s:e))),L=Q,D(),F()&&(M=requestAnimationFrame(U))}function q(){F()?M||(L=null,M=requestAnimationFrame(U)):(cancelAnimationFrame(M),M=0,L=null),D()}function V(){const Q=f.getBoundingClientRect(),B=Math.max(0,Math.min(innerHeight,Q.bottom)-Math.max(0,Q.top)),G=f.closest("section").getBoundingClientRect(),X=document.querySelector("nav.ph-sitenav")?.getBoundingClientRect(),ne=X&&X.top<=1&&X.bottom>0?X.bottom+8:8;G.height<=innerHeight-ne?(G.top<ne||G.bottom>innerHeight)&&window.scrollBy({top:G.top-ne,behavior:"instant"}):B/Q.height<.9&&f.scrollIntoView({block:"center",behavior:"instant"})}d.current={navigate(Q){const B=Math.max(t,Math.min(o.steps.length-1,k+Q));B!==k&&(A=!0,E=o.steps[B].stillAt,T=B!==6,L=null,D(),V(),q())},replay(){A=y,E=r,T=y,L=null,D(),V(),q()},toggle(){y||E>=o.duration||(A=!1,T=!T,T||V(),D(),q())}};function O(){y=b.matches,y&&(A=!1,E=o.duration,T=!1),D(),q()}const z=new IntersectionObserver(Q=>{N=Q[Q.length-1].intersectionRatio>=.8,q()},{threshold:[0,.8,1]});z.observe(f);const K=()=>{S=!1,q()},Z=()=>{S=!0,q()};return document.addEventListener("visibilitychange",q),b.addEventListener("change",O),window.addEventListener("pagehide",K),window.addEventListener("pageshow",Z),D(),q(),()=>{S=!1,cancelAnimationFrame(M),d.current=null,z.disconnect(),document.removeEventListener("visibilitychange",q),b.removeEventListener("change",O),window.removeEventListener("pagehide",K),window.removeEventListener("pageshow",Z)}},[n,o,t,r,a,e,s]),{...m,body:i,diagram:l,progress:u,navigate:reactExports.useCallback(f=>d.current?.navigate(f),[]),replay:reactExports.useCallback(()=>d.current?.replay(),[]),toggle:reactExports.useCallback(()=>
58d.current?.toggle(),[])}}function FractionQuestion({step:n}){return jsxRuntimeExports.jsxs("h2",{className:styles$6.question,"aria-label":n===5?"Three quarters of twelve equals nine.":"Three quarters of twelve equals what?",children:[jsxRuntimeExports.jsxs("span",{className:styles$6.fraction,"aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:n===4?styles$6.emphasis:void 0,children:"3"}),jsxRuntimeExports.jsx("span",{className:n===3?styles$6.emphasis:void 0,children:"4"})]}),jsxRuntimeExports.jsxs("span",{"aria-hidden":"true",children:["of 12 = ",n===5?"9":jsxRuntimeExports.jsx("span",{className:styles$6.qmark,children:"?"})]})]})}function Bubble({text:n,reserve:t=!0}){return jsxRuntimeExports.jsx("div",{className:styles$6.bubble,children:jsxRuntimeExports.jsxs("div",{className:styles$6.tutorCopy,children:[jsxRuntimeExports.jsx("p",{children:n}),t&&jsxRuntimeExports.jsx("p",{className:styles$6.teachingReserve,"aria-hidden":"true",children:FRACTION_LINES[5]})]})})}function LessonReserve(){return jsxRuntimeExports.jsxs("div",{className:styles$6.reserve,"aria-hidden":"true",inert:!0,children:[jsxRuntimeExports.jsx(FractionQuestion,{step:1}),jsxRuntimeExports.jsxs("div",{className:styles$6.guidance,children:[jsxRuntimeExports.jsx("p",{className:styles$6.context,children:"Your child’s answer is not quite right."}),jsxRuntimeExports.jsxs("div",{className:styles$6.tutor,children:[jsxRuntimeExports.jsx("div",{className:styles$6.avatar}),jsxRuntimeExports.jsx(Bubble,{text:FRACTION_LINES[1]})]})]}),jsxRuntimeExports.jsx("svg",{className:styles$6.diagram,viewBox:"0 24 480 96"}),jsxRuntimeExports.jsx("p",{className:styles$6.caption,children:FRACTION_CAPTIONS[1]})]})}function Chevron({back:n=!1}){return jsxRuntimeExports.jsx("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:jsxRuntimeExports.jsx("path",{d:n?"m14 6-6 6 6 6":"m10 6 6 6-6 6"})})}function TutorDemo({example:n,carousel:t=!1}){const e=t?1:0,s=useTutorDemo(n,e,t?1.3:1,t?2.2:1),o=reactExports.useId(),r=TUTOR_DEMOS[n],a=reactExports.useRef(null),i=reactExports.useRef(null),l=reactExports.useRef(null),u=reactExports.useRef(!1),d=s.step===0,m=s.step===e,h=s.step===6,f=new URLSearchParams(typeof window>"u"?"":window.location.search);f.set("year",String(r.year));const g=`/try?${f.toString()}`,b=s.reduced?"Return to the first step":"Replay the explanation";reactExports.useEffect(()=>{u.current&&m&&(u.current=!1,(s.reduced?i:l).current?.focus({preventScroll:!0}))},[m,s.reduced]);function x(y){s.navigate(y),s.step+y===e&&i.current?.focus({preventScroll:!0}),s.step+y===6&&a.current?.focus({preventScroll:!0})}return jsxRuntimeExports.jsxs("section",{className:styles$6.film,"data-phase":r.steps[s.step].phase,"data-example":n,"data-carousel":t||void 0,"data-running":s.running,"aria-label":`An example of Mia explaining ${r.label}`,children:[!t&&jsxRuntimeExports.jsx("div",{className:styles$6.meta,children:jsxRuntimeExports.jsx("span",{children:"This is our approach"})}),jsxRuntimeExports.jsxs("div",{ref:s.body,id:o,className:styles$6.body,children:[jsxRuntimeExports.jsx(LessonReserve,{}),jsxRuntimeExports.jsxs("div",{className:`${styles$6.scene} ${d?styles$6.intro:""} ${n!=="fraction"&&!d?styles$6.numberScene:""}`,"aria-hidden":h,children:[jsxRuntimeExports.jsxs("div",{className:styles$6.introCopy,hidden:!d,children:[jsxRuntimeExports.jsx("h2",{children:"Your child is stuck on a maths concept."}),jsxRuntimeExports.jsxs("p",{children:["Let us show you how ",jsxRuntimeExports.jsx("strong",{children:"Mia explains."})]})]}),jsxRuntimeExports.jsx("div",{hidden:d,children:n==="fraction"?jsxRuntimeExports.jsx(FractionQuestion,{step:s.step}):jsxRuntimeExports.jsxs("h2",{className:styles$6.question,children:[r.stem," = ",s.step===5?r.answer:jsxRuntimeExports.jsx("span",{className:styles$6.qmark,children:"?"})]})}),jsxRuntimeExports.jsxs("div",{className:styles$6.guidance,children:[jsxRuntimeExports.jsx("p",{className:styles$6.context,hidden:s.step!==1,children:"Your child’s answer is not quite right."}),jsxRuntimeExports.jsxs("div",{className:styles$6.tutor,children:[jsxRuntimeExports.jsx("div",{className:styles$6.avatar,"aria-hidden":"true",children:jsxRuntimeExports.jsx(Mia,{size:60,mood:"encouraging",reducedMotion:!0}
58)}),jsxRuntimeExports.jsx(Bubble,{text:r.lines[Math.min(s.step,5)],reserve:!d&&n==="fraction"})]})]}),jsxRuntimeExports.jsxs("div",{hidden:d,className:n!=="fraction"?styles$6.numberVisual:void 0,children:[n==="fraction"?jsxRuntimeExports.jsx(FractionDemoDiagram,{ref:s.diagram,description:r.descriptions[Math.min(s.step,5)]}):jsxRuntimeExports.jsx(NumberDemoDiagram,{ref:s.diagram,example:n,description:r.descriptions[Math.min(s.step,5)]}),jsxRuntimeExports.jsx("p",{className:styles$6.caption,children:r.captions[Math.min(s.step,5)]})]})]}),jsxRuntimeExports.jsxs("div",{className:styles$6.outro,hidden:!h,children:[jsxRuntimeExports.jsx("h2",{children:"Replay to see how Mia explains this concept."}),jsxRuntimeExports.jsxs("div",{className:styles$6.endActions,children:[jsxRuntimeExports.jsx("button",{type:"button","aria-label":b,onClick:()=>{u.current=!0,s.replay()},children:"↻ Replay"}),jsxRuntimeExports.jsxs("a",{className:styles$6.sample,href:g,children:["Try a sample round ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]})]})]})]}),jsxRuntimeExports.jsx("div",{className:styles$6.stillActions,hidden:!(s.reduced&&s.step===5),children:jsxRuntimeExports.jsxs("a",{className:styles$6.sample,href:g,children:["Try a sample round ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]})}),jsxRuntimeExports.jsxs("div",{className:styles$6.footer,children:[jsxRuntimeExports.jsx("button",{ref:a,className:styles$6.stepArrow,type:"button","aria-label":"Previous step",title:"Previous step","aria-controls":o,disabled:m,onClick:()=>x(-1),children:jsxRuntimeExports.jsx(Chevron,{back:!0})}),jsxRuntimeExports.jsxs("div",{className:styles$6.controls,children:[jsxRuntimeExports.jsx("button",{ref:l,type:"button",disabled:s.reduced||h,onClick:s.toggle,"aria-label":s.paused?"Resume the explanation":"Pause the explanation",children:s.reduced?"Still view":h?"Finished":s.paused?"Resume":"Pause"}),jsxRuntimeExports.jsx("button",{type:"button",onClick:s.replay,"aria-label":b,title:t?"Replay":void 0,children:t?"↻":"↻ Replay"})]}),jsxRuntimeExports.jsx("button",{ref:i,className:styles$6.stepArrow,type:"button","aria-label":"Next step",title:"Next step","aria-controls":o,disabled:h,onClick:()=>x(1),children:jsxRuntimeExports.jsx(Chevron,{})}),jsxRuntimeExports.jsx("span",{className:styles$6.srOnly,role:"status","aria-live":"polite",children:s.manual?`Step ${s.step+1} of 7: ${r.steps[s.step].name}.${s.reduced?" Still view.":s.paused?" Paused.":""}`:s.reduced?"Reduced motion.":s.paused?"Paused.":""})]}),jsxRuntimeExports.jsx("svg",{className:styles$6.progress,viewBox:"0 0 100 3",preserveAspectRatio:"none","aria-hidden":"true",children:jsxRuntimeExports.jsx("rect",{ref:s.progress,width:0,height:3})})]})}const EXAMPLES=[{id:"fraction",label:"Fractions"},{id:"multiply",label:"Multiplication"},{id:"addition",label:"Addition"}];function MeetMia(){const n=reactExports.useRef(null),t=reactExports.useRef([]),[e,s]=reactExports.useState(0);function o(){const a=n.current;if(!a)return;if(a.scrollLeft>=a.scrollWidth-a.clientWidth-2){s(EXAMPLES.length-1);return}const i=t.current.map(d=>d?d.offsetLeft-a.offsetLeft:0),l=a.scrollLeft;let u=0;i.forEach((d,m)=>{Math.abs(d-l)<Math.abs(i[u]-l)&&(u=m)}),s(u)}function r(a){const i=t.current[a];i&&(s(a),i.scrollIntoView({behavior:"smooth",block:"nearest",inline:"start"}))}return jsxRuntimeExports.jsxs("section",{className:styles$7.miaSection,id:"how-mia-helps","aria-labelledby":"meet-mia-title",children:[jsxRuntimeExports.jsxs("div",{className:styles$7.container,children:[jsxRuntimeExports.jsxs("div",{className:styles$7.miaIntro,children:[jsxRuntimeExports.jsx("div",{className:styles$7.miaFace,"aria-hidden":"true",children:jsxRuntimeExports.jsx(Mia,{size:76,mood:"happy"})}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("p",{className:styles$7.eyebrow,children:"Meet Mia"}),jsxRuntimeExports.jsx("h2",{id:"meet-mia-title",children:"Your child’s patient maths tutor."})]})]}),jsxRuntimeExports.jsx("p",{className:styles$7.miaCopy,children:"When your child gets stuck, Mia explains the idea step by step, in plain words they can hear out loud. Then they try again, with her help close by."}),jsxRuntimeExports.jsx("div",{className:styles$7.miaChips,"aria-label":"Choose an example",children:EXAMPLES.map((a,i)=>jsxRuntimeExports.jsx("button",{type:"button","aria-pressed":e===i,onClick:()=>r(i),children:a.label},a.id))}),jsxRuntimeExports.jsx("p",{className:styles$7.miaApproach,children:"This is our approach"})]}),jsxRuntimeExports.jsx("div",{ref:n,className:`${styles$7.swipe} ${styles$7.miaTrack}`,onScroll:o,children:EXAMPLES.map((a,i)=>jsxRuntimeExports.jsx("div",{ref:l=>{t.current[i]=l},className:styles$7.miaSlide,children:jsxRuntimeExports.jsx(TutorDemo,{example:a.id,carousel:!0})},a.id))})]})}const wu="_wu_5242a_9",from="_from_5242a_57",to="_to_5242a_58",styles$5={wu,"wu-phone":"_wu-phone_5242a_22","wu-screen":"_wu-screen_5242a_24","wu-wake"
58:"_wu-wake_5242a_1","wu-island":"_wu-island_5242a_25","wu-top":"_wu-top_5242a_26","wu-fade":"_wu-fade_5242a_1","wu-status":"_wu-status_5242a_27","wu-eyebrow":"_wu-eyebrow_5242a_28","wu-view":"_wu-view_5242a_29","wu-feed":"_wu-feed_5242a_30","wu-scroll":"_wu-scroll_5242a_1","wu-card":"_wu-card_5242a_32","wu-kicker":"_wu-kicker_5242a_34","wu-a":"_wu-a_5242a_37","wu-head":"_wu-head_5242a_38","wu-in-1":"_wu-in-1_5242a_1","wu-avatar":"_wu-avatar_5242a_39","wu-summary":"_wu-summary_5242a_42","wu-in-2":"_wu-in-2_5242a_1","wu-stats":"_wu-stats_5242a_43","wu-stat":"_wu-stat_5242a_27","wu-in-3":"_wu-in-3_5242a_1","wu-in-4":"_wu-in-4_5242a_1","wu-b":"_wu-b_5242a_51","wu-skill":"_wu-skill_5242a_52","wu-skill-top":"_wu-skill-top_5242a_53","wu-skill-name":"_wu-skill-name_5242a_54","wu-chip":"_wu-chip_5242a_55",from,"wu-chip-from":"_wu-chip-from_5242a_1",to,"wu-chip-to":"_wu-chip-to_5242a_1","wu-meter":"_wu-meter_5242a_59","wu-meter-labels":"_wu-meter-labels_5242a_59","wu-seg":"_wu-seg_5242a_60","wu-fill":"_wu-fill_5242a_1","wu-label":"_wu-label_5242a_1","wu-ring":"_wu-ring_5242a_67","wu-moved":"_wu-moved_5242a_68","wu-pills":"_wu-pills_5242a_69","wu-num":"_wu-num_5242a_74","wu-odo":"_wu-odo_5242a_75","wu-c":"_wu-c_5242a_32","wu-next":"_wu-next_5242a_81","wu-d":"_wu-d_5242a_86","wu-intro":"_wu-intro_5242a_88","wu-quote":"_wu-quote_5242a_89","wu-quote-skill":"_wu-quote-skill_5242a_90"},c=(...n)=>n.map(t=>styles$5[t]).join(" ");function WeeklyUpdateAnimation(){const n=reactExports.useRef(null),[t,e]=reactExports.useState(!1);return reactExports.useEffect(()=>{const s=n.current;if(!s)return;const o=new IntersectionObserver(([r])=>e(r.isIntersecting),{threshold:.4});return o.observe(s),()=>o.disconnect()},[]),jsxRuntimeExports.jsx("div",{ref:n,className:styles$5.wu,"data-play":t,role:"img","aria-label":"Example weekly update on a phone for Alice, Year 5: 4 active days and 63 questions answered. Long multiplication moved up from Building to Knows it. Next: short division. One thing to try at home: ask her to round 47,380 to the nearest 1,000.",children:jsxRuntimeExports.jsx("div",{className:styles$5["wu-phone"],"aria-hidden":"true",children:jsxRuntimeExports.jsxs("div",{className:styles$5["wu-screen"],children:[jsxRuntimeExports.jsx("div",{className:styles$5["wu-island"]}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-top"],children:[jsxRuntimeExports.jsxs("div",{className:styles$5["wu-status"],children:[jsxRuntimeExports.jsx("span",{children:"Sun 9:00"}),jsxRuntimeExports.jsx("span",{children:"SmartMaths"})]}),jsxRuntimeExports.jsx("p",{className:styles$5["wu-eyebrow"],children:"Weekly update"})]}),jsxRuntimeExports.jsx("div",{className:styles$5["wu-view"],children:jsxRuntimeExports.jsxs("div",{className:styles$5["wu-feed"],children:[jsxRuntimeExports.jsxs("section",{className:c("wu-card","wu-a"),children:[jsxRuntimeExports.jsxs("div",{className:styles$5["wu-head"],children:[jsxRuntimeExports.jsx("span",{className:styles$5["wu-avatar"],children:"A"}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("h3",{children:"This week with Alice"}),jsxRuntimeExports.jsx("p",{children:"Year 5 · Sunday update"})]})]}),jsxRuntimeExports.jsx("p",{className:styles$5["wu-summary"],children:"A steady week, and more confident with multiplying. One skill moved up."}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-stats"],children:[jsxRuntimeExports.jsxs("div",{className:styles$5["wu-stat"],children:[jsxRuntimeExports.jsx("b",{children:"4"}),jsxRuntimeExports.jsx("span",{children:"Active days"})]}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-stat"],children:[jsxRuntimeExports.jsx("b",{children:"63"}),jsxRuntimeExports.jsx("span",{children:"Questions answered"})]})]})]}),jsxRuntimeExports.jsxs("section",{className:c("wu-card","wu-b"),children:[jsxRuntimeExports.jsx("p",{className:styles$5["wu-kicker"],children:"Since the last update"}),jsxRuntimeExports.jsx("h4",{children:"What changed"}
58),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-skill"],children:[jsxRuntimeExports.jsx("span",{className:styles$5["wu-ring"]}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-skill-top"],children:[jsxRuntimeExports.jsx("span",{className:styles$5["wu-skill-name"],children:"Long multiplication"}),jsxRuntimeExports.jsxs("span",{className:styles$5["wu-chip"],children:[jsxRuntimeExports.jsx("b",{className:styles$5.from,children:"Building"}),jsxRuntimeExports.jsx("b",{className:styles$5.to,children:"Knows it"})]})]}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-meter"],children:[jsxRuntimeExports.jsx("span",{className:styles$5["wu-seg"],children:jsxRuntimeExports.jsx("i",{})}),jsxRuntimeExports.jsx("span",{className:styles$5["wu-seg"],children:jsxRuntimeExports.jsx("i",{})}),jsxRuntimeExports.jsx("span",{className:styles$5["wu-seg"],children:jsxRuntimeExports.jsx("i",{})})]}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-meter-labels"],children:[jsxRuntimeExports.jsx("span",{children:"Learning"}),jsxRuntimeExports.jsx("span",{children:"Building"}),jsxRuntimeExports.jsx("span",{children:"Knows it"})]}),jsxRuntimeExports.jsx("p",{className:styles$5["wu-moved"],children:"▲ Moved up this week"})]}),jsxRuntimeExports.jsxs("ul",{className:styles$5["wu-pills"],children:[jsxRuntimeExports.jsxs("li",{children:["Knows it · ",jsxRuntimeExports.jsx("span",{className:styles$5["wu-odo"],children:jsxRuntimeExports.jsxs("span",{children:[jsxRuntimeExports.jsx("i",{children:"1"}),jsxRuntimeExports.jsx("i",{children:"2"})]})})]}),jsxRuntimeExports.jsxs("li",{children:["Building · ",jsxRuntimeExports.jsx("span",{className:styles$5["wu-odo"],children:jsxRuntimeExports.jsxs("span",{children:[jsxRuntimeExports.jsx("i",{children:"5"}),jsxRuntimeExports.jsx("i",{children:"4"})]})})]}),jsxRuntimeExports.jsxs("li",{children:["Learning · ",jsxRuntimeExports.jsx("b",{className:styles$5["wu-num"],children:"3"})]})]})]}),jsxRuntimeExports.jsxs("section",{className:c("wu-card","wu-c"),children:[jsxRuntimeExports.jsx("p",{className:styles$5["wu-kicker"],children:"Working on"}),jsxRuntimeExports.jsx("h4",{children:"What comes next"}),jsxRuntimeExports.jsxs("div",{className:styles$5["wu-next"],children:[jsxRuntimeExports.jsx("span",{children:"Short division"}),jsxRuntimeExports.jsx("b",{children:"Next up"})]})]}),jsxRuntimeExports.jsxs("section",{className:c("wu-card","wu-d"),children:[jsxRuntimeExports.jsx("p",{className:styles$5["wu-kicker"],children:"Optional · no homework needed"}),jsxRuntimeExports.jsx("h4",{children:"One thing to try at home"}),jsxRuntimeExports.jsx("p",{className:styles$5["wu-intro"],children:"If it comes up naturally, ask Alice:"}),jsxRuntimeExports.jsx("p",{className:styles$5["wu-quote"],children:"“Round 47,380 to the nearest 1,000.”"}),jsxRuntimeExports.jsx("p",{className:styles$5["wu-quote-skill"],children:"Rounding large numbers · tricky this week"})]})]})})]})})})}const PURPOSES=[{title:"Numeracy skills for life.",text:"From telling the time to making sense of money, numbers are part of everyday life. We want to help children build skills they can carry with them, well beyond the classroom."},{title:"A one-to-one tutor for every child.",text:"Patient, one-to-one maths support shouldn’t be a luxury. A child’s chance to feel confident with numbers shouldn’t depend on their postcode or their family’s budget."},{title:"Modern learning. At their pace.",text:"AI gives us a new way to bring patient maths help to more children. With Mia, they can follow the thinking, hear an explanation and try again in their own time."}];function MarketingPurpose(){return jsxRuntimeExports.jsx("section",{className:styles$7.purposeSection,"aria-labelledby":"purpose-title",children:jsxRuntimeExports.jsxs("div",{className:styles$7.container,children:[jsxRuntimeExports.jsxs("div",{className:styles$7.purposeIntro,children:[jsxRuntimeExports.jsx("p",{className:styles$7.eyebrow,children:"About us · Why we’re here"}),jsxRuntimeExports.jsxs("h1",{id:"purpose-title",children:["Maths learning in the era of AI.",jsxRuntimeExports.jsx("br",{})," Raising the next generation."]})]}),jsxRuntimeExports.jsx("div",{className:styles$7.threeCards,children:PURPOSES.map(({title:n,text:t},e)=>jsxRuntimeExports.jsxs("article",{className:styles$7.purposeCard,children:[jsxRuntimeExports.jsxs("span",{className:styles$7.cardNumber,"aria-hidden":"true",children:["0",e+1]}),jsxRuntimeExports.jsx("h3",{children:n}),jsxRuntimeExports.jsx("p",{children:t})]},n))})]})})}const STEPS=[{title:"Short daily practice",text:"In just 10–15 minutes, your child works through a mix of questions, chosen to match where they are on their learning journey."},{title:"Mia explains the tricky bits",text:"When something trips them up, Mia talks them through it step by step, out loud if they like."},{title:"You get an honest update",text:"A plain-English weekly summary of how they are doing: progress, not vanity scores."}],REASSURANCES=[{title:"UK primary maths",text:"Guided by the UK primary maths curriculum, with familiar topics from Year 1 to Year 6."},{title:"No ads, no gaming",text:"Nothing flashing, no rewards to chase. Just calm, focused practice."},{title:"A few minutes a day",text:"Just a few short sessions a week, to supp
58ort what your child learns at school, not replace it."}];function MarketingParentStory(){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("section",{id:"for-parents",className:styles$7.howSection,"aria-labelledby":"how-title",children:jsxRuntimeExports.jsxs("div",{className:styles$7.container,children:[jsxRuntimeExports.jsxs("div",{className:styles$7.sectionHead,children:[jsxRuntimeExports.jsx("p",{className:styles$7.eyebrow,children:"How it works"}),jsxRuntimeExports.jsx("h2",{id:"how-title",children:"Personalised and adaptive learning from a young age."}),jsxRuntimeExports.jsx("p",{children:"A short, calm routine that fits around school and busy evenings."})]}),jsxRuntimeExports.jsx("ol",{className:`${styles$7.threeCards} ${styles$7.steps} ${styles$7.swipeOnPhone}`,children:STEPS.map(({title:n,text:t},e)=>jsxRuntimeExports.jsxs("li",{className:styles$7.step,children:[jsxRuntimeExports.jsxs("span",{className:styles$7.cardNumber,"aria-hidden":"true",children:["0",e+1]}),jsxRuntimeExports.jsx("h3",{children:n}),jsxRuntimeExports.jsx("p",{children:t})]},n))})]})}),jsxRuntimeExports.jsx("section",{className:styles$7.parentsSection,"aria-labelledby":"parents-title",children:jsxRuntimeExports.jsxs("div",{className:styles$7.container,children:[jsxRuntimeExports.jsxs("div",{className:styles$7.parentSplit,children:[jsxRuntimeExports.jsxs("div",{className:styles$7.parentsCopy,children:[jsxRuntimeExports.jsx("p",{className:styles$7.eyebrow,children:"For parents"}),jsxRuntimeExports.jsx("h2",{id:"parents-title",children:"Every Sunday, see what changed."}
58),jsxRuntimeExports.jsx("p",{children:"A short update in your parent area: how the week went, what moved up, what’s next, and one easy thing to try at home."})]}),jsxRuntimeExports.jsxs("figure",{className:styles$7.updateFigure,children:[jsxRuntimeExports.jsx(WeeklyUpdateAnimation,{}),jsxRuntimeExports.jsx("figcaption",{children:"Example update. It appears in your parent area every Sunday."})]})]}),jsxRuntimeExports.jsx("div",{className:`${styles$7.threeCards} ${styles$7.reassurances}`,children:REASSURANCES.map(({title:n,text:t})=>jsxRuntimeExports.jsxs("div",{className:styles$7.reassurance,children:[jsxRuntimeExports.jsx("h3",{children:n}),jsxRuntimeExports.jsx("p",{children:t})]},n))})]})})]})}function MarketingFamilyPlan({onOpenSignUp:n,onOpenLegal:t}){const[e,s]=reactExports.useState("monthly"),o=SMARTMATHS_PLANS[e],r=FOUNDING_OFFER.pricePence[e],a=(i,l)=>{i.defaultPrevented||i.button!==0||i.metaKey||i.ctrlKey||i.shiftKey||i.altKey||(i.preventDefault(),l())};return jsxRuntimeExports.jsx("section",{className:styles$7.plansSection,id:"family-plan","aria-labelledby":"plan-title",children:jsxRuntimeExports.jsxs("div",{className:`${styles$7.container} ${styles$7.plansLayout}`,children:[jsxRuntimeExports.jsxs("div",{className:styles$7.plansCopy,children:[jsxRuntimeExports.jsx("p",{className:styles$7.eyebrow,children:"One family. One simple plan."}),jsxRuntimeExports.jsx("h2",{id:"plan-title",children:"Pricing plans"}),jsxRuntimeExports.jsx("div",{className:styles$7.familyMia,"aria-hidden":"true",children:jsxRuntimeExports.jsx(Mia,{size:100})})]}),jsxRuntimeExports.jsxs("div",{className:styles$7.planCard,children:[jsxRuntimeExports.jsx("h3",{children:"SmartMaths family plan"}),jsxRuntimeExports.jsxs("p",{className:styles$7.offerLabel,children:["Welcome offer — ",FOUNDING_OFFER.stripeCouponPercent,"% off"]}),jsxRuntimeExports.jsxs("div",{className:styles$7.billingToggle,role:"group","aria-label":"Compare billing periods",children:[jsxRuntimeExports.jsx("button",{type:"button","aria-pressed":e==="monthly",onClick:()=>s("monthly"),children:"Monthly"}),jsxRuntimeExports.jsx("button",{type:"button","aria-pressed":e==="annual",onClick:()=>s("annual"),children:"Yearly"})]}),jsxRuntimeExports.jsxs("div",{className:styles$7.priceDetails,"aria-live":"polite","aria-atomic":"true",children:[jsxRuntimeExports.jsxs("p",{className:styles$7.price,children:[jsxRuntimeExports.jsx("strong",{children:formatGbp(r)}),jsxRuntimeExports.jsxs("span",{children:["/",o.intervalLabel]})]}),jsxRuntimeExports.jsxs("p",{className:styles$7.standardPrice,children:["Standard price ",jsxRuntimeExports.jsx("s",{children:formatGbp(o.pricePence)}),"/",o.intervalLabel]}),jsxRuntimeExports.jsx("p",{className:styles$7.priceCaption,children:"Welcome price subject to availability. Confirmed at checkout."}),jsxRuntimeExports.jsxs("p",{className:styles$7.priceCaption,children:[SMARTMATHS_TRIAL_DAYS,"-day free trial. Card required.",jsxRuntimeExports.jsx("br",{}),"Cancel before the trial ends to avoid being charged."]})]}),jsxRuntimeExports.jsxs("ul",{className:styles$7.planFeatures,children:[jsxRuntimeExports.jsx("li",{children:"Every child in your household"}),jsxRuntimeExports.jsx("li",{children:"Practice that adapts as they learn"}),jsxRuntimeExports.jsx("li",{children:"Hints, worked examples and spoken help"}),jsxRuntimeExports.jsx("li",{children:"Parent progress in the app and weekly updates"})]}),jsxRuntimeExports.jsxs("a",{className:`${styles$7.button} ${styles$7.trialButton}`,href:"/sign-up",onClick:i=>a(i,n),children:["Start my ",SMARTMATHS_TRIAL_DAYS,"-day family trial ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]}),jsxRuntimeExports.jsx("a",{className:styles$7.offerTerms,href:"/legal#plans",onClick:i=>a(i,()=>t("plans")),children:"Offer terms & plan facts"})]})]})})}const YEAR_LANDING_YEARS=[1,2,3,4,5,6];function yearLandingFromPath(n){const t=n.match(/^\/year-([1-6])-maths(?:\.html)?\/?$/);return t?Number(t[1]):null}const spa=n=>t=>{t.defaultPrevented||t.button!==0||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||(t.preventDefault(),window.scrollTo({top:0,left:0,behavior:"auto"}),n())},toTop=n=>()=>{window.scrollTo({top:0,left:0,behavior:"auto"}),n()};function Logo({onOpenHome:n}){return jsxRuntimeExports.jsxs("a",{className:"sm-logo ph-logo-link",href:"/",onClick:spa(n),"aria-label":"SmartMaths home",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]})}const LEGAL_LINKS=[{view:"terms",label:"Terms for families"},{view:"privacy",label:"Privacy for parents"},{view:"child",label:"Privacy for children"},{view:"plans",label:"Plan facts"},{view:"cancellation",label:"Cancel or ask for a refund"}];function LegalColumn({onOpenLegal:n,onOpenFaq:t}){return jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"Legal"}),LEGAL_LINKS.map(e=>jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:`/legal#${e.view}`,onClick:spa(()=>
58n(e.view)),children:e.label},e.view)),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/faq#safeguarding",onClick:spa(()=>t("safeguarding")),children:"Safeguarding (FAQ)"})]})}function SiteChrome({chrome:n,active:t,homepage:e=!1}){const s=reactExports.useRef(null),o=e&&n.mode==="public",[r,a]=reactExports.useState(!1),i=m=>`ph-nav-link${t===m?" is-active":""}`,l=m=>t===m?"page":void 0,u=()=>a(!1);if(n.mode==="parent"){const m=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("button",{type:"button",className:i("progress"),"aria-current":l("progress"),onClick:()=>{u(),n.onOpenParentDashboard()},children:"Progress"}),jsxRuntimeExports.jsx("button",{type:"button",className:i("account"),"aria-current":l("account"),onClick:()=>{u(),n.onOpenAccount()},children:"Account"}),jsxRuntimeExports.jsx("a",{className:i("faq"),"aria-current":l("faq"),href:"/faq",onClick:spa(()=>{u(),n.onOpenFaq()}),children:"Help"})]});return jsxRuntimeExports.jsxs("nav",{className:"ph-nav ph-sitenav ph-sitenav-parent","aria-label":"Parent area",children:[jsxRuntimeExports.jsx(Logo,{onOpenHome:n.onOpenHome}),jsxRuntimeExports.jsxs("div",{className:"ph-nav-links",children:[jsxRuntimeExports.jsx("div",{className:"ph-nav-primary",children:m}),jsxRuntimeExports.jsxs("div",{className:"ph-nav-cta",children:[jsxRuntimeExports.jsx("button",{type:"button",className:"ph-nav-link ph-mobile-only",onClick:toTop(n.onOpenAccount),children:"Account"}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-signin-link ph-desktop-only",onClick:n.onSignOut,children:"Sign out"}),jsxRuntimeExports.jsx("button",{type:"button",className:"ph-nav-menu-btn","aria-expanded":r,"aria-controls":"site-menu",onClick:()=>a(h=>!h),children:r?"Close":"Menu"})]})]}),r&&jsxRuntimeExports.jsxs("div",{id:"site-menu",className:"ph-nav-drawer",children:[m,jsxRuntimeExports.jsx("button",{type:"button",className:"ph-nav-link",onClick:n.onSignOut,children:"Sign out"})]})]})}const d=o?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("a",{className:"ph-nav-link",href:"#how-mia-helps",onClick:u,children:"Meet Mia"}),jsxRuntimeExports.jsx("a",{className:"ph-nav-link",href:"#for-parents",onClick:u,children:"How it works"}),jsxRuntimeExports.jsx("a",{className:"ph-nav-link",href:"#family-plan",onClick:u,children:"Pricing"})]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("a",{className:i("home"),"aria-current":l("home"),href:"/",onClick:spa(()=>{u(),n.onOpenHome()}),children:"Home"}),n.mode==="public"&&jsxRuntimeExports.jsx("a",{className:"ph-nav-link",href:"/try",onClick:spa(()=>{u(),n.onTrySample()}),children:"How it works"}),jsxRuntimeExports.jsx("a",{className:i("pricing"),"aria-current":l("pricing"),href:"/pricing",onClick:spa(()=>{u(),n.onOpenPricing()}),children:"Pricing"}),jsxRuntimeExports.jsx("a",{className:i("faq"),"aria-current":l("faq"),href:"/faq",onClick:spa(()=>{u(),n.onOpenFaq()}),children:"FAQ"})]});return jsxRuntimeExports.jsxs("nav",{className:"ph-nav ph-sitenav","aria-label":"SmartMaths pages",onKeyDown:m=>{o&&r&&m.key==="Escape"&&(u(),s.current?.focus())},children:[jsxRuntimeExports.jsx(Logo,{onOpenHome:o?()=>{u(),window.history.replaceState(window.history.state,"",window.location.pathname+window.location.search),window.scrollTo({top:0,left:0,behavior:"auto"})}:n.onOpenHome}),jsxRuntimeExports.jsxs("div",{className:"ph-nav-links",children:[jsxRuntimeExports.jsx("div",{className:"ph-nav-primary",children:d}),jsxRuntimeExports.jsxs("div",{className:"ph-nav-cta",children:[n.mode==="public"?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("a",{className:"sm-signin-link",href:"/sign-in",onClick:spa(n.onOpenLogin),children:"Sign in"}),jsxRuntimeExports.jsx("a",{className:"sm-btn sm-btn-accent",href:o?"#family-plan":"/sign-up",onClick:o?u:spa(n.onOpenSignUp),children:"Get started"})]}):jsxRuntimeExports.jsx("button",{type:"button",className:"ph-nav-link ph-grownups-link",onClick:toTop(n.onUnlock),children:"Grown-ups"}
58),jsxRuntimeExports.jsx("button",{ref:s,type:"button",className:"ph-nav-menu-btn","aria-expanded":r,"aria-controls":"site-menu",onClick:()=>a(m=>!m),children:r?"Close":"Menu"})]})]}),r&&jsxRuntimeExports.jsx("div",{id:"site-menu",className:"ph-nav-drawer",children:d})]})}function SiteFooter({chrome:n,companyNotice:t}){const e=jsxRuntimeExports.jsxs("div",{className:"ph-footer-brand",children:[jsxRuntimeExports.jsxs("div",{className:"sm-logo",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),jsxRuntimeExports.jsx("p",{className:"ph-footer-tag",children:"A calm, patient maths tutor for primary school children Years 1 to 6."})]}),s=jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"Practice by year"}),YEAR_LANDING_YEARS.map(a=>jsxRuntimeExports.jsxs("a",{className:"ph-footer-link",href:`/year-${a}-maths`,children:["Year ",a," maths"]},a))]}),o=jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"Company"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/about",onClick:spa(n.onOpenAbout),children:"About us"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"mailto:[email protected]",children:"Contact us"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"https://substack.com/@smartmath",target:"_blank",rel:"noopener noreferrer",children:"Blog"})]}),r=n.mode==="public"&&t?t:jsxRuntimeExports.jsx("div",{className:"ph-footer-legal",children:"© SmartMaths · Built for UK primary families. Your data, and your child’s, stays private."});return n.mode==="parent"?jsxRuntimeExports.jsx("footer",{className:"ph-footer",children:jsxRuntimeExports.jsxs("div",{className:"ph-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"ph-footer-top",children:[e,jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"For parents"}),jsxRuntimeExports.jsx("button",{type:"button",className:"ph-footer-link",onClick:toTop(n.onOpenParentDashboard),children:"Progress"}),jsxRuntimeExports.jsx("button",{type:"button",className:"ph-footer-link",onClick:toTop(n.onOpenAccount),children:"Account & children"})]}),jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"Help"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/faq",onClick:spa(()=>n.onOpenFaq()),children:"FAQ & help"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/blog",children:"Maths guides"})]}),s,o,jsxRuntimeExports.jsx(LegalColumn,{onOpenLegal:n.onOpenLegal,onOpenFaq:n.onOpenFaq})]}),r]})}):jsxRuntimeExports.jsx("footer",{className:"ph-footer",children:jsxRuntimeExports.jsxs("div",{className:"ph-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"ph-footer-top",children:[e,jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"Product"}),n.mode==="public"&&jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/try",onClick:spa(n.onTrySample),children:"How it works"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/pricing",onClick:spa(n.onOpenPricing),children:"Pricing"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/faq",onClick:spa(()=>n.onOpenFaq()),children:"FAQ"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/blog",children:"Maths guides"})]}),jsxRuntimeExports.jsxs("div",{className:"ph-footer-col",children:[jsxRuntimeExports.jsx("h4",{children:"Account"}),n.mode==="public"?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/sign-up",onClick:spa(n.onOpenSignUp),children:"Get started"}),jsxRuntimeExports.jsx("a",{className:"ph-footer-link",href:"/sign-in",onClick:spa(n.onOpenLogin),children:"Sign in"})]}):jsxRuntimeExports.jsx("button",{type:"button",className:"ph-footer-link",onClick:toTop(n.onUnlock),children:"Grown-ups"})]}),s,o,jsxRuntimeExports.jsx(LegalColumn,{onOpenLegal:n.onOpenLegal,onOpenFaq:n.onOpenFaq})]}),r]})})}function MarketingHome({chrome:n,onOpenSignUp:t,onTrySample:e}){const s=reactExports.useRef(null),o=useStickyTry(s);return useHashOnLoad(),jsxRuntimeExports.jsxs("div",{className:styles$7.page,children:[jsxRuntimeExports.jsx("div",{className:styles$7.navigation,children:jsxRuntimeExports.jsx("div",{className:styles$7.container,children:jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"home",homepage:!0})})}),jsxRuntimeExports.jsx("header",{className:styles$7.heroShell,children:jsxRuntimeExports.jsxs("div",{className:`${styles$7.container} ${styles$7.hero}`,children:[jsxRuntimeExports.jsx(HeroVideo,{}),jsxRuntimeExports.jsxs("div",{className:styles$7.heroCopy,children:[jsxRuntimeExports.jsx("p",{className:styles$7.heroEyebrow,children:"From Year 1 to Year 6"}),jsxRuntimeExports.jsxs("h1",{children:["Your patient",jsxRuntimeExports.jsx("br",{}),jsxRuntimeExports.jsx("span",{className:styles$7.tutorEmphasis,children:"maths tutor"}),jsxRuntimeExports.jsx("br",{}),"that explains",jsxRuntimeExports.jsx("br",{}),"every mistake."]}),jsxRuntimeExports.jsx("p",{className:styles$7.heroDescription,children:"SmartMaths meets your child where they are on their learning journey. We explain every mistake in plain words and keep parents updated weekly."}),jsxRuntimeExports.jsxs("div",{className:styles$7.actions,children:[jsxRuntimeExports.jsxs("button",{ref:s,className:`${styles$7.button} ${styles$7.accentButton}`,type:"button",onClick:e,children:["Try now ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]}),jsxRuntimeExports.jsxs("a",{className:styles$7.sampleLink,href:"#family-plan",children:["See plans ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]})]}),jsxRuntimeExports.jsx("p",{className:styles$7.tryNote,children:"Free · no sign-up · 5 questions"})]})]})}),jsxRuntimeExports.jsx(MeetMia,{}),jsxRuntimeExports.jsx(MarketingParentStory,{}),jsxRuntimeExports.jsx(MarketingFamilyPlan,{onOpenSignUp:t,onOpenLegal:n.onOpenLegal}),jsxRuntimeExports.jsx(SiteFooter,{chrome:n,companyNotice:jsxRuntimeExports.jsxs("p",{className:styles$7.companyNotice,children:["© ",new Date().getFullYear()," ",LEGAL_OWNER_FACTS.tradingName,". A trading name of ",LEGAL_OWNER_FACTS.legalIdentity,".",jsxRuntimeExports.jsx("br",{}),"Registered in ",LEGAL_OWNER_FACTS.registeredIn," · Company number ",LEGAL_OWNER_FACTS.companyNumber,"."]})}),jsxRuntimeExports.jsxs("div",{className:styles$7.stickyTry,"data-visible":o,"aria-hidden":!o,children:[jsxRuntimeExports.jsxs("button",{type:"button",className:`${styles$7.button} ${styles$7.accentButton}`,tabIndex:o?0:-1,onClick:e,children:["Try now ",jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"→"})]}),jsxRuntimeExports.jsx("span",{children:"Free · no sign-up"})]})]})}function useHashOnLoad(){reactExports.useEffect(()=>{const n=decodeURIComponent(window.location.hash.slice(1));if(!n)return;const t=()=>document.getElementById(n)?.scrollIntoView({block:"start"}),e=requestAnimationFrame(t),s=setTimeout(t,450);return()=>{cancelAnimationFrame(e),clearTimeout(s)}},[])}function useStickyTry(n){const[t,e]=reactExports.useState(!1);return reactExports.useEffect(()=>{const s=[n.current,document.getElementById("family-plan")].filter(a=>!!a),o=new Map,r=new IntersectionObserver(a=>{for(const i of a)o.set(i.target,i.isIntersecting);e(![...o.values()].some(Boolean))});
58return s.forEach(a=>{o.set(a,!1),r.observe(a)}),()=>r.disconnect()},[n]),t}const surface="_surface_12vfn_3 _surface_2pd64_19",adultStyles={surface};function FamilyHome({chrome:n,parentFirstName:t,childProfiles:e,isParentUnlocked:s,onUnlockParent:o,onSelectChild:r,onAddChild:a,onOpenParentDashboard:i,onOpenAccount:l,onOpenFeedback:u,childrenError:d=!1,onRetryChildren:m}){const h=t?`, ${t}`:"";return jsxRuntimeExports.jsxs("div",{className:`${s?adultStyles.surface:""} sm-app ph-page`,children:[jsxRuntimeExports.jsxs("div",{className:"ph-wrap",children:[s?jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"home"}):jsxRuntimeExports.jsxs("nav",{className:"ph-nav","aria-label":"Family home",children:[jsxRuntimeExports.jsxs("div",{className:"sm-logo",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),jsxRuntimeExports.jsx("div",{className:"ph-nav-links",children:jsxRuntimeExports.jsx("button",{type:"button",className:"ph-nav-link ph-grownups-link",onClick:o,children:"Grown-ups"})})]}),jsxRuntimeExports.jsxs("section",{className:"ph-fam",children:[jsxRuntimeExports.jsxs("div",{className:"ph-fam-head",children:[jsxRuntimeExports.jsxs("h1",{children:["Hi",h,"."]}),jsxRuntimeExports.jsx("p",{children:e.length>0?"Who’s practising today?":d?"We’re having trouble loading your children right now.":"Let’s add your first child to get started."})]}),e.length===0?d?jsxRuntimeExports.jsxs("div",{className:"ph-fam-empty",children:[jsxRuntimeExports.jsx("h3",{children:"We couldn’t load your children"}),jsxRuntimeExports.jsx("p",{children:"This is almost always a network blip — your account and their progress are safe. Try again in a moment."}),jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-btn sm-btn-accent",onClick:m,children:["Try again ",jsxRuntimeExports.jsx("span",{className:"arrow",children:"↻"})]})]}):jsxRuntimeExports.jsxs("div",{className:"ph-fam-empty",children:[jsxRuntimeExports.jsx("h3",{children:"Add your first child"}),jsxRuntimeExports.jsx("p",{children:"Tell us their name and year group and we’ll set up their practice."}),jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-btn sm-btn-accent",onClick:a,children:[s?"Add a child":"Grown-ups"," ",jsxRuntimeExports.jsx("span",{className:"arrow",children:"→"})]})]}):jsxRuntimeExports.jsxs("div",{className:"ph-children",children:[e.map((f,g)=>{const b=(f.firstName||"?").charAt(0).toUpperCase();return jsxRuntimeExports.jsxs("button",{type:"button",className:"ph-child-card",onClick:()=>r(f.id),children:[jsxRuntimeExports.jsxs("div",{className:"ph-child-top",children:[jsxRuntimeExports.jsx("span",{className:`ph-child-avatar ph-child-avatar-${g%5}`,"aria-hidden":"true",children:b}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("div",{className:"ph-child-name",children:f.firstName}),jsxRuntimeExports.jsx("div",{className:"ph-child-year",children:f.yearGroupLabel})]})]}),jsxRuntimeExports.jsxs("span",{className:"sm-btn sm-btn-primary sm-btn-full",children:["Continue ",jsxRuntimeExports.jsx("span",{className:"arrow",children:"→"})]})]},f.id)}),s&&jsxRuntimeExports.jsxs("button",{type:"button",className:"ph-add-card",onClick:a,children:[jsxRuntimeExports.jsx("span",{className:"ph-add-plus","aria-hidden":"true",children:"+"}),"Add a child"]})]}),s&&jsxRuntimeExports.jsxs("div",{className:"ph-fam-links",children:[jsxRuntimeExports.jsx("button",{type:"button",className:"sm-btn sm-btn-soft",onClick:i,children:"Parent dashboard"}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-btn sm-btn-ghost",onClick:l,children:"Account & children"}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-btn sm-btn-ghost",onClick:u,children:"Send feedback"})]})]})]}),s&&jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}function ProductHomeScreen(n){return n.isAuthenticated?jsxRuntimeExports.jsx(FamilyHome,{chrome:n.chrome,parentFirstName:n.parentFirstName,childProfiles:n.childProfiles,isParentUnlocked:n.isParentUnlocked,onUnlockParent:n.onUnlockParent,onSelectChild:n.onSelectChild,onAddChild:n.onAddChild,onOpenParentDashboard:n.onOpenParentDashboard,onOpenAccount:n.onOpenAccount,onOpenFeedback:n.onOpenFeedback,childrenError:n.childrenError,onRetryChildren:n.onRetryChildren}):jsxRuntimeExports.jsx(MarketingHome,{chrome:n.chrome,onOpenLogin:n.onOpenLogin,onOpenSignUp:n.onOpenSignUp,onTrySample:n.onTrySample})}const page="_page_5cp4e_4 _surface_2pd64_19",styles$4={page},TRIAL_LINE="14-day free trial · card required · cancel anytime",PLAN_FEATURES=["Unlimited practice rounds","Adaptive question mix that meets your child's level","Worked examples that explain every mistake","A plain-English weekly update for you"],MAX_POLL_ATTEMPTS=3,POLL_INTERVAL_MS=2e3;function PricingScreen({chrome:n,onBackHome:t,onOpenFaq:e,onOpenLegal:s,onOpenSignUp:o,onOpenAccount:r,isAuthenticated:a,entitled:i,loading:l,status:u,trialEnd:d,currentPeriodEnd:m,cancelAtPeriodEnd:h,foundingMember:f,foundingAvailable:g,checkoutJustSucceeded:b,onRefreshStatus:x,onRequireFreshParentAuth:y}){const[E,k]=reactExports.useState(null),[T,A]=reactExports.useState(null),[N,S]=reactExports.useState(0),[M,L]=reactExports.useState(!1),[P,F]=reactExports.useState(()=>new URLSearchParams(window.location.search).get("ref")??"");reactExports.useEffect(()=>{if(!b||i)return;if(N>=MAX_POLL_ATTEMPTS){L(!0);return}const Z=setTimeout(()=>{x().finally(()=>S(Q=>Q+1))},POLL_INTERVAL_MS);return()=>clearTimeout(Z)},[b,i,N,x]);const D=async Z=>{if(!await y({kind:"billing"}))return;k(Z),A(null);const B=await createCheckoutSession(Z,P.trim()||void 0);if(B.url){window.location.assign(B.url);return}B.error==="already_subscribed"?x():B.error==="invalid_referral_code"?A("That referral code doesn't look right — check it with your friend and try again."):B.error==="own_referral_code"?A("That's your own referral code — share it with a friend instead!"):A("Couldn't start checkout — please try again."),k(null)},U=g||!a,q=U?jsxRuntimeExports.jsxs("p",{className:"plan-badge pricing-founding-banner",children:["Welcome offer — ",FOUNDING_OFFER.stripeCouponPercent,"% off"]}):null,V=Z=>U?FOUNDING_OFFER.pricePence[Z]:SMARTMATHS_PLANS[Z].pricePence,O=V("monthly")*12-V("annual"),z=Z=>U?jsxRuntimeExports.jsxs("p",{className:"subtle",children:["Standard price ",jsxRuntimeExports.jsx("s",{children:formatGbp(SMARTMATHS_PLANS[Z].pricePence)}),"/",SMARTMATHS_PLANS[Z].intervalLabel,". Welcome price subject to availability, confirmed at checkout."]}):null,K=jsxRuntimeExports.jsxs("p",{className:"subtle pricing-legal-links",children:[jsxRuntimeExports.jsx("a",{href:"/legal#plans",onClick:Z=>{Z.preventDefault(),s("plans")},children:"Plan facts"})," · ",jsxRuntimeExports.jsx("a",{href:"/legal#cancellation",onClick:Z=>{Z.preventDefault(),s("cancellation")},children:"Cancel or ask for a refund"})]});if(b&&!i)return jsxRuntimeExports.jsxs("section",{className:`screen screen-enter pricing-screen ${styles$4.page}`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"pricing"}),jsxRuntimeExports.jsxs("header",{className:"card pricing-header",children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"For families"}),jsxRuntimeExports.jsx("h1",{children:"Activating your subscription…"}),jsxRuntimeExports.jsx("div",{className:"pricing-header-actions",children:jsxRuntimeExports.jsx("button",{className:"btn btn-soft",type:"button",onClick:t,children:"← Back to home"})})]}),jsxRuntimeExports.jsx("section",{className:"card pricing-activating",children:M?jsxRuntimeExports.jsx("p",{className:"subtle",children:"This can take a minute — refresh in a bit, or check your email for a receipt."}):jsxRuntimeExports.jsx("p",{className:"subtle",children:"Just a moment — we're confirming your payment with Stripe."})})]});if(a&&l)return jsxRuntimeExports.jsx("section",{className:`screen screen-enter pricing-screen ${styles$4.page}`,children:jsxRuntimeExports.jsx("div",{className:"card loading-card",children:jsxRuntimeExports.jsx("h2",{children:"Loading your plan…"})})});if(a&&i&&u){const Z=derivePlanCopy(u,d,m,h);return jsxRuntimeExports.jsxs("section",{className:`screen screen-enter pricing-screen ${styles$4.page}`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"pricing"}),jsxRuntimeExports.jsxs("header",{className:"card pricing-header",children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"For families"}),jsxRuntimeExports.jsx("h1",{children:b?"You're all set!":"Your plan"}),jsxRuntimeExports.jsx("p",{className:"subtle",children:"Family plan — covers all your children."})]}),jsxRuntimeExports.jsxs("section",{className:"card plan-card plan-card-popular",children:[f&&jsxRuntimeExports.jsx("p",{className:"plan-badge plan-badge-popular",children:"Founding family — 50% off forever"}),jsxRuntimeExports.jsx("p",{className:Z.tone==="warning"?"pricing-plan-warning":"subtle",children:Z.line}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",onClick:r,children:"Manage subscription"})]}),K,jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}return jsxRuntimeExports.jsxs("section",{className:`screen screen-enter pricing-screen ${styles$4.page}`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"pricing"}),jsxRuntimeExports.jsxs("header",{className:"card pricing-header",children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"For families"}),jsxRuntimeExports.jsx("h1",{children:"One plan, every child"}),jsxRuntimeExports.jsx("p",{className:"subtle",children:"The family plan covers every child in your household. Start with a 14-d
58ay free trial."})]}),q,jsxRuntimeExports.jsxs("section",{className:"pricing-grid","aria-label":"Choose a plan",children:[jsxRuntimeExports.jsxs("article",{className:"card plan-card",children:[jsxRuntimeExports.jsx("p",{className:"plan-badge",children:"Monthly"}),jsxRuntimeExports.jsxs("h2",{className:"plan-price",children:[formatGbp(V("monthly")),jsxRuntimeExports.jsx("span",{className:"subtle",children:" /month"})]}),z("monthly"),jsxRuntimeExports.jsx("ul",{children:PLAN_FEATURES.map(Z=>jsxRuntimeExports.jsx("li",{children:Z},Z))}),jsxRuntimeExports.jsx("p",{className:"subtle",children:TRIAL_LINE}),a?jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",disabled:E!==null,onClick:()=>{D("monthly")},children:E==="monthly"?"Starting checkout…":"Start free trial"}):jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",onClick:o,children:"Start free trial"})]}),jsxRuntimeExports.jsxs("article",{className:"card plan-card plan-card-popular",children:[jsxRuntimeExports.jsx("p",{className:"plan-badge plan-badge-popular",children:"Best value"}),jsxRuntimeExports.jsxs("h2",{className:"plan-price",children:[formatGbp(V("annual")),jsxRuntimeExports.jsx("span",{className:"subtle",children:" /year"})]}),z("annual"),jsxRuntimeExports.jsx("ul",{children:PLAN_FEATURES.map(Z=>jsxRuntimeExports.jsx("li",{children:Z},Z))}),jsxRuntimeExports.jsxs("p",{className:"subtle",children:["Save ",formatGbp(O)," a year vs paying monthly (",formatGbp(V("monthly")*12),"/year)."]}),jsxRuntimeExports.jsx("p",{className:"subtle",children:TRIAL_LINE}),a?jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",disabled:E!==null,onClick:()=>{D("annual")},children:E==="annual"?"Starting checkout…":"Start free trial"}):jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"button",onClick:o,children:"Start free trial"})]})]}),T&&jsxRuntimeExports.jsx("p",{className:"pricing-checkout-error",children:T}),jsxRuntimeExports.jsxs("section",{className:"card pricing-referral-entry",children:[jsxRuntimeExports.jsxs("label",{htmlFor:"referral-code-input",children:[jsxRuntimeExports.jsx("strong",{children:"Been given a referral code?"}),jsxRuntimeExports.jsx("span",{className:"subtle",children:" Pop it in and your first month is free."})]}),jsxRuntimeExports.jsx("input",{id:"referral-code-input",className:"pricing-referral-input",type:"text",placeholder:"e.g. SM-A1B2C3D4",value:P,onChange:Z=>F(Z.target.value),autoComplete:"off",spellCheck:!1}),P.trim()&&jsxRuntimeExports.jsx("p",{className:"subtle",children:"Your friend's code will be applied at checkout."})]}),jsxRuntimeExports.jsxs("section",{className:"card pricing-faq-teaser",children:[jsxRuntimeExports.jsx("h3",{children:"Family plan — one price, every child"}),jsxRuntimeExports.jsxs("p",{className:"subtle",children:["Add as many children as you like at no extra cost. Questions? ",jsxRuntimeExports.jsx("button",{className:"btn-link",type:"button",onClick:e,children:"See the FAQ"}),"."]})]}),K,jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}const FAQ_IDS={"What safeguarding is in place?":"safeguarding"},FAQS=[{question:"How much does it cost?",answer:`One family plan covers every child in your household: ${formatGbp(SMARTMATHS_PLANS.monthly.pricePence)} a month or ${formatGbp(SMARTMATHS_PLANS.annual.pricePence)} a year, starting with a ${SMARTMATHS_TRIAL_DAYS}-day free trial. New families can get the Welcome offer — ${FOUNDING_OFFER.stripeCouponPercent}% off: ${formatGbp(FOUNDING_OFFER.pricePence.monthly)} a month or ${formatGbp(FOUNDING_OFFER.pricePence.annual)} a year, subject to availability and confirmed at checkout. See the Pricing page for the details.`},{question:"Is there a referral scheme?",answer:"Yes, give a month, get a month. Your personal code lives in Account → Plan & billing once you’re subscribed: share it and your friend’s first month is free. When their first payment goes through, a month’s credit comes off your next bill automatically, up to six friends a year, and it works alongside the Welcome offer."},{question:"Can my child use SmartMaths AI by themself?",answer:"Yes, SmartMaths is safe to be used by children independently. Questions and scenarios are designed to be age-appropriate and relevant."},{question:"For which school year groups is SmartMaths AI suitable?",answer:`UK primary school Years 1 to 6 are available now. Choose your child’s current school year and SmartMaths will tailor practice to that level.
59
60The platform uses adaptive technology which means that questions are displayed at an optimal level of difficulty for your child. This ensures they are stretched and challenged without becoming demotivated.`},{question:"Will my child interact directly with AI?",answer:"The simple answer is no. Children cannot prompt the AI, chat with it, or type anything into it. Only pre-defined interactions are possible, and they guide your child towards the correct answer visually or explained out loud."},{question:"What safeguarding is in place?",answer:"There is no chat, no messaging, and no contact with anyone inside SmartMaths. Your child only ever answers maths questions. There are no ads or external links in a child’s session, and we collect only a first name and school year for each child. You stay in control from the parent area and can delete everything at any time."},{question:"Does SmartMaths follow the national curriculum?",answer:"SmartMaths is guided by the UK primary maths curriculum and built around the topics children meet in primary school. It does not mirror every school’s term-by-term plan or replace the curriculum your child’s school follows."},{question:"Is my child’s data safe?",answer:"Yes. We keep your child’s information in our own secure database, we never sell it, and you can ask us to delete everything at any time, just email us at [email protected]."},{question:"How often should my child use SmartMaths AI?",answer:"A short round of about ten questions, a few times a week, is plenty. Approximately 10–15 minutes a day. Little and often beats long sessions."},{question:"Can I see how my child is doing?",answer:"Yes. The parent dashboard gives you a plain-English weekly update, what’s getting stronger, what’s still settling, and one thing you could try together at home."},{question:"What is this, exactly, and who’s behind it?",answer:"SmartMaths is made by a small UK team. It’s built to sit alongside school, not replace it: short, calm practice at home, with Mia to help when your child gets stuck and a plain-English update for you. Your feedback genuinely shapes what we build next."}];function FaqScreen({chrome:n,initialAnchor:t=null}){return reactExports.useEffect(()=>{const e=document.title;return document.title="FAQ — SmartMaths",()=>{document.title=e}},[]),reactExports.useEffect(()=>{t&&document.getElementById(t)?.scrollIntoView({block:"start"})},[t]),jsxRuntimeExports.jsxs("section",{className:`screen screen-enter marketing-screen ${styles$4.page}`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"faq"}),jsxRuntimeExports.jsxs("header",{className:"card marketing-hero",children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"Frequently Asked Questions"}),jsxRuntimeExports.jsx("h1",{children:"Everything parents ask first"})]}),jsxRuntimeExports.jsxs("section",{className:"faq-list","aria-label":"FAQ list",children:[FAQS.map(e=>jsxRuntimeExports.jsxs("article",{className:"card",id:FAQ_IDS[e.question],children:[jsxRuntimeExports.jsx("h3",{children:e.question}),e.answer.split(`
61
62`).map(s=>jsxRuntimeExports.jsx("p",{className:"subtle",children:s},s))]},e.question)),jsxRuntimeExports.jsxs("article",{className:"card",children:[jsxRuntimeExports.jsx("h3",{children:"How do I get help or send feedback?"}),jsxRuntimeExports.jsxs("p",{className:"subtle",children:["Email us at ",jsxRuntimeExports.jsx("a",{href:"mailto:[email protected]",children:"[email protected]"}),", or use the feedback form in Account → Help & support once you’re signed in. We read everything."]})]})]}),jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}function AboutScreen({chrome:n}){return jsxRuntimeExports.jsxs("div",{className:styles$7.page,children:[jsxRuntimeExports.jsx("div",{className:styles$7.navigation,children:jsxRuntimeExports.jsx("div",{className:styles$7.container,children:jsxRuntimeExports.jsx(SiteChrome,{chrome:n})})}),jsxRuntimeExports.jsx(MarketingPurpose,{}),jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}const YEAR_LANDING_CONTENT={1:{year:1,ages:"5–6",metaTitle:"Year 1 Maths Practice for Ages 5–6 | SmartMaths",metaDescription:"Calm, adaptive Year 1 maths practice for ages 5–6: counting to 20, adding and taking away, telling the time and more. Free sample roun
62d, no account needed.",h1:"Year 1 maths practice",heroIntro:"Calm, adaptive maths practice for children aged 5–6, guided by the UK primary maths curriculum. Short sessions move in small steps through counting, adding and taking away, money, time and shapes — and a patient AI tutor explains every mistake in plain words. No coins, no flashing rewards, no pressure — just steady, confident progress.",topics:[{label:"Counting and numbers to 20",blurb:"Counting forwards and backwards, finding one more and one less, and seeing teen numbers as a ten plus some ones."},{label:"Adding and taking away",blurb:"Number bonds to 10, breaking numbers into parts, and adding and subtracting within 20 — including missing-number puzzles."},{label:"Counting in 2s, 5s and 10s",blurb:"Counting in equal groups and sharing objects out fairly — the first steps towards times tables."},{label:"Halves and quarters",blurb:"Checking that parts are really equal, then naming halves and quarters of shapes."},{label:"Money",blurb:"Recognising UK coins and working out how much a handful of coins is worth altogether."},{label:"Telling the time",blurb:"Reading o’clock and half-past times on a clock face, and using words like before, after and next."},{label:"Shapes and measuring",blurb:"Counting the sides and corners of 2D shapes, and measuring lengths in centimetres."}],examples:[{id:"AS.P3.M03.Q12",presentation:"described",stem:"8 + 5 = __",visualNote:"On screen: eight counters in a ten-frame, with five more alongside.",answerLine:"Answer: 13. Move two of the five counters to make ten, then add the remaining three.",whyLine:"The ten-frame makes the two empty spaces visible and shows why splitting five into two and three helps."},{id:"MEA.P6.M01.Q26",presentation:"choices",stem:"Read the clock. What time is it?",visualNote:"On screen: a clock face.",options:["6 o'clock","8 o'clock","5 o'clock","7 o'clock"],answerLine:"Answer: 6 o’clock — the short hand points to the 6.",whyLine:"The wrong options catch children who muddle the clock’s two hands — the most common mix-up when learning to tell the time."},{id:"NPV.P3.M03.Q66",presentation:"described",stem:"“Order from smallest to largest.” Your child drags four number cards — 19, 13, 16 and 11 — into the right order.",answerLine:"Answer: 11, 13, 16, 19.",whyLine:"It checks your child sees teen numbers as a ten plus some ones, rather than guessing from digits that all look similar."}],qas:[{q:"What maths should a 5 year old know?",a:"In Year 1, children work on counting to 20, knowing the pairs of numbers that make 10, and adding and taking away within 20. They also start naming halves and quarters, recognising coins, telling o’clock and half-past times, and describing simple shapes. Every child settles into these at their own pace, so it’s normal for some parts to feel secure long before others."},{q:"How can I help my child with Year 1 maths at home?",a:"Little and often works best at this age — a few minutes counting stairs, sharing snacks equally or paying with real coins does more than a long worksheet. Point out o’clock times and shapes around the house, and ask one-more, one-less questions in everyday moments. Keep it light: at 5 or 6, confidence matters as much as the maths itself."},{q:"Is SmartMaths right for a Year 1 child?",a:"Yes — Year 1 is where SmartMaths starts. Sessions are short and calm, questions adjust to your child, and when something goes wrong the AI tutor explains the mistake in plain words instead of just marking it wrong. It supports what your child does at school rather than replacing it, and you get a plain-English picture of progress each week. The sample round is free and needs no account, so you can try it together first."}],ctaLine:"Try a free Year 1 sample round — five real questions, no account needed."},2:{year:2,ages:"6–7",metaTitle:"Year 2 Maths Practice (Ages 6–7) | SmartMaths",metaDescription:"Calm, adaptive Year 2 maths practice for ages 6–7 — times tables, fractions and money, guided by the UK primary maths curriculum. Free sample roun
62d, no account.",h1:"Year 2 maths practice",heroIntro:"Short, calm practice for children aged 6–7, guided by the UK primary maths curriculum. Your child works through Year 2 topics in small steps — from two-digit adding and subtracting to the 2, 5 and 10 times tables — and the tutor explains every mistake in plain words. No coins, no flashing rewards, no pressure.",topics:[{label:"Numbers to 100",blurb:"Splitting two-digit numbers into tens and ones, comparing them, and finding 10 more or 10 less."},{label:"Adding and subtracting",blurb:"Adding and subtracting two-digit numbers, including the tricky step of crossing a ten."},{label:"Times tables",blurb:"The 2, 5 and 10 times tables, built up with dot arrays so multiplying makes sense rather than being memorised."},{label:"Fractions",blurb:"Finding halves and quarters of amounts and groups of objects, and spotting when two fractions show the same thing."},{label:"Money, measuring and time",blurb:"Working out change with coins, measuring in grams, centimetres and metres, and telling the time to quarter past and quarter to."}
62,{label:"Shapes and symmetry",blurb:"Naming 2D and 3D shapes, counting faces, spotting lines of symmetry and describing turns."},{label:"Pictograms and bar charts",blurb:"Reading pictograms and bar charts to answer questions like which choice was the most popular."}],examples:[{id:"AS.P4.M04.Q51",presentation:"described",stem:"23 + 14 = __",answerLine:"Answer: 37.",whyLine:"It teaches adding tens, then ones. Split 14 into 10 and 4: 23 + 10 = 33, then 33 + 4 = 37. The worked example shows these jumps on a number line."},{id:"MD.P2.M02.Q116",presentation:"described",stem:"The child sees an array of 20 dots — 5 rows with 4 dots in each — and is asked: “Which multiplication matches this array?” They choose between 5 × 4 = 20, 5 + 4 = 9, 5 × 5 = 25 and 4 × 4 = 16.",answerLine:"Answer: 5 × 4 = 20.",whyLine:"It catches the classic mix-up of adding the two numbers (5 + 4 = 9) instead of multiplying the rows by the dots in each row."},{id:"AS.P4.M05.Q108",presentation:"described",stem:"62 - 7 = __",answerLine:"Answer: 55.",whyLine:"It teaches crossing a ten when subtracting. Split 7 into 2 and 5: 62 − 2 = 60, then 60 − 5 = 55. The worked example shows why we split the jump at 60."}],qas:[{q:"What maths should a 7 year old know?",a:"In Year 2 (ages 6–7), children work with numbers to 100 — splitting them into tens and ones, adding and subtracting two-digit numbers, and starting the 2, 5 and 10 times tables. They also meet halves and quarters, count money and give change, tell the time to quarter past and quarter to, and read simple pictograms and bar charts. Every child moves at their own pace, so treat this as a guide rather than a checklist."},{q:"How can I help my child with Year 2 maths at home?",a:"Little and often beats long sessions — ten minutes of focused practice a few times a week is plenty. Everyday moments count too: working out change in a shop, spotting rectangles around the house, or reading the clock at quarter past. When your child gets stuck, ask them to explain their thinking out loud; the mistake usually shows you which small step needs another look."},{q:"How does SmartMaths help with Year 2 maths?",a:"SmartMaths gives your child short, adaptive practice on Year 2 topics, and the AI tutor explains each mistake in plain words instead of just marking it wrong. You get a plain-English weekly picture of what your child practised and where they are getting stronger. It works alongside school rather than replacing it, and it does not mirror every school’s term-by-term plan. The sample round is free and needs no account, so you can see how it feels first."}],ctaLine:"Try a free Year 2 sample round — five real questions, no account needed."},3:{year:3,ages:"7–8",metaTitle:"Year 3 Maths Practice (Ages 7–8) | SmartMaths",metaDescription:"Calm, adaptive Year 3 maths practice for ages 7–8. Times tables, column methods, fractions and more — with a patient tutor that explains every mistake.",h1:"Year 3 maths practice",heroIntro:"Year 3 (ages 7–8) is the year of times tables, column methods and first real fractions. SmartMaths gives your child calm, adaptive practice guided by the UK primary maths curriculum, with a patient tutor that explains every mistake in plain words. No coins, no flashing rewards, no pressure — just steady, confident progress.",topics:[{label:"Numbers to 1000",blurb:"Reading, writing and comparing three-digit numbers, splitting them into hundreds, tens and ones, and rounding to the nearest 10 or 100."},{label:"Column addition and subtraction",blurb:"Adding and subtracting three-digit numbers with the written column method, including exchanging when a column does not have enough."},{label:"Times tables",blurb:"The 3, 4 and 8 times tables, using them to divide, handling remainders, and solving word problems with simple scaling."},{label:"Fractions",blurb:"Finding a fraction of an amount, comparing and ordering fractions, and adding fractions that have the same bottom number."},{label:"Measuring and perimeter",blurb:"Comparing amounts in millilitres and litres, weighing in grams and kilograms, finding the perimeter of simple shapes, and time facts like minutes in an hour."}
62,{label:"Bar charts and pictograms",blurb:"Reading and building bar charts, reading pictograms where one symbol stands for more than one, and completing two-way tables."},{label:"Lines and angles",blurb:"Spotting horizontal and vertical lines and comparing angles with a right angle."}],examples:[{id:"MD.P3.M01.Q104",presentation:"described",stem:"3 × 8 = __",answerLine:"Answer: 24",whyLine:"Your child types the answer — the 3, 4 and 8 times tables are the backbone of Year 3, and quick recall here is what school builds on all year."},{id:"FDP.P6.M01.Q304",presentation:"choices",stem:"Zara says 2/6 + 3/6 = 5/12. Choose the correct total.",options:["5/6","5/12","6/6","1/6"],answerLine:"Answer: 5/6",whyLine:"It catches the classic mix-up of adding the top and bottom numbers of a fraction — the bottom number should stay the same."},{id:"DS.P2.M05.Q301",presentation:"described",stem:"Three teams recorded their goals this season: Tigers scored 4, Lions scored 6, and Bears scored 3. Build the bar chart to show each team's goals.",answerLine:"Answer: Tigers 4, Lions 6, Bears 3",whyLine:"Your child ta
62ps each bar to the right height on screen — it checks they can turn numbers in a sentence into a chart, not just read one."}],qas:[{q:"What maths should a 7 year old know?",a:"By the end of Year 3, most children can read, write and compare numbers to 1000, add and subtract three-digit numbers with the column method, and recall the 3, 4 and 8 times tables. They also work with fractions, measure in grams and millilitres, find perimeters, and read bar charts. Children develop at different speeds, so it is normal to be confident in some of these while still building others."},{q:"How can I help my child with Year 3 maths at home?",a:"Little and often works best — ten minutes a few times a week beats one long session. Times tables (3s, 4s and 8s) are the backbone of Year 3, so quick recall practice pays off all year. When your child gets something wrong, ask them to talk you through their thinking — most Year 3 slips come from one small mix-up, like adding the top and bottom of a fraction, rather than a general weakness."},{q:"How does SmartMaths help with Year 3 maths?",a:"SmartMaths gives short, calm practice sessions that adapt to your child, guided by the UK primary maths curriculum. When they slip, the AI tutor explains the mistake in plain words and offers a smaller step, and you get a plain-English picture of how practice is going each week. The sample round is free and needs no account. It supports what school does — it does not replace it, and it does not mirror every school’s term-by-term plan."}],ctaLine:"Try a free Year 3 sample round — no account needed."},4:{year:4,ages:"8–9",metaTitle:"Year 4 Maths Practice (Ages 8–9) | SmartMaths",metaDescription:"Calm, adaptive Year 4 maths practice for ages 8–9: times tables to 12 × 12, decimals, angles and more. Try a free sample round — no account needed.",h1:"Year 4 maths practice",heroIntro:"Calm, adaptive maths practice for children aged 8–9, guided by the UK primary maths curriculum. Year 4 brings times tables to 12 × 12, decimals, angles and coordinates. SmartMaths keeps sessions short and steady, and explains every mistake in plain words.",topics:[{label:"Times tables",blurb:"Quick, confident recall of every fact up to 12 × 12, such as knowing 7 × 9 without counting up."},{label:"Multiplying bigger numbers",blurb:"Written methods for multiplying two- and three-digit numbers by a one-digit number, like 345 × 6, and putting them to work by scaling up a recipe."},{label:"Four-digit numbers",blurb:"Understanding what each digit is worth in a four-digit number, and finding 100 or 1000 more or less."}
62,{label:"Fractions and decimals",blurb:"Meeting tenths and hundredths, writing simple fractions as decimals, and deciding which decimal is bigger."},{label:"Adding, subtracting and checking",blurb:"Solving two-step problems, finding missing numbers, and using the opposite operation to check an answer."},{label:"Angles, shapes and coordinates",blurb:"Naming acute, right and obtuse angles, sorting triangles, spotting lines of symmetry, and plotting points on a grid."},{label:"Bar charts and measures",blurb:"Reading bar charts where the axis goes up in steps, and converting between kilometres and metres, or litres and millilitres."}],examples:[{id:"MD.P3.M02.Q200",presentation:"described",stem:"7 × 8 = __",answerLine:"Answer: 56",whyLine:"The child types the answer — a quick check of times tables recall. 7 × 8 is one of the facts children muddle most often, and the tutor nudges them to skip-count in eights if it has not stuck yet."},{id:"GEO.P4.M02.Q302",presentation:"choices",stem:"On the treasure map, X marks the spot. What are the coordinates of the treasure?",visualNote:"On screen: a treasure map drawn on a coordinates grid.",options:["(4, 3)","(3, 4)","(5, 3)","(4, 2)"],answerLine:"Answer: (4, 3) — 4 across, then 3 up.",whyLine:"In the app the child reads the X on a grid map, and the (3, 4) option catches the classic mix-up of swapping the across number and the up number."},{id:"FDP.P4.M04.Q223",presentation:"described",stem:"Three jumps measure 2.3 m, 2.25 m and 2.4 m. Order them from shortest to longest.",answerLine:"Answer: 2.25 m, then 2.3 m, then 2.4 m.",whyLine:"The child drags the jumps into order — it catches the common mix-up of thinking 2.25 must be bigger than 2.3 because it has more digits."}],qas:[{q:"What maths should an 8 year old know?",a:"In Year 4, most children aged 8–9 are working on quick recall of times tables up to 12 × 12, multiplying two- and three-digit numbers, tenths and hundredths as decimals, naming types of angle, and reading bar charts. Every child is different, and gaps from earlier years are completely normal. Steady, little-and-often practice closes them more reliably than cramming."},{q:"How can I help my child with times tables in Year 4?",a:"Little and often works best — a few minutes a day of mixed questions builds the quick recall Year 4 asks for. In England, Year 4 children also take a short times tables check in the summer term, covering facts up to 12 × 12. Mixing quick-fire recall with understanding, like skip-counting in eights to reach 7 × 8, helps the tricky facts stick."},{q:"How does SmartMaths help with Year 4 maths?",a:"Your child gets short practice sessions that adapt to what they can already do, and a patient AI tutor explains every mistake in plain words. You get a simple weekly picture of how practice is going, with no coins, no flashing rewards and no pressure. It supports what school is teaching rather than replacing it. The sample round is free and needs no account, so you can see it for yourself in two minutes."}],ctaLine:"See what Year 4 practice feels like — try a free sample round, no account needed."},5:{year:5,ages:"9–10",metaTitle:"Year 5 Maths Practice (Ages 9–10) | SmartMaths",metaDescription:"Calm, adaptive maths practice for ages 9–10. Fractions, long multiplication, angles and decimals — try a free Year 5 sample round, no account needed.",h1:"Year 5 maths practice",heroIntro:"Year 5 is when the numbers get bigger — long multiplication, fractions with different denominators, decimals and missing angles. SmartMaths gives children aged 9–10 calm, adaptive practice guided by the UK primary maths curriculum. When something goes wrong, the tutor explains the mix-up in plain words, so mistakes become the useful part.",topics:[{label:"Place value and large numbers",blurb:"Reading, writing and rounding very large numbers, and putting decimals in order on a number line."},{label:"Adding and subtracting",blurb:"Adding and subtracting numbers with five or more digits, lining up decimals correctly, and using estimates to check an answer makes sense."}
62,{label:"Multiplying and dividing",blurb:"Long multiplication and short division, plus factors, multiples, prime numbers, squares and cubes."},{label:"Fractions, decimals and percentages",blurb:"Adding fractions with different denominators, writing totals as mixed numbers, and turning fractions into percentages."},{label:"Angles and shapes",blurb:"Knowing a full turn is 360 degrees, finding missing angles on a straight line, and reflecting shapes in a mirror line."},{label:"Measuring",blurb:"Working out the area of rectangles, calculating how long a journey takes, and converting between grams and kilograms."},{label:"Graphs and data",blurb:"Reading and plotting line graphs, and describing what the data is doing over time."}],examples:[{id:"MD.P4.M04.Q106",presentation:"described",stem:"The child works through 34 × 26 in the long multiplication layout on screen, then types the answer.",answerLine:"Answer: 884",whyLine:"Long multiplication with two 2-digit numbers — the classic slip is treating the second row as 34 × 2 instead of 34 × 20 and losing the zero."},{id:"GEO.P5.M04.Q101",presentation:"choices",stem:"Two angles meet on a straight line. One is 125 degrees. What is the missing angle, in degrees?",options:["55 degrees","90 degrees","125 degrees","235 degrees"],answerLine:"Answer: 55 degrees",whyLine:"Angles on a straight line add up to 180 degrees — this catches children who use 360 instead, or who guess a right angle."},{id:"NPV.P6.M05.Q114",presentation:"described",stem:"The child drags four decimals — 0.2, 0.9, 0.5 and 0.7 — into order, from largest to smallest.",answerLine:"Answer: 0.9, 0.7, 0.5, 0.2",whyLine:"Ordering decimals shows whether a child really understands decimal size, rather than treating decimals like whole numbers."}],qas:[{q:"What maths should a 9 year old know?",a:"In Year 5 (ages 9–10), most children are working on long multiplication and short division, adding fractions with different denominators, decimals, percentages, and finding missing angles. They also read line graphs and handle very large numbers. Every child moves at their own pace, so it is normal to be secure in some topics while still building others."},{q:"How can I help my child with Year 5 maths at home?",a:"Little and often beats long sessions — ten calm minutes a few times a week makes a real difference. When something goes wrong, ask your child to talk through their thinking; most Year 5 slips come down to one small mix-up, like misaligning decimal places or losing the zero row in long multiplication. Practice that explains the mistake gently, rather than just marking it wrong, builds confidence as well as skill."},{q:"How does SmartMaths help with Year 5 maths?",a:"SmartMaths gives your child short, adaptive practice guided by the UK primary maths curriculum, and its AI tutor explains every mistake in plain words. You get a simple weekly picture of how they are doing, with no jargon. It does not replace school or mirror every school’s term-by-term plan — it builds steady confidence alongside it. The first sample round is free and needs no account."}],ctaLine:"See how it works with a free Year 5 sample round — no account needed."},6:{year:6,ages:"10–11",metaTitle:"Year 6 Maths Practice (Ages 10–11) | SmartMaths",metaDescription:"Calm, adaptive Year 6 maths practice for ages 10–11 — long division, ratio, angles and more. Try a free sample round, no account needed.",h1:"Year 6 maths practice",heroIntro:"Year 6 is the final step of primary maths — long division, ratio, missing angles, and fractions, decimals and percentages side by side. SmartMaths gives children aged 10–11 short, calm practice that adapts as they answer, guided by the UK primary maths curriculum. When a question goes wrong, the tutor explains the mistake in plain words — no coins, no noise.",topics:[{label:"Place value and negative numbers",blurb:"Reading and ordering decimals to three decimal places, multiplying and dividing by 10, 100 and 1000, and putting negative numbers in order."}
62,{label:"Adding and subtracting larger numbers",blurb:"Two-step and multi-step problems with bigger numbers, decimals and money — and choosing whether to work mentally, on paper, or estimate first as a check."},{label:"Long division and order of operations",blurb:"Dividing four-digit numbers by two-digit numbers with long division, using the correct order of operations (BODMAS), and solving multi-step problems."},{label:"Fractions, decimals and percentages",blurb:"Finding a percentage of an amount, comparing fractions with different denominators, and ordering fractions, decimals and percentages together."},{label:"Ratio and proportion",blurb:"Writing and simplifying ratios, sharing an amount in a given ratio, and using scale to work out real distances on maps and models."},{label:"Angles, area and volume",blurb:"Finding missing angles in triangles and around a point, naming quadrilaterals by their properties, and working out the area of triangles and the volume of cubes and cuboids."},{label:"Pie charts and averages",blurb:"Reading pie charts, finding the mean, mode and median, and using data to answer questions and make predictions."}],examples:[{id:"GEO.P6.M03.Q109",presentation:"choices",stem:"This triangle has angles of 85° and 60°. Which is the size of the missing angle?",options:["35°","95°","145°","215°"],answerLine:"Answer: 35°",whyLine:"It checks that the three angles of a triangle add up to 180° — the wrong answers catch children who add instead of subtracting, or who work from 360°."},{id:"RP.P3.M02.Q114",presentation:"choices",stem:"48 sweets are shared between red and blue in the ratio 5:3. What are the two amounts?",options:["30 red and 18 blue","18 red and 30 blue","24 red and 24 blue","5 red and 3 blue"],answerLine:"Answer: 30 red and 18 blue",whyLine:"Sharing in a ratio means splitting 48 into 5 + 3 = 8 equal parts first — a common mix-up is swapping the order or simply cutting the total in half."},{id:"MEA.P7.M05.Q110",presentation:"described",stem:"What is the volume of this cuboid, in cubic centimetres?",answerLine:"Answer: 60",whyLine:"The question shows a 5 × 4 × 3 cuboid and the child types the answer — volume is new in Year 6, and the common slip is adding the sides or working out only the area of one face."}],qas:[{q:"What maths should a 10 year old know?",a:"By Year 6, most children are working on long division, fractions, decimals and percentages side by side, ratio, missing angles, and averages like the mean and median. They also meet negative numbers and decimals to three decimal places. Every child moves at a different pace, so gaps in earlier topics such as times tables are normal — and worth revisiting calmly."},{q:"How can I help my child with Year 6 maths at home?",a:"Little and often beats long sessions — ten calm minutes a few times a week adds up. Ask your child to talk you through how they got an answer, right or wrong, because the reasoning matters more than the result. If a topic like long division or ratio keeps causing trouble, step back to the basics underneath it rather than repeating the same question."},{q:"How does SmartMaths help with Year 6 maths?",a:"SmartMaths gives short practice rounds that adapt to your child, guided by the UK primary maths curriculum, and the tutor explains every mistake in plain words. You get a simple weekly picture of what’s going well and what needs another look. It doesn’t replace school — it’s steady practice alongside it. The sample round is free and needs no account."}],ctaLine:"Try a free Year 6 sample round — five real questions, no account needed."}}
62;function YearLandingScreen({year:n,chrome:t,onTrySample:e,onOpenPricing:s}){const o=YEAR_LANDING_CONTENT[n];return reactExports.useEffect(()=>{o&&(document.title=o.metaTitle)},[o]),o?jsxRuntimeExports.jsx("div",{className:`sm-app ph-page ${styles$4.page}`,children:jsxRuntimeExports.jsxs("div",{className:"ph-wrap",children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:t}),jsxRuntimeExports.jsx("header",{className:"ph-hero ph-year-hero",children:jsxRuntimeExports.jsxs("div",{className:"ph-hero-copy",children:[jsxRuntimeExports.jsxs("span",{className:"ph-eyebrow",children:["Ages ",o.ages," · guided by the UK primary maths curriculum"]}),jsxRuntimeExports.jsx("h1",{children:o.h1}),jsxRuntimeExports.jsx("p",{className:"ph-hero-sub",children:o.heroIntro}),jsxRuntimeExports.jsxs("div",{className:"ph-cta-row",children:[jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-btn sm-btn-accent sm-btn-large",onClick:e,children:["Try a free Year ",n," sample round"]}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-btn sm-btn-soft sm-btn-large",onClick:s,children:"See pricing"})]}),jsxRuntimeExports.jsx("p",{className:"ph-reassure",children:"No account needed for the sample. Free for 14 days after that."})]})}),jsxRuntimeExports.jsxs("section",{className:"ph-section",children:[jsxRuntimeExports.jsx("div",{className:"ph-section-head",children:jsxRuntimeExports.jsxs("h2",{children:["What Year ",n," maths covers"]})}),jsxRuntimeExports.jsx("div",{className:"ph-grid-3 ph-year-topics",children:o.topics.map(r=>jsxRuntimeExports.jsxs("div",{className:"ph-card",children:[jsxRuntimeExports.jsx("h3",{children:r.label}),jsxRuntimeExports.jsx("p",{children:r.blurb})]},r.label))})]}),jsxRuntimeExports.jsxs("section",{className:"ph-section alt",children:[jsxRuntimeExports.jsxs("div",{className:"ph-section-head",children:[jsxRuntimeExports.jsxs("h2",{children:["Real questions from our Year ",n," sample round"]}),jsxRuntimeExports.jsx("p",{children:"Every question below is one your child can meet in the free sample — with the tutor ready to show how it works, step by step, if it goes wrong."})]}),jsxRuntimeExports.jsx("div",{className:"ph-year-examples",children:o.examples.map(r=>jsxRuntimeExports.jsxs("article",{className:"ph-card ph-year-example",children:[jsxRuntimeExports.jsx("p",{className:"ph-year-example-stem",children:r.stem}),r.visualNote&&jsxRuntimeExports.jsx("p",{className:"ph-year-example-visual",children:r.visualNote}),r.presentation==="choices"&&r.options&&jsxRuntimeExports.jsx("ul",{className:"ph-year-example-options",children:r.options.map(a=>jsxRuntimeExports.jsx("li",{children:a},a))}),jsxRuntimeExports.jsxs("details",{className:"ph-year-example-reveal",children:[jsxRuntimeExports.jsx("summary",{children:"Show the answer"}),jsxRuntimeExports.jsx("p",{className:"ph-year-example-answer",children:r.answerLine}),jsxRuntimeExports.jsx("p",{className:"ph-year-example-why",children:r.whyLine})]})]},r.id))})]}),jsxRuntimeExports.jsxs("section",{className:"ph-section",children:[jsxRuntimeExports.jsx("div",{className:"ph-section-head",children:jsxRuntimeExports.jsxs("h2",{children:["Questions parents ask about Year ",n," maths"]})}),jsxRuntimeExports.jsx("div",{className:"ph-year-qas",children:o.qas.map(r=>jsxRuntimeExports.jsxs("article",{className:"ph-card ph-year-qa",children:[jsxRuntimeExports.jsx("h3",{children:r.q}),jsxRuntimeExports.jsx("p",{children:r.a})]},r.q))})]}),jsxRuntimeExports.jsxs("section",{className:"ph-section ph-year-cta",children:[jsxRuntimeExports.jsx("h2",{children:o.ctaLine}),jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-btn sm-btn-accent sm-btn-large",onClick:e,children:["Try a free Year ",n," sample round"]})]}),jsxRuntimeExports.jsx(SiteFooter,{chrome:t})]})}):null}const LEGAL_VIEWS=[{id:"privacy",label:"Parents’ privacy"},{id:"terms",label:"Terms"},{id:"child",label:"For children"},{id:"plans",label:"Plan facts"},{id:"cancellation",label:"Cancel & refunds"}],LEGAL_HEADINGS={privacy:{title:"Privacy for your family",intro:"What SmartMaths uses, why, who helps us, and the choices your family has."},terms:{title:"Terms for families",intro:"The plain-English rules for accounts, subscriptions and using SmartMaths."},child:{title:"Your privacy at SmartMaths",intro:"A short, child-friendly page about what the app remembers and why."},plans:{title:"Subscription plan facts",intro:"Prices, trial, renewal and the Founding offer in one place."},cancellation:{title:"Cancel or ask for a refund",intro:"How to stop renewal, use cancellation rights, or ask about a refund."}};function LegalScreen({chrome:n,onBack:t,initialView:e="privacy",onViewChange:s}){const[o,r]=reactExports.useState(e);reactExports.useEffect(()=>{r(e)},[e]);const a=LEGAL_HEADINGS[o],i=o==="child"?"":styles$4.page;return jsxRuntimeExports.jsxs("section",{className:`screen screen-enter marketing-screen ${i}`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"legal"}),jsxRuntimeExports.jsxs("header",{className:"card marketing-hero",children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"Legal and safeguarding"}),jsxRuntimeExports.jsx("h1",{children:a.title}),jsxRuntimeExports.jsx("p",{className:"subtle",children:a.intro}),jsxRuntimeExports.jsx("button",{className:"btn btn-soft",type:"button",onClick:t,children:"← Back"})]}),jsxRuntimeExports.jsx("nav",{className:"card sm-legal-nav","aria-label":"Legal pages",children:LEGAL_VIEWS.map(l
62=>jsxRuntimeExports.jsx("button",{className:`btn ${o===l.id?"btn-primary":"btn-soft"}`,type:"button","aria-current":o===l.id?"page":void 0,onClick:()=>{r(l.id),s?.(l.id)},children:l.label},l.id))}),jsxRuntimeExports.jsx("section",{className:"card sm-legal-card","aria-label":a.title,children:jsxRuntimeExports.jsx(LegalContent,{view:o})}),jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}const MAX_ATTEMPTS_PER_QUESTION=2,BINARY_MAX_POINTS=.5,HINT_PENALTY=.5;function isBinaryQuestion(n){return n.mathematicalForm==="true_false_check"?!0:Array.isArray(n.options)&&n.options.length===2}function getMaxAttempts(n){return isBinaryQuestion(n)?1:MAX_ATTEMPTS_PER_QUESTION}function getPointsForCorrect(n,t,e){return isBinaryQuestion(n)?t!==1?0:e>0?BINARY_MAX_POINTS*HINT_PENALTY:BINARY_MAX_POINTS:t===1?1:t===2?.5:0}function normaliseAnswer(n){return n.trim().toLowerCase().replace(/^\(\s*|\s*\)$/g,"").replace(/\s*,\s*/g,",").trim()}function isAnswerCorrect(n,t){const e=normaliseAnswer(n);return(t.acceptedAnswers?.length?t.acceptedAnswers:[t.correctAnswer]).some(o=>normaliseAnswer(o)===e)}function canUseSpeechSynthesis(){return typeof window<"u"&&"speechSynthesis"in window&&"SpeechSynthesisUtterance"in window}function scoreSpeechVoice(n){const t=n.lang.toLowerCase(),e=n.name.toLowerCase();let s=0;return t.startsWith("en-gb")?s+=60:t.startsWith("en-au")?s+=52:t.startsWith("en-us")?s+=46:t.startsWith("en")&&(s+=35),/natural|neural|enhanced|premium|siri|google uk english|microsoft|ava|daniel|serena|libby|ryan|aria/.test(e)&&(s+=34),/(female|woman|ava|serena|libby)/.test(e)&&(s+=6),/(compact|espeak|festival|mbrola|robot|default)/.test(e)&&(s-=40),n.localService||(s+=8),n.default&&(s+=3),s}function getPreferredSpeechVoice(){if(!canUseSpeechSynthesis())return null;const n=window.speechSynthesis.getVoices();return n.length===0?null:[...n].filter(e=>e.lang.toLowerCase().startsWith("en")).sort((e,s)=>scoreSpeechVoice(s)-scoreSpeechVoice(e))[0]??null}function normaliseStem$1(n){return n.replace(/\bbox\b/gi,"__")}const USE_AZURE_TTS="true".toLowerCase()==="true",AZURE_VOICE="en-GB-BellaNeural";function canUseSpokenAudio(){return USE_AZURE_TTS||canUseSpeechSynthesis()}let activeSpeech=null;function stopSpeaking(){const n=activeSpeech;activeSpeech=null,n?.stop(),canUseSpeechSynthesis()&&window.speechSynthesis.cancel()}function prefetchSpeech(n,t={}){if(!USE_AZURE_TTS)return;const e=n.trim();if(!e)return;const{rate:s=.9,pitch:o=1.02}=t;buildApiAuthHeaders().then(r=>fetch("/api/tts",{method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify({text:e,voice:AZURE_VOICE,rate:s,pitch:o})})).then(r=>r.ok?r.json():null).then(r=>{if(!r?.url)return;const a=new Audio;a.preload="auto",a.src=r.url,a.load()}).catch(()=>{})}function speak(n,t={}){const{rate:e=.9,pitch:s=1.02,onStart:o,onEnd:r,onStop:a,onError:i}=t;if(stopSpeaking(),!n.trim())return r?.(),{cancel:()=>{}};let l=!1,u=null,d=null;const m=()=>{l=!0,d&&d.abort(),u&&(u.pause(),u.src="",u=null)},h={stop:()=>{l||(m(),a?.())}};activeSpeech=h;const f=()=>{activeSpeech===h&&(activeSpeech=null)};function g(){if(l)return;if(!canUseSpeechSynthesis()){typeof window<"u"&&window.dispatchEvent(new CustomEvent("smartmaths:tts-silent")),f(),i?.();return}const y=window.speechSynthesis;y.cancel();const E=new SpeechSynthesisUtterance(n);E.rate=e,E.pitch=s,E.volume=1;const k=getPreferredSpeechVoice();k&&(E.voice=k),E.onstart=()=>{l||o?.()},E.onend=()=>{l||(f(),r?.())},E.onerror=()=>{l||(f(),i?.())},y.speak(E)}const b=()=>{l||(m(),activeSpeech===h&&(activeSpeech=null,canUseSpeechSynthesis()&&window.speechSynthesis.cancel()))};if(!USE_AZURE_TTS)return g(),{cancel:b};const x=new AbortController;return d=x,buildApiAuthHeaders().then(y=>fetch("/api/tts",{method:"POST",headers:{"Content-Type":"application/json",...y},body:JSON.stringify({text:n,voice:AZURE_VOICE,rate:e,pitch:s}),signal:x.signal})).then(y=>y.ok?y.json():Promise.reject(new Error(`http_${y.status}`))).then(y=>{if(!l){if(!y.url){g();return}u=new Audio(y.url),u.onplay=()=>{l||o?.()},u.onended=()=>{l||(f(),r?.(),u=null)},u.onerror=()=>{l||(u=null,g())},u.play().catch(()=>{l||g()})}}).catch(()=>{l||g()}),{cancel:b}}const FRACTION_DENOMINATORS={2:["half","halves"],3:["third","thirds"],4:["quarter","quarters"],5:["f
62ifth","fifths"],6:["sixth","sixths"],7:["seventh","sevenths"],8:["eighth","eighths"],9:["ninth","ninths"],10:["tenth","tenths"],12:["twelfth","twelfths"],20:["twentieth","twentieths"],25:["twenty-fifth","twenty-fifths"],50:["fiftieth","fiftieths"],100:["hundredth","hundredths"],1e3:["thousandth","thousandths"]},FRACTION_NUMERATORS=["zero","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve"];function spokenFractions(n){return n.replace(/\b(\d{1,4})\/(\d{1,4})\b/g,(t,e,s)=>{const o=FRACTION_DENOMINATORS[s];if(!o)return`${e} over ${s}`;const r=Number(e);return`${FRACTION_NUMERATORS[r]??e} ${r===1?o[0]:o[1]}`})}function toSpeechFriendlyText(n){return spokenFractions(n).replace(/\bbox\b/gi,"__").replace(/\s*=\s*__\s*([.?!])?\s*$/i," equals what is the answer$1").replace(/\s*([+\-−*/=×÷])\s*/g,(e,s,o,r)=>{if(s==="-"&&/^\s*-\d/.test(e))return e;if(s==="-"&&e==="-"){const a=r[o-1]??"",i=r[o+1]??"";if(/[A-Za-z]/.test(a)&&/[A-Za-z]/.test(i))return e}return s==="+"?" plus ":s==="-"||s==="−"?" minus ":s==="*"||s==="×"?" times ":s==="/"||s==="÷"?" divided by ":" equals "}).replace(/__/g,"blank").replace(/£\s*(\d+)(?:\.(\d{1,2}))?/g,(e,s,o)=>{const r=o?o.padEnd(2,"0"):"00";return r==="00"?`${s} pounds`:`${s} pounds and ${Number(r)} pence`}).replace(/\b(\d+)p\b/gi,(e,s)=>`${s} pence`).replace(/[▶✅💡🎉]/g,"").replace(/\s+/g," ").trim()}function readsExplanationsAloud(n){return!(Number(/^Year (\d)$/.exec(n.trim())?.[1])>=3)}const schema_version="uk_primary_maths_content_spec_v1",generated_utc="2026-02-19T21:43:47Z",notes=["Derived from uploaded KS1–KS2 worksheet photo sets; designed for adaptive practice without fixed progression.","Student progress data should be stored separately from this content spec."],skills$1=JSON.parse(`[{"id":"NPV_COUNT_FWD_20","strand":"Number & Place Value","key_stage":"KS1","year_band":"Y1–2","description":"Count forwards within 20.","prereq_ids":[],"synonyms":[]},{"id":"NPV_COUNT_BWD_20","strand":"Number & Place Value","key_stage":"KS1","year_band":"Y1–2","description":"Count backwards within 20.","prereq_ids":[],"synonyms":[]},{"id":"NPV_COUNT_FWD_100","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Count forwards within 100.","prereq_ids":[],"synonyms":[]},{"id":"NPV_COUNT_BWD_100","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Count backwards within 100.","prereq_ids":[],"synonyms":[]},{"id":"NPV_COUNT_FROM_GIVEN","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Count forwards/backwards starting from any given number.","prereq_ids":[],"synonyms":[]},{"id":"NPV_SKIP_2","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Count in steps of 2 (skip counting).","prereq_ids":[],"synonyms":[]},{"id":"NPV_SKIP_5","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Count in steps of 5 (skip counting).","prereq_ids":[],"synonyms":[]},{"id":"NPV_SKIP_10","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Count in steps of 10 (skip counting).","prereq_ids":[],"synonyms":[]},{"id":"NPV_SEQUENCE_STEP_1","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Complete sequences with step size 1.","prereq_ids":[],"synonyms":[]},{"id":"NPV_SEQUENCE_STEP_2","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Complete sequences with step size 2.","prereq_ids":[],"synonyms":[]},{"id":"NPV_SEQUENCE_STEP_5","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Complete sequences with step size 5.","prereq_ids":[],"synonyms":[]},{"id":"NPV_SEQUENCE_STEP_10","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Complete sequences with step size 10.","prereq_ids":[],"synonyms":[]},{"id":"NPV_SEQUENCE_UNKNOWN_STEP","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Infer and use an unknown step 
62size in a sequence.","prereq_ids":[],"synonyms":[]},{"id":"NPV_READ_WRITE_20","strand":"Number & Place Value","key_stage":"KS1","year_band":"Y1–2","description":"Read and write numerals to 20.","prereq_ids":[],"synonyms":[]},{"id":"NPV_READ_WRITE_100","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Read and write numerals to 100.","prereq_ids":[],"synonyms":[]},{"id":"NPV_COMPARE_20","strand":"Number & Place Value","key_stage":"KS1","year_band":"Y1–2","description":"Compare numbers within 20 using <, >, = and language.","prereq_ids":[],"synonyms":[]},{"id":"NPV_COMPARE_100","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Compare numbers within 100 using <, >, = and language.","prereq_ids":[],"synonyms":[]},{"id":"NPV_ORDER_20","strand":"Number & Place Value","key_stage":"KS1","year_band":"Y1–2","description":"Order numbers within 20.","prereq_ids":[],"synonyms":[]},{"id":"NPV_ORDER_100","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Order numbers within 100.","prereq_ids":[],"synonyms":[]},{"id":"NPV_ONE_MORE","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Find one more than a given number.","prereq_ids":[],"synonyms":[]},{"id":"NPV_ONE_LESS","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Find one less than a given number.","prereq_ids":[],"synonyms":[]},{"id":"NPV_MOST_LEAST","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Identify the greatest/least in a set.","prereq_ids":[],"synonyms":[]},{"id":"NPV_TENS_ONES_TEENS","strand":"Number & Place Value","key_stage":"KS1","year_band":"Y1–2","description":"Partition teen numbers into 10 and ones (e.g., 14=10+4).","prereq_ids":[],"synonyms":[]},{"id":"NPV_TENS_ONES_2DIGIT","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Interpret 2-digit numbers as tens and ones (e.g., 34 is 3 tens and 4 ones).","prereq_ids":[],"synonyms":[]},{"id":"NPV_PARTITION_2DIGIT","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Partition and recombine 2-digit numbers into tens/ones.","prereq_ids":[],"synonyms":[]},{"id":"NPV_NUMBERLINE_SCALE_READ","strand":"Number & Place Value","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Read number lines where tick intervals may be >1.","prereq_ids":[],"synonyms":[]},{"id":"AS_ADD_COMBINE","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Understand addition as combining quantities.","prereq_ids":[],"synonyms":[]},{"id":"AS_ADD_INCREASE","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Understand addition as an increase/change.","prereq_ids":[],"synonyms":[]},{"id":"AS_SUB_TAKEAWAY","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Understand subtraction as taking away.","prereq_ids":[],"synonyms":[]},{"id":"AS_SUB_DIFFERENCE","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Understand subtraction as finding the difference (compare).","prereq_ids":[],"synonyms":[]},{"id":"AS_EQUALS_EQUIVALENCE","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Understand '=' means both sides have the same value.","prereq_ids":[],"synonyms":[]},{"id":"AS_BONDS_10","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Recall and use number bonds to 10.","prereq_ids":[],"synonyms":[]},{"id":"AS_BONDS_20","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Use bonds and related facts within 20.","prereq_ids":[],"synonyms":[]},{"id":"AS_DOUBLES","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Recall and use doubles facts.","prereq_ids":[],"synonyms":[]},{"id":"AS_NEAR_DOUBLES","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Use near-doubles to calculate.","prereq_ids":[],"synonyms":[]},{"id":"AS_FACT_RECALL_20","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Recall basic addition/subtraction facts within 20.","prereq_ids":[],"synonyms":[]},{"id":"AS_COUNT_ON","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Add by counting on from the larger number.","prereq_ids":[],"synonyms":[]},{"id":"AS_COUNT_BACK","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Subtract by counting back.","prereq_ids":[],"synonyms":[]},{"id":"AS_BRIDGE_10_ADD","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Add across 10 using make-10/partition strategies.","prereq_ids":[],"synonyms":[]},{"id":"AS_BRIDGE_10_SUB","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Subtract across 10 using bridging strategies.","prereq_ids":[],"synonyms":[]},{"id":"AS_PARTITION_ADD","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Partition addends to make calculation easier.","prereq_ids":[],"synonyms":[]},{"id":"AS_COMPENSATE","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Use compensation (e.g., +9 as +10−1).","prereq_ids":[],"synonyms":[]},{"id":"AS_MISSING_ADDEND","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Solve addition equations with a missing addend.","prereq_ids":["AS_EQUALS_EQUIVALENCE"],"synonyms":[]},{"id":"AS_MISSING_SUBTRAHEND","strand":"Addition & Subtraction","key_stage":"KS1","year_band":"Y1–2","description":"Solve subtraction equations with a missing subtrahend.","prereq_ids":["AS_EQUALS_EQUIVALENCE"],"synonyms":[]},{"id":"AS_MISSING_MINUEND","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Solve subtraction equations with a missing minuend.","prereq_ids":["AS_EQUALS_EQUIVALENCE"],"synonyms":[]},{"id":"AS_TRUE_FALSE_EQUATION","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Decide if an equation is true/false and correct it.","prereq_ids":["AS_EQUALS_EQUIVALENCE"],"synonyms":[]},{"id":"AS_EQUATION_BALANCE","strand":"Addition & Subtraction","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Solve equations requiring balance across both sides.","prereq_ids":["AS_EQUALS_EQUIVALENCE"],"synonyms":[]},{"id":"MD_EQUAL_GROUPS","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Recognise and make equal groups.","prereq_ids":[],"synonyms":[]},{"id":"MD_REPEATED_ADDITION","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y2–3","description":"Interpret repeated addition as grouping.","prereq_ids":[],"synonyms":[]},{"id":"MD_SHARE_EQUALLY","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Share objects equally into groups.","prereq_ids":[],"synonyms":[]},{"id":"MD_GROUPING","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Group objects to support multiplication/division ideas.","prereq_ids":[],"synonyms":[]},{"id":"MD_DOUBLE","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Double a quantity (multiply by 2).","prereq_ids":[],"synonyms":[]}
62,{"id":"MD_HALF","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Find half of a quantity (divide by 2).","prereq_ids":[],"synonyms":[]},{"id":"MD_ODD_EVEN_PARITY","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y2–3","description":"Identify and justify even/odd numbers.","prereq_ids":[],"synonyms":[]},{"id":"MD_TIMES_TABLES_2_5_10","strand":"Multiplication & Division","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Count and use 2, 5, 10 times table patterns.","prereq_ids":[],"synonyms":[]},{"id":"FR_EQUAL_PARTS_CONCEPT","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Understand fractions as equal parts of a whole.","prereq_ids":["MD_DOUBLE","MD_HALF"],"synonyms":[]},{"id":"FR_HALF_SHAPE","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Recognise and find half of a shape.","prereq_ids":["FR_EQUAL_PARTS_CONCEPT"],"synonyms":[]},{"id":"FR_HALF_QUANTITY","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Find half of a set/quantity.","prereq_ids":["FR_EQUAL_PARTS_CONCEPT"],"synonyms":[]},{"id":"FR_QUARTER_SHAPE","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Recognise and find a quarter of a shape.","prereq_ids":["FR_EQUAL_PARTS_CONCEPT"],"synonyms":[]},{"id":"FR_QUARTER_QUANTITY","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y2–3","description":"Find a quarter of a set/quantity.","prereq_ids":["FR_EQUAL_PARTS_CONCEPT"],"synonyms":[]},{"id":"FR_FIND_WHOLE_FROM_HALF","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y3–4","description":"Find the whole when given half (inverse of halving).","prereq_ids":["MD_DOUBLE","MD_HALF"],"synonyms":[]},{"id":"FR_HALVE_ODD_CONTEXT","strand":"Fractions","key_stage":"KS1/KS2","year_band":"Y3–4","description":"Handle halving odd numbers appropriately to context (e.g., 7 -> 3½).","prereq_ids":["MD_DOUBLE","MD_HALF"],"synonyms":[]},{"id":"ME_MONEY_COIN_RECOG","strand":"Measurement","key_stage":"KS1","year_band":"Y1–2","description":"Recognise UK coins and their values.","prereq_ids":[],"synonyms":[]},{"id":"ME_MONEY_COUNT_TOTALS_P","strand":"Measurement","key_stage":"KS1","year_band":"Y1–2","description":"Find total value of coins in pence.","prereq_ids":[],"synonyms":[]},{"id":"ME_MONEY_MAKE_AMOUNT","strand":"Measurement","key_stage":"KS1/KS2","year_band":"Y2–3","description":"Make a given amount using different coin combinations.","prereq_ids":[],"synonyms":[]},{"id":"ME_MONEY_SIMPLE_CHANGE","strand":"Measurement","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Find simple change in money contexts.","prereq_ids":[],"synonyms":[]},{"id":"ME_TIME_OOCLOCK","strand":"Measurement","key_stage":"KS1","year_band":"Y1–2","description":"Tell time to o’clock.","prereq_ids":[],"synonyms":[]},{"id":"ME_TIME_HALFPAST","strand":"Measurement","key_stage":"KS1","year_band":"Y1–2","description":"Tell time to half past.","prereq_ids":[],"synonyms":[]},{"id":"ME_TIME_SEQUENCE_VOCAB","strand":"Measurement","key_stage":"KS1","year_band":"Y1–2","description":"Use time vocabulary and sequence events.","prereq_ids":[],"synonyms":[]},{"id":"GEO_2D_NAME_BASIC","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Name common 2D shapes.","prereq_ids":[],"synonyms":[]},{"id":"GEO_3D_NAME_BASIC","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Name common 3D shapes.","prereq_ids":[],"synonyms":[]},{"id":"GEO_2D_SIDES_VERTICES","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Describe 2D shapes using sides and vertices.","prereq_ids":[],"synonyms":[]},{"id":"GEO_3D_FACES_EDGES_VERTICES","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Describe 3D shapes using faces, edges and vertices.","prereq_ids":[],"synonyms":[]},{"id":"GEO_SORT_BY_PROPERTY","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y1–3","description":"Sort/classify shapes by a given property.","prereq_ids":[],"synonyms":[]},{"id":"GEO_ODD_ONE_OUT_SHAPES","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Identify which shape doesn’t belong and justify.","prereq_ids":[],"synonyms":[]},{"id":"GEO_SYMMETRY_LINE_IDENTIFY","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Identify a line of symmetry in simple shapes.","prereq_ids":[],"synonyms":[]},{"id":"GEO_SYMMETRY_COMPLETE_HALF","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Complete the missing half of a symmetrical figure.","prereq_ids":[],"synonyms":[]},{"id":"GEO_REFLECTION_DISTANCE_CHECK","strand":"Geometry","key_stage":"KS1/KS2","year_band":"Y3–4","description":"Check reflections by equal distance from the symmetry line.","prereq_ids":[],"synonyms":[]},{"id":"POS_DIR_LEFT_RIGHT","strand":"Geometry (Position & Direction)","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Use left and right language.","prereq_ids":[],"synonyms":[]},{"id":"POS_DIR_TURNS_QTR_HALF_FULL","strand":"Geometry (Position & Direction)","key_stage":"KS1/KS2","year_band":"Y2–3","description":"Use quarter/half/full turns.","prereq_ids":[],"synonyms":[]},{"id":"POS_DIR_FWD_BWD_UP_DOWN","strand":"Geometry (Position & Direction)","key_stage":"KS1/KS2","year_band":"Y1–2","description":"Describe movement forwards/backwards/up/down.","prereq_ids":[],"synonyms":[]},{"id":"POS_DIR_GRID_LOCATE","strand":"Geometry (Position & Direction)","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Locate objects on a grid (rows/columns).","prereq_ids":[],"synonyms":[]}
62,{"id":"POS_DIR_GRID_MOVES","strand":"Geometry (Position & Direction)","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Move on a grid by counting steps.","prereq_ids":[],"synonyms":[]},{"id":"POS_DIR_MULTI_STEP_ROUTE","strand":"Geometry (Position & Direction)","key_stage":"KS1/KS2","year_band":"Y2–4","description":"Follow/plan multi-step routes using directions.","prereq_ids":[],"synonyms":[]},{"id":"META_READ_MATHS_VOCAB","strand":"Cross-cutting","key_stage":"KS1/KS2","year_band":"Y1–6","description":"Interpret common maths vocabulary in questions.","prereq_ids":[],"synonyms":[]},{"id":"META_CHOOSE_STRATEGY","strand":"Cross-cutting","key_stage":"KS1/KS2","year_band":"Y1–6","description":"Choose an efficient strategy rather than counting all.","prereq_ids":[],"synonyms":[]},{"id":"META_CHECK_REASONABLE","strand":"Cross-cutting","key_stage":"KS1/KS2","year_band":"Y1–6","description":"Check answers for reasonableness.","prereq_ids":[],"synonyms":[]},{"id":"META_EXPLAIN_METHOD","strand":"Cross-cutting","key_stage":"KS1/KS2","year_band":"Y1–6","description":"Explain or justify a method used.","prereq_ids":[],"synonyms":[]}]`),exercise_types=[{id:"E01",name:"Missing number equation",interaction:"type_or_tap_blank",description:"",assesses_skill_ids:["AS_EQUALS_EQUIVALENCE","AS_MISSING_ADDEND","AS_MISSING_SUBTRAHEND","AS_MISSING_MINUEND","AS_FACT_RECALL_20"],diagnoses_misconception_ids:["MIS_EQ_OPERATIONAL","MIS_SUB_DIRECTION"]},{id:"E02",name:"Rapid facts grid",interaction:"type_many_fast",description:"",assesses_skill_ids:["AS_FACT_RECALL_20","AS_BONDS_10","AS_DOUBLES","AS_NEAR_DOUBLES"],diagnoses_misconception_ids:["MIS_COUNT_SKIP_DOUBLE","MIS_LOSE_REMAINDER"]},{id:"E03",name:"Bridge-10 builder",interaction:"drag_split_to_make_10",description:"",assesses_skill_ids:["AS_BRIDGE_10_ADD","AS_PARTITION_ADD","AS_BONDS_10","META_CHOOSE_STRATEGY"],diagnoses_misconception_ids:["MIS_LOSE_REMAINDER","MIS_BAD_PARTITION"]},{id:"E04",name:"Take-away objects",interaction:"remove_objects_then_count",description:"",assesses_skill_ids:["AS_SUB_TAKEAWAY","AS_COUNT_BACK"],diagnoses_misconception_ids:["MIS_COUNT_SKIP_DOUBLE","MIS_SUB_DIRECTION"]},{id:"E05",name:"Difference/compare",interaction:"compare_two_sets_answer_difference",description:"",assesses_skill_ids:["AS_SUB_DIFFERENCE","META_READ_MATHS_VOCAB"],diagnoses_misconception_ids:["MIS_KEYWORD_TRAP","MIS_SUB_DIRECTION"]},{id:"E06",name:"Number line missing labels",interaction:"fill_missing_ticks",description:"",assesses_skill_ids:["NPV_NUMBERLINE_SCALE_READ","NPV_SEQUENCE_UNKNOWN_STEP"],diagnoses_misconception_ids:["MIS_ASSUME_STEP_1"]},{id:"E07",name:"Number line hop",interaction:"animate_jumps_enter_landing",description:"",assesses_skill_ids:["AS_COUNT_ON","AS_COUNT_BACK","NPV_COUNT_FROM_GIVEN"],diagnoses_misconception_ids:["MIS_OFF_BY_ONE_MOVES"]},{id:"E08",name:"Sequence completion",interaction:"fill_blanks_in_sequence",description:"",assesses_skill_ids:["NPV_SEQUENCE_STEP_1","NPV_SEQUENCE_STEP_2","NPV_SEQUENCE_STEP_5","NPV_SEQUENCE_STEP_10","NPV_SEQUENCE_UNKNOWN_STEP"],diagnoses_misconception_ids:["MIS_COUNT_SKIP_DOUBLE"]},{id:"E09",name:"Part–whole model",interaction:"drag_parts_or_type_missing_part",description:"",assesses_skill_ids:["AS_BONDS_10","AS_BONDS_20","AS_MISSING_ADDEND","AS_SUB_TAKEAWAY"],diagnoses_misconception_ids:["MIS_PART_WHOLE_CONFUSION"]},{id:"E10",name:"Even/odd classifier",interaction:"pair_objects_then_label",description:"",assesses_skill_ids:["MD_ODD_EVEN_PARITY","MD_EQUAL_GROUPS"],diagnoses_misconception_ids:["MIS_COUNT_SKIP_DOUBLE"]},{id:"E11",name:"Halving/share",interaction:"split_into_two_equal_groups",description:"",assesses_skill_ids:["MD_HALF","MD_SHARE_EQUALLY","FR_EQUAL_PARTS_CONCEPT"],diagnoses_misconception_ids:["MIS_UNEQUAL_HALVES","MIS_ODD_CANT_HALF"]},{id:"E12",name:"Find whole from half",interaction:"type_whole_given_half",description:"",assesses_skill_ids:["FR_FIND_WHOLE_FROM_HALF","MD_DOUBLE"],diagnoses_misconception_ids:["MIS_BAD_PARTITION","MIS_KEYWORD_TRAP"]},{id:"E13",name:"Coin totals",interaction:"tap_coins_add_running_total",description:"",assesses_skill_ids:["ME_MONEY_COIN_RECOG","ME_MONEY_COUNT_TOTALS_P","AS_ADD_COMBINE"],diagnoses_misconception_ids:["MIS_COIN_FACE_VALUE","MIS_DOUBLE_COUNT"]},{id:"E14",name:"Shape sort / odd-one-out",interaction:"drag_sort_explain_rule",description:"",assesses_skill_ids:["GEO_SORT_BY_PROPERTY","GEO_2D_SIDES_VERTICES","GEO_3D_FACES_EDGES_VERTICES"],diagnoses_misconception_ids:["MIS_SHAPE_BY_ORIENTATION"]},{id:"E15",name:"Symmetry mirror completion",interaction:"place_reflected_points_or_blocks",de
62scription:"",assesses_skill_ids:["GEO_SYMMETRY_COMPLETE_HALF","GEO_REFLECTION_DISTANCE_CHECK"],diagnoses_misconception_ids:["MIS_TRANSLATE_NOT_REFLECT"]}],generators=[{id:"T01",exercise_type_id:"E01",forms:["A + □ = C","□ + B = C","C − □ = A","□ − B = A"],params_schema:{range_max:{type:"int",enum:[10,20,50,100]},allow_bridge10:{type:"bool"},blank_position:{type:"string",enum:["left","middle"]},allow_negative:{type:"bool",default:!1}},constraints:["Single integer solution within [0, range_max].","If allow_negative=false, ensure results are non-negative."],expected_methods:["fact_recall","count_on_back","inverse_check"],diagnostic_hooks:[{if_wrong_answer_pattern:"answer_equals_C_frequently",tag:"MIS_EQ_OPERATIONAL"},{if_wrong_answer_pattern:"swap_subtraction_order",tag:"MIS_SUB_DIRECTION"}]},{id:"T02",exercise_type_id:"E02",forms:["grid_of_equations"],params_schema:{ops:{type:"string",enum:["add","sub","mixed"]},count:{type:"int",min:10,max:60},range_max:{type:"int",enum:[10,20]},focus:{type:"string",enum:["bonds10","doubles","near_doubles","random"]}},constraints:["Control item distribution by focus.","Avoid negative subtraction for KS1 defaults."],expected_methods:["fact_recall","bridge10","doubles"],diagnostic_hooks:[{if:"slow_but_accurate",note:"fluency low, strategy ok"},{if:"fast_wrong_on_bridge_items",tag:"MIS_LOSE_REMAINDER"}]},{id:"T03",exercise_type_id:"E03",forms:["bridge10_addition"],params_schema:{a:{type:"int",min:6,max:9},b:{type:"int",min:6,max:9},visual:{type:"string",enum:["ten_frame","blocks","numberline"]}},constraints:["Ensure (10-a) <= b so bridging is meaningful."],expected_methods:["make10_partition"],diagnostic_hooks:[{if_wrong:"uses_10_plus_b",tag:"MIS_LOSE_REMAINDER"},{if_wrong:"invalid_partition",tag:"MIS_BAD_PARTITION"}]},{id:"T04",exercise_type_id:"E04",forms:["start_n_remove_n_objects"],params_schema:{start_n:{type:"int",min:1,max:20},remove_n:{type:"int",min:0,max:"start_n"},representation:{type:"string",enum:["dots","counters","pictures"]}},constraints:["remove_n <= start_n","Result >= 0"],expected_methods:["count_back","count_remaining"],diagnostic_hooks:[{if_wrong:"removes_wrong_quantity",tag:"MIS_COUNT_SKIP_DOUBLE"}]},{id:"T05",exercise_type_id:"E05",forms:["compare_two_quantities_difference"],params_schema:{bigger:{type:"int",min:1,max:50},smaller:{type:"int",min:0,max:"bigger"},context:{type:"string",enum:["stickers","apples","blocks","points"]},representation:{type:"string",enum:["bars","two_sets"]}},constraints:["bigger >= smaller"],expected_methods:["count_up_difference","subtraction_difference"],diagnostic_hooks:[{if_wrong:"adds_instead_of_diffs",tag:"MIS_KEYWORD_TRAP"}]},{id:"T06",exercise_type_id:"E06",forms:["numberline_with_blanks"],params_schema:{start:{type:"int",min:0,max:100},step:{type:"int",enum:[1,2,5,10]},num_ticks:{type:"int",min:5,max:12},blanks_count:{type:"int",min:1,max:6}},constraints:["Ticks are equally spaced; labels follow start + k*step."],expected_methods:["infer_step","skip_count"],diagnostic_hooks:[{if_wrong:"fills_consecutive_integers",tag:"MIS_ASSUME_STEP_1"}]},{id:"T07",exercise_type_id:"E07",forms:["numberline_hops"],params_schema:{start:{type:"int",min:0,max:100},jumps:{type:"int",min:1,max:10},jump_size:{type:"int",enum:[1,2,5,10]},direction:{type:"string",enum:["forward","backward"]}},constraints:["If direction=backward ensure landing >=0 in KS1 default."],expected_methods:["count_jumps","skip_count"],diagnostic_hooks:[{if_wrong:"off_by_one_jumps",tag:"MIS_OFF_BY_ONE_MOVES"}]},{id:"T08",exercise_type_id:"E08",forms:["sequence_with_missing_terms"],params_schema:{pattern:{type:"string",enum:["step1","step2","step5","step10","unknown_step"]},length:{type:"int",min:5,max:12},missing_positions:{type:"array",items:{type:"int"}}},constraints:["Missing positions unique and within sequence length."],expected_methods:["infer_step_then_fill"],diagnostic_hooks:[{if_wrong:"drifts_later_terms",tag:"MIS_COUNT_SKIP_DOUBLE"}]},{id:"T09",exercise_type_id:"E09",forms:["part_part_whole","whole_minus_part"],params_schema:{whole_max:{type:"int",enum:[10,20,50]},include_teens:{type:"bool",default:!0},visual:{type:"string",enum:["number_bond","bar_model","tree"]}},constraints:["All parts non-negative and sum to whole."],expected_methods:["bond_recall","inverse_subtraction"],diagnostic_hooks:[{if_wrong:"puts_whole_in_part_slot",tag:"MIS_PART_WHOLE_CONFUSION"}]},{id:"T10",exercise_type_id:"E10",forms:["pairing_parity"],params_schema:{n:{type:"int",min:1,max:40},representation:{type:"string",enum:["pairs","rows","socks","animals"]}},constraints:["n within range; show pairing visually."],expected_methods:["pairing_rule"],diagnostic_hooks:[{if_wrong:"mispairs_or_mislabels",tag:"MIS_COUNT_SKIP_DOUBLE"}]},{id:"T11",exercise_type_id:"E11",forms:["share_into_two_groups"],params_schema:{n:{type:"int",min:1,max:40},allow_odd:{type:"bool",default:!0},output_mode:{type:"string",enum:["integer_only","allow_halves"]}},constraints:["If output_mode=integer_only, choose even n unless allow_odd=false and show remainder handling."],expected_methods:["equal_sharing"],diagnostic_hooks:[{if_wrong:"unequal_groups",tag:"MIS_UNEQUAL_HALVES"},{if_wrong:"rejects_all_odds",tag:"MIS_ODD_CANT_HALF"}]},{id:"T12",exercise_type_id:"E12",forms:["given_half_find_whole"],params_schema:{half_value:{type:"int",min:1,max:50},context_unit:{type:"string",enum:["stickers","sweets","cm","points"]},representation:{type:"string",enum:["bar_model","picture","equation"]}},constraints:["Whole = 2 * half_value; optionally allow halves contexts."],expected_methods:["double"],diagnostic_hooks:[{if_wrong:"treats_half_as_minus_two",tag:"MIS_KEYWORD_TRAP"}]},{id:"T13",exercise_type_id:"E13",forms:["coin_set_total_pence"],params_schema:{target_max_p:{type:"int",enum:[20,50,100]},coins_allowed:{type:"array",items:{type:"int",enum:[1,2,5,10,20,50,100,200]}},coin_count:{type:"int",min:1,max:12}},constraints:["Total <= target_max_p in beginner modes.","Avoid mixing £ and p in early KS1 unless explicitly enabled."],expected_methods:["group_to_tens","running_total"],diagnostic_hooks:[{if_wrong:"counts_coins_as_ones",tag:"MIS_COIN_FACE_VALUE"},{if_wrong:"double_counts_when_grouping",tag:"MIS_DOUBLE_COUNT"}]},{id:"T14",exercise_type_id:"E14",forms:["sort_by_property","odd_one_out"],params_schema:{shape_pool:{type:"string",enum:["2D","3D","mixed"]},property:{type:"string",enum:["sides","vertices","faces","edges","curved"]},include_rotations:{type:"bool",default:!0}},constraints:["Provide at least one valid sorting rule; for odd-one-out include clear justification."],expected_methods:["property_check"],diagnostic_hooks:[{if_wrong:"names_by_orientation",tag:"MIS_SHAPE_BY_ORIENTATION"}]},{id:"T15",exercise_type_id:"E15",forms:["reflect_points_or_blocks"],params_schema:{grid_w:{type:"int",min:5,max:15},grid_h:{type:"int",min:5,max:15},axis:{type:"string",enum:["vertical","horizontal"]},point_count:{type:"int",min:1,max:10}},constraints:["Reflected points must be equal distance from axis on opposite sides."],expected_methods:["reflect_distance_rule"],diagnostic_hooks:[{if_wrong:"translation_instead_of_reflection",tag:"MIS_TRANSLATE_NOT_REFLECT"}]}
62],misconceptions=[{id:"MIS_EQ_OPERATIONAL",signal:"Treats '=' as 'the answer comes next' or computes only the left side.",confirm_probe:{exercise_type_id:"E01",example_items:["7+3=6+4 (true/false)","8=8+0 (true/false)"]},fix_hint_ladder:["Use a balance scale visual: both sides must match.","Ask: 'What is the value on the left? What is the value on the right?'","Let the child drag tiles to make both sides equal.","Return to missing-number equations with both-sides structure."]},{id:"MIS_COUNT_SKIP_DOUBLE",signal:"Skips or double-counts objects/steps; inconsistent one-to-one counting.",confirm_probe:{exercise_type_id:"E04",example_items:["Count 12 scattered dots (repeat shuffled)","Move 5 steps on a track"]},fix_hint_ladder:["Slow counting with touch/highlight each object once.","Chunk into groups (2s/5s/10s) when appropriate.","Remove visual clutter; reintroduce gradually."]},{id:"MIS_ASSUME_STEP_1",signal:"Assumes every number line tick increases by 1 even when the scale differs.",confirm_probe:{exercise_type_id:"E06",example_items:["0,10,20 on evenly spaced ticks; fill blanks"]},fix_hint_ladder:["Prompt: 'What is the jump between labeled ticks?'","Show equal spacing corresponds to equal jumps.","Practise step sizes 2/5/10 with explicit jumps."]},{id:"MIS_OFF_BY_ONE_MOVES",signal:"Counts starting square as step 1 (or miscounts jumps) on grid/numberline moves.",confirm_probe:{exercise_type_id:"E07",example_items:["Start at 4, jump +3, where land?"]},fix_hint_ladder:["Animate jumps; count jumps not squares.","Use finger/marker to show move between positions.","Give immediate feedback highlighting the correct landing point."]},{id:"MIS_LOSE_REMAINDER",signal:"When bridging 10, forgets the remainder (e.g., 8+7 -> 10+7).",confirm_probe:{exercise_type_id:"E03",example_items:["8+7 using ten-frame transfer"]},fix_hint_ladder:["Physically move (10-a) from b into a to make 10.","Ask: 'How many did you move? How many are left?'","Write as 8+7 = 10+5 (because moved 2)."]},{id:"MIS_BAD_PARTITION",signal:"Partitions numbers incorrectly (e.g., 7 -> 4+4) or chooses unhelpful splits.",confirm_probe:{exercise_type_id:"E03",example_items:["Make 10 from 7: how many more?"]},fix_hint_ladder:["Ten-frame fill to show complement to 10.","Bond drills: 7+3, 6+4, 8+2.","Return to bridge-10 with guided splits."]},{id:"MIS_SUB_DIRECTION",signal:"Swaps subtraction order or subtracts the wrong way around.",confirm_probe:{exercise_type_id:"E04",example_items:["9 birds, 2 fly away; how many left?"]},fix_hint_ladder:["Act out 'start with, take away'.","Use number line backwards from the start.","Contrast with difference problems to clarify meanings."]},{id:"MIS_KEYWORD_TRAP",signal:"Chooses operation based on a keyword (e.g., 'more') rather than meaning.",confirm_probe:{exercise_type_id:"E05",example_items:["'Tom has 7, Ann has 9. How many more does Ann have?'"]},fix_hint_ladder:["Prompt: 'What changed? Are we comparing or combining?'","Use bar model: compare lengths to find the difference.","Mix similar stories requiring different operations."]},{id:"MIS_PART_WHOLE_CONFUSION",signal:"Confuses parts with the whole in part-whole models.",confirm_probe:{exercise_type_id:"E09",example_items:["Whole=13, one part=10, missing part=?"]},fix_hint_ladder:["Cover the whole, reveal parts; whole is all together.","Show: whole = part + part; missing part = whole - known part.","Practise with concrete objects then diagrams."]},{id:"MIS_ODD_CANT_HALF",signal:"Believes odd numbers cannot be halved in any context.",confirm_probe:{exercise_type_id:"E11",example_items:["Half of 7 sweets (allow halves) vs 7 balloons (not)"]},fix_hint_ladder:["Explain context: some things can be split, some can’t.","Show 7 split as 3 and 4; discuss fairness.","Introduce 3½ when appropriate (KS2 contexts)."]},{id:"MIS_UNEQUAL_HALVES",signal:"Accepts unequal groups/parts as 'halves'.",confirm_probe:{exercise_type_id:"E11",example_items:["Split 8 into halves: 5+3 vs 4+4"]},fix_hint_ladder:["Lock rule: halves must be equal size.","Add visual equality check (scales or matching pairs).","Return to easy even numbers then reintroduce odds."]},{id:"MIS_COIN_FACE_VALUE",signal:"Counts the number of coins rather than their values.",confirm_probe:{exercise_type_id:"E13",example_items:["3×10p vs 3×1p: which is more?"]},fix_hint_ladder:["Always read coin value aloud before counting.","Group coins to make 10p/20p.","Use running total with coin labels shown."]},{id:"MIS_DOUBLE_COUNT",signal:"Double counts coins or changes total when regrouping.",confirm_probe:{exercise_type_id:"E13",example_items:["Total the same set two different ways"]},fix_hint_ladder:["Mark coins as 'counted' when included in subtotal.","Bucket totals (10s, 5s, 2s, 1s) then add buckets.","Reduce number of coins, then scale up."]},{id:"MIS_SHAPE_BY_ORIENTATION",signal:"Thinks a rotated shape becomes a different shape (e.g., square becomes 'diamond').",confirm_probe:{exercise_type_id:"E14",example_items:["Rotate a square 45° and ask same/different?"]},fix_hint_ladder:["Use a rotation spinner: shape stays the same.","Compare properties (4 equal sides) rather than appearance.","Sort shapes with rotated distractors."]},{id:"MIS_TRANSLATE_NOT_REFLECT",signal:"Copies a shape to the other side without mirroring in symmetry tasks.",confirm_probe:{exercise_type_id:"E15",example_items:["Reflect single points across a vertical line"]},fix_hint_ladder:["Show mirror line and equal distance rule.","Flip animation: reflect across the line.","Start with single points, then blocks/shapes."]}],mastery_rules={evidence_window_items:30,mastery:{min_items:10,min_accuracy:.8,max_level3_hint_rate:.2,n
62o_repeat_misconception_last_n:5},struggle:{min_items:8,max_accuracy:.5,repeat_same_misconception_in_last_n:{n:10,k:3}},next_step_logic:[{when:"struggle_detected",do:["reduce_parameter_range","switch_representation","run_targeted_misconception_microlesson","retry_with_scaffold_then_fade"]},{when:"mastery_detected",do:["run_transfer_check_in_new_exercise_type","then_increase_complexity_or_range"]}]},rawJson={schema_version,generated_utc,notes,skills:skills$1,exercise_types,generators,misconceptions,mastery_rules},rawSpec=rawJson,content=normalizeAndValidate(rawSpec),skillsById=new Map(content.skills.map(n=>[n.id,n])),exerciseTypesById=new Map(content.exerciseTypes.map(n=>[n.id,n]));new Map(content.generators.map(n=>[n.id,n]));const misconceptionsById=new Map(content.misconceptions.map(n=>[n.id,n]));function getAllSkills(){return[...content.skills]}function getAllExerciseTypes(){return[...content.exerciseTypes]}function getSkillById(n){return skillsById.get(n)}function getExerciseTypeById(n){return exerciseTypesById.get(n)}function getGeneratorsForExerciseType(n){return content.generators.filter(t=>t.exerciseTypeId===n)}function getMisconceptionById(n){return misconceptionsById.get(n)}function getMasteryRules(){return content.masteryRules}function normalizeAndValidate(n){assert(Array.isArray(n.skills),"Invalid content spec: skills must be an array"),assert(Array.isArray(n.exercise_types),"Invalid content spec: exercise_types must be an array"),assert(Array.isArray(n.generators),"Invalid content spec: generators must be an array"),assert(Array.isArray(n.misconceptions),"Invalid content spec: misconceptions must be an array"),assert(n.mastery_rules!=null,"Invalid content spec: mastery_rules is required");const t=n.skills.map(a=>({id:a.id,strand:a.strand,keyStage:a.key_stage,yearBand:a.year_band,description:a.description,prereqIds:a.prereq_ids??[],synonyms:a.synonyms??[]})),e=n.exercise_types.map(a=>({id:a.id,name:a.name,interactionKind:a.interaction,description:a.description??"",assessesSkillIds:a.assesses_skill_ids??[],diagnosesMisconceptionIds:a.diagnoses_misconception_ids??[]})),s=n.generators.map(a=>normalizeGenerator(a)),o=n.misconceptions.map(a=>({id:a.id,description:a.signal,confirmProbe:a.confirm_probe?{exerciseTypeId:a.confirm_probe.exercise_type_id,exampleItems:a.confirm_probe.example_items??[]}:void 0,fixHintLadder:a.fix_hint_ladder??[]})),r={id:"default",windowSize:n.mastery_rules.evidence_window_items,masteryMinItems:n.mastery_rules.mastery.min_items,masteryAccuracyThreshold:n.mastery_rules.mastery.min_accuracy,heavyHintThreshold:n.mastery_rules.mastery.max_level3_hint_rate,struggleMinItems:n.mastery_rules.struggle.min_items,struggleAccuracyThreshold:n.mastery_rules.struggle.max_accuracy,struggleMisconceptionRepeatN:n.mastery_rules.struggle.repeat_same_misconception_in_last_n?.n??0,struggleMisconceptionRepeatK:n.mastery_rules.struggle.repeat_same_misconception_in_last_n?.k??0};return validateReferences(t,e,s,o),validateMasteryRules(r),{schemaVersion:n.schema_version,generatedUtc:n.generated_utc,notes:Array.isArray(n.notes)?n.notes:n.notes?[n.notes]:[],skills:t,exerciseTypes:e,generators:s,misconceptions:o,masteryRules:r}}function normalizeGenerator(n){const t=n.diagnostic_hooks?.map(e=>{const s=e.if_wrong_answer_pattern??e.if;return!s||!e.tag?null:{ifWrongAnswerPattern:s,tag:e.tag}}).filter(e=>e!=null)??[];return{id:n.id,exerciseTypeId:n.exercise_type_id,forms:n.forms??[],paramsSchema:normalizeParamsSchema(n.params_schema),constraints:n.constraints??[],expectedMethods:n.expected_methods??[],diagnosticHooks:t}}function normalizeParamsSchema(n){if(!n)return{};const t={};for(const[e,s]of Object.entries(n)){if(!s||typeof s!="object")continue;const o=s,r=String(o.type);r!=="int"&&r!=="bool"&&r!=="string"&&r!=="number"||(t[e]={type:r,enum:Array.isArray(o.enum)?o.enum.filter(a=>typeof a=="string"||typeof a=="number"||typeof a=="boolean"):void 0,default:typeof o.default=="string"||typeof o.default=="number"||typeof o.default=="boolean"?o.default:void 0,minimum:typeof o.minimum=="number"?o.minimum:typeof o.min=="number"?o.min:void 0,maximum:typeof o.maximum=="number"?o.maximum:typeof o.max=="number"?o.max:void 0})}return t}function validateReferences(n,t,e,s){const o=new Set(n.map(i=>i.id)),r=new Set(t.map(i=>i.id)),a=new Set(s.map(i=>i.id));assertUniqueIds(n.map(i=>i.id),"skill"),assertUniqueIds(t.map(i=>i.id),"exercise_type"),assertUniqueIds(e.map(i=>i.id),"generator"),assertUniqueIds(s.map(i=>i.id),"misconception");for(const i of n)for(const l of i.prereqIds)assert(o.has(l),`Skill ${i.id} references missing prereq id ${l}`);for(const i of t){for(const l of i.assessesSkillIds)assert(o.has(l),`Exercise type ${i.id} references missing skill id ${l}`);for(const l of i.diagnosesMisconceptionIds)assert(a.has(l),`Exercise type ${i.id} references missing misconception id ${l}`)}for(const i of e){assert(r.has(i.exerciseTypeId),`Generator ${i.id} references missing exercise_type id ${i.exerciseTypeId}`);for(const l of i.diagnosticHooks)assert(a.has(l.tag),`Generator ${i.id} diagnostic hook references missing misconception id ${l.tag}`)}}function validateMasteryRules(n){assert(n.windowSize>0,"mastery_rules.evidence_window_items must be > 0"),assert(n.masteryMinItems>0,"mastery.min_items must be > 0"),assert(n.struggleMinItems>0,"struggle.min_items must be > 0"),assert(inRange01(n.masteryAccuracyThreshold),"mastery.min_accuracy must be between 0 and 1"),assert(inRange01(n.heavyHintThreshold),"mastery.max_level3_hint_rate must be between 0 and 1"),assert(inRange01(n.struggleAccuracyThreshold),"struggle.max_accuracy must be between 0 and 1")}function assertUniqueIds(n,t){const e=new Set;for(const s of n)assert(!e.has(s),`Duplicate ${t} id: ${s}`),e.add(s)}function inRange01(n){return n>=0&&n<=1}function assert(n,t){if(!n)throw new Error(t)}const skills=getAllSkills(),prereqMap=new Map(skills.map(n=>[n.id,n.prereqIds]));buildDependentMap(skills);function getPrereqs(n){return(prereqMap.get(n)??[]).map(e=>getSkillById(e)).filter(e=>e!=null)}function canAttemptSkill(n,t){const e=getPrereqs(t);return e.length===0?!0:e.every(s=>{const r=n.skillMastery[s.id]?.status??"unseen";return r==="emerging"||r==="mastered"})}function buildDependentMap(n){const t=new Map;for(const e of n)t.set(e.id,[]);for(const e of n)for(const s of e.prereqIds){const o=t.get(s);o&&o.push(e.id)}return t}function selectNextItem(n,t=getMasteryRules(),e={}){const s=createRandomSource(e.randomSeed),o=getCandidateSkills(n,e);if(o.length===0)throw new Error("No candidate skills available for item selection");const r=pickSkill(o,n,s),a=pickExerciseTypeForSkill(r.id,s),i=pickGeneratorForExerciseType(a.id,s),l=inferGeneratorParams(i,r,e.maxDifficultyStep??0);return{skillId:r.id,exerciseTypeId:a.id,generatorId:i.id,params:l}}function getCandidateSkills(n,t){const e=new Set(t.targetStrands??[]),s=new Set(t.preferSkills??[]),o=new Set(getAllExerciseTypes().flatMap(a=>a.assessesSkillIds));let r=getAllSkills().filter(a=>(n.skillMastery[a.id]?.status??"unseen")!=="mastered"&&canAttemptSkill(n,a.id)&&o.has(a.id));if(e.size>0&&(r=r.filter(a=>e.has(a.strand))),s.size>0){const a=r.filter(i=>s.has(i.id));a.length>0&&(r=a)}return r}function pickSkill(n,t,e){const s=[...n].sort((a,i)=>{const l=masteryPriority(t.skillMastery[a.id]?.status??"unseen"),u=masteryPriority(t.skillMastery[i.id]?.status??"unseen");if(l!==u)return l-u;const d=t.skillMastery[a.id]?.accuracy??0,m=t.skillMastery[i.id]?.accuracy??0;return d-m}),o=masteryPriority(t.skillMastery[s[0].id]?.status??"unseen"),r=s.filter(a=>masteryPriority(t.skillMastery[a.id]?.status??"unseen")===o);return r[randomInt$1(0,r.length-1,e)]}function pickExerciseTypeForSkill(n,t){const e=getAllExerciseTypes().filter(s=>s.assessesSkillIds.includes(n));if(e.length===0)throw new Error(`No exercise type found assessing skill ${n}`);return e[randomInt$1(0,e.length-1,t)]}function pickGeneratorForExerciseType(n,t){const e=getGeneratorsForExerciseType(n);if(e.length===0)throw new Error(`No generator found for exercise type ${n}`);return e[randomInt$1(0,e.length-1,t)]}function inferGeneratorParams(n,t,e,s){const o={};for(const[r,a]of Object.entries(n.paramsSchema)){const i=inferSkillAlignedValue(t,r,a);o[r]=i??inferParamValue(a,e)}return o}function inferSkillAlignedValue(n,t,e){if(!e.enum||e.enum.length===0)return;const s=e.enum.map(r=>String(r).toLowerCase()),o=n.id.toLowerCase();if(t==="shape_pool"){if(o.includes("_2d_")&&s.includes("2d"))return"2D";if(o.includes("_3d_")&&s.includes("3d"))return"3D"}if(t==="property"){if(o.includes("sides_vertices")){if(s.includes("sides"))return"sides";if(s.includes("vertices"))return"vertices"}if(o.includes("faces_edges_vertices")){if(s.includes("faces"))return"faces";if(s.includes("edges"))return"edges";if(s.includes("vertices"))return"vertices"}}}function inferParamValue(n,t){if(n.default!==void 0)return n.default;const e=difficultyToBucket(t);if(Array.isArray(n.enum)&&n.enum.length>0)return pickFromEnum(n.enum,e);if(n.type==="bool")return e>=.5;if((n.type==="int"||n.type==="number")&&n.minimum!==void 0){const s=n.minimum,o=n.maximum??s+10;return sampleNumber(s,o,n.type==="int",e)}return n.type==="string"?"default":null}function pickFromEnum(n,t){const e=[...n].sort(comparePrimitive),s=e.length-1,o=Math.max(0,Math.min(s,Math.round(t*s)));return e[o]}function sampleNumber(n,t,e,s){const o=t-n,r=n+o*s;return e?Math.round(r):r}function masteryPriority(n){switch(n){case"struggle":return 0;case"unseen":return 1;case"emerging":return 2;default:return 3}}function difficultyToBucket(n){return(Math.max(-2,Math.min(2,n))+2)/4}function comparePrimitive(n,t){return typeof n=="number"&&typeof t=="number"?n-t:String(n).localeCompare(String(t))}function randomInt$1(n,t,e){return Math.floor(e()*(t-n+1))+n}function createRandomSource(n){if(n===void 0)return Math.random;let t=hashSeed(n);return t===0&&(t=2654435769),()=>(t=t*1664525+1013904223>>>0,t/4294967296)}function hashSeed(n){const t=String(n);let e=2166136261;for(let s=0;s<t.length;s+=1)e^=t.charCodeAt(s),e=Math.imul(e,16777619);return e>>>0}function applyAttempt(n,t,e){if(n.pupilId!==e.pupilId)throw new Error(`Pupil mismatch: attempt pupilId ${e.pupilId} does not match state ${n.pupilId}`);const s=[...n.recentAttempts,e],o=pruneAttemptsPerSkillWindow(s,t.windowSize),r={};for(const a of getAllSkills()){const i=o.filter(l=>l.skillId===a.id);r[a.id]=computeSkillMastery(a.id,i,t)}return{pupilId:n.pupilId,recentAttempts:o,skillMastery:r}}function computeSkillMastery(n,t,e){if(t.length===0)return{skillId:n,status:"unseen",accuracy:0,itemsCount:0,hintRate:0,heavyHintRate:0};const s=t.length,o=t.filter(m=>m.correct).length,r=t.filter(m=>m.hintLevelUsed>0).length,a=t.filter(m=>m.hintLevelUsed>=3).length,i=safeDivide(o,s),l=safeDivide(r,s),u=safeDivide(a,s),d=deriveStatus({attempts:t,accuracy:i,heavyHintRate:u,rules:e});return{skillId:n,status:d,accuracy:i,itemsCount:s,hintRate:l,heavyHintRate:u}}function deriveStatus(n){const{attempts:t,accuracy:e,heavyHintRate:s,rules:o}=n,r=t.length;if(r>=o.masteryMinItems&&e>=o.masteryAccuracyThreshold&&s<=o.heavyHintThreshold)return"mastered";const i=detectRepeatedMisconception(t,o.struggleMisconceptionRepeatN,o.struggleMisconceptionRepeatK);return r>=o.struggleMinItems&&(e<=o.struggleAccuracyThreshold||i)?"struggle":"emerging"}function detectRepeatedMisconception(n,t,e){if(t<=0||e<=0)return!1;const s=n.slice(-t),o=new Map;for(const r of s)for(const a of r.misconceptionTags)if(o.set(a,(o.get(a)??0)+1),(o.get(a)??0)>=e)return!0;return!1}function pruneAttemptsPerSkillWindow(n,t){const e=new Map,s=[];for(let o=n.length-1;o>=0;o-=1){const r=n[o],a=e.get(r.skillId)??0;a>=t||(e.set(r.skillId,a+1),s.push(r))}return s.reverse(),s}function safeDivide(n,t){return t===0?0:n/t}function getNextHintForMisconception(n,t){const e=getMisconceptionById(n);if(!e)return null;const s=e.fixHintLadder;if(s.length===0)return null;const o=Math.max(0,t);return o>=s.length?null:{hint:s[o],isFinalHint:o===s.length-1}}function getNextItemForPupil(n,t={},e=getMasteryRules()){return selectNextItem(n,e,t)}function submitAttempt(n,t,e={}){const s=e.rules??getMasteryRules(),o=applyAttempt(n,s,t),r=o.skillMastery[t.skillId]??{skillId:t.skillId,status:"unseen",accuracy:0,itemsCount:0,hintRate:0,heavyHintRate:0},a=pickPrimaryMisconception(t.misconceptionTags);if(!a)return{pupil:o,skillMastery:r,primaryMisconceptionId:null,nextHint:null,nextHintLevel:0};const i=e.currentHintLevels?.[a]??0,l=getNextHintForMisconception(a,i);return{pupil:o,skillMastery:r,primaryMisconceptionId:a,nextHint:l,nextHintLevel:l?i+1:i}}function pickPrimaryMisconception(n){for(const t of n)if(getMisconceptionById(t))return t;return null}function createInitialPupilState(n){return{pupilId:n,skillMastery:{},recentAttempts:[]}}function buildQuestionFromSelected(n,t){if(n.skillId==="AS_DOUBLES"){const e=toPositiveInt(n.params.range_max,20),s=boundedDeterministicInt(n,1,Math.max(3,Math.floor(e/2)),t);return{childPrompt:`What is double ${s}?`,parentNote:"If needed, show with objects: two equal groups of the same number.",answer:String(s*2)}}if(n.skillId==="GEO_2D_SIDES_VERTICES"&&n.exerciseTypeId==="E14"){const e=n.params.property==="vertices"?"vertices":"sides",s=pickFromListDeterministic(n,["triangle","square","rectangle","pentagon"],t),o=shapeSideVertexCount(s);return{childPrompt:`Look at a ${s}. How many ${e} does it have?`,parentNote:"If helpful, draw the shape and let the child point and count.",answer:String(e==="vertices"?o.vertices:o.sides)}}if(n.exerciseTypeId==="E01"){const e=toPositiveInt(n.params.range_max,10),s=boundedDeterministicInt({...n,skillId:`${n.skillId}-a`},1,Math.max(5,e-1),t),o=boundedDeterministicInt({...n,skillId:`${n.skillId}-b`},1,Math.max(5,e-s),t+1);return{childPrompt:`Fill in the missing number: ${s} + box = ${s+o}.`,parentNote:"Ask child to count on from the first number if needed.",answer:String(o)}}if(n.exerciseTypeId==="E10"){const e=boundedDeterministicInt(n,1,20,t);return{childPrompt:`Is ${e} odd or even?`,parentNote:"The child can pair objects; leftovers mean odd.",answer:e%2===0?"even":"odd"}}if(n.exerciseTypeId==="E06"){const e=toNonNegativeInt(n.params.start,0),s=toPositiveInt(n.params.step,5),o=[1,2,5,10].includes(s)?s:5,r=Math.max(5,Math.min(8,toPositiveInt(n.params.num_ticks,6))),a=boundedDeterministicInt(n,1,r-2,t),i=Array.from({length:r},(d,m)=>e+m*o),l=i[a];return{childPrompt:`What number is missing? ${i.map((d,m)=>m===a?"__":String(d)).join(", ")}`,parentNote:`Count by ${o}s from ${e}. The missing number is ${l}.`,answer:String(l)}}if(n.exerciseTypeId==="E08"){const e={step1:1,step2:2,step5:5,step10:10,unknown_step:2},s=String(n.params.pattern??"step2"),o=e[s]??2,r=Math.max(5,Math.min(8,toPositiveInt(n.params.length,5))),i=boundedDeterministicInt({...n,skillId:`${n.skillId}-s`},0,9,t)*o,l=Array.from({length:r},(h,f)=>i+f*o),u=boundedDeterministicInt({...n,skillId:`${n.skillId}-b`},1,r-2,t),d=l[u];return{childPrompt:`What number is missing? ${l.map((h,f)=>f===u?"__":String(h)).join(", ")}`,parentNote:`The sequence goes up by ${o} each time. The missing number is ${d}.`,answer:String(d)}}if(n.exerciseTypeId==="E11"){const e=toPositiveInt(n.params.n,20),s=Math.max(1,Math.floor(e/2)),o=boundedDeterministicInt(n,1,s,t),r=o*2;return{childPrompt:`Half of ${r} is __?`,parentNote:`Share ${r} into two equal groups. Each group gets ${o}.`,answer:String(o)}}if(n.exerciseTypeId==="E12"){const e=toPositiveInt(n.params.half_value,20),s=boundedDeterministicInt(n,1,Math.max(2,e),t),o=String(n.params.context_unit??""),r=["stickers","sweets","cm","points"].includes(o)?o:"stickers";return{childPrompt:`Half of the ${r} is ${s}. How many ${r} are there altogether?`,parentNote:`The whole is double the half. ${s} + ${s} = ${s*2}.`,answer:String(s*2)}}return{childPrompt:`Let's solve a maths challenge: question ${t}.`,parentNote:"Fallback prompt. If unclear, ask a simple related question in the same skill area and record the result.",answer:"Manual check"}}function shapeSideVertexCount(n){switch(n){case"triangle":return{sides:3,vertices:3};case"square":return{sides:4,vertices:4};
62case"rectangle":return{sides:4,vertices:4};case"pentagon":return{sides:5,vertices:5};default:return{sides:0,vertices:0}}}function toPositiveInt(n,t){return typeof n=="number"&&Number.isFinite(n)&&n>0?Math.floor(n):t}function toNonNegativeInt(n,t){return typeof n=="number"&&Number.isFinite(n)&&n>=0?Math.floor(n):t}function boundedDeterministicInt(n,t,e,s){if(e<=t)return t;const o=hashSelectedItem(n)+s*7919;return t+o%(e-t+1)}function pickFromListDeterministic(n,t,e){if(t.length===0)return"shape";const s=(hashSelectedItem(n)+e*3571)%t.length;return t[s]}function hashSelectedItem(n){const t=`${n.skillId}|${n.exerciseTypeId}|${n.generatorId}|${JSON.stringify(n.params)}`;let e=2166136261;for(let s=0;s<t.length;s+=1)e^=t.charCodeAt(s),e=Math.imul(e,16777619);return e>>>0}const FALLBACK_QUESTION_BANK=[{id:"q-mc-1",strand:"Number & Place Value",stem:"Which number is the largest?",type:"multiple_choice",options:["308","380","318","330"],correctAnswer:"380",hints:["Check the hundreds digit first.","All are 3 hundreds, so compare the tens.","The biggest tens digit here is 8."]},{id:"q-eq-1",strand:"Addition & Subtraction",stem:"8 + 5 = __",type:"equation",correctAnswer:"13",hints:["Try making 10 first.","8 + 2 = 10, then add 3 more.","10 + 3 gives the final answer."]},{id:"q-line-1",strand:"Fractions",stem:"Tap the number that is halfway between 4 and 8.",type:"numberline",options:["3","4","5","6","7","8","9"],correctAnswer:"6",hints:["Halfway means equal distance from both ends.","Count 2 steps up from 4 and 2 steps down from 8.","Both ways land on the same number."]},{id:"q-mc-2",strand:"Money & Time",stem:"Which set makes 50p?",type:"multiple_choice",options:["20p + 20p + 5p","10p + 20p + 10p","20p + 20p + 10p","5p + 5p + 20p"],correctAnswer:"20p + 20p + 10p",hints:["Add the coins one by one.","You need a total of exactly 50.","Two 20ps need one more coin worth 10p."]},{id:"q-eq-2",strand:"Shape & Space",stem:"Which shape has 4 equal sides and 4 right angles?",type:"multiple_choice",options:["Rectangle","Triangle","Square","Circle"],correctAnswer:"Square",hints:["Count the sides first.","Then check if all corners are right angles.","A square has equal sides and right-angle corners."]},{id:"q-md-1",strand:"Multiplication & Division",stem:"What is 6 × 4?",type:"equation",correctAnswer:"24",hints:["Think of 6 groups of 4.","Or try 4 + 4 + 4 + 4 + 4 + 4.","6 × 4 = 24."]},{id:"q-rp-1",strand:"Ratio & Proportion",stem:"If 1 bag has 5 sweets, how many sweets in 3 bags?",type:"equation",correctAnswer:"15",hints:["Each bag has the same number.","Multiply the number in one bag by 3.","5 × 3 = 15."]},{id:"q-ds-1",strand:"Data & Statistics",stem:"A bar chart shows 4, 7, and 10. What is the total?",type:"equation",correctAnswer:"21",hints:["Add the values together.","4 + 7 = 11, then add 10.","11 + 10 = 21."]}];function buildHintsFromEngine(n,t){const e=[t.trim()].filter(Boolean),o=getExerciseTypeById(n)?.diagnosesMisconceptionIds.flatMap(r=>getMisconceptionById(r)?.fixHintLadder??[])??[];for(const r of o){const a=r.trim();if(a&&!e.includes(a)&&e.push(a),e.length>=3)break}for(;e.length<3;){const r=e.length===1?"Try one step at a time and say the numbers aloud.":"Check your answer by counting carefully.";e.push(r)}return e.slice(0,3)}function hashText(n){let t=2166136261;for(let e=0;e<n.length;e+=1)t^=n.charCodeAt(e),t=Math.imul(t,16777619);return t>>>0}function randomFromSeed(n){let t=hashText(n)||1;return()=>(t=Math.imul(t,1664525)+1013904223>>>0,t/4294967296)}function randomInt(n,t,e){return Math.floor(n()*(e-t+1))+t}function shuffle(n,t){const e=[...n];for(let s=e.length-1;s>0;s-=1){const o=Math.floor(t()*(s+1));[e[s],e[o]]=[e[o],e[s]]}return e}const BANK_FILES=["number_place_value.question_bank.json","addition_subtraction.question_bank.json","fractions_decimals_percentages.question_bank.json","measurement.question_bank.json","geometry.question_bank.json","multiplication_division.question_bank.json","ratio_proportion.question_bank.json","data_statistics.question_bank.json"],BANK_IMPORTERS=[()=>__vitePreload(()=>import("./qb-number_place_value-enrnCdN-.js"),[]),()=>__vitePreload(()=>import("./qb-addition_subtraction-CnZ2vfau.js"),[]),()=>__vitePreload(()=>import("./qb-fractions_decimals_percentages--9RRMLbe.js"),[]),()=>__vitePreload(()=>import("./qb-measurement-Cn-mI3vz.js"),[]),()=>__vitePreload(()=>import("./qb-geometry-MTUuBQKO.js"),[]),()=>__vitePreload(()=>import("./qb-multiplication_division-D2qNO2Na.js"),[]),()=>__vitePreload(()=>import("./qb-ratio_proportion-D0McfndS.js"),[]),()=>__vitePreload(()=>import("./qb-data_statistics-6AHMzZdI.js"),[])];let _banks=null,_index=null,_loadPromise=null;function buildIndex(n){const t=new Map,e=new Map,s=new Map;for(const o of n){s.set(o.strand_id,o);for(const r of o.authored_questions){t.set(r.id,r);const a=e.get(r.micro_skill_id)??[];a.push(r),e.set(r.micro_skill_id,a)}}return{byId:t,byMicroSkill:e,byStrand:s}}function loadBanksSyncNode(){const req=eval("require"),fs=req("node:fs"),path=req("node:path"),dir=path.resolve(eval("__dirname"),"../../data/curriculum/question_bank");return BANK_FILES.map(n=>JSON.parse(fs.readFileSync(path.join(dir,n),"utf8")))}function loadQuestionBankIndex(){return _index?Promise.resolve(_index):_loadPromise||(_loadPromise=Promise.all(BANK_IMPORTERS.map(n=>n())).then(n=>(_banks=n.map(t=>t.default),_index=buildIndex(_banks),_index)).catch(n=>{throw _loadPromise=null,n}),_loadPromise)}function getQuestionBankIndex(){if(_index)return _index;if(typeof window>"u")return _banks=_banks??loadBanksSyncNode(),_index=buildIndex(_banks),_index;throw new Error("Question bank not loaded yet — await loadQuestionBankIndex() before calling getQuestionBankIndex().")}function getAuthoredQuestionsForMicroSkill(n){return getQuestionBankIndex().byMicroSkill.get(n)??[]}function selectFromBank(n,t={}){const e=t.randomSeed??"",s=pickWeightedMicroSkill(n,t.skillStates,e);let o=s?getAuthoredQuestionsForMicroSkill(s):[];if(o.length===0)for(const d of n)o.push(...getAuthoredQuestionsForMicroSkill(d));o=o.filter(hasUsablePrompt),t.excludeIds&&t.excludeIds.size>0&&(o=o.filter(d=>!t.excludeIds.has(d.id)));const r=t.reviewFilter??"all";if(r==="reviewed"?o=o.filter(d=>!!d.reviewed_by):r==="approved"?o=o.filter(d=>d.status==="approved"):r==="unreviewed"&&(o=o.filter(d=>!d.reviewed_by)),o.length===0)return null;const a=difficultyStepToTarget(t.difficultyStep??0);o.sort((d,m)=>{const h=d.reviewed_by?0:1,f=m.reviewed_by?0:1;return h!==f?h-f:Math.abs(questionDifficultyScore(d)-a)-Math.abs(questionDifficultyScore(m)-a)});const i=Math.abs(questionDifficultyScore(o[0])-a),l=o.filter(d=>Math.abs(questionDifficultyScore(d)-a)<=i+1),u=fnvIndex(t.randomSeed??"",l.length);return l[u]}const PLACEHOLDER_RE=/^(Fill in the missing part for|Which answer best fits)\b/i,DESCRIPTION_AS_
62STEM_RE=/^(Use the same method|Recall the same fact|Apply the inverse|Solve a related)\b/i;function hasUsablePrompt(n){if(n.answer_spec.kind==="ordering")return!1;const t=n.prompt.spoken_prompt,e=n.prompt.stem;if(t&&PLACEHOLDER_RE.test(t)||e&&DESCRIPTION_AS_STEM_RE.test(e)||(!e||e==="__")&&!t)return!1;if(n.representation==="visual_model"&&!n.micro_skill_id.startsWith("GEO.")&&!n.visual_data){const o=(t||e||"").match(/\d+/g);if(!o||o.length<2)return!1}return!0}function questionDifficultyScore(n){return isValidDifficultyScore(n.task_complexity_score)?n.task_complexity_score:isValidDifficultyScore(n.complexity_band)?n.complexity_band:n.difficulty_rank}function isValidDifficultyScore(n){return Number.isFinite(n)&&n>=1&&n<=5}function difficultyStepToTarget(n){return Math.max(1,Math.min(5,Math.round(n+3)))}function fnvIndex(n,t){if(t<=0)return 0;let e=2166136261;for(let s=0;s<n.length;s++)e^=n.charCodeAt(s),e=Math.imul(e,16777619);return(e>>>0)%t}function fnvHash(n){let t=2166136261;for(let e=0;e<n.length;e++)t^=n.charCodeAt(e),t=Math.imul(t,16777619);return t>>>0}function stateWeight(n){return!n||n==="unseen"?4:n==="emerging"?3:n==="developing"?2:1}function pickWeightedMicroSkill(n,t,e){if(n.length===0)return null;if(!t||Object.keys(t).length===0)return n[fnvIndex(e+"-skill",n.length)];const s=n.map(a=>stateWeight(t[a])),o=s.reduce((a,i)=>a+i,0);let r=fnvHash(e+"-skill")%o;for(let a=0;a<n.length;a++){if(r<s[a])return n[a];r-=s[a]}return n[n.length-1]}const VISUAL_DRIVEN_SELECT_TYPES=new Set(["domino_set","shape_options","coin_tap","drag_order","bar_builder"]);function adaptToSessionQuestion(n,t){const e=mapAnswerKindToQuestionType(n.answer_spec.kind,n.interaction_type),s=n.prompt.stem,o=n.prompt.spoken_prompt!==n.prompt.stem?n.prompt.spoken_prompt:void 0,r=extractCorrectAnswer(n),a=extractAcceptedAnswers(n),i=extractOptions(n,e),l=extractHints(n);return{id:`cb-${n.id}`,strand:t,stem:s,spokenPrompt:o,type:e,representation:n.representation,mathematicalForm:n.mathematical_form,options:i,correctAnswer:r,acceptedAnswers:a,hints:l,visual_data:n.visual_data,engineSelection:buildSyntheticEngineSelection(n),generatedAtMs:Date.now(),misconceptionTags:n.misconception_tags??[],optionMisconceptionTags:extractOptionMisconceptionTags(n),debugInfo:{source:"canonical_bank",questionId:n.id,microSkillId:n.micro_skill_id,yearBand:n.typical_year_band_id,difficultyRank:n.difficulty_rank,questionFamily:n.question_family,formVariant:n.form_variant,scaffoldLevel:n.scaffold_level,representation:n.representation,bankSource:n.source,reviewedBy:n.reviewed_by,difficultyTier:n.difficulty_tier}}}function buildSyntheticEngineSelection(n){return{skillId:n.micro_skill_id,exerciseTypeId:`CB_${n.interaction_type.toUpperCase()}`,generatorId:`cb-${n.id}`,params:{microSkillId:n.micro_skill_id,difficultyRank:n.difficulty_rank,scaffoldLevel:n.scaffold_level,questionFamily:n.question_family}}}function extractOptionMisconceptionTags(n){if(n.answer_spec.kind!=="select")return;const t=n.answer_spec,e={};for(const s of t.options)s.misconception_tags&&s.misconception_tags.length>0&&(e[s.label]=s.misconception_tags);return Object.keys(e).length>0?e:void 0}function mapAnswerKindToQuestionType(n,t){switch(n){case"select":return"multiple_choice";case"number_line":return"numberline";case"free_response":return t==="typed_text"?"text_input":"equation";default:return"equation"}}function extractCorrectAnswer(n){const t=n.answer_spec;if(t.kind==="select"){const e=t,s=e.correct_option_ids[0];return e.options.find(r=>r.id===s)?.label??""}return t.kind==="free_response"||t.kind==="number_line"?String(t.accepted_values[0]):""}function extractAcceptedAnswers(n){const t=n.answer_spec;if(t.kind==="select"){const e=new Set(t.correct_option_ids);return t.options.filter(s=>e.has(s.id)).map(s=>s.label)}return t.kind==="free_response"||t.kind==="number_line"?t.accepted_values.map(String):[]}function extractOptions(n,t){if(t==="multiple_choice"&&n.answer_spec.kind==="select"){const s=n.answer_spec.options.map(o=>o.label);return VISUAL_DRIVEN_SELECT_TYPES.has(n.visual_data?.type??"")?s:shuffle(s,randomFromSeed(`mc-option-order:${n.id}`))}if(t==="numberline"&&n.answer_spec.kind==="number_line"){const e=n.answer_spec;return buildNumberlineTicks(e.accepted_values[0])}}function extractHints(n){const t=[];if(n.hint_ladder)for(const e of n.hint_ladder)t.push(e.message);for(;t.length<3;)t.length===0?t.push("Take your time and read the question again."):t.length===1?t.push("Try one step at a time and say the numbers aloud."):t.push("Check your answer by counting carefully.");return t.slice(0,3)}function parseNumericAnswer(n){const t=n.trim();if(!/^-?\d+(\.\d+)?$/.test(t))return null;const e=Number(t);return Number.isFinite(e)?e:null}function buildNumberlineTicks(n){const t=Math.round(n),e=Math.max(0,t-3);return Array.from({length:7},(s,o)=>String(e+o))}function buildEngineQuestion(n,t,e,s,o){try{const r=UI_TO_ENGINE_STRANDS[n],a=getNextItemForPupil(s,{targetStrands:r&&r.length>0?r:void 0,randomSeed:`web-${e}-${n}-${t}`,maxDifficultyStep:o}),i=buildQuestionFromSelected(a,t),l=i.answer.trim();if(!l||l.toLowerCase()==="manual check")return null;const u=normaliseStem$1(i.childPrompt),d=buildHintsFromEngine(a.exerciseTypeId,i.parentNote),m=u.toLowerCase(),h=parseNumericAnswer(l);if(m.includes("odd or even")){const f=l.toLowerCase().startsWith("odd")?"Odd":"Even";return{id:`eng-${a.skillId}-${t}`,strand:n,stem:u,type:"multiple_choice",options:["Odd","Even"],correctAnswer:f,hints:d,engineSelection:a,generatedAtMs:Date.now(),debugInfo:{source:"old_engine",questionId:`eng-${a.skillId}-${t}`}}}if(h!==null&&t%3===0){const f=buildNumberlineTicks(h);return{id:`eng-${a.skillId}-${t}`,strand:n,stem:u,type:"numberline",options:f,correctAnswer:String(Math.round(h)),hints:d,engineSelection:a,generatedAtMs:Date.now(),debugInfo:{source:"old_engine",questionId:`eng-${a.skillId}-${t}`}}}return h!==null?{id:`eng-${a.skillId}-${t}`,strand:n,stem:u,type:"equation",correctAnswer:String(Math.round(h)),hints:d,engineSelection:a,generatedAtMs:Date.now(),debugInfo:{source:"old_engine",questionId:`eng-${a.skillId}-${t}`}}:null}catch{return null}}function buildFallbackQuestion(n,t,e){const s=buildFallbackQuestionInner(n,t,e);return s.debugInfo={source:"fallback",questionId:s.id},s}function buildFallbackQuestionInner(n,t,e){const s=FALLBACK_QUESTION_BANK[(t-1)%FALLBACK_QUESTION_BANK.length],o=randomFromSeed(`${e}|${n}|${t}|${s.id}`);if(s.id==="q-mc-1"){const i=randomInt(o,2,8),u=shuffle([1,3,5,7],o).map(m=>String(i*100+m*10+randomInt(o,0,8))),d=String(Math.max(...u.map(m=>Number(m))));return{...s,id:`${s.id}-${hashText(`${e}-${t}`)}`,strand:n,options:u,correctAnswer:d,generatedAtMs:Date.now()}}if(s.id==="q-eq-1"){const i=randomInt(o,3,12),l=randomInt(o,2,9);return{...s,id:`${s.id}-${hashText(`${e}-${t}`)}`,strand:n,stem:`${i} + ${l} = __`,correctAnswer:String(i+l),generatedAtMs:Date.now()}}if(s.id==="q-line-1"){const i=randomInt(o,1,20),l=[2,4,6,8,10][randomInt(o,0,4)],u=i+l,d=i+l/2;return{...s,id:`${s.id}-${hashText(`${e}-${t}`)}`,strand:n,stem:`Tap the number that is halfway between ${i} and ${u}.`,options:buildNumberlineTicks(d),correctAnswer:String(d),generatedAtMs:Date.now()}}if(s.id==="q-mc-2"){const i=[30,40,50,60],l=i[randomInt(o,0,i.length-1)],u={30:"20p + 10p",40:"20p + 20p",50:"20p + 20p + 10p",60:"20p + 20p + 20p"},m=shuffle(["20p + 5p","10p + 10p + 10p","20p + 20p + 5p","50p + 5p","10p + 20p + 20p + 20p","5p + 5p + 20p"].filter(f=>!f.includes(`${l}p`)),o).slice(0,3),h=shuffle([u[l],...m],o);return{...s,id:`${s.id}-${hashText(`${e}-${t}`)}`,strand:n,stem:`Which set makes ${l}p?`,options:h,correctAnswer:u[l],generatedAtMs:Date.now()}}const r=randomInt(o,9,20),a=randomInt(o,1,Math.max(1,r-3));return{...s,id:`${s.id}-${hashText(`${e}-${t}`)}`,strand:n,type:"equation",stem:`${r} - ${a} = __`,options:void 0,correctAnswer:String(r-a),generatedAtMs:Date.now()}}const UI_STRAND_TO_BANK_STRAND={"Number & Place Value":"number_place_value","Addition & Subtraction":"addition_subtraction","Multiplication & Division":"multiplication_division",Fractions:"fractions_decimals_percentages","Money & Time":"measurement","Shape & Space":"geometry","Ratio & Proportion":"ratio_proportion","Data & Statistics":"data_statistics"},BANK_STRAND_TO_UI_STRAND=Object.fromEntries(Object.entries(UI_STRAND_TO_BANK_STRAND).map(([n,t])=>[t,n]));function uiStrandLabelFor(n,t){return BANK_STRAND_TO_UI_STRAND[n]??t}const YEAR_LABEL_TO_BAND_ID={Reception:"reception","Year 1":"year_1","Year 2":"year_2","Year 3":"year_3","Year 4":"year_4","Year 5":"year_5","Year 6":"year_6"},usedBankIds=new Map;function getUsedIds(n){let t=usedBankIds.get(n);return t||(t=new Set,usedBankIds.set(n,t)),t}function tryCanonicalBank(n,t,e,s,o,r="all",a,i,l,u,d){if(u){const A=getQuestionBankIndex().byId.get(u);return A?adaptToSessionQuestion(A,uiStrandLabelFor(A.strand_id,n)):null}if(d&&d.length>0){const A=getUsedIds(e),N=getQuestionBankIndex(),S=t>=1?d[t-1]:void 0;for(const M of[S,...d]){if(!M||A.has(M))continue;const L=N.byId.get(M);if(L)return A.add(M),adaptToSessionQuestion(L,uiStrandLabelFor(L.strand_id,n))}return null}if(a){const A=getAuthoredQuestionsForMicroSkill(a),N=getUsedIds(e),S=A.filter(L=>!N.has(L.id));if(S.length===0)return null;const M=S[t%S.length];return N.add(M.id),adaptToSessionQuestion(M,uiStrandLabelFor(M.strand_id,n))}const m=UI_STRAND_TO_BANK_STRAND[n];if(!m)return null;const h=YEAR_LABEL_TO_BAND_ID[o];if(!h)return null;const g=getQuestionBankIndex().byStrand.get(m);
62if(!g)return null;const b=g.authored_questions.filter(A=>A.typical_year_band_id===h);if(b.length===0)return null;const x=[...new Set(b.map(A=>A.micro_skill_id))],y=getUsedIds(e),E=getForcedMicroSkill()??l,k=E?selectFromBank([E],{difficultyStep:s,randomSeed:`${e}-${n}-${t}`,excludeIds:y,reviewFilter:r}):null;E&&!k&&console.info(`[session] dry-pool fallback: focus skill ${E} has no unserved question at step ${s} (filter ${r}); falling back to multi-skill selection for slot ${t}`);const T=k??selectFromBank(x,{difficultyStep:s,randomSeed:`${e}-${n}-${t}`,excludeIds:y,reviewFilter:r,skillStates:i});return T?(y.add(T.id),adaptToSessionQuestion(T,uiStrandLabelFor(T.strand_id,n))):null}function listCanonicalMicroSkillsForYear(n){const t=YEAR_LABEL_TO_BAND_ID[n];if(!t)return[];const e=getQuestionBankIndex(),s=new Set;for(const o of new Set(Object.values(UI_STRAND_TO_BANK_STRAND))){const r=e.byStrand.get(o);if(r)for(const a of r.authored_questions)a.typical_year_band_id===t&&s.add(a.micro_skill_id)}return[...s]}const BAND_ORDER=["reception","year_1","year_2","year_3","year_4","year_5","year_6"],bandRank=n=>n?BAND_ORDER.indexOf(n):-1;function microSkillBandId(n){const t=getQuestionBankIndex().byMicroSkill.get(n)??[];let e;for(const s of t){const o=bandRank(s.typical_year_band_id);o>=0&&(e===void 0||o<bandRank(e))&&(e=s.typical_year_band_id)}return e}const PLANNER_SETTLED_STATES=new Set(["secure","mastered"]),LOWER_BAND_GAP_ACCURACY=.5;function buildPlannerScope(n,t={},e={},s={}){const o=listCanonicalMicroSkillsForYear(n);if(o.length===0)return{ids:[],reviewOnly:new Set,reviewClock:new Set};const r=bandRank(YEAR_LABEL_TO_BAND_ID[n]),a=new Set(o),i=new Set,l=new Set,u=new Set([...Object.keys(t),...Object.keys(e)]);for(const d of u){if(a.has(d))continue;const m=bandRank(microSkillBandId(d));if(!(m>=0&&m<r)||(l.add(d),PLANNER_SETTLED_STATES.has(t[d]??"unseen")))continue;const h=s[d];(!Number.isFinite(h)||h<LOWER_BAND_GAP_ACCURACY)&&(a.add(d),i.add(d))}return{ids:[...a],reviewOnly:i,reviewClock:l}}async function getNextQuestion(n,t,e,s,o,r="",a="all",i,l,u,d,m){await new Promise(g=>setTimeout(g,120));const h=tryCanonicalBank(n,t,e,o,r,a,i,l,u,d,m);if(h)return h;if(a==="reviewed"||a==="approved"){const g=tryCanonicalBank(n,t,e,0,r,a,i,l,u,d,m);if(g)return g}const f=buildEngineQuestion(n,t,e,s,o);return f||buildFallbackQuestion(n,t,e)}const SECURE_STATES=new Set(["secure","mastered"]),WEAK="weak",STRAND_ORDER=["NPV","AS","MD","FDP","MEA","GEO","DS","RP"],strandRank=n=>{const t=STRAND_ORDER.indexOf(n);return t===-1?STRAND_ORDER.length:t},GAP_DAYS={unseen:0,[WEAK]:0,emerging:1,developing:7,secure:18,mastered:40},WORKING_SET=3,REVIEW_GAP_DAYS=3,REVIEW_FORCE_DAYS=10,SITTING_GAP_DAYS=30/1440;function compareMicroSkillId(n,t){const e=r=>{const[a,i,l]=r.split("."),u=d=>{const m=Number((d??"").replace(/[^0-9]/g,""));return Number.isFinite(m)?m:0};return{strand:a??"",phase:u(i),skill:u(l)}},s=e(n),o=e(t);if(s.strand!==o.strand){const r=strandRank(s.strand),a=strandRank(o.strand);return r!==a?r-a:s.strand<o.strand?-1:1}return s.phase!==o.phase?s.phase-o.phase:s.skill!==o.skill?s.skill-o.skill:n<t?-1:n>t?1:0}const gapFor=n=>GAP_DAYS[n]??0,dueScore=n=>n.daysSinceSeen-gapFor(n.state),practisedThisSitting=n=>n.daysSinceSeen<SITTING_GAP_DAYS,isDue=n=>dueScore(n)>=0&&!practisedThisSitting(n);function selectSessionMicroSkill(n,t,e={},s={}){if(n.length===0)return null;const o=s.reviewOnly??new Set,r=s.reviewClockIds??o,a=[...n].sort(compareMicroSkillId).map(M=>{const L=t[M]??"unseen",P=e[M]??1/0,F=L==="unseen"&&Number.isFinite(P)?WEAK:L;return{id:M,state:F,daysSinceSeen:P}}),i=a.filter(M=>o.has(M.id)&&!SECURE_STATES.has(M.state)&&Number.isFinite(M.daysSinceSeen)),l=[...r,...o].reduce((M,L)=>Math.min(M,e[L]??1/0),1/0),u=i.filter(M=>!practisedThisSitting(M)).sort((M,L)=>L.daysSinceSeen-M.daysSinceSeen||compareMicroSkillId(M.id,L.id))[0],d=l<SITTING_GAP_DAYS,m=u!==void 0&&l>=REVIEW_GAP_DAYS,h=a.filter(M=>!o.has(M.id)).reduce((M,L)=>Math.min(M,L.daysSinceSeen),1/0),f=l<h;if(u!==void 0&&!d&&!f&&u.daysSinceSeen>=REVIEW_FORCE_DAYS)return u.id;const b=a.filter(M=>!o.has(M.id)&&(M.state===WEAK||M.state==="emerging"||M.state==="developing")),y=b.slice(0,WORKING_SET).filter(isDue).sort((M,L)=>dueScore(L)-dueScore(M));if(y.length>
620)return y[0].id;if(b.length<WORKING_SET){const M=a.find(L=>L.state==="unseen"&&!o.has(L.id));if(M)return M.id}const E=new Set(b.slice(WORKING_SET).map(M=>M.id)),k=a.filter(M=>M.state!=="unseen"&&!E.has(M.id)&&!o.has(M.id)&&isDue(M)).sort((M,L)=>dueScore(L)-dueScore(M));if(k.length>0)return k[0].id;if(m)return u.id;const T=a.filter(M=>M.state!=="unseen"&&!E.has(M.id)&&!o.has(M.id)),A=T.filter(M=>!SECURE_STATES.has(M.state)),N=A.length>0?A:T.length>0?T:a,S=N.find(M=>!practisedThisSitting(M));return S?S.id:[...N].sort((M,L)=>L.daysSinceSeen-M.daysSinceSeen||gapFor(M.state)-gapFor(L.state))[0].id}const SAMPLE_FALLBACK_LINES={"AS.P3.M03.Q12":{default:"Make 10 first: split 5 into 2 and 3. Add 2 to 8 to make 10, then add the remaining 3. So 8 + 5 = 13."},"MEA.P6.M01.Q26":{default:"It is 6 o'clock. The short hand shows the hour — it is pointing at the 6.",byWrongAnswer:{"8 o'clock":"It is 6 o'clock. The SHORT hand shows the hour, and it points at the 6.","5 o'clock":"It is 6 o'clock. Look at the short hand — it points straight at the 6, not the 5.","7 o'clock":"It is 6 o'clock. Look at the short hand — it points straight at the 6, not the 7."}},"AS.P2.M02.Q34":{default:"The domino with 5 spots makes 5. Touch each spot once and say one number per touch.",byWrongAnswer:{4:"That domino has 4 spots — one too few. Count each spot once: the domino with 5 spots makes 5.",6:"That domino has 6 spots — one too many. Count each spot once: the domino with 5 spots makes 5."}},"MEA.P4.M02.Q47":{default:"Sam needs 11p. A 10p coin and a 1p coin make 11p together — 10 and 1 more."},"NPV.P3.M03.Q66":{default:"From smallest to largest the order is 11, 13, 16, 19. Compare the numbers one pair at a time, starting with the smallest."},"AS.P4.M04.Q51":{default:"Split 14 into 10 and 4. Add 10 to 23 to make 33, then add 4. So 23 + 14 = 37."},"MD.P2.M02.Q116":{default:"The array shows 5 × 4 = 20 — five rows with four in each row.",byWrongAnswer:{"5 + 4 = 9":"It is 5 × 4 = 20. The array shows 5 rows OF 4, so we multiply — skip-count 4, 8, 12, 16, 20.","5 × 5 = 25":"It is 5 × 4 = 20. Count how many are in one row — there are 4, not 5 — then count the 5 rows.","4 × 4 = 16":"It is 5 × 4 = 20. Count the rows — there are 5, each with 4 — then skip-count 4, 8, 12, 16, 20."}},"AS.P4.M05.Q108":{default:"Split 7 into 2 and 5. Take 2 from 62 to reach 60, then take 5 more. So 62 − 7 = 55."},"MEA.P4.M03.Q201":{default:"Mia's change should be 7p. Count up from 13p to 20p — that is 7p.",byWrongAnswer:{"Yes, 6p is right":"The change should be 7p, not 6p. Count up from 13p: 14, 15, 16, 17, 18, 19, 20 — seven steps.","No, it should be 8p":"The change should be 7p, not 8p. Count up from 13p to 20p — exactly seven pennies.","No, it should be 33p":"The change should be 7p. Change is the gap between the price and the coin, not the two added — count up from 13p to 20p."}},"FDP.P3.M02.Q128":{default:"The true fraction is 2/4 — two of the four equal parts are shaded.",byWrongAnswer:{"1/4":"It is 2/4 — count the shaded parts again: two out of four equal parts are shaded.","3/4":"It is 2/4. Count only the SHADED parts for the top number — two are shaded, out of 4 parts.","2 white parts":"It is 2/4. The fraction counts the SHADED parts, not the white ones — two shaded out of four."}},"MD.P3.M01.Q104":{default:"3 × 8 = 24. Three eights: double 8 is 16, then one more 8 makes 24."},"AS.P5.M01.Q106":{default:"357 + 236 = 593. Ones: 7 + 6 = 13, so write 3 and carry 1. Tens: 5 + 3 + 1 = 9. Hundreds: 3 + 2 = 5."},"DS.P2.M05.Q301":{default:"The bars should show Tigers 4, Lions 6, Bears 3 — each bar as tall as its number. Lions scored the most, so its bar is tallest."},"FDP.P6.M01.Q304":{default:"The total is 5/6. The pieces are all sixths, so only the top numbers add: 2 + 3 = 5 sixths.",byWrongAnswer:{"5/12":"It is 5/6. Zara added the bottoms too — but the pieces stay sixths. Only the tops add: 2 + 3 = 5, so 5/6.","6/6":"It is 5/6. Add just the tops: 2 + 3 = 5 shaded sixths — one sixth is still empty, so it is not a whole.","1/6":"It is 5/6. The question adds the fractions, so the tops add: 2 + 3 = 5 sixths."}},"MEA.P5.M05.Q102":{default:"The perimeter is 40. Perimeter goes all the way round — add every side, not just some of them."},"DS.P3.M02.Q200":{default:"The Reds scored 7. The axis goes up in fives, so a bar between the 5 and the 10 lines needs reading carefully.",byWrongAnswer:{5:"It is 7. The bar goes PAST the 5 line — each small step is worth 1, so read where the top really is.",10:"It is 7. The bar stops short of the 10 line — work out what each step on the axis is worth, then read the top.",2:"It is 7. Check what the axis numbers mean — they count goals in fives, and this bar tops out at 7."}},"MD.P3.M02.Q200":{default:"7 × 8 = 56. If it slips your mind, build it: 7 × 4 = 28, then double it."},"FDP.P4.M04.Q223":{default:"Shortest to longest: 2.25 m, 2.3 m, 2.4 m. Compare the tenths first — 2 tenths is less than 3 tenths, even though 25 looks bigger than 3."},"GEO.P4.M02.Q302":{default:"The treasure is at (4, 3) — along the corridor to 4, then up the stairs to 3.",byWrongAnswer:{"(3, 4)":"It is (4, 3). The ACROSS number comes first, then the up number — along the corridor, then up the stairs.","(5, 3)":"It is (4, 3). Check the across number against the bottom axis — the X sits above the 4, not the 5.","(4, 2)":"It is (4, 3). Check the up number against the side axis — the X sits level with the 3."}},"MD.P4.M02.Q100":{default:"345 × 6 = 2070. Multiply each part: 300 × 6 = 1800, 40 × 6 = 240, 5 × 6 = 30, then add them."},"NPV.P6.M05.Q114":{default:"Largest to smallest: 0.9, 0.7, 0.5, 0.2. Compare the tenths digit — 9 tenths is the biggest."},"MD.P4.M05.Q102":{default:"9368 ÷ 4 = 2342. Exchange the leftover thousand for ten hundreds, making thirteen hundreds. The
62n exchange the leftover hundred for ten tens, making sixteen tens."},"FDP.P6.M02.Q102":{default:"It makes 3 quarters. A half is the same as 2 quarters, and 2 quarters + 1 quarter = 3 quarters."},"GEO.P5.M04.Q101":{default:"The missing angle is 55 degrees. Angles on a straight line add to 180, so it is 180 − 125.",byWrongAnswer:{"235 degrees":"It is 55 degrees. A straight line makes 180, not 360 — the missing angle is 180 − 125.","90 degrees":"It is 55 degrees. The two angles add to 180 (a straight line), so the missing one is 180 − 125.","125 degrees":"It is 55 degrees. The two angles are not the same size — together they make 180, so 180 − 125 = 55."}},"MD.P4.M04.Q106":{default:"34 × 26 = 884. Split it: 34 × 20 = 680 and 34 × 6 = 204, then add them."},"FDP.P6.M06.Q101":{default:"Smallest to largest: 0.25, 30%, 1/2, 0.6. Write them all as decimals first: 30% is 0.30 and 1/2 is 0.50, then the order is easy to see."},"DS.P4.M03.Q113":{default:"25 shoppers chose apples. The apple sector is a quarter of the circle, and a quarter of 100 is 25.",byWrongAnswer:{50:"It is 25. The apple sector is a quarter of the circle, not half — a quarter of 100 shoppers is 25.",4:"It is 25. The quarter is a fraction OF the 100 shoppers — 100 ÷ 4 = 25, not the 4 itself."}},"GEO.P6.M03.Q109":{default:"The missing angle is 35°. Angles in a triangle add to 180: 85 + 60 = 145, and 180 − 145 = 35.",byWrongAnswer:{"95°":"It is 35°. Both given angles come off first: 85 + 60 = 145, then 180 − 145 = 35.","145°":"It is 35°. Adding 85 + 60 = 145 is the first step — then subtract from 180 to find what is missing.","215°":"It is 35°. A triangle's angles total 180, not 360: 180 − 85 − 60 = 35."}},"RP.P3.M02.Q114":{default:"30 red and 18 blue. The parts make 5 + 3 = 8, so one part is 48 ÷ 8 = 6; red gets 5 × 6 and blue gets 3 × 6.",byWrongAnswer:{"18 red and 30 blue":"It is 30 red and 18 blue. Red is named first with 5 parts, so red takes the bigger share.","24 red and 24 blue":"It is 30 red and 18 blue. The ratio 5:3 is not an equal split — one part is 48 ÷ 8 = 6, so 30 and 18.","5 red and 3 blue":"It is 30 red and 18 blue. The 5 and 3 are parts, not sweets — one part is 48 ÷ 8 = 6."}},"MEA.P7.M05.Q110":{default:"Volume is length × width × height. One layer is 5 × 4 = 20 cubes, and 3 layers make 60 cm³."}};function sampleFallbackLine(n,t){const e=SAMPLE_FALLBACK_LINES[n];return e?t&&e.byWrongAnswer?.[t]?e.byWrongAnswer[t]:e.default:null}function recordNextAction(n,t){n&&buildApiAuthHeaders().then(e=>fetch("/api/tutor-next-action",{method:"POST",headers:{"Content-Type":"application/json",...e},body:JSON.stringify({childId:n,outcome:t})})).then(e=>{e.ok||reportCloudWriteFailure("/api/tutor-next-action",{kind:"http",status:e.status},logClientError)}).catch(e=>{reportCloudWriteFailure("/api/tutor-next-action",{kind:"throw",error:e},logClientError)})}function buildFallback(n,t,e="reveal",s,o){if(e==="hint")return"Have another careful look — count step by step. Now try yours.";const r=s?sampleFallbackLine(s,o):null;if(r)return r;const a=n.replace("__",t);return`The answer was ${t}! ${/[.!?]$/.test(a)?a:`${a}.`}`}async function getTutorExplanation(n,t,e,s=3e3,o="",r={}){const a=new AbortController,i=setTimeout(()=>a.abort(),s),l={questionId:r.questionId??"unknown",questionStem:n,wrongAnswer:t,correctAnswer:e,yearGroup:o,childId:r.childId,hintTier:r.hintTier??"wrong_exhausted",mode:r.mode??"reveal"};try{const u=await buildApiAuthHeaders();return(await(await fetch("/api/tutor",{method:"POST",headers:{"Content-Type":"application/json",...u},body:JSON.stringify(l),signal:a.signal})).json()).explanation??buildFallback(n,e,r.mode??"reveal",r.questionId,t)}catch{return buildFallback(n,e,r.mode??"reveal",r.questionId,t)}finally{clearTimeout(i)}}const WINDOW_SIZE=6,UP_THRESHOLD=.8,DOWN_THRESHOLD=.33,MIN_ATTEMPTS_BEFORE_STEP_UP=6,MAX_STEP_UPS_PER_SESSION=1,DEFAULT_DIFFICULTY_TUNING={windowSize:WINDOW_SIZE,upThreshold:UP_THRESHOLD,downThreshold:DOWN_THRESHOLD,minAttemptsBeforeStepUp:MIN_ATTEMPTS_BEFORE_STEP_UP,maxStepUpsPerSession:MAX_STEP_UPS_PER_SESSION,ceilingOffset:1,floorOffset:0},WITHIN_BAND_FLOOR=-2,WITHIN_BAND_CAP=2,YEAR_GROUP_START_STEPS={Reception:-1,"Year 1":-1,"Year 2":-1,"Year 3":-1,"Year 4":-1,"Year 5":-1,"Year 6":-1},DEFAULT_START_STEP=-1;function initDifficultyState(n,t){const e={...DEFAULT_DIFFICULTY_TUNING,...t?.tuning},s=YEAR_GROUP_START_STEPS[n]??DEFAULT_START_STEP,o=Math.max(WITHIN_BAND_FLOOR-e.floorOffset,WITHIN_BAND_FLOOR),r=Math.max(o,Math.min(t?.initialStep??s,WITHIN_BAND_CAP));return{step:r,floor:o,ceiling:Math.min(r+e.ceilingOffset,WITHIN_BAND_CAP),stepsUpThisSession:0,recentOutcomes:[],totalResolved:0,tuning:e}}const SETTLING_STEPPING_TUNING={windowSize:4,upThreshold:.75,minAttemptsBeforeStepUp:4,maxStepUpsPerSession:3,ceilingOffset:3,floorOffset:1},CALIBRATED_STEPPING_TUNING={ceilingOffset:1,downThreshold:.5};function initDifficultyStateForPupil(n,t,e){return t?t.sessionsCompleted<e?initDifficultyState(n,{tuning:SETTLING_STEPPING_TUNING,initialStep:t.lastEndStep??void 0}):t.lastEndStep===null?initDifficultyState(n):initDifficultyState(n,{tuning:CALIBRATED_STEPPING_TUNING,initialStep:t.lastEndStep}):initDifficultyState(n)}function recordQuestionOutcome(n,t){const e=n.tuning??DEFAULT_DIFFICULTY_TUNING,s=[...n.recentOutcomes,t].slice(-e.windowSize),o=n.totalResolved+1;let{step:r,stepsUpThisSession:a}=n;if(s.length>
62=e.windowSize){const i=s.filter(Boolean).length/s.length;i>=e.upThreshold&&o>=e.minAttemptsBeforeStepUp&&a<e.maxStepUpsPerSession&&r<n.ceiling?(r+=1,a+=1):i<=e.downThreshold&&r>n.floor&&(r-=1)}return{...n,step:r,stepsUpThisSession:a,recentOutcomes:s,totalResolved:o}}function mean(n){return n.length===0?0:n.reduce((t,e)=>t+e,0)/n.length}function detectLatencyRiseAcrossRound(n){const t=n.filter(l=>Number.isFinite(l)&&l>0);if(t.length<4)return{fired:!1,firstHalfMeanMs:mean(t),secondHalfMeanMs:mean(t),factor:NaN,sampleSize:t.length};const e=Math.floor(t.length/2),s=t.slice(0,e),o=t.slice(e),r=mean(s),a=mean(o),i=r>0?a/r:NaN;return{fired:Number.isFinite(i)&&i>=PROGRESS_CONFIG.stuck.latencyIncreaseFactor,firstHalfMeanMs:r,secondHalfMeanMs:a,factor:i,sampleSize:t.length}}function detectSessionAccuracyFloor(n,t){if(t<=0)return{fired:!1,accuracy:NaN,correctCount:n,resolvedCount:t};const e=n/t;return{fired:e<PROGRESS_CONFIG.stuck.sessionAccuracyFloor,accuracy:e,correctCount:n,resolvedCount:t}}function detectMisconceptionRepeats(n){const t=PROGRESS_CONFIG.stuck.misconceptionRepeats,e=Object.entries(n).filter(([,s])=>s>=t).map(([s,o])=>({tag:s,count:o})).sort((s,o)=>o.count-s.count);return{fired:e.length>0,repeatedTags:e}}function detectSessionStuckSignals(n,t,e,s={}){return{latencyRise:detectLatencyRiseAcrossRound(n),sessionAccuracyFloor:detectSessionAccuracyFloor(t,e),misconceptionRepeat:detectMisconceptionRepeats(s)}}function withHelpUsed(n,t){return t?{...n,help_used:t}:n}const reviewFilterOverride=typeof window<"u"?new URLSearchParams(window.location.search).get("reviewFilter"):null,defaultReviewFilter=reviewFilterOverride==="all"?"all":"approved";function createClientSessionId(){const n=typeof globalThis.crypto>"u"?null:globalThis.crypto;if(typeof n?.randomUUID=="function")return n.randomUUID();const t=new Uint8Array(16);if(n)n.getRandomValues(t);else for(let s=0;s<t.length;s+=1)t[s]=Math.floor(Math.random()*256);t[6]=t[6]&15|64,t[8]=t[8]&63|128;const e=Array.from(t,s=>s.toString(16).padStart(2,"0")).join("");return[e.slice(0,8),e.slice(8,12),e.slice(12,16),e.slice(16,20),e.slice(20)].join("-")}function reportSessionStartFailure(n){if(isRequiredCloudWriteError(n))return;const t=n instanceof Error?n:new Error(String(n)),e=new Error(t.message);e.stack=t.stack,e.name=t.name==="Error"?"SessionStartFailed":t.name,logClientError("cloud_write_failed",e,{tag:"/api/sessions/start"})}function useSessionEngine(n,t,e,s,o,r){const{progressSavingRequired:a=!1,onStartBackendSession:i,onRecordAttempt:l,onLoadSkillStates:u,onLoadSkillRecency:d,onLoadSkillAccuracy:m,onLoadDifficultyCarry:h,onCompleteBackendSession:f}=s,g=reactExports.useRef(a);a&&(g.current=!0);const b=g.current,[x,y]=reactExports.useState([]),[E,k]=reactExports.useState(()=>createInitialPupilState(t)),[T,A]=reactExports.useState(""),[N,S]=reactExports.useState(0),[M,L]=reactExports.useState({}),[P,F]=reactExports.useState({}),[D,U]=reactExports.useState({}),[q,V]=reactExports.useState({}),[O,z]=reactExports.useState(0),[K,Z]=reactExports.useState({}),[Q,B]=reactExports.useState({}),[G,X]=reactExports.useState({}),[ne,se]=reactExports.useState({}),[te,oe]=reactExports.useState([]),[le,ae]=reactExports.useState(()=>new Set),ie=reactExports.useRef(0),[we,ke]=reactExports.useState(!1),[Te,Se]=reactExports.useState(!0),[$e,Re]=reactExports.useState(b?"starting":"not_required"),[ye,Ne]=reactExports.useState(!1),[Ye,Ge]=reactExports.useState(!1),[Fe,Xe]=reactExports.useState(!1),[Rt,ge]=reactExports.useState(!1),[Ie,ft]=reactExports.useState(0),[wt,mt]=reactExports.useState(null),et=reactExports.useRef(null),Ke=reactExports.useRef(Date.now()),Pt=reactExports.useRef(createClientSessionId()),[ht,lt]=reactExports.useState(()=>initDifficultyState(e)),[ct,$t]=reactExports.useState({}),ot=reactExports.useRef([]),nn=reactExports.useRef(0),It=reactExports.useRef(0),ln=reactExports.useRef({}),Et=reactExports.useRef(null),Qe=reactExports.useRef(void 0),Ut=reactExports.useCallback(async xe=>{if(!i){const tt=new Error("Required session-start handler is unavailable");throw tt.name="SessionStartUnavailable",tt}const Ve=await i({childId:t,sessionConfig:n,sessionId:xe});if(Ve.sessionId!==xe||Ve.childId!==t){const tt=new Error("Session start returned a different session or child");throw tt.name="SessionStartIdentityMismatch",tt}return Ve},[i,t,n]),Xt=reactExports.useCallback(async()=>
62{if(b){Re("starting"),Se(!0);try{const xe=await Ut(Pt.current);et.current=xe,mt(xe),Re("ready"),Et.current=Date.now(),Se(!1)}catch(xe){reportSessionStartFailure(xe),Re("failed")}}},[b,Ut]);reactExports.useEffect(()=>{let xe=!0;return(async()=>{Se(!0),Re(b?"starting":"not_required");const tt=createClientSessionId();if(await new Promise(Tt=>setTimeout(Tt,0)),!xe)return;const Le=b?Ut(tt).then(Tt=>({ok:!0,started:Tt}),Tt=>({ok:!1,error:Tt})):null,He=`${Date.now()}-${Math.random().toString(36).slice(2,10)}`,gt=createInitialPupilState(t);let at=null;if(h)try{at=await Promise.race([h(t),new Promise(Tt=>setTimeout(()=>Tt(null),4e3))])}catch{at=null}if(!xe)return;const be=initDifficultyStateForPupil(e,at,PROGRESS_CONFIG.settling.sessions),We=!!at&&at.sessionsCompleted<PROGRESS_CONFIG.settling.sessions;Ge(We);let De={},An={},Jt={},Ct=!1;if(u){const Tt=qt=>Promise.race([qt,new Promise(jt=>setTimeout(()=>jt(null),4e3))]),xs=m?Tt(m(t).then(qt=>qt??{})).catch(()=>null):Promise.resolve(null);try{const qt=await Tt(Promise.all([u(t).then(jt=>jt??{}),d?d(t).then(jt=>jt??{}):Promise.resolve({})]));qt!==null&&(De=qt[0],An=qt[1],Ct=!0)}catch{Ct=!1}Jt=await xs??{}}if(!xe)return;let Bs;if(!We&&!n.targetMicroSkillId&&Ct){const Tt=buildPlannerScope(e,De,An,Jt);Bs=selectSessionMicroSkill(Tt.ids,De,An,{reviewOnly:Tt.reviewOnly,reviewClockIds:Tt.reviewClock})??void 0}Qe.current=Bs;const xn=n.strands[0]??"Number & Place Value",Kn=await getNextQuestion(xn,1,He,gt,be.step,e,n.reviewFilter??defaultReviewFilter,n.targetMicroSkillId,De,Bs,n.targetQuestionId,n.curatedQuestionIds);if(xe){if(A(He),k(gt),lt(be),$t(De),y([Kn]),S(0),L({}),F({}),U({}),V({}),z(0),Z({}),B({}),X({}),se({}),oe([]),ae(new Set),ie.current=0,ft(0),mt(null),et.current=null,Ke.current=Date.now(),Pt.current=tt,ot.current=[],nn.current=0,It.current=0,ln.current={},Et.current=null,ke(!1),Ne(!1),Le){const Tt=await Le;if(!xe)return;if(!Tt.ok){reportSessionStartFailure(Tt.error),Re("failed");return}et.current=Tt.started,mt(Tt.started),Re("ready")}Et.current=Date.now(),Se(!1)}})(),()=>{xe=!1}},[u,d,m,h,b,t,n,Ut,e]),reactExports.useEffect(()=>{stopSpeaking(),Xe(!1),ge(!1)},[N]),reactExports.useEffect(()=>{Et.current=Date.now()},[N,Te]);const Ce=x[N],kt=Ce?M[Ce.id]??null:null,sn=Ce?ne[Ce.id]??null:null,En=Ce?D[Ce.id]??"idle":"idle",wn=Ce?q[Ce.id]??0:0,Zt=Ce?P[Ce.id]??0:0,$n=Ce&&Zt>0?Ce.hints[Math.min(Zt,Ce.hints.length)-1]:void 0,cn=Ce?getMaxAttempts(Ce):0,kn=!Ce||Zt>=Ce.hints.length||En==="correct"||wn>=cn,Tn=En==="correct"||wn>=cn,zn=xe=>{Ce&&(xe!==""&&(En==="correct"||wn>=cn)||(L(Ve=>({...Ve,[Ce.id]:xe})),En==="incorrect"&&wn<cn&&U(Ve=>({...Ve,[Ce.id]:"idle"}))))},Sn=()=>{!Ce||kn||F(xe=>({...xe,[Ce.id]:(xe[Ce.id]??0)+1}))},Rn=async()=>{if(!(b&&$e!=="ready")&&!(!Ce||!kt||we||wn>=cn)){ke(!0),stopSpeaking();try{await Yn(Ce,kt)}catch(xe){const Ve=xe instanceof Error?xe:new Error(String(xe)),tt=new Error(Ve.message);tt.stack=Ve.stack,tt.name=`CheckThrow:${Ce.id}`.slice(0,80),logClientError("error",tt),U(Le=>({...Le,[Ce.id]:"idle"}))}finally{ke(!1)}}},Yn=async(xe,Ve)=>{const tt=Date.now(),Le=Math.max(50,tt-(Et.current??xe.generatedAtMs));Et.current=tt,await new Promise(be=>setTimeout(be,220));const He=wn+1;V(be=>({...be,[xe.id]:He})),se(be=>({...be,[xe.id]:Ve.trim()}));const gt=isAnswerCorrect(Ve,xe),at=gt?[]:xe.optionMisconceptionTags?.[Ve.trim()]??xe.misconceptionTags??[];for(const be of at)ln.current[be]=(ln.current[be]??0)+1;if(xe.engineSelection){const be={pupilId:E.pupilId,skillId:xe.engineSelection.skillId,exerciseTypeId:xe.engineSelection.exerciseTypeId,generatorId:xe.engineSelection.generatorId,params:xe.engineSelection.params,response:Ve.trim(),correct:gt,latencyMs:Le,hintLevelUsed:Zt,misconceptionTags:at,timestamp:new Date().toISOString()},We=submitAttempt(E,be);k(We.pupil)}if(l&&l({childId:t,sessionId:et.current?.sessionId??Pt.current,strand:xe.strand,skillId:xe.engineSelection?.skillId??null,exerciseTypeId:xe.engineSelection?.exerciseTypeId??null,generatorId:xe.engineSelection?.generatorId??null,questionUid:xe.id,questionPosition:N+1,prompt:xe.stem,params:withHelpUsed(xe.engineSelection?.params??{},s.getHelpUsed?.(xe.id)??null),responseText:Ve.trim(),isCorrect:gt,hintLevelUsed:Zt,latencyMs:Le,misconceptionTags:at,occurredAt:new Date().toISOString()}).then(be=>{be&&$t(We=>({...We,[be.skillId]:be.state}))}).catch(be=>{logClientError("cloud_write_failed",be,{tag:"/api/attempts"})}),gt){const be=getPointsForCorrect(xe,He,Zt);be>0&&z(De=>De+be),ft(De=>De+1),U(De=>({...De,[xe.id]:"correct"}));const We=He===1&&Zt===0;lt(De=>recordQuestionOutcome(De,We)),ot.current.push(Le),nn.current+=1,It.current+=1,recordNextAction(t,"correct");return}He>=cn&&!G[xe.id]&&(X(be=>({...be,[xe.id]:!0})),Z(be=>({...be,[xe.strand]:(be[xe.strand]??0)+1})),B(be=>{const We=be[xe.strand]??[],De=`${xe.stem} (answered: ${Ve.trim()})`;return We.includes(De)?be:{...be,[xe.strand]:[...We,De].slice(0,5)}}),lt(be=>recordQuestionOutcome(be,!1)),ot.current.push(Le),It.current+=1,recordNextAction(t,"wrong_again")),U(be=>({...be,[xe.id]:"incorrect"}))},Gt=async()=>{const xe=N+1;if(xe>=effectiveQuestionCount(n)){const Le=detectSessionStuckSignals(ot.current,nn.current,It.current,ln.current);
62if(Le.latencyRise.fired&&console.warn(`[stuck] §5.1 #3 latency-rise: 1st half ${Math.round(Le.latencyRise.firstHalfMeanMs)}ms, 2nd half ${Math.round(Le.latencyRise.secondHalfMeanMs)}ms, factor ${Le.latencyRise.factor.toFixed(2)} (n=${Le.latencyRise.sampleSize})`),Le.sessionAccuracyFloor.fired&&console.warn(`[stuck] §5.1 #5 session-accuracy-floor: ${Le.sessionAccuracyFloor.correctCount}/${Le.sessionAccuracyFloor.resolvedCount} = ${(Le.sessionAccuracyFloor.accuracy*100).toFixed(0)}%`),Le.misconceptionRepeat.fired&&console.warn("[stuck] §5.1 #4 misconception-repeat: "+Le.misconceptionRepeat.repeatedTags.map(be=>`${be.tag}×${be.count}`).join(", ")),f&&(et.current??wt)){const be=(et.current??wt).sessionId;f({sessionId:be,completed:!0,durationSeconds:Math.max(1,Math.round((Date.now()-Ke.current)/1e3)),totalQuestions:effectiveQuestionCount(n),resolvedQuestions:xe,correctPoints:O,metadata:{stuck_signals:{latency_rise:{fired:Le.latencyRise.fired,factor:Number.isFinite(Le.latencyRise.factor)?Number(Le.latencyRise.factor.toFixed(2)):null,sample_size:Le.latencyRise.sampleSize},accuracy_floor:{fired:Le.sessionAccuracyFloor.fired,correct:Le.sessionAccuracyFloor.correctCount,resolved:Le.sessionAccuracyFloor.resolvedCount},misconception_repeat:{fired:Le.misconceptionRepeat.fired,tags:Le.misconceptionRepeat.repeatedTags}}}})}const He=[];for(let be=0;be<effectiveQuestionCount(n);be+=1){const We=x[be];if(!We){He.push("skipped");continue}const De=D[We.id]??"idle",An=q[We.id]??0,Jt=getMaxAttempts(We);De==="correct"&&An<=1?He.push("first"):De==="correct"?He.push("retry"):De==="incorrect"&&An>=Jt?He.push("miss"):He.push("skipped")}const gt=He.map((be,We)=>{const De=x[We];return De?{stem:De.stem,yourAnswer:ne[De.id]??null,correctAnswer:De.correctAnswer,outcome:be,tip:De.hints[0],visualData:De.visual_data,questionId:De.debugInfo?.questionId??De.id}:null}),at=He.filter(be=>be==="first"||be==="retry").length;o({total:effectiveQuestionCount(n),correct:at,strands:n.strands,sessionLabel:buildSessionLabel(n.strands),wrongByTheme:K,wrongExamplesByTheme:Q,perQuestion:He,questionRecaps:gt,endDifficultyStep:ht.step});return}if(xe<x.length){S(xe);return}const Ve=effectiveQuestionCount(n)-xe;if(te.length>0&&te.length>=Ve){const[Le,...He]=te;oe(He),y(gt=>[...gt,Le]),S(xe);return}Ne(!0);const tt=n.strands[xe%n.strands.length]??"Number & Place Value";try{const Le=await getNextQuestion(tt,xe+1,T,E,ht.step,e,n.reviewFilter??defaultReviewFilter,n.targetMicroSkillId,ct,Qe.current,n.targetQuestionId,n.curatedQuestionIds);y(He=>[...He,Le]),S(xe)}finally{Ne(!1)}},rt=async()=>{!Ce||!Tn||await Gt()},At=!!Ce&&!Tn&&!we&&!ye&&!le.has(Ce.id)&&N<effectiveQuestionCount(n)-1,un=async()=>{if(!Ce||!At)return;const xe=Ce;ae(He=>new Set(He).add(xe.id)),U(He=>({...He,[xe.id]:"idle"}));const Ve=[...te,xe],tt=effectiveQuestionCount(n),Le=tt-N;if(Ve.length>=Le){const[He,...gt]=Ve;oe(gt),y(at=>{const be=[...at];return be[N]=He,be}),Et.current=Date.now();return}oe(Ve),Ne(!0);try{const He=n.strands[N%n.strands.length]??"Number & Place Value",gt=new Set([...x,...Ve].map(We=>We.id));let at=null;for(let We=0;We<3&&!at;We+=1){ie.current+=1;const De=await getNextQuestion(He,tt+ie.current,T,E,ht.step,e,n.reviewFilter??defaultReviewFilter,n.targetMicroSkillId,ct,Qe.current,n.targetQuestionId,n.curatedQuestionIds);gt.has(De.id)||(at=De)}const be=at??Ve[0];at||oe(Ve.slice(1)),y(We=>{const De=[...We];return De[N]=be,De}),Et.current=Date.now()}finally{Ne(!1)}},On=()=>{Ce&&Gt()},as=()=>{const xe=Object.values(q).filter(Le=>Le>0).length,Ve=Object.values(M).filter(Le=>Le.trim().length>0).length;if(!((xe>0||Ve>0||N>0)&&typeof window<"u"&&!window.confirm("Leave this session? Progress from this unfinished round will not be recorded."))){if(stopSpeaking(),Xe(!1),ge(!1),f&&(et.current??wt)){const Le=(et.current??wt).sessionId;f({sessionId:Le,completed:!1,durationSeconds:Math.max(1,Math.round((Date.now()-Ke.current)/1e3)),totalQuestions:effectiveQuestionCount(n),resolvedQuestions:N,correctPoints:O})}r({strand:Ce?.strand??n.strands[0]??"Number & Place Value",totalQuestions:effectiveQuestionCount(n),currentQuestion:N+1,answeredQuestions:Ve,attemptedQuestions:xe,playedAtIso:new Date().toISOString()})}};return{questions:x,currentIndex:N,currentQuestion:Ce,selectedAnswer:kt,lastCheckedAnswer:sn,status:En,attempts:wn,shownHintCount:Zt,hintText:$n,hintDisabled:kn,canAdvance:Tn,scorePoints:O,isLoading:Te,sessionSaveStatus:$e,retrySessionStart:Xt,isLoadingNext:ye,isCalibrating:Ye,
62isChecking:we,celebrationKey:Ie,isSpeakingQuestion:Fe,isSpeakingHint:Rt,sessionPupil:E,wrongByTheme:K,wrongExamplesByTheme:Q,difficultyStep:ht.step,backendSessionId:wt?.sessionId??null,canSaveForLater:At,onChangeAnswer:zn,handleHint:Sn,handleCheck:Rn,handleNext:rt,handleSaveForLater:un,handleSkipBroken:On,handleExitToMenu:as,setIsSpeakingQuestion:Xe,setIsSpeakingHint:ge}}const CORRECT_FIRST_ATTEMPT=["Yes, that's right.","That's it — well done.","Correct. Well done.","Yes, that's the right answer.","That's right. Good thinking.","Yes — well done.","Good — that's it.","That's right. Good work.","Yes, well thought out.","Correct — onto the next one."],CORRECT_RETRY=["That's it — you worked it out.","Yes — well done for sticking with it.","That's right. Good thinking — you got there.","Yes, you got there. Well done.","That's it. Good for trying again.","Well done — that's the right answer."],WRONG_FIRST=["Not quite. Have another look.","Not this time — try again.","Have another go.","Take another look at it.","Not quite — have another think.","Try once more."],QUESTION_SAVED=["Saved — that one will come back at the end of the round.","We'll come back to that one at the end.","No rush. You'll see that one again before we finish."],HINT_INTRO=["Here's a clue:","Try this:","Something to think about:","Here's an idea:"],THINKING_MESSAGE="One moment — let me think how to explain this…",SESSION_START={"Number & Place Value":"Let's practise numbers today.","Addition & Subtraction":"Adding and taking away today. Let's begin.",Fractions:"Today we'll work on fractions.","Money & Time":"Today we'll think about money and time.","Shape & Space":"Today we'll work on shapes."},DEFAULT_SESSION_START="Let's begin.";function pickLine(n){return n.length===0?"":n[Math.floor(Math.random()*n.length)]??n[0]??""}function getSessionStartMessage(n){return SESSION_START[n]??DEFAULT_SESSION_START}function getSessionCompleteMessage(n){return n>=80?"Really good work today — well done.":n>=60?"Good work today. You made solid progress.":n>=40?"That was a tough one. Well done for sticking with it.":"Practice makes these easier. We'll keep going next time."}function buildHintMessage(n){return`${pickLine(HINT_INTRO)} ${n}`}function useTutor(n="Year 1",t,e){const[s,o]=reactExports.useState(""),[r,a]=reactExports.useState("idle"),[i,l]=reactExports.useState("chat"),u=reactExports.useRef(null),d=reactExports.useRef(0),m=reactExports.useRef(n);m.current=n;const h=reactExports.useRef(t);h.current=t;const f=reactExports.useRef(e);f.current=e;const g=reactExports.useRef(null),b=reactExports.useCallback(x=>{u.current!==null&&(clearTimeout(u.current),u.current=null);const y=++d.current,E=()=>d.current===y,k=(A,N,S)=>{o(A),a(N),l("chat"),u.current=setTimeout(()=>{o(""),a("idle"),u.current=null},S)},T=(A,N)=>{o(A),a(N),l("chat")};switch(x.type){case"session_start":k(getSessionStartMessage(x.strand),"idle",5e3);break;case"correct_first":T(pickLine(CORRECT_FIRST_ATTEMPT),"happy");break;case"correct_retry":T(pickLine(CORRECT_RETRY),"happy");break;case"wrong_first":T(pickLine(WRONG_FIRST),"encouraging");break;case"wrong_exhausted":{o(THINKING_MESSAGE),a("thinking"),l("explanation"),getTutorExplanation(x.questionStem,x.wrongAnswer,x.correctAnswer,8e3,m.current,{questionId:x.questionId,hintTier:"wrong_exhausted",mode:"reveal",childId:f.current}).then(A=>{E()&&(o(A),a("encouraging"),h.current?.(A,"reveal"))});break}case"explain_request":{o(THINKING_MESSAGE),a("thinking"),l("explanation"),getTutorExplanation(x.questionStem,x.wrongAnswer,x.correctAnswer,8e3,m.current,{questionId:x.questionId,hintTier:"wrong_exhausted",mode:"reveal",childId:f.current}).then(A=>{E()&&(o(A),a("encouraging"),h.current?.(A,"reveal"))});break}case"prefetch_tier_model":{const A=x.questionId??"unknown";if(g.current?.questionId===A)break;const N=getTutorExplanation(x.questionStem,x.wrongAnswer??"",x.correctAnswer,12e3,m.current,{questionId:x.questionId,hintTier:"model",mode:"hint",childId:f.current});g.current={questionId:A,promise:N};break}case"hint_tier_model":{const A=x.questionId??"unknown",N=g.current?.questionId===A?g.current.promise:null;g.current=null,o(THINKING_MESSAGE),a("thinking"),l("clue"),(N??getTutorExplanation(x.questionStem,x.wrongAnswer??"",x.correctAnswer,8e3,m.current,{questionId:x.questionId,hintTier:"model",mode:"hint",childId:f.current})).then(M=>
62{E()&&(o(M),a("hint"),h.current?.(M,"hint"))});break}case"hint":o(buildHintMessage(x.hintText)),a("hint"),l("clue");break;case"session_complete":o(getSessionCompleteMessage(x.scorePercent)),a("happy"),l("chat");break;case"question_saved":k(pickLine(QUESTION_SAVED),"idle",4e3);break;case"question_advance":o(""),a("idle"),l("chat"),g.current=null;break}},[]);return{tutorMessage:s,tutorMood:r,tutorKind:i,fireTutorEvent:b}}const objects="_objects_rsht6_20 _surface_rsht6_16 _palette_rsht6_4",mixed="_mixed_rsht6_30 _palette_rsht6_4",sequence$3="_sequence_rsht6_35 _surface_rsht6_16 _palette_rsht6_4",equation="_equation_rsht6_48 _palette_rsht6_4",equationCompact="_equationCompact_rsht6_63",equationTerm="_equationTerm_rsht6_64",equationMissing="_equationMissing_rsht6_64",equationOperator="_equationOperator_rsht6_77",partWhole="_partWhole_rsht6_84 _palette_rsht6_4",partWholeSvg="_partWholeSvg_rsht6_85",partKnown="_partKnown_rsht6_86",partMissing="_partMissing_rsht6_87",partLabel="_partLabel_rsht6_88",partMissingLabel="_partMissingLabel_rsht6_88",numberLine="_numberLine_rsht6_91 _surface_rsht6_16 _palette_rsht6_4",comparison$3="_comparison_rsht6_96 _surface_rsht6_16 _palette_rsht6_4",story="_story_rsht6_106 _palette_rsht6_4",storyObjects="_storyObjects_rsht6_118",storyObject="_storyObject_rsht6_118",storyRemoved="_storyRemoved_rsht6_120",storyMultiplier="_storyMultiplier_rsht6_121",storyCaption="_storyCaption_rsht6_122",pictogram="_pictogram_rsht6_124 _surface_rsht6_16 _palette_rsht6_4",pictogramKey="_pictogramKey_rsht6_125",pictogramLabel="_pictogramLabel_rsht6_126",pictogramLane="_pictogramLane_rsht6_127",pictogramLaneAlt="_pictogramLaneAlt_rsht6_128",clock="_clock_rsht6_130 _surface_rsht6_16 _palette_rsht6_4",clockFace="_clockFace_rsht6_131",table="_table_rsht6_133 _surface_rsht6_16 _palette_rsht6_4",order$1="_order_rsht6_140 _surface_rsht6_16 _palette_rsht6_4",orderInstruction="_orderInstruction_rsht6_141",orderDirection="_orderDirection_rsht6_148",orderArrow="_orderArrow_rsht6_156",orderSlots="_orderSlots_rsht6_161",exampleObjects="_exampleObjects_rsht6_199 _palette_rsht6_4",exampleNumberLine="_exampleNumberLine_rsht6_207 _palette_rsht6_4",exampleBaseline="_exampleBaseline_rsht6_208",exampleMajorTick="_exampleMajorTick_rsht6_209",exampleMinorTick="_exampleMinorTick_rsht6_210",exampleNumberLabel="_exampleNumberLabel_rsht6_211",styles$3={objects,mixed,sequence:sequence$3,equation,equationCompact,equationTerm,equationMissing,equationOperator,partWhole,partWholeSvg,partKnown,partMissing,partLabel,partMissingLabel,numberLine,comparison:comparison$3,story,storyObjects,storyObject,storyRemoved,storyMultiplier,storyCaption,pictogram,pictogramKey,pictogramLabel,pictogramLane,pictogramLaneAlt,clock,clockFace,table,order:order$1,orderInstruction,orderDirection,orderArrow,orderSlots,exampleObjects,exampleNumberLine,exampleBaseline,exampleMajorTick,exampleMinorTick,exampleNumberLabel};function PartWholeModel({stem:n,data:t}){let e="?",s="?",o="?";if(t)e=t.whole,s=t.parts[0]??"?",o=t.parts[1]??"?";else{if(/\d[./]\d/.test(n))return null;const r=n.match(/\d+/g)?.map(Number)??[],a=n.toLowerCase().includes("missing part")||n.toLowerCase().includes("missing number"),i=n.toLowerCase().includes("whole?")||n.toLowerCase().includes("total");if(a&&r.length>=2&&r[1]<=r[0])e=r[0],s=r[1],o="?";else if(i&&r.length>=2)s=r[0],o=r[1],e="?";else if(r.length>=3&&r[0]===r[1]+r[2])e=r[0],s=r[1],o=r[2];else return null}return jsxRuntimeExports.jsx("div",{className:`part-whole-model ${styles$3.partWhole}`,children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 300 120",width:"100%",className:styles$3.partWholeSvg,children:[jsxRuntimeExports.jsxs("g",{transform:"translate(10, 10)",children:[jsxRuntimeExports.jsx("rect",{x:"0",y:"0",width:"280",height:"40",rx:"6",className:e==="?"?styles$3.partMissing:styles$3.partKnown,strokeDasharray:e==="?"?"6 6":"none"}),jsxRuntimeExports.jsx("text",{x:"140",y:"26",textAnchor:"middle",fontWeight:"bold",fontSize:"22",className:e==="?"?styles$3.partMissingLabel:styles$3.partLabel,children:e})]}),jsxRuntimeExports.jsxs("g",{transform:"translate(10, 65)",children:[jsxRuntimeExports.jsx("rect",{x:"0",y:"0",width:"135",height:"40",rx:"6",className:s==="?"?styles$3.partMissing:styles$3.partKnown,strokeDasharray:s==="?"?"6 6":"none"}),jsxRuntimeExports.jsx("text",{x:"67.5",y:"26",textAnchor:"middle",fontWeight:"bold",fontSize:"22",className:s==="?"?styles$3.partMissingLabel:styles$3.partLabel,children:s}),jsxRuntimeExports.jsx("rect",{x:"145",y:"0",width:"135",height:"40",rx:"6",className:o==="?"?styles$3.partMissing:styles$3.partKnown,strokeDasharray:o==="?"?"6 6":"none"}),jsxRuntimeExports.jsx("text",{x:"212.5",y:"26",textAnchor:"middle",fontWeight:"bold",fontSize:"22",className:o==="?"?styles$3.partMissingLabel:styles$3.partLabel,children:o})]})]})})}function CounterGroups({stem:n}){const{group1:t,group2:e}=parseGroups(n);if(t===0&&e===0)return null;const s=14,o=36,r=24,a=5,i=20,l=20,u=k=>{const T=[];for(let S=0;S<k;S++)T.push({row:Math.floor(S/a),col:S%a});const A=T.length>0?T[T.length-1].row+1:0,N=Math.min(k,a);return{positions:T,rowCount:A,maxCols:N}},d=u(t),m=u(e),h=d.maxCols*o,f=m.maxCols*o,g=i+h+r+f+i,b=Math.max(d.rowCount,m.rowCount,1),x=l+b*o+l,y=i,E=i+h+r;return jsxRuntimeExports.jsx("div",{style:{display:"flex",justifyContent:"center",margin:"16px 0"},children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${g} ${x}`,width:"100%",style:{maxWidth:`${Math.min(g,360)}px`,overflow:"visible"},role:"img","aria-label":`${t} counters and ${e} counters`,children:[d.positions.map(({row:k,col:T},A)=>jsxRuntimeExports.jsx("circle",{cx:y+T*o+s,cy:l+k*o+s,r:s,fill:"#ef4444",stroke:"#b91c1c",strokeWidth:2},`g1-${A}`)),t>0&&e>0&&jsxRuntimeExports.jsx("line",{x1:E-r/2,y1:l-4,x2:E-r/2,y2:l+b*o+4,stroke:"#d1d5db",strokeWidth:2,strokeDasharray:"4 4"}),m.positions.map(({row:k,col:T},A)=>jsxRuntimeExports.jsx("circle",{cx:E+T*o+s,cy:l+k*o+s,r:s,fill:"#3b82f6",stroke:"#1d4ed8",strokeWidth:2},`g2-${A}`)),t>0&&jsxRuntimeExports.jsx("text",{x:y+h/2,y:x-2,textAnchor:"middle",fontSize:"16",fontWeight:"bold",fill:"#991b1b",fontFamily:"'Plus Jakarta Sans', system-ui, sans-serif",children:t}),e>0&&jsxRuntimeExports.jsx("text",{x:E+f/2,y:x-2,textAnchor:"middle",fontSize:"16",fontWeight:"bold",fill:"#1e3a8a",fontFamily:"'Plus Jakarta Sans', system-ui, sans-serif",children:e})]})})}function parseGroups(n){const t=n.match(/(\d+)\s+(?:\w+\s+)*?and\s+(\d+)/i);if(t)return{group1:Number(t[1]),group2:Number(t[2])};const e=n.match(/\d+/g)?.map(Number)??[];return e.length>=2?{group1:e[0],group2:e[1]}:{group1:e[0]??0,group2:0}}function ShapeVisual({stem:n,microSkillId:t}){const e=detectShape(n);if(!e)return null;const s=SHAPES$3[e];return s?jsxRuntimeExports.jsx("div",{style:{display:"flex",justifyContent:"center",margin:"16px 0"},children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 200 200",width:"100%",style:{maxWidth:"180px",overflow:"visible"},role:"img","aria-label":`A ${e}`,children:[jsxRuntimeExports.jsx("path",{d:s.path,fill:s.fill,stroke:s.stroke,strokeWidth:3,strokeLinejoin:"round"}),t?.includes("M02")&&s.vertices.map((o,r)=>
62jsxRuntimeExports.jsx("circle",{cx:o.x,cy:o.y,r:6,fill:"#f59e0b",stroke:"#92400e",strokeWidth:2},`v-${r}`))]})}):null}const SHAPES$3={triangle:{path:"M 100 30 L 170 170 L 30 170 Z",fill:"#dbeafe",stroke:"#3b82f6",vertices:[{x:100,y:30},{x:170,y:170},{x:30,y:170}]},square:{path:"M 40 40 L 160 40 L 160 160 L 40 160 Z",fill:"#dcfce7",stroke:"#22c55e",vertices:[{x:40,y:40},{x:160,y:40},{x:160,y:160},{x:40,y:160}]},rectangle:{path:"M 20 55 L 180 55 L 180 145 L 20 145 Z",fill:"#fef9c3",stroke:"#eab308",vertices:[{x:20,y:55},{x:180,y:55},{x:180,y:145},{x:20,y:145}]},circle:{path:"M 100 20 A 80 80 0 1 1 99.99 20 Z",fill:"#fce7f3",stroke:"#ec4899",vertices:[]},pentagon:{path:polygonPath(5,100,100,78),fill:"#e0e7ff",stroke:"#6366f1",vertices:polygonVertices(5,100,100,78)},hexagon:{path:polygonPath(6,100,100,78),fill:"#f3e8ff",stroke:"#a855f7",vertices:polygonVertices(6,100,100,78)}};function polygonPath(n,t,e,s){return polygonVertices(n,t,e,s).map((r,a)=>`${a===0?"M":"L"} ${r.x} ${r.y}`).join(" ")+" Z"}function polygonVertices(n,t,e,s){const o=[];for(let r=0;r<n;r++){const a=2*Math.PI*r/n-Math.PI/2;o.push({x:Math.round(t+s*Math.cos(a)),y:Math.round(e+s*Math.sin(a))})}return o}function detectShape(n){const t=n.toLowerCase().replace(/\bsquare[\s-]+(corner|number|metre|meter|centimetre|centimeter|millimetre|millimeter|kilometre|kilometer|unit|based)\w*/g," ").replace(/\bsquare\s?(cm|mm|km|m)\b/g," ").replace(/\b(\d+\s+)?squares?\s+(to\s+the\s+)?(right|left|up|down)(wards?)?\b/g," ");return t.includes("hexagon")?"hexagon":t.includes("pentagon")?"pentagon":t.includes("rectangle")?"rectangle":t.includes("square")?"square":t.includes("triangle")?"triangle":t.includes("circle")?"circle":null}function TenFrame({data:n,filled:t,total:e}){const s=n?.filled??t??0,o=n?.total??e??10,r=Array.from({length:o},(a,i)=>i<s);return jsxRuntimeExports.jsx("div",{className:"ten-frame-wrap",children:jsxRuntimeExports.jsx("div",{className:"ten-frame-grid",children:r.map((a,i)=>jsxRuntimeExports.jsx("div",{className:`tf-cell ${a?"tf-filled":"tf-empty"}`,children:a&&jsxRuntimeExports.jsx("div",{className:"tf-dot"})},i))})})}const UNIT_WORDS$2={one:{one:"one",many:"ones"},ones:{one:"one",many:"ones"},ten:{one:"ten",many:"tens"},tens:{one:"ten",many:"tens"},hundred:{one:"hundred",many:"hundreds"},hundreds:{one:"hundred",many:"hundreds"},thousand:{one:"thousand",many:"thousands"},thousands:{one:"thousand",many:"thousands"},unit:{one:"unit",many:"units"},units:{one:"unit",many:"units"}};function agreeUnitWord(n,t){if(typeof n!="string")return null;const e=UNIT_WORDS$2[n.toLowerCase()];return e?typeof t=="number"&&t===1?e.one:e.many:null}function EquationDisplay({data:n}){const t=n.terms.length>=7;return jsxRuntimeExports.jsx("div",{className:`${styles$3.equation}${t?` ${styles$3.equationCompact}`:""}`,children:n.terms.map((e,s)=>{const o=typeof e=="string"&&["+","−","×","÷","=","-","*","/"].includes(e),r=e==="?"||e===null,a=agreeUnitWord(e,n.terms[s-1]);return r?jsxRuntimeExports.jsx("span",{className:styles$3.equationMissing,children:"?"},s):o?jsxRuntimeExports.jsx("span",{className:styles$3.equationOperator,children:e},s):jsxRuntimeExports.jsx("span",{className:styles$3.equationTerm,children:a??e},s)})})}const MAX_EMOJI_DISPLAY=20;function Story({data:n}){const{emoji:t,count:e,count_removed:s,count_total:o,total:r}=n,a=e??o??r??0,i=s??0,l=Math.min(a,MAX_EMOJI_DISPLAY);return jsxRuntimeExports.jsxs("div",{className:styles$3.story,children:[jsxRuntimeExports.jsxs("div",{className:styles$3.storyObjects,children:[Array.from({length:l},(u,d)=>{const m=i>0&&d>=l-i;return jsxRuntimeExports.jsx("span",{className:`${styles$3.storyObject}${m?` ${styles$3.storyRemoved}`:""}`,children:t},d)}),a>MAX_EMOJI_DISPLAY&&jsxRuntimeExports.jsxs("span",{className:styles$3.storyMultiplier,children:["×",a]})]}),jsxRuntimeExports.jsx("div",{className:styles$3.storyCaption,children:i>0?`${a} total · ${i} removed`:o!=null?`${e??"?"} + more = ${o}`:r!=null?`total: ${r}`:`${a}`})]})}function signedDelta(n){return n<0?`−${Math.abs(n)}`:`+${n}`}function Box({value:n,label:t}){const e=n===null;return jsxRuntimeExports.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"4px"},children:[t&&jsxRuntimeExports.jsx("span",{style:{fontSize:"0.7rem",color:"#64748b",fontWeight:600,textTransform:"uppercase",letterSpacing:"0.05em"},children:t}),jsxRuntimeExports.jsx("div",{style:{width:"56px",height:"56px",borderRadius:"10px",background:e?"#fef2f2":"#eff6ff",border:e?"2px dashed #f87171":"2px solid #93c5fd",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"1.6rem",fontWeight:800,color:e?"#ef4444":"#1e40af"},children:e?"?":n})]})}function Arrow({label:n,unknown:t}){return jsxRuntimeExports.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"2px",paddingTop:"18px"},children:[jsxRuntimeExports.jsx("span",{style:{fontSize:"0.75rem",fontWeight:700,color:t?"#ef4444":"#4f8ef7",background:t?"#fef2f2":"#eff6ff",border:t?"2px dashed #f87171":"2px solid transparent",borderRadius:"9999px",padding:"2px 8px",whiteSpace:"nowrap"},children:n}),jsxRuntimeExports.jsx("span",{style:{fontSize:"1.2rem",color:t?"#ef4444":"#4f8ef7",lineHeight:1},children:"→"})]})}function TwoStep({data:n}){const{start:t,step1_add:e,step1_result:s,step2_add:o,final_result:r}=n;return jsxRuntimeExports.jsxs("div",{style:{display:"flex",alignItems:"flex-start",justifyContent:"center",gap:"6px",padding:"16px",margin:"16px 0",background:"#f8fafc",borderRadius:"12px",border:"2px s
62olid #e2e8f0",flexWrap:"wrap"},children:[jsxRuntimeExports.jsx(Box,{value:t,label:"start"}),jsxRuntimeExports.jsx(Arrow,{label:signedDelta(e)}),jsxRuntimeExports.jsx(Box,{value:s,label:"make 10"}),jsxRuntimeExports.jsx(Arrow,{label:o!==null?signedDelta(o):"+?",unknown:o===null}),jsxRuntimeExports.jsx(Box,{value:r,label:"total"})]})}function FilledSlots({data:n}){const{emoji:t,filled:e,total:s,context_label:o}=n,r=Array.from({length:s},(l,u)=>u<e),a=5,i=[];for(let l=0;l<r.length;l+=a)i.push(r.slice(l,l+a));return jsxRuntimeExports.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:"10px",padding:"16px",margin:"16px 0",background:"#f0f9ff",borderRadius:"12px",border:"2px solid #bae6fd"},children:[o&&jsxRuntimeExports.jsx("div",{style:{fontSize:"0.8rem",color:"#0369a1",fontWeight:600,textTransform:"uppercase",letterSpacing:"0.05em"},children:o}),jsxRuntimeExports.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"6px",alignItems:"center"},children:i.map((l,u)=>jsxRuntimeExports.jsx("div",{style:{display:"flex",gap:"6px"},children:l.map((d,m)=>jsxRuntimeExports.jsx("div",{style:{width:"44px",height:"44px",borderRadius:"8px",border:`2px solid ${d?"#7dd3fc":"#94a3b8"}`,background:d?"#e0f2fe":"#f1f5f9",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"1.5rem"},children:d?t:""},m))},u))})]})}const W$9=380,H$3=130,LINE_Y$1=90,MARGIN$1=28,USABLE$1=W$9-MARGIN$1*2;function xAt$1(n,t,e){return MARGIN$1+(n-t)/(e-t)*USABLE$1}function precisionOf(n){if(!Number.isFinite(n)||n<=0)return 0;for(let t=0;t<=6;t++)if(Math.abs(n*10**t-Math.round(n*10**t))<1e-9)return t;return 6}function roundTo$1(n,t){const e=10**t;return Math.round(n*e)/e}function fmt$4(n,t){return roundTo$1(n,t).toFixed(t).replace(/\.?0+$/,"")||"0"}function NumberLine({data:n}){const{min:t,max:e,step:s,hops:o=[],marked:r=[],unknown:a}=n,i=s!=null&&s>0&&!Number.isInteger(s),l=i?precisionOf(s):0,u=(h,f)=>Math.abs(h-f)<(i?s/2:.5),d=o.filter(h=>h!=null&&Number.isFinite(h.from)&&Number.isFinite(h.to));new Set(r);const m=new Set([t,e,...r]);return d.forEach(h=>{m.add(h.from),m.add(h.to)}),jsxRuntimeExports.jsx("div",{className:`nl-wrap ${styles$3.numberLine}`,children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${W$9} ${H$3}`,width:"100%",className:"nl-svg",children:[d.map((h,f)=>{const g=xAt$1(h.from,t,e),b=xAt$1(h.to,t,e),x=(g+b)/2,y=b-g,E=Math.max(28,y*.55),k=LINE_Y$1-E;return jsxRuntimeExports.jsxs("g",{className:h.unknown?"nl-hop nl-hop--unknown":"nl-hop",children:[jsxRuntimeExports.jsx("path",{d:`M ${g} ${LINE_Y$1} Q ${x} ${k} ${b} ${LINE_Y$1}`,fill:"none",strokeWidth:2.5,strokeDasharray:h.unknown?"5,4":void 0}),jsxRuntimeExports.jsx("text",{x,y:k-6,textAnchor:"middle",fontSize:13,fontWeight:700,className:"nl-label",children:h.label})]},f)}),jsxRuntimeExports.jsx("line",{x1:MARGIN$1-6,y1:LINE_Y$1,x2:W$9-MARGIN$1+6,y2:LINE_Y$1,className:"nl-baseline",strokeWidth:2}),!i&&(()=>{const h=e-t,f=h<=20?1:h<=50?5:h<=200?10:h<=500?25:50;return Array.from({length:e-t+1},(g,b)=>b+t).map(g=>{const b=a!=null&&g===a,x=m.has(g)&&!b,y=(g-t)%f===0;if(!x&&!b&&!y)return null;const E=xAt$1(g,t,e),k=x?10:7;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:E,y1:LINE_Y$1-k,x2:E,y2:LINE_Y$1+k,strokeWidth:x?2:1,className:x?"nl-tick nl-tick--major":"nl-tick nl-tick--minor"}),x&&jsxRuntimeExports.jsx("text",{x:E,y:LINE_Y$1+20,textAnchor:"middle",fontSize:13,fontWeight:700,className:"nl-label nl-label--major",children:g})]},g)})})(),i&&(()=>{const h=Math.round((e-t)/s),g=Math.max(1,Math.ceil(h/12));return Array.from({length:h+1},(b,x)=>roundTo$1(t+x*s,l)).map((b,x)=>{const y=a!=null&&u(b,a),E=[...m].some(N=>u(b,N))&&!y,k=!y&&(E||x%g===0),T=xAt$1(b,t,e),A=E?10:7;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:T,y1:LINE_Y$1-A,x2:T,y2:LINE_Y$1+A,strokeWidth:E?2:1,className:E?"nl-tick nl-tick--major":"nl-tick nl-tick--minor"}),k&&jsxRuntimeExports.jsx("text",{x:T,y:LINE_Y$1+20,textAnchor:"middle",fontSize:12,fontWeight:700,className:E?"nl-label nl-label--major":"nl-label",children:fmt$4(b,l)})]},x)})})(),d.map((h,f)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:xAt$1(h.from,t,e),cy:LINE_Y$1,r:5,className:"nl-dot nl-
62dot--hop"}),jsxRuntimeExports.jsx("circle",{cx:xAt$1(h.to,t,e),cy:LINE_Y$1,r:5,className:h.unknown?"nl-dot nl-dot--unknown-hop":"nl-dot nl-dot--hop"})]},f)),r.filter(h=>a==null||h!==a).map(h=>jsxRuntimeExports.jsx("circle",{cx:xAt$1(h,t,e),cy:LINE_Y$1,r:5,className:"nl-dot nl-dot--marked"},h)),a!=null&&(()=>{const h=xAt$1(a,t,e),f=9;return jsxRuntimeExports.jsxs("g",{className:"nl-unknown",children:[jsxRuntimeExports.jsx("polygon",{points:`${h},${LINE_Y$1-f} ${h+f},${LINE_Y$1} ${h},${LINE_Y$1+f} ${h-f},${LINE_Y$1}`}),jsxRuntimeExports.jsx("text",{x:h,y:LINE_Y$1-f-8,textAnchor:"middle",fontSize:14,fontWeight:800,className:"nl-label nl-label--unknown",children:"?"})]})})()]})})}function TenFrameAndOnes({data:n}){const{tens:t,ones:e}=n;return jsxRuntimeExports.jsxs("div",{className:"tfo-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"tfo-section",children:[jsxRuntimeExports.jsx("div",{className:"ten-frame-grid",children:Array.from({length:t*10}).map((s,o)=>jsxRuntimeExports.jsx("div",{className:"tf-cell tf-filled",children:jsxRuntimeExports.jsx("div",{className:"tf-dot"})},o))}),jsxRuntimeExports.jsx("span",{className:"tfo-label",children:t===1?"1 ten":`${t} tens`})]}),jsxRuntimeExports.jsx("span",{className:"tfo-plus",children:"+"}),jsxRuntimeExports.jsxs("div",{className:"tfo-section",children:[jsxRuntimeExports.jsx("div",{className:"tfo-ones-grid",children:Array.from({length:e}).map((s,o)=>jsxRuntimeExports.jsx("div",{className:"tf-cell tfo-ones-cell",children:jsxRuntimeExports.jsx("div",{className:"tfo-ones-dot"})},o))}),jsxRuntimeExports.jsxs("span",{className:"tfo-label",children:[e," ",e===1?"one":"ones"]})]})]})}function Sequence({data:n}){const{values:t}=n;return jsxRuntimeExports.jsx("div",{className:`seq-wrap ${styles$3.sequence}`,children:t.map((e,s)=>jsxRuntimeExports.jsxs(React.Fragment,{children:[s>0&&jsxRuntimeExports.jsx("span",{className:"seq-comma",children:","}),e===null?jsxRuntimeExports.jsx("div",{className:"seq-box seq-box--unknown","aria-label":"unknown"}):jsxRuntimeExports.jsx("div",{className:"seq-box",children:e})]},s))})}function ObjectsGrid({data:n}){const{emoji:t,count:e,group_size:s,scatter:o}=n;if(o){const i=Math.PI*(3-Math.sqrt(5));return jsxRuntimeExports.jsx("div",{className:"og-scatter","aria-label":`${e} items to count`,children:Array.from({length:e},(l,u)=>{const d=Math.sqrt((u+.5)/e),m=u*i,h=50+d*42*Math.cos(m),f=50+d*42*Math.sin(m);return jsxRuntimeExports.jsx("span",{className:"og-emoji og-scatter-item",style:{left:`${h}%`,top:`${f}%`},role:"img","aria-hidden":"true",children:t},u)})})}const r=[];let a=e;for(;a>0;){const i=Math.min(a,s);r.push(Array.from({length:i},(l,u)=>u)),a-=i}return jsxRuntimeExports.jsx("div",{className:`og-wrap ${styles$3.objects}`,children:r.map((i,l)=>jsxRuntimeExports.jsx("div",{className:"og-group",children:i.map((u,d)=>jsxRuntimeExports.jsx("span",{className:"og-emoji",role:"img","aria-hidden":"true",children:t},d))},l))})}function Comparison({data:n}){const{left:t,right:e,symbol:s}=n,o=u=>u.toLocaleString("en-GB"),r=o(t),a=o(e),i=Math.max(r.length,a.length),l=i>=8?" cmp-number--xl":i>=7?" cmp-number--l":"";return jsxRuntimeExports.jsxs("div",{className:`cmp-wrap ${styles$3.comparison}`,children:[jsxRuntimeExports.jsx("div",{className:`cmp-number${l}`,children:r}),s!=="none"&&jsxRuntimeExports.jsx("div",{className:s===null?"cmp-symbol cmp-symbol--unknown":"cmp-symbol",children:s??"?"}),jsxRuntimeExports.jsx("div",{className:`cmp-number${l}`,children:a})]})}function Mirror({data:n}){const{each_side:t,emoji:e,right_hidden:s}=n,o=Array.from({length:t});return jsxRuntimeExports.jsxs("div",{className:"mir-wrap",children:[jsxRuntimeExports.jsx("div",{className:"mir-side",children:o.map((r,a)=>jsxRuntimeExports.jsx("span",{className:"mir-dot",role:"img","aria-hidden":"true",children:e},a))}),jsxRuntimeExports.jsx("div",{className:"mir-line","aria-hidden":"true"}),jsxRuntimeExports.jsx("div",{className:`mir-side${s?" mir-side--hidden":""}`,children:s?jsxRuntimeExports.jsx("span",{className:"mir-unknown",children:"?"}):o.map((r,a)=>jsxRuntimeExports.jsx("span",{className:"mir-dot",role:"img","aria-hidden":"true",children:e},a))})]})}function Triangle({color:n}){return jsxRuntimeExports.jsx("polygon",{points:"50,6 94,88 6,88",fill:n,stroke:n,strokeWidth:2,strokeLinejoin:"round"})}function Square({color:n}){return jsxRuntimeExports.jsx("rect",{x:"8",y:"8",width:"84",height:"84",fill:n,stroke:n,strokeWidth:2})}function Rectangle({color:n}){return jsxRuntimeExports.jsx("rect",{x:"4",y:"22",width:"92",height:"56",fill:n,stroke:n,strokeWidth:2})}function Circle({color:n}){return jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"44",fill:n,stroke:n,strokeWidth:2})}function Pentagon({color:n}){return jsxRuntimeExports.jsx("polygon",{points:"50,5 97,36 79,91 21,91 3,36",fill:n,stroke:n,strokeWidth:2,strokeLinejoin:"round"})}function Hexagon({color:n}){return jsxRuntimeExports.jsx("polygon",{points:"50,4 93,27 93,73 50,96 7,73 7,27",fill:n,stroke:n,strokeWidth:2,strokeLinejoin:"round"})}const SHAPE_COMPONENTS={triangle:Triangle,square:Square,rectangle:Rectangle,circle:Circle,pentagon:Pentagon,hexagon:Hexagon};function ShapeSvg({data:n}){const{shape:t,color:e}=n,s=SHAPE_COMPONENTS[t];return jsxRuntimeExports.jsx("div",{className:"shp-wrap",children:jsxRuntimeExports.jsx("svg",{viewBox:"0 0 100 100",className:"shp-svg","aria-label":t,role:"img",children:jsxRuntimeExports.jsx(s,{color:e})})})}const DEFAULT_COLORS$1=["#ec4899","#f59e0b","#10b981","#3b82f6","#8b5cf6","#ef4444"];function ShapeOptions({data:n,onSelect:t,selectedOption:e,status:s,attemptsExhausted:o}){const{shapes:r,correct:a,colors:i=DEFAULT_COLORS$1}=n;return jsxRuntimeExports.jsx("div",{className:"shape-options",children:r.map((l,u)=>{const d=e===l,m=l===a,h=s==="correct"&&d&&m||s==="incorrect"&&m&&o,f=s==="incorrect"&&d&&!m,g=["shape-opt-btn",d&&!h&&!f?"shape-opt-selected":"",h?"shape-opt-correct":"",f?"shape-opt-wrong":""].filter(
62Boolean).join(" ");return jsxRuntimeExports.jsx("button",{className:g,onClick:()=>t?.(l),type:"button","aria-label":l,children:jsxRuntimeExports.jsx(ShapeSvg,{data:{type:"shape_svg",shape:l,color:i[u%i.length]}})},u)})})}const NAMED$3={red:"#d94545",green:"#3aa66b",blue:"#2f6fd6",yellow:"#e6c229",orange:"#e8843c",purple:"#8b5cf6",pink:"#ec6fa8",brown:"#8b5e3c",black:"#2b2b2b",white:"#ffffff",grey:"#9aa0a8",gray:"#9aa0a8"};function colourForLabel(n){const t=String(n??"").trim().toLowerCase(),e=t.endsWith("s")?t.slice(0,-1):t;return NAMED$3[e]??METALS[e]??null}const METALS={gold:"#e5b612",silver:"#c3cad3",bronze:"#b0712f"};function colourOfNamedThing(n){const t=String(n??"").trim().toLowerCase().split(/\s+/);return t.length>2?null:NAMED$3[t[0]]??METALS[t[0]]??null}function pictogramRowColours(n){if(n.length<2)return null;const t=[];for(const e of n){const s=e.label.trim().toLowerCase().split(/\s+/);if(s.length>2||s.length===2&&!s[1].endsWith("s"))return null;const o=NAMED$3[s[0]]??METALS[s[0]];if(!o)return null;t.push(o)}return t}function withNamedBarColours(n){return n.bars.some(t=>colourOfNamedThing(t.label))?{...n,bars:n.bars.map(t=>({...t,color:colourOfNamedThing(t.label)??t.color}))}:n}const PICTOGRAM_COUNTER="●";function Beaker({x:n,y:t,w:e,h:s,clipId:o}){const a=`M ${n} ${t} L ${n} ${t+s-10} Q ${n} ${t+s} ${n+10} ${t+s} L ${n+e-10} ${t+s} Q ${n+e} ${t+s} ${n+e} ${t+s-10} L ${n+e} ${t}`;return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("defs",{children:jsxRuntimeExports.jsx("clipPath",{id:o,children:jsxRuntimeExports.jsx("path",{d:`${a} Z`})})}),jsxRuntimeExports.jsx("path",{d:`M ${n+e} ${t+s*.18} q 20 4 20 ${s*.22} q 0 ${s*.22} -20 ${s*.26}`,fill:"none",stroke:"#94a3b8",strokeWidth:5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("path",{d:`M ${n} ${t+4} q -9 -3 -13 4`,fill:"none",stroke:"#64748b",strokeWidth:4,strokeLinecap:"round"}),jsxRuntimeExports.jsx("path",{d:a,fill:"none",stroke:"#64748b",strokeWidth:4,strokeLinejoin:"round"})]})}function VesselJugs({data:n}){const t=withNamedBarColours(n),{bars:e,unit:s,show_values:o}=t;if(t.mark_count!=null){const y=t.mark_count,k=y+3,T=96,A=108,N=14,S=210,M=12,L=N+S-6,P=N+M,F=(L-P)/k,D=L-y*F;return jsxRuntimeExports.jsx("div",{className:"mb-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 300 ${N+S+12}`,className:"mb-svg mb-svg-vessel",role:"img","aria-label":"measuring jug with marked scale",children:[jsxRuntimeExports.jsx("rect",{x:T,y:D,width:A,height:L-D,fill:"#2563eb",fillOpacity:.82,clipPath:"url(#q-vjug-marks)"}),Array.from({length:k+1},(U,q)=>q).map(U=>jsxRuntimeExports.jsx("line",{x1:T,y1:L-U*F,x2:T+15,y2:L-U*F,stroke:"#64748b",strokeWidth:1.6},U)),jsxRuntimeExports.jsx(Beaker,{x:T,y:N,w:A,h:S,clipId:"q-vjug-marks"})]})})}const r=t.capacity??Math.max(...e.map(y=>(y.start??0)+y.length),1);if(e.length>1){const S=[40,168];return jsxRuntimeExports.jsx("div",{className:"mb-wrap",children:jsxRuntimeExports.jsx("svg",{viewBox:"0 0 300 242",className:"mb-svg mb-svg-vessel",role:"img","aria-label":`${e.map(M=>M.label).join(" and ")} shown as jugs`,children:e.map((M,L)=>{const P=S[L]??40+L*128,F=`q-vjug-cmp-${L}`,U=196-172*Math.min(1,M.length/r),q=o||M.show_value;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:P,y:U,width:92,height:196-U,fill:M.color,fillOpacity:.82,clipPath:`url(#${F})`}),jsxRuntimeExports.jsx(Beaker,{x:P,y:14,w:92,h:188,clipId:F}),jsxRuntimeExports.jsxs("text",{x:P+92/2,y:226,textAnchor:"middle",fontSize:14,fontWeight:800,fill:M.color,children:[M.label,q?` — ${M.length}${s?` ${s}`:""}`:""]})]},L)})})})}const a=96,i=108,l=210,u=14,d=12,m=u+l-6,h=u+d,f=y=>m-y/r*(m-h),g=r<=500?100:r<=1e3?250:r<=2e3?500:1e3,b=Array.from({length:Math.floor(r/g)+1},(y,E)=>E*g),x=e[0];return jsxRuntimeExports.jsx("div",{className:"mb-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 300 ${u+l+12}`,className:"mb-svg mb-svg-vessel",role:"img","aria-label":"measuring jug with a numbered scale",children:[jsxRuntimeExports.jsx("rect",{x:a,y:f(x.length),width:i,height:m-f(x.length),fill:x.color||"#2563eb",fillOpacity:.82,clipPath:"url(#q-vjug-one)"}),b.map(y=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:a,y1:f(y),x2:a+15,y2:f(y),stroke:"#64748b",strokeWidth:1.4}),jsxRuntimeExports.jsx("text",{x:a-7,y:f(y)+4,textAnchor:"end",className:"mb-tick-num",fontSize:10,
62children:y})]},y)),s&&jsxRuntimeExports.jsx("text",{x:a-7,y:h-4,textAnchor:"end",className:"mb-unit",fontSize:10,children:s}),jsxRuntimeExports.jsx(Beaker,{x:a,y:u,w:i,h:l,clipId:"q-vjug-one"})]})})}function MeasureBars({data:n}){const t=withNamedBarColours(n);if(t.vessel)return jsxRuntimeExports.jsx(VesselJugs,{data:t});const{bars:e,unit:s,show_values:o}=t,r=A=>/ruler/i.test(A.label),a=e.filter(r).map(A=>A.length);let i=e.filter(A=>!r(A));i.length===0&&(i=e);const l=Math.max(...a,...i.map(A=>(A.start??0)+A.length),1),u=16,d=300,m=d/l,h=46,f=10+i.length*h,g=f+44,b=l<=20?1:l<=50?5:l<=100?10:l<=500?50:l<=1e3?100:l<=2500?250:500,x=Math.max(1,b/5),y=Array.from({length:Math.floor(l/x)+1},(A,N)=>N*x),E=[];for(const A of i)(o||A.show_value)&&E.push(`${A.length}${s?` ${s}`:""}`);s&&E.push(s);const k=E.reduce((A,N)=>Math.max(A,N.length),0),T=Math.max(24,12+k*7);return jsxRuntimeExports.jsx("div",{className:"mb-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${u+d+T} ${g}`,className:"mb-svg",role:"img","aria-label":`${i.map(A=>A.label).join(" and ")} shown on a ${s?`${s} `:""}scale`,children:[i.map((A,N)=>{const S=10+N*h,M=A.start??0,L=u+M*m,P=A.length*m,F=o||A.show_value;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("text",{x:u,y:S+10,className:"mb-bar-label",children:A.label}),jsxRuntimeExports.jsx("rect",{x:L,y:S+15,width:P,height:20,rx:5,fill:A.color,fillOpacity:.9}),A.emoji&&jsxRuntimeExports.jsx("text",{x:L+P-14,y:S+30,fontSize:15,children:A.emoji}),F&&jsxRuntimeExports.jsxs("text",{x:L+P+8,y:S+30,className:"mb-value-text",children:[A.length,s?` ${s}`:""]})]},N)}),jsxRuntimeExports.jsx("rect",{x:u,y:f,width:d,height:30,rx:4,className:"mb-ruler-body"}),y.map(A=>{const N=A%b===0;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:u+A*m,y1:f,x2:u+A*m,y2:f+(N?16:9),className:"mb-tick"}),N&&jsxRuntimeExports.jsx("text",{x:u+A*m,y:f+26,textAnchor:"middle",className:"mb-tick-num",children:A})]},A)}),s&&jsxRuntimeExports.jsx("text",{x:u+d+8,y:f+26,className:"mb-unit",children:s})]})})}const CX$8=50,CY$8=52,R$8=42,START_ANGLE$2=135,SWEEP$3=270;function pointAt$2(n,t){const e=(START_ANGLE$2+n*SWEEP$3)*(Math.PI/180);return{x:CX$8+t*Math.cos(e),y:CY$8+t*Math.sin(e)}}function MassScale({data:n}){const{reading:t,max:e,unit:s}=n,o=e>0?e:1,r=n.interval&&n.interval>0?n.interval:niceInterval$1(o),a=Math.max(1,Math.round(o/r)),i=a+1>8?2:1,l=Array.from({length:a+1},(f,g)=>{const b=Math.min(g*r,o),x=g%i===0||g===a;return{value:b,fraction:b/o,labelled:x}}),u=[];for(let f=0;f<a;f++)for(let g=1;g<5;g++)u.push((f+g/5)/a);const d=Math.max(0,Math.min(1,t/o)),m=pointAt$2(d,R$8-8),h=pointAt$2(d,-6);return jsxRuntimeExports.jsx("div",{className:"mass-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 100 100",className:"mass-svg",role:"img","aria-label":`Kitchen scale dial reading from 0 to ${o} ${s}. Read where the needle points.`,children:[jsxRuntimeExports.jsx("circle",{cx:CX$8,cy:CY$8,r:R$8+4,fill:"#ffffff",stroke:"#334155",strokeWidth:2.5}),jsxRuntimeExports.jsx("circle",{cx:CX$8,cy:CY$8,r:R$8,fill:"#f8fafc",stroke:"#cbd5e1",strokeWidth:1}),u.map((f,g)=>{const b=pointAt$2(f,R$8),x=pointAt$2(f,R$8-4);return jsxRuntimeExports.jsx("line",{x1:b.x,y1:b.y,x2:x.x,y2:x.y,stroke:"#94a3b8",strokeWidth:.8,strokeLinecap:"round"},`min-${g}`)}),l.map(({value:f,fraction:g})=>{const b=pointAt$2(g,R$8),x=pointAt$2(g,R$8-7);return jsxRuntimeExports.jsx("line",{x1:b.x,y1:b.y,x2:x.x,y2:x.y,stroke:"#334155",strokeWidth:1.8,strokeLinecap:"round"},`majtick-${f}`)}),jsxRuntimeExports.jsx("line",{x1:h.x,y1:h.y,x2:m.x,y2:m.y,stroke:"#dc2626",strokeWidth:2.6,strokeLinecap:"round"}),l.map(({value:f,fraction:g,labelled:b})=>{if(!b)return null;const x=pointAt$2(g,R$8-14);return jsxRuntimeExports.jsx("text",{x:x.x,y:x.y,textAnchor:"middle",dominantBaseline:"central",fontSize:7.5,fontWeight:"700",fill:"#1e293b",fontFamily:"system-ui, sans-serif",stroke:"#ffffff",strokeWidth:2,paintOrder:"stroke",children:f},`majnum-${f}`)}),jsxRuntimeExports.jsx("circle",{cx:CX$8,cy:CY$8,r:4,fill:"#dc2626",stroke:"#ffffff",strokeWidth:1.2}),jsxRuntimeExports.jsx("circle",{cx:CX$8,cy:CY$8,r:1.4,fill:"#ffffff"}),jsxRuntimeExports.jsx("text",{x:CX$8,y:CY$8+14,textAnchor:"middle",dominantBaseline:"central",fontSize:8,fontWeight:"700",fill:"#2563eb",fontFamily:"system-ui, sans-serif",children:s})]})})}function niceInterval$1(n){const t=[1,2,5,10,20,25,50,100,200,250,500,1e3];for(const e of t){const s=n/e;if(s>=4&&s<=6)return e}return Math.max(1,Math.round(n/5))}function MassCompare({data:n}){const{left:t,right:e,left_label:s,right_label:o}=n;return jsxRuntimeExports.jsxs("div",{className:"masscmp-wrap",children:[jsxRuntimeExports.jsxs("figure",{className:"masscmp-item",children:[jsxRuntimeExports.jsx(MassScale,{data:{type:"mass_scale",...t}}),jsxRuntimeExports.jsx("figcaption",{className:"masscmp-label",children:s})]}),jsxRuntimeExports.jsxs("figure",{className:"masscmp-item",children:[jsxRuntimeExports.jsx(MassScale,{data:{type:"mass_scale",...e}}),jsxRuntimeExports.jsx("figcaption",{className:"masscmp-label",children:o})]})]})}const COL_W$3=52,BAR_W$2=30,TOP$3=8,CHART_H=150,BASE_Y=TOP$3+CHART_H,H$2=BASE_Y+6,YW$1=22;function BarBuilder({data:n,onSelect:t}){const{categories:e,max:s,step:o=1,hide_targets:r=!1}=n,a=e.length,[i,l]=reactExports.useState(()=>e.map(()=>0)),u=reactExports.useRef(t);u.current=t,reactExports.useEffect(()=>{l(e.map(()=>0)),u.current?.("")},[e]);const d=(b,x)=>
62{const y=i.map((E,k)=>k===b?Math.max(0,Math.min(s,E+x)):E);l(y),u.current?.(y.join(","))},m=[];for(let b=0;b<=s;b+=o)m.push(b);const h=b=>BASE_Y-b/s*CHART_H,f=b=>b*COL_W$3+(COL_W$3-BAR_W$2)/2,g=a*COL_W$3;return jsxRuntimeExports.jsxs("div",{className:"bb-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"bb-chart",children:[jsxRuntimeExports.jsxs("svg",{className:"bb-yaxis",viewBox:`0 0 ${YW$1} ${H$2}`,preserveAspectRatio:"none","aria-hidden":"true",children:[m.map(b=>jsxRuntimeExports.jsx("text",{x:YW$1-4,y:h(b),textAnchor:"end",dominantBaseline:"central",fontSize:9,fill:"#64748b",children:b},b)),jsxRuntimeExports.jsx("line",{x1:YW$1-.6,y1:h(s),x2:YW$1-.6,y2:h(0),stroke:"#334155",strokeWidth:1})]}),jsxRuntimeExports.jsxs("svg",{className:"bb-bars",viewBox:`0 0 ${g} ${H$2}`,preserveAspectRatio:"none",role:"img","aria-label":"Bar chart to build. Use the plus and minus buttons under each bar to set it to the value given.",children:[m.map(b=>jsxRuntimeExports.jsx("line",{x1:0,y1:h(b),x2:g,y2:h(b),stroke:b===0?"#334155":"#e2e8f0",strokeWidth:b===0?1.4:1},b)),e.map((b,x)=>{const y=i[x]/s*CHART_H;return y>0?jsxRuntimeExports.jsx("rect",{x:f(x),y:BASE_Y-y,width:BAR_W$2,height:y,rx:2,fill:"#3b82f6"},x):null})]})]}),jsxRuntimeExports.jsxs("div",{className:"bb-controls-row",children:[jsxRuntimeExports.jsx("div",{className:"bb-yaxis-spacer","aria-hidden":"true"}),jsxRuntimeExports.jsx("div",{className:"bb-controls",children:e.map((b,x)=>jsxRuntimeExports.jsxs("div",{className:"bb-ctrl",children:[jsxRuntimeExports.jsx("button",{type:"button",className:"bb-btn",onClick:()=>d(x,o),"aria-label":`Raise ${b.label}, currently ${i[x]}`,children:"+"}),jsxRuntimeExports.jsx("button",{type:"button",className:"bb-btn",onClick:()=>d(x,-o),"aria-label":`Lower ${b.label}, currently ${i[x]}`,children:"−"}),jsxRuntimeExports.jsx("div",{className:"bb-target",children:r?b.label:jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[b.label,": ",jsxRuntimeExports.jsx("b",{children:b.count})]})})]},x))})]})]})}const CX$7=50,CY$7=50,R$7=44;function handEnd(n,t){const e=(n-90)*(Math.PI/180);return{x:CX$7+t*Math.cos(e),y:CY$7+t*Math.sin(e)}}function Clock({data:n}){const{hour:t,minute:e}=n,s=e/60*360,o=t%12/12*360+e/60*30,r=handEnd(s,32),a=handEnd(o,16),i=Array.from({length:12},(u,d)=>{const m=d+1,f=(m/12*360-90)*(Math.PI/180);return{n:m,x:CX$7+34*Math.cos(f),y:CY$7+34*Math.sin(f)}}),l=Array.from({length:60},(u,d)=>{const h=(d/60*360-90)*(Math.PI/180),f=d%5===0,g=f?R$7-6:R$7-3;return{x1:CX$7+g*Math.cos(h),y1:CY$7+g*Math.sin(h),x2:CX$7+R$7*Math.cos(h),y2:CY$7+R$7*Math.sin(h),isHour:f}});return jsxRuntimeExports.jsx("div",{className:`clk-wrap ${styles$3.clock}`,children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 100 100",className:"clk-svg","aria-label":`Clock showing ${t}:${e.toString().padStart(2,"0")}`,role:"img",children:[jsxRuntimeExports.jsx("circle",{className:styles$3.clockFace,cx:CX$7,cy:CY$7,r:R$7,fill:"white",stroke:"#334155",strokeWidth:2}),l.map((u,d)=>jsxRuntimeExports.jsx("line",{x1:u.x1,y1:u.y1,x2:u.x2,y2:u.y2,stroke:"#94a3b8",strokeWidth:u.isHour?1.5:.8},d)),i.map(({n:u,x:d,y:m})=>jsxRuntimeExports.jsx("text",{x:d,y:m,textAnchor:"middle",dominantBaseline:"central",fontSize:7.5,fontWeight:"600",fill:"#1e293b",fontFamily:"system-ui, sans-serif",children:u},u)),jsxRuntimeExports.jsx("line",{x1:CX$7,y1:CY$7,x2:a.x,y2:a.y,stroke:"#1e293b",strokeWidth:5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("line",{x1:CX$7,y1:CY$7,x2:r.x,y2:r.y,stroke:"#1e293b",strokeWidth:2.4,strokeLinecap:"round"}),jsxRuntimeExports.jsx("circle",{cx:CX$7,cy:CY$7,r:2.5,fill:"#1e293b"})]})})}const SIZE=120,CX$6=SIZE/2,CY$6=SIZE/2,R$6=50,STROKE="#555",EMPTY="#fff";function polarToXY(n,t){const e=(n-90)*Math.PI/180;return{x:CX$6+t*Math.cos(e),y:CY$6+t*Math.sin(e)}}const UNEQUAL_CIRCLE_ANGLES={2:[120,240],4:[60,80,100,120]};function CircleFraction({total:n,shaded:t,color:e,equal:s=!0,showNumbers:o=!1,partMarks:r}){const a=s?Array(n).fill(360/n):UNEQUAL_CIRCLE_ANGLES[n];let i=0;const l=a.map((u,d)=>{const m=i+u,h=polarToXY(i,R$6),f=polarToXY(m,R$6),g=u>180?1:0,b=`M ${CX$6} ${CY$6} L ${h.x} ${h.y} A ${R$6} ${R$6} 0 ${g} 1 ${f.x} ${f.y} Z`,x=polarToXY(i+u/2,R$6*.6);return i=m,{d:b,shaded:d<t,label:x}});return jsxRuntimeExports.jsxs("svg",{width:SIZE,height:SIZE,viewBox:`0 0 ${SIZE} ${SIZE}`,className:"fsh-svg",children:[l.map((u,d)=>{const m=r?.[d];return jsxRuntimeExports.jsx("path",{d:u.d,fill:u.shaded?e:EMPTY,stroke:m??STROKE,strokeWidth:m?3:1.5},d)}),o&&l.map((u,d)=>jsxRuntimeExports.jsx("text",{x:u.label.x,y:u.label.y,fontSize:13,fontWeight:700,textAnchor:"middle",dominantBaseline:"central",fill:u.shaded?"#fff":"#333",children:(d+1)%l.length+1},`n-${d}`))]})}function RectFraction({total:n,shaded:t,color:e,square:s,equal:o=!0,splitColumns:r=!1,showNumbers:a=!1,partMar
62ks:i}){const l=s?SIZE:SIZE*1.6,u=b=>jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[b.map((x,y)=>{const E=i?.[y];return jsxRuntimeExports.jsx("rect",{x:x.x,y:x.y,width:x.w,height:x.h,fill:y<t?e:EMPTY,stroke:E??STROKE,strokeWidth:E?3:1.5},y)}),a&&b.map((x,y)=>jsxRuntimeExports.jsx("text",{x:x.x+x.w/2,y:x.y+x.h/2,fontSize:13,fontWeight:700,textAnchor:"middle",dominantBaseline:"central",fill:y<t?"#fff":"#333",children:y+1},`n-${y}`))]});if(!o){const b=Math.round(l*.33),x=Math.round(SIZE*.33),y=n===2?[{x:0,y:0,w:b,h:SIZE},{x:b,y:0,w:l-b,h:SIZE}]:[{x:0,y:0,w:b,h:x},{x:b,y:0,w:l-b,h:x},{x:0,y:x,w:b,h:SIZE-x},{x:b,y:x,w:l-b,h:SIZE-x}];return jsxRuntimeExports.jsx("svg",{width:l,height:SIZE,viewBox:`0 0 ${l} ${SIZE}`,className:"fsh-svg",children:u(y)})}const d=n===4&&!r?2:n,m=n===4&&!r?2:1,h=l/d,f=SIZE/m,g=Array.from({length:n},(b,x)=>{const y=x%d,E=Math.floor(x/d);return{x:y*h,y:E*f,w:h,h:f}});return jsxRuntimeExports.jsx("svg",{width:l,height:SIZE,viewBox:`0 0 ${l} ${SIZE}`,className:"fsh-svg",children:u(g)})}function FractionShape({data:n}){const{shape:t,total_parts:e,shaded_parts:s,color:o,equal:r=!0,split_columns:a=!1,show_part_numbers:i=!1,part_marks:l}=n;return jsxRuntimeExports.jsx("div",{className:"fsh-wrap",children:t==="circle"?jsxRuntimeExports.jsx(CircleFraction,{total:e,shaded:s,color:o,equal:r,showNumbers:i,partMarks:l}):jsxRuntimeExports.jsx(RectFraction,{splitColumns:a,total:e,shaded:s,color:o,square:t==="square",equal:r,showNumbers:i,partMarks:l})})}const DOT_POSITIONS={0:[],1:[[30,31]],2:[[42,18],[18,44]],3:[[42,18],[30,31],[18,44]],4:[[18,18],[42,18],[18,44],[42,44]],5:[[18,18],[42,18],[30,31],[18,44],[42,44]],6:[[18,18],[18,31],[18,44],[42,18],[42,31],[42,44]]},DOT_R$1=5;function DominoHalf({value:n,yOffset:t}){const e=DOT_POSITIONS[Math.min(Math.max(n,0),6)]??[];return jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:e.map(([s,o],r)=>jsxRuntimeExports.jsx("circle",{cx:s,cy:o+t,r:DOT_R$1,fill:"#1a1a1a"},r))})}function DominoSet({data:n,onSelect:t,selectedOption:e,status:s,attemptsExhausted:o}){const{target:r,options:a}=n;return jsxRuntimeExports.jsx("div",{className:"domino-set",children:a.map((i,l)=>{const u=i.left+i.right,d=String(u),m=e===d,h=u===r,f=s==="correct"&&m&&h||s==="incorrect"&&h&&o,g=s==="incorrect"&&m&&!h,b=["domino-btn",m&&!f&&!g?"domino-selected":"",f?"domino-correct":"",g?"domino-wrong":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsx("button",{className:b,onClick:()=>t?.(d),type:"button","aria-label":`Domino: ${i.left} and ${i.right}, total ${u}`,children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 60 124",width:"60",height:"124","aria-hidden":"true",children:[jsxRuntimeExports.jsx("rect",{x:"1",y:"1",width:"58",height:"122",rx:"7",ry:"7",fill:"#fefefe",stroke:"#2a2a2a",strokeWidth:"2"}),jsxRuntimeExports.jsx("line",{x1:"10",y1:"62",x2:"50",y2:"62",stroke:"#2a2a2a",strokeWidth:"1.5"}),jsxRuntimeExports.jsx(DominoHalf,{value:i.left,yOffset:0}),jsxRuntimeExports.jsx(DominoHalf,{value:i.right,yOffset:62})]})},l)})})}function TenFrameOverflow({data:n}){const{start:t,adding:e}=n,s=Array.from({length:10},(r,a)=>a<t),o=Array.from({length:e});return jsxRuntimeExports.jsxs("div",{className:"tfov-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"tfo-section",children:[jsxRuntimeExports.jsx("div",{className:"ten-frame-grid",children:s.map((r,a)=>jsxRuntimeExports.jsx("div",{className:`tf-cell ${r?"tf-filled":"tf-empty"}`,children:r&&jsxRuntimeExports.jsx("div",{className:"tf-dot"})},a))}),jsxRuntimeExports.jsx("span",{className:"tfo-label",children:t})]}),jsxRuntimeExports.jsx("span",{className:"tfo-plus",children:"+"}),jsxRuntimeExports.jsxs("div",{className:"tfo-section",children:[jsxRuntimeExports.jsx("div",{className:"tfov-dots-grid",children:o.map((r,a)=>jsxRuntimeExports.jsx("div",{className:"tf-cell tfov-adding-cell",children:jsxRuntimeExports.jsx("div",{className:"tfov-adding-dot"})},a))}),jsxRuntimeExports.jsx("span",{className:"tfo-label",children:e})]})]})}const COIN_SIZE_PX={"5p":70,"1p":78,"20p":82,"£1":91,"10p":93,"2p":101,"50p":106,"£2":109},SIZE_PX=COIN_SIZE_PX;function CoinSet({data:n}){const t=n.hide_value===!0;return jsxRuntimeExports.jsxs("div",{className:"coin-set-wrap",children:[jsxRuntimeExports.jsx(CoinDefs,{}),n.coins.map((e,s)=>jsxRuntimeExports.jsx(Coin,{denom:e,hideValue:t},`${e}-${s}`))]})}function CoinDefs(){return jsxRuntimeExports.jsx("svg",{width:"0",height:"0",style:{position:"absolute"},"aria-hidden":"true",children:jsxRuntimeExports.jsxs("defs",{children:[jsxRuntimeExports.jsxs("radialGradient",{id:"coin-bronze",cx:"35%",cy:"28%",r:"78%",children:[jsxRuntimeExports.jsx("stop",{offset:"0%",stopColor:"#f4c9a0"}),jsxRuntimeExports.jsx("stop",{offset:"55%",stopColor:"#bd7239"}),jsxRuntimeExports.jsx("stop",{offset:"100%",stopColor:"#6e3d18"})]}),jsxRuntimeExports.jsxs("radialGradient",{id:"coin-silver",cx:"35%",cy:"28%",r:"78%",children:[jsxRuntimeExports.jsx("stop",{offset:"0%",stopColor:"#fafafa"}),jsxRuntimeExports.jsx("stop",{offset:"55%",stopColor:"#c4c4c4"}),jsxRuntimeExports.jsx("stop",{offset:"100%",stopColor:"#7a7a7a"})]}),jsxRuntimeExports.jsxs("radialGradient",{id:"coin-gold",cx:"35%",cy:"28%",r:"78%",children:[jsxRuntimeExports.jsx("stop",{offset:"0%",stopColor:"#fbe9a3"}),jsxRuntimeExports.jsx("stop",{offset:"55%",stopColor:"#d6b23e"}),jsxRuntimeExports.jsx("stop",{offset:"100%",stopColor:"#8a681c"})]}),jsxRuntimeExports.jsxs("radialGradient",{id:"coin-silver-core",cx:"35%",cy:"28%",r:"78%",children:[jsxRuntimeExports.jsx("stop",{offset:"0%",stopColor:"#f4f4f4"}
62),jsxRuntimeExports.jsx("stop",{offset:"60%",stopColor:"#bababa"}),jsxRuntimeExports.jsx("stop",{offset:"100%",stopColor:"#7a7a7a"})]}),jsxRuntimeExports.jsx("path",{id:"coin-curve-top",d:"M 50,50 m -41,0 a 41,41 0 0,1 82,0",fill:"none"}),jsxRuntimeExports.jsx("path",{id:"coin-curve-bottom",d:"M 50,50 m -41,0 a 41,41 0 1,0 82,0",fill:"none"})]})})}function Coin({denom:n,hideValue:t}){const e=SIZE_PX[n],s={width:e,height:e,viewBox:"0 0 100 100"};switch(n){case"1p":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"1p coin",role:"img",children:jsxRuntimeExports.jsx(OnePenny,{hideValue:t})});case"2p":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"2p coin",role:"img",children:jsxRuntimeExports.jsx(TwoPence,{hideValue:t})});case"5p":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"5p coin",role:"img",children:jsxRuntimeExports.jsx(FivePence,{hideValue:t})});case"10p":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"10p coin",role:"img",children:jsxRuntimeExports.jsx(TenPence,{hideValue:t})});case"20p":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"20p coin",role:"img",children:jsxRuntimeExports.jsx(TwentyPence,{hideValue:t})});case"50p":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"50p coin",role:"img",children:jsxRuntimeExports.jsx(FiftyPence,{hideValue:t})});case"£1":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"one pound coin",role:"img",children:jsxRuntimeExports.jsx(OnePound,{hideValue:t})});case"£2":return jsxRuntimeExports.jsx("svg",{...s,"aria-label":"two pound coin",role:"img",children:jsxRuntimeExports.jsx(TwoPound,{hideValue:t})})}}const HEPTAGON_OUTER="50,2 92,18 98,60 73,96 27,96 2,60 8,18",HEPTAGON_INNER="50,8 87,22.5 93,59 71,89 29,89 7,59 13,22.5",DODECAGON_OUTER="50,3 73,9.2 89.2,25.4 95.5,49 89.2,72.5 73,88.7 50,95 27,88.7 10.8,72.5 4.5,49 10.8,25.4 27,9.2",LEGEND_FONT={fontFamily:"Georgia, 'Times New Roman', serif",fontWeight:700,fill:"#0a0a0a"};function CurvedLegend({text:n,size:t=9,letterSpacing:e=2,position:s="top"}){return jsxRuntimeExports.jsx("text",{...LEGEND_FONT,fontSize:t,letterSpacing:e,children:jsxRuntimeExports.jsx("textPath",{href:`#coin-curve-${s}`,startOffset:"50%",textAnchor:"middle",children:n})})}function Crown(){return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:"46",y:"28",width:"8",height:"2",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("path",{d:"M 46 28 L 47 25 L 48.5 27 L 50 24 L 51.5 27 L 53 25 L 54 28",fill:"#0a0a0a",stroke:"#0a0a0a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"23",r:"0.7",fill:"#0a0a0a"})]})}function OnePenny({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"48",fill:"url(#coin-bronze)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#1a1a1a",strokeWidth:"0.4",opacity:"0.5"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"ONE PENNY"}),jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",fill:"none",children:[jsxRuntimeExports.jsx("rect",{x:"44",y:"32",width:"12",height:"2",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("path",{d:"M 44 32 L 45 28 L 47 31 L 50 27 L 53 31 L 55 28 L 56 32",fill:"#0a0a0a",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"26",r:"0.8",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("rect",{x:"40",y:"36",width:"20",height:"22",strokeWidth:"1.1",fill:"#3a3a3a",fillOpacity:"0.4"}),jsxRuntimeExports.jsx("line",{x1:"44",y1:"36",x2:"44",y2:"58",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"48",y1:"36",x2:"48",y2:"58",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"52",y1:"36",x2:"52",y2:"58",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"56",y1:"36",x2:"56",y2:"58",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"40",y1:"42",x2:"60",y2:"42",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"40",y1:"48",x2:"60",y2:"48",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"40",y1:"54",x2:"60",y2:"54",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("polygon",{points:"39,58 41,62 43,58",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("polygon",{points:"43,58 45,62 47,58",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("polygon",{points:"47,58 49,62 51,58",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("polygon",{points:"51,58 53,62 55,58",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("polygon",{points:"55,58 57,62 59,58",fill:"#0a0a0a"})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"86",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"20",fontWeight:"700",fill:"#0a0a0a",children:"1"})]})}function TwoPence({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"48",fill:"url(#coin-bronze)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#1a1a1a",strokeWidth:"0.4",opacity:"0.5"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"TWO PENCE",size:8,letterSpacing:2.4}),jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",strokeLinejoin:"round",children:[jsxRuntimeExports.jsx("path",{d:"M 50 60 C 47 52 47 40 49 30 C 50 27 50 27 51 30 C 53 40 53 52 50 60 Z",fill:"#f4f4f4",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("path",{d:"M 47 60 C 41 56 35 48 32 39 C 31 36 32 35 35 37 C 43 42 47 51 49 60 Z",fill:"#f4f4f4",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("path",{d:"M 53 60 C 59 56 65 48 68 39 C 69 36 68 35 65 37 C 57 42 53 51 51 60 Z",fill:"#f4f4f4",strokeWidth:"0.7"}
62),jsxRuntimeExports.jsxs("g",{strokeWidth:"0.3",fill:"none",children:[jsxRuntimeExports.jsx("line",{x1:"50",y1:"34",x2:"48",y2:"36"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"38",x2:"47",y2:"40"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"42",x2:"47",y2:"44"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"46",x2:"47",y2:"48"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"50",x2:"48",y2:"52"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"34",x2:"52",y2:"36"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"38",x2:"53",y2:"40"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"42",x2:"53",y2:"44"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"46",x2:"53",y2:"48"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"50",x2:"52",y2:"52"})]}),jsxRuntimeExports.jsx("rect",{x:"42",y:"60",width:"16",height:"4",fill:"#0a0a0a",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("circle",{cx:"46",cy:"62",r:"0.6",fill:"#f4f4f4",stroke:"none"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"62",r:"0.6",fill:"#f4f4f4",stroke:"none"}),jsxRuntimeExports.jsx("circle",{cx:"54",cy:"62",r:"0.6",fill:"#f4f4f4",stroke:"none"})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"86",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"20",fontWeight:"700",fill:"#0a0a0a",children:"2"})]})}function FivePence({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"48",fill:"url(#coin-silver)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#1a1a1a",strokeWidth:"0.4",opacity:"0.5"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"FIVE PENCE"}),jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",strokeLinecap:"round",children:[jsxRuntimeExports.jsx(Crown,{}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"36",x2:"50",y2:"33",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("line",{x1:"47",y1:"36",x2:"46",y2:"34",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("line",{x1:"53",y1:"36",x2:"54",y2:"34",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("line",{x1:"44",y1:"38",x2:"42",y2:"36",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("line",{x1:"56",y1:"38",x2:"58",y2:"36",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("ellipse",{cx:"50",cy:"44",rx:"7",ry:"8",fill:"#bdbdbd",strokeWidth:"0.6"}),jsxRuntimeExports.jsxs("g",{strokeWidth:"0.3",stroke:"#2a2a2a",fill:"none",children:[jsxRuntimeExports.jsx("line",{x1:"44",y1:"40",x2:"56",y2:"48"}),jsxRuntimeExports.jsx("line",{x1:"44",y1:"48",x2:"56",y2:"40"}),jsxRuntimeExports.jsx("line",{x1:"46",y1:"38",x2:"46",y2:"50"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"37",x2:"50",y2:"51"}),jsxRuntimeExports.jsx("line",{x1:"54",y1:"38",x2:"54",y2:"50"})]}),jsxRuntimeExports.jsx("path",{d:"M 44 50 L 50 56 L 56 50 L 54 53 L 50 56 L 46 53 Z",fill:"#2a2a2a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"50",y1:"56",x2:"50",y2:"68",strokeWidth:"1",stroke:"#2a2a2a"}),jsxRuntimeExports.jsx("path",{d:"M 50 60 Q 41 60 38 64 Q 42 64 44 63 Q 41 66 40 68 Q 44 67 47 65 Z",fill:"#5a5a5a",stroke:"#0a0a0a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("path",{d:"M 50 62 Q 59 62 62 66 Q 58 66 56 65 Q 59 68 60 70 Q 56 69 53 67 Z",fill:"#5a5a5a",stroke:"#0a0a0a",strokeWidth:"0.3"})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"86",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"20",fontWeight:"700",fill:"#0a0a0a",children:"5"})]})}function TenPence({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"48",fill:"url(#coin-silver)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"45",fill:"none",stroke:"#1a1a1a",strokeWidth:"0.4",opacity:"0.5"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"TEN PENCE",size:8,letterSpacing:2.4}),jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",strokeLinejoin:"round",children:[jsxRuntimeExports.jsx("rect",{x:"62",y:"29",width:"9",height:"2",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("path",{d:"M 62 29 L 63 26 L 65 28 L 67 25 L 69 28 L 70 26 L 71 29",fill:"#0a0a0a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("circle",{cx:"66.5",cy:"24",r:"0.7",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("path",{d:"M 26 56 C 26 50 30 48 36 48 L 56 48 C 60 48 63 46 64 42 L 65 35 L 71 33 L 71 36 L 67 38 C 66 40 66 44 66 46 L 66 52 C 66 58 64 64 62 66 
62L 62 70 L 60 70 L 60 64 L 52 64 L 52 70 L 50 70 L 50 64 L 38 64 L 38 70 L 36 70 L 36 64 L 30 64 L 30 70 L 28 70 L 28 60 C 26 60 26 58 26 56 Z",fill:"#5e5e5e",strokeWidth:"0.6"}),jsxRuntimeExports.jsx("path",{d:"M 60 38 Q 62 34 66 34 Q 70 34 70 38",fill:"#3a3a3a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("path",{d:"M 58 48 L 58 42 L 60.5 42 L 60.5 48 Z",fill:"#3a3a3a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("path",{d:"M 26 56 Q 19 50 19 40 Q 19 32 25 32 Q 28 32 27 38 Q 24 40 24 44",fill:"none",stroke:"#3a3a3a",strokeWidth:"1.2",strokeLinecap:"round"}),jsxRuntimeExports.jsx("path",{d:"M 19 40 Q 16 38 17 34 Q 19 35 20 38 Z",fill:"#3a3a3a"}),jsxRuntimeExports.jsx("circle",{cx:"68",cy:"38",r:"0.5",fill:"#fff"})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"86",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"18",fontWeight:"700",fill:"#0a0a0a",children:"10"})]})}function TwentyPence({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("polygon",{points:HEPTAGON_OUTER,fill:"url(#coin-silver)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("polygon",{points:HEPTAGON_INNER,fill:"none",stroke:"#1a1a1a",strokeWidth:"0.4",opacity:"0.5"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"TWENTY PENCE",size:7.5}),jsxRuntimeExports.jsx(Crown,{}),jsxRuntimeExports.jsxs("g",{transform:"translate(50,52)",children:[jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",strokeWidth:"0.5",children:[jsxRuntimeExports.jsx("ellipse",{cx:"0",cy:"-11",rx:"5",ry:"6",fill:"#fff"}),jsxRuntimeExports.jsx("ellipse",{cx:"10.4",cy:"-3.4",rx:"5",ry:"6",transform:"rotate(72 10.4 -3.4)",fill:"#fff"}),jsxRuntimeExports.jsx("ellipse",{cx:"6.4",cy:"8.9",rx:"5",ry:"6",transform:"rotate(144 6.4 8.9)",fill:"#fff"}),jsxRuntimeExports.jsx("ellipse",{cx:"-6.4",cy:"8.9",rx:"5",ry:"6",transform:"rotate(216 -6.4 8.9)",fill:"#fff"}),jsxRuntimeExports.jsx("ellipse",{cx:"-10.4",cy:"-3.4",rx:"5",ry:"6",transform:"rotate(288 -10.4 -3.4)",fill:"#fff"})]}),jsxRuntimeExports.jsxs("g",{fill:"#2a2a2a",stroke:"#0a0a0a",strokeWidth:"0.4",children:[jsxRuntimeExports.jsx("ellipse",{cx:"6.0",cy:"-8.2",rx:"2.5",ry:"3.8",transform:"rotate(36 6.0 -8.2)"}),jsxRuntimeExports.jsx("ellipse",{cx:"9.7",cy:"3.2",rx:"2.5",ry:"3.8",transform:"rotate(108 9.7 3.2)"}),jsxRuntimeExports.jsx("ellipse",{cx:"0",cy:"10.2",rx:"2.5",ry:"3.8",transform:"rotate(180 0 10.2)"}),jsxRuntimeExports.jsx("ellipse",{cx:"-9.7",cy:"3.2",rx:"2.5",ry:"3.8",transform:"rotate(252 -9.7 3.2)"}),jsxRuntimeExports.jsx("ellipse",{cx:"-6.0",cy:"-8.2",rx:"2.5",ry:"3.8",transform:"rotate(324 -6.0 -8.2)"})]}),jsxRuntimeExports.jsx("circle",{r:"2.8",fill:"#bababa",stroke:"#0a0a0a",strokeWidth:"0.5"}),jsxRuntimeExports.jsxs("g",{fill:"#0a0a0a",children:[jsxRuntimeExports.jsx("circle",{r:"0.4",cx:"0",cy:"-1.2"}),jsxRuntimeExports.jsx("circle",{r:"0.4",cx:"1.1",cy:"-0.4"}),jsxRuntimeExports.jsx("circle",{r:"0.4",cx:"0.7",cy:"0.9"}),jsxRuntimeExports.jsx("circle",{r:"0.4",cx:"-0.7",cy:"0.9"}),jsxRuntimeExports.jsx("circle",{r:"0.4",cx:"-1.1",cy:"-0.4"})]})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"84",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"18",fontWeight:"700",fill:"#0a0a0a",children:"20"})]})}function FiftyPence({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("polygon",{points:HEPTAGON_OUTER,fill:"url(#coin-silver)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("polygon",{points:HEPTAGON_INNER,fill:"none",stroke:"#1a1a1a",strokeWidth:"0.4",opacity:"0.5"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"FIFTY PENCE",size:7.5}),jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",strokeLinejoin:"round",children:[jsxRuntimeExports.jsx("path",{d:"M 38 66 C 36 60 38 54 42 52 L 60 52 C 64 56 66 62 64 68 L 64 72 L 36 72 Z",fill:"#3a3a3a",strokeWidth:"0.5"}),jsxRuntimeExports.jsxs("g",{stroke:"#fff",strokeWidth:"0.4",fill:"none",opacity:"0.6",children:[jsxRuntimeExports.jsx("line",{x1:"44",y1:"54",x2:"42",y2:"70"}),jsxRuntimeExports.jsx("line",{x1:"48",y1:"54",x2:"46",y2:"70"}),jsxRuntimeExports.jsx("line",{x1:"52",y1:"54",x2:"50",y2:"70"}),jsxRuntimeExports.jsx("line",{x1:"56",y1:"54",x2:"55",y2:"70"}),jsxRuntimeExports.jsx("line",{x1:"60",y1:"54",x2:"60",y2:"70"})]}),jsxRuntimeExports.jsx("path",{d:"M 44 52 L 44 44 C 44 40 46 38 49 38 L 53 38 C 56 38 58 40 58 44 L 58 52 Z",fill:"#3a3a3a",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("path",{d:"M 44 44 L 38 48 L 36 56 L 38 56 L 40 48 L 46 46 Z",fill:"#3a3a3a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("path",{d:"M 56 42 L 62 34 L 64 32 L 64 35 L 60 38 L 58 46 Z",fill:"#3a3a3a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"32",r:"3.6",fill:"#c8c8c8",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("path",{d:"M 46 30 Q 46 24 50 24 Q 54 24 54 30",fill:"#3a3a3a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("path",{d:"M 50 24 Q 46 22 42 24 Q 46 23 50 26",fill:"#1a1a1a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"64",y1:"32",x2:"76",y2:"16",stroke:"#0a0a0a",strokeWidth:"1.2"}),jsxRuntimeExports.jsxs("g",{stroke:"#0a0a0a",strokeWidth:"0.6",fill:"#0a0a0a",children:[jsxRuntimeExports.jsx("line",{x1:"76",y1:"16",x2:"74",y2:"10"}),jsxRuntimeExports.jsx("line",{x1:"76",y1:"16",x2:"78",y2:"10"}),jsxRuntimeExports.jsx("line",{x1:"76",y1:"16",x2:"76",y2:"9"}),jsxRuntimeExports.jsx("polygon",{points:"73,10 75,10 74,8"}),jsxRuntimeExports.jsx("polygon",{points:"77,10 79,10 78,8"}),jsxRuntimeExports.jsx("polygon",{points:"75,9 77,9 76,7"}),jsxRuntimeExports.jsx("line",{x1:"73",y1:"14",x2:"79",y2:"14",strokeWidth:"0.8"})]}),jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:"32",cy:"56",r:"7.5",fill:"#fff",stroke:"#0a0a0a",strokeWidth:"0.7"}),jsxRuntimeExports.jsx("line",{x1:"24.5",y1:"56",x2:"39.5",y2:"56",stroke:"#0a0a0a",strokeWidth:"2"}),jsxRuntimeExports.jsx("line",{x1:"32",y1:"48.5",x2:"32",y2:"63.5",stroke:"#0a0a0a",strokeWidth:"2"}),jsxRuntimeExports.jsx("line",{x1:"26.7",y1:"50.7",x2:"37.3",y2:"61.3",stroke:"#0a0a0a",strokeWidth:"0.9"}),jsxRuntimeExports.jsx("line",{x1:"37.3",y1:"50.7",x2:"26.7",y2:"61.3",stroke:"#0a0a0a",strokeWidth:"0.9"}),jsxRuntimeExports.jsx("circle",{cx:"32",cy:"56",r:"7.5",fill:"none",stroke:"#0a0a0a",strokeWidth:"0.7"})]}),jsxRuntimeExports.jsxs("g",{fill:"#5a5a5a",stroke:"#0a0a0a",strokeWidth:"0.3",children:[jsxRuntimeExports.jsx("ellipse",{cx:"64",cy:"76",rx:"6",ry:"2"}),jsxRuntimeExports.jsx("circle",{cx:"68",cy:"74",r:"1.6"})]})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"84",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"14",fontWeight:"700",fill:"#0a0a0a",children:"50"})]})}
62function OnePound({hideValue:n}){return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("polygon",{points:DODECAGON_OUTER,fill:"url(#coin-gold)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"29",fill:"url(#coin-silver-core)",stroke:"#1a1a1a",strokeWidth:"0.6"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"ONE POUND",size:6.5}),jsxRuntimeExports.jsxs("g",{transform:"translate(50,52)",stroke:"#0a0a0a",strokeWidth:"0.4",children:[jsxRuntimeExports.jsx("line",{x1:"0",y1:"14",x2:"0",y2:"-2",strokeWidth:"0.8"}),jsxRuntimeExports.jsxs("g",{transform:"translate(0,-8)",children:[jsxRuntimeExports.jsx("circle",{r:"4",fill:"#3a3a3a",strokeWidth:"0.4"}),jsxRuntimeExports.jsx("circle",{r:"2.2",fill:"#1a1a1a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("circle",{r:"0.8",fill:"#fafafa"})]}),jsxRuntimeExports.jsxs("g",{transform:"translate(-9,2)",children:[jsxRuntimeExports.jsx("ellipse",{cx:"0",cy:"0",rx:"2.2",ry:"2.8",fill:"#e8e8e8",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"0",y1:"-2.5",x2:"-1.5",y2:"-9",stroke:"#3a3a3a",strokeWidth:"1.4"}),jsxRuntimeExports.jsx("line",{x1:"0",y1:"-2.5",x2:"1",y2:"-9",stroke:"#3a3a3a",strokeWidth:"1.4"})]}),jsxRuntimeExports.jsxs("g",{transform:"translate(9,2)",children:[jsxRuntimeExports.jsx("ellipse",{cx:"0",cy:"0",rx:"2.2",ry:"2.6",fill:"#7a7a7a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"0",y1:"-2",x2:"0",y2:"-5",stroke:"#1a1a1a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"-1",y1:"-2",x2:"-2",y2:"-5",stroke:"#1a1a1a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"1",y1:"-2",x2:"2",y2:"-5",stroke:"#1a1a1a",strokeWidth:"0.3"})]}),jsxRuntimeExports.jsxs("g",{transform:"translate(0,9)",children:[jsxRuntimeExports.jsx("circle",{r:"1.8",cx:"-2",cy:"-1",fill:"#3a3a3a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("circle",{r:"1.8",cx:"2",cy:"-1",fill:"#3a3a3a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("circle",{r:"1.8",cx:"0",cy:"2",fill:"#3a3a3a",strokeWidth:"0.3"}),jsxRuntimeExports.jsx("line",{x1:"0",y1:"2.5",x2:"0",y2:"5",stroke:"#3a3a3a",strokeWidth:"0.6"})]}),jsxRuntimeExports.jsxs("g",{transform:"translate(-3,-15)",children:[jsxRuntimeExports.jsx("rect",{x:"0",y:"2",width:"6",height:"1.4",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("path",{d:"M 0 2 L 0.7 0 L 1.5 1.4 L 3 -0.5 L 4.5 1.4 L 5.3 0 L 6 2",fill:"#0a0a0a"}),jsxRuntimeExports.jsx("circle",{cx:"3",cy:"-1",r:"0.5",fill:"#0a0a0a"})]})]}),!n&&jsxRuntimeExports.jsx("text",{x:"50",y:"91",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"14",fontWeight:"700",fill:"#0a0a0a",children:"£1"})]})}function TwoPound({hideValue:n}){const t=[0,30,60,90,120,150,180,210,240,270,300,330],e=[0,36,72,108,144,180,216,252,288,324],s=[0,45,90,135,180,225,270,315];return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"48",fill:"url(#coin-gold)",stroke:"#1a1a1a",strokeWidth:"0.8"}),jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"30",fill:"url(#coin-silver-core)",stroke:"#1a1a1a",strokeWidth:"0.6"}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"TWO POUNDS",size:6.5}),!n&&jsxRuntimeExports.jsx(CurvedLegend,{text:"2024",size:5.5,letterSpacing:3,position:"bottom"}),jsxRuntimeExports.jsxs("g",{transform:"translate(50,50)",stroke:"#0a0a0a",fill:"none",children:[jsxRuntimeExports.jsx("circle",{r:"26",strokeWidth:"0.6"}),jsxRuntimeExports.jsx("g",{strokeWidth:"0.7",children:t.map(o=>jsxRuntimeExports.jsx("line",{x1:"0",y1:"-26",x2:"0",y2:"-29",transform:`rotate(${o})`},o))}),jsxRuntimeExports.jsx("circle",{r:"20",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("g",{strokeWidth:"0.4",children:e.map(o=>jsxRuntimeExports.jsx("ellipse",{cx:"0",cy:"-20",rx:"1.4",ry:"2",transform:`rotate(${o})`},o))}),jsxRuntimeExports.jsx("circle",{r:"14",strokeWidth:"0.5"}),jsxRuntimeExports.jsx("g",{strokeWidth:"0.5",children:s.map(o=>jsxRuntimeExports.jsx("line",{x1:"0",y1:"-14",x2:"0",y2:"-9",transform:`rotate(${o})`},o))}),jsxRuntimeExports.jsx("circle",{r:"9",fill:"#dcdcdc",strokeWidth:"0.5"}),!n&&jsxRuntimeExports.jsx("text",{y:"3",textAnchor:"middle",fontFamily:"Georgia, serif",fontSize:"9",fontWeight:"700",fill:"#0a0a0a",stroke:"none",children:"£2"})]})]})}const COIN_VALUE={"1p":1,"2p":2,"5p":5,"10p":10,"20p":20,"50p":50,"£1":100,"£2":200};
62function formatAmount(n){if(n<100)return`${n}p`;const t=n/100;return Number.isInteger(t)?`£${t}`:`£${t.toFixed(2)}`}function CoinTap({data:n,onSelect:t,status:e,attemptsExhausted:s}){const{target:o,tray:r}=n,a=n.show_running_total===!0,i=`${o}|${r.join(",")}`,[l,u]=reactExports.useState({key:i,selected:r.map(()=>!1)}),d=l.key===i?l.selected:r.map(()=>!1);l.key!==i&&u({key:i,selected:d});const m=r.reduce((x,y,E)=>x+(d[E]?COIN_VALUE[y]:0),0),h=e==="correct",f=e==="incorrect"&&s,g=h||f;function b(x){if(g)return;const y=d.map((k,T)=>T===x?!k:k);u({key:i,selected:y});const E=r.reduce((k,T,A)=>k+(y[A]?COIN_VALUE[T]:0),0);t?.(E>0?String(E):"")}return jsxRuntimeExports.jsxs("div",{className:"coin-tap",children:[jsxRuntimeExports.jsx(CoinDefs,{}),jsxRuntimeExports.jsxs("div",{className:"coin-tap-target",children:["Make ",formatAmount(o)]}),jsxRuntimeExports.jsx("div",{className:"coin-tap-tray",role:"group","aria-label":`Tap coins to make ${formatAmount(o)}`,children:r.map((x,y)=>{const E=d[y],k=["coin-tap-btn",E&&!h&&!f?"coin-tap-selected":"",E&&h?"coin-tap-correct":"",E&&f?"coin-tap-wrong":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsxs("button",{type:"button",className:k,onClick:()=>b(y),"aria-pressed":E,"aria-label":`${x} coin${E?", selected":""}`,disabled:g,children:[jsxRuntimeExports.jsx(Coin,{denom:x,hideValue:!1}),E&&jsxRuntimeExports.jsx("span",{className:"coin-tap-tick","aria-hidden":"true",children:"✓"})]},`${x}-${y}`)})}),a&&jsxRuntimeExports.jsxs("div",{className:["coin-tap-total",h?"coin-tap-total--correct":"",f?"coin-tap-total--wrong":""].filter(Boolean).join(" "),"aria-live":"polite",children:["So far: ",jsxRuntimeExports.jsx("strong",{children:formatAmount(m)})]})]})}const NOUN_EMOJI$2={grape:"🍇",apple:"🍏",orange:"🍊",banana:"🍌",strawberry:"🍓",strawberrie:"🍓",pear:"🍐",lemon:"🍋",cherry:"🍒",cherrie:"🍒",cookie:"🍪",biscuit:"🍪",cake:"🧁",cupcake:"🧁",sweet:"🍬",lolly:"🍭",balloon:"🎈",star:"⭐",flower:"🌸",fish:"🐟",bird:"🐦",car:"🚗",ball:"⚽",shell:"🐚",egg:"🥚",pencil:"✏️",book:"📚",leaf:"🍃",leave:"🍃"};function singularise(n){const t=n.toLowerCase();return t.endsWith("s")?t.slice(0,-1):t}function nounEmoji(n){if(!n)return null;const t=n.toLowerCase().match(/[a-z]+/g);if(!t)return null;for(const e of t){const s=NOUN_EMOJI$2[e];if(s)return s;const o=NOUN_EMOJI$2[singularise(e)];if(o)return o}return null}function TakeAway({data:n,stem:t}){const{total:e,take_away:s}=n,o=Array.from({length:e},(a,i)=>i),r=nounEmoji(t);return jsxRuntimeExports.jsx("div",{className:"ta-wrap","aria-hidden":"true",children:jsxRuntimeExports.jsx("div",{className:"ta-row",children:o.map(a=>{const i=a>=e-s,l=`ta-counter${r?" ta-counter--emoji":""}${i?" ta-counter--taken":""}`;return jsxRuntimeExports.jsx("span",{className:l,children:r},a)})})})}function TenFrameMixed({data:n}){const t=Array(10).fill(null);let e=0;for(const s of n.groups)for(let o=0;o<s.count&&e<10;o++,e++)t[e]=s.color;return jsxRuntimeExports.jsx("div",{className:`ten-frame-wrap ${styles$3.mixed}`,children:jsxRuntimeExports.jsx("div",{className:"ten-frame-grid",children:t.map((s,o)=>jsxRuntimeExports.jsx("div",{className:`tf-cell ${s?`tf-filled tf-filled-${s}`:"tf-empty"}`,children:s&&jsxRuntimeExports.jsx("div",{className:`tf-dot tf-dot-${s}`})},o))})})}const STROKE_PX=3,MARK_HEIGHT=44,MARK_PITCH=9,GATE_WIDTH=MARK_PITCH*3+STROKE_PX,GROUP_GAP=18,PAD_X$3=8,PAD_Y=4;function GateGroup({x:n}){const t=[0,1,2,3].map(e=>n+e*MARK_PITCH);return jsxRuntimeExports.jsxs("g",{children:[t.map((e,s)=>jsxRuntimeExports.jsx("line",{x1:e,y1:PAD_Y,x2:e,y2:PAD_Y+MARK_HEIGHT,strokeWidth:STROKE_PX,strokeLinecap:"round",stroke:"#1a1a1a"},s)),jsxRuntimeExports.jsx("line",{x1:n-STROKE_PX,y1:PAD_Y+MARK_HEIGHT-4,x2:n+3*MARK_PITCH+STROKE_PX,y2:PAD_Y+4,strokeWidth:STROKE_PX,strokeLinecap:"round",stroke:"#1a1a1a"})]})}function RemainderGroup({x:n,count:t}){const e=Array.from({length:t},(s,o)=>n+o*MARK_PITCH);return jsxRuntimeExports.jsx("g",{children:e.map((s,o)=>jsxRuntimeExports.jsx("line",{x1:s,y1:PAD_Y,x2:s,y2:PAD_Y+MARK_HEIGHT,strokeWidth:STROKE_PX,strokeLinecap:"round",stroke:"#1a1a1a"},o))})}function Tally({data:n}){const{count:t}=n;if(t<=0)return jsxRuntimeExports.jsx("div",{className:"tally-wrap tally-empty",children:"0"});const e=Math.floor(t/5),s=t%5,o=s===0?0:(s-1)*MARK_PITCH+STROKE_PX;let r=e+(s>0?1:0),a=PAD_X$3*2;a+=e*GATE_WIDTH+(s>0?o:0),r>1&&(a+=(r-1)*GROUP_GAP);const i=PAD_Y*2+MARK_HEIGHT,l=[];let u=PAD_X$3;for(let d=0;d<e;d++)l.push(jsxRuntimeExports.jsx(GateGroup,{x:u},`g${d}`)),u+=GATE_WIDTH+GROUP_GAP;return s>0&&l.push(jsxRuntimeExports.jsx(RemainderGroup,{x:u,count:s},"rem")),jsxRuntimeExports.jsx("div",{className:"tally-wrap",children:jsxRuntimeExports.jsx("svg",{className:"tally-svg",width:a,height:i,viewBox:`0 0 ${a} ${i}`,role:"img","aria-label":`Tally showing ${t}`,children:l})})}function TenFrameAndOnesPaired({data:n}
62){const{left:t,right:e,symbol:s}=n,o=s!==void 0,r=s===null;return jsxRuntimeExports.jsxs("div",{className:"tfaop-wrap",children:[jsxRuntimeExports.jsx("div",{className:"tfaop-side",children:jsxRuntimeExports.jsx(TenFrameAndOnes,{data:{type:"ten_frame_and_ones",tens:t.tens,ones:t.ones}})}),o&&jsxRuntimeExports.jsx("div",{className:`tfaop-symbol${r?" tfaop-symbol-unknown":""}`,children:r?"?":s}),jsxRuntimeExports.jsx("div",{className:"tfaop-side",children:jsxRuntimeExports.jsx(TenFrameAndOnes,{data:{type:"ten_frame_and_ones",tens:e.tens,ones:e.ones}})})]})}function orderValue(n){if(typeof n=="number")return n;const t=n.trim(),e=/^(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)$/.exec(t);if(e)return Number(e[1])/Number(e[2]);const s=/^(\d+(?:\.\d+)?)\s*%$/.exec(t);return s?Number(s[1])/100:Number(t)}function DragOrder({data:n,onSelect:t,status:e,attemptsExhausted:s}){const{numbers:o,direction:r}=n,a=o.length,i=e==="correct"||e==="incorrect"&&s,[l,u]=reactExports.useState(()=>Array(a).fill(null)),[d,m]=reactExports.useState(null),h=`${o.join(",")}|${r}`,f=reactExports.useRef(h);reactExports.useEffect(()=>{f.current!==h&&(f.current=h,u(Array(a).fill(null)),m(null))},[h,a]),reactExports.useEffect(()=>{t&&(l.every(M=>M!==null)?t(l.join(",")):t(""))},[l,t]);const g=new Set(l.filter(M=>M!==null)),b=o.filter(M=>!g.has(M)||M===d);function x(M){m(L=>L===M?null:M)}function y(M){const L=l[M];if(d!==null){u(P=>P.map((F,D)=>D===M?d:F)),m(null);return}L!==null&&(u(P=>P.map((F,D)=>D===M?null:F)),m(L))}function E(){u(Array(a).fill(null)),m(null)}const k=[...o].sort((M,L)=>r==="asc"?orderValue(M)-orderValue(L):orderValue(L)-orderValue(M)),T=M=>!i||l[M]===null?"":l[M]===k[M]?"ok":"wrong",A=r==="asc"?"smallest":"largest",N=r==="asc"?"largest":"smallest",S=l.some(M=>M!==null);return jsxRuntimeExports.jsxs("div",{className:`dord-wrap dord-count-${a} ${styles$3.order}`,children:[jsxRuntimeExports.jsx("p",{className:styles$3.orderInstruction,children:"Tap a number, then an empty box."}),jsxRuntimeExports.jsx("div",{className:"dord-pool","aria-label":"Number pool",children:b.length===0?jsxRuntimeExports.jsx("span",{className:"dord-pool-empty",children:"— all placed —"}):b.map(M=>jsxRuntimeExports.jsx("button",{type:"button",className:`dord-card${d===M?" dord-card-held":""}`,onClick:()=>x(M),"aria-pressed":d===M,children:M},M))}),jsxRuntimeExports.jsxs("div",{className:styles$3.orderDirection,"aria-label":`Order from ${A} to ${N}`,children:[jsxRuntimeExports.jsx("span",{className:"dord-direction-label",children:A}),jsxRuntimeExports.jsx("span",{className:styles$3.orderArrow,children:"→"}),jsxRuntimeExports.jsx("span",{className:"dord-direction-label",children:N})]}),jsxRuntimeExports.jsxs("div",{className:`dord-slot-row ${styles$3.orderSlots}`,"aria-label":"Ordered slots",children:[jsxRuntimeExports.jsx("span",{className:"sm-sr-only",children:A}),l.map((M,L)=>{const P=T(L),F=["dord-slot"];return M===null&&F.push("dord-slot-empty"),P&&F.push(`dord-slot-${P}`),jsxRuntimeExports.jsx("button",{type:"button",className:F.join(" "),onClick:()=>y(L),"aria-label":M===null?`Empty slot ${L+1}`:`Slot ${L+1}: ${M}`,children:M??""},L)})]}),S&&jsxRuntimeExports.jsx("button",{type:"button",className:"dord-clear-btn",onClick:E,children:"Clear all"})]})}function ArrayGrid({data:n}){const t=Math.max(1,Math.min(Math.round(n.rows),10)),e=Math.max(1,Math.min(Math.round(n.cols),10)),s=n.emoji,o=44,r=14,a=16,i=14,l=e*o+(e-1)*r,u=t*o+(t-1)*r,d=l+a*2,m=u+a*2,h=Array.from({length:t},(x,y)=>y),f=Array.from({length:e},(x,y)=>y),g=x=>a+x*(o+r)+o/2,b=x=>a+x*(o+r)+o/2;return jsxRuntimeExports.jsx("div",{className:"arr-wrap",children:jsxRuntimeExports.jsx("svg",{viewBox:`0 0 ${d} ${m}`,className:"arr-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${Math.min(d,360)}px`,height:"auto"},role:"img","aria-label":`An array of ${t} ${t===1?"row":"rows"} and ${e} ${e===1?"column":"columns"}`,children:h.map(x=>f.map(y=>{const E=g(y),k=b(x);return s?jsxRuntimeExports.jsx("text",{x:E,y:k,fontSize:o*.78,textAnchor:"middle",dominantBaseline:"central","aria-hidden":"true",children:s},`${x}-${y}`):jsxRuntimeExports.jsx("circle",{cx:E,cy:k,r:i,fill:"#2563eb",stroke:"#1e3a8a",strokeWidth:2},`${x}-${y}`)}))})})}function barChartScale(n,t,e){const s=n<=40||Math.ceil(n/t)<=12?t:e,o=Math.ceil(n/s)*s,r=Array.from({length:o/s+1},(l,u)=>u*s),a=s>1?o<=40?1:Math.max(1,Math.round(s/5)):0,i=a?Array.from({length:Math.floor(o/a)+1},(l,u)=>u*a).filter(l=>l%s!==0):[];return{step:s,axisTop:o,ticks:r,minorTicks:i,plotHeight:Math.max(200,o/s*16)}}const LINE_COLOR="#2f6fd6",DOT_FILL="#2f6fd6",AXIS_INK="#33373d",MAX_POINTS$1=8,NICE_STEPS=[1,2,5,10,20,25,50,100,200,250,500,1e3];function niceAxisStep(n){for(const t of NICE_STEPS)if(Math.ceil(n/t)<=12)return t;return NICE_STEPS[NICE_STEPS.length-1]}function LineGraph({data:n}){const{x_labels:t,values:e,axis_max:s,axis_step:o,y_axis_label:r,x_label:a,unit:i,highlight_index:l}=n,u=Math.min(t.length,e.length,MAX_POINTS$1),d=Array.from({length:u},(Q,B)=>({label:t[B],value:e[B]})),m=Math.max(...e.slice(0,u),1),h=Math.max(s??0,m),f=o!=null&&Math.ceil(h/o)<=12?o:niceAxisStep(h),g=Math.ceil(h/f)*f,b=Array.from({length:g/f+1},(Q,B)=>B*f),x=f>1?Math.max(1,Math.round(f/5)):0,y=x?Array.from({length:Math.floor(g/x)+1},(Q,B)=>B*x).filter(Q=>Q%f!==0):[],E=r?`${r}${i?` (${i})`:""}`:i||"",k=E?56:40,T=20,A=16,N=u<=2?150:200,S=A+N,M=34,L=a?24:0,P=S+M+L,F=60,D=Math.max(1,u-1)*F+24,U=k,q=k+D+T,V=Q=>S-Q/g*N,O=12,z=Q=>U+O+Q*F,K=d.map((Q,B)=>`${z(B)},${V(Q.value)}`).join(" "),Z=l!=null&&l>=0&&l<u;return jsxRuntimeExports.jsx("div",{className:"lg-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${q} ${P}`,className:"lg-svg",style:u<=2?{maxWidth:220}:void 0,role:"img","aria-label":`Line graph${E?` of ${E}`:""}${a?` against ${a}`:""}, with a value axis from 0 to ${g} in steps of ${f}. Read each point's height off the axis.`,children:[y.map(Q=>{const B=V(Q);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:U,y1:B,x2:U+D,y2:B,stroke:"#eef0f3",strokeWidth:1}),jsxRuntimeExports.jsx("line",{x1:U-3,y1:B,x2:U,y2:B,stroke:"#aab1bb",strokeWidth:1.5})]},`minor-${Q}`)}),b.map(Q=>{const B=V(Q);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:U,y1:B,x2:U+D,y2:B,stroke:"#d6dbe2",strokeWidth:Q===0?0:1.5}),jsxRuntimeExports.jsx("line",{x1:U-6,y1:B,x2:U,y2:B,stroke:AXIS_INK,strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:U-10,y:B+5,textAnchor:"end",fontSize:13,fontWeight:700,fill:AXIS_INK,children:Q})]},Q)}),Z&&jsxRuntimeExports.jsx("line",{x1:U,y1:V(d[l].value),x2:z(l),y2:V(d[l].value),stroke:"#b45309",strokeWidth:1.5,strokeDasharray:"4,3"}),jsxRuntimeExports.jsx("polyline",{points:K,fill:"none",stroke:LINE_COLOR,strokeWidth:2.5,strokeLinejoin:"round",strokeLinecap:"round"}),d.map((Q,B)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:z(B),cy:V(Q.value),r:5,fill:DOT_FILL,stroke:"#fff",strokeWidth:1.5}),jsxRuntimeExports.jsx("text",{x:z(B),y:S+18,textAnchor:"middle",fontSize:12,fontWeight:700,fill:AXIS_INK,children:Q.label})]},B)),jsxRuntimeExports.jsx("line",{x1:U,y1:A,x2:U,y2:S,stroke:AXIS_INK,strokeWidth:2.5}),jsxRuntimeExports.jsx("line",{x1:U,y1:S,x2:U+D,y2:S,stroke:AXIS_INK,strokeWidth:2.5}),E&&jsxRuntimeExports.jsx("text",{x:16,y:A+N/2,textAnchor:"middle",fontSize:12,fontWeight:700,fill:AXIS_INK,transform:`rotate(-90 16 ${A+N/2})`,children:E}),a&&jsxRuntimeExports.jsx("text",{x:U+D/2,y:P-6,textAnchor:"middle",fontSize:12,fontWeight:700,fill:AXIS_INK,children:a})]})})}const DEFAULT_COLORS=["#2f6fd6","#e8843c","#3aa66b","#a45bd1","#d94f6e"],CHAR_W_RATIO=.58;function fitLabel(n,t,e){const s=(i,l)=>i.length*l*CHAR_W_RATIO<=t-6;if(s(n,e))return{lines:[n],fontSize:e};const o=Math.max(11,e-2);if(s(n,o))return{lines:[n],fontSize:o};
62const r=n.split(/\s+/).filter(Boolean);if(r.length>1){let i=null;for(let u=1;u<r.length;u++){const d=r.slice(0,u).join(" "),m=r.slice(u).join(" ");s(d,o)&&s(m,o)&&(!i||Math.max(d.length,m.length)<Math.max(i[0].length,i[1].length))&&(i=[d,m])}if(i)return{lines:i,fontSize:o};const l=Math.ceil(r.length/2);return{lines:[r.slice(0,l).join(" "),r.slice(l).join(" ")],fontSize:o}}let a=o;for(;a>9&&!s(n,a);)a-=1;return{lines:[n],fontSize:a}}function BarChart({data:n}){const{bars:t,axis_max:e,axis_step:s,y_label:o,x_label:r}=n,a=Math.max(...t.map(U=>U.value),1),i=Math.max(e??0,a),l=s??(i<=10?1:2),{step:u,axisTop:d,ticks:m,minorTicks:h,plotHeight:f}=barChartScale(i,l,niceAxisStep(i)),g=o?56:40,b=16,x=16,y=x+f,E=40,k=r?24:0,T=y+E+k,A=t.length,N=64,S=40,M=A*N,L=(N-S)/2,P=g+M+b,F=U=>y-U/d*f,D=g;return jsxRuntimeExports.jsx("div",{className:"bc-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${P} ${T}`,className:"bc-svg",role:"img","aria-label":`Bar chart${o?` of ${o}`:""}${r?` by ${r}`:""}, with a value axis from 0 to ${d} in steps of ${u}. Read each bar's height off the axis.`,children:[h.map(U=>{const q=F(U);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:D,y1:q,x2:D+M,y2:q,stroke:"#eef0f3",strokeWidth:1}),jsxRuntimeExports.jsx("line",{x1:D-3,y1:q,x2:D,y2:q,stroke:"#aab1bb",strokeWidth:1.5})]},`minor-${U}`)}),m.map(U=>{const q=F(U);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:D,y1:q,x2:D+M,y2:q,stroke:"#d6dbe2",strokeWidth:U===0?0:1.5}),jsxRuntimeExports.jsx("line",{x1:D-6,y1:q,x2:D,y2:q,stroke:"#33373d",strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:D-10,y:q+5,textAnchor:"end",fontSize:13,fontWeight:700,fill:"#33373d",children:U})]},U)}),t.map((U,q)=>{const V=U.color??colourForLabel(U.label)??DEFAULT_COLORS[q%DEFAULT_COLORS.length],O=D+q*N+L,z=F(U.value),K=y-z,{lines:Z,fontSize:Q}=fitLabel(U.label,N-4,13);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:O,y:z,width:S,height:K,rx:5,fill:V,stroke:"#33373d",strokeWidth:1.5}),jsxRuntimeExports.jsx("text",{x:O+S/2,y:y+20,textAnchor:"middle",fontSize:Q,fontWeight:700,fill:"#33373d",children:Z.map((B,G)=>jsxRuntimeExports.jsx("tspan",{x:O+S/2,dy:G===0?0:Q+1,children:B},G))})]},q)}),jsxRuntimeExports.jsx("line",{x1:D,y1:x,x2:D,y2:y,stroke:"#33373d",strokeWidth:2.5}),jsxRuntimeExports.jsx("line",{x1:D,y1:y,x2:D+M,y2:y,stroke:"#33373d",strokeWidth:2.5}),o&&jsxRuntimeExports.jsx("text",{x:16,y:x+f/2,textAnchor:"middle",fontSize:12,fontWeight:700,fill:"#33373d",transform:`rotate(-90 16 ${x+f/2})`,children:o}),r&&jsxRuntimeExports.jsx("text",{x:D+M/2,y:T-6,textAnchor:"middle",fontSize:12,fontWeight:700,fill:"#33373d",children:r})]})})}const CX$5=60,CY$5=60,R$5=42,TURN_DEGREES={quarter:90,half:180,three_quarter:270,full:360},START_BEARING$3={up:0,right:90,down:180,left:270};function point(n,t){const e=(n-90)*Math.PI/180;return{x:CX$5+t*Math.cos(e),y:CY$5+t*Math.sin(e)}}function TurnDiagram({data:n}){const{turn:t,direction:e}=n,s=n.start??"up",o=START_BEARING$3[s],r=TURN_DEGREES[t],a=e==="clockwise"?r:-r,i=o+a,l=point(o,R$5),u=point(i,R$5),d=28,m=Math.min(r,350),h=o+(e==="clockwise"?m:-m),f=point(o,d),g=point(h,d),b=m>180?1:0,x=e==="clockwise"?1:0,y=`M ${f.x.toFixed(2)} ${f.y.toFixed(2)} A ${d} ${d} 0 ${b} ${x} ${g.x.toFixed(2)} ${g.y.toFixed(2)}`,E=h+(e==="clockwise"?90:-90),k=9,T=22,A=g.x,N=g.y,S=D=>{const U=(E+180+D)*Math.PI/180;return{x:A+k*Math.cos(U),y:N+k*Math.sin(U)}},M=S(T),L=S(-T),F=`A pointer turning ${e==="clockwise"?"clockwise":"anticlockwise"} from pointing ${s}, with a dashed pointer showing where it started and a curved arrow showing the turn`;return jsxRuntimeExports.jsx("div",{className:"turn-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 120 120",className:"turn-svg",role:"img","aria-label":F,style:{width:"100%",maxWidth:220,height:"auto"},children:[jsxRuntimeExports.jsx("circle",{cx:CX$5,cy:CY$5,r:R$5,fill:"#f1f5f9",stroke:"#cbd5e1",strokeWidth:1}),jsxRuntimeExports.jsx("line",{x1:CX$5,y1:CY$5,x2:l.x,y2:l.y,stroke:"#94a3b8",strokeWidth:4,strokeLinecap:"round",strokeDasharray:"5 4"}),jsxRuntimeExports.jsx("circle",{cx:l.x,cy:l.y,r:4,fill:"#94a3b8"}),jsxRuntimeExports.jsx("path",{d:y,fill:"none",stroke:"#7c3aed",strokeWidth:3.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("polygon",{points:`${A.toFixed(2)},${N.toFixed(2)} ${M.x.toFixed(2)},${M.y.toFixed(2)} ${L.x.toFixed(2)},${L.y.toFixed(2)}`,fill:"#7c3aed"}),jsxRuntimeExports.jsx("line",{x1:CX$5,y1:CY$5,x2:u.x,y2:u.y,stroke:"#1e293b",strokeWidth:6,strokeLinecap:"round"}),jsxRuntimeExports.jsx("circle",{cx:u.x,cy:u.y,r:6,fill:"#1e293b"}),jsxRuntimeExports.jsx("circle",{cx:CX$5,cy:CY$5,r:5,fill:"#1e293b"}),jsxRuntimeExports.jsx("circle",{cx:CX$5,cy:CY$5,r:2.5,fill:"#ffffff"})]})})}const DISPLAY_EMOJI={"🍎":"🍏"};function displayEmoji(n){return DISPLAY_EMOJI[n]??n}function displayEmojiInText(n){let t=n;for(const[e,s]of Object.entries(DISPLAY_EMOJI))t=t.split(e).join(s);return t}const SYMBOL_SIZE=30,SYMBOL_PITCH=34,ROW_HEIGHT=44,LABEL_WIDTH=96,LANE_PAD=8,PAD_X$2=14,KEY_HEIGHT=40,TOP_PAD=12,BOTTOM_PAD=12;function Pictogram({data:n}){const{rows:t,emoji:e,per_symbol:s,key_label:o}=n,r=displayEmoji(e),a=T=>Math.max(0,Math.floor(T+1e-9)),i=T=>T-Math.floor(T+1e-9)>=.5-1e-9,l=T=>a(T)+(i(T)?1:0),u=Math.max(...t.map(T=>l(T.symbols)),1),d=LANE_PAD*2+(u-1)*SYMBOL_PITCH+SYMBOL_SIZE,m=PAD_X$2+LABEL_WIDTH,h=m+d+PAD_X$2,f=TOP_PAD+KEY_HEIGHT+8,g=f+t.length*ROW_HEIGHT+BOTTOM_PAD,b=pictogramRowColours(t),x=o?displayEmojiInText(o):`each ${r} = ${s}`,y=b?x.split(r).join(PICTOGRAM_COUNTER):x,E=(T,A,N,S)=>b?jsxRuntimeExports.jsx("circle",{cx:A,cy:N,r:SYMBOL_SIZE*.4,fill:b[T],stroke:"#475569",strokeWidth:1.5},S):jsxRuntimeExports.jsx("text",{x:A,y:N,fontSize:SYMBOL_SIZE,textAnchor:"middle",dominantBaseline:"middle",filter:"url
62(#pic-edge)",children:r},S),k=`Pictogram. Key: ${y}. ${t.map(T=>{const A=a(T.symbols),S=i(T.symbols)?`${A} and a half symbols`:`${A} symbols`;return`${T.label}: ${S}`}).join("; ")}.`;return jsxRuntimeExports.jsx("div",{className:`pic-wrap ${styles$3.pictogram}`,children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${h} ${g}`,className:"pic-svg",role:"img","aria-label":k,style:{maxWidth:"100%",height:"auto"},children:[jsxRuntimeExports.jsxs("defs",{children:[jsxRuntimeExports.jsx("clipPath",{id:"pic-half",children:jsxRuntimeExports.jsx("rect",{x:-SYMBOL_SIZE/2,y:-SYMBOL_SIZE,width:SYMBOL_SIZE/2,height:SYMBOL_SIZE*2})}),jsxRuntimeExports.jsx("filter",{id:"pic-edge",x:"-20%",y:"-20%",width:"140%",height:"140%",children:jsxRuntimeExports.jsx("feDropShadow",{dx:"0",dy:"0",stdDeviation:"0.7",floodColor:"#475569",floodOpacity:"0.55"})})]}),jsxRuntimeExports.jsx("rect",{x:PAD_X$2,y:TOP_PAD,width:h-PAD_X$2*2,height:KEY_HEIGHT,rx:10,className:styles$3.pictogramKey,fill:"#fffbeb",stroke:"#fbbf24",strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:PAD_X$2+14,y:TOP_PAD+KEY_HEIGHT/2+1,fontSize:15,fontWeight:600,className:styles$3.pictogramLabel,fill:"#92400e",dominantBaseline:"middle",children:`Key: ${y}`}),t.map((T,A)=>{const N=f+A*ROW_HEIGHT,S=N+ROW_HEIGHT/2,M=a(T.symbols),L=i(T.symbols);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:m,y:N+3,width:d,height:ROW_HEIGHT-6,rx:8,className:A%2===0?styles$3.pictogramLane:styles$3.pictogramLaneAlt,fill:A%2===0?"#f1f5f9":"#e2e8f0"}),jsxRuntimeExports.jsx("text",{x:PAD_X$2+LABEL_WIDTH-10,y:S+1,fontSize:15,fontWeight:600,className:styles$3.pictogramLabel,fill:"#1e293b",textAnchor:"end",dominantBaseline:"middle",children:T.label}),Array.from({length:M},(P,F)=>E(A,m+LANE_PAD+F*SYMBOL_PITCH+SYMBOL_SIZE/2,S+1,F)),L&&jsxRuntimeExports.jsx("g",{transform:`translate(${m+LANE_PAD+M*SYMBOL_PITCH+SYMBOL_SIZE/2}, ${S+1})`,clipPath:"url(#pic-half)",children:E(A,0,0)})]},A)})]})})}const DEFAULT_COLOR="#3b82f6",EDGE$4="#1e293b",EDGE_W=2.5,edge={stroke:EDGE$4,strokeWidth:EDGE_W,strokeLinejoin:"round",strokeLinecap:"round"},hidden={stroke:EDGE$4,strokeWidth:1.4,strokeDasharray:"4 4",strokeLinecap:"round",fill:"none"};function Cube({color:n}){return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("polyline",{points:"22,78 22,40 38,24",...hidden}),jsxRuntimeExports.jsx("line",{x1:"22",y1:"78",x2:"62",y2:"78",...hidden}),jsxRuntimeExports.jsx("polygon",{points:"22,40 62,40 78,24 38,24",fill:n,fillOpacity:.55,...edge}),jsxRuntimeExports.jsx("polygon",{points:"62,40 78,24 78,62 62,78",fill:n,fillOpacity:.85,...edge}),jsxRuntimeExports.jsx("polygon",{points:"22,40 62,40 62,78 22,78",fill:n,fillOpacity:.35,...edge})]})}function Cuboid({color:n}){return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("polyline",{points:"14,72 14,46 30,30",...hidden}),jsxRuntimeExports.jsx("line",{x1:"14",y1:"72",x2:"70",y2:"72",...hidden}),jsxRuntimeExports.jsx("polygon",{points:"14,46 70,46 86,30 30,30",fill:n,fillOpacity:.55,...edge}),jsxRuntimeExports.jsx("polygon",{points:"70,46 86,30 86,56 70,72",fill:n,fillOpacity:.85,...edge}),jsxRuntimeExports.jsx("polygon",{points:"14,46 70,46 70,72 14,72",fill:n,fillOpacity:.35,...edge})]})}function Sphere({color:n}){return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:"50",cy:"50",r:"34",fill:n,fillOpacity:.45,...edge}),jsxRuntimeExports.jsx("ellipse",{cx:"50",cy:"50",rx:"34",ry:"11",...hidden}),jsxRuntimeExports.jsx("ellipse",{cx:"40",cy:"38",rx:"9",ry:"6",fill:"#ffffff",fillOpacity:.7,stroke:"none"})]})}function Cylinder({color:n}){return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("path",{d:"M20,28 L20,72 A30,10 0 0 0 80,72 L80,28 Z",fill:n,fillOpacity:.4,...edge}),jsxRuntimeExports.jsx("path",{d:"M20,72 A30,10 0 0 1 80,72",...hidden}),jsxRuntimeExports.jsx("ellipse",{cx:"50",cy:"28",rx:"30",ry:"10",fill:n,fillOpacity:.65,...edge})]})}function Cone({color:n}){return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("path",{d:"M50,16 L18,74 A32,10 0 0 0 82,74 Z",fill:n,fillOpacity:.45,...edge}),jsxRuntimeExports.jsx("path",{d:"M18,74 A32,10 0 0 1 82,74",...hidden})]})}function Pyramid({color:n}
62){const t="50,16",e="40,82",s="84,64",o="16,64";return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("polyline",{points:`${o} 60,52 ${s}`,...hidden}),jsxRuntimeExports.jsx("line",{x1:"60",y1:"52",x2:"50",y2:"16",...hidden}),jsxRuntimeExports.jsx("polygon",{points:`${t} ${e} ${s}`,fill:n,fillOpacity:.8,...edge}),jsxRuntimeExports.jsx("polygon",{points:`${t} ${e} ${o}`,fill:n,fillOpacity:.45,...edge})]})}const SOLID_COMPONENTS={cube:Cube,cuboid:Cuboid,sphere:Sphere,cylinder:Cylinder,cone:Cone,pyramid:Pyramid},SHAPE_LABEL={cube:"a cube",cuboid:"a cuboid",sphere:"a sphere",cylinder:"a cylinder",cone:"a cone",pyramid:"a pyramid"};function Solid({shape:n,color:t}){const e=SOLID_COMPONENTS[n];return jsxRuntimeExports.jsx(e,{color:t??DEFAULT_COLOR})}function Shape3D({data:n}){const{shape:t}=n,e=n.color??DEFAULT_COLOR,s=SOLID_COMPONENTS[t];return jsxRuntimeExports.jsx("div",{className:"s3d-wrap",children:jsxRuntimeExports.jsx("svg",{viewBox:"0 0 100 100",className:"s3d-svg",role:"img","aria-label":`A 3D drawing of ${SHAPE_LABEL[t]}`,children:jsxRuntimeExports.jsx(s,{color:e})})})}function ColumnMethod({data:n}){const{operation:t,top:e,bottom:s}=n,o=String(e),r=String(s),a=t==="add"?e+s:e-s,i=Math.max(o.length,r.length,String(Math.abs(a)).length),l=30,u=26,d=12,m=10,h=40,f=d+u,g=i*l,b=f+g+d,x=m+28,y=x+h,E=y+12,k=E+16,T=S=>f+S*l+l/2,A=(S,M)=>S.split("").map((L,P)=>{const F=i-S.length+P;return jsxRuntimeExports.jsx("text",{x:T(F),y:M,textAnchor:"middle",fontSize:26,fontWeight:700,fontFamily:"ui-monospace, 'SFMono-Regular', Menlo, monospace",fill:"#1f2733",children:L},P)}),N=t==="add"?"+":"−";return jsxRuntimeExports.jsx("div",{className:"cm-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${b} ${k}`,className:"cm-svg",role:"img","aria-label":`${t==="add"?"Addition":"Subtraction"} in columns: ${e} ${t==="add"?"plus":"minus"} ${s}. Work out the answer.`,children:[A(o,x),jsxRuntimeExports.jsx("text",{x:d+u/2,y,textAnchor:"middle",fontSize:26,fontWeight:700,fontFamily:"ui-monospace, 'SFMono-Regular', Menlo, monospace",fill:"#1f2733",children:N}),A(r,y),jsxRuntimeExports.jsx("line",{x1:d,y1:E,x2:f+g,y2:E,stroke:"#1f2733",strokeWidth:2.5})]})})}function TwoWayTable({data:n}){const{col_headers:t,row_headers:e,cells:s,corner_label:o}=n,r=t.length>=5;return jsxRuntimeExports.jsx("div",{className:`twt-wrap ${styles$3.table}`,children:jsxRuntimeExports.jsxs("table",{className:r?"twt-table twt-wide":"twt-table",children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{className:"twt-corner",children:o??""}),t.map((a,i)=>jsxRuntimeExports.jsx("th",{scope:"col",className:"twt-colhead",children:a},i))]})}),jsxRuntimeExports.jsx("tbody",{children:e.map((a,i)=>jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("th",{scope:"row",className:"twt-rowhead",children:a}),t.map((l,u)=>{const d=s[i]?.[u],m=d==null;return jsxRuntimeExports.jsx("td",{className:m?"twt-cell twt-unknown":"twt-cell",children:m?"?":d},u)})]},i))})]})})}function CoordinateGrid({data:n}){const{max_x:t,max_y:e,points:s}=n,o=30,r=34,a=28,i=22,l=30,u=t*o,d=e*o,m=r+u+a,h=i+d+l,f=r,g=i+d,b=k=>f+k*o,x=k=>g-k*o,y=Array.from({length:t+1},(k,T)=>T),E=Array.from({length:e+1},(k,T)=>T);return jsxRuntimeExports.jsx("div",{className:"cg-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${m} ${h}`,className:"cg-svg",role:"img","aria-label":`A coordinate grid with an x-axis from 0 to ${t} and a y-axis from 0 to ${e}, with ${s.length} marked point${s.length===1?"":"s"}.`,children:[y.map(k=>jsxRuntimeExports.jsx("line",{x1:b(k),y1:x(0),x2:b(k),y2:x(e),stroke:"#dde3ea",strokeWidth:1},`v${k}`)),E.map(k=>jsxRuntimeExports.jsx("line",{x1:b(0),y1:x(k),x2:b(t),y2:x(k),stroke:"#dde3ea",strokeWidth:1},`h${k}`)),jsxRuntimeExports.jsx("line",{x1:b(0),y1:x(0),x2:b(t),y2:x(0),stroke:"#33373d",strokeWidth:2}),jsxRuntimeExports.jsx("line",{x1:b(0),y1:x(0),x2:b(0),y2:x(e),stroke:"#33373d",strokeWidth:2}),y.map(k=>jsxRuntimeExports.jsx("text",{x:b(k),y:x(0)+16,textAnchor:"middle",fontSize:11,fontWeight:600,fill:"#5b6b85",children:k},`xn${k}`)),E.map(k=>jsxRuntimeExports.jsx("text",{x:b(0)-8,y:x(k)+4,textAnchor:"end",fontSize:11,fontWeight:600,fill:"#5b6b85",children:k},`yn${k}`)),jsxRuntimeExports.jsx("text",{x:b(t)+15,y:x(0)+5,textAnchor:"middle",fontSize:14,fontWeight:700,fontStyle:"italic",fill:"#33373d",children:"x"}),jsxRuntimeExports.jsx("text",{x:b(0),y:x(e)-11,textAnchor:"middle",fontSize:14,fontWeight:700,fontStyle:"italic",fill:"#33373d",children:"y"}),s.map((k,T)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:b(k.x),cy:x(k.y),r:5,fill:"#2563c9",stroke:"#1f2733",strokeWidth:1.2}),k.label&&jsxRuntimeExports.jsx("text",{x:b(k.x)+8,y:x(k.y)-7,fontSize:13,fontWeight:700,fill:"#1f2733",children:k.label})]},T))]})})}function AngleDiagram({data:n}){const{angles:t,mark_right_angle:e=!1}=n,s=n.baseline??"none";if(s==="point"){const M=t.reduce((L,P)=>
62L+P.degrees,0);M!==360&&typeof console<"u"&&console.warn(`AngleDiagram: "point" mode angles should sum to 360°, got ${M}°`)}const o=220,r=o/2,a=s==="point"?o/2:o/2+10,i=92,l=34,u=52,d=16,m=M=>M*Math.PI/180,h=(M,L)=>r+L*Math.cos(m(M)),f=(M,L)=>a-L*Math.sin(m(M)),g=s==="straight"?180:0,b=[g];let x=g;for(const M of t)x+=M.degrees,b.push(x);const y=(M,L,P)=>{const F=h(M,P),D=f(M,P),U=h(L,P),q=f(L,P),O=Math.abs(L-M)>180?1:0;return`M ${F} ${D} A ${P} ${P} 0 ${O} 0 ${U} ${q}`},E=(M,L,P)=>{const F=h(M,P),D=f(M,P),U=h(L,P),q=f(L,P),O=Math.abs(L-M)>180?1:0;return`M ${r} ${a} L ${F} ${D} A ${P} ${P} 0 ${O} 0 ${U} ${q} Z`},k=M=>{const L={x:h(M,d),y:f(M,d)},P={x:h(M+90,d),y:f(M+90,d)},F={x:L.x+(P.x-r),y:L.y+(P.y-a)};return`M ${L.x} ${L.y} L ${F.x} ${F.y} L ${P.x} ${P.y}`},T=["#dbeafe","#fde68a","#ddd6fe","#bbf7d0"],A=["#2563c9","#d97706","#7c3aed","#16a34a"],N=t.map(M=>M.unknown?"an unknown angle":M.label?`angle ${M.label}`:"an angle").join(" and "),S=s==="straight"?`Angles on a straight line: ${N} meeting at a point on a line.`:s==="point"?`Angles around a point: ${N} meeting at a point.`:`An angle diagram showing ${N}. Compare it to a right angle.`;return jsxRuntimeExports.jsx("div",{className:"ang-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${o} ${o}`,className:"ang-svg",role:"img","aria-label":S,children:[s==="straight"&&jsxRuntimeExports.jsx("line",{x1:h(0,i),y1:f(0,i),x2:h(180,i),y2:f(180,i),stroke:"#1f2733",strokeWidth:2.5,strokeLinecap:"round"}),t.map((M,L)=>jsxRuntimeExports.jsx("path",{d:E(b[L],b[L+1],l),fill:T[L%T.length],fillOpacity:.75,stroke:"none"},`wedge${L}`)),t.map((M,L)=>jsxRuntimeExports.jsx("path",{d:y(b[L],b[L+1],l),fill:"none",stroke:A[L%A.length],strokeWidth:2},`arc${L}`)),e&&t.map((M,L)=>M.degrees===90?jsxRuntimeExports.jsx("path",{d:k(b[L]),fill:"none",stroke:"#1f2733",strokeWidth:2},`sq${L}`):null),b.map((M,L)=>s==="point"&&L===b.length-1&&M%360===0||s==="straight"&&(L===0||L===b.length-1)&&M%180===0&&(M===0||M===180)?null:jsxRuntimeExports.jsx("line",{x1:r,y1:a,x2:h(M,i),y2:f(M,i),stroke:"#1f2733",strokeWidth:2.5,strokeLinecap:"round"},`ray${L}`)),jsxRuntimeExports.jsx("circle",{cx:r,cy:a,r:3.5,fill:"#1f2733"}),t.map((M,L)=>{const P=(b[L]+b[L+1])/2,F=M.unknown?"?":M.label;if(!F)return null;const D=b[L+1]-b[L],U=Math.max(D,2)/2*(Math.PI/180),q=Math.min(100,Math.max(u,12/Math.sin(U)));return jsxRuntimeExports.jsx("text",{x:h(P,q),y:f(P,q)+5,textAnchor:"middle",fontSize:18,fontWeight:800,fill:M.unknown?"#b45309":"#1f2733",children:F},`lbl${L}`)})]})})}function PerimeterShape({data:n}){const{shape:t,sides:e,unit:s="cm"}=n,o=252,r=200,a="#1f2733",i="#eaf2ff";let l=[],u=[];if(t==="square"||t==="rectangle"){const b=t==="square"?108:130,x=t==="square"?108:96;l=[[56,46],[56+b,46],[56+b,46+x],[56,46+x]];const y=0,E=e[1]!==void 0?1:0,k=e[2]!==void 0?2:y,T=e[3]!==void 0?3:E;u=[{mx:56+b/2,my:46,nx:0,ny:-1,side:y},{mx:56+b,my:46+x/2,nx:1,ny:0,side:E},{mx:56+b/2,my:46+x,nx:0,ny:1,side:k},{mx:56,my:46+x/2,nx:-1,ny:0,side:T}]}else if(t==="triangle"){const f=e[0]??1,g=e[1]??e[0]??1,b=e[2]??e[0]??1,x=(g*g+b*b-f*f)/(2*g),y=b*b-x*x;let E;y>.01?E=[[x,-Math.sqrt(y)],[g,0],[0,0]]:E=[[g/2,-g*.8],[g,0],[0,0]];const k=E.map(K=>K[0]),T=E.map(K=>K[1]),A=Math.min(...k),N=Math.max(...k),S=Math.min(...T),M=Math.max(...T),L=48,P=40,F=o-2*L,D=118,U=Math.min(F/Math.max(N-A,1e-6),D/Math.max(M-S,1e-6)),q=L+(F-(N-A)*U)/2-A*U,V=P+(D-(M-S)*U)/2-S*U;l=E.map(([K,Z])=>[K*U+q,Z*U+V]);const O=(l[0][0]+l[1][0]+l[2][0])/3,z=(l[0][1]+l[1][1]+l[2][1])/3;u=l.map((K,Z)=>{const Q=l[(Z+1)%3],B=(K[0]+Q[0])/2,G=(K[1]+Q[1])/2;let X=Q[1]-K[1],ne=-(Q[0]-K[0]);const se=Math.hypot(X,ne)||1;return X/=se,ne/=se,(B-O)*X+(G-z)*ne<0&&(X=-X,ne=-ne),{mx:B,my:G,nx:X,ny:ne,side:Z}})}else{l=[[50,40],[170,40],[170,96],[106,96],[106,160],[50,160]];const y=l.map((k,T)=>{const A=l[(T+1)%l.length];return[(k[0]+A[0])/2,(k[1]+A[1])/2]}),E=[[0,-1],[1,0],[0,-1],[1,0],[0,1],[-1,0]];u=y.map((k,T)=>({mx:k[0],my:k[1],nx:E[T][0],ny:E[T][1],side:T}))}const d=l.map(f=>f.join(",")).join(" "),m=16,h=f=>`${f}${s?` ${s}`:""}`;return jsxRuntimeExports.jsx("div",{className:"per-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${o} ${r}
62`,className:"per-svg",role:"img","aria-label":`A ${t.replace("_","-")} with sides ${e.map(f=>h(f)).join(", ")}. Work out the perimeter.`,children:[jsxRuntimeExports.jsx("polygon",{points:d,fill:i,stroke:a,strokeWidth:2.5,strokeLinejoin:"round"}),u.map((f,g)=>{const b=e[f.side];if(b===void 0)return null;const x=Math.abs(f.nx)>.5,y=x?f.nx>0?"start":"end":"middle",E=x?7:m,k=f.mx+f.nx*E,T=f.my+(x?0:f.ny*E);return jsxRuntimeExports.jsx("text",{x:k,y:T+4,textAnchor:y,fontSize:15,fontWeight:700,fill:"#1f2733",children:h(b)},g)})]})})}function AreaGrid({data:n}){const t=Math.max(1,Math.min(Math.round(n.width),25)),e=Math.max(1,Math.min(Math.round(n.height),12)),s=n.unit_label??"cm",o=22,r=28,a=40,i=8,l=t*o,u=e*o,d=a,m=r,h=a+l+i,f=r+u+i,g=T=>`${T}${s?` ${s}`:""}`,b=Math.max(180,Math.min(h,360)),x="#dbeafe",y="#1e3a8a",E="#60a5fa",k="#1f2733";return jsxRuntimeExports.jsx("div",{className:"area-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${h} ${f}`,className:"area-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${b}px`,height:"auto"},role:"img","aria-label":`A rectangle ${g(t)} long and ${g(e)} wide, drawn as a grid of one-${s} squares. Work out the area.`,children:[jsxRuntimeExports.jsx("rect",{x:d,y:m,width:l,height:u,fill:x}),Array.from({length:t-1},(T,A)=>{const N=d+(A+1)*o;return jsxRuntimeExports.jsx("line",{x1:N,y1:m,x2:N,y2:m+u,stroke:E,strokeWidth:1},`v${A}`)}),Array.from({length:e-1},(T,A)=>{const N=m+(A+1)*o;return jsxRuntimeExports.jsx("line",{x1:d,y1:N,x2:d+l,y2:N,stroke:E,strokeWidth:1},`h${A}`)}),jsxRuntimeExports.jsx("rect",{x:d,y:m,width:l,height:u,fill:"none",stroke:y,strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:d+l/2,y:m-10,textAnchor:"middle",fontSize:15,fontWeight:700,fill:k,children:g(t)}),jsxRuntimeExports.jsx("text",{x:d-14,y:m+u/2,textAnchor:"middle",fontSize:15,fontWeight:700,fill:k,transform:`rotate(-90 ${d-14} ${m+u/2})`,children:g(e)})]})})}function ShortDivision({data:n}){const t=Math.max(1,Math.min(Math.abs(Math.round(n.divisor)),9)),e=Math.max(0,Math.min(Math.abs(Math.round(n.dividend)),9999)),s=String(t),r=String(e).split(""),a=38,i=8,l=14,u=14,d=14,m=30,h=8,f=46,g=26,b=8,x=r.length,y=x*a+(x-1)*i,E=l+g+b,k=E,T=k+y,A=T+l,N=u,S=N+m+h,M=S+f*.72,L=S+f+d,P=U=>k+U*(a+i)+a/2,F=U=>k+U*(a+i),D="ui-monospace, 'SFMono-Regular', Menlo, monospace";return jsxRuntimeExports.jsx("div",{className:"sdiv-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${A} ${L}`,className:"sdiv-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${Math.min(A,340)}px`,height:"auto"},role:"img","aria-label":`Short division: ${e} divided by ${t} using the bus-stop method. Work out the answer.`,children:[jsxRuntimeExports.jsx("text",{x:l+g/2,y:M,textAnchor:"middle",fontSize:30,fontWeight:700,fontFamily:D,fill:"#1f2733",children:s}),jsxRuntimeExports.jsx("path",{d:`M ${E} ${S+f*.92}
63              C ${E-10} ${S+f*.45},
64                ${E-6} ${S+6},
65                ${E} ${S}`,fill:"none",stroke:"#1f2733",strokeWidth:2.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("line",{x1:E,y1:S,x2:T,y2:S,stroke:"#1f2733",strokeWidth:2.5,strokeLinecap:"round"}),r.map((U,q)=>jsxRuntimeExports.jsx("line",{x1:F(q)+5,y1:N+m-2,x2:F(q)+a-5,y2:N+m-2,stroke:"#94a3b8",strokeWidth:2,strokeLinecap:"round"},`q-${q}`)),r.map((U,q)=>q===0?null:jsxRuntimeExports.jsx("line",{x1:F(q)-i-1,y1:S+8,x2:F(q)-1,y2:S+8,stroke:"#cbd5e1",strokeWidth:1.5,strokeLinecap:"round"},`c-${q}`)),r.map((U,q)=>jsxRuntimeExports.jsx("text",{x:P(q),y:M,textAnchor:"middle",fontSize:30,fontWeight:700,fontFamily:D,fill:"#1f2733",children:U},`d-${q}`))]})})}const SECTOR_FILLS$1=["#2f6fd6","#e8843c","#3aa66b","#a45bd1","#d94f6e","#e6b93a"];function sectorWeight$1(n){if(n.fraction!=null){if(typeof n.fraction=="number")return Math.max(0,n.fraction);const{num:t,den:e}=n.fraction;return e>0?Math.max(0,t/e):0}return n.value!=null?Math.max(0,n.value):1}function polar$1(n,t,e,s){return[n+e*Math.sin(s),t-e*Math.cos(s)]}function PieChart({data:n}){const t=n.sectors.slice(0,6),e=t.map(sectorWeight$1),s=e.reduce((f,g)=>f+g,0)||1,o=220,a=o+56*2,i=a/2,l=o/2,u=82,d=u+14;let m=0;const h=t.map((f,g)=>{const b=e[g]/s,x=m*2*Math.PI,y=(m+b)*2*Math.PI,E=(x+y)/2;m+=b;const[k,T]=polar$1(i,l,u,x),[A,N]=polar$1(i,l,u,y),S=b>.5?1:0,M=t.length===1?`M ${i} ${l-u} A ${u} ${u} 0 1 1 ${i-.01} ${l-u} Z`:`M ${i} ${l} L ${k.toFixed(2)} ${T.toFixed(2)} A ${u} ${u} 0 ${S} 1 ${A.toFixed(2)} ${N.toFixed(2)} Z`,[L,P]=polar$1(i,l,u,E),[F,D]=polar$1(i,l,d,E),U=F>i+2?"start":F<i-2?"end":"middle";return{s:f,path:M,fill:colourForLabel(f.label)??SECTOR_FILLS$1[g%SECTOR_FILLS$1.length],lx:L,ly:P,tx:F,ty:D,anchor:U}});return jsxRuntimeExports.jsx("div",{className:"pie-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${a} ${o}`,className:"pie-svg",role:"img","aria-label":`A pie chart split into ${t.length} sectors: ${t.map(f=>f.label).join(", ")}. Judge each sector's share of the whole circle.`,children:[h.map((f,g)=>jsxRuntimeExports.jsx("path",{d:f.path,fill:f.fill,stroke:"#ffffff",strokeWidth:2},`w${g}`)),jsxRuntimeExports.jsx("circle",{cx:i,cy:l,r:u,fill:"none",stroke:"#33373d",strokeWidth:1.5}),h.map((f,g)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:f.lx,y1:f.ly,x2:f.tx,y2:f.ty,stroke:"#8a929c",strokeWidth:1}),jsxRuntimeExports.jsx("text",{x:f.anchor==="start"?f.tx+2:f.anchor==="end"?f.tx-2:f.tx,y:f.ty+4,textAnchor:f.anchor,fontSize:12,fontWeight:700,fill:"#33373d",children:f.s.label})]},`l${g}`))]})})}function ReflectionGrid({data:n}){const{grid:t,mirror:e,shape_points:s,reflected_points:o,labelled_points:r,axes:a=!1}=n;if(!t||t.w<=0||t.h<=0)return console.warn("ReflectionGrid: grid must have positive w and h",t),null;if(e.position<0||e.position>(e.axis==="vertical"?t.w:t.h))return console.warn("ReflectionGrid: mirror position out of range",e),null;if(!s||s.length<2)return console.warn("ReflectionGrid: shape_points needs at least 2 vertices",s),null;const i=28,l=a?26:12,u=12,d=12,m=a?24:12,h=t.w*i,f=t.h*i,g=l+h+u,b=d+f+m,x=l,y=d+f,E=S=>
65x+S*i,k=S=>y-S*i,T=Array.from({length:t.w+1},(S,M)=>M),A=Array.from({length:t.h+1},(S,M)=>M),N=S=>S.map(([M,L])=>`${E(M)},${k(L)}`).join(" ");return jsxRuntimeExports.jsx("div",{className:"refl-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${g} ${b}`,className:"refl-svg",role:"img","aria-label":`A ${t.w} by ${t.h} squared grid with a ${e.axis} mirror line${o?" and two shapes":" and a shape"}.`,children:[T.map(S=>jsxRuntimeExports.jsx("line",{x1:E(S),y1:k(0),x2:E(S),y2:k(t.h),stroke:"#dde3ea",strokeWidth:1},`v${S}`)),A.map(S=>jsxRuntimeExports.jsx("line",{x1:E(0),y1:k(S),x2:E(t.w),y2:k(S),stroke:"#dde3ea",strokeWidth:1},`h${S}`)),a&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("line",{x1:E(0),y1:k(0),x2:E(t.w),y2:k(0),stroke:"#33373d",strokeWidth:2}),jsxRuntimeExports.jsx("line",{x1:E(0),y1:k(0),x2:E(0),y2:k(t.h),stroke:"#33373d",strokeWidth:2}),T.map(S=>jsxRuntimeExports.jsx("text",{x:E(S),y:k(0)+15,textAnchor:"middle",fontSize:10,fontWeight:600,fill:"#5b6b85",children:S},`xn${S}`)),A.map(S=>jsxRuntimeExports.jsx("text",{x:E(0)-7,y:k(S)+4,textAnchor:"end",fontSize:10,fontWeight:600,fill:"#5b6b85",children:S},`yn${S}`))]}),e.axis==="vertical"?jsxRuntimeExports.jsx("line",{x1:E(e.position),y1:k(0),x2:E(e.position),y2:k(t.h),stroke:"#7c3aed",strokeWidth:3,strokeDasharray:"7 5",strokeLinecap:"round"}):jsxRuntimeExports.jsx("line",{x1:E(0),y1:k(e.position),x2:E(t.w),y2:k(e.position),stroke:"#7c3aed",strokeWidth:3,strokeDasharray:"7 5",strokeLinecap:"round"}),jsxRuntimeExports.jsx("polygon",{points:N(s),fill:"#2563c9",fillOpacity:.28,stroke:"#2563c9",strokeWidth:2.5,strokeLinejoin:"round"}),o&&o.length>=2&&jsxRuntimeExports.jsx("polygon",{points:N(o),fill:"#16a34a",fillOpacity:.22,stroke:"#16a34a",strokeWidth:2.5,strokeLinejoin:"round"}),r?.map((S,M)=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:E(S.x),cy:k(S.y),r:5,fill:"#b45309",stroke:"#1f2733",strokeWidth:1.2}),jsxRuntimeExports.jsx("text",{x:E(S.x)+8,y:k(S.y)-7,fontSize:13,fontWeight:800,fill:"#1f2733",children:S.label})]},`lp${M}`))]})})}function TriangleFigure({data:n}){const{angles:t,labels:e,equal_side_marks:s,mark_right_angle:o=!1,rotation:r=0}=n;if(!Array.isArray(t)||t.length!==3)return console.warn("TriangleFigure: angles must be a 3-tuple",t),null;if(t.some(se=>!(se>0&&se<180)))return console.warn("TriangleFigure: each angle must be between 0 and 180",t),null;if(Math.abs(t[0]+t[1]+t[2]-180)>1)return console.warn("TriangleFigure: interior angles must sum to ~180",t),null;if(!Array.isArray(e)||e.length!==3)return console.warn("TriangleFigure: labels must be a 3-tuple",e),null;const a=se=>se*Math.PI/180,i=Math.sin(a(t[2])),l=Math.sin(a(t[1])),u=[[0,0],[i,0],[l*Math.cos(a(t[0])),l*Math.sin(a(t[0]))]],d=(u[0][0]+u[1][0]+u[2][0])/3,m=(u[0][1]+u[1][1]+u[2][1])/3,h=a(r),f=Math.cos(h),g=Math.sin(h),b=u.map(([se,te])=>{const oe=se-d,le=te-m;return[d+oe*f-le*g,m+oe*g+le*f]}),x=220,E=x-2*36,k=Math.min(...b.map(se=>se[0])),T=Math.max(...b.map(se=>se[0])),A=Math.min(...b.map(se=>se[1])),N=Math.max(...b.map(se=>se[1])),S=Math.max(T-k,1e-6),M=Math.max(N-A,1e-6),L=Math.min(E/S,E/M),P=(x-S*L)/2,F=(x-M*L)/2,D=se=>P+(se-k)*L,U=se=>x-F-(se-A)*L,q=b.map(([se,te])=>[D(se),U(te)]),V=q.map(([se,te])=>`${se},${te}`).join(" "),O=(se,te)=>Math.atan2(te[1]-se[1],te[0]-se[0]),z=se=>{let te=se;for(;te<=-Math.PI;)te+=2*Math.PI;for(;te>Math.PI;)te-=2*Math.PI;return te},K=22,Z=34,Q=q.map((se,te)=>{const oe=q[(te+2)%3],le=q[(te+1)%3],ae=O(se,le),ie=O(se,oe),we=z(ie-ae),ke=ae+we/2,Te=[se[0]+K*Math.cos(ae),se[1]+K*Math.sin(ae)],Se=[se[0]+K*Math.cos(ie),se[1]+K*Math.sin(ie)],$e=we>0?1:0,Re=`M ${Te[0]} ${Te[1]} A ${K} ${K} 0 0 ${$e} ${Se[0]} ${Se[1]}`,ye=t[te]<45?-22:Z,Ne=[Math.max(20,Math.min(x-20,se[0]+ye*Math.cos(ke))),Math.max(16,Math.min(x-20,se[1]+ye*Math.sin(ke)))],Ye=[Math.cos(ae),Math.sin(ae)],Ge=[Math.cos(ie),Math.sin(ie)],Fe=15,Xe=`M ${se[0]+Fe*Ye[0]} ${se[1]+Fe*Ye[1]} L ${se[0]+Fe*(Ye[0]+Ge[0])} ${se[1]+Fe*(Ye[1]+Ge[1])} L ${se[0]+Fe*Ge[0]} ${se[1]+Fe*Ge[1]}`;return{arcPath:Re,labelPos:Ne,sq:Xe,isRight:Math.abs(t[te]-90)<.5}}),B=12,G=5,X=[];
65(s??[]).forEach((se,te)=>{const oe=te+1;se.forEach(le=>{const ae=q[le%3],ie=q[(le+1)%3],we=[(ae[0]+ie[0])/2,(ae[1]+ie[1])/2],ke=Math.hypot(ie[0]-ae[0],ie[1]-ae[1])||1,Te=[(ie[0]-ae[0])/ke,(ie[1]-ae[1])/ke],Se=[-Te[1],Te[0]];for(let $e=0;$e<oe;$e++){const Re=($e-(oe-1)/2)*G,ye=[we[0]+Re*Te[0],we[1]+Re*Te[1]];X.push({x1:ye[0]-B/2*Se[0],y1:ye[1]-B/2*Se[1],x2:ye[0]+B/2*Se[0],y2:ye[1]+B/2*Se[1],key:`t${le}-${$e}`})}})});const ne=["#2563c9","#d97706","#7c3aed"];return jsxRuntimeExports.jsx("div",{className:"tri-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${x} ${x}`,className:"tri-svg",role:"img","aria-label":"A triangle with labelled angles.",children:[jsxRuntimeExports.jsx("polygon",{points:V,fill:"#dbeafe",fillOpacity:.55,stroke:"#1f2733",strokeWidth:2.5,strokeLinejoin:"round"}),Q.map((se,te)=>o&&se.isRight?null:jsxRuntimeExports.jsx("path",{d:se.arcPath,fill:"none",stroke:ne[te%ne.length],strokeWidth:2},`arc${te}`)),o&&Q.map((se,te)=>se.isRight?jsxRuntimeExports.jsx("path",{d:se.sq,fill:"none",stroke:"#1f2733",strokeWidth:2.5},`sq${te}`):null),X.map(se=>jsxRuntimeExports.jsx("line",{x1:se.x1,y1:se.y1,x2:se.x2,y2:se.y2,stroke:"#1f2733",strokeWidth:2,strokeLinecap:"round"},se.key)),Q.map((se,te)=>{const oe=e[te];
65if(oe==null)return null;const le=oe==="?";return jsxRuntimeExports.jsx("text",{stroke:"white",strokeWidth:4,paintOrder:"stroke",x:se.labelPos[0],y:se.labelPos[1]+5,textAnchor:"middle",fontSize:16,fontWeight:800,fill:le?"#b45309":"#1f2733",children:oe},`lbl${te}`)})]})})}function TriangleArea({data:n}){const t=Math.max(1,Math.min(Math.round(n.base),20)),e=Math.max(1,Math.min(Math.round(n.height),12)),s=n.unit_label??"cm",o=n.orientation??"right",r=n.show_grid===!0,a=22,i=22,l=30,u=44,d=48,m=t*a,h=e*a,f=u,g=i,b=u+m+d,x=i+h+l,y=g+h,E=Math.max(200,Math.min(b,340)),k=O=>`${O}${s?` ${s}`:""}`,T="#dbeafe",A="#1e3a8a",N="#bfdbfe",S="#b45309",M="#1f2733",L="#64748b",P={x:f,y},F={x:f+m,y},U={x:f+(o==="right"?0:.36)*m,y:g},q={x:U.x,y},V=(O,z)=>jsxRuntimeExports.jsx("path",{d:`M ${O} ${z-9} L ${O+9} ${z-9} L ${O+9} ${z}`,fill:"none",stroke:S,strokeWidth:1.5});return jsxRuntimeExports.jsx("div",{className:"tri-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${b} ${x}`,className:"tri-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${E}px`,height:"auto"},role:"img","aria-label":`A triangle with a base of ${k(t)} and a perpendicular height of ${k(e)}. Work out its area.`,children:[jsxRuntimeExports.jsx("polygon",{points:`${P.x},${P.y} ${F.x},${F.y} ${U.x},${U.y}`,fill:T,stroke:A,strokeWidth:2,strokeLinejoin:"round"}),r&&jsxRuntimeExports.jsxs("g",{children:[Array.from({length:t+1},(O,z)=>jsxRuntimeExports.jsx("line",{x1:f+z*a,y1:g,x2:f+z*a,y2:y,stroke:N,strokeWidth:1},`gv${z}`)),Array.from({length:e+1},(O,z)=>jsxRuntimeExports.jsx("line",{x1:f,y1:g+z*a,x2:f+m,y2:g+z*a,stroke:N,strokeWidth:1},`gh${z}`))]}),jsxRuntimeExports.jsx("line",{x1:U.x,y1:U.y,x2:q.x,y2:q.y,stroke:S,strokeWidth:2,strokeDasharray:"5 4"}),V(q.x,q.y),jsxRuntimeExports.jsx("text",{x:o==="right"?f-14:q.x+12,y:(U.y+q.y)/2,textAnchor:"middle",fontSize:14,fontWeight:700,fill:S,transform:o==="right"?`rotate(-90 ${f-14} ${(U.y+q.y)/2})`:void 0,children:k(e)}),jsxRuntimeExports.jsx("text",{x:(P.x+F.x)/2,y:y+20,textAnchor:"middle",fontSize:14,fontWeight:700,fill:M,children:k(t)}),n.other_side_label&&jsxRuntimeExports.jsx("text",{x:(F.x+U.x)/2+12,y:(F.y+U.y)/2-4,textAnchor:"middle",fontSize:13,fontWeight:600,fill:L,children:n.other_side_label})]})})}function LongMultiplication({data:n}){const t=Math.max(10,Math.min(Math.round(Math.abs(n.top)),99)),e=Math.max(10,Math.min(Math.round(Math.abs(n.bottom)),99)),s=String(t),o=String(e),r=e%10,a=Math.floor(e/10),i=String(t*r).length,l=String(t*a).length+1,u=String(t*e).length,d=Math.max(s.length,o.length,i,l,u),m=30,h=26,f=12,g=10,b=40,x=f+h,y=d*m,E=x+y+f,k=g+28,T=k+b,A=T+12,N=A+b,S=N+b,M=S+12,L=M+b,P=L+14,F="ui-monospace, 'SFMono-Regular', Menlo, monospace",D=O=>x+O*m+m/2,U=O=>x+O*m,q=(O,z)=>O.split("").map((K,Z)=>{const Q=d-O.length+Z;return jsxRuntimeExports.jsx("text",{x:D(Q),y:z,textAnchor:"middle",fontSize:26,fontWeight:700,fontFamily:F,fill:"#1f2733",children:K},`${z}-${Z}`)}),V=(O,z)=>Array.from({length:d},(K,Z)=>jsxRuntimeExports.jsx("line",{x1:U(Z)+5,y1:z+4,x2:U(Z)+m-5,y2:z+4,stroke:"#cbd5e1",strokeWidth:2,strokeLinecap:"round"},`${O}-${Z}`));return jsxRuntimeExports.jsx("div",{className:"lmul-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${E} ${P}`,className:"lmul-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${Math.min(E,220)}px`,height:"auto"},role:"img","aria-label":`Long multiplication: ${t} times ${e}. Work out the answer using the written method.`,children:[q(s,k),jsxRuntimeExports.jsx("text",{x:f+h/2,y:T,textAnchor:"middle",fontSize:26,fontWeight:700,fontFamily:F,fill:"#1f2733",children:"×"}),q(o,T),jsxRuntimeExports.jsx("line",{x1:f,y1:A,x2:x+y,y2:A,stroke:"#1f2733",strokeWidth:2.5}),V("p1",N),V("p2",S),jsxRuntimeExports.jsx("line",{x1:f,y1:M,x2:x+y,y2:M,stroke:"#1f2733",strokeWidth:2.5}),V("tot",L)]})})}function LongDivision({data:n}){const t=Math.max(10,Math.min(Math.abs(Math.round(n.divisor)),99)),e=Math.max(100,Math.min(Math.abs(Math.round(n.dividend)),99999)),s=n.mode==="complete"?"complete":"setup",o=String(t),a=String(e).split("").map(Number),i=a.length,l=[];let u=0,d=!1;for(let G=0;G<i;G++){u=u*10+a[G];const X=Math.floor(u/t),ne=u%t;X>0&&(d=!0),d&&l.push({col:G,quotientDigit:X,productStr:String(X*t),remainderStr:String(ne),broughtDown:G<i-1?a[G+1]:null,isLast:G===i-1}),u=ne}const m=l.length,h=s==="complete"?typeof n.visible_steps=="number"?Math.max(0,Math.min(Math.round(n.visible_steps),m)):m:0,f=l.slice(0,h),g=30,b=40,x=10,y=14,E=14,k=16,T=y+b+x,A=i*g,N=T+A+y,S=E+22,M=S+10,L=M+30,P="ui-monospace, 'SFMono-Regular', Menlo, monospace",F="#1f2733",D="#94a3b8",U="#1f2733",q=G=>T+G*g+g/2,V=G=>T+G*g,O=30,z=[];let K=L;const Z=(G,X,ne,se,te)=>{const oe=X-G.length+1;return G.split("").map((le,ae)=>jsxRuntimeExports.jsx("text",{x:q(oe+ae),y:ne,textAnchor:"middle",fontSize:24,fontWeight:700,fontFamily:P,fill:F,className:se,children:le},`${te}-${ae}`))};f.forEach((G,X)=>{const ne=G.col-G.productStr.length+1;K+=O;const se=K;z.push(jsxRuntimeExports.jsx("text",{x:V(ne)-4,y:se,textAnchor:"end",fontSize:20,fontWeight:700,fontFamily:P,fill:F,children:"−"},`minus-${X}`)),z.push(...Z(G.productStr,G.col,se,"ldiv-work",`sub-${X}`));const te=se+7;z.push(jsxRuntimeExports.jsx("line",{x1:V(ne),y1:te,x2:V(G.col+1),y2:te,stroke:U,strokeWidth:2,strokeLinecap:"round"},`rule-${X}`)),K=te+O;const oe=K,le=G.isLast?"ldiv-r":"ldiv-work";z.push(...Z(G.remainderStr,G.col,oe,le,`rem-${X}
65`)),G.broughtDown!==null&&z.push(jsxRuntimeExports.jsx("text",{x:q(G.col+1),y:oe,textAnchor:"middle",fontSize:24,fontWeight:700,fontFamily:P,fill:D,className:"ldiv-bd",children:G.broughtDown},`bd-${X}`))});const Q=(z.length>0?K:L)+k,B=new Set(f.map(G=>G.col));return jsxRuntimeExports.jsx("div",{className:"ldiv-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${N} ${Q}`,className:"ldiv-svg",width:"100%",height:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${Math.min(N,360)}px`,height:"auto"},role:"img","aria-label":s==="complete"?`Long division worked solution: ${e} divided by ${t} using the bus-stop method.`:`Long division: ${e} divided by ${t} using the bus-stop method. Work out the answer.`,children:[jsxRuntimeExports.jsx("text",{x:y+b/2,y:L,textAnchor:"middle",fontSize:24,fontWeight:700,fontFamily:P,fill:F,children:o}),jsxRuntimeExports.jsx("path",{d:`M ${T} ${L+6}
66              C ${T-10} ${M+18},
67                ${T-6} ${M+6},
68                ${T} ${M}`,fill:"none",stroke:U,strokeWidth:2.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("line",{x1:T,y1:M,x2:T+A,y2:M,stroke:U,strokeWidth:2.5,strokeLinecap:"round"}),f.map(G=>jsxRuntimeExports.jsx("text",{x:q(G.col),y:S,textAnchor:"middle",fontSize:24,fontWeight:700,fontFamily:P,fill:F,className:"ldiv-q",children:G.quotientDigit},`q-${G.col}`)),a.map((G,X)=>B.has(X)?null:jsxRuntimeExports.jsx("line",{x1:V(X)+5,y1:S+4,x2:V(X)+g-5,y2:S+4,stroke:D,strokeWidth:2,strokeLinecap:"round"},`qslot-${X}`)),a.map((G,X)=>jsxRuntimeExports.jsx("text",{x:q(X),y:L,textAnchor:"middle",fontSize:24,fontWeight:700,fontFamily:P,fill:F,className:"ldiv-dividend",children:G},`d-${X}`)),z]})})}function alignedSide$1(n,t,e){const s=n[t],o=n[(t+1)%n.length],r=n[e],a=n[(e+1)%n.length];return(o[0]-s[0])*(a[0]-r[0])+(o[1]-s[1])*(a[1]-r[1])<0?[o,s]:[s,o]}const SHAPES$2={parallelogram:{verts:[[30,20],[95,20],[75,80],[10,80]],parallel:[[0,2],[1,3]]},rhombus:{verts:[[50,10],[85,50],[50,90],[15,50]],parallel:[[0,2],[1,3]]},trapezium:{verts:[[27,22],[73,22],[90,80],[10,80]],parallel:[[0,2]]},rectangle:{verts:[[12,28],[88,28],[88,72],[12,72]],parallel:[[0,2],[1,3]]},square:{verts:[[20,18],[80,18],[80,78],[20,78]],parallel:[[0,2],[1,3]]},kite:{verts:[[50,8],[82,42],[50,92],[18,42]],parallel:[]}},ALLOWED$2=new Set(Object.keys(SHAPES$2)),CELL$3=120;function Quadrilateral({data:n}){const{shapes:t,show_parallel_marks:e=!1}=n;if(!Array.isArray(t)||t.length<1||t.length>4)return console.warn("Quadrilateral: shapes must be an array of 1–4 items",t),null;if(t.some(l=>!l||!ALLOWED$2.has(l.shape)))return console.warn("Quadrilateral: every shape must be one of",[...ALLOWED$2],t),null;const s=t.length,o=s*CELL$3,r=144,a=(l,u,d,m)=>{const h=[(l[0]+u[0])/2,(l[1]+u[1])/2],f=Math.hypot(u[0]-l[0],u[1]-l[1])||1,g=[(u[0]-l[0])/f,(u[1]-l[1])/f],b=[-g[1],g[0]],x=6.5,y=6,E=[];for(let k=0;k<d;k++){const T=[h[0]+(k-(d-1)/2)*y*g[0],h[1]+(k-(d-1)/2)*y*g[1]],A=[T[0]+x*g[0],T[1]+x*g[1]],N=[T[0]+x*b[0],T[1]+x*b[1]],S=[T[0]-x*b[0],T[1]-x*b[1]];E.push(jsxRuntimeExports.jsx("line",{x1:A[0],y1:A[1],x2:N[0],y2:N[1],stroke:"#7c3aed",strokeWidth:2,strokeLinecap:"round"},`${m}-${k}-a`),jsxRuntimeExports.jsx("line",{x1:A[0],y1:A[1],x2:S[0],y2:S[1],stroke:"#7c3aed",strokeWidth:2,strokeLinecap:"round"},`${m}-${k}-b`))}return E},i=t.map(l=>l.label??"unlabelled").join(", ");return jsxRuntimeExports.jsx("div",{className:"quad-wrap",style:{display:"flex",justifyContent:"center",padding:"16px 12px",margin:"12px 0 4px",background:"#f8fafc",borderRadius:14,border:"2px solid #e2e8f0"},children:jsxRuntimeExports.jsx("svg",{viewBox:`0 0 ${o} ${r}`,style:{display:"block",width:"100%",maxWidth:Math.min(s*130,480),margin:"0 auto"},role:"img","aria-label":`${s===1?"A quadrilateral":`${s} quadrilaterals`} to identify: ${i}.`,children:t.map((l,u)=>{const{verts:d,parallel:m}=SHAPES$2[l.shape],h=u*CELL$3,f=d.map(([b,x])=>[h+10+b,8+x]),g=f.map(([b,x])=>`${b},${x}`).join(" ");return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("polygon",{points:g,fill:"#dbeafe",fillOpacity:.55,stroke:"#1f2733",strokeWidth:2.5,strokeLinejoin:"round"}),e&&m.flatMap((b,x)=>b.flatMap(y=>{const[E,k]=alignedSide$1(f,y,b[0]);return a(E,k,x+1,`c${u}-${x}-${y}`)})),l.label!=null&&jsxRuntimeExports.jsx("text",{x:h+CELL$3/2,y:132,textAnchor:"middle",fontSize:16,fontWeight:800,fill:"#1f2733",children:l.label})]},`q${u}`)})})})}
68const N_GON$1={equilateral_triangle:3,square:4,regular_pentagon:5,regular_hexagon:6,regular_octagon:8},ALLOWED$1=["equilateral_triangle","square","regular_pentagon","regular_hexagon","regular_octagon","rectangle"],VIEW$2=170,CX$4=VIEW$2/2,CY$4=VIEW$2/2,R$4=64,mid$1=(n,t)=>[(n[0]+t[0])/2,(n[1]+t[1])/2];function extend$1(n,t,e){const s=Math.hypot(t[0]-n[0],t[1]-n[1])||1,o=(t[0]-n[0])/s,r=(t[1]-n[1])/s;return{x1:n[0]-o*e,y1:n[1]-r*e,x2:t[0]+o*e,y2:t[1]+r*e}}function regularVerts$1(n){return Array.from({length:n},(t,e)=>{const s=(-90+e*360/n)*Math.PI/180;return[CX$4+R$4*Math.cos(s),CY$4+R$4*Math.sin(s)]})}const RECT_HW$1=66,RECT_HH$1=42,RECT$1=[[CX$4-RECT_HW$1,CY$4-RECT_HH$1],[CX$4+RECT_HW$1,CY$4-RECT_HH$1],[CX$4+RECT_HW$1,CY$4+RECT_HH$1],[CX$4-RECT_HW$1,CY$4+RECT_HH$1]];function describedLineSeg$1(n,t){if(t==="rect_diagonal")return extend$1(RECT$1[0],RECT$1[2],0);if(t==="rect_vertical_midline")return extend$1([CX$4,CY$4-RECT_HH$1],[CX$4,CY$4+RECT_HH$1],8);if(t==="rect_horizontal_midline")return extend$1([CX$4-RECT_HW$1,CY$4],[CX$4+RECT_HW$1,CY$4],8);const e=N_GON$1[n];if(e==null)return null;const s=regularVerts$1(e),o=s.map((r,a)=>mid$1(s[a],s[(a+1)%e]));return t==="vertex_to_opposite_vertex"?e%2!==0?null:extend$1(s[0],s[e/2],6):t==="vertex_to_opposite_mid"?extend$1(s[0],o[(0+Math.floor((e-1)/2))%e],8):t==="adjacent_vertices"?extend$1(s[0],s[1],0):null}function trueLines$1(n){if(n==="rectangle"){const r=extend$1([CX$4,CY$4-RECT_HH$1],[CX$4,CY$4+RECT_HH$1],8),a=extend$1([CX$4-RECT_HW$1,CY$4],[CX$4+RECT_HW$1,CY$4],8);return[r,a]}const t=N_GON$1[n],e=regularVerts$1(t),s=e.map((r,a)=>mid$1(e[a],e[(a+1)%t])),o=[];if(t%2===1)for(let r=0;r<t;r++)o.push(extend$1(e[r],s[(r+(t-1)/2)%t],8));else{const r=t/2;for(let a=0;a<r;a++)o.push(extend$1(e[a],e[a+r],6));for(let a=0;a<r;a++)o.push(extend$1(s[a],s[a+r],6))}return o}function SymmetryLines({data:n}){const{shape:t,show_lines:e,described_line:s}=n;if(!ALLOWED$1.includes(t))return console.warn("SymmetryLines: unknown shape",t),null;const r=(t==="rectangle"?RECT$1:regularVerts$1(N_GON$1[t])).map(([i,l])=>`${i},${l}`).join(" ");let a=[];if(e==="all")a=trueLines$1(t);else if(typeof e=="number"&&Number.isFinite(e)&&e>0){const i=Math.floor(e);t==="rectangle"&&i===1?a=[extend$1(RECT$1[0],RECT$1[2],0)]:a=trueLines$1(t).slice(0,i)}if(s){const i=describedLineSeg$1(t,s);i?a=[...a,i]:console.warn("SymmetryLines: described_line not valid for shape",s,t)}return jsxRuntimeExports.jsx("div",{className:"sym-wrap",style:{display:"flex",justifyContent:"center",padding:"16px 24px",margin:"12px 0 4px",background:"#f8fafc",borderRadius:14,border:"2px solid #e2e8f0"},children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${VIEW$2} ${VIEW$2}`,style:{display:"block",width:"100%",maxWidth:240,margin:"0 auto"},role:"img","aria-label":`A ${t.replace(/_/g," ")} with lines of symmetry shown.`,children:[jsxRuntimeExports.jsx("polygon",{points:r,fill:"#dbeafe",fillOpacity:.55,stroke:"#1f2733",strokeWidth:2.5,strokeLinejoin:"round"}),a.map((i,l)=>jsxRuntimeExports.jsx("line",{x1:i.x1,y1:i.y1,x2:i.x2,y2:i.y2,stroke:"#d97706",strokeWidth:2,strokeDasharray:"6,5",strokeLinecap:"round"},`s${l}`))]})})}const MAX_PX$1=150,MIN_PX$1=46,DEPTH_F$1=.6,MAX_CELLS=20,FRONT$1="#dbeafe",TOP$2="#93c5fd",SIDE$1="#60a5fa",EDGE$3="#1e3a8a",GRID$4="#1e3a8a",LABEL$3="#1f2733",MISSING$1="#b45309";function VolumeCuboid({data:n}){const t=Math.max(1,Math.round(n.length)),e=Math.max(1,Math.round(n.width)),s=Math.max(1,Math.round(n.height)),o=n.unit??"cm",r=n.show_unit_cubes===!0,a=n.hide_dimension,i=Math.max(t,e,s),l=MAX_PX$1/i,u=le=>Math.max(MIN_PX$1,Math.min(MAX_PX$1,le*l)),d=u(t),m=u(s),h=u(e),f=h*DEPTH_F$1,g=-h*DEPTH_F$1,b=40,x=62,y=22,E=34,k=b,T=y+m+Math.abs(g),A=b+d+f+x,N=T+E,S=Math.max(200,Math.min(A,360)),M={x:k,y:T},L={x:k+d,y:T},P={x:k,y:T-m},F={x:k+d,y:T-m},D=le=>({x:le.x+f,y:le.y+g}),U=D(M),q=D(L),V=D(P),O=D(F),z=(...le)=>le.map(ae=>`${ae.x},${ae.y}`).join(" "),K=(le,ae,ie,we,ke,Te)=>{const Se=[];for(let $e=1;$e<ie;$e++){const Re=$e/ie,ye={x:le.x+Re*ae.x,y:le.y+Re*ae.y},Ne={x:ye.x+we.x,y:ye.y+we.y};
68Se.push(jsxRuntimeExports.jsx("line",{x1:ye.x,y1:ye.y,x2:Ne.x,y2:Ne.y,stroke:GRID$4,strokeWidth:1,strokeOpacity:.35},`${Te}u${$e}`))}for(let $e=1;$e<ke;$e++){const Re=$e/ke,ye={x:le.x+Re*we.x,y:le.y+Re*we.y},Ne={x:ye.x+ae.x,y:ye.y+ae.y};Se.push(jsxRuntimeExports.jsx("line",{x1:ye.x,y1:ye.y,x2:Ne.x,y2:Ne.y,stroke:GRID$4,strokeWidth:1,strokeOpacity:.35},`${Te}v${$e}`))}return Se},Z={x:f,y:g},Q={x:d,y:0},B={x:0,y:m},G=r&&Math.max(t,e,s)<=MAX_CELLS,X=le=>`${le}${o?` ${o}`:""}`,ne=a==="length"?"?":X(t),se=a==="width"?"?":X(e),te=a==="height"?"?":X(s),oe=le=>a===le?MISSING$1:LABEL$3;return jsxRuntimeExports.jsx("div",{className:"vc-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${A} ${N}`,className:"vc-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${S}px`,height:"auto"},role:"img","aria-label":`A cuboid measuring ${ne} long, ${se} wide and ${te} high.`,children:[jsxRuntimeExports.jsx("polyline",{points:z(V,U,q),fill:"none",stroke:EDGE$3,strokeWidth:1.3,strokeDasharray:"4 4"}),jsxRuntimeExports.jsx("line",{x1:U.x,y1:U.y,x2:M.x,y2:M.y,stroke:EDGE$3,strokeWidth:1.3,strokeDasharray:"4 4"}),jsxRuntimeExports.jsx("polygon",{points:z(P,F,O,V),fill:TOP$2,stroke:EDGE$3,strokeWidth:2,strokeLinejoin:"round"}),jsxRuntimeExports.jsx("polygon",{points:z(L,q,O,F),fill:SIDE$1,stroke:EDGE$3,strokeWidth:2,strokeLinejoin:"round"}),jsxRuntimeExports.jsx("polygon",{points:z(M,L,F,P),fill:FRONT$1,stroke:EDGE$3,strokeWidth:2,strokeLinejoin:"round"}),G&&jsxRuntimeExports.jsxs("g",{children:[K(P,Q,t,B,s,"front"),K(P,Q,t,Z,e,"top"),K(F,Z,e,B,s,"right")]}),jsxRuntimeExports.jsx("text",{x:(M.x+L.x)/2,y:M.y+22,textAnchor:"middle",fontSize:14,fontWeight:700,fill:oe("length"),children:ne}),jsxRuntimeExports.jsx("text",{x:M.x-12,y:(M.y+P.y)/2,textAnchor:"middle",fontSize:14,fontWeight:700,fill:oe("height"),transform:`rotate(-90 ${M.x-12} ${(M.y+P.y)/2})`,children:te}),jsxRuntimeExports.jsx("text",{x:(F.x+O.x)/2+12,y:(F.y+O.y)/2-4,textAnchor:"middle",fontSize:14,fontWeight:700,fill:oe("width"),children:se})]})})}const NAMED_COLORS=[[/\bred\b/i,{fill:"#dc2626",ink:"#b91c1c"}],[/\bblue\b/i,{fill:"#2563eb",ink:"#1d4ed8"}],[/\bgreen\b/i,{fill:"#16a34a",ink:"#15803d"}],[/\borange\b/i,{fill:"#ea580c",ink:"#c2410c"}],[/\byellow\b/i,{fill:"#eab308",ink:"#a16207"}],[/\bpurple\b/i,{fill:"#7c3aed",ink:"#6d28d9"}],[/\bpink\b/i,{fill:"#db2777",ink:"#be185d"}],[/\bgr[ae]y\b/i,{fill:"#6b7280",ink:"#4b5563"}],[/\bblack\b/i,{fill:"#111827",ink:"#111827"}],[/\bwhite\b/i,{fill:"#f8fafc",ink:"#475569"}],[/\bgold\b/i,{fill:"#d4a017",ink:"#92400e"}],[/\bsilver\b/i,{fill:"#9ca3af",ink:"#6b7280"}],[/\bbrown\b/i,{fill:"#92400e",ink:"#7c2d12"}]];function namedRatioColor(n){if(/\bnot\b/i.test(n))return null;const t=NAMED_COLORS.find(([e])=>e.test(n));return t?t[1]:null}function ratioValueInk(n){return["#f8fafc","#eab308","#9ca3af","#d4a017"].includes(n)?"#1f2733":"#ffffff"}const LEFT_COLOR$1="#2f6fd6",RIGHT_COLOR$1="#e8843c",SEG_STROKE$1="#ffffff",AXIS$5="#1f2733";function RatioBar({data:n}){const t=Math.max(0,Math.round(n.left_parts)),e=Math.max(0,Math.round(n.right_parts)),s=Math.max(1,t+e),o=n.show_part_values===!0,r=n.total!=null,a=namedRatioColor(n.left_label),i=namedRatioColor(n.right_label),l=a?.fill??LEFT_COLOR$1,u=i?.fill??RIGHT_COLOR$1,d=14,m=300,h=m/s,f=46,g=46,b=f+g,x=o&&r?n.total/s:null,y=d+m+14,E=b+40,k=Math.max(220,Math.min(y,420)),T=d+t*h/2,A=d+t*h+e*h/2,N=f-16;return jsxRuntimeExports.jsx("div",{className:"rb-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${y} ${E}`,className:"rb-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${k}px`,height:"auto"},role:"img","aria-label":`A bar split in the ratio ${t} to ${e}, ${n.left_label} to ${n.right_label}${r?`, total ${n.total}`:""}.`,children:[r&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("path",{d:`M ${d} ${N+6} L ${d} ${N} L ${d+m} ${N} L ${d+m} ${N+6}`,fill:"none",stroke:AXIS$5,strokeWidth:1.5}),jsxRuntimeExports.jsx("text",{x:d+m/2,y:N-5,textAnchor:"middle",fontSize:15,fontWeight:800,fill:AXIS$5,children:n.total})]}),Array.from({length:s},(S,M)=>{const L=M<t,P=d+M*h;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:P,y:f,width:h,height:g,fill:L?l:u,stroke:SEG_STROKE$1,strokeWidth:2}),x!=null&&jsxRuntimeExports.jsx("text",{x:P+h/2,y:f+g/2+5,textAnchor:"middle",fontSize:14,fontWeight:700,fill:ratioValueInk(L?l:u),children:x})]},M)}),t>0&&jsxRuntimeExports.jsxs("text",{x:T,y:b+22,textAnchor:"middle",fontSize:13,fontWeight:700,fill:a?.ink??LEFT_COLOR$1,children:[n.left_label," — ",t," part",t===1?"":"s"]}),e>0&&jsxRuntimeExports.jsxs("text",{x:A,y:b+22,textAnchor:"middle",fontSize:13,fontWeight:700,fill:i?.ink??RIGHT_COLOR$1,children:[n.right_label," — ",e," part",e===1?"":"s"]})]})})}function VisualDataRenderer({data:n,onSelect:t,selectedOption:e,status:s,attemptsExhausted:o,stem:r}){switch(n.type){case"part_whole":return jsxRuntimeExports.jsx(PartWholeModel,{stem:"",data:n});case"ten_frame":return jsxRuntimeExports.jsx(TenFrame,{data:n});case"ten_frame_mixed":return jsxRuntimeExports.jsx(TenFrameMixed,{data:n});case"equation_display":return jsxRuntimeExports.jsx(EquationDisplay,{data:n});case"story":return jsxRuntimeExports.jsx(Story,{data:n});case"two_step":return jsxRuntimeExports.jsx(TwoStep,{data:n});case"filled_slots":return jsxRuntimeExports.jsx(FilledSlots,{data:n});case"number_line":return jsxRuntimeExports.jsx(NumberLine,{data:n});case"ten_frame_and_ones":return jsxRuntimeExports.jsx(TenFrameAndOnes,{data:n});case"sequence":return jsxRuntimeExports.jsx(Sequence,{data:n});case"objects_grid":return jsxRuntimeExports.jsx(ObjectsGrid,{data:n});case"comparison":return jsxRuntimeExports.jsx(Comparison,{data:n});case"mirror":return jsxRuntimeExports.jsx(Mirror,{data:n});case"take_away":return jsxRuntimeExports.jsx(TakeAway,{data:n,stem:r});case"shape_svg":return jsxRuntimeExports.jsx(ShapeSvg,{data:n});case"shape_options":return jsxRuntimeExports.jsx(ShapeOptions,{data:n,onSelect:t,selectedOption:e,status:s,attemptsExhausted:o});
68case"array":return jsxRuntimeExports.jsx(ArrayGrid,{data:n});case"measure_bars":return jsxRuntimeExports.jsx(MeasureBars,{data:n});case"mass_scale":return jsxRuntimeExports.jsx(MassScale,{data:n});case"clock":return jsxRuntimeExports.jsx(Clock,{data:n});case"fraction_shape":return jsxRuntimeExports.jsx(FractionShape,{data:n});case"domino_set":return jsxRuntimeExports.jsx(DominoSet,{data:n,onSelect:t,selectedOption:e,status:s,attemptsExhausted:o});case"ten_frame_overflow":return jsxRuntimeExports.jsx(TenFrameOverflow,{data:n});case"coin_set":return jsxRuntimeExports.jsx(CoinSet,{data:n});case"coin_tap":return jsxRuntimeExports.jsx(CoinTap,{data:n,onSelect:t,status:s,attemptsExhausted:o});case"tally":return jsxRuntimeExports.jsx(Tally,{data:n});case"ten_frame_and_ones_paired":return jsxRuntimeExports.jsx(TenFrameAndOnesPaired,{data:n});case"drag_order":return jsxRuntimeExports.jsx(DragOrder,{data:n,onSelect:t,status:s,attemptsExhausted:o});case"bar_builder":return jsxRuntimeExports.jsx(BarBuilder,{data:n,onSelect:t,status:s,attemptsExhausted:o});case"mass_compare":return jsxRuntimeExports.jsx(MassCompare,{data:n});case"bar_chart":return jsxRuntimeExports.jsx(BarChart,{data:n});case"pictogram":return jsxRuntimeExports.jsx(Pictogram,{data:n});case"turn":return jsxRuntimeExports.jsx(TurnDiagram,{data:n});case"shape_3d":return jsxRuntimeExports.jsx(Shape3D,{data:n});case"column_method":return jsxRuntimeExports.jsx(ColumnMethod,{data:n});case"two_way_table":return jsxRuntimeExports.jsx(TwoWayTable,{data:n});case"coordinate_grid":return jsxRuntimeExports.jsx(CoordinateGrid,{data:n});case"angle_diagram":return jsxRuntimeExports.jsx(AngleDiagram,{data:n});case"perimeter_shape":return jsxRuntimeExports.jsx(PerimeterShape,{data:n});case"area_grid":return jsxRuntimeExports.jsx(AreaGrid,{data:n});case"short_division":return jsxRuntimeExports.jsx(ShortDivision,{data:n});case"line_graph":return jsxRuntimeExports.jsx(LineGraph,{data:n});case"pie_chart":return jsxRuntimeExports.jsx(PieChart,{data:n});case"reflection_grid":return jsxRuntimeExports.jsx(ReflectionGrid,{data:n});case"triangle_figure":return jsxRuntimeExports.jsx(TriangleFigure,{data:n});case"triangle_area":return jsxRuntimeExports.jsx(TriangleArea,{data:n});case"long_multiplication":return jsxRuntimeExports.jsx(LongMultiplication,{data:n});case"long_division":return jsxRuntimeExports.jsx(LongDivision,{data:n});case"quadrilateral":return jsxRuntimeExports.jsx(Quadrilateral,{data:n});case"symmetry_lines":return jsxRuntimeExports.jsx(SymmetryLines,{data:n});case"volume_cuboid":return jsxRuntimeExports.jsx(VolumeCuboid,{data:n});case"ratio_bar":return jsxRuntimeExports.jsx(RatioBar,{data:n});case"fact_family":case"shape_3d_name":return null;default:return null}}function stemEmbedsSequence(n,t){if(!n||!n.includes("__"))return!1;const e=t.values.filter(o=>typeof o=="number");if(e.length===0)return!1;const s=(n.match(/-?\d+/g)??[]).map(Number);return s.length!==e.length?!1:e.every((o,r)=>s[r]===o)}function formatTypedAnswer(n){const t=/^(-?)(\d+)(\.\d+)?$/.exec(n);if(!t)return n;const[,e,s,o=""]=t;return s.length<4?n:`${e}${Number(s).toLocaleString("en-GB")}${o}`}function optionBadgeLabel(n,t){return n.length>0&&n.every(s=>/^[A-Za-z]$/.test(s.trim()))?String(t+1):String.fromCharCode(65+t)}function parseBondsNumber(n){const t=n.match(/^(\d+)\s+and/i)??n.match(/and\s+(\d+)\s+make/i);return t?parseInt(t[1],10):null}const BASIC_SHAPE_RECOGNITION_SKILLS=new Set(["GEO.P3.M01","GEO.P3.M02","GEO.P3.M05"]);function isEarlyAdditivePartWhole(n){return/^AS\.P[1-4]\./.test(n)}function renderVisual(n){const t=n.mathematicalForm??"",e=n.debugInfo?.microSkillId??"",s=n.representation??"";if(BASIC_SHAPE_RECOGNITION_SKILLS.has(e))return jsxRuntimeExports.jsx(ShapeVisual,{stem:n.stem,microSkillId:e});if(e.startsWith("AS.P2.M02")&&/make\s+10/i.test(n.stem)){const o=parseBondsNumber(n.stem);if(o!==null&&o>=0&&o<=10)return jsxRuntimeExports.jsx(TenFrame,{filled:o,total:10})}return s==="visual_model"||t==="part_whole_visual"||t==="visual_quantity_to_symbol"||t==="misconception_contrast_probe"&&isEarlyAdditivePartWhole(e)?jsxRuntimeExports.jsx(PartWholeModel,{stem:n.stem}):t==="set_combine_to_total"||t==="set_remove_to_find_remaining"?jsxRuntimeExports.jsx(CounterGroups,{stem:n.stem}):null}function QuestionCard({question:n,selectedAnswer:t,status:e,attemptsExhausted:s,onChangeAnswer:o,onSpeak
68Question:r,canSpeak:a,isSpeaking:i,onReviewQuestion:l,hideDebugBar:u=!1,hideKeypad:d=!1,helpNote:m=null}){const[h,f]=reactExports.useState("idle"),[g,b]=reactExports.useState(null),[x,y]=reactExports.useState("idle"),[E,k]=reactExports.useState(!1),T=reactExports.useRef(o);T.current=o;const A=n.visual_data?.type,N=A==="domino_set"||A==="coin_tap"||A==="drag_order"||A==="shape_options"||A==="bar_builder";reactExports.useEffect(()=>{if(e!=="incorrect"||n.type!=="equation"&&n.type!=="text_input"||n.visual_data?.type==="coin_tap"||n.visual_data?.type==="drag_order"||n.visual_data?.type==="bar_builder")return;k(!0);const G=setTimeout(()=>{k(!1),T.current("")},600);return()=>clearTimeout(G)},[e,n.type]);const S=n.debugInfo?.questionId??null,M=reactExports.useMemo(()=>n.type!=="numberline"?[]:n.options??["0","1","2","3","4","5"],[n.options,n.type]),L=async G=>{if(!(!l||!n.debugInfo?.questionId)){f("saving");try{await l(n.debugInfo.questionId,G),f(G==="approve"?"approved":"flagged")}catch{f("idle")}}};reactExports.useEffect(()=>{if(!isAdminMode()||!S){b(null),y("idle");return}let G=!1;return y("loading"),buildApiAuthHeaders().then(X=>fetch(`/api/dev-question-reviews?questionId=${encodeURIComponent(S)}`,{headers:X})).then(async X=>{if(!X.ok)throw new Error("Failed to load review state");return await X.json()}).then(X=>{G||(b(X.review),y("idle"))}).catch(()=>{G||(b(null),y("error"))}),()=>{G=!0}},[S]);const P=async G=>{if(!(!S||x==="saving")){y("saving");try{const X=await buildApiAuthHeaders(),ne=await fetch("/api/dev-question-reviews",{method:"POST",headers:{"Content-Type":"application/json",...X},body:JSON.stringify({questionId:S,reason:G})}),se=await ne.json().catch(()=>({}));if(!ne.ok||!se.review)throw new Error("Failed to save review tag");b(se.review),y("idle")}catch{y("error")}}},F=n.debugInfo?.reviewedBy||h==="approved",D=isAdminMode()&&l&&n.debugInfo&&!F&&h!=="flagged",U=isAdminMode()&&!!n.debugInfo&&!g,q=formatTypedAnswer(t??""),V=q.length>=8?" answer-long answer-xlong":q.length>=6?" answer-long":"",O=n.type==="equation"&&!!n.stem&&/_{2,}\s*$/.test(n.stem),z=n.visual_data?.type==="sequence"&&stemEmbedsSequence(n.stem,n.visual_data),K=O&&!!n.visual_data&&!z,Z=K?n.stem.replace(/\s*_{2,}\s*$/,""):n.stem,Q=n.visual_data?z?null:jsxRuntimeExports.jsx(VisualDataRenderer,{data:n.visual_data,mode:"pupil",stem:n.stem,onSelect:N?o:void 0,selectedOption:n.visual_data.type==="domino_set"||n.visual_data.type==="shape_options"?t??void 0:void 0,status:N?e??void 0:void 0,attemptsExhausted:N?s:void 0}):renderVisual(n),B=Q?`question-card--has-visual${N?" question-card--interactive":""}`:"question-card--no-visual";return jsxRuntimeExports.jsxs("article",{className:`card question-card ${B}`,children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:n.strand}),jsxRuntimeExports.jsxs("div",{className:`stem-row${m?" stem-row--with-note":""}`,children:[jsxRuntimeExports.jsx("h2",{className:"stem-text",children:n.type==="equation"&&n.stem.includes("__")&&!K?n.stem.split(/_{2,}/).map((G,X,ne)=>jsxRuntimeExports.jsxs("span",{children:[G,X<ne.length-1&&jsxRuntimeExports.jsx("span",{className:`answer-display inline-answer${V}${E?" is-shaking":""}`,children:q})]},X)):Z}),jsxRuntimeExports.jsx("button",{className:`icon-btn ${i?"speaking":""}`,"aria-label":i?"Reading question":"Read question aloud",title:a?"Read question aloud":"Read aloud unavailable on this browser",type:"button",onClick:r,disabled:!a,children:jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 24 24",role:"presentation","aria-hidden":"true",children:[jsxRuntimeExports.jsx("path",{d:"M4 10h4l5-4v12l-5-4H4z"}),jsxRuntimeExports.jsx("path",{d:"M16 9c1.4 1.2 1.4 4.8 0 6"}),jsxRuntimeExports.jsx("path",{d:"M18.8 6.8c2.8 2.5 2.8 7.9 0 10.4"})]})}),m]}),Q,n.type==="multiple_choice"&&n.visual_data?.type!=="domino_set"&&n.visual_data?.type!=="shape_options"&&jsxRuntimeExports.jsx("div",{className:`option-grid ${(n.options??[]).length===2?"two-options":""}`,children:(n.options??[]).map((G,X)=>{const ne=t===G,se=G===n.correctAnswer,te=e==="correct"&&ne&&se||e==="incorrect"&&se&&s,oe=e==="incorrect"&&ne&&!se,le=oe&&s,ae=optionBadgeLabel(n.options??[],X),ie=te?`${G} — correct answer`:oe?`${G} — your answer, not right`:void 0;return jsxRuntimeExports.jsxs("button",{className:`option-btn ${ne?"active":""} ${te?"option-correct":""} ${oe?"option-wrong":""} ${le?"option-dimmed":""}`,onClick:()=>o(G),"aria-label":ie,children:[jsxRuntimeExports.jsx("span",{className:"option-badge","data-glyph":te?"✓":oe?"!":ae,"aria-hidden":"true"}),jsxRuntimeExports.jsx("span",{className:"option-text",children:G})]},G)})}),n.type==="equation"&&n.visual_data?.type!=="coin_tap"&&n.visual_data?.type!=="drag_order"&&n.visual_data?.type!=="bar_builder"&&jsxRuntimeExports.jsxs("div",{className:"equation-wrap",children:[(!n.stem||!n.stem.includes("__")||K)&&jsxRuntimeExports.jsx("div",{className:"equation-display-row",children:jsxRuntimeExports.jsx("div",{className:`answer-display${V}${E?" is-shaking":""}`,children:q})}),!d&&jsxRuntimeExports.jsxs("div",{className:"keypad",children:[["1","2","3","4","5","6","7","8","9"].map(G=>jsxRuntimeExports.jsx("button",{className:"key-btn",onClick:()=>o(`${t??""}${G}`.slice(0,7)),type:"button",children:G},G)),jsxRuntimeExports.jsx("span",{className:"key-spacer","aria-hidden":"true"}),jsxRuntimeExports.jsx("button",{className:"key-btn",onClick:()=>o(`${t??""}0`.slice(0,7)),type:"button",children:"0"}),jsxRuntimeExports.jsx("button",{className:"key-btn key-clear",onClick:()=>o((t??"").slice(0,-1)),type:"button","aria-label":"Delete last digit",children:"Delete"})]})]}),n.type==="text_input"&&jsxRuntimeExports.jsx("div",{className:"text-input-wrap",children:jsxRuntimeExports.jsx("input",{className:"text-input-field",type:"text",value:t??"",onChange:G=>o(G.target.value),placeholder:"Type your answer…",autoComplete:"off",spellCheck:!1})}),n.type==="numberline"&&jsxRuntimeExports.jsxs("div",{className:"numberline-wrap",children:[jsxRuntimeExports.jsx("div",{className:"numberline"}),jsxRuntimeExports.jsx("div",{className:"tick-row",children:M.map(G=>{const X=t===G,ne=G===n.correctAnswer,se=e==="correct"&&X&&ne||e==="incorrect"&&ne&&s,te=e==="incorrect"&&X&&!ne;return jsxRuntimeExports.jsxs("button",{className:`tick-btn ${X?"active":""} ${se?"tick-correct":""} ${te?"tick-wrong":""}`,onClick:()=>o(G),type:"button",children:[jsxRuntimeExports.jsx("span",{className:"tick-mark"}),jsxRuntimeExports.jsx("span",{children:G})]},G)})})]}),!u&&isAdminMode()&&n.debugInfo&&jsxRuntimeExports.jsxs("footer",{className:"debug-info-bar",children:[jsxRuntimeExports.jsxs("div",{className:"debug-status-row",children:[h==="approved"?jsxRuntimeExports.jsx("span",{className:"debug-badge debug-badge-reviewed",children:"Approved"}):h==="flagged"?jsxRuntimeExports.jsx("span",{className:"debug-badge debug-badge-flagged",children:"Flagged"}):F?jsxRuntimeExports.jsx("span",{className:"debug-badge debug-badge-reviewed",children:"Reviewed"}):jsxRuntimeExports.jsx("span",{className:"debug-badge debug-badge-unreviewed",children:"Unreviewed"}),D&&jsxRuntimeExports.jsxs("span",{className:"debug-review-actions",children:[jsxRuntimeExports.jsx("button",{className:"debug-review-btn debug
68-review-approve",type:"button",disabled:h==="saving",onClick:()=>{L("approve")},children:h==="saving"?"...":"Approve"}),jsxRuntimeExports.jsx("button",{className:"debug-review-btn debug-review-flag",type:"button",disabled:h==="saving",onClick:()=>{L("flag")},children:"Flag"})]}),n.debugInfo.difficultyRank!=null&&jsxRuntimeExports.jsxs("span",{className:"debug-badge debug-badge-difficulty",children:["Difficulty ",n.debugInfo.difficultyRank,"/5",n.debugInfo.difficultyTier&&` · ${n.debugInfo.difficultyTier}`]}),g&&jsxRuntimeExports.jsxs("span",{className:"debug-badge debug-badge-open-review",children:["Open review: ",humanizeReviewReason(g.reason)]})]}),(U||x==="loading"||x==="saving"||x==="error")&&jsxRuntimeExports.jsxs("div",{className:"debug-review-queue",children:[x==="loading"&&jsxRuntimeExports.jsx("span",{className:"debug-review-note",children:"Checking review tag…"}),x==="saving"&&jsxRuntimeExports.jsx("span",{className:"debug-review-note",children:"Saving tag…"}),x==="error"&&jsxRuntimeExports.jsx("span",{className:"debug-review-note debug-review-note-error",children:"Could not update review tag."}),U&&x!=="loading"&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("span",{className:"debug-review-note",children:"Tag for later review:"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("wrong_answer")},children:"Wrong answer"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("too_hard")},children:"Too hard"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("too_easy")},children:"Too easy"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("confusing")},children:"Confusing"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("typo")},children:"Typo"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("bad_wording")},children:"Bad wording"}),jsxRuntimeExports.jsx("button",{className:"debug-review-chip",type:"button",onClick:()=>{P("other")},children:"Other"})]})]}),jsxRuntimeExports.jsxs("div",{className:"debug-meta-row",children:[jsxRuntimeExports.jsx("span",{className:`debug-source debug-source-${n.debugInfo.source}`,children:n.debugInfo.source==="canonical_bank"?"Bank":n.debugInfo.source==="old_engine"?"Engine":"Fallback"}),jsxRuntimeExports.jsx("span",{children:n.debugInfo.questionId}),n.debugInfo.yearBand&&jsxRuntimeExports.jsx("span",{children:n.debugInfo.yearBand}),n.debugInfo.microSkillId&&jsxRuntimeExports.jsx("span",{children:n.debugInfo.microSkillId}),n.debugInfo.questionFamily&&jsxRuntimeExports.jsx("span",{children:n.debugInfo.questionFamily}),n.debugInfo.representation&&jsxRuntimeExports.jsx("span",{children:n.debugInfo.representation}),n.debugInfo.bankSource&&jsxRuntimeExports.jsx("span",{children:n.debugInfo.bankSource})]})]})]})}function humanizeReviewReason(n){switch(n){case"wrong_answer":return"Wrong answer";case"too_hard":return"Too hard";case"too_easy":return"Too easy";case"confusing":return"Confusing";case"typo":return"Typo";case"bad_wording":return"Bad wording";default:return"Other"}}function SessionStartGate({status:n,onRetry:t,onBack:e}){const s=n==="failed";return jsxRuntimeExports.jsxs("div",{className:"sm-app",children:[jsxRuntimeExports.jsx("div",{className:"sm-grain","aria-hidden":"true"}),jsxRuntimeExports.jsx("div",{className:"sm-screen sm-session-wrap sm-save-gate-wrap",children:jsxRuntimeExports.jsx("div",{className:"sm-session-body",children:jsxRuntimeExports.jsxs("section",{className:`sm-save-gate${s?" is-error":""}`,role:s?"alert":"status","aria-live":"polite",children:[jsxRuntimeExports.jsx("span",{className:"sm-save-gate-icon","aria-hidden":"true",children:s?"!":"↻"}),jsxRuntimeExports.jsx("p",{className:"sm-eyebrow",children:"Progress saving"}),jsxRuntimeExports.jsx("h1",{children:s?"We couldn’t start saving":"Getting your round ready…"}),jsxRuntimeExports.jsx("p",{children:s?"Your round hasn’t started, so no work has been lost. Check the connection and try again.":"We’re connecting progress saving before the first question starts."}),s&&jsxRuntimeExports.jsxs("div",{className:"sm-save-gate-actions",children:[jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-btn sm-btn-primary",onClick:t,children:["Try again ",jsxRuntimeExports.jsx("span",{className:"arrow",children:"↻"})]}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-btn sm-btn-ghost",onClick:e,children:"Back to practice"})]})]})})})]})}class QuestionErrorBoundary extends reactExports.Component{state={hasError:!1};static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(t){const e=new Error(t.message);
68e.stack=t.stack,e.name=`QuestionRender:${this.props.questionId}`.slice(0,80),logClientError("react_render",e),this.props.onTrip?.(!0)}componentWillUnmount(){this.state.hasError&&this.props.onTrip?.(!1)}render(){return this.state.hasError?jsxRuntimeExports.jsxs("div",{className:"sm-qerr-wrap",role:"alert",children:[jsxRuntimeExports.jsx("p",{className:"sm-qerr-msg",children:"Hmm, this question isn't loading properly. Let's skip it — it won't count."}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-qerr-skip",onClick:this.props.onSkip,children:"Skip this question →"})]}):this.props.children}}const WORKED_EXAMPLES_BY_MICRO_SKILL={"AS.P6.M01":asP6M01Raw,"AS.P6.M02":asP6M02Raw,"AS.P6.M03":asP6M03Raw,"AS.P6.M04":asP6M04Raw,"AS.P6.M05":asP6M05Raw,"AS.P6.M06":asP6M06Raw,"DS.P4.M01":dsP4M01Raw,"DS.P4.M02":dsP4M02Raw,"DS.P4.M03":dsP4M03Raw,"FDP.P6.M01":fdpP6M01Raw,"FDP.P6.M08":fdpP6M08Raw,"FDP.P6.M09":fdpP6M09Raw,"FDP.P6.M02":fdpP6M02Raw,"FDP.P6.M03":fdpP6M03Raw,"GEO.P6.M01":geoP6M01Raw,"GEO.P6.M02":geoP6M02Raw,"GEO.P6.M03":geoP6M03Raw,"MD.P4.M04":mdP4M04Raw,"MD.P4.M05":mdP4M05Raw,"MD.P5.M01":mdP5M01Raw,"MD.P5.M02":mdP5M02Raw,"MD.P5.M03":mdP5M03Raw,"MD.P5.M04":mdP5M04Raw,"MEA.P7.M04":meaP7M04Raw,"MEA.P7.M06":meaP7M06Raw,"NPV.P6.M05":npvP6M05Raw,"NPV.P7.M01":npvP7M01Raw,"NPV.P7.M02":npvP7M02Raw,"RP.P2.M01":rpP2M01Raw,"RP.P2.M02":rpP2M02Raw,"RP.P2.M03":rpP2M03Raw,"AS.P3.M03":asP3M03Raw,"AS.P3.M01":asP3M01Raw,"AS.P2.M03":asP2M03Raw,"AS.P3.M04":asP3M04Raw,"AS.P3.M05":asP3M05Raw,"AS.P3.M02":asP3M02Raw,"AS.P2.M02":asP2M02Raw,"AS.P2.M04":asP2M04Raw,"NPV.P2.M01":npvP2M01Raw,"NPV.P2.M02":npvP2M02Raw,"NPV.P2.M03":npvP2M03Raw,"NPV.P2.M04":npvP2M04Raw,"NPV.P3.M01":npvP3M01Raw,"NPV.P3.M02":npvP3M02Raw,"NPV.P3.M03":npvP3M03Raw,"MD.P1.M02":mdP1M02Raw,"MD.P1.M03":mdP1M03Raw,"AS.P4.M02":asP4M02Raw,"DS.P1.M02":dsP1M02Raw,"FDP.P1.M01":fdpP1M01Raw,"FDP.P1.M02":fdpP1M02Raw,"FDP.P1.M03":fdpP1M03Raw,"FDP.P2.M01":fdpP2M01Raw,"FDP.P2.M02":fdpP2M02Raw,"GEO.P3.M01":geoP3M01Raw,"GEO.P3.M02":geoP3M02Raw,"MEA.P4.M01":meaP4M01Raw,"MEA.P4.M02":meaP4M02Raw,"MEA.P4.M04":meaP4M04Raw,"MEA.P6.M01":meaP6M01Raw,"MEA.P6.M02":meaP6M02Raw,"MEA.P3.M03":meaP3M03Raw,"AS.P4.M08":asP4M08Raw,"DS.P2.M01":dsP2M01Raw,"FDP.P2.M04":fdpP2M04Raw,"MD.P2.M01":mdP2M01Raw,"AS.P4.M01":asP4M01Raw,"AS.P4.M03":asP4M03Raw,"AS.P4.M04":asP4M04Raw,"AS.P4.M05":asP4M05Raw,"AS.P4.M06":asP4M06Raw,"AS.P4.M07":asP4M07Raw,"DS.P2.M02":dsP2M02Raw,"FDP.P2.M03":fdpP2M03Raw,"FDP.P3.M01":fdpP3M01Raw,"FDP.P3.M02":fdpP3M02Raw,"GEO.P3.M03":geoP3M03Raw,"GEO.P3.M05":geoP3M05Raw,"GEO.P4.M01":geoP4M01Raw,"MD.P2.M02":mdP2M02Raw,"MD.P2.M03":mdP2M03Raw,"MD.P2.M04":mdP2M04Raw,"MD.P2.M05":mdP2M05Raw,"MEA.P4.M03":meaP4M03Raw,"MEA.P4.M05":meaP4M05Raw,"MEA.P5.M01":meaP5M01Raw,"MEA.P6.M03":meaP6M03Raw,"NPV.P4.M01":npvP4M01Raw,"NPV.P4.M02":npvP4M02Raw,"NPV.P4.M03":npvP4M03Raw,"NPV.P4.M04":npvP4M04Raw,"NPV.P4.M05":npvP4M05Raw,"NPV.P5.M05":npvP5M05Raw,"NPV.P5.M06":npvP5M06Raw,"NPV.P6.M01":npvP6M01Raw,"NPV.P6.M02":npvP6M02Raw,"NPV.P6.M03":npvP6M03Raw,"NPV.P6.M04":npvP6M04Raw,"AS.P5.M03":asP5M03Raw,"AS.P5.M04":asP5M04Raw,"AS.P5.M05":asP5M05Raw,"MD.P3.M02":mdP3M02Raw,"MD.P4.M01":mdP4M01Raw,"MD.P4.M02":mdP4M02Raw,"MD.P4.M03":mdP4M03Raw,"FDP.P4.M01":fdpP4M01Raw,"FDP.P4.M02":fdpP4M02Raw,"FDP.P4.M03":fdpP4M03Raw,"FDP.P4.M04":fdpP4M04Raw,"GEO.P4.M03":geoP4M03Raw,"GEO.P4.M05":geoP4M05Raw,"GEO.P5.M03":geoP5M03Raw,"GEO.P5.M04":geoP5M04Raw,"MEA.P7.M02":meaP7M02Raw,"MEA.P7.M03":meaP7M03Raw,"DS.P3.M02":dsP3M02Raw,"DS.P3.M03":dsP3M03Raw,"RP.P1.M03":rpP1M03Raw,"NPV.P5.M03":npvP5M03Raw,"AS.P5.M01":asP5M01Raw,"AS.P5.M02":asP5M02Raw,"DS.P2.M03":dsP2M03Raw,"DS.P2.M04":dsP2M04Raw,"DS.P2.M05":dsP2M05Raw,"DS.P3.M01":dsP3M01Raw,"FDP.P3.M03":fdpP3M03Raw,"FDP.P3.M04":fdpP3M04Raw,"FDP.P5.M01":fdpP5M01Raw,"FDP.P5.M02":fdpP5M02Raw,"FDP.P5.M03":fdpP5M03Raw,"FDP.P6.M07":fdpP6M07Raw,"FDP.P5.M04":fdpP5M04Raw,"GEO.P4.M02":geoP4M02Raw,"GEO.P5.M01":geoP5M01Raw,"GEO.P5.M02":geoP5M02Raw,"MD.P3.M01":mdP3M01Raw,"MD.P3.M03":mdP3M03Raw,"MD.P3.M04":mdP3M04Raw,"MD.P3.M05":mdP3M05Raw,"MEA.P5.M03":meaP5M03Raw,"MEA.P5.M04":meaP5M04Raw,"MEA.P7.M07":meaP7M07Raw,"MEA.P5.M05":meaP5M05Raw,"MEA.P6.M04":meaP6M04Raw,"MEA.P7.M01":meaP7M01Raw,"NPV.P5.M02":npvP5M02Raw,"NPV.P5.M04":npvP5M04Raw,"NPV.P5.M07":npvP5M07
68Raw,"RP.P1.M01":rpP1M01Raw,"RP.P1.M02":rpP1M02Raw,"NPV.P7.M03":npvP7M03Raw,"NPV.P7.M04":npvP7M04Raw,"NPV.P7.M05":npvP7M05Raw,"NPV.P7.M06":npvP7M06Raw,"MEA.P7.M05":meaP7M05Raw,"AS.P6.M07":asP6M07Raw,"AS.P6.M08":asP6M08Raw,"AS.P6.M09":asP6M09Raw,"AS.P6.M10":asP6M10Raw,"MD.P5.M05":mdP5M05Raw,"MD.P5.M06":mdP5M06Raw,"MD.P5.M07":mdP5M07Raw,"DS.P5.M01":dsP5M01Raw,"DS.P5.M02":dsP5M02Raw,"DS.P5.M03":dsP5M03Raw,"FDP.P6.M04":fdpP6M04Raw,"FDP.P6.M05":fdpP6M05Raw,"FDP.P6.M06":fdpP6M06Raw,"GEO.P6.M04":geoP6M04Raw,"GEO.P6.M05":geoP6M05Raw,"GEO.P6.M06":geoP6M06Raw,"RP.P3.M01":rpP3M01Raw,"RP.P3.M02":rpP3M02Raw,"RP.P3.M03":rpP3M03Raw,"RP.P3.M04":rpP3M04Raw};function getWorkedExampleForMicroSkill(n){return WORKED_EXAMPLES_BY_MICRO_SKILL[n]??null}function listWorkedExampleMicroSkills(){return Object.keys(WORKED_EXAMPLES_BY_MICRO_SKILL)}const WORKED_EXAMPLES_BY_MICRO_SKILL_FORM={"AS.P6.M10::misconception_contrast_probe":asP6M10MisconceptionContrastProbeRaw,"DS.P5.M03::trend_prediction":dsP5M03TrendPredictionRaw,"AS.P6.M01::missing_addend":asP6M01MissingAddendRaw,"AS.P6.M01::rounding_entry":asP6M01RoundingEntryRaw,"AS.P6.M03::missing_addend":asP6M03MissingAddendRaw,"AS.P6.M04::equation_completion":asP6M04EquationCompletionRaw,"AS.P6.M05::estimate_reasonableness_check":asP6M05EstimateReasonablenessCheckRaw,"AS.P6.M05::estimate_sufficiency":asP6M05EstimateSufficiencyRaw,"AS.P6.M06::rounding_entry":asP6M06RoundingEntryRaw,"AS.P6.M06::context_reasoning_judgement":asP6M06RoundingEntryRaw,"DS.P4.M01::line_graph_difference":dsP4M01LineGraphDifferenceRaw,"DS.P4.M02::trend_direction_read":dsP4M02TrendDirectionReadRaw,"DS.P4.M02::trend_prediction":dsP4M02TrendPredictionRaw,"DS.P4.M02::trend_shape_description":dsP4M02TrendShapeDescriptionRaw,"DS.P4.M02::trend_statement_evaluation":dsP4M02TrendShapeDescriptionRaw,"DS.P4.M03::comparison_statement":dsP4M03ComparisonStatementRaw,"DS.P4.M03::symbolic_choice":dsP4M03ComparisonStatementRaw,"DS.P4.M03::pie_chart_fraction_read":dsP4M03PieChartFractionReadRaw,"FDP.P6.M08::equation_completion":fdpP6M08EquationCompletionRaw,"FDP.P6.M03::fraction_identification":fdpP6M03FractionIdentificationRaw,"GEO.P6.M02::comparison_statement":geoP6M02ComparisonStatementRaw,"GEO.P6.M03::impossible_triangle_check":geoP6M03ImpossibleTriangleCheckRaw,"MD.P4.M05::context_reasoning":mdP4M05ContextReasoningRaw,"MD.P5.M02::factor_multiple_classify":mdP5M02ClassifyRaw,"MD.P5.M02::multiple_identification":mdP5M02MultipleFindRaw,"MD.P5.M04::cube_number_evaluate":mdP5M04CubeNumberEvaluateRaw,"MEA.P7.M04::inverse_area_missing_side":meaP7M04InverseAreaMissingSideRaw,"MEA.P7.M06::time_find_end_time":meaP7M06TimeFindEndTimeRaw,"MEA.P7.M06::time_find_start_time":meaP7M06TimeFindStartTimeRaw,"NPV.P6.M05::number_line_identify":npvP6M05NumberLineIdentifyRaw,"NPV.P7.M01::comparison":npvP7M01ComparisonRaw,"NPV.P7.M01::digit_value_entry":npvP7M01DigitValueEntryRaw,"NPV.P7.M01::ordering_large_numbers":npvP7M01OrderingLargeNumbersRaw,"NPV.P7.M01::sequence_continue":npvP7M01SequenceContinueRaw,"NPV.P7.M01::sequence_fill_gap":npvP7M01SequenceContinueRaw,"NPV.P7.M02::rounding_reverse_identify":npvP7M02RoundingReverseIdentifyRaw,"RP.P2.M01::ratio_interpretation":rpP2M01RatioInterpretationRaw,"RP.P2.M02::misconception_contrast_probe":rpP2M02MisconceptionContrastProbeRaw,"MD.P1.M02::count_equal_groups":mdP1M02CountGroupsRaw,"AS.P3.M04::subtraction_story_context":asP3M04DiffRaw,"AS.P2.M02::domino_bonds":asP2M02DominoRaw,"MEA.P4.M02::coin_tap":meaP4M02CoinTapRaw,"MEA.P4.M04::comparative_length":meaP4M04ComparativeLengthRaw,"AS.P3.M01::near_double":asP3M01NearDoubleRaw,"AS.P3.M03::doubling":asP3M03DoublingRaw,"NPV.P2.M01::word_to_numeral":npvP2M01WordRaw,"NPV.P2.M01::word_to_typed_numeral":npvP2M01WordRaw,"NPV.P2.M01::count_to_numeral":npvP2M01CountRaw,"NPV.P3.M02::teen_equation_identification":npvP3M02TeenIdRaw,"NPV.P4.M01::ten_and_ones_to_numeral":npvP4M01BuildRaw,"MEA.P5.M01::comparison_statement":meaP5M01UnitsRaw,"MD.P2.M04::symbolic_choice":mdP2M04FactFamilyRaw,"MD.P2.M04::symbolic_numeric_entry":mdP2M04FactFamilyRaw,"MD.P2.M04::true_false_check":mdP2M04FactFamilyRaw,"MD.P2.M04::misconception_contrast_probe":mdP2M04FactFamilyRaw,"MD.P2.M04::equation_completion":mdP2M04FactFamilyRaw,"MD.P2.M04::context_calculation":mdP2M04FactFamilyRaw,"MD.P2.M04::varied_symbolic_calculation":mdP2M04FactFamilyRaw,"MD.P2.M05::equation_completion":mdP2M05EquationCompletionRaw,"GEO.P3.M03::symbolic_choice":geoP3M03NameRaw,"GEO.P4.M01::context_calculation":geoP4M01DirRaw,"DS.P2.M02::comparison_statement":dsP2M02MostRaw,"DS.P2.M03::symbolic_numeric_entry":dsP2M03ReadRaw,"GEO.P4.M01::comparison_statement":geoP4M01CompareRaw,"FDP.P3.M03::equation_completion":fdpP3M03EquationCompletionRaw,"FDP.P4.M02::varied_symbolic_calculation":fdpP4M02VariedSymbolicRaw,"MEA.P5.M03::context_reasoning":meaP5M03ContextReasoningRaw,"MEA.P5.M03::symbolic_choice":meaP5M03SymbolicChoiceRaw,"MEA.P5.M03::comparison_statement":meaP5M03ComparisonStatementRaw,"MEA.P5.M04::comparison_statement":meaP5M04ComparisonStatementRaw,"AS.P4.M01::varied_symbolic_calculation":asP4M01MissingStartRaw,"AS.P4.M02::missing_addend":asP4M02MissingAddendRaw,"AS.P4.M04::missing_addend":asP4M04MissingAddendRaw,"AS.P4.M04::equation_completion":asP4M04MissingAddendRaw,"AS.P4.M05::missing_addend":asP4M05MissingAddendRaw,"AS.P4.M06::missing_addend":asP4M06MissingAddendRaw,"AS.P4.M06::near_double":asP4M06NearDoubleRaw,"AS.P4.M07::missing_addend":asP4M07MissingAddendRaw};function getWorkedExampleForMicroSkillForm(n,t){if(t){const e=WORKED_EXAMPLES_BY_MICRO_SKILL_FORM[`${n}::${t}`];if(e)return e}return getWorkedExampleForMicroSkill(n)}function listWorkedExampleMicroSkillForms(){return Object.keys(WORKED_EXAMPLES_BY_MICRO_SKILL_FORM)}const HELP_COPY={clueFirst:"Give me a clue",clueNext:"Another clue",clueLast:"One more clue",read:"Read it to me",reading:"Reading…",watchBefore:"Show me one like it",anotherWay:"Try another way",comeBack:"Come back to this one",helpButton:"Help",miaIdle:"Stuck? Tap me.",miaIdleDesktop:"Stuck? I can help.",sheetTitle:"What would help?",askAfterMiss:"That one was tricky! Want me to show you how?",askAnotherWayAfterMiss:"That one was tricky! Want me to explain it another way?",thinkingAnotherWay:"Let me think of another way to show you…",anotherWayFailed:"Hmm, I couldn't think of another way just then.",thinking:"Thinking…",tryAgain:"Try again",yesShowMe:"Yes, show me",noThanksNext:"No thanks, next →",introTitle:"Hi, I'm Mia!",introBody:"Stuck? Tap Help at the bottom for a clue, or tap the speaker to hear the question.",introBodyDesktop:"Stuck? Pick a clue below, or tap the speaker to hear the question."},HELP_SUBLINES={clue:"Mia gives you a nudge",watch:"Mia shows one like it",read:"Hear the question again"};function clueLabel(n){return n<=0?HELP_COPY.clueFirst:n===1?HELP_COPY.clueNext:HELP_COPY.clueLast}const SPOKEN_CLUE_PREFIX="Here's a clue.";function askSentence(n){const t=n.trim();if(t.length<=70)return t;const e=t.split(new RegExp("(?<=[.?!])\\s+")).filter(o=>o.length>0);if(e.length<2)return t;let s=e.length-1;for(;s>0&&!/[.?!]$/.test(e[s]);)s--;return e.slice(s).join(" ").trim()}const TEN_FRAME_CELLS=10,CELL_PITCH=47,FRAME_X0=29,FRAME_Y0=9,OVERFLOW_X0=290;function TenFrameOverflowFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="ten_frame_overflow")return null;const{start:s,adding:o}=n.visual_data,r=Math.max(0,TEN_FRAME_CELLS-s),a=Math.min(r,o),i=Array.from({length:TEN_FRAME_CELLS},(m,h)=>h),l=t===1?s:Math.min(TEN_FRAME_CELLS,s+o),u=t===1?o:o-a,d=t===3;return jsxRuntimeExports.jsxs("div",{className:`wex-frame-wrap${d?" wex-with-total":""}`,children:[jsxRuntimeExports.jsxs("div",{className:"wex-frame-row",children:[jsxRuntimeExports.jsxs("div",{className:"wex-frame-group",children:[jsxRuntimeExports.jsx("div",{className:"wex-ten-frame",children:i.map(m=>{const h=m<s,f=m>=s&&m<l,b=h?`wex-cell wex-filled${h&&t===1?" wex-cell-enter":""}`:f?"wex-cell wex-filled wex-cell-landed":"wex-cell wex-empty wex-pulse";return jsxRuntimeExports.jsx("div",{className:b,children:(h||f)&&jsxRuntimeExports.jsx("span",{className:"wex-dot-blue"})},m)})}),jsxRuntimeExports.jsx("span",{className:"wex-label",children:l}),t===2&&Array.from({length:a},(m,h)=>{const f=s+h;return jsxRuntimeExports.jsx("span",{className:"wex-fly-dot wex-fly-go",style:{"--start-x":`${OVERFLOW_X0+CELL_PITCH*h}px`,"--start-y":`${FRAME_Y0}px`,"--end-x":`${FRAME_X0+CELL_PITCH*(f%5)}px`,"--end-y":`${FRAME_Y0+CELL_PITCH*Math.floor(f/5)}px`,"--fly-i":h},"aria-hidden":"true"},`fly-${h}-${e}`)})]}),jsxRuntimeExports.jsx("span",{className:"wex-plus","aria-hidden":"true",children:"+"}),jsxRuntimeExports.jsxs("div",{className:"wex-frame-group",children:[jsxRuntimeExports.jsx("div",{className:"wex-overflow",children:Array.from({length:u},(m,h)=>jsxRuntimeExports.jsx("div",{className:`wex-overflow-cell${t===3?" wex-highlight":""}`,children:jsxRuntimeExports.jsx("span",{className:"wex-dot-purple"})},h))}),jsxRuntimeExports.jsx("span",{className:"wex-label wex-overflow-label",style:{"--overflow-cols":Math.max(1,Math.min(5,u))},children:u})]})]}),d&&jsxRuntimeExports.jsxs("div",{className:"wex-total","aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-equals",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:s+o})]},`total-${e}`)]})}const digitHighlight="_digitHighlight_g0qbq_2",styles$2={digitHighlight},LEGACY_CELL_WIDTH=60,LEGACY_GAP=8,WIDE_GAP=10,WIDE_CHAR_PX=13,WIDE_PADDING=22;function wideCellWidth(n){const t=Math.max(1,...n);return Math.max(LEGACY_CELL_WIDTH,Math.min(160,t*WIDE_CHAR_PX+WIDE_PADDING))}
68function SequenceFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="sequence")return null;const s=n.visual_data,o=s.values,r=o.findIndex(h=>h===null),a=r>=0?r:o.length-1,i=s.wide===!0,l=i?wideCellWidth(o.map((h,f)=>String(s.labels?.[f]??h??"").length)):LEGACY_CELL_WIDTH,u=i?WIDE_GAP:LEGACY_GAP,d=l+u,m=l/2;return jsxRuntimeExports.jsx("div",{className:"wex-seq-wrap",children:jsxRuntimeExports.jsxs("div",{className:"wex-seq-row",style:i?{gap:`${WIDE_GAP}px`}:void 0,children:[o.map((h,f)=>{const g=h===null,b=f===a,x=s.emphasis===f,y=s.labels?.[f],E=String(y??h??""),k=s.highlight_digits?.[f],T=k!=null&&Number.isInteger(k)&&k>=0&&k<E.length&&/[0-9]/.test(E[k]),A=t===3&&b&&!s.hide_hop,N=g?`wex-seq-cell wex-seq-blank wex-pulse${i?" wex-seq-cell-wide":""}`:`wex-seq-cell wex-seq-filled${i?" wex-seq-cell-wide":""}${t===1&&!b?" wex-seq-enter":""}${A?" wex-seq-write-in":""}${x?" wex-seq-emph":""}`,S={...t===1&&!g?{"--seq-enter-i":f}:{},...i?{width:`${l}px`}:{}};return jsxRuntimeExports.jsx("div",{className:N,style:S,children:g?null:jsxRuntimeExports.jsx("span",{children:T?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[E.slice(0,k),jsxRuntimeExports.jsx("span",{className:styles$2.digitHighlight,children:E[k]}),E.slice(k+1)]}):y??h})},A?`cell-${f}-${e}`:f)}),t===2&&!s.hide_hop&&jsxRuntimeExports.jsx("div",{className:"wex-seq-hop",style:{"--hop-from-x":`${(a-1)*d+m}px`,"--hop-to-x":`${a*d-u/2}px`},"aria-hidden":"true",children:jsxRuntimeExports.jsx("span",{className:"wex-seq-hop-label",children:s.step>0?`+${s.step}`:s.step})},`hop-${e}`)]})})}function PartWholeFrame({step:n,stepNumber:t,replayKey:e}){const s=reactExports.useRef(-1);if(n.visual_data.type!=="part_whole")return null;const o=n.visual_data,{whole:r,parts:a}=o,i=a.findIndex(b=>b===null);i>=0?s.current=i:t===1&&(s.current=-1);const l=i>=0?i:s.current,d=a[l===0?1:0],m=typeof a[l]=="number"?a[l]:typeof d=="number"&&typeof r=="number"?r-d:null;l>=0&&typeof d=="number"&&typeof r=="number"&&Array.from({length:r-d},(b,x)=>d+1+x);const h=typeof r=="number"&&Number.isInteger(r)&&r>=2&&r<=20&&a.every(b=>b===null||Number.isInteger(b)&&b>=0),f=b=>b===l?m:a[b],g=b=>{const x=b===l,y=f(b),E=!x||t===3,k=h&&typeof y=="number"?y:0,T=x?t===1?"wex-pwb-part wex-pwb-unknown wex-pulse":t===2?"wex-pwb-part wex-pwb-unknown wex-pwb-counting":"wex-pwb-part wex-pwb-found":`wex-pwb-part wex-pwb-known${t===1?" wex-pw-enter":""}`;return jsxRuntimeExports.jsxs("div",{className:`${T}${k>0?" wex-pwb-has-cells":""}`,style:{flex:`${typeof y=="number"&&y>0?y:1} 1 0`,...t===1&&!x?{"--pw-enter-i":b+1}:{}},children:[k>0&&jsxRuntimeExports.jsx("div",{className:"wex-pwb-cells","aria-hidden":"true",children:Array.from({length:k},(A,N)=>{const S=x&&t===2;return jsxRuntimeExports.jsx("span",{className:`wex-pwb-cell${S?" wex-pwb-cell-count":""}${x&&t===1?" wex-pwb-cell-empty":""}`,style:S?{"--pw-chip-i":N}:void 0,children:S&&typeof d=="number"?d+1+N:null},N)})}),x&&t===1&&jsxRuntimeExports.jsx("span",{className:"wex-pwb-label wex-pwb-q",children:"?"}),E&&y!==null&&jsxRuntimeExports.jsx("span",{className:`wex-pwb-label${x?" wex-pw-write-in":""}`,children:y})]},`part-${b}-${x?e:0}`)};return jsxRuntimeExports.jsxs("div",{className:"wex-pwb-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-pwb-part wex-pwb-whole",style:t===1?{"--pw-enter-i":0}:void 0,children:jsxRuntimeExports.jsx("span",{className:"wex-pwb-label",children:r})}),jsxRuntimeExports.jsxs("div",{className:"wex-pwb-parts",children:[g(0),g(1)]})]})}function TenFrameFillFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="ten_frame"&&n.visual_data.type!=="filled_slots")return null;const s=n.visual_data,{total:o,filled:r}=s,a=s.type==="filled_slots"?s.emoji:null,i=s.type==="filled_slots"?s.context_label:void 0,l=reactExports.useRef(r);t===1&&(l.current=r);const u=l.current,d=Array.from({length:o},(m,h)=>h);return jsxRuntimeExports.jsxs("div",{className:"wex-tf-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-tf-grid",style:{"--tf-cols":Math.min(o,5)},children:d.map(m=>{const h=m<r,f=h&&m>=u,g=u+(m-u)+1,b=h?f?"wex-tf-cell wex-tf-filled wex-tf-new":"wex-tf-cell wex-tf-filled":"wex-tf-cell wex-tf-empty wex-pulse";
68return jsxRuntimeExports.jsxs("div",{className:b,style:f?{"--tf-enter-i":m-u}:void 0,children:[h&&(a?jsxRuntimeExports.jsx("span",{className:"wex-tf-emoji",children:a}):jsxRuntimeExports.jsx("span",{className:"wex-tf-dot"})),f&&t===2&&jsxRuntimeExports.jsx("span",{className:"wex-tf-count-badge",children:g})]},m)})}),i&&jsxRuntimeExports.jsx("div",{className:"wex-tf-context",children:i}),t===3&&o-u>0&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsxs("span",{className:"wex-total-number",children:["+",o-u," more"]})},`label-${e}`)]},e)}function MirrorFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="mirror")return null;const o=n.visual_data.each_side,r=o*2,a=Array.from({length:o},(u,d)=>d),i=t>=2,l=t===3;return jsxRuntimeExports.jsxs("div",{className:`wex-mir-wrap${l?" wex-mir-with-total":""}`,children:[jsxRuntimeExports.jsxs("div",{className:"wex-mir-row",children:[jsxRuntimeExports.jsxs("div",{className:"wex-mir-column",children:[a.map(u=>jsxRuntimeExports.jsx("span",{className:`wex-mir-dot${t===1?" wex-mir-enter":""}`,style:t===1?{"--mir-enter-i":u}:void 0},u)),jsxRuntimeExports.jsx("span",{className:"wex-mir-label",children:o})]}),jsxRuntimeExports.jsx("div",{className:"wex-mir-line","aria-hidden":"true"}),jsxRuntimeExports.jsxs("div",{className:"wex-mir-column",children:[i?a.map(u=>jsxRuntimeExports.jsxs("div",{className:"wex-mir-dot-row",children:[jsxRuntimeExports.jsx("span",{className:"wex-mir-dot wex-mir-mirror-in",style:{"--mir-mirror-i":u}}),jsxRuntimeExports.jsx("span",{className:`wex-mir-count${t===2?" wex-mir-count-in":""}`,style:t===2?{"--mir-mirror-i":u}:void 0,children:o+u+1})]},`right-${u}-${e}`)):a.map(u=>jsxRuntimeExports.jsx("span",{className:"wex-mir-dot wex-mir-blank wex-pulse"},`right-empty-${u}`)),jsxRuntimeExports.jsx("span",{className:"wex-mir-label",children:i?o:"?"})]})]}),l&&jsxRuntimeExports.jsxs("div",{className:"wex-mir-total","aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-mir-equals",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-mir-total-number",children:r})]},`total-${e}`)]})}function hopLabelFontSize(n){return(n.match(/\d/g)??[]).length>4?12:16}const BOLD_CHAR_W=.6,LABEL_GAP=4,HOP_GAP=2;function labelWidth(n,t){return n.length*t*BOLD_CHAR_W}function fitUnderLabels(n,t){const e=[...n].sort((i,l)=>i.x-l.x);if(e.length<=2)return new Set(e.map(i=>i.key));const s=(i,l)=>Math.abs(l.x-i.x)>=(labelWidth(i.text,t)+labelWidth(l.text,t))/2+LABEL_GAP,o=e[0],r=e[e.length-1];let a=[o,r];for(let i=1;i<e.length-1;i++){const l=e.slice(1,-1).filter((d,m)=>(m+1)%i===0);l.length&&!s(l[l.length-1],r)&&l.pop();const u=[o,...l,r];if(u.every((d,m)=>m===0||s(u[m-1],d))){a=u;break}}return new Set(a.map(i=>i.key))}function visibleHopLabels(n){return n.map((t,e)=>{const s=n[e-1];return e===0||t.label===""||t.label!==s.label||(t.row??0)!==(s.row??0)?!0:t.span>=labelWidth(t.label,hopLabelFontSize(t.label))+HOP_GAP})}const W$8=330,H$1=154,LINE_Y=106,TICK_FONT=15,TICK_CHAR_HALF_W=4.5,MARGIN=28,USABLE=W$8-MARGIN*2;function xAt(n,t,e){return MARGIN+(n-t)/(e-t)*USABLE}function round4$5(n){return Math.round(n*1e4)/1e4}function NumberLineFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="number_line")return null;const s=n.visual_data,{min:o,max:r,step:a,hops:i=[],marked:l=[],unknown:u,conclude_label:d,marker_at:m,pulse_steps:h,crossed:f,tick_labels:g,under_labels:b,hide_result_number:x}=s,y=O=>g?.[O]??String(O),E=r-o,k=a!=null&&a>0&&a<E&&!Number.isInteger(a),T=a!=null&&a>1&&a<E&&Number.isInteger(a),A=TICK_FONT-1,N=b?fitUnderLabels(Object.entries(b).map(([O,z])=>({key:Number(O),x:xAt(Number(O),o,r),text:z})),A):null,S=visibleHopLabels(i.map(O=>({label:O.label??"",span:Math.abs(xAt(O.to,o,r)-xAt(O.from,o,r)),row:O.label_row}))),M=new Set([o,r,...l]);i.forEach(O=>{M.add(O.from),M.add(O.to)}),u!=null&&M.add(u);const L=k?a/100:0,P=O=>k?[...M].some(z=>Math.abs(z-O)<L):M.has(O),F=O=>k?l.some(z=>Math.abs(z-O)<L):l.includes(O),D=k?Array.from({length:Math.round(E/a)+1},(O,z)=>round4$5(o+z*a)):T?(()=>{const O=Array.from({length:Math.floor(E/a)+1},(Z,Q)=>
68o+Q*a),z=new Set(O),K=[...M].filter(Z=>!z.has(Z));return[...O,...K].sort((Z,Q)=>Z-Q)})():Array.from({length:E+1},(O,z)=>z+o),U=new Map;{const O=[];for(const K of D)P(K)&&O.push({x:xAt(K,o,r),text:y(K),row:0});if(k)for(const K of l)D.some(Z=>Math.abs(Z-K)<L)||O.push({x:xAt(K,o,r),text:String(K),row:0});O.sort((K,Z)=>K.x-Z.x);const z=(K,Z)=>Z.x-K.x<(K.text.length+Z.text.length)*TICK_CHAR_HALF_W+6;for(let K=1;K<O.length;K++){const Z=O.slice(0,K),Q=[...Z].reverse().find(B=>B.row===0);if(Q&&z(Q,O[K])){const B=[...Z].reverse().find(G=>G.row===1);!B||!z(B,O[K])?O[K].row=1:O[K].row=-1}}for(const K of O)U.set(Math.round(K.x*100),K.row)}const q=O=>U.get(Math.round(O*100)),V=O=>LINE_Y+24+(q(O)===1?16:0);return jsxRuntimeExports.jsxs("div",{className:`wex-nl-wrap ${styles$3.exampleNumberLine}`,children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${W$8} ${H$1}`,width:"100%",className:"wex-nl-svg",children:[jsxRuntimeExports.jsx("line",{x1:MARGIN-6,y1:LINE_Y,x2:W$8-MARGIN+6,y2:LINE_Y,className:`wex-nl-baseline ${styles$3.exampleBaseline}`,strokeWidth:2,stroke:"#cbd5e1"}),D.map((O,z)=>
68{const K=xAt(O,o,r),Z=P(O),Q=Z?10:6,B=F(O);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:K,y1:LINE_Y-Q,x2:K,y2:LINE_Y+Q,strokeWidth:Z?2:1,className:Z?styles$3.exampleMajorTick:styles$3.exampleMinorTick,stroke:Z?"#64748b":k?"#cbd5e1":"#e2e8f0"}),Z&&q(K)!==-1&&jsxRuntimeExports.jsx("text",{x:K,y:V(K),textAnchor:"middle",fontSize:TICK_FONT,fontWeight:700,className:styles$3.exampleNumberLabel,children:y(O)}),b?.[O]!==void 0&&N?.has(O)&&jsxRuntimeExports.jsx("text",{x:K,y:LINE_Y+24+18,textAnchor:"middle",fontSize:A,fontWeight:800,fill:"#2563eb",className:"wex-nl-under",children:b[O]}),B&&(h?h.includes(t):t===1)&&jsxRuntimeExports.jsx("circle",{cx:K,cy:LINE_Y,r:7,className:"wex-nl-start-pulse",fill:"none",stroke:"#3b82f6",strokeWidth:2})]},z)}),k&&l.map((O,z)=>{const K=D.some(Z=>Math.abs(Z-O)<L);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:xAt(O,o,r),cy:LINE_Y,r:5,fill:"#3b82f6",stroke:"#1e3a8a",strokeWidth:1.5}),!K&&q(xAt(O,o,r))!==-1&&jsxRuntimeExports.jsx("text",{x:xAt(O,o,r),y:V(xAt(O,o,r)),textAnchor:"middle",fontSize:TICK_FONT-1,fontWeight:700,className:styles$3.exampleNumberLabel,children:O})]},`marked-${z}`)}),(f??[]).map((O,z)=>{const K=xAt(O,o,r);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:K,cy:LINE_Y,r:5,fill:"#94a3b8",stroke:"#64748b",strokeWidth:1.5}),jsxRuntimeExports.jsx("line",{x1:K-7,y1:LINE_Y-7,x2:K+7,y2:LINE_Y+7,stroke:"#d97706",strokeWidth:2.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("line",{x1:K-7,y1:LINE_Y+7,x2:K+7,y2:LINE_Y-7,stroke:"#d97706",strokeWidth:2.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("text",{x:K,y:V(K),textAnchor:"middle",fontSize:TICK_FONT-1,fontWeight:700,fill:"#64748b",children:O})]},`crossed-${z}`)}),m!=null&&!(t===3&&u===m)&&jsxRuntimeExports.jsx("path",{d:`M ${xAt(m,o,r)} ${LINE_Y-22} l -6 -10 l 12 0 z`,fill:"#3b82f6",stroke:"#1e3a8a",strokeWidth:1,className:"wex-nl-marker-pin"}),i.map((O,z)=>{const K=xAt(O.from,o,r),Z=xAt(O.to,o,r),Q=(K+Z)/2,B=Math.abs(Z-K),G=Math.min(58,Math.max(24,B*.45)),X=LINE_Y-G,ne=LINE_Y-G/2,se=z===i.length-1,te=se&&t>1,oe=hopLabelFontSize(O.label);return jsxRuntimeExports.jsxs("g",{className:"wex-nl-hop-group",children:[jsxRuntimeExports.jsx("path",{d:`M ${K} ${LINE_Y} Q ${Q} ${X} ${Z} ${LINE_Y}`,fill:"none",stroke:se?"#3b82f6":"#94a3b8",strokeWidth:3,strokeLinecap:"round",className:te?"wex-nl-hop-path":""}),jsxRuntimeExports.jsx("text",{x:Q,y:ne-10+(O.label_row===1?14:0),textAnchor:"middle",fontSize:oe,fontWeight:800,fill:se?"#2563eb":"#64748b",className:te?"wex-nl-hop-label":"",children:S[z]?O.label:""}),O.sublabel&&jsxRuntimeExports.jsx("text",{x:Q,y:ne-24,textAnchor:"middle",fontSize:12,fontWeight:700,fill:se?"#64748b":"#94a3b8",className:te?"wex-nl-hop-label":"",children:O.sublabel}),jsxRuntimeExports.jsx("circle",{cx:Z,cy:LINE_Y,r:5,fill:se?"#3b82f6":"#94a3b8",className:te?"wex-nl-hop-dot":""})]},`${z}-${e}`)}),t===3&&u!==null&&u!==void 0&&jsxRuntimeExports.jsxs("g",{className:"wex-nl-result",children:[jsxRuntimeExports.jsx("circle",{cx:xAt(u,o,r),cy:LINE_Y,r:8,fill:"none",stroke:"#f59e0b",strokeWidth:3,className:"wex-pulse"}),!x&&jsxRuntimeExports.jsx("text",{x:xAt(u,o,r),y:i.some(O=>O.to===u)?LINE_Y-56:LINE_Y-40,textAnchor:"middle",fontSize:20,fontWeight:900,fill:"#d97706",className:"wex-write-in",children:u})]})]}),d&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:d})},`label-${e}`)]},e)}function TakeAwayFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="take_away")return null;const s=n.visual_data,o=s.total,r=s.take_away,a=o-r,i=Array.from({length:o},(u,d)=>d),l=t===3;return jsxRuntimeExports.jsxs("div",{className:`wex-ta-wrap${l?" wex-ta-with-total":""}`,children:[jsxRuntimeExports.jsx("div",{className:"wex-ta-row",children:i.map(u=>{const d=u-(o-r),m=d>=0,x=`wex-ta-counter${t===1?" wex-ta-enter":""}${m?t===2?" wex-ta-fade-out":t===3?" wex-ta-faded":"":""}${t===3&&!m?" wex-ta-remaining":""}`;return jsxRuntimeExports.jsxs("div",{className:x,style:t===1?{"--ta-enter-i":u}:t===2&&m?{"--ta-fade-i":d}:void 0,children:[jsxRuntimeExports.jsx("span",{className:"wex-ta-dot"}),t===2&&m&&!s.hide_count&&jsxRuntimeExports.jsx("span",{className:"wex-ta-count",style:{"--ta-fade-i":d},children:d+1})]},m&&t===2?`take-${u}-${e}`:u)})}),l&&jsxRuntimeExports.jsxs("div",{className:"wex-ta-total","aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-ta-equals",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-ta-total-number",children:a})]},`total-${e}`)]})}function Dots({emoji:n,n:t,animate:e,replayKey:s,gi:o}){return jsxRuntimeExports.jsx("div",{className:"wex-og-dots",children:Array.from({length:t},(r,a)=>jsxRuntimeExports.jsx("span",{className:`wex-og-dot${e?" wex-og-enter":""}`,style:e?{"--og-i":a}:void 0,"aria-hidden":"true",children:n},e?`d-${o}-${a}-${s}`:`d-${o}-${a}`))})}function ObjectsGridFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="objects_grid")return null;const s=n.visual_data,{emoji:o,count:r,group_size:a,hide_subtotals:i,hide_total:l,total_label:u,scatter:d,chosen:m,compare_group_size:h,regroup_into:f,groups_formed:g,dealt:b,numbered:x,show_extra_group:y,show_division_fact:E,keyword:k,mode:T}=s,A=k?jsxRuntimeExports.jsx("div",{className:"wex-og-keyword",children:jsxRuntimeExports.jsx("span",{className:"wex-og-keyword-text",children:k})},`kw-${e}`):null;if(x!=null){const V=Math.max(0,Math.min(x,r));return jsxRuntimeExports.jsxs("div",{className:"wex-og-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-og-countgrid",children:Array.from({length:r},(O,z)=>jsxRuntimeExports.jsxs("span",{className:"wex-og-countcell",children:[jsxRuntimeExports.jsx("span",{className:"wex-og-dot","aria-hidden":"true",children:o}),z<V&&jsxRuntimeExports.jsx("span",{className:`wex-og-countnum${t===2?" wex-og-enter":""}${t===3&&z===V-1?" wex-og-countnum-last":""}`,style:t===2?{"--og-i":z}:void 0,"aria-hidden":"true",children:z+1})]},z))}),t===3&&!l&&u&&jsxRuntimeExports.jsx("div",{className:"wex-og-total","aria-hidden":"true",children:jsxRuntimeExports.jsx("span",{className:"wex-og-total-text",children:u})},`total-${e}`)]},e)}
68if(b!=null){const V=Math.max(1,Math.round(r/a)),O=Math.max(0,Math.min(b,a)),z=Math.max(0,r-V*O);return jsxRuntimeExports.jsxs("div",{className:"wex-og-wrap",children:[A,jsxRuntimeExports.jsx("div",{className:"wex-og-rings",children:Array.from({length:V},(K,Z)=>jsxRuntimeExports.jsx("div",{className:`wex-og-ring${O===0?" wex-og-ring-empty":""}`,style:{"--og-ring-i":Z},children:jsxRuntimeExports.jsx(Dots,{emoji:o,n:O,animate:!0,gi:Z,replayKey:e})},`ring-${Z}`))}),z>0&&jsxRuntimeExports.jsx("div",{className:"wex-og-pile","aria-hidden":"true",children:Array.from({length:z},(K,Z)=>jsxRuntimeExports.jsx("span",{className:"wex-og-dot",children:o},Z))}),t===3&&!l&&u&&jsxRuntimeExports.jsx("div",{className:"wex-og-total","aria-hidden":"true",children:jsxRuntimeExports.jsx("span",{className:"wex-og-total-text",children:u})},`total-${e}`)]},e)}if(g!=null){const V=g*a,O=Math.max(0,r-V),z=y&&O>0;return jsxRuntimeExports.jsxs("div",{className:"wex-og-wrap",children:[A,jsxRuntimeExports.jsxs("div",{className:"wex-og-rings",children:[Array.from({length:g},(K,Z)=>jsxRuntimeExports.jsx("div",{className:"wex-og-ring",style:{"--og-ring-i":Z},children:jsxRuntimeExports.jsx(Dots,{emoji:o,n:a,animate:!0,gi:Z,replayKey:e})},`ring-${Z}`)),O>0&&z&&jsxRuntimeExports.jsx("div",{className:"wex-og-ring wex-og-ring-needed",style:{"--og-ring-i":g},children:jsxRuntimeExports.jsx(Dots,{emoji:o,n:O,animate:!0,gi:g,replayKey:e})},"ring-extra"),O>0&&!z&&jsxRuntimeExports.jsx("div",{className:"wex-og-leftover","aria-hidden":"true",children:Array.from({length:O},(K,Z)=>jsxRuntimeExports.jsx("span",{className:"wex-og-dot wex-og-leftover-dot",children:o},Z))})]}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-og-total","aria-hidden":"true",children:jsxRuntimeExports.jsx("span",{className:"wex-og-total-text",children:u??`${r} ÷ ${a} = ${g} r${O}`})},`total-${e}`)]},e)}if(f!=null){const V=Math.round(r/f);return jsxRuntimeExports.jsxs("div",{className:"wex-og-wrap",children:[A,jsxRuntimeExports.jsx("div",{className:"wex-og-rings",children:Array.from({length:f},(O,z)=>jsxRuntimeExports.jsx("div",{className:"wex-og-ring wex-og-ring-in",style:{"--og-ring-i":z},children:jsxRuntimeExports.jsx(Dots,{emoji:o,n:V,animate:!0,gi:z,replayKey:e})},`grp-${z}`))}),t===3&&E&&jsxRuntimeExports.jsx("div",{className:"wex-og-total","aria-hidden":"true",children:jsxRuntimeExports.jsxs("span",{className:"wex-og-total-text",children:[f," × ",V," = ",r,", so ",r," ÷ ",f," = ",V]})},`total-${e}`)]},e)}if(h!=null){const V=Math.ceil(r/a),O=V,z=O*h;return jsxRuntimeExports.jsxs("div",{className:"wex-og-wrap",children:[A,jsxRuntimeExports.jsxs("div",{className:"wex-og-compare",children:[jsxRuntimeExports.jsxs("div",{className:"wex-og-compare-one wex-og-ref",children:[jsxRuntimeExports.jsxs("span",{className:"wex-og-compare-cap",children:[O," × ",h," = ",z]}),jsxRuntimeExports.jsx("div",{className:"wex-og-groups",children:Array.from({length:O},(K,Z)=>jsxRuntimeExports.jsx("div",{className:"wex-og-group",children:jsxRuntimeExports.jsx(Dots,{emoji:o,n:h,animate:!1,gi:Z,replayKey:e})},`ref-${Z}`))})]}),jsxRuntimeExports.jsxs("div",{className:"wex-og-compare-one",children:[jsxRuntimeExports.jsxs("span",{className:"wex-og-compare-cap wex-og-compare-cap-lit",children:[V," × ",a," = ",r]}),jsxRuntimeExports.jsx("div",{className:"wex-og-groups",children:Array.from({length:V},(K,Z)=>jsxRuntimeExports.jsx("div",{className:"wex-og-group",children:jsxRuntimeExports.jsx(Dots,{emoji:o,n:a,animate:!0,gi:Z,replayKey:e})},`main-${Z}`))})]})]})]},e)}if(m!=null){const V=Math.ceil(r/a),O=new Set(m),z=m.length*a,K=new Map(m.map((Z,Q)=>[Z,Q]));return jsxRuntimeExports.jsxs("div",{className:`wex-og-wrap ${styles$3.exampleObjects}`,children:[A,jsxRuntimeExports.jsx("div",{className:"wex-og-groups",children:Array.from({length:V},(Z,Q)=>{const B=O.has(Q),G=K.get(Q);return jsxRuntimeExports.jsxs("div",{className:`wex-og-group${B?" wex-og-chosen":" wex-og-unchosen"}`,children:[jsxRuntimeExports.jsx(Dots,{emoji:o,n:a,animate:B,gi:Q,replayKey:e}),B&&G!=null&&!u&&jsxRuntimeExports.jsx("span",{className:"wex-og-count-chip",style:{"--og-i":G},"aria-hidden":"true",children:(G+1)*a},`chip-${Q}-${e}`)]},Q)})}),jsxRuntimeExports.jsx("div",{className:"wex-og-total","aria-hidden":"true",children:jsxRuntimeExports.jsx("span",{className:"wex-og-total-text",children:u??`${m.length} of ${V} groups = ${z}`})},`total-${e}`)]},e)}if(d)return jsxRuntimeExports.jsxs("div",{className:"wex-og-wrap",children:[A,jsxRuntimeExports.jsx("div",{className:"wex-og-scatter","aria-hidden":"true",children:Array.from({length:r},(V,O)=>jsxRuntimeExports.jsx("span",{className:"wex-og-dot wex-og-enter",style:{"--og-i":O},children:o},`s-${O}-${e}`))})]},e);const N=Math.ceil(r/a),S=T==="share",M=18,L=3,P=Math.max(1,Math.ceil(Math.sqrt(a))),F=Math.ceil(a/P),D=P*M+(P-1)*L,U=F*M+(F-1)
68*L,q=Math.ceil(Math.hypot(D,U))+22;return jsxRuntimeExports.jsxs("div",{className:`wex-og-wrap${S?"":` ${styles$3.exampleObjects}`}`,children:[A,jsxRuntimeExports.jsx("div",{className:`wex-og-groups${S?" wex-og-share-row":""}`,children:Array.from({length:N},(V,O)=>{const z=O===N-1,K=(O+1)*a,Z=S?{width:`${q}px`,height:`${q}px`,"--share-w":`${D}px`}:void 0;return jsxRuntimeExports.jsxs("div",{className:`wex-og-group${S?" wex-og-share":""}`,style:Z,children:[S&&jsxRuntimeExports.jsx("span",{className:"wex-og-share-head","aria-hidden":"true",children:"🧒"}),jsxRuntimeExports.jsx(Dots,{emoji:o,n:a,animate:z,gi:O,replayKey:e}),!i&&!S&&jsxRuntimeExports.jsx("span",{className:`wex-og-subtotal${z?" wex-og-subtotal-in":""}`,children:K},z?`sub-${O}-${e}`:`sub-${O}`)]},O)})}),t===3&&!l&&jsxRuntimeExports.jsx("div",{className:"wex-og-total","aria-hidden":"true",children:jsxRuntimeExports.jsx("span",{className:"wex-og-total-text",children:u??(S?`${r} shared between ${N} = ${a} each`:`${N} groups of ${a} = ${r}`)})},`total-${e}`)]},e)}function TwoStepFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="two_step")return null;const{start:s,step1_add:o,step1_result:r,step2_add:a,final_result:i,format_thousands:l}=n.visual_data,u=T=>T===null?"":l?T.toLocaleString("en-GB"):String(T),d=o??0,m=a??0,h=d+m,f=h<0?"−":"+",g=d<0?"−":"+",b=m<0?"−":"+",x=Math.abs,y=r!==null,E=i!==null,k=d!==0&&m!==0&&d>0!=m>0;return jsxRuntimeExports.jsxs("div",{className:"wex-ts-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"wex-ts-problem",children:[jsxRuntimeExports.jsx("span",{className:"wex-ts-num",children:u(s)}),k?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:g}),jsxRuntimeExports.jsx("span",{className:"wex-ts-num",children:u(x(d))}),jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:b}),jsxRuntimeExports.jsx("span",{className:"wex-ts-num",children:u(x(m))})]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:f}),jsxRuntimeExports.jsx("span",{className:"wex-ts-num",children:u(x(h))})]}),jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-ts-unknown",children:"?"})]}),!y&&jsxRuntimeExports.jsxs("div",{className:"wex-ts-split","aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-ts-split-arrow",children:"↙"}),jsxRuntimeExports.jsx("span",{className:"wex-ts-split-label",children:k?`${g}${u(x(d))}, then ${b}${u(x(m))}`:`${u(x(d))} + ${u(x(m))}`})]},`split-${e}`),y&&jsxRuntimeExports.jsxs("div",{className:`wex-ts-row${t===2?" wex-ts-slide-in":""}`,children:[jsxRuntimeExports.jsx("span",{className:"wex-ts-num",children:u(s)}),jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:g}),jsxRuntimeExports.jsx("span",{className:"wex-ts-step-add",children:u(x(d))}),jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-ts-milestone",children:u(r)})]},t===2?`bridge-${e}`:"bridge"),E&&jsxRuntimeExports.jsxs("div",{className:"wex-ts-row wex-ts-slide-in",children:[jsxRuntimeExports.jsx("span",{className:"wex-ts-num",children:u(r)}),jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:b}),jsxRuntimeExports.jsx("span",{className:"wex-ts-step-add",children:u(x(m))}),jsxRuntimeExports.jsx("span",{className:"wex-ts-op",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-ts-answer",children:u(i)})]},`final-${e}`)]})}function fitSizing(n){return n<=3?{fontSize:32,minWidth:48}:n<=5?{fontSize:24,minWidth:40}:n<=7?{fontSize:20,minWidth:34}:{fontSize:16,minWidth:28}}function EquationDisplayFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="equation_display")return null;const{terms:s,fit:o}=n.visual_data,r=o?(()=>{const{fontSize:l,minWidth:u}=fitSizing(s.length);return{"--eq-font-size":`${l}px`,"--eq-min-width":`${u}px`}})():void 0,a=new Set((n.visual_data.highlight??[]).filter(l=>Number.isInteger(l))),i=React.useRef(null);if(t===1)i.current=s.findIndex(l=>l==="__");else if(i.current==null||i.current===-1){const l=s.findIndex(u=>u==="__");l!==-1&&(i.current=l)}return jsxRuntimeExports.jsx("div",{className:"wex-eq-wrap",children:jsxRuntimeExports.jsx("div",{className:"wex-eq-row",style:r,children:s.map((l,u)=>{const d=l==="__",m=typeof l=="string"&&["+","−","=","-","×","÷"].includes(l),h=t===3&&u===i.current,f=a.has(u);let g="wex-eq-term";return d&&(g+=" wex-eq-unknown wex-pulse"),m&&(g+=" wex-eq-operator"),f&&(g+=" wex-eq-first-op"),h&&(g+=" wex-eq-answer wex-eq-write-in"),jsxRuntimeExports.jsx("div",{className:g,style:{"--eq-i":u},children:jsxRuntimeExports.jsx("span",{children:l==="__"?"?":l})},u)})})},e)}function ComparisonFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="comparison")return null;const s=n.visual_data,{left:o,right:r,symbol:a,left_text:i,right_text:l,crossed_symbol:u}=s,d=n.animation.includes("show_statement"),m=(t===3||d)&&a!==null&&a!=="none",h=t===3&&u!=null?u:null,f=!m&&a!=="none",g=n.animation.includes("highlight_none");let b=null;g||(n.animation.includes("highlight_hundredths")?b=-2:n.animation.includes("highlight_tenths")?b=-1:n.animation.includes("highlight_millions")?b=6:n.animation.includes("highlight_hundred_thous
68ands")?b=5:n.animation.includes("highlight_ten_thousands")?b=4:n.animation.includes("highlight_thousands")?b=3:n.animation.includes("highlight_hundreds")?b=2:n.animation.includes("highlight_tens")?b=1:n.animation.includes("highlight_ones")&&(b=0));let x=!g&&b==null&&t===3;if(x&&i==null&&l==null&&Number.isInteger(o)&&Number.isInteger(r)&&o>=0&&r>=0&&o!==r){const E=String(o),k=String(r);if(E.length!==k.length)b=Math.max(E.length,k.length)-1;else for(let T=0;T<E.length;T++)if(E[T]!==k[T]){b=E.length-1-T;break}x=!1}const y=(E,k)=>{const T=k??String(E),A=T.split(""),N=T.indexOf("."),S=N===-1?T.length:N;return jsxRuntimeExports.jsx("div",{className:"wex-cmp-number",children:A.map((M,L)=>{const P=L===N?null:L<S?S-1-L:-(L-N),F=x?L===A.length-1:b!=null&&P===b;return jsxRuntimeExports.jsx("span",{className:`wex-cmp-digit${F?" wex-cmp-highlight":""}`,children:M},L)})})};return jsxRuntimeExports.jsxs("div",{className:"wex-cmp-wrap",children:[h&&jsxRuntimeExports.jsxs("div",{className:"wex-cmp-crossed","aria-hidden":"true",children:[jsxRuntimeExports.jsxs("span",{className:"wex-cmp-crossed-text",children:[i??o," ",h," ",l??r]}),jsxRuntimeExports.jsx("span",{className:"wex-cmp-strike"})]}),jsxRuntimeExports.jsxs("div",{className:"wex-cmp-row",children:[y(o,i),jsxRuntimeExports.jsx("div",{className:"wex-cmp-symbol-slot",children:m?jsxRuntimeExports.jsx("span",{className:"wex-cmp-symbol wex-cmp-write-in",children:a}):f?jsxRuntimeExports.jsx("div",{className:"wex-cmp-blank wex-pulse",children:"?"}):null}),y(r,l)]})]},e)}function TenFrameAndOnesFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="ten_frame_and_ones")return null;const{tens:s,ones:o,loose:r,regrouped:a=0}=n.visual_data,i=s-Math.max(0,Math.min(a,s)),l=s*10+o;if(r)return jsxRuntimeExports.jsx("div",{className:"wex-tfo-wrap",children:jsxRuntimeExports.jsx("div",{className:"wex-tfo-loose",children:Array.from({length:o}).map((f,g)=>jsxRuntimeExports.jsx("div",{className:"wex-tfo-dot wex-tfo-blue-dot wex-tfo-enter",style:{"--tfo-i":Math.min(g,30)}},g))})},e);const u=t>=2,d=t===3,m=s>0,h=o>0;return jsxRuntimeExports.jsxs("div",{className:"wex-tfo-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"wex-tfo-row",children:[m&&jsxRuntimeExports.jsxs("div",{className:`wex-tfo-section${u?" wex-tfo-highlight-ten":""}`,children:[jsxRuntimeExports.jsx("div",{className:`wex-tfo-tens${s>1?" wex-tfo-tens-multi":""}`,children:Array.from({length:s}).map((f,g)=>{const b=g>=i;return jsxRuntimeExports.jsx("div",{className:`wex-tfo-grid wex-tfo-ten-grid${b?" wex-tfo-new-ten":""}`,children:Array.from({length:10}).map((x,y)=>jsxRuntimeExports.jsx("div",{className:"wex-tfo-cell",children:jsxRuntimeExports.jsx("div",{className:`wex-tfo-dot ${b?"wex-tfo-amber-dot":"wex-tfo-blue-dot"} wex-tfo-enter`,style:{"--tfo-i":Math.min(g*10+y,30)}})},y))},g)})}),jsxRuntimeExports.jsx("span",{className:"wex-tfo-label",children:s===1?"1 ten":`${s} tens`})]}),m&&h&&jsxRuntimeExports.jsx("span",{className:"wex-tfo-plus",children:"+"}),h&&jsxRuntimeExports.jsxs("div",{className:`wex-tfo-section${d?" wex-tfo-highlight-ones":""}`,children:[jsxRuntimeExports.jsx("div",{className:"wex-tfo-grid wex-tfo-ones-grid",children:Array.from({length:o}).map((f,g)=>jsxRuntimeExports.jsx("div",{className:"wex-tfo-cell",children:jsxRuntimeExports.jsx("div",{className:`wex-tfo-dot wex-tfo-amber-dot wex-tfo-enter${d?" wex-pulse":""}`,style:{"--tfo-i":Math.min(g+10,30)}})},g))}),jsxRuntimeExports.jsxs("span",{className:"wex-tfo-label",children:[o," ",o===1?"one":"ones"]})]})]}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-tfo-total wex-tfo-write-in",children:jsxRuntimeExports.jsxs("span",{children:["= ",l]})})]},e)}function TallyFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="tally")return null;const s=n.visual_data,o=Math.floor(s.count/5),r=s.count%5,a=s.mode==="groups",i=Array.from({length:o},(u,d)=>a?d+1:(d+1)*5),l=Array.from({length:r},(u,d)=>a?"|":o*5+d+1);return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx("div",{className:t===1?"wex-pulse":void 0,children:jsxRuntimeExports.jsx(Tally,{data:s})}),t>=2&&jsxRuntimeExports.jsxs("div",{className:"wex-chip-row",children:[i.map((u,d)=>jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-pop",style:{"--wex-i":d},children:u},`b-${d}`)),t===3&&l.map((u,d)=>jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-soft wex-chip-pop",style:{"--wex-i":i.length+d},children:u},`e-${d}`))]}),t===3&&jsxRuntimeExports.jsxs("div",{className:"wex-total",children:["= ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:a?o:s.count}),a?o===1?" group":" groups":null]},`total-${e}`)]},e)}function FractionShapeFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="fraction_shape")return null;const s=n.visual_data;if(s.shapes&&s.shapes.length>0){const r=s.shapes,a=t===3&&s.compare_symbol!=null&&r.length===2,i=t===3&&s.order_labels!=null,l=(u,d)=>{const m={type:"fraction_shape",shape:"rectangle",total_parts:u.total_parts,shaded_parts:u.shaded_parts,color:s.color,split_columns:!0},h=t===3&&s.emphasis===d;return jsxRuntimeExports.jsxs("div",{className:`wex-frac-bar${h?" wex-frac-emph":""}${t===2?" wex-pulse":""}`,style:{"--frac-bar-i":d},children:[s.half_line?jsxRuntimeExports.jsxs("div",{className:"wex-frac-half",children:[jsxRuntimeExports.jsx(FractionShape,{data:m}),jsxRuntimeExports.jsx("span",{className:"wex-frac-half-line","aria-hidden":"true"},`hl-${d}-${e}`)]}):jsxRuntimeExports.jsx(FractionShape,{data:m}),u.label&&jsxRuntimeExports.jsx("span",{className:"wex-frac-bar-label",children:u.label})]},`bar-${d}`)};return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx("div",{className:"wex-frac-bars wex-fade-enter",children:a?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[l(r[0],0),jsxRuntimeExports.jsx("span",{className:"wex-frac-symbol",children:s.compare_symbol}),l(r[1],1)]}):r.map(l)}),i&&jsxRuntimeExports.jsx("div",{className:"wex-frac-order",children:s.order_labels.map((u,d)=>jsxRuntimeExports.jsxs("span",{className:"wex-frac-order-item",children:[d>0&&jsxRuntimeExports.jsx("span",{className:"wex-frac-order-lt",children:s.order_symbol??"<"}),u]},d))},`order-${e}`)]},e)}if(s.half_line){const r=s.shape!=="circle"&&s.total_parts===4&&!s.split_columns&&s.equal!==!1;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("div",{className:"wex-frac-half",children:[jsxRuntimeExports.jsx(FractionShape,{data:s}),jsxRuntimeExports.jsx("span",{className:`wex-frac-half-line${r?" wex-frac-half-line-across":""}`,"aria-hidden":"true"},`hl-${e}`)]}),s.benchmark_verdict&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:s.benchmark_verdict})},`verdict-${e}`)]},e)}if(t===3&&s.compare_shaded!=null){const r={...s,shaded_parts:s.compare_shaded},a=`${s.compare_shaded}/${s.total_parts}`,i=`${s.shaded_parts}/${s.total_parts}`,l=s.compare_shaded<s.shaded_parts?"<":s.compare_shaded>s.shaded_parts?">":"=";return jsxRuntimeExports.jsx("div",{className:"wex-frame-center",children:jsxRuntimeExports.jsxs("div",{className:"wex-frac-compare wex-fade-enter",children:[jsxRuntimeExports.jsxs("div",{className:"wex-frac-one",children:[jsxRuntimeExports.jsx(FractionShape,{data:r}),jsxRuntimeExports.jsx("span",{className:"wex-frac-cap",children:a})]}),jsxRuntimeExports.jsx("span",{className:"wex-frac-symbol",children:l}),jsxRuntimeExports.jsxs("div",{className:"wex-frac-one",children:[jsxRuntimeExports.jsx(FractionShape,{data:s}),jsxRuntimeExports.jsx("span",{className:"wex-frac-cap",children:i})]})]})},e)}const o=s.conclude_label??(s.shaded_parts>0?`${s.shaded_parts}/${s.total_parts}`:s.equal===!1?"Not equal":"Equal parts!");return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx("div",{className:t===2?"wex-pulse":"wex-fade-enter",children:jsxRuntimeExports.jsx(FractionShape,{data:s})}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:o})},`total-${e}`)]},e)}const VERTICES$1={triangle:[[100,18],[184,152],[16,152]],square:[[42,27],[158,27],[158,143],[42,143]],rectangle:[[24,49],[176,49],[176,121],[24,121]],pentagon:[[100,14],[180,73],[149,158],[51,158],[20,73]],hexagon:[[60,25],[140,25],[180,85],[140,145],[60,145],[20,85]]};function ShapeGlyph({shape:n,color:t,className:e}){const s=VERTICES$1[n];return s?jsxRuntimeExports.jsx("polygon",{points:s.map(o=>o.join(",")).join(" "),fill:t,fillOpacity:.25,stroke:t,strokeWidth:4,strokeLinejoin:"round",className:e}):jsxRuntimeExports.jsx("circle",{cx:100,cy:85,r:70,fill:t,fillOpacity:.25,stroke:t,strokeWidth:4,className:e})}function CompareRow({items:n,stepNumber:t,sideMode:e,replayKey:s}){const o=r=>e?r===1?"side":"sides":r===1?"corner":"corners";return jsxRuntimeExports.jsx("div",{className:"wex-shape-compare-row",children:n.map((r,a)=>{const i=t>=2&&typeof r.count=="number",l=t===3&&r.highlight;return jsxRuntimeExports.jsxs("div",{className:`wex-shape-compare-cell${l?" wex-shape-compare-win":""}`,children:[jsxRuntimeExports.jsx("svg",{viewBox:"0 0 200 170",className:"wex-shape-compare-svg",role:"img","aria-label":r.shape,children:jsxRuntimeExports.jsx(ShapeGlyph,{shape:r.shape,color:r.color,className:"wex-fade-enter"})}),r.label&&jsxRuntimeExports.jsx("div",{className:"wex-shape-compare-name",children:r.label}),i&&jsxRuntimeExports.jsxs("div",{className:"wex-shape-compare-count",children:[r.count," ",o(r.count)]},`n-${a}-${s}`)]},`c-${a}-${s}`)})},s)}function ShapeSvgFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="shape_svg")return null;const s=n.visual_data,o=n.animation.includes("side");if(s.compare&&s.compare.length>0)return jsxRuntimeExports.jsx(CompareRow,{items:s.compare,stepNumber:t,sideMode:o,replayKey:e});const r=n.animation.includes("name"),a=s.shape.charAt(0).toUpperCase()+s.shape.slice(1),i=VERTICES$1[s.shape],l=!i,u=i?o?i.map((f,g)=>{const b=i[(g+1)%i.length];return[(f[0]+b[0])/2,(f[1]+b[1])/2]}):i:[],d=o?u.length===1?"side":"sides":u.length===1?"corner":"corners",m=t>=2||s.show_markers===!0,h=l&&s.trace_curve===!0&&t===2;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 200 170",className:"wex-shape-svg",role:"img","aria-label":s.shape,children:[jsxRuntimeExports.jsx(ShapeGlyph,{shape:s.shape,color:s.color,className:"wex-fade-enter"}),h&&jsxRuntimeExports.jsx("circle",{cx:100,cy:85,r:70,className:"wex-shape-trace"},`trace-${e}`),m&&u.map(([f,g],b)=>jsxRuntimeExports.jsxs("g",{className:t===2?"wex-shape-marker wex-shape-marker-pop":"wex-shape-marker",style:{"--wex-i":b},children:[jsxRuntimeExports.jsx("circle",{cx:f,cy:g,r:13,className:"wex-shape-marker-dot"}),jsxRuntimeExports.jsx("text",{x:f,y:g+5,textAnchor:"middle",className:"wex-shape-marker-num",children:b+1})]},`m-${b}-${e}`))]}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:r?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:a})," · ",u.length," ",d]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:u.length})," ",d]})},`total-${e}`)]},e)}const PENCE$3={"1p":1,"2p":2,"5p":5,"10p":10,"20p":20,"50p":50,"£1":100,"£2":200},penceLabel$3=n=>n>=100&&n%100===0?`£${n/100}`:`${n}p`,chipLabel=n=>n>=100&&n%100===0?penceLabel$3(n):`${n}`;function CoinSetFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="coin_set")return null;const s=n.visual_data;if(s.compare&&s.coins.length===2){const i=s.coins.map(u=>PENCE$3[u]??0),l=i[0]===i[1]?-1:i[0]>i[1]?0:1;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx(CoinDefs,{}),jsxRuntimeExports.jsx("div",{className:"wex-coin-row",children:s.coins.map((u,d)=>jsxRuntimeExports.jsxs("div",{className:"wex-coin-cell",children:[jsxRuntimeExports.jsx("div",{className:t===3&&d===l?"wex-pulse":"wex-fade-enter",children:jsxRuntimeExports.jsx(Coin,{denom:u,hideValue:!1})}),t>=2&&jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-pop",style:{"--wex-i":d},children:penceLabel$3(i[d])}),t===3&&d===l&&jsxRuntimeExports.jsx("span",{className:"wex-coin-add-start",children:"worth more"})]},`c-${d}`))})]},e)}const o=[];let r=0;
68for(const i of s.coins)r+=PENCE$3[i]??0,o.push(r);const a=t>=2&&s.coins.length>1;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx(CoinDefs,{}),jsxRuntimeExports.jsx("div",{className:"wex-coin-row",children:s.coins.map((i,l)=>jsxRuntimeExports.jsxs("div",{className:`wex-coin-cell${s.chosen&&!s.chosen.includes(l)?" wex-coin-faded":""}`,children:[jsxRuntimeExports.jsx("div",{className:t===2&&l===0&&s.coins.length===1?"wex-pulse":"wex-fade-enter",children:jsxRuntimeExports.jsx(Coin,{denom:i,hideValue:!1})}),a&&(l===0?jsxRuntimeExports.jsx("span",{className:"wex-coin-add",children:jsxRuntimeExports.jsx("span",{className:"wex-coin-add-start",children:"start"})}):jsxRuntimeExports.jsxs("span",{className:"wex-coin-add wex-chip-pop",style:{"--wex-i":l},"aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-coin-add-arrow",children:"↓"}),jsxRuntimeExports.jsxs("span",{className:"wex-coin-add-amt",children:["+",o[l]-o[l-1]]})]})),t>=2&&jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-pop",style:{"--wex-i":l},children:chipLabel(o[l])})]},`c-${l}`))}),t===3&&jsxRuntimeExports.jsxs("div",{className:"wex-total",children:["= ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:penceLabel$3(r)})]},`total-${e}`)]},e)}function MeasureBarsFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="measure_bars")return null;const s=withNamedBarColours(n.visual_data),o=s.bars.filter(D=>!/ruler/i.test(D.label));if(!o.length)return null;const r=s.unit??"cm";if(s.vessel)return jsxRuntimeExports.jsx(VesselFrame,{visual:s,bars:o,unit:r,stepNumber:t,replayKey:e});if(s.mark_count!=null){const D=s.mark_count,U=s.mark_value??1,q=D*U,V=r||"ml",O=24,z=244,K=z/D,Z=26,Q=16,B=Q+Z+10,G=B+40,X=D*K;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 300 ${G}`,className:"wex-mb-svg",role:"img","aria-label":"measuring jug scale",children:[jsxRuntimeExports.jsx("rect",{x:O,y:Q,width:X,height:Z,rx:5,fill:"#2563eb",fillOpacity:.85,className:t===1?"wex-mb-bar":void 0}),jsxRuntimeExports.jsx("rect",{x:O,y:B,width:z,height:24,rx:4,fill:"#FDF6E3",stroke:"#D9CDB8"}),Array.from({length:D+1},(ne,se)=>se).map(ne=>{const se=O+ne*K,te=ne*U,oe=t>=2&&ne>0;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:se,y1:B,x2:se,y2:B+14,stroke:"#8A7B63",strokeWidth:1.5}),oe&&jsxRuntimeExports.jsx("text",{x:se,y:B+24,textAnchor:"middle",className:t===2?"wex-mb-num wex-mb-num-pop":"wex-mb-num",style:{"--wex-i":ne},fontSize:9,children:te})]},ne)})]}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsxs("span",{className:"wex-total-number",children:[D," × ",U," ",V," = ",q," ",V]})},`total-${e}`)]},e)}const a=Math.max(...s.bars.map(D=>(D.start??0)+D.length),1),i=Array.from({length:a+1},(D,U)=>U),l=new Set([...o.map(D=>(D.start??0)+D.length),...o.filter(D=>(D.start??0)>0).map(D=>D.start??0)]),u=20,d=230,m=d/a,h=a<=30?1:a<=120?5:a<=1e3?10:100,f=a<=12?1:a<=24?2:a<=60?10:a<=1e3?20:500,g=D=>String(D).length*5.6,x=[1,2,5,10,20,50,100,200,500,1e3].find(D=>D>=f&&D*m>=g(a)*1.5+2)??f,y=(D,U)=>Math.abs(D-U)*m<(g(D)+g(U))/2+6,E=new Map,k=o.map(D=>D.start??0).filter(D=>D>0);for(const D of[0,...[...new Set([...k,...l])].sort((U,q)=>U-q)]){if(E.has(D))continue;const U=[...E].some(([q,V])=>V===0&&y(D,q));E.set(D,U?1:0)}for(const D of[a,...i.filter(U=>U%x===0)])E.has(D)||[...E.keys()].some(U=>y(D,U))||E.set(D,0);const T=[...E.values()].includes(1),A=o.length>1,N=t===3||s.show_values,S=22,M=8,L=12,P=L+o.length*(S+M)+2,F=P+(T?48:38);return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 300 ${F}`,className:"wex-mb-svg",role:"img","aria-label":"objects on a ruler",children:[o.map((D,U)=>{const q=L+U*(S+M),V=D.length*m,O=u+(D.start??0)*m;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:O,y:q,height:S,rx:5,width:V,fill:D.color,fillOpacity:.85,className:t===1?"wex-mb-bar":void 0,style:t===1?{"--wex-i":U}:void 0}),A&&jsxRuntimeExports.jsx("text",{x:O+6,y:q+S/2+4,fontSize:11,fontWeight:700,fill:"#fff",children:D.label}),D.emoji&&jsxRuntimeExports.jsx("text",{x:O+V-16,y:q+S/2+5,fontSize:16,children:D.emoji}),N&&jsxRuntimeExports.jsxs("text",{x:O+V+6,y:q+S/2+4,fontSize:12,fontWeight:800,fill:D.color,children:[D.length,r]})]},U)}),jsxRuntimeExports.jsx("rect",{x:u,y:P,width:d,height:28,rx:4,fill:"#FDF6E3",stroke:"#D9CDB8"}),i.map(D=>
68{if(D%h!==0&&D!==a&&!l.has(D)&&!k.includes(D))return null;const U=E.get(D),q=(t>=2||D===0)&&U!==void 0;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:u+D*m,y1:P,x2:u+D*m,y2:D%5===0?P+15:P+9,stroke:"#8A7B63",strokeWidth:1.5}),q&&jsxRuntimeExports.jsx("text",{x:u+D*m,y:P+(U===1?37:26),textAnchor:"middle",className:t===2&&D>0?"wex-mb-num wex-mb-num-pop":"wex-mb-num",style:{"--wex-i":D},fontSize:9,children:D})]},D)})]}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:s.verdict??s.conclude_label?jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:s.verdict??s.conclude_label}):s.diff!=null?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:["= ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:s.diff})," ",r]}):o.length>1?(()=>{if(s.compare==="same")return jsxRuntimeExports.jsxs("span",{className:"wex-total-number",children:["Both are ",o[0].length," ",r," — the same"]});const D=s.compare==="shorter",U=[...o].sort((K,Z)=>K.length-Z.length),q=D?U[0]:U[U.length-1],V=/^m?l$/i.test(r.trim()),O=o.length>2,z=V?D?O?"holds the least":"holds less":O?"holds the most":"holds more":D?O?"is the shortest":"is shorter":O?"is the longest":"is longer";return jsxRuntimeExports.jsxs("span",{className:"wex-total-number",children:[q.label," ",z]})})():jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:["= ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:o[0].length})," ",r]})},`total-${e}`)]},e)}function VesselFrame({visual:n,bars:t,unit:e,stepNumber:s,replayKey:o}){if(n.mark_count!=null){const T=n.mark_count,A=n.mark_value??1,N=T*A,S=e||"ml",L=T+3,P=96,F=108,D=14,U=210,q=12,V=D+U-6,O=D+q,z=(V-O)/L,K=V-T*z,Z=D+U+20,Q=`vjug-marks-${o}`;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 300 ${Z}`,className:"wex-mb-svg",role:"img","aria-label":"measuring jug scale",children:[jsxRuntimeExports.jsx("rect",{x:P,y:K,width:F,height:V-K,fill:"#2563eb",fillOpacity:.82,clipPath:`url(#${Q})`,className:s===1?"wex-vessel-fill":void 0}),Array.from({length:L+1},(B,G)=>G).map(B=>{const G=V-B*z,X=s>=2&&B>0&&B<=T;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:P,y1:G,x2:P+15,y2:G,stroke:"#64748b",strokeWidth:1.6}),X&&jsxRuntimeExports.jsx("text",{x:P-7,y:G+4,textAnchor:"end",className:s===2?"wex-mb-num wex-mb-num-pop":"wex-mb-num",style:{"--wex-i":B},fontSize:11,children:B*A})]},B)}),jsxRuntimeExports.jsx(Beaker,{x:P,y:D,w:F,h:U,clipId:Q})]}),s===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsxs("span",{className:"wex-total-number",children:[T," × ",A," ",S," = ",N," ",S]})},`total-${o}`)]},o)}const r=n.capacity??Math.max(...t.map(T=>(T.start??0)+T.length),1),a=/^m?l$/i.test(e.trim());if(t.length>1&&t.every(T=>!(T.start??0))){const P=[40,168],F=236,D=[...t].sort((U,q)=>q.length-U.length)[0];return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx("svg",{viewBox:`0 0 300 ${F}`,className:"wex-mb-svg",role:"img","aria-label":"two measuring jugs",children:t.map((U,q)=>{const V=P[q]??40+q*128,O=`vjug-cmp-${q}-${o}`,K=196-172*Math.min(1,U.length/r);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:V,y:K,width:92,height:196-K,fill:U.color,fillOpacity:.82,clipPath:`url(#${O})`,className:s===1?"wex-vessel-fill":void 0}),jsxRuntimeExports.jsx(Beaker,{x:V,y:14,w:92,h:188,clipId:O}),jsxRuntimeExports.jsx("text",{x:V+92/2,y:226,textAnchor:"middle",fontSize:14,fontWeight:800,fill:U.color,children:U.label})]},q)})}),s===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:n.conclude_label??`${D.label} ${a?"holds more":"is more"}`})},`total-${o}`)]},o)}const l=96,u=108,d=210,m=14,h=12,f=m+d-6,g=m+h,b=m+d+24,x=`vjug-one-${o}`,y=T=>f-T/r*(f-g),E=r<=1e3?250:r<=2e3?500:1e3,k=Array.from({length:Math.floor(r/E)+1},(T,A)=>A*E);return jsxRuntimeExports.jsx("div",{className:"wex-frame-center",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 3
6800 ${b}`,className:"wex-mb-svg",role:"img","aria-label":"measuring jug",children:[t.map((T,A)=>{const N=T.start??0,S=N>0,M=y(N+T.length),L=y(N);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{x:l,y:M,width:u,height:L-M,fill:T.color,fillOpacity:S?.5:.82,clipPath:`url(#${x})`,className:s===1&&!S?"wex-vessel-fill":void 0}),jsxRuntimeExports.jsx("text",{x:l+u/2,y:(M+L)/2+5,textAnchor:"middle",fontSize:14,fontWeight:800,fill:S?"#b45309":"#fff",children:T.label})]},A)}),k.map(T=>{const A=y(T);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:l,y1:A,x2:l+15,y2:A,stroke:"#64748b",strokeWidth:1.4}),jsxRuntimeExports.jsx("text",{x:l-7,y:A+4,textAnchor:"end",className:"wex-mb-num",fontSize:10,children:T})]},T)}),jsxRuntimeExports.jsx(Beaker,{x:l,y:m,w:u,h:d,clipId:x})]})},o)}function ClockFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="clock")return null;const s=n.visual_data,{hour:o,minute:r}=s,a=n.animation.includes("minute"),i=n.animation.includes("hour"),l=n.animation.includes("advance"),u=n.animation.includes("advance_back"),d=n.animation.includes("sweep"),m=s.sweep_start??0,h=s.counter_unit??"min",[f,g]=React.useState(m);React.useEffect(()=>{if(!d)return;const U=m+60;if(typeof window<"u"&&typeof window.matchMedia=="function"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches){g(U);return}const V=1500;g(m);let O=0;const z=typeof performance<"u"?performance.now():Date.now(),K=Z=>{const Q=Math.min(1,(Z-z)/V);g(Math.round(m+Q*60)),Q<1&&(O=requestAnimationFrame(K))};return O=requestAnimationFrame(K),()=>cancelAnimationFrame(O)},[d,m,e,t]);const b=90,x=90,y=78,E=r/60*360,k=o%12/12*360+r/60*30,T=(U,q)=>{const V=(U-90)*Math.PI/180;return[b+q*Math.cos(V),x+q*Math.sin(V)]},[A,N]=T(E,y-22),[S,M]=T(k,y-40),L=r===0?`${o} o'clock`:r===15?`quarter past ${o}`:r===30?`half past ${o}`:r===45?`quarter to ${o%12+1}`:`${o}:${String(r).padStart(2,"0")}`,P=s.conclude_label??L,F=jsxRuntimeExports.jsx("line",{x1:b,y1:x,x2:S,y2:M,className:`wex-clock-hand${i&&!d?" lit":""}`,strokeWidth:7}),D=jsxRuntimeExports.jsx("line",{x1:b,y1:x,x2:A,y2:N,className:`wex-clock-hand${a?" lit":""}`,strokeWidth:5});return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 180 180",className:"wex-clock-svg",role:"img","aria-label":`clock showing ${L}`,children:[jsxRuntimeExports.jsx("circle",{cx:b,cy:x,r:y,fill:"#FFFDF7",stroke:"#3B3325",strokeWidth:5}),s.shade_to_minute!=null&&(()=>{const U=s.shade_from_minute??0,q=s.shade_to_minute,[V,O]=T(U/60*360,y-3),[z,K]=T(q/60*360,y-3),Z=q-U>30?1:0;return jsxRuntimeExports.jsx("path",{className:"wex-clock-shade",d:`M ${b} ${x} L ${V} ${O} A ${y-3} ${y-3} 0 ${Z} 1 ${z} ${K} Z`,fill:"#fde68a"})})(),Array.from({length:12},(U,q)=>{const V=q+1,[O,z]=T(V*30,y-13);return jsxRuntimeExports.jsx("text",{x:O,y:z+4.5,textAnchor:"middle",className:"wex-clock-num",children:V},V)}),d?jsxRuntimeExports.jsx("g",{className:"wex-clock-minute-sweep",children:D},`sweep-${e}`):D,l?jsxRuntimeExports.jsx("g",{className:u?"wex-clock-advance-back":"wex-clock-advance",children:F},`adv-${e}`):F,jsxRuntimeExports.jsx("circle",{cx:b,cy:x,r:6,fill:"#3B3325"})]}),d&&jsxRuntimeExports.jsxs("div",{className:"wex-clock-counter","aria-hidden":"true",children:[f," ",h]},`ctr-${e}`),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:P})},`total-${e}`)]},e)}function ArrayFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="array")return null;const s=n.visual_data,{rows:o,cols:r,emoji:a}=s;if(n.animation.startsWith("prime_")&&s.outcome){const x=s.n??o*r+(s.leftover??0),y=s.leftover??0,E=(T,A=!1)=>a?jsxRuntimeExports.jsx("span",{className:"wex-arr-emoji","aria-hidden":"true",children:a},T):jsxRuntimeExports.jsx("span",{className:`wex-arr-dot${A?" wex-arr-dot-lit":""}`,"aria-hidden":"true"},T);if(t===1)return jsxRuntimeExports.jsx("div",{className:"wex-arr-wrap",children:jsxRuntimeExports.jsx("div",{className:"wex-arr-blob",children:Array.from({length:x},(T,A)=>jsxRuntimeExports.jsx("span",{className:`${a?"wex-arr-emoji":"wex-arr-dot"} wex-arr-enter`,style:{"--arr-i":A},"aria-hidden":"true",children:a??""},A))})},e);if(t===2)return jsxRuntimeExports.jsxs("div",{className:"wex-arr-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-arr-grid wex-arr-grouped",children:Array.from({length:o},(T,A)=>jsxRuntimeExports.jsx("div",{className:"wex-arr-row wex-arr-group",children:jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:r},(N,S)=>E(`${A}-${S}`))})},A))}),y>0&&jsxRuntimeExports.jsxs("div",{className:"wex-arr-tray",children:[jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:y},(T,A)=>E(`lo-${A}`,!0))}),jsxRuntimeExports.jsx("span",{className:"wex-arr-tray-label","aria-hidden":"true",children:"left over"})]})]},e);const k=s.outcome==="prime"?jsxRuntimeExports.jsx("div",{className:"wex-arr-grid",children:jsxRuntimeExports.jsx("div",{className:"wex-arr-row wex-arr-lit",children:jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:x},(T,A)=>E(A))})})}):jsxRuntimeExports.jsx("div",{className:"wex-arr-grid wex-arr-grouped",children:Array.from({length:o},(T,A)=>jsxRuntimeExports.jsx("div",{className:"wex-arr-row wex-arr-group wex-arr-lit",children:jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:r},(N,S)=>E(`${A}-${S}`))})},A))});return jsxRuntimeExports.jsxs("div",{className:"wex-arr-wrap",children:[k,s.conclude_label&&jsxRuntimeExports.jsx("div",{className:"wex-arr-total","aria-hidden":"true",children:s.conclude_label})]},e)}const i=/^(quarter|half|third|share|divide)/.test(n.animation),l=n.animation.startsWith("divide"),u=n.animation.startsWith("divide_fit"),d=n.animation.startsWith("commute"),m=n.animation==="array_count_all",h=n.animation.startsWith("group_count"),f={2:"½",3:"⅓",4:"¼"},g=o*r,b=r;if(d&&t===3){const x=(y,E)=>jsxRuntimeExports.jsx("div",{className:"wex-arr-grid",children:Array.from({length:y},(k,T)=>jsxRuntimeExports.jsx("div",{className:"wex-arr-row wex-arr-lit",children:jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:E},(A,N)=>a?jsxRuntimeExports.jsx("span",{className:"wex-arr-emoji","aria-hidden":"true",children:a},N):jsxRuntimeExports.jsx("span",{className:"wex-arr-dot","aria-hidden":"true"},N))})},T))});return jsxRuntimeExports.jsxs("div",{className:"wex-arr-wrap",children:[jsxRuntimeExports.jsxs("div",{className:"wex-arr-commute",children:[jsxRuntimeExports.jsxs("div",{className:"wex-arr-commute-one",children:[x(o,r),jsxRuntimeExports.jsxs("span",{className:"wex-arr-commute-label",children:[o," × ",r]})]}),jsxRuntimeExports.jsx("span",{className:"wex-arr-commute-eq",children:"="}),jsxRuntimeExports.jsxs("div",{className:"wex-arr-commute-one",children:[x(r,o),jsxRuntimeExports.jsxs("span",{className:"wex-arr-commute-label",children:[r," × ",o]})]})]}),jsxRuntimeExports.jsxs("div",{className:"wex-arr-total","aria-hidden":"true",children:[o," × ",r," = ",r," × ",o," = ",g]}
68)]},e)}return jsxRuntimeExports.jsxs("div",{className:"wex-arr-wrap",children:[jsxRuntimeExports.jsx("div",{className:`wex-arr-grid${(i||h)&&t>=2?" wex-arr-grouped":""}`,children:Array.from({length:o},(x,y)=>{const E=m||!i&&(t===3||t===2&&y<o-1),k=m||!i&&t>=2&&(t===3||y<o-1),T=m||t!==3||y===o-1,A=!m&&t===3?0:y,N=i&&y===0,S=i&&t===3&&N,M=i&&t===3&&!N,L=["wex-arr-row",(i||h)&&t>=2?"wex-arr-group":"",k||S?"wex-arr-lit":"",M?"wex-arr-dim":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsxs("div",{className:L,children:[jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:r},(P,F)=>{const D=y*r+F,U=t===1,q=a?"wex-arr-emoji":"wex-arr-dot";return jsxRuntimeExports.jsx("span",{className:`${q}${U?" wex-arr-enter":""}`,style:U?{"--arr-i":D}:void 0,"aria-hidden":"true",children:a??""},D)})}),E&&jsxRuntimeExports.jsx("span",{className:`wex-chip wex-arr-chip${T?" wex-chip-pop":""}`,style:T?{"--wex-i":A}:void 0,"aria-hidden":"true",children:(y+1)*b},`chip-${y}-${t}`)]},y)})}),t===3&&!m&&jsxRuntimeExports.jsx("div",{className:"wex-arr-total","aria-hidden":"true",children:h?`${g} ÷ ${b} = ${o}`:!i&&!d&&s.conclude_label?s.conclude_label:i?l?u?`${g} ÷ ${b} = ${o}`:`${g} ÷ ${o} = ${b}`:`${f[o]??`1/${o}`} of ${g} = ${b}`:d?`${o} × ${r} = ${r} × ${o} = ${g}`:`${o} × ${r} = ${g}`})]},e)}function PictogramFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="pictogram")return null;const{rows:s,emoji:o,per_symbol:r,key_label:a,count_index:i,count_indices:l,total_label:u}=n.visual_data,d=displayEmoji(o),m=pictogramRowColours(s),h=a?displayEmojiInText(a):`each ${d} = ${r}`,f=m?h.split(d).join(PICTOGRAM_COUNTER):h,g=N=>m?jsxRuntimeExports.jsx("span",{className:"wex-pic-dot",style:{"--pic-dot":m[N]}}):d,b=l&&l.length>0?new Set(l):null,x=i??0,y=N=>b?b.has(N):N===x,E=s[x],k=(E?.symbols??0)*r,T=b!=null||i!=null||t===3,A=b?s.filter((N,S)=>b.has(S)):[];return jsxRuntimeExports.jsxs("div",{className:"wex-pic-wrap",children:[jsxRuntimeExports.jsx("div",{className:`wex-pic-key${t===1?" wex-pic-key-lit":""}`,children:jsxRuntimeExports.jsxs("span",{className:"wex-pic-key-text",children:["Key: ",f]})}),jsxRuntimeExports.jsx("div",{className:"wex-pic-rows",children:s.map((N,S)=>{const M=y(S),L=N.symbols*r,P=b?s.slice(0,S).filter((O,z)=>b.has(z)).reduce((O,z)=>O+Math.ceil(z.symbols),0):0,F=t>=2&&M,D=t>=2&&!M,U=["wex-pic-row",F?"wex-pic-lit":"",D?"wex-pic-dim":""].filter(Boolean).join(" "),q=Math.floor(N.symbols),V=N.symbols-q>=.5;return jsxRuntimeExports.jsxs("div",{className:U,children:[jsxRuntimeExports.jsx("span",{className:"wex-pic-label",children:N.label}),jsxRuntimeExports.jsxs("div",{className:"wex-pic-symbols",children:[Array.from({length:q},(O,z)=>jsxRuntimeExports.jsxs("span",{className:"wex-pic-symcol",children:[jsxRuntimeExports.jsx("span",{className:"wex-pic-sym","aria-hidden":"true",children:g(S)}),M&&T&&jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-pop wex-pic-chip",style:{"--wex-i":z+P},"aria-hidden":"true",children:(z+1)*r})]},z)),V&&jsxRuntimeExports.jsxs("span",{className:"wex-pic-symcol",children:[jsxRuntimeExports.jsx("span",{className:"wex-pic-sym wex-pic-sym-half","aria-hidden":"true",children:g(S)}),M&&T&&jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-pop wex-pic-chip wex-pic-chip-half",style:{"--wex-i":q+P},"aria-hidden":"true",children:L})]})]})]},S)})}),b?jsxRuntimeExports.jsx("div",{className:"wex-pic-total","aria-hidden":"true",children:u??A.map(N=>`${N.label}: ${N.symbols*r}`).join(" · ")}):T&&E&&jsxRuntimeExports.jsxs("div",{className:"wex-pic-total","aria-hidden":"true",children:[E.label,": ",k]})]},e)}function levelStartScales(n,t){if(!n||n.length!==t.length||t.length===0)return null;const e=t[0];return!(e>0)||t.some(s=>s!==e)||n.every((s,o)=>s===t[o])?null:n.map(s=>s/e)}const PLOT_H$1=150,SLOT$2=58,BAR_W$1=34,PAD_L$1=34,PAD_T$2=14;function BarChartFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="bar_chart")return null;const{bars:s,axis_max:o,axis_step:r,read_index:a,y_label:i,hide_diff:l,lit_indices:u}=n.visual_data,d=reactExports.useRef({});t===1&&(d.current={});const m=s.map(z=>z.value);d.current[t]=m;const h=t>1?d.current[t-1]:void 0,f=levelStartScales(h,m),g=f!=null,b=Math.max(...s.map(z=>z.value),1),x=Math.max(o??0,b),y=r??(x<=10?1:2),E=Math.ceil(x/y)*y,k=Array.from({length:E/y+1},(z,K)=>K*y),A=y>1&&E<=40?Array.from({length:E+1},(z,K)=>K).filter(z=>z%y!==0):[],N=s.length*SLOT$2,S=PAD_T$2+PLOT_H$1,M=PAD_L$1+N+14,L=s.some(z=>z.label.length>6&&z.label.includes(" "))?S+32:S+26,P=z=>S-z/E*PLOT_H$1,F=g?P(m[0]):null,D=a!=null&&s[a]!=null,U=s[0],q=s[1],V=U&&q?Math.abs(U.value-q.value):0,O=z=>u?u.includes(z):D?z===a:z<=1;return jsxRuntimeExports.jsxs("div",{className:"wex-bc-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${M} ${L}`,className:"wex-bc-svg","aria-hidden":"true",children:[A.map(z=>jsxRuntimeExports.jsx("line",{x1:PAD_L$1,y1:P(z),x2:PAD_L$1+N,y2:P(z),stroke:"#eef0f3",strokeWidth:1},`minor-${z}`)),k.map(z=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:PAD_L$1,y1:P(z),x2:PAD_L$1+N,y2:P(z),stroke:"#e2e8f0",strokeWidth:1}),jsxRuntimeExports.jsx("text",{x:PAD_L$1-8,y:P(z)+4,textAnchor:"end",fontSize:11,fontWeight:700,fill:"#475569",children:z})]},z)),s.map((z,K)=>
68{const Z=O(K)&&(u!=null||t>=2),Q=PAD_L$1+K*SLOT$2+(SLOT$2-BAR_W$1)/2,B=P(z.value),G=S-B,X=colourForLabel(z.label);return jsxRuntimeExports.jsxs("g",{children:[Z&&D&&jsxRuntimeExports.jsx("line",{x1:PAD_L$1,y1:B,x2:Q+BAR_W$1,y2:B,stroke:"#b45309",strokeWidth:1.5,strokeDasharray:"4,3"}),jsxRuntimeExports.jsx("rect",{x:Q,y:B,width:BAR_W$1,height:G,rx:4,className:t===1?"wex-bc-bar":g?"wex-bc-level":void 0,style:t===1?{"--bc-i":K,transformOrigin:`${Q}px ${S}px`}:g?{"--bc-from":f[K],transformOrigin:`${Q}px ${S}px`}:void 0,fill:X??(Z?"#fbbf24":"#3b82f6"),stroke:Z?"#b45309":"#1e3a8a",strokeWidth:X&&Z?4:1.5}),Z&&jsxRuntimeExports.jsx("text",{x:Q+BAR_W$1/2,y:B-5,textAnchor:"middle",fontSize:13,fontWeight:800,fill:"#b45309",className:g?"wex-bc-level-num":void 0,children:z.value}),!z.label.includes(" ")&&z.label.length>7?jsxRuntimeExports.jsx("text",{x:Q+BAR_W$1/2,y:S+16,textAnchor:"middle",fontSize:z.label.length>10?8:9,fontWeight:700,fill:"#334155",children:z.label}):z.label.length>6&&z.label.includes(" ")?jsxRuntimeExports.jsxs("text",{x:Q+BAR_W$1/2,y:S+14,textAnchor:"middle",fontSize:11,fontWeight:700,fill:"#334155",children:[jsxRuntimeExports.jsx("tspan",{x:Q+BAR_W$1/2,dy:0,children:z.label.slice(0,z.label.indexOf(" "))}),jsxRuntimeExports.jsx("tspan",{x:Q+BAR_W$1/2,dy:12,children:z.label.slice(z.label.indexOf(" ")+1)})]}):jsxRuntimeExports.jsx("text",{x:Q+BAR_W$1/2,y:S+16,textAnchor:"middle",fontSize:12,fontWeight:700,fill:"#334155",children:z.label})]},K)}),F!=null&&jsxRuntimeExports.jsx("line",{x1:PAD_L$1,y1:F,x2:PAD_L$1+N,y2:F,stroke:"#b45309",strokeWidth:2,strokeDasharray:"6,4",className:"wex-bc-levelline"}),jsxRuntimeExports.jsx("line",{x1:PAD_L$1,y1:PAD_T$2,x2:PAD_L$1,y2:S,stroke:"#334155",strokeWidth:2}),jsxRuntimeExports.jsx("line",{x1:PAD_L$1,y1:S,x2:PAD_L$1+N,y2:S,stroke:"#334155",strokeWidth:2})]}),!D&&!l&&t===3&&U&&q&&jsxRuntimeExports.jsxs("div",{className:"wex-bc-total","aria-hidden":"true",children:[Math.max(U.value,q.value)," − ",Math.min(U.value,q.value)," = ",V]})]},e)}const CX$3=50,CY$3=52,R$3=42,START_ANGLE$1=135,SWEEP$2=270;function pointAt$1(n,t){const e=(START_ANGLE$1+n*SWEEP$2)*(Math.PI/180);return{x:CX$3+t*Math.cos(e),y:CY$3+t*Math.sin(e)}}function MassScaleFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="mass_scale")return null;const{reading:s,max:o,unit:r,interval:a}=n.visual_data,i=o>0?o:1,l=a&&a>0?a:Math.max(1,Math.round(i/5)),u=Math.max(1,Math.round(i/l)),d=Array.from({length:u+1},(P,F)=>Math.min(F*l,i)),m=u+1>8?2:1,h=P=>P%m===0||P===u,g=Math.max(0,Math.min(1,s/i))*SWEEP$2,b=pointAt$1(0,R$3-20),x=/bracket|mark_value|count_on/.test(n.animation),y=/mark_value|count_on/.test(n.animation);let E=-1,k=-1;x&&d.forEach((P,F)=>{h(F)&&(P<s&&(E=F),P>s&&k===-1&&(k=F))});const T=x&&E>=0&&k>E,A=P=>T&&P>E&&P<k,N=/count_on/.test(n.animation),S=P=>T&&(P===E||!N&&P===k);let M=null;if(T&&y){const P=d[E]/i,F=(N?s:d[k])/i,D=pointAt$1(P,R$3-3.5),U=pointAt$1(F,R$3-3.5),q=(F-P)*SWEEP$2>180?1:0;M=`M ${D.x.toFixed(2)} ${D.y.toFixed(2)} A ${R$3-3.5} ${R$3-3.5} 0 ${q} 1 ${U.x.toFixed(2)} ${U.y.toFixed(2)}`}const L=`wex-mass-needle${t===2&&!y?" wex-mass-sweep":""}`;return jsxRuntimeExports.jsxs("div",{className:"wex-mass-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 100 100",className:"wex-mass-svg","aria-hidden":"true",children:[jsxRuntimeExports.jsx("circle",{cx:CX$3,cy:CY$3,r:R$3+4,fill:"#fff",stroke:"#334155",strokeWidth:2.5}),jsxRuntimeExports.jsx("circle",{cx:CX$3,cy:CY$3,r:R$3,fill:"#f8fafc",stroke:"#cbd5e1",strokeWidth:1}),jsxRuntimeExports.jsx("line",{x1:CX$3,y1:CY$3,x2:b.x,y2:b.y,className:L,style:{"--mass-rot":`${g}deg`},stroke:"#1e293b",strokeWidth:2.6,strokeLinecap:"round"}),M&&jsxRuntimeExports.jsx("path",{d:M,pathLength:1,className:"wex-mass-arc",fill:"none",stroke:"#fbbf24",strokeWidth:3,strokeLinecap:"round"}),d.map((P,F)=>{const D=P/i,U=pointAt$1(D,R$3),q=pointAt$1(D,R$3-7),V=pointAt$1(D,R$3-14),O=t===3&&P===s,z=O&&h(F),K=S(F),Z=y&&!N&&A(F),Q=O||K||Z;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:U.x,y1:U.y,x2:q.x,y2:q.y,stroke:Q?"#d97706":"#334155",strokeWidth:O||Z?3:K?2.4:1.8,strokeLinecap:"round"}),h(F)&&jsxRuntimeExports.jsx("text",{x:V.x,y:V.y,textAnchor:"middle",dominantBaseline:"central",fontSize:z?9.5:7.5,fontWeight:700,fill:z||K?"#b45309":"#1e293b",stroke:"#f8fafc",strokeWidth:2.5,paintOrder:"stroke",className:z?"wex-mass-num-lit":void 0,children:P})]},P)}),jsxRuntimeExports.jsx("text",{x:CX$3,y:CY$3+11,textAnchor:"middle",dominantBaseline:"central",fontSize:8,fontWeight:700,fill:"#2563eb",children:r}),jsxRuntimeExports.jsx("circle",{cx:CX$3,cy:CY$3,r:4,fill:"#1e293b",stroke:"#fff",strokeWidth:1.2}),jsxRuntimeExports.jsx("circle",{cx:CX$3,cy:CY$3,r:1.4,fill:"#fff"})]}),T&&y&&t===2&&jsxRuntimeExports.jsxs("div",{className:"wex-mass-total","aria-hidden":"true",children:["Each mark = ",l," ",r]}),t===3&&jsxRuntimeExports.jsxs("div",{className:"wex-mass-total","aria-hidden":"true",children:[T&&y?`${d[E]}, ${s} → `:"",s," ",r]})]},e)}const EDGE$2={stroke:"#1e293b",strokeWidth:2.5,strokeLinejoin:"round",strokeLinecap:"round"},HIDDEN={stroke:"#1e293b",strokeWidth:1.4,strokeDasharray:"4 4",strokeLinecap:"round",fill:"none"},LIT="#fde68a",BADGE="#b45309",FACE_COUNT$1={cube:6,cuboid:6,sphere:1,cylinder:3,cone:2,pyramid:5},BOX={cube:{faces:[{points:"22,40 62,40 62,78 22,78",cx:42,cy:59,op:.35},{points:"22,40 62,40 78,24 38,24",cx:50,cy:32,op:.55},{points:"62,40 78,24 78,62 62,78",cx:70,cy:51,op:.85}],hiddenPolyline:"22,78 22,40 38,24",hiddenLine:[22,78,62,78],backFaces:[{points:"38,24 78,24 78,62 38,62",cx:58,cy:43},{points:"22,78 62,78 78,62 38,62",cx:50,cy:70},{points:"22,40 38,24 38,62 22,78",cx:30,cy:51}],nearEdges:[[62,40,22,40],[62,40,78,24],[62,40,62,78]]},cuboid:{faces:[{points:"14,46 70,46 70,72 14,72",cx:42,cy:59,op:.35},{points:"14,46 70,46 86,30 30,30",cx:50,cy:38,op:.55},{points:"70,46 86,30 86,56 70,72",cx:78,cy:51,op:.85}],hiddenPolyline:"14,72 14,46 30,30",hiddenLine:[14,72,70,72],backFaces:[{points:"30,30 86,30 86,56 30,56",cx:60,cy:42},{points:"14,72 70,72 86,56 30,56",cx:50,cy:65},{points:"14,46 30,30 30,56 14,72",cx:22,cy:51}],nearEdges:[[70,46,14,46],[70,46,86,30],[70,46,70,72]]}},PYRAMID_BASE="38,38 62,38 62,62 38,62",PYRAMID_TRIS=[{points:"38,38 62,38 50,12",cx:50,cy:30,ox:50,oy:38,axis:"y"},{points:"62,38 62,62 88,50",cx:70,cy:50,ox:62,oy:50,axis:"x"},{points:"38,62 62,62 50,88",cx:50,cy:70,ox:50,oy:62,axis:"y"},{points:"38,38 38,62 12,50",cx:30,cy:50,ox:38,oy:50,axis:"x"}];function SolidPyramid({fill:n}){const t="50,16",e="40,82";return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("polyline",{points:"16,64 60,52 84,64",...HIDDEN}),jsxRuntimeExports.jsx("line",{x1:"60",y1:"52",x2:"50",y2:"16",...HIDDEN}),jsxRuntimeExports.jsx("polygon",{points:`${t} ${e} 84,64`,fill:n,fillOpacity:.8,...EDGE$2}),jsxRuntimeExports.jsx("polygon",{points:`${t} ${e} 16,64`,fill:n,fillOpacity:.45,...EDGE$2})]})}function PyramidNet({fill:n,stepNumber:t,subset:e}){const s=t===2,o=t>=2&&e!=="triangles",r=t>=2&&e!=="base";return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("polygon",{points:PYRAMID_BASE,fill:o?LIT:n,fillOpacity:o?.9:e==="triangles"?.25:.6,...EDGE$2}),PYRAMID_TRIS.map((a,i)=>jsxRuntimeExports.jsx("polygon",{points:a.points,fill:r?LIT:n,fillOpacity:r?.9:e==="base"?.25:.45,className:s?`wex-s3d-fold wex-s3d-fold-${a.axis}`:void 0,style:s?{transformOrigin:`${a.ox}px ${a.oy}px`,"--s3d-i":i}:void 0,...EDGE$2},i)),t>=2&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[o&&jsxRuntimeExports.jsx(Badge,{cx:50,cy:50,n:1,i:0,late:s}),r&&PYRAMID_TRIS.map((a,i)=>jsxRuntimeExports.jsx(Badge,{cx:a.cx,cy:a.cy,n:e==="triangles"?i+1:i+2,i:i+1,late:s},`t-${i}`))]})]})}function BoxSolid({geom:n,fill:t,stepNumber:e}){const s=e>=2,o=e===3;return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsxs("g",{className:o?"wex-s3d-turn-away":void 0,children:[jsxRuntimeExports.jsx("polyline",{points:n.hiddenPolyline,...HIDDEN}),jsxRuntimeExports.jsx("line",{x1:n.hiddenLine[0],y1:n.hiddenLine[1],x2:n.hiddenLine[2],y2:n.hiddenLine[3],...HIDDEN}),n.faces.map((r,a)=>jsxRuntimeExports.jsx("polygon",{points:r.points,fill:s?LIT:t,fillOpacity:s?.9:r.op,...EDGE$2},a))]}),o&&jsxRuntimeExports.jsxs("g",{className:"wex-s3d-turn-in",children:[n.nearEdges.map(([r,a,i,l],u)=>jsxRuntimeExports.jsx("line",{x1:r,y1:a,x2:i,y2:l,...HIDDEN},`n-${u}`)),n.backFaces.map((r,a)=>jsxRuntimeExports.jsx("polygon",{points:r.points,fill:LIT,fillOpacity:.9,...EDGE$2},`bf-${a}`))]}),s&&!o&&n.faces.map((r,a)=>jsxRuntimeExports.jsx(Badge,{cx:r.cx,cy:r.cy,n:a+1,i:a},`b-${a}`)),o&&n.backFaces.map((r,a)=>jsxRuntimeExports.jsx(Badge,{cx:r.cx,cy:r.cy,n:a+4,i:a,late:!0},`bb-${a}`)),o&&jsxRuntimeExports.jsxs("g",{className:"wex-s3d-turn-cue","aria-hidden":"true",children:[jsxRuntimeExports.jsx("path",{d:"M 32 13 A 20 20 0 0 1 68 13",fill:"none",stroke:BADGE,strokeWidth:2,strokeLinecap:"round"}),jsxRuntimeExports.jsx("path",{d:"M 68 13 l -6 -3 m 6 3 l -3 6",fill:"none",stroke:BADGE,strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"})]})]})}
68function Badge({cx:n,cy:t,n:e,i:s,late:o}){return jsxRuntimeExports.jsxs("g",{className:o?"wex-s3d-badge wex-s3d-badge-late":"wex-s3d-badge",style:{"--s3d-i":s},children:[jsxRuntimeExports.jsx("circle",{cx:n,cy:t,r:7,fill:"#fff",stroke:BADGE,strokeWidth:1.8}),jsxRuntimeExports.jsx("text",{x:n,y:t,textAnchor:"middle",dominantBaseline:"central",fontSize:9,fontWeight:800,fill:BADGE,children:e})]})}function Shape3DFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="shape_3d")return null;const{shape:s,color:o,total_label:r,count_subset:a}=n.visual_data,i=o??"#3b82f6",l=FACE_COUNT$1[s],u=s==="cube"||s==="cuboid";return jsxRuntimeExports.jsxs("div",{className:"wex-s3d-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 100 100",className:"wex-s3d-svg","aria-hidden":"true",children:[u&&jsxRuntimeExports.jsx(BoxSolid,{geom:BOX[s],fill:i,stepNumber:t}),s==="pyramid"&&(t===1?jsxRuntimeExports.jsx(SolidPyramid,{fill:i}):jsxRuntimeExports.jsx(PyramidNet,{fill:i,stepNumber:t,subset:a})),!u&&s!=="pyramid"&&jsxRuntimeExports.jsx(Solid,{shape:s,color:i}),t>=2&&r!==void 0&&s==="cone"&&jsxRuntimeExports.jsx("ellipse",{className:"wex-s3d-flat",cx:"50",cy:"74",rx:"32",ry:"10",fill:LIT,fillOpacity:.85,stroke:BADGE,strokeWidth:1.6}),t>=2&&r!==void 0&&s==="cylinder"&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("ellipse",{className:"wex-s3d-flat",cx:"50",cy:"28",rx:"30",ry:"10",fill:LIT,fillOpacity:.85,stroke:BADGE,strokeWidth:1.6}),jsxRuntimeExports.jsx("ellipse",{className:"wex-s3d-flat",style:{"--s3d-i":1},cx:"50",cy:"72",rx:"30",ry:"10",fill:LIT,fillOpacity:.6,stroke:BADGE,strokeWidth:1.6})]}),t>=2&&r!==void 0&&s==="sphere"&&jsxRuntimeExports.jsx("circle",{className:"wex-s3d-curve-cue",cx:"50",cy:"50",r:"38",fill:"none",stroke:BADGE,strokeWidth:2.2,strokeDasharray:"6 7",strokeLinecap:"round"})]}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-s3d-total","aria-hidden":"true",children:r??`${l} faces`})]},e)}const CX$2=60,CY$2=60,R$2=40,TURN_DEG$1={quarter:90,half:180,three_quarter:270,full:360},START_BEARING$2={up:0,right:90,down:180,left:270},TURN_WORD={quarter:"Quarter turn",half:"Half turn",three_quarter:"Three-quarter turn",full:"Full turn"},BEARING_WORD$1={0:"Up",90:"Right",180:"Down",270:"Left"},DIAL_LABELS=[{text:"Up",x:CX$2,y:11,anchor:"middle"},{text:"Right",x:118,y:CY$2-7,anchor:"end"},{text:"Down",x:CX$2,y:116,anchor:"middle"},{text:"Left",x:2,y:CY$2-7,anchor:"start"}];function TurnDiagramFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="turn")return null;const{turn:s,direction:o,showDegreeTotal:r,compare_turn:a}=n.visual_data,i=n.visual_data.start??"up",l=START_BEARING$2[i],u=(o==="clockwise"?1:-1)*TURN_DEG$1[s],d=l+u,m=/direction/.test(n.animation),h=/compare/.test(n.animation),f=BEARING_WORD$1[(d%360+360)%360],g=(P,F)=>({x:CX$2+F*Math.sin(P*Math.PI/180),y:CY$2-F*Math.cos(P*Math.PI/180)}),b=g(l,R$2),x=g(d,R$2),y=Math.abs(u)>180?1:0,E=u>0?1:0,k=`M ${CX$2} ${CY$2} L ${b.x.toFixed(2)} ${b.y.toFixed(2)} A ${R$2} ${R$2} 0 ${y} ${E} ${x.x.toFixed(2)} ${x.y.toFixed(2)} Z`,T={x:CX$2,y:CY$2-R$2},N=`wex-turn-arm${!a&&(m||h||t===2)?" wex-turn-spin":""}`,S=m||h||t>=2?d:l,M=jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 120 120",className:"wex-turn-svg","aria-hidden":"true",children:[jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:R$2,fill:"#f1f5f9",stroke:"#cbd5e1",strokeWidth:1}),m&&DIAL_LABELS.map(P=>{const F=P.text===f;return jsxRuntimeExports.jsx("text",{x:P.x,y:P.y,textAnchor:P.anchor,fontSize:9,fontWeight:F?800:600,fill:F?"#b45309":"#64748b",children:P.text},P.text)}),(m||h||t===3)&&(Math.abs(u)===360?jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:R$2,fill:"#fde68a",fillOpacity:.7}):jsxRuntimeExports.jsx("path",{d:k,fill:"#fde68a",fillOpacity:.7})),jsxRuntimeExports.jsx("line",{x1:CX$2,y1:CY$2-R$2,x2:CX$2,y2:CY$2+R$2,stroke:"#cbd5e1",strokeWidth:1,strokeDasharray:"3 3"}),jsxRuntimeExports.jsx("line",{x1:CX$2-R$2,y1:CY$2,x2:CX$2+R$2,y2:CY$2,stroke:"#cbd5e1",strokeWidth:1,strokeDasharray:"3 3"}),(m||h||t>=2)&&jsxRuntimeExports.jsx("line",{x1:CX$2,y1:CY$2,x2:T.x,y2:T.y,stroke:"#94a3b8",strokeWidth:4,strokeLinecap:"round",strokeDasharray:"5 4",style:{transformBox:"view-box",transformOrigin:`${CX$2}px ${CY$2}px`,transform:`rotate(${l}deg)`}}),jsxRuntimeExports.jsx("line",{x1:CX$2,y1:CY$2,x2:T.x,y2:T.y,className:N,style:{"--turn-from":`${l}deg`,"--turn-to":`${d}deg`,transform:`rotate(${S}deg)`},stroke:"#1e3a8a",strokeWidth:6,strokeLinecap:"round"}),jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:5,fill:"#1e293b"}),jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:2.5,fill:"#fff"})]}),L=(m||h||t===3)&&jsxRuntimeExports.jsx("div",{className:"wex-turn-total","aria-hidden":"true",children:m?f:`${TURN_WORD[s]}${r?` = ${TURN_DEG$1[s]}°`:""}`});return a?jsxRuntimeExports.jsx("div",{className:"wex-turn-wrap",children:jsxRuntimeExports.jsxs("div",{className:"wex-turn-pair",children:[jsxRuntimeExports.jsxs("div",{className:"wex-turn-col wex-turn-side",children:[jsxRuntimeExports.jsx(StaticTurnDial,{turn:a,direction:o,startBearing:l}),jsxRuntimeExports.jsx("div",{className:"wex-turn-total wex-turn-total-side","aria-hidden":"true",children:TURN_WORD[a]})]}),jsxRuntimeExports.jsxs("div",{className:"wex-turn-col",children:[M,L]})]})},e):jsxRuntimeExports.jsxs("div",{className:"wex-turn-wrap",children:[M,L]},e)}function StaticTurnDial({turn:n,direction:t,startBearing:e}
68){const s=(t==="clockwise"?1:-1)*TURN_DEG$1[n],o=e+s,r=m=>({x:CX$2+R$2*Math.sin(m*Math.PI/180),y:CY$2-R$2*Math.cos(m*Math.PI/180)}),a=r(e),i=r(o),l=Math.abs(s)>180?1:0,u=s>0?1:0,d=`M ${CX$2} ${CY$2} L ${a.x.toFixed(2)} ${a.y.toFixed(2)} A ${R$2} ${R$2} 0 ${l} ${u} ${i.x.toFixed(2)} ${i.y.toFixed(2)} Z`;return jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 120 120",className:"wex-turn-svg","aria-hidden":"true",children:[jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:R$2,fill:"#f1f5f9",stroke:"#cbd5e1",strokeWidth:1}),Math.abs(s)===360?jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:R$2,fill:"#fde68a",fillOpacity:.7}):jsxRuntimeExports.jsx("path",{d,fill:"#fde68a",fillOpacity:.7}),jsxRuntimeExports.jsx("line",{x1:CX$2,y1:CY$2-R$2,x2:CX$2,y2:CY$2+R$2,stroke:"#cbd5e1",strokeWidth:1,strokeDasharray:"3 3"}),jsxRuntimeExports.jsx("line",{x1:CX$2-R$2,y1:CY$2,x2:CX$2+R$2,y2:CY$2,stroke:"#cbd5e1",strokeWidth:1,strokeDasharray:"3 3"}),jsxRuntimeExports.jsx("line",{x1:CX$2,y1:CY$2,x2:CX$2,y2:CY$2-R$2,stroke:"#94a3b8",strokeWidth:4,strokeLinecap:"round",strokeDasharray:"5 4",style:{transformBox:"view-box",transformOrigin:`${CX$2}px ${CY$2}px`,transform:`rotate(${e}deg)`}}),jsxRuntimeExports.jsx("line",{x1:CX$2,y1:CY$2,x2:CX$2,y2:CY$2-R$2,className:"wex-turn-arm",style:{transform:`rotate(${o}deg)`},stroke:"#1e3a8a",strokeWidth:6,strokeLinecap:"round"}),jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:5,fill:"#1e293b"}),jsxRuntimeExports.jsx("circle",{cx:CX$2,cy:CY$2,r:2.5,fill:"#fff"})]})}const COL_W$2=34,OP_W=30,PAD_X$1=14,PAD_TOP$2=14,ROW_H=42,DIGIT_SIZE=28,CARRY_SIZE=20,MONO="ui-monospace, 'SFMono-Regular', Menlo, monospace";function ColumnMethodFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="column_method")return null;const{operation:s,top:o,bottom:r,show_answer:a,keep_exchange:i,focus_column:l}=n.visual_data,u=Number.isInteger(l)&&l>=0?l:0,d=String(o),m=String(r),h=s==="add"?o+r:o-r,f=String(Math.abs(h)),g=Math.max(d.length,m.length,f.length),b=digitsFromRight$1(d,g),x=digitsFromRight$1(m,g),{carries:y,fullCarries:E}=computeMarks(s,b,x,g,u),k=s==="subtract"?computeExchangedTop(b,x,g):[],T=PAD_X$1+OP_W,A=g*COL_W$2,N=T+A+PAD_X$1,S=PAD_TOP$2+10,M=S+34,L=M+ROW_H,P=L+14,F=P+ROW_H,D=F+14,U=ie=>T+(g-1-ie)*COL_W$2+COL_W$2/2,q=t===3||a===!0,V=t===2&&a!==!0,O=V&&n.animation.split(/\s+/).includes("show_all_regrouping"),z=(t===3||a===!0)&&i===!0&&s==="subtract"&&k.some(ie=>ie!==null),K=V&&s==="subtract"&&n.animation.includes("through_zero")&&u===0,Z=z||K||O&&s==="subtract",Q=(q||O)&&!Z?E:V&&!Z?y:{},B=s==="add"?"+":"−",G=V&&s==="add"&&n.animation.includes("carry"),X=V&&s==="subtract"&&n.animation.includes("exchange")&&u===0&&!O,ne=b[1]?b[1].digit-1:0,se=b[0]?b[0].digit+10:0,te="#3b82f6",oe="#475569",le="#d97706";function ae(ie,we,ke,Te={}){const Se=Te.lit?te:oe,$e=Te.reveal?"wex-write-in":void 0,Re=Te.reveal?{animationDelay:`${Te.delayMs??0}ms`,transformBox:"fill-box",transformOrigin:"center"}:void 0;return jsxRuntimeExports.jsx("text",{x:U(we),y:ke,textAnchor:"middle",fontSize:DIGIT_SIZE,fontWeight:Te.lit?800:700,fontFamily:MONO,fill:Se,className:$e,style:Re,children:ie},`d-${ke}-${we}`)}return jsxRuntimeExports.jsxs("div",{className:"wex-cm-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${N} ${D}`,className:"wex-cm-svg","aria-hidden":"true",children:[!X&&Object.entries(Q).map(([ie,we])=>{const ke=Number(ie),Te=G&&ke===u+1,Se=Te?{"--cm-dx":`${U(u)-U(u+1)}px`,"--cm-dy":`${M-S}px`,transformBox:"fill-box",transformOrigin:"center"}:{animationDelay:"260ms",transformBox:"fill-box",transformOrigin:"center"};return jsxRuntimeExports.jsx("text",{x:U(ke),y:S,textAnchor:"middle",fontSize:CARRY_SIZE,fontWeight:800,fontFamily:MONO,fill:le,className:Te?"wex-cm-carry-slide":"wex-write-in",style:Se,children:we},`carry-${ie}`)}),b.map((ie,we)=>{if(!ie.present)return null;if(X&&we===1)return jsxRuntimeExports.jsxs("g",{children:[ae(ie.ch,we,M,{}),jsxRuntimeExports.jsx("line",{x1:U(1)-11,y1:M-DIGIT_SIZE*.35,x2:U(1)+11,y2:M-DIGIT_SIZE*.35,stroke:le,strokeWidth:2.5,className:"wex-cm-strike"}),jsxRuntimeExports.jsx("text",{x:U(1),y:S,textAnchor:"middle",fontSize:CARRY_SIZE,fontWeight:800,fontFamily:MONO,fill:le,className:"wex-write-in",style:{animationDelay:"260ms",transformBox:"fill-box",transformOrigin:"center"},children:ne})]},"ex-tens");if(X&&we===0)return jsxRuntimeExports.jsx("text",{x:U(0),y:M,textAnchor:"middle",fontSize:DIGIT_SIZE*.78,fontWeight:800,fontFamily:MONO,fill:le,className:"wex-cm-regroup",style:{transformBox:"fill-box",transformOrigin:"center"},children:se},"ex-ones");if(Z&&k[we]!==null){const ke=k[we];return ke>=10?jsxRuntimeExports.jsx("text",{x:U(we),y:M,textAnchor:"middle",fontSize:DIGIT_SIZE*.78,fontWeight:800,fontFamily:MONO,fill:le,children:ke},`rex-${we}`):jsxRuntimeExports.jsxs("g",{children:[ae(ie.ch,we,M,{}),jsxRuntimeExports.jsx("line",{x1:U(we)-11,y1:M-DIGIT_SIZE*.35,x2:U(we)+11,y2:M-DIGIT_SIZE*.35,stroke:le,strokeWidth:2.5}),jsxRuntimeExports.jsx("text",{x:U(we),y:S,textAnchor:"middle",fontSize:CARRY_SIZE,fontWeight:800,fontFamily:MONO,fill:le,children:ke})]},`rex-${we}`)}return ae(ie.ch,we,M,{lit:V&&we===u})}),jsxRuntimeExports.jsx("text",{x:PAD_X$1+OP_W/2,y:L,textAnchor:"middle",fontSize:DIGIT_SIZE,fontWeight:700,fontFamily:MONO,fill:oe,children:B}),x.map((ie,we)=>ie.present?ae(ie.ch,we,L,{lit:V&&we===u}):null),jsxRuntimeExports.jsx("line",{x1:PAD_X$1,y1:P,x2:T+A,y2:P,stroke:oe,strokeWidth:2.5}),V&&jsxRuntimeExports.jsx("rect",{x:U(u)-COL_W$2/2+2,y:M-DIGIT_SIZE,width:COL_W$2-4,height:L-M+DIGIT_SIZE+6,rx:6,fill:"none",stroke:te,strokeWidth:2,strokeDasharray:"4 3",className:"wex-fade-in"}),q&&digitsFromRight$1(f,g).map((ie,we)=>ie.present?ae(ie.ch,we,F,{reveal:!0,delayMs:we*110}):null)]}),q&&jsxRuntimeExports.jsxs("div",{className:"wex-cm-total","aria-hidden":"true",children:[o," ",B," ",r," = ",h]})]},e)}function digitsFromRight$1(n,t){const e=[];for(let s=0;s<t;s++){const o=n.length-1-s;
68if(o<0)e.push({ch:"",digit:0,present:!1});else{const r=n[o];e.push({ch:r,digit:Number(r),present:!0})}}return e}function computeMarks(n,t,e,s,o){const r={},a={};if(n==="add"){let i=0;for(let l=0;l<s;l++){const d=t[l].digit+e[l].digit+i>=10?1:0;d===1&&l+1<s&&(a[l+1]="1",l===o&&(r[l+1]="1")),i=d}}else{let i=0;for(let l=0;l<s;l++){const u=e[l].digit+i;t[l].digit<u?(l+1<s&&(a[l+1]="−1",l===o&&(r[l+1]="−1")),i=1):i=0}}return{carries:r,fullCarries:a}}function computeExchangedTop(n,t,e){const s=[];let o=0;for(let r=0;r<e;r++){let a=n[r].digit-o;a<t[r].digit?(a+=10,o=1):o=0,s.push(a===n[r].digit?null:a)}return s}function computeShortDivision(n,t){const e=Math.max(1,Math.min(Math.abs(Math.round(t)),9)),s=Math.max(0,Math.min(Math.abs(Math.round(n)),9999)),o=String(s).split("").map(i=>Number(i)),r=[];let a=0;for(const i of o){const l=a*10+i,u=Math.floor(l/e),d=l-u*e;r.push({digit:i,carryIn:a,working:l,q:u,carryOut:d}),a=d}return{columns:r,quotient:Math.floor(s/e),remainder:a}}const COL_W$1=38,GAP=10,PAD_X=14,PAD_TOP$1=14,PAD_BOTTOM$1=14,QUOTIENT_H=32,BAR_GAP=8,DIGITS_H=46,DIVISOR_W=26,BRACKET_GAP=8,DIGIT_FONT="ui-monospace, 'SFMono-Regular', Menlo, monospace",INK$2="#475569",RULE_SOFT="#e2e8f0",BLUE$b="#3b82f6",BLUE_FAINT$2="#94a3b8",AMBER_DEEP$4="#d97706";function ShortDivisionFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="short_division")return null;const{dividend:s,divisor:o,reveal_columns:r,full_reveal:a,show_carries_on_reveal:i}=n.visual_data,l=Math.max(1,Math.min(Math.abs(Math.round(o)),9)),u=Math.max(0,Math.min(Math.abs(Math.round(s)),9999)),{columns:d,quotient:m,remainder:h}=computeShortDivision(u,l),f=d.length,g=t===3||a===!0,b=q=>q<f-1&&d.slice(0,q+1).every(V=>V.q===0),x=Math.min(2,f),y=typeof r=="number"?Math.max(0,Math.min(Math.round(r),f)):x,E=f*COL_W$1+(f-1)*GAP,k=PAD_X+DIVISOR_W+BRACKET_GAP,T=k,A=T+E,N=A+PAD_X,S=PAD_TOP$1,M=S+QUOTIENT_H+BAR_GAP,L=M+DIGITS_H*.72,P=S+QUOTIENT_H*.82,F=M+DIGITS_H+PAD_BOTTOM$1,D=q=>T+q*(COL_W$1+GAP)+COL_W$1/2,U=q=>T+q*(COL_W$1+GAP);return jsxRuntimeExports.jsxs("div",{className:"wex-sdiv-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${N} ${F}`,className:"wex-sdiv-svg","aria-hidden":"true",children:[jsxRuntimeExports.jsx("text",{x:PAD_X+DIVISOR_W/2,y:L,textAnchor:"middle",fontSize:30,fontWeight:700,fontFamily:DIGIT_FONT,fill:INK$2,children:String(l)}),jsxRuntimeExports.jsx("path",{d:`M ${k} ${M+DIGITS_H*.92}
69              C ${k-10} ${M+DIGITS_H*.45},
70                ${k-6} ${M+6},
71                ${k} ${M}`,fill:"none",stroke:INK$2,strokeWidth:2.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("line",{x1:k,y1:M,x2:A,y2:M,stroke:INK$2,strokeWidth:2.5,strokeLinecap:"round"}),!g&&d.map((q,V)=>b(V)?null:jsxRuntimeExports.jsx("line",{x1:U(V)+5,y1:S+QUOTIENT_H-2,x2:U(V)+COL_W$1-5,y2:S+QUOTIENT_H-2,stroke:BLUE_FAINT$2,strokeWidth:2,strokeLinecap:"round"},`q-blank-${V}`)),d.map((q,V)=>{const O=t===2&&!a&&V<y;return jsxRuntimeExports.jsx("text",{x:D(V),y:L,textAnchor:"middle",fontSize:30,fontWeight:700,fontFamily:DIGIT_FONT,fill:O?BLUE$b:INK$2,className:O?"wex-sdiv-active":void 0,style:O?{"--sdiv-i":V}:void 0,children:String(q.digit)},`d-${V}`)}),(t===2&&!a||a&&i)&&d.map((q,V)=>q.carryIn>0&&V<=y?jsxRuntimeExports.jsx("text",{x:U(V)-GAP/2,y:M+16,textAnchor:"middle",fontSize:15,fontWeight:800,fontFamily:DIGIT_FONT,fill:AMBER_DEEP$4,className:"wex-sdiv-carry",style:{"--sdiv-i":V},children:String(q.carryIn)},`carry-${V}`):null),t===2&&!a&&d.map((q,V)=>V<y&&!b(V)?jsxRuntimeExports.jsx("text",{x:D(V),y:P,textAnchor:"middle",fontSize:26,fontWeight:800,fontFamily:DIGIT_FONT,fill:BLUE$b,className:"wex-sdiv-qstep",style:{"--sdiv-i":V},children:String(q.q)},`qstep-${V}`):null),g&&d.map((q,V)=>b(V)?null:jsxRuntimeExports.jsx("text",{x:D(V),y:P,textAnchor:"middle",fontSize:28,fontWeight:800,fontFamily:DIGIT_FONT,fill:AMBER_DEEP$4,className:"wex-sdiv-answer",style:{"--sdiv-i":V},children:String(q.q)},`q-${V}`)),g&&jsxRuntimeExports.jsx("line",{x1:T,y1:S+QUOTIENT_H-1,x2:A,y2:S+QUOTIENT_H-1,stroke:RULE_SOFT,strokeWidth:1.5})]}),g&&jsxRuntimeExports.jsxs("div",{className:"wex-sdiv-total","aria-hidden":"true",children:[`${u} ÷ ${l} = ${m}`,h>0?` remainder ${h}`:""]})]},e)}const BLUE$a="#3b82f6",BLUE_FAINT$1="#94a3b8",AMBER$8="#f59e0b",AMBER_DEEP$3="#d97706",AXIS$4="#475569",TICK$1="#64748b",GRID$3="#e2e8f0",GRID_BOLD="#cbd5e1",MAX_PLOT=250,PAD_L=28,PAD_R=28,PAD_T$1=24,PAD_B=26;function CoordinateGridFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="coordinate_grid")return null;const{max_x:s,max_y:o,points:r,contrast_point:a}=n.visual_data,i=r[0],l=r[1],u=!!(i&&l),d=Math.min(30,MAX_PLOT/Math.max(s,o)),m=s*d,h=o*d,f=PAD_L+m+PAD_R,g=PAD_T$1+h+PAD_B,b=PAD_L,x=PAD_T$1+h,y=q=>b+q*d,E=q=>x-q*d,k=Array.from({length:s+1},(q,V)=>V),T=Array.from({length:o+1},(q,V)=>V),A=u?l.x-i.x:0,N=u?l.y-i.y:0,S=u?{x:l.x,y:i.y}:null,M=i?.label??"A",L=l?.label??`${M}′`,P=(q,V,O,z=!1)=>{const K=O.length*8,Z=z&&q>0?y(q)-9>=K:y(q)+9+K>f;return{x:Z?Math.max(K+2,y(q)-9):Math.min(f-K-2,y(q)+9),y:Math.max(14,E(V)-9),textAnchor:Z?"end":"start"}},F=t===3&&u,D=u&&t>=2,U=u&&t===2;return jsxRuntimeExports.jsxs("div",{className:"wex-cg-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${f} ${g}`,className:"wex-cg-svg",role:"img","aria-label":u?t===1?`Point ${M} is at ${i.x} across, ${i.y} up.`:t===2?`Move ${Math.abs(A)} ${A>=0?"right":"left"}, then ${Math.abs(N)} ${N>=0?"up":"down"}.`:`Point ${L} lands at ${l.x} across, ${l.y} up.`:i?`Point ${M} is at ${i.x} across, ${i.y} up.`:`An empty coordinate grid from 0 to ${s} across and 0 to ${o} up.`,children:[jsxRuntimeExports.jsx("defs",{children:jsxRuntimeExports.jsx("marker",{id:"wex-cg-arrow",viewBox:"0 0 10 10",refX:"8",refY:"5",markerWidth:"6",markerHeight:"6",orient:"auto-start-reverse",children:jsxRuntimeExports.jsx("path",{d:"M 0 1 L 9 5 L 0 9 z",fill:AMBER_DEEP$3})})}),k.map(q=>jsxRuntimeExports.jsx("line",{x1:y(q),y1:E(0),x2:y(q),y2:E(o),stroke:GRID$3,strokeWidth:1},`v${q}`)),T.map(q=>jsxRuntimeExports.jsx("line",{x1:y(0),y1:E(q),x2:y(s),y2:E(q),stroke:GRID$3,strokeWidth:1},`h${q}`)),jsxRuntimeExports.jsx("line",{x1:y(0),y1:E(0),x2:y(s),y2:E(0),stroke:AXIS$4,strokeWidth:2}),jsxRuntimeExports.jsx("line",{x1:y(0),y1:E(0),x2:y(0),y2:E(o),stroke:AXIS$4,strokeWidth:2}),k.map(q=>jsxRuntimeExports.jsx("text",{x:y(q),y:E(0)+16,textAnchor:"middle",fontSize:12,fontWeight:600,fill:TICK$1,children:q},`xn${q}`)),T.map(q=>jsxRuntimeExports.jsx("text",{x:y(0)-8,y:E(q)+4,textAnchor:"end",fontSize:12,fontWeight:600,fill:TICK$1,children:q},`yn${q}`)),D&&S&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:y(i.x),y1:E(i.y),x2:y(i.x),y2:E(0),stroke:GRID_BOLD,strokeWidth:1,strokeDasharray:"2 3"}),jsxRuntimeExports.jsx("line",{x1:y(i.x),y1:E(i.y),x2:y(0),y2:E(i.y),stroke:GRID_BOLD,strokeWidth:1,strokeDasharray:"2 3"}),jsxRuntimeExports.jsx("path",{d:`M ${y(i.x)} ${E(i.y)} L ${y(S.x)} ${E(S.y)} L ${y(l.x)} ${E(l.y)}`,fill:"none",stroke:AMBER$8,strokeOpacity:.28,strokeWidth:6,strokeLinecap:"round",strokeLinejoin:"round"}),A!==0&&jsxRuntimeExports.jsx("line",{x1:y(i.x),y1:E(i.y),x2:y(S.x),y2:E(S.y),stroke:AMBER_DEEP$3,strokeWidth:3,strokeLinecap:"round",markerEnd:"url(#wex-cg-arrow)",children:U&&jsxRuntimeExports.jsx("animate",{attributeName:"stroke-opacity",from:"0",to:"1",begin:"0s",dur:"0.5s",fill:"freeze"})}),N!==0&&jsxRuntimeExports.jsx("line",{x1:y(S.x),y1:E(S.y),x2:y(l.x),y2:E(l.y),stroke:AMBER_DEEP$3,strokeWidth:3,strokeLinecap:"round",markerEnd:"url(#wex-cg-arrow)",children:U&&jsxRuntimeExports.jsx("animate",{attributeName:"stroke-opacity",from:"0",to:"1",begin:"0.55s",dur:"0.5s",fill:"freeze"})}),A!==0&&jsxRuntimeExports.jsxs("text",{x:(y(i.x)+y(S.x))/2,y:E(i.y)+(A<0?18:-8),textAnchor:"middle",fontSize:12,fontWeight:700,fill:AMBER_DEEP$3,children:[A>0?"→":"←"," ",Math.abs(A)," ",A>0?"right":"left",U&&jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",begin:"0.25s",dur:"0.4s",fill:"freeze"})]}),N!==0&&jsxRuntimeExports.jsxs("text",{x:y(S.x)+60>f?y(S.x)-10:y(S.x)+10,y:(E(S.y)+E(l.y))/2,textAnchor:y(S.x)+60>f?"end":"start",fontSize:12,fontWeight:700,fill:AMBER_DEEP$3,children:[N>0?"↑":"↓"," ",Math.abs(N)," ",N>0?"up":"down",U&&jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",begin:"0.8s",dur:"0.4s",fill:"freeze"})]}),U&&jsxRuntimeExports.jsxs("circle",{r:5,fill:AMBER_DEEP$3,children:[jsxRuntimeExports.jsx("animateMotion",{dur:"1s",begin:"0s",fill:"freeze",keyPoints:"0;0.5;1",keyTimes:"0;0.55;1",calcMode:"linear",path:`M ${y(i.x)} ${E(i.y)} L ${y(S.x)} ${E(S.y)} L ${y(l.x)} ${E(l.y)}`}),jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"1",to:"0",begin:"1s",dur:"0.3s",fill:"freeze"})]})]}),i&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:y(i.x),cy:E(i.y),r:6,fill:F?BLUE_FAINT$1:BLUE$a,stroke:"#fff",strokeWidth:1.5}),jsxRuntimeExports.jsx("text",{...P(i.x,i.y,M,!0),fontSize:13,fontWeight:700,fill:F?BLUE_FAINT$1:BLUE$a,children:M})]}),u&&!F&&n.visual_data.show_target_ghost===!0&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:y(l.x),cy:E(l.y),r:6,fill:"none",stroke:BLUE_FAINT$1,strokeWidth:2,strokeDasharray:"3 3"}),jsxRuntimeExports.jsx("text",{...P(l.x,l.y,L),fontSize:13,fontWeight:700,fill:BLUE_FAINT$1,children:L})]}),F&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsxs("circle",{cx:y(l.x),cy:E(l.y),r:9,fill:"none",stroke:AMBER_DEEP$3,strokeWidth:2,children:[jsxRuntimeExports.jsx("animate",{attributeName:"r",from:"14",to:"9",begin:"0s",dur:"0.45s",fill:"freeze"}),jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",begin:"0s",dur:"0.45s",fill:"freeze"})]}),jsxRuntimeExports.jsx("circle",{cx:y(l.x),cy:E(l.y),r:6,fill:BLUE$a,stroke:"#fff",strokeWidth:1.5,children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",begin:"0.1s",dur:"0.35s",fill:"freeze"})}),jsxRuntimeExports.jsxs("text",{...P(l.x,l.y,L),fontSize:13,fontWeight:700,fill:BLUE$a,children:[L,jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",begin:"0.2s",dur:"0.35s",fill:"freeze"})]})]}),t===3&&a&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:y(a.x),cy:E(a.y),r:6,fill:"none",stroke:BLUE_FAINT$1,strokeWidth:2,strokeDasharray:"3 3",children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",values:"0;1;0.35;1",dur:"1.2s",begin:"0.4s",fill:"freeze"})}),jsxRuntimeExports.jsxs("text",{...P(a.x,a.y,a.label??`(${a.x},${a.y})`),fontSize:12,fontWeight:700,fill:BLUE_FAINT$1,children:[a.label??`(${a.x},${a.y})`,jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",begin:"0.5s",dur:"0.4s",fill:"freeze"})]})]},`contrast-${e}`)]}),F?jsxRuntimeExports.jsxs("div",{className:"wex-cg-coords","aria-hidden":"true",children:[L," = (",l.x,", ",l.y,")"]}):i&&t===1?jsxRuntimeExports.jsxs("div",{className:"wex-cg-coords","aria-hidden":"true",children:[M," = (",i.x,", ",i.y,")"]}):null]},e)}function angleDiagramFrameGeometry(n,t){return{size:220,cx:220/2,cy:n==="point"?220/2:220/2+10,baseDeg:n==="straight"?180:0,total:n==="straight"?180:t?90:360}}function AngleDiagramFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="angle_diagram")return null;const s=n.visual_data,{angles:o,baseline:r="none",mark_right_angle:a=!1,right_angle_verdict:i=!1,gap_guide:l=!1}=s,{size:u,cx:d,cy:m,baseDeg:h}=angleDiagramFrameGeometry(r,a),f=92,g=34,b=58,x=18,y=te=>te*Math.PI/180,E=(te,oe)=>d+oe*Math.cos(y(te)),k=(te,oe)=>m-oe*Math.sin(y(te)),T=[h];let A=h;for(const te of o)A+=te.degrees,T.push(A);const N=(te,oe,le)=>{const ae=E(te,le),ie=k(te,le),we=E(oe,le),ke=k(oe,le),Se=Math.abs(oe-te)>180?1:0;return`M ${d} ${m} L ${ae} ${ie} A ${le} ${le} 0 ${Se} 0 ${we} ${ke} Z`},S=(te,oe,le)=>{const ae=E(te,le),ie=k(te,le),we=E(oe,le),ke=k(oe,le),Se=Math.abs(oe-te)>180?1:0;return`M ${ae} ${ie} A ${le} ${le} 0 ${Se} 0 ${we} ${ke}`},M=te=>{const oe={x:E(te,x),y:k(te,x)}
71,le={x:E(te+90,x),y:k(te+90,x)},ae={x:oe.x+(le.x-d),y:oe.y+(le.y-m)};return`M ${oe.x} ${oe.y} L ${ae.x} ${ae.y} L ${le.x} ${le.y}`},L=o.findIndex(te=>te.unknown),P=L!==-1,{total:F}=angleDiagramFrameGeometry(r,a),D=o.reduce((te,oe)=>oe.unknown?te:te+oe.degrees,0),U=F-D,q=o.length===1?o[0].degrees:0,V=q<90?"Acute":q===90?"Right":"Obtuse",O="#3b82f6",z="#dbeafe",K="#94a3b8",Z="#f59e0b",Q="#d97706",B="#475569",G="#475569",X="#cbd5e1",ne=r==="straight",se=te=>{const oe=o[te];return oe.unknown?t===3?`${U}°`:"?":P?oe.label??`${oe.degrees}°`:oe.label&&!oe.label.includes("°")?oe.label:null};return jsxRuntimeExports.jsxs("div",{className:"wex-ang-wrap",style:{display:"flex",flexDirection:"column",alignItems:"center",gap:8},children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${u} ${u}`,className:"wex-ang-svg",width:220,height:220,style:{maxWidth:"100%",height:"auto"},"aria-hidden":"true",children:[ne&&jsxRuntimeExports.jsx("line",{x1:E(0,f),y1:k(0,f),x2:E(180,f),y2:k(180,f),stroke:G,strokeWidth:2.5,strokeLinecap:"round"}),o.map((te,oe)=>{const le=!te.unknown||t===3;return jsxRuntimeExports.jsx("path",{d:N(T[oe],T[oe+1],g),fill:te.unknown&&!le?"#f1f5f9":z,fillOpacity:le?.85:.5,stroke:"none"},`wedge${oe}`)}),o.map((te,oe)=>jsxRuntimeExports.jsx("path",{d:S(T[oe],T[oe+1],g),fill:"none",stroke:te.unknown&&t!==3?K:O,strokeWidth:2.5},`arc${oe}`)),P&&t===2&&jsxRuntimeExports.jsx("g",{children:ne?jsxRuntimeExports.jsx("line",{x1:E(0,f),y1:k(0,f),x2:E(180,f),y2:k(180,f),stroke:Z,strokeWidth:4,strokeLinecap:"round",opacity:.85,children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"0.85",dur:"0.5s",fill:"freeze"})}):a?jsxRuntimeExports.jsx("path",{d:M(h),fill:"none",stroke:Z,strokeWidth:3,children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",dur:"0.5s",fill:"freeze"})}):jsxRuntimeExports.jsx("circle",{cx:d,cy:m,r:g+8,fill:"none",stroke:Z,strokeWidth:3,opacity:.85,children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"0.85",dur:"0.5s",fill:"freeze"})})}),!P&&t>=2&&o.length>=1&&jsxRuntimeExports.jsx("path",{d:M(T[0]),fill:"none",stroke:t===3?Q:Z,strokeWidth:3,strokeLinejoin:"round",children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",dur:"0.45s",fill:"freeze"})}),!P&&i&&l&&t>=2&&o.length===1&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:d,y1:m,x2:E(T[0]+90,f),y2:k(T[0]+90,f),stroke:Z,strokeWidth:2,strokeDasharray:"5 4",strokeLinecap:"round",children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",dur:"0.45s",fill:"freeze"})}),q!==90&&jsxRuntimeExports.jsx("path",{d:N(T[0]+Math.min(q,90),T[0]+Math.max(q,90),f*.72),fill:Z,fillOpacity:.28,stroke:Q,strokeWidth:1.5,children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",dur:"0.45s",fill:"freeze"})})]}),T.map((te,oe)=>{if(ne&&(oe===0||oe===T.length-1)&&te%180===0&&(te===0||te===180)||r==="point"&&oe===T.length-1&&te%360===0)return null;const le=P&&(oe===L||oe===L+1)&&t!==3;return jsxRuntimeExports.jsx("line",{x1:d,y1:m,x2:E(te,f),y2:k(te,f),stroke:le?X:G,strokeWidth:2.5,strokeLinecap:"round"},`ray${oe}`)}),jsxRuntimeExports.jsx("circle",{cx:d,cy:m,r:4,fill:B}),o.map((te,oe)=>{const le=(T[oe]+T[oe+1])/2,ae=se(oe);if(!ae)return null;const ie=te.unknown&&t===3;return jsxRuntimeExports.jsxs("text",{x:E(le,b),y:k(le,b)+5,textAnchor:"middle",fontSize:17,fontWeight:800,fill:te.unknown?ie?O:Q:B,children:[ae,ie&&jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",dur:"0.5s",fill:"freeze"})]},`lbl${oe}`)})]},e),P&&t>=2&&jsxRuntimeExports.jsx("div",{className:"wex-ang-rule","aria-hidden":"true",style:{fontSize:18,fontWeight:800,color:t===3?O:Q,letterSpacing:"0.5px"},children:t===2?`${F}° − ${D}° = ?`:`${F}° − ${D}° = ${U}°`}),!P&&i&&t>=2&&o.length===1&&jsxRuntimeExports.jsx("div",{className:"wex-ang-verdict","aria-hidden":"true",style:{display:"flex",alignItems:"baseline",gap:8},children:jsxRuntimeExports.jsx("span",{style:{fontSize:16,fontWeight:700,color:Q,border:`2px solid ${Z}`,borderRadius:999,padding:"2px 12px"},children:q>90?"Bigger than a right angle":q<90?"Smaller than a right angle":"A right angle"})}),!P&&!i&&t===3&&o.length===1&&jsxRuntimeExports.jsxs("div",{className:"wex-ang-verdict","aria-hidden":"true",style:{display:"flex",alignItems:"baseline",gap:8},children:[jsxRuntimeExports.jsxs("span",{style:{fontSize:22,fontWeight:800,color:O},children:[q,"°"]}),jsxRuntimeExports.jsx("span",{style:{fontSize:15,fontWeight:700,color:Q,border:`2px solid ${Z}`,borderRadius:999,padding:"1px 10px"},children:V})]})]})}const CELL$2=22,TOP_GUTTER=28,LEFT_GUTTER=40,PAD$1=8,MAX_COLS=25,MAX_ROWS=12,FILL$1="#dbeafe",BORDER$1="#1e3a8a",GRID_STROKE="#94a3b8",AMBER_FILL$3="#fde68a",AMBER_LINE$1="#d97706",AMBER_TEXT$1="#b45309",LABEL$2="#475569",NUM$2="#64748b";function areaGridFrameModel(n,t,e){const s=Math.max(1,Math.min(Math.round(n),MAX_COLS)),o=Math.max(1,Math.min(Math.round(t),MAX_ROWS)),r=s*o,a=e===2,i=e===3,l=e===3;return{cols:s,rows:o,area:r,highlightStructure:a,fillAll:i,showProduct:l,productLabel:l?`${s} × ${o} = ${r}`:null}}function AreaGridFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="area_grid")return null;const s=n.visual_data,o=s.unit_label??"cm",{cols:r,rows:a,highlightStructure:i,fillAll:l,productLabel:u}=areaGridFrameModel(s.width,s.height,t),d=r*CELL$2,m=a*CELL$2,h=LEFT_GUTTER,f=TOP_GUTTER,g=LEFT_GUTTER+d+PAD$1,b=TOP_GUTTER+m+PAD$1,x=Math.max(180,Math.min(g,360)),y=k=>`${k}${o?` ${o}`:""}`,E=(k,T)=>l||i&&(k===0||T===0)?AMBER_FILL$3:FILL$1;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${g} ${b}`,className:"wex-fade-enter",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${x}px`,height:"auto"},role:"img","aria-label":`A rectangle ${y(r)} long and ${y(a)} wide, drawn as unit ${o} squares.`,children:[Array.from({length:a},(k,T)=>Array.from({length:r},(A,N)=>
71jsxRuntimeExports.jsx("rect",{x:h+N*CELL$2,y:f+T*CELL$2,width:CELL$2,height:CELL$2,fill:E(T,N),stroke:GRID_STROKE,strokeWidth:1},`cell-${T}-${N}`))),i&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[Array.from({length:r},(k,T)=>jsxRuntimeExports.jsx("text",{x:h+T*CELL$2+CELL$2/2,y:f+CELL$2/2+4,textAnchor:"middle",fontSize:12,fontWeight:800,fill:AMBER_LINE$1,children:T+1},`rownum-${T}`)),jsxRuntimeExports.jsx("rect",{x:h,y:f,width:d,height:CELL$2,fill:"none",stroke:AMBER_LINE$1,strokeWidth:2.5}),jsxRuntimeExports.jsx("rect",{x:h,y:f,width:CELL$2,height:m,fill:"none",stroke:AMBER_LINE$1,strokeWidth:2.5}),jsxRuntimeExports.jsxs("text",{x:h+d/2,y:f+m+7,textAnchor:"middle",fontSize:11,fontWeight:700,fill:AMBER_TEXT$1,children:[r," across"]}),jsxRuntimeExports.jsxs("text",{x:h-22,y:f+m/2,textAnchor:"middle",fontSize:11,fontWeight:700,fill:AMBER_TEXT$1,transform:`rotate(-90 ${h-22} ${f+m/2})`,children:[a," down"]})]}),l&&r*a<=60&&Array.from({length:a},(k,T)=>Array.from({length:r},(A,N)=>jsxRuntimeExports.jsx("text",{x:h+N*CELL$2+CELL$2/2,y:f+T*CELL$2+CELL$2/2+4,textAnchor:"middle",fontSize:11,fontWeight:700,fill:NUM$2,children:T*r+N+1},`scan-${T}-${N}`))),jsxRuntimeExports.jsx("rect",{x:h,y:f,width:d,height:m,fill:"none",stroke:BORDER$1,strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:h+d/2,y:f+m+7,textAnchor:"middle",fontSize:15,fontWeight:700,fill:LABEL$2,opacity:i?0:1,children:y(r)}),jsxRuntimeExports.jsx("text",{x:h-14,y:f+m/2,textAnchor:"middle",fontSize:15,fontWeight:700,fill:LABEL$2,transform:`rotate(-90 ${h-14} ${f+m/2})`,opacity:i?0:1,children:y(a)})]}),u&&jsxRuntimeExports.jsxs("div",{className:"wex-total",children:[r," × ",a," = ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:r*a})]},`total-${e}`)]},e)}const LINE="#3b82f6",AXIS$3="#334155",TICK="#475569",GRID$2="#e2e8f0",GRID_FAINT="#eef0f3",AMBER$7="#b45309",AMBER_FILL$2="#fbbf24",DIM="#94a3b8",PAD_T=14,PLOT_H=160,SLOT$1=52,INSET=12,MAX_POINTS=8;function LineGraphFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="line_graph")return null;const s=n.visual_data,{x_labels:o,values:r,axis_max:a,axis_step:i,highlight_index:l}=s,u=Math.min(o.length,r.length,MAX_POINTS);if(u===0)return null;const d=Math.max(...r.slice(0,u),1),m=Math.max(a??0,d),h=i??niceAxisStep(m),f=Math.ceil(m/h)*h,g=Array.from({length:f/h+1},(Q,B)=>B*h),x=h>1&&f<=40?Array.from({length:f+1},(Q,B)=>B).filter(Q=>Q%h!==0):[],y=34,E=PAD_T+PLOT_H,k=Math.max(1,u-1)*SLOT$1+24,T=y,A=y+k+16,N=E+30,S=Q=>E-Q/f*PLOT_H,M=Q=>T+INSET+Q*SLOT$1,L=Array.from({length:u},(Q,B)=>`${M(B)},${S(r[B])}`).join(" "),P=s.read_mode==="trend",F=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[x.map(Q=>jsxRuntimeExports.jsx("line",{x1:T,y1:S(Q),x2:T+k,y2:S(Q),stroke:GRID_FAINT,strokeWidth:1},`minor-${Q}`)),g.map(Q=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:T,y1:S(Q),x2:T+k,y2:S(Q),stroke:GRID$2,strokeWidth:1}),jsxRuntimeExports.jsx("text",{x:T-8,y:S(Q)+4,textAnchor:"end",fontSize:11,fontWeight:700,fill:TICK,children:Q})]},Q)),jsxRuntimeExports.jsx("polyline",{points:L,fill:"none",stroke:LINE,strokeWidth:2.5,strokeLinejoin:"round",strokeLinecap:"round"}),Array.from({length:u},(Q,B)=>jsxRuntimeExports.jsx("circle",{cx:M(B),cy:S(r[B]),r:4,fill:LINE,stroke:"#fff",strokeWidth:1.2},`dot${B}`))]}),D=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("line",{x1:T,y1:PAD_T,x2:T,y2:E,stroke:AXIS$3,strokeWidth:2}),jsxRuntimeExports.jsx("line",{x1:T,y1:E,x2:T+k,y2:E,stroke:AXIS$3,strokeWidth:2})]});if(P){const Q=s.segment_highlight,B=Q?Math.max(0,Math.min(Q[0],u-1)):null,G=Q?Math.max(0,Math.min(Q[1],u-1)):null,X=s.trace_line?u-1:null,ne=(le,ae)=>Array.from({length:ae-le+1},(ie,we)=>`${M(le+we)},${S(r[le+we])}`).join(" "),se=le=>{const ae=M(le),ie=S(r[le]),we=M(le+1),ke=S(r[le+1]),Te=r[le+1]-r[le],Se=(ie+ke)/2,$e=we+6;return jsxRuntimeExports.jsxs("g",{className:"wex-lg-slope",children:[jsxRuntimeExports.jsx("line",{x1:ae,y1:ie,x2:we,y2:ie,stroke:AMBER$7,strokeWidth:1.5,strokeDasharray:"3,3"}),jsxRuntimeExports.jsx("line",{x1:we,y1:ie,x2:we,y2:ke,stroke:AMBER$7,strokeWidth:1.5,strokeDasharray:"3,3"}),s.slope_value_labels&&jsxRuntimeExports.jsx("text",{x:$e,y:Se,textAnchor:"start",dominantBaseline:"middle",fontSize:11,fontWeight:800,fill:AMBER$7,children:Te>0?`+${Te}`:Te})]},`slope${le}`)};let te=null;if(X!==null&&X>0){const le=M(X),ae=S(r[X]),ie=Math.atan2(ae-S(r[X-1]),le-M(X-1)),we=8,ke=`${le},${ae}`,Te=`${le-we*Math.cos(ie-.5)},${ae-we*Math.sin(ie-.5)}`,Se=`${le-we*Math.cos(ie+.5)},${ae-we*Math.sin(ie+.5)}`;te=jsxRuntimeExports.jsx("polygon",{points:`${ke} ${Te} ${Se}`,fill:AMBER$7})}const oe=new Set;return B!==null&&G!==null&&(oe.add(B),oe.add(G)),jsxRuntimeExports.jsxs("div",{className:"wex-lg-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${A} ${N}`,className:"wex-lg-svg","aria-hidden":"true",children:[F,s.dim_index!=null&&s.dim_index>=0&&s.dim_index<u&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:M(s.dim_index),cy:S(r[s.dim_index]),r:7,fill:"none",stroke:DIM,strokeWidth:2,strokeDasharray:"2,2"}),jsxRuntimeExports.jsx("text",{x:M(s.dim_index),y:s.dim_label_safe&&S(r[s.dim_index])-12<PAD_T+8?S(r[s.dim_index])+18:S(r[s.dim_index])-12,textAnchor:"middle",fontSize:10,fontWeight:700,fill:DIM,children:"highest"})]}),s.show_slope&&B!==null&&G!==null&&Array.from({length:Math.max(0,G-B)},(le,ae)=>se(B+ae)),B!==null&&G!==null&&G>B&&jsxRuntimeExports.jsx("polyline",{className:"wex-lg-hilite",points:ne(B,G),fill:"none",stroke:AMBER$7,strokeWidth:4,strokeLinejoin:"round",strokeLinecap:"round",pathLength:1}),X!==null&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("polyline",{className:"wex-lg-hilite",points:ne(0,X),fill:"none",stroke:AMBER$7,strokeWidth:4,strokeLinejoin:"round",strokeLinecap:"round",pathLength:1}),te]}),s.projected&&u>=2&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("line",{x1:M(u-2),y1:S(r[u-2]),x2:M(u-1),y2:S(r[u-1]),stroke:AMBER$7,strokeWidth:3,strokeDasharray:"6,4"}),jsxRuntimeExports.jsx("circle",{className:"wex-lg-proj",cx:M(u-1),cy:S(r[u-1]),r:6.5,fill:AMBER_FILL$2,stroke:AMBER$7,strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:M(u-1),y:s.proj_label_safe&&S(r[u-1])-10<PAD_T+8?S(r[u-1])+20:S(r[u-1])-10,textAnchor:"middle",fontSize:13,fontWeight:800,fill:AMBER$7,children:r[u-1]})]}),B!==null&&G!==null&&G>B&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("circle",{cx:M(B),cy:S(r[B]),r:5.5,fill:AMBER_FILL$2,stroke:AMBER$7,strokeWidth:2}
71),jsxRuntimeExports.jsx("circle",{cx:M(G),cy:S(r[G]),r:5.5,fill:AMBER_FILL$2,stroke:AMBER$7,strokeWidth:2})]}),Array.from({length:u},(le,ae)=>jsxRuntimeExports.jsx("text",{x:M(ae),y:E+18,textAnchor:"middle",fontSize:11,fontWeight:oe.has(ae)?800:700,fill:oe.has(ae)?AMBER$7:"#334155",children:o[ae]},`xl${ae}`)),D,s.scale_note&&jsxRuntimeExports.jsx("text",{x:T,y:PAD_T-4,textAnchor:"start",fontSize:10,fontWeight:700,fill:AMBER$7,children:s.scale_note})]}),s.conclude_label&&jsxRuntimeExports.jsx("div",{className:"wex-lg-value","aria-hidden":"true",children:s.conclude_label})]},e)}if(s.read_index!=null){const Q=oe=>Math.max(0,Math.min(oe,u-1)),B=Q(s.read_index),G=M(B),X=S(r[B]),ne=(s.carry_indices??[]).map(Q).filter(oe=>oe!==B),se=new Set([B,...ne]),te=s.threshold!=null?S(Math.max(0,Math.min(s.threshold,f))):null;return jsxRuntimeExports.jsxs("div",{className:"wex-lg-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${A} ${N}`,className:"wex-lg-svg","aria-hidden":"true",children:[F,te!=null&&jsxRuntimeExports.jsx("line",{x1:T,y1:te,x2:T+k,y2:te,stroke:AMBER$7,strokeWidth:1.75,strokeDasharray:"6,4",children:jsxRuntimeExports.jsx("animate",{attributeName:"x2",from:T,to:T+k,dur:"0.5s",fill:"freeze"})}),ne.map(oe=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("circle",{cx:M(oe),cy:S(r[oe]),r:5.5,fill:AMBER_FILL$2,stroke:AMBER$7,strokeWidth:2}),jsxRuntimeExports.jsx("text",{x:M(oe),y:S(r[oe])-10,textAnchor:"middle",fontSize:12,fontWeight:800,fill:AMBER$7,children:r[oe]})]},`carry${oe}`)),s.show_gap&&ne.length>=1&&(()=>{const oe=ne[0],le=Math.max(G,M(oe))+14,ae=Math.min(X,S(r[oe])),ie=Math.max(X,S(r[oe]));return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:le,y1:ae,x2:le,y2:ie,stroke:AMBER$7,strokeWidth:1.75}),jsxRuntimeExports.jsx("line",{x1:le-4,y1:ae,x2:le+4,y2:ae,stroke:AMBER$7,strokeWidth:1.75}),jsxRuntimeExports.jsx("line",{x1:le-4,y1:ie,x2:le+4,y2:ie,stroke:AMBER$7,strokeWidth:1.75})]})})(),jsxRuntimeExports.jsx("line",{x1:G,y1:E,x2:G,y2:X,stroke:AMBER$7,strokeWidth:1.75,strokeDasharray:"4,3",children:jsxRuntimeExports.jsx("animate",{attributeName:"y2",from:E,to:X,dur:"0.45s",fill:"freeze"})}),jsxRuntimeExports.jsx("line",{x1:G,y1:X,x2:T,y2:X,stroke:AMBER$7,strokeWidth:1.75,strokeDasharray:"4,3",children:jsxRuntimeExports.jsx("animate",{attributeName:"x2",from:G,to:T,dur:"0.45s",begin:"0.35s",fill:"freeze"})}),jsxRuntimeExports.jsx("circle",{cx:G,cy:X,r:6.5,fill:AMBER_FILL$2,stroke:AMBER$7,strokeWidth:2,children:jsxRuntimeExports.jsx("animate",{attributeName:"r",from:"4",to:"6.5",dur:"0.35s",fill:"freeze"})}),jsxRuntimeExports.jsx("text",{x:T+4,y:Math.max(X-6,11),textAnchor:"start",fontSize:13,fontWeight:800,fill:AMBER$7,children:r[B]}),Array.from({length:u},(oe,le)=>jsxRuntimeExports.jsx("text",{x:M(le),y:E+18,textAnchor:"middle",fontSize:11,fontWeight:se.has(le)?800:700,fill:se.has(le)?AMBER$7:"#334155",children:o[le]},`xl${le}`)),D]}),s.conclude_label&&jsxRuntimeExports.jsx("div",{className:"wex-lg-value","aria-hidden":"true",children:s.conclude_label})]},e)}const U=Math.min(Math.max(l??Math.floor((u-1)/2),0),u-1),q=r[U],V=M(U),O=S(q),z=t>=1,K=t>=2,Z=t>=3;return jsxRuntimeExports.jsxs("div",{className:"wex-lg-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${A} ${N}`,className:"wex-lg-svg","aria-hidden":"true",children:[F,z&&jsxRuntimeExports.jsx("line",{x1:V,y1:E,x2:V,y2:K?O:E-6,stroke:AMBER$7,strokeWidth:1.75,strokeDasharray:"4,3",children:jsxRuntimeExports.jsx("animate",{attributeName:"y2",from:E,to:K?O:E-6,dur:"0.5s",fill:"freeze"})}),Z&&jsxRuntimeExports.jsx("line",{x1:V,y1:O,x2:T,y2:O,stroke:AMBER$7,strokeWidth:1.75,strokeDasharray:"4,3",children:jsxRuntimeExports.jsx("animate",{attributeName:"x2",from:V,to:T,dur:"0.5s",fill:"freeze"})}),K&&jsxRuntimeExports.jsx("circle",{cx:V,cy:O,r:6.5,fill:AMBER_FILL$2,stroke:AMBER$7,strokeWidth:2,children:jsxRuntimeExports.jsx("animate",{attributeName:"r",from:"4",to:"6.5",dur:"0.35s",fill:"freeze"})}),Z&&jsxRuntimeExports.jsx("text",{x:T+4,y:Math.max(O-6,11),textAnchor:"start",fontSize:13,fontWeight:800,fill:AMBER$7,children:q}),Array.from({length:u},(Q,B)=>jsxRuntimeExports.jsx("text",{x:M(B),y:E+18,textAnchor:"middle",fontSize:11,fontWeight:B===U&&z?800:
71700,fill:B===U&&z?AMBER$7:"#334155",children:o[B]},`xl${B}`)),D]}),t===3&&jsxRuntimeExports.jsxs("div",{className:"wex-lg-value","aria-hidden":"true",children:[o[U],": ",q]})]},e)}const SECTOR_FILLS=["#93b4e6","#f4c99a","#a9d8bf","#cdb0e0","#eeb0bd","#efd99a"],AMBER$6="#fbbf24",AMBER_DEEP$2="#b45309",INK$1="#33373d",KNOWN_FRACTIONS=[[.5,[1,2]],[.25,[1,4]],[.75,[3,4]],[1/3,[1,3]],[2/3,[2,3]],[.125,[1,8]],[.375,[3,8]],[.625,[5,8]],[.875,[7,8]],[.1,[1,10]],[.2,[1,5]],[.3,[3,10]],[.4,[2,5]],[.6,[3,5]],[.7,[7,10]],[.8,[4,5]],[.9,[9,10]]];function resolveFraction(n,t){if(n.fraction!=null&&typeof n.fraction=="object"){const{num:o,den:r}=n.fraction;return{num:o,den:r,frac:r>0?o/r:0}}let e=null;if(typeof n.fraction=="number"?e=n.fraction:n.value!=null&&t&&t>0&&(e=n.value/t),e==null)return{num:0,den:1,frac:0};const s=KNOWN_FRACTIONS.find(([o])=>Math.abs(o-e)<.01);return s?{num:s[1][0],den:s[1][1],frac:e}:{num:Math.round(e*100),den:100,frac:e}}function sectorWeight(n){if(n.fraction!=null){if(typeof n.fraction=="number")return Math.max(0,n.fraction);const{num:t,den:e}=n.fraction;return e>0?Math.max(0,t/e):0}return n.value!=null?Math.max(0,n.value):1}function polar(n,t,e,s){return[n+e*Math.sin(s),t-e*Math.cos(s)]}function PieChartFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="pie_chart")return null;const s=n.visual_data,o=s.sectors.slice(0,6);if(o.length===0)return null;const r=s.total,a=Math.min(Math.max(s.highlight_index??0,0),o.length-1),i=o.map(sectorWeight),l=i.reduce((S,M)=>S+M,0)||1,u=210,d=u/2,m=u/2,h=78,f=h+12;let g=0;const b=o.map((S,M)=>{const L=i[M]/l,P=g*2*Math.PI,F=(g+L)*2*Math.PI,D=(P+F)/2;g+=L;const[U,q]=polar(d,m,h,P),[V,O]=polar(d,m,h,F),z=L>.5?1:0,K=`M ${d} ${m} L ${U.toFixed(2)} ${q.toFixed(2)} A ${h} ${h} 0 ${z} 1 ${V.toFixed(2)} ${O.toFixed(2)} Z`,[Z,Q]=polar(0,0,8,D),B=colourForLabel(S.label);return{s:S,path:K,mid:D,fill:B??SECTOR_FILLS[M%SECTOR_FILLS.length],named:B,ox:Z,oy:Q}}),x=resolveFraction(o[a],r),y=o[a].value!=null?o[a].value:r!=null?Math.round(x.frac*r):null,[E,k]=polar(d,m,f,b[a].mid),T=E>d+2?"start":E<d-2?"end":"middle";let A=`0 0 ${u} ${u}`;{const S=[],M=(F,D,U,q)=>{const V=U.length*q*.62;D==="start"?S.push([F,F+V]):D==="end"?S.push([F-V,F]):S.push([F-V/2,F+V/2])};b.forEach(F=>{const[D]=polar(d,m,f,F.mid),U=D>d+2?"start":D<d-2?"end":"middle";M(D,U,F.s.label??"",11)}),M(E,T,`${x.num}/${x.den}`,14);const L=Math.min(0,...S.map(F=>F[0]))-4,P=Math.max(u,...S.map(F=>F[1]))+4;A=`${L.toFixed(1)} 0 ${(P-L).toFixed(1)} ${u}`}const N=s.guide_divisions!=null&&s.guide_divisions>=2&&s.guide_divisions<=12?Math.round(s.guide_divisions):null;return jsxRuntimeExports.jsxs("div",{className:"wex-pie-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:A,className:"wex-pie-svg","aria-hidden":"true",children:[b.map((S,M)=>{const L=M===a,P=L&&t>=2;return jsxRuntimeExports.jsx("g",{transform:P?`translate(${S.ox.toFixed(2)} ${S.oy.toFixed(2)})`:void 0,children:jsxRuntimeExports.jsx("path",{d:S.path,fill:P&&!S.named?AMBER$6:S.fill,stroke:P&&S.named?AMBER_DEEP$2:"#ffffff",strokeWidth:P&&S.named?4:2,opacity:t>=2&&!L?.55:1})},M)}),jsxRuntimeExports.jsx("circle",{cx:d,cy:m,r:h,fill:"none",stroke:INK$1,strokeWidth:1.5}),N!=null&&Array.from({length:N},(S,M)=>{const[L,P]=polar(d,m,h,M/N*2*Math.PI);return jsxRuntimeExports.jsx("line",{x1:d,y1:m,x2:L.toFixed(2),y2:P.toFixed(2),stroke:INK$1,strokeWidth:1,strokeDasharray:"3 4",opacity:.45},`gd${M}`)}),t===1&&r!=null&&jsxRuntimeExports.jsx("text",{x:d,y:m+5,textAnchor:"middle",fontSize:16,fontWeight:800,fill:INK$1,children:r}),t>=2&&x.den>0&&jsxRuntimeExports.jsxs("text",{x:T==="start"?E+2:T==="end"?E-2:E,y:k+4,textAnchor:T,fontSize:14,fontWeight:800,fill:AMBER_DEEP$2,children:[x.num,"/",x.den]}),b.map((S,M)=>{if(M===a&&t>=2)return null;const[L,P]=polar(d,m,f,S.mid),F=L>d+2?"start":L<d-2?"end":"middle";return jsxRuntimeExports.jsx("text",{x:L,y:P+4,textAnchor:F,fontSize:11,fontWeight:700,fill:"#475569",children:S.s.label},`cl${M}`)})]}),t===3&&r!=null&&y!=null&&jsxRuntimeExports.jsxs("div",{className:"wex-pie-total","aria-hidden":"true",children:[x.num,"/",x.den," of ",r," = ",y]})]},e)}const BLUE$9="#3b82f6",BLUE_FAINT="#94a3b8",AMBER$5="#f59e0b",AMBER_DEEP$1="#d97706",MIRROR="#7c3aed",AXIS$2="#475569",GRID$1="#e2e8f0",GREEN$2="#16a34a",GREEN_FAINT="#86efac",CRIMSON="#dc2626",CELL$1=30,PAD=16;function ReflectionGridFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="reflection_grid")return null;const{grid:s,mirror:o,shape_points:r,given_points:a,axes:i,measure_point:l}=n.visual_data,u=o.axis==="vertical",d=o.position,m=([$e,Re])=>u?[2*d-$e,Re]:[$e,2*d-Re],h=([$e,Re])=>Math.abs(u?$e-d:Re-d),f=r.map(m),g=[0,s.w,...r.map($e=>$e[0]),...f.map($e=>$e[0])],b=[0,s.h,...r.map($e=>$e[1]),...f.map($e=>$e[1])];a&&(g.push(...a.map($e=>
71$e[0])),b.push(...a.map($e=>$e[1])));const x=Math.min(...g),y=Math.max(...g),E=Math.min(...b),k=Math.max(...b),T=(y-x)*CELL$1,A=(k-E)*CELL$1,N=i?14:0,S=PAD+N,M=PAD+N,L=S+T+PAD,P=PAD+A+M,F=S-x*CELL$1,D=PAD+k*CELL$1,U=$e=>F+$e*CELL$1,q=$e=>D-$e*CELL$1,V=Array.from({length:s.w+1},($e,Re)=>Re),O=Array.from({length:s.h+1},($e,Re)=>Re),z=l?r.findIndex(([$e,Re])=>$e===l[0]&&Re===l[1]):-1;let K=0;if(z!==-1)K=z;else for(let $e=1;$e<r.length;$e++)h(r[$e])>h(r[K])&&(K=$e);const Z=$e=>$e.map(([Re,ye])=>`${U(Re)},${q(ye)}`).join(" "),Q=t>=2,B=t===3,G=!!a&&a.length===r.length,X=a,ne=G?r.findIndex(($e,Re)=>{const ye=m($e),Ne=X[Re];return ye[0]!==Ne[0]||ye[1]!==Ne[1]}):-1,se=ne!==-1?ne:K,te=r[se],oe=h(te),le=u?[d,te[1]]:[te[0],d],ae=m(te),ie=G?r.every(([$e,Re],ye)=>{const[Ne,Ye]=m([$e,Re]),[Ge,Fe]=X[ye];return Ne===Ge&&Ye===Fe}):null,we=G?X[se]:null,ke=we?h(we):0,Te=we?u?[d,we[1]]:[we[0],d]:null,Se=G?t===1?"A shape and a candidate (green) shape on a squared grid beside a mirror line.":t===2?"Testing the candidate's corner against the mirror line.":ie?"The candidate shape is a correct reflection — same distance, opposite side.":"The candidate shape is NOT a correct reflection.":t===1?"A shape on a squared grid beside a mirror line.":t===2?`The chosen corner is ${oe} square${oe===1?"":"s"} from the mirror line.`:"The reflected shape, each corner the same distance on the other side.";return jsxRuntimeExports.jsxs("div",{className:"wex-refl-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${L} ${P}`,className:"wex-refl-svg",role:"img","aria-label":Se,children:[V.map($e=>jsxRuntimeExports.jsx("line",{x1:U($e),y1:q(0),x2:U($e),y2:q(s.h),stroke:GRID$1,strokeWidth:1},`v${$e}`)),O.map($e=>jsxRuntimeExports.jsx("line",{x1:U(0),y1:q($e),x2:U(s.w),y2:q($e),stroke:GRID$1,strokeWidth:1},`h${$e}`)),u?jsxRuntimeExports.jsx("line",{x1:U(d),y1:q(0),x2:U(d),y2:q(s.h),stroke:MIRROR,strokeWidth:3,strokeDasharray:"7 5",strokeLinecap:"round",children:t===1&&jsxRuntimeExports.jsx("animate",{attributeName:"stroke-width",values:"3;6;3",dur:"1.1s",repeatCount:"2"})}):jsxRuntimeExports.jsx("line",{x1:U(0),y1:q(d),x2:U(s.w),y2:q(d),stroke:MIRROR,strokeWidth:3,strokeDasharray:"7 5",strokeLinecap:"round",children:t===1&&jsxRuntimeExports.jsx("animate",{attributeName:"stroke-width",values:"3;6;3",dur:"1.1s",repeatCount:"2"})}),jsxRuntimeExports.jsx("polygon",{points:Z(r),fill:BLUE$9,fillOpacity:B?.16:.28,stroke:B?BLUE_FAINT:BLUE$9,strokeWidth:2.5,strokeLinejoin:"round"}),G&&jsxRuntimeExports.jsx("polygon",{points:Z(X),fill:GREEN$2,fillOpacity:B?.32:.24,stroke:B&&ie===!1?GREEN_FAINT:GREEN$2,strokeWidth:2.5,strokeLinejoin:"round"}),Q&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:U(te[0]),y1:q(te[1]),x2:U(le[0]),y2:q(le[1]),stroke:AMBER_DEEP$1,strokeWidth:2.5,strokeDasharray:"4 3",strokeLinecap:"round",children:t===2&&jsxRuntimeExports.jsx("animate",{attributeName:"stroke-opacity",from:"0",to:"1",dur:"0.5s",fill:"freeze"})}),jsxRuntimeExports.jsx("circle",{cx:U(te[0]),cy:q(te[1]),r:5,fill:AMBER_DEEP$1}),jsxRuntimeExports.jsx("text",{x:(U(te[0])+U(le[0]))/2+(u?0:10),y:(q(te[1])+q(le[1]))/2-(u?8:0),textAnchor:"middle",fontSize:13,fontWeight:800,fill:AMBER_DEEP$1,children:oe})]}),G&&Q&&we&&Te&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:U(we[0]),y1:q(we[1]),x2:U(Te[0]),y2:q(Te[1]),stroke:ie?AMBER_DEEP$1:CRIMSON,strokeWidth:2.5,strokeDasharray:"4 3",strokeLinecap:"round",children:t===2&&jsxRuntimeExports.jsx("animate",{attributeName:"stroke-opacity",from:"0",to:"1",dur:"0.5s",fill:"freeze"})}),jsxRuntimeExports.jsx("circle",{cx:U(we[0]),cy:q(we[1]),r:5,fill:ie?AMBER_DEEP$1:CRIMSON}),jsxRuntimeExports.jsx("text",{x:(U(we[0])+U(Te[0]))/2+(u?0:10),y:(q(we[1])+q(Te[1]))/2-(u?8:0),textAnchor:"middle",fontSize:13,fontWeight:800,fill:ie?AMBER_DEEP$1:CRIMSON,children:ke})]}),B&&!G&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:U(le[0]),y1:q(le[1]),x2:U(ae[0]),y2:q(ae[1]),stroke:AMBER$5,strokeWidth:2.5,strokeDasharray:"4 3",strokeLinecap:"round",children:jsxRuntimeExports.jsx("animate",{attributeName:"stroke-opacity",from:"0",to:"1",dur:"0.5s",fill:"freeze"})}),jsxRuntimeExports.jsx("text",{x:(U(le[0])+U(ae[0]))/2+(u?0:10),y:(q(le[1])+q(ae[1]))/2-(u?8:0),textAnchor:"middle",fontSize:13,fontWeight:800,fill:AMBER_DEEP$1,children:oe}),jsxRuntimeExports.jsx("polygon",{points:Z(f),fill:BLUE$9,fillOpacity:.3,stroke:BLUE$9,strokeWidth:2.5,strokeLinejoin:"round",children:jsxRuntimeExports.jsx("animate",{attributeName:"fill-opacity",from:"0",to:"0.3",dur:"0.5s",begin:"0.3s",fill:"freeze"})}),jsxRuntimeExports.jsx("circle",{cx:U(ae[0]),cy:q(ae[1]),r:5,fill:BLUE$9,stroke:"#fff",strokeWidth:1.5})]}),B&&G&&ie===!1&&jsxRuntimeExports.jsx("polygon",{points:Z(f),fill:"none",stroke:BLUE$9,strokeWidth:2,strokeDasharray:"3 3",children:jsxRuntimeExports.jsx("animate",{attributeName:"stroke-opacity",from:"0",to:"1",dur:"0.5s",fill:"freeze"})}),jsxRuntimeExports.jsx("line",{x1:U(0),y1:q(0),x2:U(s.w),y2:q(0),stroke:AXIS$2,strokeWidth:1.5}),jsxRuntimeExports.jsx("line",{x1:U(0),y1:q(0),x2:U(0),y2:q(s.h),stroke:AXIS$2,strokeWidth:1.5}),i&&V.map($e=>jsxRuntimeExports.jsx("text",{x:U($e),y:q(0)+12,textAnchor:"middle",fontSize:9,fontWeight:600,fill:AXIS$2,children:$e},`xt${$e}`)),i&&O.map($e=>jsxRuntimeExports.jsx("text",{x:U(0)-7,y:q($e)+3,textAnchor:"end",fontSize:9,fontWeight:600,fill:AXIS$2,children:$e},`yt${$e}`))]}),G?B?jsxRuntimeExports.jsx("div",{className:"wex-refl-note","aria-hidden":"true",children:ie?`Same distance both 
71sides: ${oe} ↔ ${ke} squares — correct.`:`${oe} squares blue vs ${ke} squares green — not a correct reflection.`}):t===2?jsxRuntimeExports.jsxs("div",{className:"wex-refl-note","aria-hidden":"true",children:["Blue: ",oe," squares · Green: ",ke," squares"]}):null:B?jsxRuntimeExports.jsxs("div",{className:"wex-refl-note","aria-hidden":"true",children:["Same distance both sides: ",oe," ↔ ",oe," squares"]}):t===2?jsxRuntimeExports.jsxs("div",{className:"wex-refl-note","aria-hidden":"true",children:[oe," square",oe===1?"":"s"," to the mirror"]}):null]},e)}const INK="#1f2733",AMBER_DEEP="#d97706",FILL="#dbeafe",ARC_STROKES=["#2563c9","#d97706","#7c3aed"];function TriangleFigureFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="triangle_figure")return null;const s=n.visual_data,{angles:o,labels:r,equal_side_marks:a,mark_right_angle:i=!1,rotation:l=0}=s;if(!Array.isArray(o)||o.length!==3||Math.abs(o[0]+o[1]+o[2]-180)>1)return null;const u=/classify/i.test(n.animation??""),d=ge=>ge*Math.PI/180,m=Math.sin(d(o[2])),h=Math.sin(d(o[1])),f=[[0,0],[m,0],[h*Math.cos(d(o[0])),h*Math.sin(d(o[0]))]],g=(f[0][0]+f[1][0]+f[2][0])/3,b=(f[0][1]+f[1][1]+f[2][1])/3,x=d(l),y=Math.cos(x),E=Math.sin(x),k=f.map(([ge,Ie])=>{const ft=ge-g,wt=Ie-b;return[g+ft*y-wt*E,b+ft*E+wt*y]}),T=220,N=T-2*36,S=Math.min(...k.map(ge=>ge[0])),M=Math.max(...k.map(ge=>ge[0])),L=Math.min(...k.map(ge=>ge[1])),P=Math.max(...k.map(ge=>ge[1])),F=Math.max(M-S,1e-6),D=Math.max(P-L,1e-6),U=Math.min(N/F,N/D),q=(T-F*U)/2,V=(T-D*U)/2,O=k.map(([ge,Ie])=>[q+(ge-S)*U,T-V-(Ie-L)*U]),z=O.map(([ge,Ie])=>`${ge},${Ie}`).join(" "),K=(ge,Ie)=>Math.atan2(Ie[1]-ge[1],Ie[0]-ge[0]),Z=ge=>{let Ie=ge;for(;Ie<=-Math.PI;)Ie+=2*Math.PI;for(;Ie>Math.PI;)Ie-=2*Math.PI;return Ie},Q=22,B=34,G=O.map((ge,Ie)=>{const ft=O[(Ie+2)%3],wt=O[(Ie+1)%3],mt=K(ge,wt),et=K(ge,ft),Ke=Z(et-mt),Pt=mt+Ke/2,ht=[ge[0]+Q*Math.cos(mt),ge[1]+Q*Math.sin(mt)],lt=[ge[0]+Q*Math.cos(et),ge[1]+Q*Math.sin(et)],ct=Ke>0?1:0,$t=`M ${ht[0]} ${ht[1]} A ${Q} ${Q} 0 0 ${ct} ${lt[0]} ${lt[1]}`,ot=o[Ie]<45?-22:B,nn=[Math.max(20,Math.min(T-20,ge[0]+ot*Math.cos(Pt))),Math.max(16,Math.min(T-20,ge[1]+ot*Math.sin(Pt)))],It=[Math.cos(mt),Math.sin(mt)],ln=[Math.cos(et),Math.sin(et)],Et=15,Qe=`M ${ge[0]+Et*It[0]} ${ge[1]+Et*It[1]} L ${ge[0]+Et*(It[0]+ln[0])} ${ge[1]+Et*(It[1]+ln[1])} L ${ge[0]+Et*ln[0]} ${ge[1]+Et*ln[1]}`;return{arcPath:$t,labelPos:nn,sq:Qe,isRight:Math.abs(o[Ie]-90)<.5}}),X=12,ne=5,se=u?t>=2:!0,te=[];se&&(a??[]).forEach((ge,Ie)=>{const ft=Ie+1;ge.forEach(wt=>{const mt=O[wt%3],et=O[(wt+1)%3],Ke=[(mt[0]+et[0])/2,(mt[1]+et[1])/2],Pt=Math.hypot(et[0]-mt[0],et[1]-mt[1])||1,ht=[(et[0]-mt[0])/Pt,(et[1]-mt[1])/Pt],lt=[-ht[1],ht[0]];for(let ct=0;ct<ft;ct++){const $t=(ct-(ft-1)/2)*ne,ot=[Ke[0]+$t*ht[0],Ke[1]+$t*ht[1]];te.push({x1:ot[0]-X/2*lt[0],y1:ot[1]-X/2*lt[1],x2:ot[0]+X/2*lt[0],y2:ot[1]+X/2*lt[1],key:`t${wt}-${ct}`})}})});const oe=r.findIndex(ge=>ge==="?"),le=[0,1,2].filter(ge=>ge!==oe&&r[ge]!=null),ae=le.reduce((ge,Ie)=>ge+o[Ie],0),ie=oe>=0?Math.round(o[oe]):0,we=le.map(ge=>Math.round(o[ge])),ke=!u&&t===3&&oe>=0,Te=ge=>ge===oe&&!u?ke?`${ie}°`:"?":r[ge],Se=ge=>{if(!u)return ge%3;const Ie=o.findIndex(ft=>Math.abs(ft-o[ge])<.5);return(Ie<0?ge:Ie)%3},$e=(a??[]).length>0||i||o.some(ge=>Math.abs(ge-90)<.5),Re=new Set(o.map(ge=>Math.round(ge))),ye=(a??[]).map(ge=>ge.length),Ne=ye.length?Math.max(...ye):0,Ye=!/classify_sides/.test(n.animation??"")&&(i||o.some(ge=>Math.abs(ge-90)<.5)),Ge=Ne>=3||Re.size===1,Fe=!Ge&&(Ne===2||Re.size===2),Xe=Ge?"Three equal sides → equilateral":Ye?"A right angle → right-angled":Fe?"Two equal sides → isosceles":"All sides different → scalene",Rt=()=>{const et=ht=>[90+40*Math.cos(d(ht)),50-40*Math.sin(d(ht))];let Ke=0;const Pt=o.map((ht,lt)=>{const ct=Ke;Ke+=ht;const $t=Ke,[ot,nn]=et(ct),[It,ln]=et($t);return jsxRuntimeExports.jsx("path",{d:`M 90 50 L ${ot} ${nn} A 40 40 0 0 0 ${It} ${ln} Z`,fill:ARC_STROKES[lt%3],fillOpacity:.32,stroke:ARC_STROKES[lt%3],strokeWidth:1.5},`w${lt}`)});return jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 180 60",width:180,height:60,role:"img","aria-label":"The three angles form a straight line: 180 degrees.",children:[jsxRuntimeExports.jsx("line",{x1:50,y1:50,x2:130,y2:50,stroke:INK,strokeWidth:2.5,strokeLinecap:"round"}),Pt,jsxRuntimeExports.jsx("text",{x:134,y:54,fontSize:13,fontWeight:800,fill:INK,children:"180°"})]})};return jsxRuntimeExports.jsxs("div",{className:"wex-tri-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${T} ${T}`,className:"wex-tri-svg",role:"img","aria-label":"A triangle with labelled angles.",children:[jsxRuntimeExports.jsx("polygon",{points:z,fill:FILL,fillOpacity:.55,stroke:INK,strokeWidth:2.5,strokeLinejoin:"round"}),G.map((ge,Ie)=>jsxRuntimeExports.jsx("path",{d:ge.arcPath,fill:"none",stroke:ARC_STROKES[Se(Ie)],strokeWidth:Ie===oe&&!u?3:2},`arc${Ie}`)),(i||u)&&G.map((ge,Ie)=>ge.isRight&&(u?t>=2:i)?jsxRuntimeExports.jsx("path",{d:ge.sq,fill:"none",stroke:u?AMBER_DEEP:INK,strokeWidth:2.5},`sq${Ie}`):null),te.map(ge=>jsxRuntimeExports.jsx("line",{x1:ge.x1,y1:ge.y1,x2:ge.x2,y2:ge.y2,stroke:u?AMBER_DEEP:INK,strokeWidth:u?3:2,strokeLinecap:"round"},ge.key)),s.side_labels?.map((ge,Ie)=>{if(!ge)return null;
71const ft=O[Ie],wt=O[(Ie+1)%3],mt=O[(Ie+2)%3],et=(ft[0]+wt[0])/2,Ke=(ft[1]+wt[1])/2,Pt=et-mt[0],ht=Ke-mt[1],lt=Math.hypot(Pt,ht)||1;return jsxRuntimeExports.jsx("text",{x:Math.max(23,Math.min(T-23,et+Pt/lt*21)),y:Math.max(14,Math.min(T-8,Ke+ht/lt*21+4)),textAnchor:"middle",fontSize:13,fontWeight:700,fill:INK,stroke:"white",strokeWidth:4,paintOrder:"stroke",children:ge},`side-label-${Ie}`)}),G.map((ge,Ie)=>{const ft=Te(Ie);if(ft==null)return null;const wt=ft==="?"||Ie===oe&&ke;return jsxRuntimeExports.jsx("text",{stroke:"white",strokeWidth:4,paintOrder:"stroke",x:ge.labelPos[0],y:ge.labelPos[1]+5,textAnchor:"middle",fontSize:16,fontWeight:800,fill:wt?AMBER_DEEP:INK,children:ft},`lbl${Ie}`)})]}),u?t===3?jsxRuntimeExports.jsx("div",{className:"wex-tri-note","aria-hidden":"true",children:Xe}):t===2?jsxRuntimeExports.jsx("div",{className:"wex-tri-note","aria-hidden":"true",children:s.side_labels?"Compare the side lengths…":$e?"Look at the marks…":"Compare the angles…"}):null:t===1?Rt():t===2&&le.length>=1?jsxRuntimeExports.jsxs("div",{className:"wex-tri-note","aria-hidden":"true",children:[we.join("° + "),"° = ",ae,"°"]}):ke?jsxRuntimeExports.jsxs("div",{className:"wex-tri-note","aria-hidden":"true",children:["180° − ",ae,"° = ",ie,"°"]}):null]},e)}const CELL=22,PAD_TOP=22,PAD_BOTTOM=30,PAD_LEFT=44,PAD_RIGHT=20,BODY="#dbeafe",BORDER="#1e3a8a",AMBER_FILL$1="#fbbf24",AMBER_LIGHT="#fef3c7",AMBER_LINE="#d97706",AMBER_TEXT="#b45309",LABEL$1="#475569";function triangleAreaFrameModel(n,t,e){const s=Math.max(1,Math.min(Math.round(n),20)),o=Math.max(1,Math.min(Math.round(t),12)),r=s*o/2,a=e>=2,i=e===3;return{base:s,height:o,area:r,showHalves:a,showResult:i,formulaLabel:i?`${s} × ${o} ÷ 2 = ${r}`:null}}function TriangleAreaFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="triangle_area")return null;const s=n.visual_data,o=s.unit_label??"cm",r=s.orientation??"right",{base:a,height:i,area:l,showHalves:u,showResult:d,formulaLabel:m}=triangleAreaFrameModel(s.base,s.height,t),h=a*CELL,f=i*CELL,g=PAD_LEFT,b=PAD_TOP,x=PAD_LEFT+h+PAD_RIGHT,y=PAD_TOP+f+PAD_BOTTOM,E=Math.max(200,Math.min(x,340)),k=b+f,T=U=>`${U}${o?` ${o}`:""}`,A={x:g,y:k},N={x:g+h,y:k},M={x:g+(r==="right"?0:.36)*h,y:b},L={x:g,y:b},P={x:g+h,y:b},F=`${A.x},${A.y} ${N.x},${N.y} ${M.x},${M.y}`,D=r==="right"?[`${N.x},${N.y} ${P.x},${P.y} ${M.x},${M.y}`]:[`${A.x},${A.y} ${L.x},${L.y} ${M.x},${M.y}`,`${M.x},${M.y} ${P.x},${P.y} ${N.x},${N.y}`];return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${x} ${y}`,className:"wex-fade-enter",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${E}px`,height:"auto"},role:"img","aria-label":`A triangle with base ${T(a)} and height ${T(i)}, boxed inside its enclosing rectangle.`,children:[jsxRuntimeExports.jsx("rect",{x:g,y:b,width:h,height:f,fill:"none",stroke:u?AMBER_LINE:"#94a3b8",strokeWidth:u?2:1.5,strokeDasharray:u?void 0:"4 3"}),u&&D.map((U,q)=>jsxRuntimeExports.jsx("polygon",{points:U,fill:AMBER_LIGHT,stroke:AMBER_LINE,strokeWidth:1},`comp-${q}`)),jsxRuntimeExports.jsx("polygon",{points:F,fill:u?AMBER_FILL$1:BODY,stroke:BORDER,strokeWidth:2,strokeLinejoin:"round"}),s.show_grid&&jsxRuntimeExports.jsxs("g",{"aria-hidden":"true",children:[Array.from({length:a-1},(U,q)=>jsxRuntimeExports.jsx("line",{x1:g+(q+1)*CELL,y1:b,x2:g+(q+1)*CELL,y2:k,stroke:"#64748b",strokeWidth:.75,opacity:.45},`gv${q}`)),Array.from({length:i-1},(U,q)=>jsxRuntimeExports.jsx("line",{x1:g,y1:b+(q+1)*CELL,x2:g+h,y2:b+(q+1)*CELL,stroke:"#64748b",strokeWidth:.75,opacity:.45},`gh${q}`))]}),jsxRuntimeExports.jsx("line",{x1:M.x,y1:M.y,x2:M.x,y2:k,stroke:AMBER_TEXT,strokeWidth:2,strokeDasharray:"5 4"}),jsxRuntimeExports.jsx("text",{x:r==="right"?g-14:M.x+12,y:(M.y+k)/2,textAnchor:"middle",fontSize:14,fontWeight:700,fill:AMBER_TEXT,stroke:"#ffffff",strokeWidth:3.5,paintOrder:"stroke",transform:r==="right"?`rotate(-90 ${g-14} ${(M.y+k)/2})`:void 0,children:T(i)}),jsxRuntimeExports.jsx("text",{x:(A.x+N.x)/2,y:k+20,textAnchor:"middle",fontSize:14,fontWeight:700,fill:LABEL$1,children:T(a)}),u&&!d&&jsxRuntimeExports.jsx("text",{x:g+h/2,y:b-8,textAnchor:"middle",fontSize:11,fontWeight:700,fill:AMBER_TEXT,children:"half of the rectangle"})]}),m&&jsxRuntimeExports.jsxs("div",{className:"wex-total",children:[a," × ",i," ÷ 2 = ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:l})]},`total-${e}`)]},e)}function LongMultiplicationFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="long_multiplication")return null;const{top:s,bottom:o,show_rows:r}=n.visual_data,a=Math.max(10,Math.min(Math.round(Math.abs(s)),99)),i=Math.max(10,Math.min(Math.round(Math.abs(o)),99)),l=String(a),u=i%10,d=Math.floor(i/10),m=a*u,h=a*d,f=a*i,g=String(m),b=String(h),x=String(f),y=Math.max(l.length,String(i).length,g.length,b.length+1,x.length),E=typeof r=="number"?Math.max(0,Math.min(Math.round(r),3)):t,k=E>=1,T=E>=2,A=E>=3,N=r===void 0&&t===1,S=r===void 0&&t===2,M=(Re,ye)=>{const Ne=Re.split("").reverse().map(Number);let Ye=0;const Ge={};for(let Fe=0;Fe<Ne.length;Fe++){const Xe=Ne[Fe]*ye+Ye;Ye=Math.floor(Xe/10),Ye>0&&Fe+1<Ne.length&&(Ge[Fe+1]=String(Ye))}return Ge},L=M(l,u),P=M(l,d),F=32,D=28,U=12,q=14,V=42,O=26,z="ui-monospace, 'SFMono-Regular', Menlo, monospace",K=U+D,Z=y*F,Q=K+Z+U,B=q+6,G=q+30,X=G+V,ne=X+12,se=ne+V,te=se+V,oe=te+12,le=oe+V,ae=le+14,ie=Re=>K+(y-1-Re)*F+F/2,we="#475569",ke="#3b82f6",Te="#d97706",Se=(Re,ye,Ne,Ye=0,Ge=!1)=>Re.split("").reverse().map((Fe,Xe)=>jsxRuntimeExports.jsx("text",{x:ie(Xe+Ye),y:ye,textAnchor:"middle",fontSize:O,fontWeight:Ge?800:700,fontFamily:z,fill:Ne,className:"wex-fade-enter",children:Fe},`${ye}-${Xe}`)),$e=Re=>Object.entries(Re).map(([ye,Ne])=>jsxRuntimeExports.jsx("text",{x:ie(Number(ye)),y:B,textAnchor:"middle",fontSize:15,fontWeight:800,fontFamily:z,fill:Te,className:"wex-fade-enter",children:Ne},`carry-${ye}`));return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${Q} ${ae}`,className:"wex-fade-enter",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${Math.min(Q,240)}px`,height:"auto"},"aria-hidden":"true",children:[N&&$e(L),S&&$e(P),l.split("").reverse().map((Re,ye)=>{const Ne=N&&ye===0||S&&ye===1;return jsxRuntimeExports.jsx("text",{x:ie(ye),y:G,textAnchor:"middle",fontSize:O,fontWeight:Ne?800:700,fontFamily:z,fill:Ne?ke:we,children:Re},`t-${ye}`)}),jsxRuntimeExports.jsx("text",{x:U+D/2,y:X,textAnchor:"middle",fontSize:O,fontWeight:700,fontFamily:z,fill:we,children:"×"}),String(i).split("").reverse().map((Re,ye)=>{const Ne=N&&ye===0||S&&ye===1;return jsxRuntimeExports.jsx("text",{x:ie(ye),y:X,textAnchor:"middle",fontSize:O,fontWeight:Ne?800:700,fontFamily:z,fill:Ne?ke:we,children:Re},`b-${ye}`)}),jsxRuntimeExports.jsx("line",{x1:U,y1:ne,x2:K+Z,y2:ne,stroke:we,strokeWidth:2.5}),k&&Se(g,se,N?ke:we,0,N),T&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("text",{x:ie(0),y:te,textAnchor:"middle",fontSize:O,fontWeight:800,fontFamily:z,fill:Te,className:"wex-fade-enter",style:{transformBox:"fill-box",transformOrigin:"center"},children:"0"}),S&&jsxRuntimeExports.jsx("rect",{x:ie(0)-F/2+3,y:te-O,width:F-6,height:O+8,rx:5,fill:"none",stroke:Te,strokeWidth:2,strokeDasharray:"4 3"}),Se(b,te,S?ke:we,1,S)]}),jsxRuntimeExports.jsx("line",{x1:U,y1:oe,x2:K+Z,y2:oe,stroke:we,strokeWidth:2.5}),A&&Se(x,le,Te,0,!0)]}),A&&jsxRuntimeExports.jsxs("div",{className:"wex-total","aria-hidden":"true",children:[a," × ",i," = ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:f})]})]},e)}function LongDivisionFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="long_division")return null;const s=n.visual_data,o=Math.max(10,Math.min(Math.abs(Math.round(s.divisor)),99)),r=Math.max(100,Math.min(Math.abs(Math.round(s.dividend)),99999)),a=React.useRef(0),i=countSteps(r,o);t===1&&(a.current=i);const l=a.current||i,u=t===1?1:t===2?Math.min(2,l):l,d=Math.floor(r/o),m=r%o;return jsxRuntimeExports.jsxs("div",{className:"wex-ldiv-wrap wex-ldiv-fade",children:[jsxRuntimeExports.jsx(LongDivision,{data:{type:"long_division",dividend:r,divisor:o,mode:"complete",visible_steps:u}}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-ldiv-answer","aria-hidden":"true",children:s.final_badge_override!=null?s.final_badge_override:jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[r," ÷ ",o," = ",d,m>0?` r ${m}`:""]})})]},`${e}-${t}`)}function countSteps(n,t){const e=String(n).split("").map(Number);let s=0,o=!1,r=0;for(const a of e)s=s*10+a,Math.floor(s/t)>0&&(o=!0),o&&r++,s=s%t;return Math.max(1,r)}const MAX_PX=132,MIN_PX=40,DEPTH_F=.6,GRID_CAP=12,FRONT="#dbeafe",TOP$1="#93c5fd",SIDE="#60a5fa",EDGE$1="#1e3a8a",GRID="#1e3a8a",LAYER_FILL="#fde68a",LAYER_EDGE="#d97706",LABEL="#1f2733";function VolumeCuboidFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="volume_cuboid")return null;const s=n.visual_data,o=Math.max(1,Math.round(s.length)),r=Math.max(1,Math.round(s.width)),a=Math.max(1,Math.round(s.height)),i=s.unit??"cm",l=Math.max(o,r,a),u=MAX_PX/l,d=Se=>Math.max(MIN_PX,Math.min(MAX_PX,Se*u)),m=d(o),h=d(a),f=d(r),g=h/a,b=f*DEPTH_F,x=-f*DEPTH_F,y=44,E=30,k=26,T=28,A=y,N=k+h+Math.abs(x),S=y+m+b+E,M=N+T,L=(Se,$e=1)=>({x:Se.x+b*$e,y:Se.y+x*$e}),P=(...Se)=>Se.map($e=>`${$e.x},${$e.y}`).join(" "),F=Se=>({x:A,y:N-Se*g}),D=Se=>({x:A+m,y:N-Se*g}),U=(Se,$e,Re,ye,Ne,Ye)=>{const Ge=[];for(let Fe=1;Fe<Re;Fe++){const Xe=Fe/Re,Rt={x:Se.x+Xe*$e.x,y:Se.y+Xe*$e.y};Ge.push(jsxRuntimeExports.jsx("line",{x1:Rt.x,y1:Rt.y,x2:Rt.x+ye.x,y2:Rt.y+ye.y,stroke:GRID,strokeWidth:1,strokeOpacity:.32},`${Ye}u${Fe}`))}for(let Fe=1;Fe<Ne;Fe++){const Xe=Fe/Ne,Rt={x:Se.x+Xe*ye.x,y:Se.y+Xe*ye.y};Ge.push(jsxRuntimeExports.jsx("line",{x1:Rt.x,y1:Rt.y,x2:Rt.x+$e.x,y2:Rt.y+$e.y,stroke:GRID,strokeWidth:1,strokeOpacity:.32},`${Ye}v${Fe}`))}return Ge},q={x:m,y:0},V={x:b,y:x}
71,O=t===1?1:a,z=t!==3,K=o*r,Z=o*r*a;let Q;t===1?Q=`${o} × ${r} = ${K}`:t===2?Q=`${K} × ${a}`:Q=`${Z} ${i}³`;const B=F(0),G=D(0),X=F(O),ne=D(O),se=L(X),te=L(ne),oe=L(G);L(B);const le=F(a),ae=D(a),ie=L(le),we=L(ae),ke=L(B),Te=t===1?"wex-vc-slab":t===2?"wex-vc-stack":"";return jsxRuntimeExports.jsxs("div",{className:"wex-vc-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${S} ${M}`,width:"100%",className:"wex-vc-svg",role:"img","aria-label":`A cuboid ${o} by ${r} by ${a} ${i}, built from unit cubes layer by layer.`,children:[t!==3&&jsxRuntimeExports.jsxs("g",{stroke:EDGE$1,strokeOpacity:.28,strokeWidth:1.2,fill:"none",children:[jsxRuntimeExports.jsx("polygon",{points:P(B,G,ae,le)}),jsxRuntimeExports.jsx("polygon",{points:P(le,ae,we,ie)}),jsxRuntimeExports.jsx("line",{x1:le.x,y1:le.y,x2:ie.x,y2:ie.y}),jsxRuntimeExports.jsx("line",{x1:B.x,y1:B.y,x2:ke.x,y2:ke.y,strokeDasharray:"4 4"})]}),jsxRuntimeExports.jsxs("g",{className:Te,children:[jsxRuntimeExports.jsx("polygon",{points:P(X,ne,te,se),fill:z?"#fcd34d":TOP$1,stroke:z?LAYER_EDGE:EDGE$1,strokeWidth:2,strokeLinejoin:"round"}),jsxRuntimeExports.jsx("polygon",{points:P(G,oe,te,ne),fill:z?"#fbbf24":SIDE,stroke:z?LAYER_EDGE:EDGE$1,strokeWidth:2,strokeLinejoin:"round"}),jsxRuntimeExports.jsx("polygon",{points:P(B,G,ne,X),fill:z?LAYER_FILL:FRONT,stroke:z?LAYER_EDGE:EDGE$1,strokeWidth:2,strokeLinejoin:"round"}),Math.max(o,r,a)<=GRID_CAP&&jsxRuntimeExports.jsxs("g",{children:[U(X,q,o,{x:0,y:O*g},O,"front"),U(X,q,o,V,r,"top"),U(ne,V,r,{x:0,y:O*g},O,"right")]})]}),jsxRuntimeExports.jsxs("text",{x:(B.x+G.x)/2,y:B.y+20,textAnchor:"middle",fontSize:13,fontWeight:700,fill:LABEL,children:[o," ",i]}),jsxRuntimeExports.jsxs("text",{x:B.x-10,y:(B.y+le.y)/2,textAnchor:"middle",fontSize:13,fontWeight:700,fill:LABEL,transform:`rotate(-90 ${B.x-10} ${(B.y+le.y)/2})`,children:[a," ",i]}),jsxRuntimeExports.jsxs("text",{x:(ae.x+we.x)/2+10,y:(ae.y+we.y)/2-3,textAnchor:"middle",fontSize:13,fontWeight:700,fill:LABEL,children:[r," ",i]})]}),jsxRuntimeExports.jsx("div",{className:`wex-vc-badge${t===3?" wex-vc-badge-final":""}`,children:Q},`badge-${e}`)]},e)}const LEFT_COLOR="#2563eb",RIGHT_COLOR="#60a5fa",AMBER$4="#f59e0b",AMBER_STROKE$1="#d97706",THIRD_COLOR="#0891b2",SEG_STROKE="#ffffff",AXIS$1="#1f2733";function RatioBarFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="ratio_bar")return null;const s=n.visual_data,o=Math.max(0,Math.round(s.left_parts)),r=Math.max(0,Math.round(s.right_parts)),a=s.third_parts!=null?Math.max(0,Math.round(s.third_parts)):0,i=React.useRef(null);t===1&&(i.current={a:o,b:r,c:a});const l=i.current?.a??o,u=i.current?.b??r,d=i.current?.c??a,m=Math.max(1,l+u+d),h=s.total!=null,f=h?s.total/m:null,[g,b]=n.animation.split(":"),x=new Set,y=()=>{for(let ye=0;ye<l;ye++)x.add(ye)},E=()=>{for(let ye=l;ye<l+u;ye++)x.add(ye)},k=()=>{for(let ye=l+u;ye<m;ye++)x.add(ye)},T=()=>l>=u?y():E(),A=()=>l>=u?E():y(),N=()=>{if(l>u)for(let ye=u;ye<l;ye++)x.add(ye);else for(let ye=m-(u-l);ye<m;ye++)x.add(ye)};let S=null,M=null;const L=ye=>Math.round(ye);if(t===2&&g==="rb_shade"&&(b==="left"?y():b==="third"?k():E()),t===3)if(g==="rb_share"&&f!=null)switch(b){case"one":x.add(0),S=String(f);break;case"left":y(),S=String(f*l);break;case"right":E(),S=String(f*u);break;case"third":k(),S=String(f*d);break;case"larger":T(),S=String(f*Math.max(l,u));break;case"smaller":A(),S=String(f*Math.min(l,u));break;case"diff":N(),S=String(f*Math.abs(l-u));break;case"both":S=String(f*l),M=String(f*u);break}else g==="rb_frac"?b==="left"?(y(),S=`${l}/${m}`):b==="third"?(k(),S=`${d}/${m}`):(E(),S=`${u}/${m}`):g==="rb_pct"?b==="left"?(y(),S=`${L(l/m*100)}%`):b==="third"?(k(),S=`${L(d/m*100)}%`):(E(),S=`${L(u/m*100)}%`):g==="rb_ratio"&&(S=d>0?`${l}:${u}:${d}`:`${l}:${u}`);const P=f!=null&&(t===2&&g==="rb_distribute"||t===3&&g==="rb_share"&&b==="both"),F=s.named_colors?namedRatioColor(s.left_label):null,D=s.named_colors?namedRatioColor(s.right_label):null,U=s.named_colors&&s.third_label?namedRatioColor(s.third_label):null,q=F?.fill??LEFT_COLOR,V=D?.fill??RIGHT_COLOR,O=U?.fill??THIRD_COLOR,z=F?.ink??LEFT_COLOR,K=D?.ink??RIGHT_COLOR,Z=U?.ink??THIRD_COLOR,Q=F!=null||D!=null||U!=null,B=ye=>ye<l?q:ye<l+u?V:O,G=14,X=300,ne=X/m,se=46,te=46,oe=se+te;let le=G+X+14,ae=0;if(s.fit_bars){const ye=(Rt,ge)=>`${Rt} — ${ge} part${ge===1?"":"s"}`.length*13*.62/2,Ne=G+l*ne/2,Ye=G+l*ne+u*ne/2,Ge=G+(l+u)*ne+d*ne/2,Fe=Math.min(0,l>
710?Ne-ye(s.left_label,l)-4:0),Xe=Math.max(le,u>0?Ye+ye(s.right_label,u)+4:le,d>0&&s.third_label?Ge+ye(s.third_label,d)+4:le);ae=Fe,le=Xe-Fe}const ie=oe+66,we=Math.max(240,Math.min(le,440)),ke=G+l*ne/2,Te=G+l*ne+u*ne/2,Se=G+(l+u)*ne+d*ne/2,$e=se-16,Re=t===1&&g==="rb_build";return jsxRuntimeExports.jsx("div",{className:"wex-rb-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`${ae.toFixed(1)} 0 ${le.toFixed(1)} ${ie}`,className:"wex-rb-svg",width:"100%",style:{display:"block",margin:"0 auto",maxWidth:`${we}px`,height:"auto"},role:"img","aria-label":`A bar split in the ratio ${d>0?`${l} to ${u} to ${d}`:`${l} to ${u}`}, ${s.left_label} to ${s.right_label}${d>0&&s.third_label?` to ${s.third_label}`:""}${h?`, total ${s.total}`:""}.`,children:[h&&jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("path",{d:`M ${G} ${$e+6} L ${G} ${$e} L ${G+X} ${$e} L ${G+X} ${$e+6}`,fill:"none",stroke:AXIS$1,strokeWidth:1.5}),jsxRuntimeExports.jsx("text",{x:G+X/2,y:$e-5,textAnchor:"middle",fontSize:15,fontWeight:800,fill:AXIS$1,children:s.total})]}),Array.from({length:m},(ye,Ne)=>{const Ye=G+Ne*ne,Ge=x.has(Ne);return jsxRuntimeExports.jsxs("g",{className:Re?"wex-rb-seg-in":void 0,style:{"--wex-rb-i":Ne},children:[jsxRuntimeExports.jsx("rect",{x:Ye,y:se,width:ne,height:te,fill:Ge&&!Q?AMBER$4:B(Ne),stroke:Ge?AMBER_STROKE$1:SEG_STROKE,strokeWidth:Ge?Q?3.5:2.5:2,className:Ge?"wex-rb-hl":void 0}),P&&jsxRuntimeExports.jsx("text",{className:"wex-rb-perpart",x:Ye+ne/2,y:se+te/2+5,textAnchor:"middle",fontSize:13,fontWeight:800,fill:ratioValueInk(B(Ne)),style:{"--wex-rb-i":Ne},children:f})]},Ne)}),l>0&&jsxRuntimeExports.jsxs("text",{x:ke,y:oe+20,textAnchor:"middle",fontSize:13,fontWeight:700,fill:z,children:[s.left_label," — ",l," part",l===1?"":"s"]}),u>0&&jsxRuntimeExports.jsxs("text",{x:Te,y:oe+20,textAnchor:"middle",fontSize:13,fontWeight:700,fill:K,children:[s.right_label," — ",u," part",u===1?"":"s"]}),d>0&&s.third_label&&jsxRuntimeExports.jsxs("text",{x:Se,y:oe+20,textAnchor:"middle",fontSize:13,fontWeight:700,fill:Z,children:[s.third_label," — ",d," part",d===1?"":"s"]}),S!=null&&jsxRuntimeExports.jsx("text",{className:"wex-rb-badge",x:M!=null?G+X*.3:G+X/2,y:oe+46,textAnchor:"middle",fontSize:20,fontWeight:900,fill:AMBER_STROKE$1,children:S}),M!=null&&jsxRuntimeExports.jsx("text",{className:"wex-rb-badge",x:G+X*.7,y:oe+46,textAnchor:"middle",fontSize:20,fontWeight:900,fill:AMBER_STROKE$1,children:M})]})},e)}const BLUE$8="#2563eb",BLUE_FILL$3="#dbeafe",BLUE_INK$1="#1e3a8a",AMBER$3="#fbbf24",AMBER_STROKE="#d97706",AMBER_INK$1="#b45309",GREY_FILL="#f3f4f6",GREY_STROKE="#9ca3af",AXIS="#1f2733",W$7=330,X0=12,PLOT_W=306,BAR_TOP=40,BAR_H=46,BAR_BOTTOM=BAR_TOP+BAR_H,LABEL_SIZE=15,ROW1=BAR_BOTTOM+30,ROW2=ROW1+28,textW=(n,t)=>n.length*t*.62;function fitFont(n,t,e,s,o){for(let r=e;r>=s;r--)if(textW(n,r)<=t-o)return r;return null}function clampCentre(n,t,e){const s=textW(e,LABEL_SIZE)/2;return Math.min(Math.max((n+t)/2,s+2),W$7-s-2)}function PercentBarFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="percent_bar")return null;const s=n.visual_data,o=Math.min(20,Math.max(2,Math.round(s.parts))),r=Math.min(o,Math.max(0,s.lit??0)),a=Math.max(0,s.reserve??0),i=PLOT_W/(o+a),l=X0+o*i,u=X0+r*i,d=s.extend_label!=null&&r>0,m=d?l+r*i:l,h=n.animation==="pb_split"||n.animation==="pb_build"||t===1,f=n.animation==="pb_share",g=n.animation==="pb_shade",b=s.taken===!0,x=`${Math.round(Math.min(260,1800/Math.max(1,Math.ceil(r))))}ms`,y=s.part_label?fitFont(s.part_label,i,15,13,8):null;let E=s.part_value?fitFont(s.part_value,i,17,12,7):null;const k=y!=null&&E!=null;k&&(E=Math.min(E,15));const T=d?l:X0,A=d?m:u,N=s.lit_label?clampCentre(T,A,s.lit_label):0,S=s.rest_label?clampCentre(u,l,s.rest_label):0,M=s.lit_label!=null&&s.rest_label!=null&&N+textW(s.lit_label,LABEL_SIZE)/2+8>S-textW(s.rest_label,LABEL_SIZE)/2,L=M?ROW2:ROW1,F=(M||d?ROW2:ROW1)+10,D=(V,O,z,K)=>{const Z=z===ROW1?BAR_BOTTOM+6:ROW1+10;return jsxRuntimeExports.jsx("path",{d:`M ${V+1.5} ${Z} L ${V+1.5} ${Z+6} L ${O-1.5} ${Z+6} L ${O-1.5} ${Z}`,fill:"none",stroke:K,strokeWidth:1.8})},U=(V,O,z,K)=>jsxRuntimeExports.jsx("text",{x:O,y:z+5,textAnchor:"middle",fontSize:LABEL_SIZE,fontWeight:800,fill:K,children:V}),q=(V,O)=>{const z=(O?l:X0)+V*i,K=Math.min(1,Math.max(0,r-V));if(O&&K===0)return null;const Z=K>0,Q=b&&!O,B={"--wex-pb-i":V,"--wex-pb-gap":x},G=K===0||K===1,X=Z&&K===1?Q?GREY_STROKE:AMBER_INK$1:BLUE_INK$1,ne=z+i*(O?K:1)/2;return jsxRuntimeExports.jsxs("g",{className:h&&!O?"wex-pb-box-in":O?"wex-pb-extend":void 0,style:B,children:[jsxRuntimeExports.jsx("rect",{x:z,y:BAR_TOP,width:O?i*K:i,height:BAR_H,fill:BLUE_FILL$3,stroke:BLUE$8,strokeWidth:1.5,strokeDasharray:O?"4 3":void 0}),Z&&jsxRuntimeExports.jsx("rect",{className:g?"wex-pb-lit-in":void 0,style:B,x:z,y:BAR_TOP,width:i*K,height:BAR_H,fill:Q?GREY_FILL:AMBER$3,stroke:Q?GREY_STROKE:AMBER_STROKE,strokeWidth:2,strokeDasharray:Q||O?"4 3":void 0}),G&&y!=null&&jsxRuntimeExports.jsx("text",{className:n.animation==="pb_value"?"wex-pb-label-pop":void 0,style:B,x:ne,y:k?BAR_TOP+18:BAR_TOP+BAR_H/2+y/3,textAnchor:"middle",fontSize:y,fontWeight:700,fill:X,children:s.part_label}),G&&E!=null&&jsxRuntimeExports.jsx("text",{className:h?"wex-pb-value-in":f?"wex-pb-label-pop":void 0,style:B,x:ne,y:k?BAR_TOP+37:BAR_TOP+BAR_H/2+E/3,textAnchor:"middle",fontSize:E,fontWeight:800,fill:X,children:s.part_value})]},`${O?"x":"b"}${V}`)};return jsxRuntimeExports.jsx("div",{className:"wex-pb-wrap",children:jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${W$7} ${F}`,className:"wex-pb-svg",width:"100%",role:"img","aria-label":`A bar of ${o} equal boxes worth ${s.whole_label} in all${s.lit_label?`; ${s.lit_label}`:""}.`,children:[jsxRuntimeExports.jsx("path",{d:`M ${X0+1.5} ${BAR_TOP-5} L ${X0+1.5} ${BAR_TOP-11} L ${l-1.5} ${BAR_TOP-11} L ${l-1.5} ${BAR_TOP-5}`,fill:"none",stroke:AXIS,strokeWidth:1.8}
71),jsxRuntimeExports.jsx("text",{x:(X0+l)/2,y:BAR_TOP-17,textAnchor:"middle",fontSize:17,fontWeight:800,fill:AXIS,children:s.whole_label}),Array.from({length:o},(V,O)=>q(O,!1)),d&&Array.from({length:Math.ceil(r)},(V,O)=>q(O,!0)),s.first_label!=null&&jsxRuntimeExports.jsxs("g",{className:"wex-pb-label-in",children:[D(X0,X0+i,ROW1,BLUE$8),U(s.first_label,clampCentre(X0,X0+i,s.first_label),ROW1,BLUE_INK$1)]}),s.lit_label!=null&&r>0&&jsxRuntimeExports.jsxs("g",{className:"wex-pb-label-in",children:[D(T,A,ROW1,b?GREY_STROKE:AMBER_STROKE),U(s.lit_label,N,ROW1,b?GREY_STROKE:AMBER_INK$1)]}),s.rest_label!=null&&jsxRuntimeExports.jsxs("g",{className:"wex-pb-label-in",children:[D(u,l,L,BLUE$8),U(s.rest_label,S,L,BLUE_INK$1)]}),d&&jsxRuntimeExports.jsxs("g",{className:"wex-pb-label-in",children:[D(X0,m,ROW2,BLUE$8),U(s.extend_label,clampCentre(X0,m,s.extend_label),ROW2,BLUE_INK$1)]})]})},e)}const BLUE$7="#2563eb",BLUE_FILL$2="#eff6ff",BLUE_INK="#1e3a8a",AMBER$2="#f59e0b",AMBER_FILL="#fef3c7",AMBER_INK="#b45309",GREY$1="#9ca3af",W$6=330,HEADERS=["O","T","H","Th","TTh"],HEAD_Y=24,DIGIT_Y=66,GRID_TOP=86,DOT_R=7,DOT_GAP=18,VALUE_Y=172;function PlaceValueChartFrame({step:n,replayKey:t}){if(n.visual_data.type!=="place_value_chart")return null;const e=n.visual_data,s=e.digits.slice(0,5).map(d=>Math.min(9,Math.max(0,Math.round(d)))),o=s.length;if(o===0)return null;const r=Math.min(76,310/o),a=(W$6-r*o)/2,l=e.values!=null&&Object.keys(e.values).length>0?VALUE_Y+14:VALUE_Y-12,u=d=>{const m=s[d],h=e.highlight===d,f=a+d*r,g=f+r/2,b=HEADERS[o-1-d]??"",x=e.digit_text?.[d]??String(m),y=e.values?.[d],E=h&&e.change===1,k=h&&e.change===-1,T=m+(k?1:0),A=Array.from({length:T},(N,S)=>{const M=Math.floor(S/3),L=S%3,P=g+(L-1)*DOT_GAP,F=GRID_TOP+M*DOT_GAP+DOT_R,D=E&&S===m-1,U=k&&S===m;return jsxRuntimeExports.jsxs("g",{className:D?"wex-pvc-pop":U?"wex-pvc-gone":void 0,children:[jsxRuntimeExports.jsx("circle",{cx:P,cy:F,r:DOT_R,fill:U?"#ffffff":h?AMBER$2:BLUE$7,stroke:U?GREY$1:h?AMBER_INK:BLUE_INK,strokeWidth:D?2.5:1,strokeDasharray:U?"3 2":void 0}),U&&jsxRuntimeExports.jsx("path",{d:`M ${P-6} ${F-6} L ${P+6} ${F+6}`,stroke:GREY$1,strokeWidth:2})]},S)});return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("rect",{className:h?"wex-pvc-lit":void 0,x:f+2,y:4,width:r-4,height:VALUE_Y-20,rx:8,fill:h?AMBER_FILL:BLUE_FILL$2,stroke:h?AMBER$2:BLUE$7,strokeWidth:h?2.5:1.2}),jsxRuntimeExports.jsx("text",{x:g,y:HEAD_Y,textAnchor:"middle",fontSize:15,fontWeight:800,fill:h?AMBER_INK:BLUE_INK,children:b}),jsxRuntimeExports.jsx("text",{x:g,y:DIGIT_Y,textAnchor:"middle",fontSize:30,fontWeight:900,fill:h?AMBER_INK:"#1f2733",children:x}),A,y!=null&&jsxRuntimeExports.jsx("text",{className:"wex-pvc-value",x:g,y:VALUE_Y,textAnchor:"middle",fontSize:16,fontWeight:800,fill:h?AMBER_INK:BLUE_INK,children:y})]},d)};return jsxRuntimeExports.jsx("div",{className:"wex-pvc-wrap",children:jsxRuntimeExports.jsx("svg",{viewBox:`0 0 ${W$6} ${l}`,className:"wex-pvc-svg",width:"100%",role:"img","aria-label":`A place-value chart showing ${s.join("")}.`,children:s.map((d,m)=>u(m))})},t)}function StatementCheckFrame({step:n,replayKey:t}){if(n.visual_data.type!=="statement_check")return null;
71const e=n.visual_data,s=new Set(e.focus??[]);let o=0;return jsxRuntimeExports.jsx("ul",{className:"wex-sc-list","aria-label":"Check each statement",children:e.statements.map((r,a)=>{const i=e.verdicts[a]??null,l=e.reasons[a]??null,u=e.answer===a,d=s.has(a),m=d?{"--wex-sc-i":o++}:void 0,h=["wex-sc-card",i==="true"?"wex-sc-true":i==="false"?"wex-sc-false":"wex-sc-open",u?"wex-sc-answer":"",d?"wex-sc-in":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsxs("li",{className:h,style:m,children:[jsxRuntimeExports.jsx("span",{className:"wex-sc-mark","aria-hidden":"true",children:i==="true"?"✓":i==="false"?"✗":"?"}),jsxRuntimeExports.jsxs("span",{className:"wex-sc-body",children:[jsxRuntimeExports.jsx("span",{className:"wex-sc-text",children:r}),i!==null&&l&&jsxRuntimeExports.jsx("span",{className:"wex-sc-reason",children:l})]})]},a)})},t)}function foldedHalf(n,t){const e=t.x2-t.x1,s=t.y2-t.y1,o=e*e+s*s;if(!o)return[];const r=([u,d])=>e*(d-t.y1)-s*(u-t.x1),a=n.find(u=>Math.abs(r(u))>1e-7);if(!a)return[];const i=Math.sign(r(a)),l=[];for(let u=0;u<n.length;u++){const d=n[u],m=n[(u+1)%n.length],h=r(d)*i,f=r(m)*i;if(h>=-1e-7&&l.push(d),h>1e-7&&f<-1e-7||h<-1e-7&&f>1e-7){const g=h/(h-f);l.push([d[0]+g*(m[0]-d[0]),d[1]+g*(m[1]-d[1])])}}return l.map(([u,d])=>{const m=((u-t.x1)*e+(d-t.y1)*s)/o;return[2*(t.x1+m*e)-u,2*(t.y1+m*s)-d]})}const VIEW$1=170,CX$1=VIEW$1/2,CY$1=VIEW$1/2,R$1=60,N_GON={equilateral_triangle:3,square:4,regular_pentagon:5,regular_hexagon:6,regular_octagon:8},RECT_HW=62,RECT_HH=40,RECT=[[CX$1-RECT_HW,CY$1-RECT_HH],[CX$1+RECT_HW,CY$1-RECT_HH],[CX$1+RECT_HW,CY$1+RECT_HH],[CX$1-RECT_HW,CY$1+RECT_HH]],mid=(n,t)=>[(n[0]+t[0])/2,(n[1]+t[1])/2];function extend(n,t,e){const s=Math.hypot(t[0]-n[0],t[1]-n[1])||1,o=(t[0]-n[0])/s,r=(t[1]-n[1])/s;return{x1:n[0]-o*e,y1:n[1]-r*e,x2:t[0]+o*e,y2:t[1]+r*e}}function regularVerts(n){return Array.from({length:n},(t,e)=>{const s=(-90+e*360/n)*Math.PI/180;return[CX$1+R$1*Math.cos(s),CY$1+R$1*Math.sin(s)]})}function trueLines(n){if(n==="rectangle"){const r=extend([CX$1,CY$1-RECT_HH],[CX$1,CY$1+RECT_HH],8),a=extend([CX$1-RECT_HW,CY$1],[CX$1+RECT_HW,CY$1],8);return[r,a]}const t=N_GON[n],e=regularVerts(t),s=e.map((r,a)=>mid(e[a],e[(a+1)%t])),o=[];if(t%2===1)for(let r=0;r<t;r++)o.push(extend(e[r],s[(r+(t-1)/2)%t],8));else{const r=t/2;for(let a=0;a<r;a++)o.push(extend(e[a],e[a+r],6));for(let a=0;a<r;a++)o.push(extend(s[a],s[a+r],6))}return o}function symmetryLinesFrameModel(n,t,e=!1){const s=n!=="rectangle",o=n==="rectangle"?4:N_GON[n]??0,r=n==="rectangle"?2:N_GON[n]??0,a=e?0:t===1?Math.min(1,r):r,i=t>=2,l=t===3?s?`${r} lines of symmetry — ${o} sides, ${r} lines`:`${r} lines of symmetry`:null;return{isRegular:s,sides:o,lineCount:r,visibleCount:a,showCount:i,concludeText:l}}const ALLOWED=["equilateral_triangle","square","regular_pentagon","regular_hexagon","regular_octagon","rectangle"],BLUE_FILL$1="#dbeafe",BLUE_LINE$1="#1e3a8a",AMBER$1="#d97706",AMBER_DK$1="#b45309",CLAIM$8="#64748b";function describedLineSeg(n,t){if(t==="rect_diagonal")return extend(RECT[0],RECT[2],0);if(t==="rect_vertical_midline")return extend([CX$1,CY$1-RECT_HH],[CX$1,CY$1+RECT_HH],8);if(t==="rect_horizontal_midline")return extend([CX$1-RECT_HW,CY$1],[CX$1+RECT_HW,CY$1],8);const e=N_GON[n];if(e==null)return null;const s=regularVerts(e),o=s.map((r,a)=>mid(s[a],s[(a+1)%e]));return t==="vertex_to_opposite_vertex"?e%2!==0?null:extend(s[0],s[e/2],6):t==="vertex_to_opposite_mid"?extend(s[0],o[(0+Math.floor((e-1)/2))%e],8):t==="adjacent_vertices"?extend(s[0],s[1],0):null}function SymmetryLinesFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="symmetry_lines")return null;const s=n.visual_data,o=s.shape;if(!ALLOWED.includes(o))return null;const r=o==="rectangle"?RECT:regularVerts(N_GON[o]),a=r.map(([N,S])=>`${N},${S}`).join(" "),i=trueLines(o),l=s.described_line?describedLineSeg(o,s.described_line):null,u=l!=null&&s.show_lines==="none",{lineCount:d,visibleCount:m,showCount:h,concludeText:f}=symmetryLinesFrameModel(o,n.animation==="count"?2:t,u),g=i.slice(0,m),x=l&&(/^fold_check/.test(n.animation??"")||n.animation==="check_diagonal")&&t>=2?foldedHalf(r,l):[],y=[...r,...x],E=Math.min(0,...y.map(N=>N[0]-12)),k=Math.min(0,...y.map(N=>
71N[1]-12)),T=Math.max(VIEW$1,...y.map(N=>N[0]+12)),A=Math.max(VIEW$1,...y.map(N=>N[1]+12));return jsxRuntimeExports.jsxs("div",{className:"wex-sym-wrap",style:{display:"flex",flexDirection:"column",alignItems:"center",gap:10},children:[jsxRuntimeExports.jsxs("svg",{viewBox:`${E} ${k} ${T-E} ${A-k}`,width:220,height:220,style:{maxWidth:"100%",height:"auto"},"aria-hidden":"true",children:[jsxRuntimeExports.jsx("polygon",{points:a,fill:BLUE_FILL$1,fillOpacity:.6,stroke:BLUE_LINE$1,strokeWidth:2.5,strokeLinejoin:"round"}),x.length>0&&jsxRuntimeExports.jsx("polygon",{"data-folded-half":"true",points:x.map(N=>N.join(",")).join(" "),fill:AMBER$1,fillOpacity:.28,stroke:AMBER$1,strokeWidth:2.5,strokeLinejoin:"round",children:jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:"0",to:"1",dur:"0.6s",fill:"freeze"})}),g.map((N,S)=>{const M=t===1?S===0:t===2?S>=1:!1,L=t===2&&S>=1?`${(S-1)*.25}s`:"0s";return jsxRuntimeExports.jsx("line",{x1:N.x1,y1:N.y1,x2:N.x2,y2:N.y2,stroke:AMBER$1,strokeWidth:2.5,strokeDasharray:"6,5",strokeLinecap:"round",opacity:M?0:1,children:M&&jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:0,to:1,dur:"0.45s",begin:L,fill:"freeze"})},`sym-${S}`)}),l&&jsxRuntimeExports.jsx("line",{x1:l.x1,y1:l.y1,x2:l.x2,y2:l.y2,stroke:CLAIM$8,strokeWidth:3,strokeLinecap:"round"})]},e),h&&!f&&!u&&jsxRuntimeExports.jsxs("div",{style:{fontSize:18,fontWeight:800,color:AMBER_DK$1},"aria-hidden":"true",children:[d," fold lines found"]}),f&&!u&&jsxRuntimeExports.jsx("div",{className:"wex-sym-badge","aria-hidden":"true",style:{fontSize:16,fontWeight:800,color:AMBER_DK$1,border:`2px solid ${AMBER$1}`,borderRadius:999,padding:"3px 14px"},children:f})]})}function alignedSide(n,t,e){const s=n[t],o=n[(t+1)%n.length],r=n[e],a=n[(e+1)%n.length];return(o[0]-s[0])*(a[0]-r[0])+(o[1]-s[1])*(a[1]-r[1])<0?[o,s]:[s,o]}const SHAPES$1={parallelogram:{verts:[[30,20],[95,20],[75,80],[10,80]],parallel:[[0,2],[1,3]]},rhombus:{verts:[[50,10],[85,50],[50,90],[15,50]],parallel:[[0,2],[1,3]]},trapezium:{verts:[[27,22],[73,22],[90,80],[10,80]],parallel:[[0,2]]},rectangle:{verts:[[12,28],[88,28],[88,72],[12,72]],parallel:[[0,2],[1,3]]},square:{verts:[[20,18],[80,18],[80,78],[20,78]],parallel:[[0,2],[1,3]]},kite:{verts:[[50,8],[82,42],[50,92],[18,42]],parallel:[]}},NAME$2={parallelogram:"Parallelogram",rhombus:"Rhombus",trapezium:"Trapezium",rectangle:"Rectangle",square:"Square",kite:"Kite"},VIEW=130,AMBER="#d97706",AMBER_DK="#b45309",BLUE_FILL="#dbeafe",BLUE_LINE="#1f2733";function quadrilateralFrameModel(n,t,e=!1){const s=SHAPES$1[n].parallel.length,o=e||t!==1?s:Math.min(1,s),r=t===2&&s<2?s===0?"No parallel pairs":"Only one parallel pair":null,a=t===3?NAME$2[n]:null;return{pairCount:s,visiblePairs:o,noPairNote:r,concludeName:a,name:NAME$2[n]}}function chevrons(n,t,e,s){const o=[(n[0]+t[0])/2,(n[1]+t[1])/2],r=Math.hypot(t[0]-n[0],t[1]-n[1])||1,a=[(t[0]-n[0])/r,(t[1]-n[1])/r],i=[-a[1],a[0]],l=6.5,u=6,d=[];for(let m=0;m<e;m++){const h=[o[0]+(m-(e-1)/2)*u*a[0],o[1]+(m-(e-1)/2)*u*a[1]],f=[h[0]+l*a[0],h[1]+l*a[1]],g=[h[0]+l*i[0],h[1]+l*i[1]],b=[h[0]-l*i[0],h[1]-l*i[1]];d.push(jsxRuntimeExports.jsx("line",{x1:f[0],y1:f[1],x2:g[0],y2:g[1],stroke:AMBER,strokeWidth:2,strokeLinecap:"round"},`${s}-${m}-a`),jsxRuntimeExports.jsx("line",{x1:f[0],y1:f[1],x2:b[0],y2:b[1],stroke:AMBER,strokeWidth:2,strokeLinecap:"round"},`${s}-${m}-b`))}return d}function rightAngleMarks(n){const t=[];for(let s=0;s<4;s++){const o=n[s],r=n[(s+3)%4],a=n[(s+1)%4],i=[r[0]-o[0],r[1]-o[1]],l=[a[0]-o[0],a[1]-o[1]],u=Math.hypot(i[0],i[1])||1,d=Math.hypot(l[0],l[1])||1,m=(i[0]*l[0]+i[1]*l[1])/(u*d);if(Math.abs(m)>.05)continue;const h=[o[0]+i[0]/u*9,o[1]+i[1]/u*9],f=[o[0]+l[0]/d*9,o[1]+l[1]/d*9],g=[h[0]+l[0]/d*9,h[1]+l[1]/d*9];t.push(jsxRuntimeExports.jsx("polyline",{points:`${h[0]},${h[1]} ${g[0]},${g[1]} ${f[0]},${f[1]}`,fill:"none",stroke:AMBER,strokeWidth:2},`ra-${s}`))}return t}function equalSideTicks(n){const t=n.map((r,a)=>{const i=n[(a+1)%4];return Math.hypot(i[0]-r[0],i[1]-r[1])}),e=[-1,-1,-1,-1];let s=0;for(let r=0;r<4;r++){if(e[r]!==-1)continue;const a=[r];for(let i=r+1;i<4;i++)e[i]===-1&&Math.abs(t[r]-t[i])<1.5&&a.push(i);a.length>
71=2&&(a.forEach(i=>e[i]=s),s++)}const o=[];for(let r=0;r<4;r++){const a=e[r];if(a===-1)continue;const i=n[r],l=n[(r+1)%4],u=t[r]||1,d=[(l[0]-i[0])/u,(l[1]-i[1])/u],m=[-d[1],d[0]],h=a+1;for(let f=0;f<h;f++){const g=u*.3+(f-(h-1)/2)*5,b=[i[0]+d[0]*g,i[1]+d[1]*g];o.push(jsxRuntimeExports.jsx("line",{x1:b[0]-m[0]*5,y1:b[1]-m[1]*5,x2:b[0]+m[0]*5,y2:b[1]+m[1]*5,stroke:BLUE_LINE,strokeWidth:2,strokeLinecap:"round"},`eq-${r}-${f}`))}}return o}function sideExtensions(n){const t=[],s=[];for(const r of[0,1,2,3]){const a=n[r],i=n[(r+1)%4],l=Math.hypot(i[0]-a[0],i[1]-a[1])||1,u=[(i[0]-a[0])/l,(i[1]-a[1])/l];s.push(u),t.push(jsxRuntimeExports.jsx("line",{x1:a[0]-u[0]*30,y1:a[1]-u[1]*30,x2:i[0]+u[0]*30,y2:i[1]+u[1]*30,stroke:"#64748b",strokeWidth:1.5,strokeDasharray:"4 4",opacity:.7},`ext-${r}`))}const o=(r,a)=>Math.abs(s[r][0]*s[a][1]-s[r][1]*s[a][0])<.05;if(!o(0,2)&&!o(1,3)){const r=n.reduce((l,u)=>l+u[0],0)/4,a=n.reduce((l,u)=>l+u[1],0)/4,i=8;t.push(jsxRuntimeExports.jsxs("g",{opacity:.95,children:[jsxRuntimeExports.jsx("line",{x1:r-i,y1:a-i,x2:r+i,y2:a+i,stroke:AMBER,strokeWidth:3.5,strokeLinecap:"round"}),jsxRuntimeExports.jsx("line",{x1:r-i,y1:a+i,x2:r+i,y2:a-i,stroke:AMBER,strokeWidth:3.5,strokeLinecap:"round"})]},"ext-x"))}return t}function QuadrilateralFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="quadrilateral")return null;const o=n.visual_data.shapes?.[0];if(!o||!SHAPES$1[o.shape])return null;const r=o.shape,{verts:a,parallel:i}=SHAPES$1[r],l=a.map(([b,x])=>[15+b,8+x]),u=l.map(([b,x])=>`${b},${x}`).join(" "),d=/^compare_/.test(n.animation??""),{visiblePairs:m,noPairNote:h,concludeName:f}=quadrilateralFrameModel(r,t,d),g=i.slice(0,m);return jsxRuntimeExports.jsxs("div",{className:"wex-quad-wrap",style:{display:"flex",flexDirection:"column",alignItems:"center",gap:10},children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${VIEW} ${VIEW}`,width:210,height:210,style:{maxWidth:"100%",height:"auto"},"aria-hidden":"true",children:[o.show_side_extensions&&sideExtensions(l),jsxRuntimeExports.jsx("polygon",{points:u,fill:BLUE_FILL,fillOpacity:.6,stroke:BLUE_LINE,strokeWidth:2.5,strokeLinejoin:"round"}),o.mark_right_angles&&rightAngleMarks(l),o.equal_side_marks&&equalSideTicks(l),g.map((b,x)=>{const y=d?t!==3:t===1&&x===0||t===2&&x===m-1&&x>=1;return jsxRuntimeExports.jsxs("g",{opacity:y?0:1,children:[y&&jsxRuntimeExports.jsx("animate",{attributeName:"opacity",from:0,to:1,dur:"0.5s",fill:"freeze"}),b.flatMap(E=>{const[k,T]=alignedSide(l,E,b[0]);return chevrons(k,T,x+1,`c-${x}-${E}`)})]},`grp-${x}`)})]},e),h&&jsxRuntimeExports.jsx("div",{style:{fontSize:16,fontWeight:800,color:AMBER_DK},"aria-hidden":"true",children:h}),f&&jsxRuntimeExports.jsx("div",{className:"wex-quad-badge","aria-hidden":"true",style:{fontSize:18,fontWeight:800,color:AMBER_DK,border:`2px solid ${AMBER}`,borderRadius:999,padding:"3px 16px"},children:f})]})}const PIPS={0:[],1:[[.5,.5]],2:[[.3,.3],[.7,.7]],3:[[.3,.3],[.5,.5],[.7,.7]],4:[[.3,.3],[.7,.3],[.3,.7],[.7,.7]],5:[[.3,.3],[.7,.3],[.5,.5],[.3,.7],[.7,.7]],6:[[.3,.26],[.7,.26],[.3,.5],[.7,.5],[.3,.74],[.7,.74]]};function DominoFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="domino_set")return null;const s=n.visual_data,{target:o,options:r,checking:a}=s,i=r.findIndex(y=>y.left+y.right===o),l=38,u=l*2,d=l,m=16,h=12,f=14,g=h*2+r.length*u+(r.length-1)*m,b=f*2+d,x=(y,E,k,T)=>(PIPS[k]??[]).map(([A,N],S)=>jsxRuntimeExports.jsx("circle",{cx:y+A*l,cy:E+N*l,r:3.4,className:"wex-dom-pip"},`${T}-${S}`));return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx("svg",{viewBox:`0 0 ${g} ${b}`,className:"wex-dom-svg",role:"img","aria-label":`dominoes — find the one that makes ${o}`,children:r.map((y,E)=>{const k=h+E*(u+m),T=f,A=E===i,S=t===2&&(a!==void 0&&E===a)?"wex-dom wex-dom-checking":A&&(a===void 0?t>=2:t===3)?"wex-dom wex-dom-pick":t===3&&!A?"wex-dom wex-dom-dim":"wex-dom";return jsxRuntimeExports.jsxs("g",{className:S,style:{"--wex-i":E},children:[jsxRuntimeExports.jsx("rect",{x:k,y:T,width:u,height:d,rx:8,className:"wex-dom-body"}),jsxRuntimeExports.jsx("line",{x1:k+l,y1:T+5,x2:k+l,y2:T+d-5,className:"wex-dom-divider"}),x(k,T,y.left,`l${E}`),x(k+l,T,y.right,`r${E}`)]},E)})}),t===3&&jsxRuntimeExports.jsx("div",{className:"wex-total",children:jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:o})},`total-${e}`)]},e)}const PENCE$2={"1p":1,"2p":2,"5p":5,"10p":10,"20p":20,"50p":50,"£1":100,"£2":200},penceLabel$2=n=>n>=100&&n%100===0?`£${n/100}`:`${n}p`;function subsetForTarget(n,t){const e=n.length;let s=null;for(let o=1;o<1<<e;o++){let r=0;const a=[];for(let i=0;i<e;i++)o&1<<i&&(r+=PENCE$2[n[i]]??0,a.push(i));r===t&&(!s||a.length<s.length)&&(s=a)}return s??[]}function CoinTapFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="coin_tap")return null;const s=n.visual_data,o=subsetForTarget(s.tray,s.target),r=new Set(o),a={};let i=0;for(const l of o)i+=PENCE$2[s.tray[l]]??0,a[l]=i;return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsx(CoinDefs,{}),jsxRuntimeExports.jsxs("div",{className:"wex-coin-target",children:["Make ",jsxRuntimeExports.jsx("strong",{children:penceLabel$2(s.target)})]}),jsxRuntimeExports.jsx("div",{className:"wex-coin-row",children:s.tray.map((l,u)=>{const d=r.has(u),m=t>=2?d?" wex-coin-pick":" wex-coin-faded":"";return jsxRuntimeExports.jsxs("div",{className:`wex-coin-cell${m}`,children:[jsxRuntimeExports.jsx("div",{className:t>=2&&d?"wex-coin-lift":"wex-fade-enter",children:jsxRuntimeExports.jsx(Coin,{denom:l,hideValue:!1})}),t>=2&&d&&jsxRuntimeExports.jsx("span",{className:"wex-chip wex-chip-pop",style:{"--wex-i":o.indexOf(u)},children:a[u]})]},u)})}),t===3&&jsxRuntimeExports.jsxs("div",{className:"wex-total",children:["= ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:penceLabel$2(s.target)})]},`total-${e}`)]},e)}const COLORS={blue:"#2563EB",amber:"#D97706",purple:"#D97706",green:"#2563EB"};function TenFrameMixedFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="ten_frame_mixed")return null;const o=n.visual_data.groups,r=o.map(y=>y.count),a=Math.min(...r),i=r.reduce((y,E)=>
71y+E,0),l=30,u=10,d=12,m=l+14,h=Math.max(...r,1),f=d*2+h*l+(h-1)*u,g=d+o.length*m,b=g+12,x=y=>d+y*(l+u);return jsxRuntimeExports.jsxs("div",{className:"wex-frame-center",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${f} ${b}`,className:"wex-tfm-svg",role:"img","aria-label":r.join(" plus "),children:[o.map((y,E)=>Array.from({length:y.count},(k,T)=>{const A=d+E*m+l/2,N=T>=a,S="wex-tfm-dot"+(t===1?" wex-tfm-enter":"")+(t>=2&&N?" wex-tfm-extra":"");return jsxRuntimeExports.jsx("circle",{cx:x(T)+l/2,cy:A,r:l/2-3,fill:COLORS[y.color]??"#2563EB",className:S,style:{"--wex-i":E*h+T}},`${E}-${T}`)})),t>=2&&a>0&&jsxRuntimeExports.jsx("line",{x1:x(0)+2,y1:g+2,x2:x(a-1)+l-2,y2:g+2,className:"wex-tfm-bracket"})]}),t===3&&jsxRuntimeExports.jsxs("div",{className:"wex-total",children:["= ",jsxRuntimeExports.jsx("span",{className:"wex-total-number",children:i})]},`total-${e}`)]},e)}function FactFamilyFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="fact_family")return null;const{rows:s,cols:o,show_group_counts:r}=n.visual_data,a=s*o,i=t>=2,l=t===3,u=t>=2,d=l&&r;return jsxRuntimeExports.jsxs("div",{className:"wex-ff-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-arr-grid wex-ff-grid",children:Array.from({length:s},(m,h)=>jsxRuntimeExports.jsxs("div",{className:`wex-arr-row${u?" wex-ff-box":""}`,style:u?{"--ff-r":h}:void 0,children:[jsxRuntimeExports.jsx("div",{className:"wex-arr-dots",children:Array.from({length:o},(f,g)=>{const b=h*o+g,x=t===1;return jsxRuntimeExports.jsx("span",{className:`wex-arr-dot${x?" wex-arr-enter":""}`,style:x?{"--arr-i":b}:void 0,"aria-hidden":"true"},b)})}),d&&jsxRuntimeExports.jsx("span",{className:"wex-ff-count",style:{"--ff-r":h},"aria-hidden":"true",children:o},`cnt-${h}-${e}`)]},h))}),jsxRuntimeExports.jsxs("div",{className:`wex-ff-eqs${l?" wex-ff-family":""}`,children:[jsxRuntimeExports.jsxs("div",{className:`wex-ff-eq${t===1?" wex-ff-write":""}`,"aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-ff-n rows",children:s}),jsxRuntimeExports.jsx("span",{className:"wex-ff-op",children:"×"}),jsxRuntimeExports.jsx("span",{className:"wex-ff-n cols",children:o}),jsxRuntimeExports.jsx("span",{className:"wex-ff-op",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-ff-n prod",children:a})]}),i&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("div",{className:"wex-ff-link","aria-hidden":"true",children:"so"}),jsxRuntimeExports.jsxs("div",{className:`wex-ff-eq${t===2?" wex-ff-write":""}`,"aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{className:"wex-ff-n prod",children:a}),jsxRuntimeExports.jsx("span",{className:"wex-ff-op",children:"÷"}),jsxRuntimeExports.jsx("span",{className:"wex-ff-n rows",children:s}),jsxRuntimeExports.jsx("span",{className:"wex-ff-op",children:"="}),jsxRuntimeExports.jsx("span",{className:"wex-ff-n cols",children:o})]})]})]})]},e)}const SLOT=100,SCALE=.82;function Shape3DNameFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="shape_3d_name")return null;const{solids:s,color:o}=n.visual_data,r=o??"#3b82f6",a=s.length,i=Math.ceil(a/3),l=Math.min(t*i,a);return jsxRuntimeExports.jsx("div",{className:"wex-s3dn-wrap",children:jsxRuntimeExports.jsx("svg",{viewBox:`0 0 ${a*SLOT} 128`,className:"wex-s3dn-svg","aria-hidden":"true",children:s.map((u,d)=>{const m=d<l,h=d>=l-i&&d<l,f=d*SLOT+SLOT/2,g=d*SLOT+SLOT*(1-SCALE)/2;return jsxRuntimeExports.jsxs("g",{children:[h&&jsxRuntimeExports.jsx("circle",{cx:f,cy:44,r:46,className:"wex-s3dn-glow"}),jsxRuntimeExports.jsx("g",{transform:`translate(${g}, 0) scale(${SCALE})`,opacity:m?1:.3,children:jsxRuntimeExports.jsx(Solid,{shape:u.shape,color:r})}),m&&jsxRuntimeExports.jsx("text",{x:f,y:118,textAnchor:"middle",className:`wex-s3dn-name${h?" wex-s3dn-name-in":""}`,style:{"--s3dn-i":d},children:u.name})]},d)})})},e)}const YW=26,COL_W=56,BAR_W=30,TOP=12,CHART_H_MIN=150,MIN_TICK_PX=11,LABEL_H=18;function BarBuilderFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="bar_builder")return null;const s=n.visual_data,{categories:o,max:r}=s,a=s.step&&s.step>0?s.step:1,i=r>0?r:1,l=o.length,u=s.built??o.map(()=>0),d=n.animation==="show_data",m=n.animation==="grow_bars",h=n.animation==="check_match",f=l*COL_W,g=YW+f,b=M
71ath.floor(i/a)+1,x=Math.max(CHART_H_MIN,(b-1)*MIN_TICK_PX),y=TOP+x,E=y+8+LABEL_H,k=N=>y-N/i*x,T=N=>YW+N*COL_W+(COL_W-BAR_W)/2,A=[];for(let N=0;N<=i;N+=a)A.push(N);return jsxRuntimeExports.jsxs("div",{className:"wex-bb-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-bb-data",children:o.map((N,S)=>{const M=m&&u[S]>0,L=d&&S===0;return jsxRuntimeExports.jsxs("span",{className:`wex-bb-chip${M?" wex-bb-chip-lit":""}${L?" wex-pulse":""}${d?" wex-bb-chip-in":""}`,style:{"--wex-bb-i":S},children:[N.label,": ",jsxRuntimeExports.jsx("b",{children:N.count})]},S)})}),jsxRuntimeExports.jsxs("svg",{className:"wex-bb-svg",viewBox:`0 0 ${g} ${E}`,role:"img","aria-label":"Building a bar chart to match the data.",children:[A.map(N=>jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:YW,y1:k(N),x2:g,y2:k(N),className:N===0?"wex-bb-axis":"wex-bb-grid"}),jsxRuntimeExports.jsx("text",{x:YW-5,y:k(N)+3,className:"wex-bb-ylabel",textAnchor:"end",children:N})]},N)),o.map((N,S)=>jsxRuntimeExports.jsx("text",{x:T(S)+BAR_W/2,y:y+8+LABEL_H-5,className:"wex-bb-xlabel",textAnchor:"middle",children:N.label},`name-${S}`)),jsxRuntimeExports.jsx("line",{x1:YW,y1:k(i),x2:YW,y2:k(0),className:"wex-bb-axis"}),o.map((N,S)=>{const M=u[S]/i*x;if(M<=0)return null;const L=colourForLabel(N.label)??"#2563eb",P=L==="#ffffff";return jsxRuntimeExports.jsx("rect",{x:T(S),y:y-M,width:BAR_W,height:M,rx:2,className:`wex-bb-bar${P?" wex-bb-bar-pale":""}${m?" wex-bb-bar-grow":""}`,style:{transformOrigin:`${T(S)+BAR_W/2}px ${y}px`,"--wex-bb-i":S,"--wex-bb-colour":L}},S)}),h&&o.map((N,S)=>{const M=k(u[S]);if(u[S]<=0)return null;const L=T(S)+BAR_W/2;return jsxRuntimeExports.jsxs("g",{className:"wex-bb-check",style:{"--wex-bb-i":S},children:[jsxRuntimeExports.jsx("line",{x1:YW,y1:M,x2:L,y2:M,className:"wex-bb-trace"}),jsxRuntimeExports.jsx("text",{x:L,y:M-6,className:"wex-bb-tick",textAnchor:"middle",children:"✓"})]},`chk-${S}`)})]})]},e)}function TwoWayTableFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="two_way_table")return null;const s=n.visual_data,{col_headers:o,row_headers:r,cells:a,corner_label:i,chip_expression:l,highlight_col:u,highlight_cell:d}=s,m=React.useRef(null);if(t===1){m.current=null;for(let S=0;S<a.length&&!m.current;S++)for(let M=0;M<a[S].length;M++){const L=a[S][M];if(L==null){m.current={row:S,col:M};break}}}const h=m.current,f=h?.row??-1,g=n.animation==="highlight_missing",b=n.animation==="sum_known",x=n.animation==="reveal_answer",y=f>=0?a[f].filter(S=>typeof S=="number"):[],E=y.reduce((S,M)=>S+M,0),k=(b||x)&&(y.length>0||!!l),T=u!==void 0&&(b||x),A=S=>T&&S===u,N=(S,M)=>d!==void 0&&S===d.row&&M===d.col;return jsxRuntimeExports.jsxs("div",{className:"wex-twt-wrap",children:[jsxRuntimeExports.jsx("div",{className:"wex-twt-chip-slot",children:k&&jsxRuntimeExports.jsx("span",{className:"wex-twt-total-chip",children:l??`${y.join(" + ")} = ${E}`})}),jsxRuntimeExports.jsxs("table",{className:"wex-twt-table",children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{className:"wex-twt-corner",children:i??""}),o.map((S,M)=>jsxRuntimeExports.jsx("th",{scope:"col",className:`wex-twt-colhead${A(M)?" wex-twt-col-tint":""}`,children:S},M))]})}),jsxRuntimeExports.jsx("tbody",{children:r.map((S,M)=>{const L=M===f,P=[g?"wex-twt-row-in":"",L&&(g||b)?"wex-twt-row-tint":"",L&&x?"wex-twt-row-flash":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsxs("tr",{className:P,style:{"--wex-twt-r":M},children:[jsxRuntimeExports.jsx("th",{scope:"row",className:"wex-twt-rowhead",children:S}),o.map((F,D)=>{const U=a[M]?.[D];if(U==null)return jsxRuntimeExports.jsx("td",{className:"wex-twt-cell wex-twt-unknown wex-pulse",children:"?"},D);const V=x&&h!=null&&M===h.row&&D===h.col,O=b&&L&&!(h!=null&&D===h.col),z=N(M,D),K=["wex-twt-cell",O?"wex-twt-known-lit":"",V?"wex-twt-reveal":"",A(D)?"wex-twt-col-tint":"",z?x?"wex-twt-cell-spot-flash":"wex-twt-cell-spot wex-pulse":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsx("td",{className:K,style:{"--wex-twt-c":D},children:U},D)})]},M)})})]})]},e)}const CX=50,CY=52,R=42,START_ANGLE=135,SWEEP$1=270;function pointAt(n,t){const e=(START_ANGLE+n*SWEEP$1)*(Math.PI/180);return{x:CX+t*Math.cos(e),y:CY+t*Math.sin(e)}}function niceInterval(n){const t=[1,2,5,10,20,25,50,100,200,250,500,1e3];for(const e of t){const s=n/e;if(s>=4&&s<=6)return e}return Math.max(1,Math.round(n/5))}function Dial({reading:n,max:t,unit:e,interval:s,sweeping:o,atReading:r,litTicks:a,emphasis:i,label:l,showReading:u}){const d=t>0?t:1,m=s&&s>0?s:niceInterval(d),h=Math.max(1,Math.round(d/m)),f=Array.from({length:h+1},(k,T)=>{const A=Math.min(T*m,d);return{value:A,fraction:A/d}}),g=Math.max(0,Math.min(1,n/d)),b=r||o?g:0,x=pointAt(b,R-8),y=pointAt(b,-6),E=g*SWEEP$1;return jsxRuntimeExports.jsxs("div",{className:`wex-mc-dial${i?" wex-mc-emph":""}`,children:[jsxRuntimeExports.jsxs("svg",{viewBox:"0 0 100 100",className:"wex-mc-svg",role:"img","aria-label":`${l} scale`,children:[jsxRuntimeExports.jsx("circle",{cx:CX,cy:CY,r:R+4,className:"wex-mc-face-outer"}),jsxRuntimeExports.jsx("circle",{cx:CX,cy:CY,r:R,className:"wex-mc-face-inner"}),f.map(({value:k,fraction:T})=>{const A=pointAt(T,R),N=pointAt(T,R-7),S=pointAt(T,R-14),M=a&&T<=g+1e-6;return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx("line",{x1:A.x,y1:A.y,x2:N.x,y2:N.y,className:`wex-mc-tick${M?" wex-mc-tick-lit":""}`,style:{"--wex-mc-f":T}}),jsxRuntimeExports.jsx("text",{x:S.x,y:S.y+2.5,textAnchor:"middle",className:"wex-mc-numeral",children:k})]},k)}),jsxRuntimeExports.jsx("text",{x:CX,y:CY+22,textAnchor:"middle",className:"wex-mc-unit",children:e}),jsxRuntimeExports.jsxs("g",{className:`wex-mc-needle${o?" wex-mc-sweep":""}`,style:{"--wex-mc-sweep":`${E}deg`},children:[jsxRuntimeExports.jsx("line",{x1:y.x,y1:y.y,x2:x.x,y2:x.y,className:"wex-mc-needle-line"}),jsxRuntimeExports.jsx("circle",{cx:CX,cy:CY,r:4,className:"wex-mc-hub"}),jsxRuntimeExports.jsx("circle",{cx:CX,cy:CY,r:1.4,className:"wex-mc-hub-dot"})]})]}),jsxRuntimeExports.jsx("div",{className:"wex-mc-label",children:l}),u&&jsxRuntimeExports.jsxs("div",{className:"wex-mc-reading",children:[n," ",e]})]})}function MassCompareFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="mass_compare")return null;const s=n.visual_data,{left:o,right:r,left_label:a,right_label:i}=s,l=s.show_readings===!0,u=n.animation==="sweep_left",d=n.animation==="sweep_right",m=n.animation==="highlight_heavier",h=d||m,f=m,g=l&&(h||u),b=l&&(f||d);return jsxRuntimeExports.jsxs("div",{className:"wex-mc-wrap",children:[jsxRuntimeExports.jsx(Dial,{reading:o.reading,max:o.max,unit:o.unit,interval:o.interval,sweeping:u,atReading:h,litTicks:u||h,emphasis:m&&s.emphasis==="left",label:a,showReading:g}),jsxRuntimeExports.jsx(Dial,{reading:r.reading,max:r.max,unit:r.unit,interval:r.interval,sweeping:d,atReading:f,litTicks:d||f,emphasis:m&&s.emphasis==="right",label:i,showReading:b})]},e)}const W$5=240,H=210;function buildShape(n,t){if(n==="square"||n==="rectangle"){const d=n==="square"?112:132,m=(W$5-d)/2,h=50,f=n==="square"?112:98,g=[[m,h],[m+d,h],[m+d,h+f],[m,h+f]],b=0,x=t[1]!==void 0?1:0,y=t[2]!==void 0?2:b,E=t[3]!==void 0?3:x,k=[{mx:m+d/2,my:h,nx:0,ny:-1,side:b,opposite:2},{mx:m+d,my:h+f/2,nx:1,ny:0,side:x,opposite:3},{mx:m+d/2,my:h+f,nx:0,ny:1,side:y,opposite:0},{mx:m,my:h+f/2,nx:-1,ny:0,side:E,opposite:1}];return{points:g,edges:k}}if(n==="triangle"){let d=[W$5/2,40],m=[50,166],h=[W$5-50,166];const[f,g,b]=[t[0],t[1],t[2]];if(f!==void 0&&g!==void 0&&b!==void 0&&f>0&&g>0&&b>0&&f+g>b&&g+b>f&&f+b>g){const N=(g*g+b*b-f*f)/(2*g),S=b*b-N*N;if(S>.01){const M=Math.sqrt(S),L=Math.min(0,g,N),P=Math.max(0,g,N),F=Math.min(150/(P-L),128/M),D=(W$5-(P-L)*F)/2-L*F,U=166;m=[D,U],h=[D+g*F,U],d=[D+N*F,U-M*F]}}const x=[d,h,m],y=(N,S)=>[(N[0]+S[0])/2,(N[1]+S[1])/2],E=y(d,h),k=y(h,m),T=y(m,d),A=[{mx:E[0]+14,my:E[1],nx:1,ny:-.2,side:0,opposite:0},{mx:k[0],my:k[1],nx:0,ny:1,side:1,opposite:1},{mx:T[0]-14,my:T[1],nx:-1,ny:-.2,side:2,opposite:2}];return{points:x,edges:A}}const e=56,s=44,o=124,r=66,a=[[e,s],[e+o,s],[e+o,s+(o-r)],[e+(o-r),s+(o-r)],[e+(o-r),s+o],[e,s+o]],i=a.map((d,m)=>{const h=a[(m+1)%a.length];return[(d[0]+h[0])/2,(d[1]+h[1])/2]}),l=[[0,-1],[1,0],[0,-1],[1,0],[0,1],[-1,0]],u=i.map((d,m)=>({mx:d[0],my:d[1],nx:l[m][0],ny:l[m][1],side:m,opposite:m}));return{points:a,edges:u}}function PerimeterShapeFrame({step:n,stepNumber:t,replayKey:e}){if(n.visual_data.type!=="perimeter_shape")return null;const s=n.visual_data,{shape:o,sides:r}=s,a=s.unit??"cm",i=s.given??r.map((A,N)=>N),l=s.walked??0,u=s.hidden??[],d=n.animation==="show_shape",m=n.animation==="walk_sides",h=n.animation==="total",{points:f,edges:g}=buildShape(o,r),b=f.map(A=>A.join(",")).join(" "),x=g.reduce((A,N)=>A+(r[N.side]??0),0),y=g.slice(0,Math.max(0,Math.min(l,g.length))).reduce((A,N)=>A+(r[N.side]??0),0),E=18,k=A=>`${A}${a?` ${a}`:""}`,T=f[Math.min(l,f.length-1)]??f[0];return jsxRuntimeExports.jsxs("div",{className:"wex-ps-wrap",children:[jsxRuntimeExports.jsxs("svg",{viewBox:`0 0 ${W$5} ${H}`,className:"wex-ps-svg",role:"img","aria-label":`${o.replace("_","-")} perimeter demo`,children:[d&&g.map((A,N)=>{if(!(!i.includes(A.side)&&A.opposite!==A.side&&!u.includes(A.side)))return null;const M=g.find(L=>
71L.side===A.opposite)??g[A.opposite];return M?jsxRuntimeExports.jsx("line",{x1:M.mx,y1:M.my,x2:A.mx,y2:A.my,className:"wex-ps-link"},`link-${N}`):null}),jsxRuntimeExports.jsx("polygon",{points:b,className:`wex-ps-outline${d?" wex-ps-draw":""}${h?" wex-ps-glow":""}`}),m&&g.slice(0,Math.max(0,Math.min(l,g.length))).map((A,N)=>{const S=f[N],M=f[(N+1)%f.length];return jsxRuntimeExports.jsx("line",{x1:S[0],y1:S[1],x2:M[0],y2:M[1],className:"wex-ps-walked",style:{"--wex-ps-i":N}},`walk-${N}`)}),m&&jsxRuntimeExports.jsx("circle",{cx:T[0],cy:T[1],r:6,className:"wex-ps-walker"}),g.map((A,N)=>{const S=r[A.side];if(S===void 0)return null;const M=u.includes(A.side),L=Math.abs(A.nx)>Math.abs(A.ny),P=A.mx+A.nx*(L?8:E),F=A.my+A.ny*E,D=L?A.nx<0?"end":"start":"middle",U=i.includes(A.side)&&!M,q=d&&!U&&!M&&A.opposite!==A.side,V=["wex-ps-label",d&&U?"wex-ps-label-given":"",q?"wex-ps-label-copy":""].filter(Boolean).join(" ");return jsxRuntimeExports.jsx("text",{x:P,y:F+4,textAnchor:D,className:V,children:M?"?":k(S)},N)})]}),m&&jsxRuntimeExports.jsxs("div",{className:"wex-ps-running",children:[y," ",a]}),h&&jsxRuntimeExports.jsxs("div",{className:"wex-ps-banner",children:["Perimeter = ",x," ",a]})]},e)}const AUTO_MIN_STEP_MS=3e3,AUTO_AFTER_VOICE_MS=700,AUTO_LAST_HOLD_MS=2200,AUTO_VOICE_STALL_MS=45e3;function autoStepEstimateMs(n){return Math.min(12e3,Math.max(3800,n.length*62))}function isNotTrueAsk(n){return/\bnot\b[^?]*\btrue\b/i.test(n)}function WorkedExample({microSkillId:n,example:t,onDismiss:e,tieBack:s,eyebrow:o,onTryAnotherWay:r,yourTurn:a,autoPlay:i=!1}){const l=reactExports.useMemo(()=>t??getWorkedExampleForMicroSkill(n),[t,n]),[u,d]=reactExports.useState(0),[m,h]=reactExports.useState(0);reactExports.useEffect(()=>{h(B=>B+1)},[u]);const f=l?.steps[u],g=reactExports.useMemo(()=>{if(!s||!l)return!1;const B=G=>G.replace(/\s+/g," ").trim().toLowerCase();return!!s.optionLetter||!B(l.title).includes(B(s.stem))},[s,l]),b=reactExports.useMemo(()=>{const B=s?.options;return!B||B.length<2||B.length>6||B.filter(X=>X.correct).length!==1||l?.steps[l.steps.length-1]?.visual_data.type==="statement_check"?null:B},[s,l]),x=reactExports.useRef(null),[y,E]=reactExports.useState(!1),k=reactExports.useRef("playing"),T=reactExports.useRef(!1),A=reactExports.useRef(0),N=reactExports.useCallback(()=>{if(!f)return;x.current?.cancel(),k.current="playing",T.current=!1;const G=l!=null&&f===l.steps[l.steps.length-1]&&g&&s?isNotTrueAsk(s.stem)?` Now back to your question. The statement that is not true is: ${s.answer}`:b?` Now back to your question. The answer is ${s.answer}, so the other options are ruled out.`:` Now back to your question. The answer is ${s.answer}.`:"";x.current=speak(toSpeechFriendlyText(f.spoken)+toSpeechFriendlyText(G),{rate:.92,pitch:1.02,onStart:()=>{E(!0),T.current=!0,A.current=Date.now()},onEnd:()=>{E(!1),k.current="done"},onStop:()=>E(!1),onError:()=>{E(!1),k.current="none"}})},[f,l,s,g,b]);reactExports.useEffect(()=>(N(),()=>{x.current?.cancel(),x.current=null,canUseSpeechSynthesis()&&window.speechSynthesis.cancel(),E(!1)}),[N]);const S=reactExports.useRef(0),M=reactExports.useRef(!1),L=B=>{M.current||(M.current=!0,e({outcome:B,stepsReached:u+1,replays:S.current}))},P=l?.steps.length??0,F=f?autoStepEstimateMs(f.spoken):AUTO_MIN_STEP_MS;if(reactExports.useEffect(()=>{if(!i||P===0)return;const B=Date.now(),G=u>=P-1;let X=null;const ne=setInterval(()=>{const se=Date.now()-B,te=k.current,oe=T.current;(te==="done"&&se>=AUTO_MIN_STEP_MS||(te==="none"||!oe)&&se>=F||oe&&te==="playing"&&Date.now()-A.current>=AUTO_VOICE_STALL_MS)&&(clearInterval(ne),X=setTimeout(()=>{G?L("completed"):d(ae=>Math.min(ae+1,P-1))},G?AUTO_LAST_HOLD_MS:AUTO_AFTER_VOICE_MS))},150);return()=>{clearInterval(ne),X&&clearTimeout(X)}},[i,u,P,F]),!l||l.steps.length===0||!f)return L("skipped"),null;const D=s?.stem.split(/_{2,}/),U=s&&!s.optionLetter&&D?.length===2?D:null,q=s?askSentence(s.stem):"",V=s&&isNotTrueAsk(s.stem)?"The statement that is NOT true":"Answer",O=l.steps.length,z=u===O-1,K=u+1,Z=()=>{if(z){L("completed");return}d(u+1)},Q=()=>{S.current+=1,h(B=>B+1),N()};return jsxRuntimeExports.jsx("div",{className:"wex-overlay",role:"dialog","aria-modal":"true","aria-labelledby":"wex-title",onClick:()=>L("backdrop"),children:jsxRuntimeExports.jsxs("div",{className:"wex-card",onClick:B=>B.stopPropagation(),"data-auto":i||void 0,children:[i&&jsxRuntimeExports.jsx("div",{className:"wex-auto-progress","aria-hidden":"true",children:l.steps.map((B,G)=>jsxRuntimeExports.jsx("span",{className:"wex-auto-seg",children:jsxRuntimeExports.jsx("span",{className:`wex-auto-fill${G<u?" done":G===u?" running":""}`,style:G===u?{"--wex-auto-ms":`${F}ms`}:void 0},G===u?`run-${u}`:`seg-${G}`)},G))}),jsxRuntimeExports.jsxs("header",{className:"wex-head wex-head-mia",children:[jsxRuntimeExports.jsx("span",{className:"wex-mia",children:jsxRuntimeExports.jsx(Mia,{size:46,mood:"hint",speaking:y})}),jsxRuntimeExports.jsxs("div",{className:"wex-head-text",children:[jsxRuntimeExports.jsx("p",{className:"wex-eyebrow",children:o??"Watch me do one"}),jsxRuntimeExports.jsx("h2",{id:"wex-title",className:"wex-title",children:l.title})]}),jsxRuntimeExports.jsx("button",{type:"button",className:"wex-skip wex-close",onClick:()=>L("skipped"),"aria-label":"Skip worked example",title:"Close",children:"✕"})]}),jsxRuntimeExports.jsx("div",{className:"wex-stage",children:jsxRuntimeExports.jsx(FitStage,{fitKey:`${u}-${m}`,children:jsxRuntimeExports.jsx(Frame,{step:f,stepNumber:K,replayKey:m})})},i?`stage-${u}`:"stage"),jsxRuntimeExports.jsxs("div",{className:"wex-caption-row",children:[jsxRuntimeExports.jsx("p",{className:"wex-caption",children:displayEmojiInText(f.caption)},`cap-${u}`),!i&&jsxRuntimeExports.jsx("button",{type:"button",className:"wex-replay",onClick:Q,"aria-label":"Replay this step",title:"Replay",children:"↻ Replay"})]}),z&&g&&s&&jsxRuntimeExports.jsxs("div",{className:"wex-tieback",children:[jsxRuntimeExports.jsx("p",{className:"wex-tieback-label",children:"Back to your question"}),U?jsxRuntimeExports.jsxs("p",{className:"wex-tieback-stem wex-tieback-filled",children:[U[0],jsxRuntimeExports.jsx("span",{className:"wex-tieback-value",children:s.answer}),U[1]]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("p",{className:"wex-tieback-stem",children:q}),b?jsxRuntimeExports.jsx("ul",{className:`wex-tieback-options${b.length%2===0&&b.every(B=>B.label.length<=8)?" wex-tieback-options-grid":""}`,"aria-label":"Every option, with the wrong ones ruled out",children:b.map(B=>jsxRuntimeExports.jsxs("li",{className:`wex-tbo ${B.correct?"wex-tbo-answer":"wex-tbo-out"}${B.picked?" wex-tbo-picked":""}`,"aria-label":`option ${B.letter}: ${B.label}${B.correct?", the answer":", ruled out"}${B.picked?", your answer":""}`,children:[jsxRuntimeExports.jsx("span",{className:"wex-tieback-letter",children:B.letter}),jsxRuntimeExports.jsxs("span",{className:"wex-tbo-body",children:[jsxRuntimeExports.jsx("span",{className:"wex-tbo-label",children:B.label}),B.picked&&jsxRuntimeExports.jsx("span",{className:"wex-tbo-tag",children:"your answer"})]}),jsxRuntimeExports.jsx("span",{className:"wex-tbo-mark","aria-hidden":"true",children:B.correct?"✓":"✗"})]},B.letter))}):jsxRuntimeExports.jsxs("p",{className:"wex-tieback-answer",children:[jsxRuntimeExports.jsx("span",{className:"wex-tieback-answer-label",children:V}),s.optionLetter?jsxRuntimeExports.jsxs("span",{className:"wex-tieback-option","aria-label":`option ${s.optionLetter}: ${s.answer}`,children:[jsxRuntimeExports.jsx("span",{className:"wex-tieback-letter",children:s.optionLetter}),jsxRuntimeExports.jsx("span",{className:"wex-tieback-value",children:s.answer})]}):jsxRuntimeExports.jsx("span",{className:"wex-tieback-value",children:s.answer})]})]})]},`tie-${m}`),z&&a&&!s&&jsxRuntimeExports.jsxs("p",{className:"wex-your-turn",children:[jsxRuntimeExports.jsx("b",{children:"Your turn!"})," Now try yours the same way."]}),!i&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("div",{className:"wex-progress","aria-hidden":"true",children:l.steps.map((B,G)=>jsxRuntimeExports.jsx("span",{className:`wex-dot${G<=u?" active":""}`},G))}),jsxRuntimeExports.jsx("button",{type:"button",className:"wex-next",onClick:Z,children:f.cta})]}),z&&r&&!i&&jsxRuntimeExports.jsxs("button",{type:"button",className:"wex-another-way",onClick:()=>{L("completed"),r()},children:["Still not sure? ",HELP_COPY.anotherWay]})]})})}function FitStage({children:n,fitKey:t}){const e=reactExports.useRef(null),s=reactExports.useRef(null),[o,r]=reactExports.useState(null),a=reactExports.useRef(null);return a.current=o,reactExports.useLayoutEffect(()=>{const i=()=>{const u=e.current,d=s.current;if(!u||!d)return;const m=u.clientWidth,h=a.current?.scale??1;let f=1/0,g=-1/0;for(const y of Array.from(d.querySelectorAll("*"))){const E=y.getBoundingClientRect();E.width===0&&E.height===0||(E.left<f&&(f=E.left),E.right>g&&(g=E.right))}const b=g>f?(g-f)/h:0,x=Math.max(d.scrollWidth,Math.ceil(b));if(x>m+1&&m>0){const y=Math.max(m-8,1)/x,E=Math.ceil(d.scrollHeight*y),k=a.current;(!k||Math.abs(k.scale-y)>.005||k.height!==E)&&r({scale:y,height:E})}else a.current&&r(null)};i();const l=new ResizeObserver(i);return e.current&&l.observe(e.current),s.current&&l.observe(s.current),()=>l.disconnect()},[t]),jsxRuntimeExports.jsx("div",{ref:e,className:"wex-fit-outer",style:o?{height:o.height}:void 0,children:jsxRuntimeExports.jsx("div",{ref:s,className:"wex-fit-inner",style:o?{transform:`scale(${o.scale})`,transformOrigin:"top center"}:void 0,children:n})})}function Frame({step:n,stepNumber:t,replayKey:e}){switch(n.visual_data.type){case"ten_frame_overflow":return jsxRuntimeExports.jsx(TenFrameOverflowFrame,{step:n,stepNumber:t,replayKey:e});case"sequence":return jsxRuntimeExports.jsx(SequenceFrame,{step:n,stepNumber:t,replayKey:e});case"part_whole":return jsxRuntimeExports.jsx(PartWholeFrame,{step:n,stepNumber:t,replayKey:e});case"ten_frame":case"filled_slots":return jsxRuntimeExports.jsx(TenFrameFillFrame,{step:n,stepNumber:t,replayKey:e});case"mirror":return jsxRuntimeExports.jsx(MirrorFrame,{step:n,stepNumber:t,replayKey:e});case"number_line":return jsxRuntimeExports.jsx(NumberLineFrame,{step:n,stepNumber:t,replayKey:e});case"take_away":return jsxRuntimeExports.jsx(TakeAwayFrame,{step:n,stepNumber:t,replayKey:e});case"objects_grid":return jsxRuntimeExports.jsx(ObjectsGridFrame,{step:n,stepNumber:t,replayKey:e});case"two_step":return jsxRuntimeExports.jsx(TwoStepFrame,{step:n,stepNumber:t,replayKey:e});case"equation_display":return jsxRuntimeExports.jsx(EquationDisplayFrame,{step:n,stepNumber:t,replayKey:e});case"comparison":return jsxRuntimeExports.jsx(ComparisonFrame,{step:n,stepNumber:t,replayKey:e});case"ten_frame_and_ones":return jsxRuntimeExports.jsx(TenFrameAndOnesFrame,{step:n,stepNumber:t,replayKey:e});case"tally":return jsxRuntimeExports.jsx(TallyFrame,{step:n,stepNumber:t,replayKey:e});case"fraction_shape":return jsxRuntimeExports.jsx(FractionShapeFrame,{step:n,stepNumber:t,replayKey:e});case"shape_svg":return jsxRuntimeExports.jsx(ShapeSvgFrame,{step:n,stepNumber:t,replayKey:e});case"coin_set":return jsxRuntimeExports.jsx(CoinSetFrame,{step:n,stepNumber:t,replayKey:e});case"measure_bars":return jsxRuntimeExports.jsx(MeasureBarsFrame,{step:n,stepNumber:t,replayKey:e});case"clock":return jsxRuntimeExports.jsx(ClockFrame,{step:n,stepNumber:t,replayKey:e});case"array":return jsxRuntimeExports.jsx(ArrayFrame,{step:n,stepNumber:t,replayKey:e});case"pictogram":return jsxRuntimeExports.jsx(PictogramFrame,{step:n,stepNumber:t,replayKey:e});case"bar_chart":return jsxRuntimeExports.jsx(BarChartFrame,{step:n,stepNumber:t,replayKey:e});case"mass_scale":return jsxRuntimeExports.jsx(MassScaleFrame,{step:n,stepNumber:t,replayKey:e});case"shape_3d":return jsxRuntimeExports.jsx(Shape3DFrame,{step:n,stepNumber:t,replayKey:e});case"turn":return jsxRuntimeExports.jsx(TurnDiagramFrame,{step:n,stepNumber:t,replayKey:e});case"column_method":return jsxRuntimeExports.jsx(ColumnMethodFrame,{step:n,stepNumber:t,replayKey:e});case"short_division":return jsxRuntimeExports.jsx(ShortDivisionFrame,{step:n,stepNumber:t,replayKey:e});case"coordinate_grid":return jsxRuntimeExports.jsx(CoordinateGridFrame,{step:n,stepNumber:t,replayKey:e});case"angle_diagram":return jsxRuntimeExports.jsx(AngleDiagramFrame,{step:n,stepNumber:t,replayKey:e});case"area_grid":return jsxRuntimeExports.jsx(AreaGridFrame,{step:n,stepNumber:t,replayKey:e});case"domino_set":return jsxRuntimeExports.jsx(DominoFrame,{step:n,stepNumber:t,replayKey:e});case"coin_tap":return jsxRuntimeExports.jsx(CoinTapFrame,{step:n,stepNumber:t,replayKey:e});case"ten_frame_mixed":return jsxRuntimeExports.jsx(TenFrameMixedFrame,{step:n,stepNumber:t,replayKey:e});case"fact_family":return jsxRuntimeExports.jsx(FactFamilyFrame,{step:n,stepNumber:t,replayKey:e});case"shape_3d_name":return jsxRuntimeExports.jsx(Shape3DNameFrame,{step:n,stepNumber:t,replayKey:e});case"bar_builder":return jsxRuntimeExports.jsx(BarBuilderFrame,{step:n,stepNumber:t,replayKey:e});case"two_way_table":return jsxRuntimeExports.jsx(TwoWayTableFrame,{step:n,stepNumber:t,replayKey:e});case"mass_compare":return jsxRuntimeExports.jsx(MassCompareFrame,{step:n,stepNumber:t,replayKey:e});case"perimeter_shape":return jsxRuntimeExports.jsx(PerimeterShapeFrame,{step:n,stepNumber:t,replayKey:e});case"line_graph":return jsxRuntimeExports.jsx(LineGraphFrame,{step:n,stepNumber:t,replayKey:e});case"pie_chart":return jsxRuntimeExports.jsx(PieChartFrame,{step:n,stepNumber:t,replayKey:e});case"reflection_grid":return jsxRuntimeExports.jsx(ReflectionGridFrame,{step:n,stepNumber:t,replayKey:e});case"triangle_figure":return jsxRuntimeExports.jsx(TriangleFigureFrame,{step:n,stepNumber:t,replayKey:e});case"triangle_area":return jsxRuntimeExports.jsx(TriangleAreaFrame,{step:n,stepNumber:t,replayKey:e});case"long_multiplication":return jsxRuntimeExports.jsx(LongMultiplicationFrame,{step:n,stepNumber:t,replayKey:e});case"long_division":return jsxRuntimeExports.jsx(LongDivisionFrame,{step:n,stepNumber:t,replayKey:e});case"volume_cuboid":return jsxRuntimeExports.jsx(VolumeCuboidFrame,{step:n,stepNumber:t,replayKey:e});case"ratio_bar":return jsxRuntimeExports.jsx(RatioBarFrame,{step:n,stepNumber:t,replayKey:e});case"percent_bar":return jsxRuntimeExports.jsx(PercentBarFrame,{step:n,stepNumber:t,replayKey:e});case"place_value_chart":return jsxRuntimeExports.jsx(PlaceValueChartFrame,{step:n,stepNumber:t,replayKey:e});case"statement_check":return jsxRuntimeExports.jsx(StatementCheckFrame,{step:n,stepNumber:t,replayKey:e});case"symmetry_lines":return jsxRuntimeExports.jsx(SymmetryLinesFrame,{step:n,stepNumber:t,replayKey:e});case"quadrilateral":return jsxRuntimeExports.jsx(QuadrilateralFrame,{step:n,stepNumber:t,replayKey:e});default:return null}}const FAMILY_LABELS={"number line":"with a number line","counting on or back":"by counting","part-whole (number bond)":"with a number bond","base-ten (tens and ones)":"with tens and ones","ten-frame":"with a ten frame","groups/array":"with groups","short real-life object story":"with a story","bar model":"with a bar model","area model (rectangle split into parts)":"with an area model","place-value chart (columns)":"with a place-value chart","fraction bar (a strip split into equal parts, some shaded)":"with a fraction strip","hundred grid (100 small squares, some shaded)":"with a hundred square","ratio table":"with a ratio table","quick labelled sketch":"with a sketch"};function explainAltEyebrow(n){const t=n?FAMILY_LABELS[n]:void 0;return t?`Another way · ${t}`:"Another way"}function ExplainAltOverlay({steps:n,representation:t,onDismiss:e,onAnother:s,canAnother:o,loadingAnother:r}){const[a,i]=reactExports.useState(0),l=reactExports.useRef(null),[u,d]=reactExports.useState(!1),m=n[a],h=a===n.length-1;reactExports.useEffect(()=>{i(0)},[n]);const f=reactExports.useCallback(()=>{m&&(l.current?.cancel(),l.current=speak(toSpeechFriendlyText(m.spoken),{rate:.92,pitch:1.02,onStart:()=>d(!0),onEnd:()=>d(!1),onStop:()=>d(!1),onError:()=>d(!1)}))},[m]);if(reactExports.useEffect(()=>(f(),()=>{l.current?.cancel(),l.current=null,canUseSpeechSynthesis()&&window.speechSynthesis.cancel(),d(!1)}),[f]),!m)return null;const g=()=>{if(h){l.current?.cancel(),e();return}i(E=>E+1)},b=()=>f(),x=()=>{l.current?.cancel(),e(
71)},y=()=>{l.current?.cancel(),s?.()};return jsxRuntimeExports.jsx("div",{className:"wex-overlay",role:"dialog","aria-modal":"true","aria-labelledby":"explain-alt-title",onClick:x,children:jsxRuntimeExports.jsxs("div",{className:"wex-card",onClick:E=>E.stopPropagation(),children:[jsxRuntimeExports.jsxs("header",{className:"wex-head wex-head-mia",children:[jsxRuntimeExports.jsx("span",{className:"wex-mia",children:jsxRuntimeExports.jsx(Mia,{size:46,mood:"hint",speaking:u})}),jsxRuntimeExports.jsxs("div",{className:"wex-head-text",children:[jsxRuntimeExports.jsx("p",{className:"wex-eyebrow",children:explainAltEyebrow(t)}),jsxRuntimeExports.jsx("h2",{id:"explain-alt-title",className:"wex-title",children:"Let's try it a different way"})]}),jsxRuntimeExports.jsx("button",{type:"button",className:"wex-skip wex-close",onClick:x,"aria-label":"Close",title:"Close",children:"✕"})]}),jsxRuntimeExports.jsx("div",{className:"wex-stage wex-stage--text",children:jsxRuntimeExports.jsx("p",{className:"wex-alt-step",children:m.text},`step-${a}`)}),jsxRuntimeExports.jsxs("div",{className:"wex-caption-row",children:[jsxRuntimeExports.jsx("span",{className:"wex-alt-badge","aria-hidden":"true",children:"✨ made just now"}),jsxRuntimeExports.jsx("button",{type:"button",className:"wex-replay",onClick:b,"aria-label":"Hear this step again",title:"Replay",children:"↻ Replay"})]}),jsxRuntimeExports.jsx("div",{className:"wex-progress","aria-hidden":"true",children:n.map((E,k)=>jsxRuntimeExports.jsx("span",{className:`wex-dot${k<=a?" active":""}`},E.step))}),jsxRuntimeExports.jsx("button",{type:"button",className:"wex-next",onClick:g,children:m.cta||(h?"Got it":"Next")}),h&&s&&o&&jsxRuntimeExports.jsx("button",{type:"button",className:"wex-alt-another",onClick:y,disabled:r,children:r?"Thinking…":"🔁 Still not sure — show me another way"}),h&&s&&!o&&jsxRuntimeExports.jsx("p",{className:"wex-alt-grownup",children:"If it's still tricky, ask a grown-up to help. 💛"})]})})}async function getAlternateExplanation(n,t={},e=12e3){const s=JSON.stringify({questionId:n,wrongAnswer:t.wrongAnswer,alreadyShown:t.alreadyShown}),o=async()=>{const a=new AbortController,i=setTimeout(()=>a.abort(),e);try{const l=await buildApiAuthHeaders(),u=await fetch("/api/explain-alt",{method:"POST",headers:{"Content-Type":"application/json",...l},body:s,signal:a.signal});if(!u.ok)return null;const d=await u.json();return!d.steps||d.steps.length===0?null:{representation:d.representation??"alternative",steps:d.steps}}catch{return null}finally{clearTimeout(i)}},r=new Promise(a=>setTimeout(()=>a(null),e+1500));return Promise.race([o(),r])}function ClueNote({kind:n,message:t,thinking:e,isSpeaking:s,onReplay:o,replayLabel:r,onAnotherClue:a,anotherLabel:i}){return jsxRuntimeExports.jsxs("div",{className:`sm-clue-note sm-clue-note-${n}`,children:[jsxRuntimeExports.jsx(Mia,{size:34,mood:e?"thinking":"hint",speaking:s}),jsxRuntimeExports.jsxs("div",{className:"sm-clue-body",role:"status","aria-live":"polite","aria-atomic":"true","aria-label":e?"Mia is thinking":`Mia: ${t}`,children:[e?jsxRuntimeExports.jsxs("span",{className:"sm-tutor-thinking","aria-hidden":"true",children:[jsxRuntimeExports.jsx("span",{}),jsxRuntimeExports.jsx("span",{}),jsxRuntimeExports.jsx("span",{})]}):jsxRuntimeExports.jsx("p",{className:"sm-clue-text",children:t}),!e&&(o||a)&&jsxRuntimeExports.jsxs("div",{className:"sm-clue-actions",children:[o&&jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-clue-again",onClick:o,"aria-label":r??"Hear it again",children:[jsxRuntimeExports.jsx(SpeakerIcon$1,{})," ",r??"Again"]}),a&&jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-clue-more",onClick:a,children:[i??"Another clue"," →"]})]})]})]})}const STORAGE_KEY="sm.helpIntroSeen.v1";function hasSeenHelpIntro(){try{return typeof window<"u"&&window.localStorage.getItem(STORAGE_KEY)==="1"}catch{return!1}}function markHelpIntroSeen(){try{window.localStorage.setItem(STORAGE_KEY,"1")}catch{}}function HelpIntro({onDismiss:n}){return jsxRuntimeExports.jsxs("div",{className:"sm-help-intro",role:"note","aria-label":"Mia's help",children:[jsxRuntimeExports.jsxs("p",{className:"sm-help-intro-text",children:[jsxRuntimeExports.jsx("b",{children:HELP_COPY.introTitle})," ",jsxRuntimeExports.jsx("span",{className:"sm-help-intro-mobile",children:HELP_COPY.introBody}),jsxRuntimeExports.jsx("span",{className:"sm-help-intro-desktop",children:HELP_COPY.introBodyDesktop})]}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-help-intro-close",onClick:n,"aria-label":"Got it",children:"✕"})]})}function HelpChoices({shownHintCount:n,hintDisabled:t,canWatch:e,canRead:s,isReading:o,onClue:r,onWatch:a,onRead:i,variant:l}){const u=n>=3,d=!(u&&e),m=l==="sheet";return jsxRuntimeExports.jsxs("div",{className:`sm-help-choices sm-help-choices-${l}`,role:"group","aria-label":"Help from Mia",children:[d&&jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-help-choice sm-help-clue",onClick:r,disabled:t,children:[jsxRuntimeExports.jsx(ClueIcon,{}),jsxRuntimeExports.jsxs("span",{className:"sm-help-choice-text",children:[u?"That's all my clues":clueLabel(n),m&&!u&&jsxRuntimeExports.jsx("small",{children:HELP_SUBLINES.clue})]})]}),e&&jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-help-choice sm-help-watch",onClick:a,children:[jsxRuntimeExports.jsx(PlayIcon,{}),jsxRuntimeExports.jsxs("span",{className:"sm-help-choice-text",children:[HELP_COPY.watchBefore,m&&jsxRuntimeExports.jsx("small",{children:HELP_SUBLINES.watch})]})]}),s&&jsxRuntimeExports.jsxs("button",{type:"button",className:`sm-help-choice sm-help-read${o?" active":""}`,onClick:i,children:[jsxRuntimeExports.jsx(SpeakerIcon$1,{}),jsxRuntimeExports.jsxs("span",{className:"sm-help-choice-text",children:[o?HELP_COPY.reading:HELP_COPY.read,m&&jsxRuntimeExports.jsx("small",{children:HELP_SUBLINES.read})]})]})]})}function HelpSheet({onClose:n,onClue:t,onWatch:e,onRead:s,...o}){const r=reactExports.useRef(null);reactExports.useEffect(()=>{r.current?.querySelector("button:not(:disabled)")?.focus();const l=u=>{u.key==="Escape"&&n()};return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[n]);const a=i=>()=>{n(),i()};return jsxRuntimeExports.jsx("div",{className:"sm-help-sheet-backdrop",onClick:n,children:jsxRuntimeExports.jsxs("div",{ref:r,className:"sm-help-sheet",role:"dialog","aria-modal":"true","aria-labelledby":"sm-help-sheet-title",onClick:i=>i.stopPropagation(),children:[jsxRuntimeExports.jsxs("div",{className:"sm-help-sheet-head",children:[jsxRuntimeExports.jsx(Mia,{size:48,mood:"hint"}),jsxRuntimeExports.jsx("p",{id:"sm-help-sheet-title",className:"sm-help-sheet-bubble",children:HELP_COPY.sheetTitle}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-help-close",onClick:n,"aria-label":"Close help",children:"✕"})]}),jsxRuntimeExports.jsx(HelpChoices,{...o,variant:"sheet",onClue:a(t),onWatch:a(e),onRead:a(s)})]})})}function SessionDock({mode:n}){return jsxRuntimeExports.jsxs("div",{className:"sm-dock",role:"group","aria-label":"Answer bar",children:[n.kind==="answer"&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsxs("button",{type:"button",className:`sm-dock-
71help${n.helpPulse?" pulse":""}`,onClick:n.onHelp,"aria-haspopup":"dialog",children:[jsxRuntimeExports.jsx(Mia,{size:28,mood:"idle"}),HELP_COPY.helpButton]}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-check sm-dock-main",disabled:n.checkDisabled,onClick:n.onCheck,children:n.checkLabel})]}),n.kind==="ask"&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsxs("div",{className:"sm-dock-ask",children:[jsxRuntimeExports.jsx(Mia,{size:30,mood:"hint"}),jsxRuntimeExports.jsx("p",{role:"status","aria-live":"polite",children:n.question})]}),jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-watch-btn sm-dock-yes",onClick:n.onYes,children:[jsxRuntimeExports.jsx(PlayIcon,{})," ",n.yesLabel]}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-quiet-btn sm-dock-no",onClick:n.onNext,disabled:n.nextDisabled,children:n.nextLabel})]}),n.kind==="next"&&jsxRuntimeExports.jsx("button",{type:"button",className:"sm-check next sm-dock-main",onClick:n.onNext,disabled:n.nextDisabled,children:n.nextLabel})]})}function MiaAsks({question:n,yesLabel:t,noLabel:e,onYes:s,onNo:o,noDisabled:r,isSpeaking:a,cue:i,lookAt:l}){return jsxRuntimeExports.jsxs("div",{className:"sm-mia-asks",children:[jsxRuntimeExports.jsxs("div",{className:"sm-mia-asks-head",children:[jsxRuntimeExports.jsx(Mia,{size:56,mood:"hint",speaking:a,cue:i,lookAt:l}),jsxRuntimeExports.jsx("p",{className:"sm-mia-asks-bubble",role:"status","aria-live":"polite",children:n})]}),jsxRuntimeExports.jsxs("div",{className:"sm-mia-asks-actions",children:[jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-watch-btn",onClick:s,children:[jsxRuntimeExports.jsx(PlayIcon,{})," ",t]}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-quiet-btn",onClick:o,disabled:r,children:e})]})]})}function SessionSidePanel({answering:n,chatMessage:t,chatMood:e,isSpeaking:s,cue:o,lookAt:r,onMiaTap:a,showIntro:i,onDismissIntro:l,choices:u,ask:d,check:m,next:h,onComeBack:f,autoNextIn:g}){return jsxRuntimeExports.jsxs("aside",{className:"sm-q-side",children:[n&&i&&jsxRuntimeExports.jsx(HelpIntro,{onDismiss:l}),d?jsxRuntimeExports.jsx(MiaAsks,{question:d.question,yesLabel:d.yesLabel,noLabel:d.noLabel,onYes:d.onYes,onNo:d.onNo,noDisabled:d.noDisabled,isSpeaking:s,cue:o,lookAt:r}):t?jsxRuntimeExports.jsx(TutorAvatar,{message:t,mood:e,tutorName:"Mia",size:64,isSpeaking:s,cue:o,lookAt:r}):jsxRuntimeExports.jsxs("div",{className:"sm-tutor-block",children:[n?jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-tutor-head sm-mia-tap",onClick:a,"aria-label":"Ask Mia for help",children:[jsxRuntimeExports.jsx(Mia,{size:64,mood:"idle",cue:o,lookAt:r,speaking:s}),jsxRuntimeExports.jsxs("span",{className:"sm-mia-tap-text",children:[jsxRuntimeExports.jsx("b",{children:"Mia"}),jsxRuntimeExports.jsx("span",{children:"Your tutor"})]})]}):jsxRuntimeExports.jsxs("div",{className:"sm-tutor-head",children:[jsxRuntimeExports.jsx(Mia,{size:64,mood:"idle",cue:o,lookAt:r,speaking:s}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("b",{children:"Mia"}),jsxRuntimeExports.jsx("span",{children:"Your tutor"})]})]}),n&&jsxRuntimeExports.jsxs("p",{className:"sm-tutor-msg tone-neutral sm-mia-invite",children:[jsxRuntimeExports.jsx("span",{className:"sm-help-intro-mobile",children:HELP_COPY.miaIdle}),jsxRuntimeExports.jsx("span",{className:"sm-help-intro-desktop",children:HELP_COPY.miaIdleDesktop})]})]}),n&&jsxRuntimeExports.jsx(HelpChoices,{...u,variant:"panel"}),n?jsxRuntimeExports.jsx("button",{className:"sm-check sm-side-main",type:"button",disabled:m.disabled,onClick:m.onCheck,children:m.label}):!d&&jsxRuntimeExports.jsx("button",{className:"sm-check next sm-side-main",type:"button",disabled:h.disabled,onClick:h.onNext,children:h.label}),n&&f&&jsxRuntimeExports.jsxs("button",{type:"button",className:"sm-come-back",onClick:f,children:[jsxRuntimeExports.jsx(ComeBackIcon,{})," ",HELP_COPY.comeBack]}),!n&&g!==null&&jsxRuntimeExports.jsxs("p",{className:"sm-autonext","aria-live":"polite",children:["Next one in ",g,"…"]})]})}function wexStemForQuestion(n,t,e){const s=t?.trim();return e?.type==="drag_order"&&s?s:n}const NUMBER_WORDS=["zero","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen","twenty","twenty-one","twenty-two","twenty-three","twenty-four","twenty-five","twenty-six","twenty-seven","twenty-eight","twenty-nine","thirty"];function numberWord(n){const t=NUMBER_WORDS[n];if(t===void 0)throw new Error(`numberWord out of range: ${n}`);return t}function capitalised(n){return n.charAt(0).toUpperCase()+n.slice(1)}function countingRun(n,t){const e=[];for(let s=n;s<=t;s++)e.push(numberWord(s));return e.join(", ")}const ONES_WORDS=["zero","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"],TENS_WORDS$1=["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"];function numberToWords(n){if(!Number.isInteger(n)||n<0||n>999)throw new Error(`numberToWords out of range: ${n}`);if(n<20)return ONES_WORDS[n];if(n<100){const o=Math.floor(n/10),r=n%10;return r===0?TENS_WORDS$1[o]:`${TENS_WORDS$1[o]}-${ONES_WORDS[r]}`}const t=Math.floor(n/100),e=n%100,s=`${ONES_WORDS[t]} hundred`;return e===0?s:`${s} and ${numberToWords(e)}`}function countingWords(n,t){const e=[];for(let s=n;s<=t;s++)e.push(numberToWords(s));return e.join(", ")}function numberToWordsLong(n){if(!Number.isInteger(n)||n<0||n>999999)throw new Error(`numberToWordsLong out of range: ${n}`);if(n<1e3)return numberToWords(n);const t=Math.floor(n/1e3),e=n%1e3,s=`${numberToWords(t)} thousand`;return e===0?s:e<100?`${s} and ${numberToWords(e)}`:`${s} ${numberToWords(e)}`}function digitsToWords(n){return n.replace(/\d+/g,t=>numberToWordsLong(Number(t)))}const DIGIT_WORDS$1=["zero","one","two","three","four","five","six","seven","eight","nine"];function decimalToWords(n){const t=(typeof n=="number"?String(n):n).trim(),e=t.match(/^(\d+)(?:\.(\d+))?$/);if(!e)throw new Error(`decimalToWords cannot read: ${t}`);const s=numberToWordsLong(Number(e[1]));if(!e[2])return s;const o=[...e[2]].map(r=>DIGIT_WORDS$1[Number(r)]).join(" ");return`${s} point ${o}`}function normaliseStem(n){return n.replace(/−/g,"-").replace(/_{2,}/g,"__").replace(/\s+/g," ").trim().replace(/\.$/,"")}function matchInts(n,t){const e=normaliseStem(n).match(t);return e?e.slice(1).map(Number):null}function answerInt(n){const t=Number(n.correctAnswer);return Number.isInteger(t)?t:null}function isAffirmative(n){return/^yes\b/i.test(n.trim())}function isNegative(n){return/^no\b/i.test(n.trim())}function partWholeFromVisual(n){const t=n.visualData;if(!t||t.type!=="part_whole"||!Number.isInteger(t.whole)||!Array.isArray(t.parts)||t.parts.length!==2)return null;const e=t.parts.filter(o=>Number.isInteger(o)),s=t.parts.filter(o=>o===null);return e.length!==1||s.length!==1?null:{whole:t.whole,knownPart:e[0]}}function buildExample(n){const t=n.steps.map((e,s)=>({step:s+1,visual_data:e.visual_data,animation:e.animation,caption:e.caption,spoken:e.spoken,cta:s===n.steps.length-1?"Got it!":"Next",input:null,audio_url:null}));return{micro_skill_id:n.question.microSkillId,title:n.title,steps:t,provenance:{kind:"parameterized",generator_id:n.generatorId,source_question_id:n.question.questionId,computed:n.computed}}}function countOnPlan(n,t){const e=t-n;if(e>=4&&n<10&&t>10){const s=10-n,o=t-10;return{kind:"bridge",toTen:s,rest:o,hops:[{from:n,to:10,label:`+${s}`},{from:10,to:t,label:`+${o}`}]}}if(e>=1&&e<=9){const s=[];for(let o=n;o<t;o++)s.push({from:o,to:o+1,label:"+1"});return{kind:"ones",hops:s}}return{kind:"jump",hops:[{from:n,to:t,label:`+${e}`}]}}function onesWindow(n,t){const e=Math.min(10,Math.max(0,5*Math.floor((n-1)/5)));return t<=e+10?{min:e,max:e+10}:{min:0,max:20}}const SPECS=[{questionId:"FDP.P4.M03.Q214",microSkillId:"FDP.P4.M03",stem:"Which statement about 0.08 is true?",ask:"true",statements:[{label:"It has 8 hundredths",verdict:!0,reason:"0.08 is 0 ones, 0 tenths and 8 hundredths.",said:"Zero point zero eight has no ones, no tenths and eight hundredths.",labelSaid:"It has eight hundredths"},{label:"It has 8 tenths",labelSaid:"It has eight tenths",verdict:!1,reason:"The tenths digit is 0. The 8 is in the hundredths.",said:"The tenths digit is zero; the eight is one place further right, in the hundredths."},{label:"It is the same as 80",labelSaid:"It is the same as eighty",verdict:!1,reason:"80 is eighty whole ones. 0.08 is less than 1.",said:"Eighty is eighty whole ones, but zero point zero eight is less than one."},{label:"It is the same as 8",labelSaid:"It is the same as eight",verdict:!1,reason:"8 is eight whole ones. 0.08 is less than 1.",said:"Eight is eight whole ones, but zero point zero eight is less than one."}]},{questionId:"GEO.P6.M01.Q115",microSkillId:"GEO.P6.M01",stem:"A triangle is reflected in a mirror line. Which statement about the reflection is always true?",ask:"true",statements:[{label:"Each corner is the same distance from the line as before, on the other side",verdict:!0,reason:"A mirror keeps every distance and flips it across the line.",said:"A mirror keeps every distance the same and flips it across the line."},{label:"The corners move closer to the mirror line",verdict:!1,reason:"Each corner stays as far from the line as before.",s
71aid:"Each corner stays exactly as far from the mirror line as it was."},{label:"The reflection stays on the same side as the triangle",verdict:!1,reason:"A reflection lands on the other side of the mirror line.",said:"A reflection always lands on the other side of the mirror line."}]},{questionId:"GEO.P6.M02.Q111",microSkillId:"GEO.P6.M02",stem:"Which statement about an equilateral triangle is true?",ask:"true",statements:[{label:"All three of its angles are equal.",verdict:!0,reason:"Three equal sides make three equal angles, 60° each.",said:"Three equal sides make three equal angles, sixty degrees each."},{label:"It has exactly two equal sides.",verdict:!1,reason:"That is isosceles. Equilateral has all three sides equal.",said:"Exactly two equal sides is an isosceles triangle. An equilateral triangle has all three sides equal."},{label:"One of its angles is a right angle.",verdict:!1,reason:"Its angles are all 60°, so none is 90°.",said:"Its angles are all sixty degrees, so none of them is a right angle."}]},{questionId:"GEO.P6.M02.Q116",microSkillId:"GEO.P6.M02",stem:"Which statement is NOT true?",ask:"not-true",statements:[{label:"An equilateral triangle has three equal sides.",verdict:!0,reason:"Equilateral means all sides equal.",said:"Equilateral means all its sides are equal."},{label:"A scalene triangle has all sides different.",verdict:!0,reason:"Scalene means no two sides are equal.",said:"Scalene means no two sides are the same length."},{label:"An isosceles triangle has three equal angles.",verdict:!1,reason:"Isosceles has two equal angles, not three.",said:"An isosceles triangle has two equal sides and two equal angles, not three."}]},{questionId:"GEO.P6.M02.Q126",microSkillId:"GEO.P6.M02",stem:"A triangle is equilateral. Which of these is also true about it?",ask:"true",statements:[{label:"All three of its angles are 60°.",verdict:!0,reason:"The angles add to 180°, shared equally: 60° each.",said:"The angles of a triangle add up to one hundred and eighty degrees, shared equally between three: sixty degrees each.",labelSaid:"All three of its angles are sixty degrees."},{label:"It has one right angle.",verdict:!1,reason:"Its angles are all 60°, so none is 90°.",said:"Its angles are all sixty degrees, so none of them is a right angle."},{label:"Two of its sides are different.",verdict:!1,reason:"All three sides of an equilateral triangle are equal.",said:"All three sides of an equilateral triangle are the same length."}]}],norm$1=n=>n.trim().replace(/\s+/g," ");function specFor(n){const t=n.questionId.trim().replace(/^cb-/,"");return SPECS.find(e=>e.questionId===t)??null}function build$1(n,t){const e=specFor(t);if(!e||e.microSkillId!==t.microSkillId||norm$1(t.stem)!==norm$1(e.stem))return null;const s=(t.options??[]).map(norm$1);if(s.length!==e.statements.length)return null;const o=s.map(g=>e.statements.find(b=>norm$1(b.label)===g)??null);if(o.some(g=>g===null)||new Set(s).size!==s.length)return null;const r=o,a=r.map(g=>g.label),i=r.map((g,b)=>(e.ask==="true"?g.verdict:!g.verdict)?b:-1).filter(g=>g>=0);if(i.length!==1)return null;const l=i[0];if(norm$1(t.correctAnswer)!==norm$1(a[l]))return null;const u=r.map((g,b)=>b).filter(g=>g!==l),d=g=>r[g].verdict?"true":"false",m=(g,b={})=>({type:"statement_check",statements:a,verdicts:a.map((x,y)=>g.includes(y)?d(y):null),reasons:a.map((x,y)=>g.includes(y)?r[y].reason:null),...b}),h=e.ask==="not-true",f=g=>{const b=r[g];return`${(b.labelSaid??b.label).replace(/[.]$/,"")}? ${b.verdict?"Yes":"No"}. ${b.said}`};return buildExample({generatorId:n,question:t,title:h?"Which statement is NOT true?":"Which statement is true?",computed:{statements:a.length,answer:l+1},steps:[{visual_data:m([]),animation:"show_statements",caption:h?"Check each statement. Find the one that is NOT true.":"Check each statement. Find the true one.",spoken:h?"Let's check each statement in turn and give a reason for each. We are looking for the one that is not true.":"Let's check each statement in turn and give a reason for each. We are looking for the one that is true."},{visual_data:m(u,{focus:u}),animation:"check_others",caption:h?"These are true, so they are not the answer.":"These are not true.",spoken:`${u.map(f).join(" ")} ${h?"So those statements are true.":"So none of those is true."}`},{visual_data:m([...u,l],{focus:[l],answer:l}),animation:"check_answer",caption:h?"This one is NOT true: that is the answer.":"This one is true: that is the answer.",spoken:`${f(l)} So this is the statement that is ${h?"not true":"true"}, and that is the answer.`}]})}const make=(n,t)=>({id:`${n}.statement-check.v1`,microSkillId:n,forms:t,generate(e){return build$1(this.id,e)}}),STATEMENT_CHECK_GENERATORS=[make("FDP.P4.M03",["comparison_statement"]),make("GEO.P6.M01",["comparison_statement"]),make("GEO.P6.M02",["misconception_contrast_probe"])];SPECS.map(n=>n.questionId);const M02="MD.P1.M02",M03="MD.P1.M03";function skipCountWords$1(n){return n.map(t=>numberToWords(t)).join(", ")}function readSequence(n){let t=null;const e=normaliseStem(n.stem);if(/^[\d,\s]*__[\d,\s]*$/.test(e)){const a=e.split(",").map(i=>i.trim());if(a.length>=4&&a.filter(i=>
71i==="__").length===1){const i=a.map(l=>l==="__"?null:Number(l));i.every(l=>l===null||Number.isInteger(l))&&(t=i)}}if(!t){const a=n.visualData;if(a&&a.type==="sequence"){const i=a;Array.isArray(i.values)&&i.values.filter(l=>l===null).length===1&&(t=i.values)}}if(!t)return null;const s=t.indexOf(null);if(s===-1||t.filter(a=>a!==null).length<3)return null;let r=null;for(let a=1;a<t.length;a++){const i=t[a-1],l=t[a];if(i===null||l===null)continue;const u=l-i;if(u===0)return null;if(r===null)r=u;else if(r!==u)return null}return r===null||![2,5,10].includes(Math.abs(r))?null:{values:t,step:r,gapIndex:s}}function fillGap(n,t,e){if(e>0&&n[e-1]!==null)return n[e-1]+t;if(e<n.length-1&&n[e+1]!==null)return n[e+1]-t;const s=n.findIndex(o=>o!==null);return n[s]+t*(e-s)}function buildSkipCountNumberLine(n){const{generatorId:t,q:e,values:s,step:o,gapIndex:r,missing:a,toWords:i}=n,l=Math.abs(o),u=o>0,d=s.map((q,V)=>V===r?a:q);let m=Math.min(...d),h=Math.max(...d);a===m?m-=l:a===h&&(h+=l);const f=s.filter(q=>q!==null),g=`${u?"+":"-"}${l}`,b=[];for(let q=1;q<s.length;q++){const V=s[q-1],O=s[q];V!==null&&O!==null&&b.push({from:V,to:O,label:g})}const x=r>0?s[r-1]:null,y=r<s.length-1?s[r+1]:null,E=[];let k=null,T=u;x!==null?(k=x,T=u,E.push({from:x,to:a,label:g}),y!==null&&E.push({from:a,to:y,label:g})):y!==null&&(k=y,T=!u,E.push({from:y,to:a,label:`${u?"-":"+"}${l}`}));const A=(q,V)=>({type:"number_line",min:m,max:h,step:l,hops:q,marked:f,unknown:V}),N={step:l,missing:a,answer:a};k!==null&&(N.anchor=k),d.forEach((q,V)=>N[`v${V}`]=q);const M=k!==null?`${k} ${T?"+":"-"} ${l} = ${a}.`:`${a}.`,L=`${d.join(", ")}.`,P=L.length<=72?L:M,F=i(l),D=i(a),U=k!==null?i(k):"";return buildExample({generatorId:t,question:e,title:`Counting in ${l}s`,computed:N,steps:[{visual_data:A(b,null),animation:"highlight_jumps",caption:l===a?"Find the equal jumps.":`Each jump ${u?"adds":"takes away"} ${l}.`,spoken:`Look at the number line. Each jump ${u?"adds":"takes away"} ${F}.`},{visual_data:A([...b,...E.slice(0,1)],null),animation:"hop_to_gap",caption:M,spoken:k!==null?`Take one more jump. ${capitalised(U)} ${T?"plus":"minus"} ${F} is ${D}.`:`Take one more jump to fill the gap. The missing number is ${D}.`},{visual_data:A([...b,...E.slice(1),...E.slice(0,1)],a),animation:"reveal_missing",caption:P,spoken:`The missing number is ${D}.`}]})}function buildSequenceDemo(n,t,e,s,o,r){return buildSkipCountNumberLine({generatorId:t,q:n,values:e,step:s,gapIndex:o,missing:r,toWords:numberToWords})}const mdP1M02SequenceContinueGenerator={id:"MD.P1.M02.sequence-continue.v1",microSkillId:M02,forms:["sequence_continue"],generate(n){const t=answerInt(n);if(t===null)return null;const e=readSequence(n);if(!e||e.gapIndex!==e.values.length-1)return null;const s=fillGap(e.values,e.step,e.gapIndex);return s!==t||s<0||s>999?null:buildSequenceDemo(n,mdP1M02SequenceContinueGenerator.id,e.values,e.step,e.gapIndex,s)}},mdP1M02SequenceFillGapGenerator={id:"MD.P1.M02.sequence-fill-gap.v1",microSkillId:M02,forms:["sequence_fill_gap"],generate(n){const t=answerInt(n);if(t===null)return null;const e=readSequence(n);if(!e)return null;const s=fillGap(e.values,e.step,e.gapIndex);return s!==t||s<0||s>999?null:buildSequenceDemo(n,mdP1M02SequenceFillGapGenerator.id,e.values,e.step,e.gapIndex,s)}},REVERSED_GROUPS_STEM=/^(\d+)\s+\S+\s+come in (pairs|fives|tens)\.\s*how many (?:pairs|fives|tens|groups)\??$/i,GROUP_WORD_SIZE={pairs:2,fives:5,tens:10};function buildQuotitiveGroupsDemo(n,t,e,s,o,r,a){const i={total:s,groupSize:o,groups:r,answer:r},l={type:"objects_grid",emoji:e,count:s,group_size:s,hide_subtotals:!0},u={type:"objects_grid",emoji:e,count:Math.max(o,(r-1)*o),group_size:o,hide_subtotals:!0},d={type:"objects_grid",emoji:e,count:s,group_size:o,hide_subtotals:!0},m=numberToWords(s),h=numberToWords(o),f=numberToWords(r);return buildExample({generatorId:t,question:n,title:`How many groups of ${o}?`,computed:i,steps:[{visual_data:l,animation:"show_pile",caption:"Split them into equal groups.",spoken:`We have ${m}. Let's split them into equal groups of ${h}.`},{visual_data:u,animation:"build_groups",caption:`Make groups of ${o}.`,spoken:`Count out groups of ${h}, one group at a time, until none are left.`},{visual_data:d,animation:"count_groups",caption:`${r} ${a}, ${o} each = ${s}.`,spoken:`${capitalised(m)} makes ${f} groups of ${h}.`}]})}const mdP1M02CountEqualGroupsGenerator={id:"MD.P1.M02.count-equal-groups.v1",microSkillId:M02,forms:["count_equal_groups"],generate(n){const t=answerInt(n);if(t===null)return null;const e=n.visualData;if(!e||e.type!=="objects_grid")return null;const{emoji:s,count:o,group_size:r}=e;if(!Number.isInteger(o)||!Number.isInteger(r)||r<1)return null;if(o%r===0&&o/r>=2&&o<=999&&r<=999&&o===t){const d=o/r,m=Array.from({length:d},(x,y)=>(y+1)*r),h={groups:d,groupSize:r,total:o,answer:o};m.forEach((x,y)=>{h[`partial${y+1}`]=x});const f={type:"objects_grid",emoji:s,count:r,group_size:r},g={type:"objects_grid",emoji:s,count:Math.min(2,d)*r,group_size:r},b={type:"objects_grid",emoji:s,count:o,group_size:r};return buildExample({generatorId:mdP1M02CountEqualGroupsGenerator.id,question:n,title:`How many in ${d} groups of ${r}?`,computed:h,steps:[{visual_data:f,animation:"show_first_group",caption:`Equal groups of ${r}. Count the first: ${r}.`,spoken:`We count equal groups of ${numberToWords(r)}. The first group is ${numberToWords(r)}.`},{visual_data:g,animation:"add_second_group",caption:`Count on in ${r}s: ${m.slice(0,Math.max(1,Math.min(2,d-1))).join(", ")}.`,spoken:`Count on in ${numberToWords(r)}s for each group: ${skipCountWords$1(m.slice(0,Math.max(1,Math.min(2,d-1))))}.`},{visual_data:b,animation:"add_third_group",caption:`${d} groups of ${r} = ${o}.`,spoken:`Now count every group, one jump of ${numberToWords(r)} each: ${skipCountWords$1(m)}. ${capitalised(numberToWords(d))} groups of ${numberToWords(r)} makes ${numberToWords(o)} in all.`}]})}const a=normaliseStem(n.stem).match(REVERSED_GROUPS_STEM);if(!a)return null;const i=Number(a[1]);if(i!==o)return null;const l=GROUP_WORD_SIZE[a[2].toLowerCase()];if(!l||i%l!==0)return null;const u=i/l;return u<2||u!==t||i>999||l>999?null:buildQuotitiveGroupsDemo(n,mdP1M02CountEqualGroupsGenerator.id,s,i,l,u,a[2].toLowerCase())}};function verdictAnswer(n){const t=n.correctAnswer.trim().toLowerCase();return t==="true"?"True":t==="false"?"False":null}const mdP1M02TrueFalseGenerator={id:"MD.P1.M02.true-false-check.v1",microSkillId:M02,forms:["true_false_check"],generate(n){const t=verdictAnswer(n);if(!t)return null;const e=n.visualData;if(!e||e.type!=="equation_display")return null;const{terms:s}=e;if(!Array.isArray(s))return null;const o=s.indexOf("=");if(o<1||o!==s.length-2)return null;const r=s.slice(0,o),a=s[s.length-1];if(typeof a!="number"||!Number.isInteger(a))return null;const i=[];for(let k=0;k<r.length;k++){const T=r[k];if(k%2===0){if(typeof T!="number"||!Number.isInteger(T))return null;i.push(T)}else if(T!=="+")return null}if(i.length<2)return null;const l=i.reduce((k,T)=>k+T,0),u=l===a?"True":"False";if(u!==t||l>999||a>999)return null;const d=[];let m=0;for(const k of i)m+=k,d.push(m);const h=i[0],f=i.every(k=>k===h),g={lhs:l,rhs:a,groups:i.length,answer:t==="True"?1:0};
71f&&(g.step=h),i.forEach((k,T)=>g[`addend${T}`]=k),d.forEach((k,T)=>g[`running${T}`]=k);const b=s.map(k=>k==="+"?"plus":k==="="?"equals":numberToWords(k)).join(" "),x=i.map((k,T)=>T*2),y=s.slice();y[y.length-1]=l;const E=u==="True"?[l,"=",a]:[l,"≠",a];return buildExample({generatorId:mdP1M02TrueFalseGenerator.id,question:n,title:"Is the equation true?",computed:g,steps:[{visual_data:{type:"equation_display",terms:s,highlight:x},animation:"highlight_addends",caption:"Add these numbers up.",spoken:f?`This adds ${numberToWords(h)}, ${numberToWords(i.length)} times. Let's count on and see where we land.`:"Let's add these numbers up and see where we land."},{visual_data:{type:"equation_display",terms:y,highlight:[y.length-1]},animation:"count_on",caption:`Count on: ${d.join(", ")}.`,spoken:`Count on: ${skipCountWords$1(d)}. The addition really makes ${numberToWords(l)}.`},{visual_data:{type:"equation_display",terms:E,highlight:[0,2]},animation:"reveal_verdict",caption:u==="True"?`${l} = ${a}, so it is True.`:`${l} ≠ ${a}, so it is False.`,spoken:u==="True"?`${capitalised(numberToWords(l))} equals ${numberToWords(a)}, so ${b} is true.`:`${capitalised(numberToWords(l))} is not ${numberToWords(a)}, so the statement ${b} is false.`}]})}},mdP1M02NumberLineGenerator={id:"MD.P1.M02.number-line-identify.v1",microSkillId:M02,forms:["number_line_identify"],generate(n){const t=answerInt(n);if(t===null)return null;const e=n.visualData;if(!e||e.type!=="number_line")return null;const s=e,{min:o,max:r}=s,a=Array.isArray(s.marked)?s.marked:[];if(!Number.isInteger(o)||!Number.isInteger(r)||a.length<3||!a.every(b=>Number.isInteger(b)))return null;let i=null;for(let b=1;b<a.length;b++){const x=a[b]-a[b-1];if(x<=0)return null;if(i===null)i=x;else if(i!==x)return null}if(i===null)return null;const l=a[a.length-1],u=l+i;if(u!==t||u>r||u<o||u>999)return null;const d=[];for(let b=1;b<a.length;b++)d.push({from:a[b-1],to:a[b],label:`+${i}`});const m={from:l,to:u,label:`+${i}`},h=i,f={step:h,last:l,answer:u,unknown:u};a.forEach((b,x)=>f[`mark${x}`]=b);const g=(b,x)=>({type:"number_line",min:o,max:r,hops:b,marked:a,unknown:x});return buildExample({generatorId:mdP1M02NumberLineGenerator.id,question:n,title:`Counting on in ${h}s`,computed:f,steps:[{visual_data:g(d,null),animation:"highlight_jumps",caption:`Each jump adds ${h}.`,spoken:`Look at the number line. Each jump adds ${numberToWords(h)}.`},{visual_data:g([...d,m],null),animation:"hop_to_unknown",caption:`Add ${h} to ${l}.`,spoken:`Take one more jump of ${numberToWords(h)} from ${numberToWords(l)}.`},{visual_data:g([...d,m],u),animation:"reveal_unknown",caption:`${l} + ${h} = ${u}.`,spoken:`That jump lands on ${numberToWords(u)}. ${capitalised(numberToWords(l))} plus ${numberToWords(h)} is ${numberToWords(u)}.`}]})}},HALF_EQ=/^half of (\d+) = __$/i,HALF_TF$1=/^half of (\d+) is (\d+)\. true or false\??$/i,mdP1M03HalvingGenerator={id:"MD.P1.M03.halving.v1",microSkillId:M03,forms:["halving"],generate(n){const t=normaliseStem(n.stem);let e=null,s=null,o=null,r=null;const a=matchInts(t,HALF_EQ),i=t.match(HALF_TF$1);if(a){if(e=a[0],o=answerInt(n),o===null)return null}else if(i){if(e=Number(i[1]),r=Number(i[2]),r>999||(s=verdictAnswer(n),s===null)||e%2!==0||(e/2===r?"True":"False")!==s)return null}else return null;if(e===null||!Number.isInteger(e)||e<2||e%2!==0)return null;const l=e/2;if(o!==null&&o!==l||e>20)return null;const u={whole:e,half:l,groups:2,answer:l};s!==null&&(u.verdict=s==="True"?1:0),r!==null&&(u.claimed=r);const d="🔵",m={type:"objects_grid",emoji:d,count:e,group_size:e,hide_subtotals:!0},h={type:"objects_grid",emoji:d,count:e,group_size:l,hide_subtotals:!0},f=s!==null?s==="True"?`Half of ${e} is ${l}. True.`:`Half of ${e} is ${l}, not ${r}. False.`:`Half of ${e} is ${l}.`,g=s==="False"?`Each group has ${numberToWords(l)}, so half of ${numberToWords(e)} is ${numberToWords(l)}, not ${numberToWords(r)}. The statement is false.`:s==="True"?`Each group has ${numberToWords(l)}, so half of ${numberToWords(e)} really is ${numberToWords(l)}. The statement is true.`:`Each group has ${numberToWords(l)}. Half of ${numberToWords(e)} is ${numberToWords(l)}.`;return buildExample({generatorId:mdP1M03HalvingGenerator.id,question:n,title:`Half of ${e}`,computed:u,steps:[{visual_data:m,animation:"show_whole",caption:"Half means sharing into two equal groups.",spoken:"Half of a number means we share it into two equal groups."},{visual_data:h,animation:"share_equally",caption:`Share ${e} into 2 equal groups.`,spoken:`Share ${numberToWords(e)} equally into two groups. Each group gets the same.`},{visual_data:h,animation:"reveal_result",caption:f,spoken:g}]})}},SHARE_STEM$1=/^share (\d+) [a-z]+ between (\d+)\. how many each\??$/i,SHARED_BETWEEN_STEM=/^(\d+) [a-z]+ shared between (\d+)\. how many each\??$/i,QUOTITIVE_STEM=/^(\d+) [a-z]+,? (\d+) each\. how many ([a-z]+)\??$/i;function emojiFromVisual$1(n){const t=n.visualData;return t&&t.type==="objects_grid"&&typeof t.emoji=="string"?t.emoji:"🔵"}function buildPartitiveShareDemo(n,t,e,s,o,r){const a={total:s,groups:o,each:r,answer:r},i={type:"objects_grid",emoji:e,count:s,group_size:s,hide_subtotals:!0},l={type:"objects_grid",emoji:e,count:o,group_size:1,mode:"share"},u={type:"objects_grid",emoji:e,count:s,group_size:r,mode:"share"},d=`${Array(o).fill(r).join(" + ")} = ${s}.`,m=d.length<=72?d:`${s} shared into ${o} = ${r} each.`;return buildExample({generatorId:t,question:n,title:`Share ${s} into ${o} groups`,computed:a,steps:[{visual_data:i,animation:"show_whole",caption:"Share them into equal groups.",spoken:`We have ${numberToWords(s)} to share equally between ${numberToWords(o)} groups.`},{visual_data:l,animation:"deal_one_each",caption:"Give one to each group.",spoken:"Deal them out one at a time — one for each group, and keep going."},{visual_data:u,animation:"share_result",caption:m,spoken:`Every group ends up with ${numberToWords(r)}. ${capitalised(numberToWords(s))} shared into ${numberToWords(o)} groups is ${numberToWords(r)} each.`}]})}const mdP1M03ContextCalcGenerator={id:"MD.P1.M03.context-calculation.v1",microSkillId:M03,forms:["context_calculation"],generate(n){const t=answerInt(n);if(t===null)return null;const e=normaliseStem(n.stem),s=emojiFromVisual$1(n),o=e.match(SHARE_STEM$1)??e.match(SHARED_BETWEEN_STEM);if(o){const a=Number(o[1]),i=Number(o[2]);if(!Number.isInteger(a)||!Number.isInteger(i)||i<2||a%i!==0)return null;const l=a/i;return l<1||a>999||l!==t?null:buildPartitiveShareDemo(n,mdP1M03ContextCalcGenerator.id,s,a,i,l)}const r=e.match(QUOTITIVE_STEM);if(r){const a=Number(r[1]),i=Number(r[2]);if(!Number.isInteger(a)||!Number.isInteger(i)||i<1||a%i!==0)return null;const l=a/i;return l<2||a>999||l!==t?null:buildQuotitiveGroupsDemo(n,mdP1M03ContextCalcGenerator.id,s,a,i,l,r[3].toLowerCase())}return null}},mdY1Generators=[mdP1M02SequenceContinueGenerator,mdP1M02SequenceFillGapGenerator,mdP1M02CountEqualGroupsGenerator,mdP1M02TrueFalseGenerator,mdP1M02NumberLineGenerator,mdP1M03HalvingGenerator,mdP1M03ContextCalcGenerator];function parseSequence(n,t){const e=normaliseStem(n).replace(/^Count in \d+s: /,"");if(!/^[\d, _]+$/.test(e))return null;const s=e.split(",").map(m=>m.trim());if(s.length<3||s.length>5)return null;const o=[];let r=-1;for(let m=0;m<s.length;m++)if(/^_+$/.test(s[m])){if(r!==-1)return null;r=m,o.push(null)}else if(/^\d+$/.test(s[m]))o.push(Number(s[m]));else return null;if(r<2||o.filter(m=>m!==null).some(m=>m<0||m>30))return null;const i=o[1]-o[0];if(!t.includes(i))return null;for(let m=1;m<o.length;m++){const h=o[m-1],f=o[m];if(h!==null&&f!==null&&f-h!==i)return null}const l=o[r-1],u=l+i;if(u<0||u>30)return null;const d=o[r+1];return d!=null&&d-u!==i?null:{values:o,blankIndex:r,step:i,missing:u,prev:l}}
71function sequenceExample(n){const{parsed:t}=n,e=t.values.map((o,r)=>r===t.blankIndex?t.missing:o),s=`${e.join(", ")}.`;return buildExample({generatorId:n.generatorId,question:n.q,title:n.title,computed:{step:t.step,prev:t.prev,missing:t.missing,answer:t.missing,...Object.fromEntries(e.map((o,r)=>[`v${r}`,o]))},steps:[{visual_data:{type:"sequence",values:t.values,step:t.step},animation:"highlight_pattern",caption:n.caption1,spoken:n.spoken1},{visual_data:{type:"sequence",values:t.values,step:t.step},animation:t.step>0?"arc_forward":"arc_back",caption:n.caption2,spoken:n.spoken2},{visual_data:{type:"sequence",values:e,step:t.step},animation:"fill_blank",caption:s,spoken:n.spoken3}]})}function leadWords(n){return n.values.slice(0,n.blankIndex).map(numberWord).join(", ")}function sequenceFromVisual(n,t){if(!n||n.type!=="sequence"||!Array.isArray(n.values))return null;const e=n.values;if(e.length<3||e.length>5)return null;let s=-1;for(let u=0;u<e.length;u++){const d=e[u];if(d===null){if(s!==-1)return null;s=u}else if(!Number.isInteger(d)||d<0||d>30)return null}if(s===-1||!Number.isInteger(n.step)||!t.includes(n.step))return null;const o=n.step;for(let u=1;u<e.length;u++){const d=e[u-1],m=e[u];if(d!==null&&m!==null&&m-d!==o)return null}const r=s>0?e[s-1]:null,a=s<e.length-1?e[s+1]:null;let i;if(r!==null)i=r+o;else if(a!==null)i=a-o;else return null;return i<0||i>30||r!==null&&a!==null&&a-i!==o?null:{values:e,blankIndex:s,step:o,missing:i,prev:r!==null?r:a}}function countOnExample(n,t,e){return sequenceExample({generatorId:n,q:t,parsed:e,title:"How do we work out what comes next?",caption1:"These numbers go up by 1.",spoken1:`To find the missing number, first we spot the pattern. Let's count: ${leadWords(e)}. Each number is one more than the one before.`,caption2:`Add 1 to ${e.prev}.`,spoken2:`The numbers go up by one, so the next number is one more than ${numberWord(e.prev)}. We add one to ${numberWord(e.prev)}.`,spoken3:`One more than ${numberWord(e.prev)} is ${numberWord(e.missing)}. So ${numberWord(e.missing)} goes in the empty box.`})}function numberLineOneMoreDemo(n,t,e,s,o,r){return buildExample({generatorId:n,question:t,title:`What is one more than ${o}?`,computed:{start:o,missing:r,answer:r,one:1},steps:[{visual_data:{type:"number_line",min:e,max:s,marked:[o]},animation:"highlight_pattern",caption:`Find ${o} on the line.`,spoken:`We want one more than ${numberWord(o)}, so first we find ${numberWord(o)} on the number line. That is where we start.`},{visual_data:{type:"number_line",min:e,max:s,marked:[o]},animation:"arc_forward",caption:"One more means hop on 1.",spoken:"Numbers get bigger as we go right along the line. So one more means one hop to the right."},{visual_data:{type:"number_line",min:e,max:s,marked:[o],hops:[{from:o,to:r,label:"+1"}],unknown:r},animation:"write_answer",caption:`${o} + 1 = ${r}.`,spoken:`One hop from ${numberWord(o)} lands on ${numberWord(r)}. So one more than ${numberWord(o)} is ${numberWord(r)}.`}]})}const ONE_MORE_THAN=/^(?:What is one|1) more than (\d+)[.?]?(?: __)?$/,countOnByOne={id:"NPV.P2.M02.count-on.v1",microSkillId:"NPV.P2.M02",forms:["count_sequence","equation_completion","sequence_fill_gap","sequence_continue","number_line_identify","true_false_check"],generate(n){let t=parseSequence(n.stem,[1]);if(t||(t=sequenceFromVisual(n.visualData,[1])),t)return t.missing!==answerInt(n)?null:countOnExample(this.id,n,t);const e=matchInts(n.stem,ONE_MORE_THAN);if(e){const[o]=e,r=o+1;if(r!==answerInt(n))return null;if(n.mathematicalForm==="number_line_identify"){const l=n.visualData;return l&&l.type==="number_line"&&Number.isInteger(l.min)&&Number.isInteger(l.max)&&(l.marked??[]).includes(o)&&o>=l.min&&r<=l.max?numberLineOneMoreDemo(this.id,n,l.min,l.max,o,r):null}const a=[o-2,o-1,o];if([...a,r].some(l=>l<0||l>30))return null;const i={values:[...a,null],blankIndex:3,step:1,missing:r,prev:o};return countOnExample(this.id,n,i)}const s=[...n.stem.matchAll(/\d+/g)].map(o=>Number(o[0]));if(s.length===1&&/\bnext\b/i.test(n.stem)){const[o]=s,r=[o-2,o-1,o],a=o+1;if(![...r,a].some(i=>i<0||i>30)&&a===answerInt(n)){const i={values:[...r,null],blankIndex:3,step:1,missing:a,prev:o};return countOnExample(this.id,n,i)}}return countOnTrueFalse(this.id,n)}},CLAIM_AFTER_COMES=/^\w+ says after (\d+) comes (\d+)\. True or false\?$/;function countOnTrueFalse(n,t){const e=t.correctAnswer.trim();if(e!=="True"&&e!=="False")return null;let s,o;const r=t.visualData;if(r&&r.type==="equation_display"&&Array.isArray(r.terms)&&r.terms.length===5){const[l,u,d,m,h]=r.terms;if(u!=="+"||d!==1||m!=="="||!Number.isInteger(l)||!Number.isInteger(h)
71)return null;s=l,o=h}else{const l=matchInts(t.stem,CLAIM_AFTER_COMES);if(!l)return null;[s,o]=l}if(s<9||s>19||o<0||o>20)return null;const a=s+1,i=o===a?"True":"False";return i!==e?null:buildExample({generatorId:n,question:t,title:`Is ${s} + 1 = ${o}?`,computed:{start:s,claimed:o,real:a,one:1},steps:[{visual_data:{type:"equation_display",terms:[s,"+",1,"=",o]},animation:"pulse_unknown",caption:`Is ${s} + 1 = ${o}?`,spoken:`Is ${numberWord(s)} plus one equal to ${numberWord(o)}? Let's count one on and check.`},{visual_data:{type:"sequence",values:[s,null],step:1},animation:"arc_forward",caption:`Count on 1 from ${s}.`,spoken:`${capitalised(numberWord(s))}, then one more is ${numberWord(a)}.`},{visual_data:{type:"equation_display",terms:[s,"+",1,"=",a]},animation:"write_answer",caption:`${s} + 1 = ${a} — so it's ${i}.`,spoken:a===o?`${capitalised(numberWord(s))} plus one is ${numberWord(a)}. That matches, so it is true.`:`${capitalised(numberWord(s))} plus one is ${numberWord(a)}, not ${numberWord(o)}. So it is false.`}]})}const countBackByOne={id:"NPV.P2.M03.count-back.v1",microSkillId:"NPV.P2.M03",forms:["count_sequence","equation_completion","sequence_fill_gap","true_false_check"],generate(n){let t=parseSequence(n.stem,[-1]);if(!t){const e=trailingSequence(n.stem);e&&(t=parseSequence(e,[-1]))}return t?t.missing!==answerInt(n)?null:sequenceExample({generatorId:this.id,q:n,parsed:t,title:"How do we count back in ones?",caption1:"Counting back in ones.",spoken1:`Let's read the numbers: ${leadWords(t)}. Each number is one less than the one before, so we are counting back in ones.`,caption2:"Each step goes back one.",spoken2:`To find the missing number, we go back one from ${numberWord(t.prev)}. One less than ${numberWord(t.prev)} is ${numberWord(t.missing)}.`,spoken3:`So ${numberWord(t.missing)} goes in the empty box. Now the whole count reads: ${t.values.map((e,s)=>s===t.blankIndex?t.missing:e).map(numberWord).join(", ")}.`}):countBackTrueFalse(this.id,n)}};function trailingSequence(n){const e=normaliseStem(n).match(/(\d+(?:,\s*\d+)*,\s*_+)\s*$/);return e?e[1]:null}const COUNTBACK_CLAIM=/^\w+ counts back: ([\d, ]+)\. True or false\?$/;function countBackTrueFalse(n,t){const e=t.correctAnswer.trim();if(e!=="True"&&e!=="False")return null;const s=normaliseStem(t.stem).match(COUNTBACK_CLAIM);if(!s)return null;const o=s[1].split(",").map(x=>Number(x.trim()));if(o.length<3||o.length>5||o.some(x=>!Number.isInteger(x)||x<0||x>30))return null;const r=o[0],a=o.map((x,y)=>r-y);if(a.some(x=>x<0||x>30))return null;const i=o.every((x,y)=>x===a[y])?"True":"False";if(i!==e)return null;const l={one:1};o.forEach((x,y)=>{l[`claim${y}`]=x}),a.forEach((x,y)=>{l[`real${y}`]=x});const u=i==="True",d=u?`${a.join(", ")} — so it's True.`:`The real count is ${a.join(", ")} — so it's False.`,m=u?`${a.map(numberWord).join(", ")}. That matches, so it is true.`:`The real count is ${a.map(numberWord).join(", ")}. That's not what was said, so it is false.`;let h=-1;for(let x=1;x<o.length;x++)if(o[x]!==o[x-1]-1){h=x;break}const f=o.map((x,y)=>{if(y===0)return null;const E=o[y]===o[y-1]-1;return`${x} ${E?"✓":"✗"}`}),g=u?"Check each gap: back 1 every time.":`${o[h-1]} to ${o[h]} skips ${o[h-1]-1}.`,b=u?"Each number counts back one from the last. Every gap checks out.":`From ${numberWord(o[h-1])}, the next number back should be ${numberWord(o[h-1]-1)}, but it jumps to ${numberWord(o[h])}.`;return buildExample({generatorId:n,question:t,title:`Is ${o.join(", ")} counting back correctly?`,computed:l,steps:[{visual_data:{type:"sequence",values:[...o],step:-1,hide_hop:!0},animation:"highlight_pattern",caption:"Check this countdown.",spoken:"Let's check every step counts back by one."},{visual_data:{type:"sequence",values:[...o],step:-1,hide_hop:!0,labels:f,emphasis:u?void 0:h},animation:"highlight_gaps",caption:g,spoken:b},{visual_data:{type:"sequence",values:[...a],step:-1},animation:"write_answer",caption:d,spoken:m}]})}const ONE_MORE=/^(?:What is one|1) more than (\d+)[.?]?(?: __)?$/,ONE_LESS=/^(?:What is one|1) less than (\d+)[.?]?(?: __)?$/,INVERSE_ONE_MORE=/^One more than a 
71number is (\d+)\. Find it$/,INVERSE_ONE_LESS=/^One less than a number is (\d+)\. Find it$/,oneMoreOneLess={id:"NPV.P2.M04.one-more-less.v1",microSkillId:"NPV.P2.M04",forms:["equation_completion"],generate(n){const t=matchInts(n.stem,ONE_MORE),e=t?null:matchInts(n.stem,ONE_LESS),s=t??e;if(!s){const d=matchInts(n.stem,INVERSE_ONE_MORE),m=d?null:matchInts(n.stem,INVERSE_ONE_LESS),h=d??m;if(!h)return null;const[f]=h,g=d?-1:1,b=f+g,x=[f-2*g,f-g,f];if([...x,b].some(T=>T<0||T>30)||b!==answerInt(n))return null;const y={values:[...x,null],blankIndex:3,step:g,missing:b,prev:f},E=d?"one more than":"one less than",k=g>0?"on":"back";return sequenceExample({generatorId:this.id,q:n,parsed:y,title:`${capitalised(E)} a number is ${f}. Find it.`,caption1:`Count ${k} from ${f} to find the number.`,spoken1:`${capitalised(E)} a mystery number is ${numberWord(f)}. If we count ${k} one step from ${numberWord(f)}, we land on the number.`,caption2:`One step ${g>0?"forward":"back"} from ${f}.`,spoken2:`Stepping ${g>0?"forward":"back"} one undoes the ${d?"one more":"one less"}. From ${numberWord(f)}, one step ${g>0?"forward":"back"} lands on ${numberWord(b)}.`,spoken3:`So the mystery number is ${numberWord(b)}. Check: ${E} ${numberWord(b)} is ${numberWord(f)}.`})}if(e||n.visualData?.type==="number_line")return null;const[o]=s,r=t?1:-1,a=o+r,i=[o-2*r,o-r,o];if([...i,a].some(d=>d<0||d>30)||a!==answerInt(n))return null;const l={values:[...i,null],blankIndex:3,step:r,missing:a,prev:o},u=t?"more":"less";return sequenceExample({generatorId:this.id,q:n,parsed:l,title:`What is one ${u} than ${o}?`,caption1:`Counting ${t?"on":"back"} from ${o}.`,spoken1:`One ${u} means one step ${t?"on":"back"} when we count. Let's count to ${numberWord(o)}: ${i.map(numberWord).join(", ")}.`,caption2:`One step ${t?"forward":"back"} from ${o}.`,spoken2:`Counting ${t?"on":"back"} one step always gives one ${u}. From ${numberWord(o)}, one step ${t?"forward":"back"} lands on ${numberWord(a)}.`,spoken3:`So one ${u} than ${numberWord(o)} is ${numberWord(a)}. That is the number that goes in the empty box.`})}};function teenOnesFromEquationDisplay(n){if(!n||n.type!=="equation_display"||!Array.isArray(n.terms))return null;const t=n.terms;if(t.length!==7||t[0]!==1||t[1]!=="ten"||t[2]!=="+"||t[4]!=="ones"||t[5]!=="="||t[6]!=="?")return null;const e=t[3];return!Number.isInteger(e)||e<0||e>9?null:e}const TEEN_TF_CLAIM=/(?:shows|is) (\d+)\b/,TENS_WORDS={10:"ten",20:"twenty",30:"thirty",40:"forty",50:"fifty",60:"sixty",70:"seventy",80:"eighty",90:"ninety"};function tensWord(n){const t=TENS_WORDS[n];if(!t)throw new Error(`tensWord out of range: ${n}`);return t}const NOT_A_NOUN=new Set(["in","are","is","altogether","there","do","does","can","will","more","fewer","less"]);function teenObjectNoun(n){const t=n.match(/how many ([a-z]+)\b/i);if(!t)return null;const e=t[1].toLowerCase();return NOT_A_NOUN.has(e)?null:e}function teenStep1(n,t,e){const s=10+t;if(n==="word_to_numeral"||n==="word_to_typed_numeral")return{caption:`Show ${numberWord(s)} with counters.`,spoken:`${capitalised(numberWord(s))} is a teen number. Let's count out ${numberWord(s)} counters and make a ten.`};if(n==="language_to_teen_numeral"||n==="teen_equation_identification"){const r=t===1?"one":"ones";return{caption:`One ten and ${t} ${r} — let's build it.`,spoken:`One ten and ${numberWord(t)} ${r}. Let's build it with counters and make a ten.`}}const o=teenObjectNoun(e);return o?{caption:`Here are the ${o} — each counter stands for one. Let's count them.`,spoken:`Here are the ${o}. Each counter stands for one. Let's count them and make a ten.`}:{caption:"Some counters — let's count them.",spoken:"Here are some counters. Let's count them and make a ten."}}function teenStep3Trailer(n,t,e){if(n==="word_to_numeral"){const s=e*10;return{computed:{reversed:s},captionSuffix:` Not ${s}.`,spokenSuffix:` ${capitalised(tensWord(s))} would be ${numberWord(e)} tens, not one ten and ${numberWord(e)} ones.`}}if(n==="word_to_typed_numeral")return{computed:{one:1},captionSuffix:` Write 1 then ${e}.`,spokenSuffix:` One ten and ${numberWord(e)} ones — write one, then ${numberWord(e)}: ${numberWord(t)}.`};if(n==="teen_equation_identification"){const s=e*10+1;return s===t?{computed:{},captionSuffix:"",spokenSuffix:""}:{computed:{reversed:s},captionSuffix:` Not ${s}.`,spokenSuffix:" Careful not to flip the digits — that would be the wrong number."}}return{computed:{},captionSuffix:"",spokenSuffix:""}}function tenAndOnes(n,t,e,s){return{id:n,microSkillId:t,generate(o){const r=o.correctAnswer.trim();if(r==="True"||r==="False")return teenTrueFalse(n,o);const a=o.visualData;if(t==="NPV.P2.M01"&&o.mathematicalForm==="count_to_numeral"&&a&&(a.type==="tally"||a.type==="objects_grid"&&a.emoji))return null;const i=matchInts(o.stem,/^(\d+) is 1 ten and __ ones$/);if(i){const[x]=i;if(x<11||x>19)return null;const y=x-10;return y!==answerInt(o)?null:buildExample({generatorId:n,question:o,title:`${x} is 1 ten and how many ones?`,computed:{n:x,ones:y,answer:y},steps:[{visual_data:{type:"ten_frame_and_ones",tens:0,ones:x,loose:!0},animation:"show_objects",caption:`${x} counters — let's sort them.`,spoken:`${capitalised(numberWord(x))} counters. Let's fill a ten-frame and see what's left.`},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:y},animation:"highlight_ten",caption:"The ten-frame: that's 10.",spoken:`The full ten-frame is ten. Now count what's left over: ${countingRun(1,y)}.`},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:y},animation:"reveal_total",caption:`${x} is 10 + ${y}, so ${y} ones.`,spoken:`Ten and ${numberWord(y)} more make ${numberWord(x)}. So there are ${numberWord(y)} ones.`}]})}const l=matchInts(o.stem,/^1 ten and (\d+) ones? is __$/),u=teenOnesFromEquationDisplay(o.visualData),d=answerInt(o);if(d===null||d<11||d>19)return null;const m=d-10;if(l&&l[0]!==m||u!==null&&u!==m)return null;const h=o.visualData;if(h&&h.type==="ten_frame_and_ones"&&(h.tens!==1||h.ones!==m))return null;const f=teenStep1(o.mathematicalForm,m,o.stem),g=teenStep3Trailer(o.mathematicalForm,d,m),b=`10 + ${m} = ${d}.${g.captionSuffix}`;return buildExample({generatorId:n,question:o,title:s(d),computed:{n:d,ones:m,answer:d,...g.computed},steps:[{visual_data:{type:"ten_frame_and_ones",tens:0,ones:d,loose:!0},animation:"show_objects",caption:f.caption,spoken:f.spoken},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:m},animation:"highlight_ten",caption:"Ten, then count on the extras.",spoken:`The full ten-frame is ten. Now count on from ten: ${countingRun(11,d)}.`},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:m},animation:"reveal_total",caption:b,spoken:`${capitalised(numberWord(m))} more. Ten and ${numberWord(m)} is ${numberWord(d)}.${g.spokenSuffix}`}]})},forms:e}}function teenTrueFalse(n,t){const e=t.correctAnswer.trim();if(e!=="True"&&e!=="False")return null;const s=t.visualData;if(!s||s.type!=="ten_frame_and_ones"||s.tens!==1||!Number.isInteger(s.ones)||s.ones<0||s.ones>9)return null;const o=matchInts(t.stem,TEEN_TF_CLAIM);if(!o)return null;const r=o[0];if(r<10||r>20)return null;const a=10+s.ones;return(r===a?"True":"False")!==e?null:buildExample({generatorId:n,question:t,title:`Does this 
71show ${r}?`,computed:{ones:s.ones,real:a,claimed:r},steps:[{visual_data:{type:"ten_frame_and_ones",tens:1,ones:s.ones},animation:"show_objects",caption:`Does this show ${r}?`,spoken:`Does this show ${numberWord(r)}? Let's count the ten and the ones to check.`},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:s.ones},animation:"highlight_ten",caption:"The full frame: 10. Now count the extras.",spoken:s.ones>0?`The full ten-frame is ten. Now count on from ten: ${countingRun(11,a)}.`:"The full ten-frame is ten, and there are no extras."},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:s.ones},animation:"reveal_total",caption:a===r?`10 + ${s.ones} = ${a}. That matches ${r} — True.`:`10 + ${s.ones} = ${a}, not ${r} — False.`,spoken:a===r?`Ten and ${numberWord(s.ones)} more is ${numberWord(a)}. That matches ${numberWord(r)}, so it is true.`:`Ten and ${numberWord(s.ones)} more is ${numberWord(a)}, not ${numberWord(r)}. So it is false.`}]})}const countObjectsToTeen=tenAndOnes("NPV.P2.M01.ten-and-ones.v1","NPV.P2.M01",["count_to_numeral","word_to_numeral","word_to_typed_numeral"],()=>"How do we count objects to find how many?"),teenFromTenAndOnes=tenAndOnes("NPV.P3.M01.ten-and-ones.v1","NPV.P3.M01",["language_to_teen_numeral","ten_and_ones_to_numeral","teen_equation_identification","true_false_check","numeral_to_ten_and_ones"],n=>`What number is 1 ten and ${n-10} ones?`),TEEN_EQ_A=/^(\d+) = (\d+) \+ __$/,TEEN_EQ_B=/^(\d+) \+ __ = (\d+)$/,TEEN_EQ_C=/^__ \+ (\d+) = (\d+)$/,teenPartition={id:"NPV.P3.M02.teen-partition.v1",microSkillId:"NPV.P3.M02",forms:["numeral_to_ten_and_ones","teen_partition_equation","teen_equation_identification"],generate(n){const t=n.visualData;if(t&&t.type==="ten_frame_and_ones"&&t.tens===1&&Number.isInteger(t.ones)&&t.ones>=0&&t.ones<=9){const x=t.ones,y=10+x,E=`10 + ${x} = ${y}`;if(E===n.correctAnswer.trim())return buildExample({generatorId:this.id,question:n,title:"Which equation matches the picture?",computed:{n:y,ones:x,answer:y},steps:[{visual_data:{type:"ten_frame_and_ones",tens:1,ones:x},animation:"show_objects",caption:"One full ten and some loose ones.",spoken:"To match an equation, first we work out the number in the picture. It has one full ten, and some loose ones."},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:x},animation:"highlight_ten",caption:"The ten-frame: that's 10.",spoken:`A full ten-frame always holds ten, so we don't need to count it. Then there are ${numberWord(x)} loose ones.`},{visual_data:{type:"equation_display",terms:[10,"+",x,"=",y]},animation:"write_answer",caption:E,spoken:`Ten and ${numberWord(x)} ones make ${numberWord(y)}. So the matching equation is ten plus ${numberWord(x)} equals ${numberWord(y)}.`}]})}const e=teenOnesFromEquationDisplay(n.visualData);if(e!==null){const x=10+e;if(x<11||x>19||x!==answerInt(n))return null;const y=e===1?"one":"ones",E=e*10+1,k=E!==x&&(n.options??[]).some(T=>T.trim()===String(E));return buildExample({generatorId:this.id,question:n,title:`What is 1 ten and ${e} ${y}?`,computed:k?{n:x,ones:e,answer:x,flipped:E}:{n:x,ones:e,answer:x},steps:[{visual_data:{type:"equation_display",terms:[10,"+",e,"=","?"]},animation:"pulse_unknown",caption:`One ten and ${e} ${y}.`,spoken:`We need the number made from one ten and ${numberWord(e)} ${y}. The ten is worth ten, and the ${y} ${e===1?"is":"are"} ${numberWord(e)} more.`},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:e},animation:"highlight_ten",caption:`Ten, then ${e} more.`,spoken:`Teen numbers start with a ten, so we start at ten and count on ${numberWord(e)} more.`},{visual_data:{type:"equation_display",terms:[10,"+",e,"=",x]},animation:"write_answer",caption:`10 + ${e} = ${x}.${k?` Not ${E}.`:""}`,spoken:`Ten and ${numberWord(e)} more make ${numberWord(x)}. So one ten and ${numberWord(e)} ${y} is ${numberWord(x)}.`+(k?` The ten is written first, so it is not ${numberToWords(E)}.`:"")}]})}let s=null,o=!1,r=!1;const a=normaliseStem(n.stem).match(/^(\d+)p and __p make (\d+)p$/),i=a?`${a[1]} + __ = ${a[2]}`:n.stem,l=matchInts(i,TEEN_EQ_A),u=l?null:matchInts(i,TEEN_EQ_B),d=l??u?null:matchInts(i,TEEN_EQ_C);if(l&&l[1]===10)s=l[0];else if(u&&u[0]===10)s=u[1];else if(d)if(s=d[1],r=!0,d[0]===10)o=!1;else if(d[0]===s-10)o=!0;else return null;if(s===null||s<11||s>19)return null;const m=s-10,h=o?10:m;if(h!==answerInt(n))return null;const f=n.visualData;if(f&&f.type==="part_whole"){const x=f.parts.findIndex(k=>k===null),y=x===0?1:0;if(f.whole!==s||x===-1||f.parts[y]!==10||h!==m)return null;const E=f.parts.map((k,T)=>T===x?h:k);return buildExample({generatorId:this.id,question:n,title:`How do we write ${s} as 10 + ones?`,computed:{n:s,ones:m,missing:h,answer:h},steps:[{visual_data:{type:"part_whole",whole:s,parts:f.parts},animation:"pulse_unknown",caption:`${s}
71 = 10 + ?`,spoken:`Every teen number is a ten and some ones. So ${numberWord(s)} is ten plus some ones, and we need to find how many.`},{visual_data:{type:"part_whole",whole:s,parts:f.parts},animation:"count_up",caption:`Count on from 10 to ${s}.`,spoken:`To find the ones, we start at ten and count on until we reach ${numberWord(s)}. The number of counts is the missing part.`},{visual_data:{type:"part_whole",whole:s,parts:E},animation:"write_answer",caption:`10 + ${h} = ${s}.`,spoken:`We counted on ${numberWord(h)}, so the missing number is ${numberWord(h)}. Ten plus ${numberWord(h)} equals ${numberWord(s)}.`}]})}const g=o?["__","+",m,"=",s]:r?["__","+",10,"=",s]:[10,"+","__","=",s],b=o?[10,"+",m,"=",s]:r?[m,"+",10,"=",s]:[10,"+",m,"=",s];return buildExample({generatorId:this.id,question:n,title:`How do we write ${s} as 10 + ones?`,computed:{n:s,ones:m,missing:h,answer:h},steps:[{visual_data:{type:"equation_display",terms:g},animation:"pulse_unknown",caption:o?`${s} = ? + ${m}`:r?`${s} = ? + 10`:`${s} = 10 + ?`,spoken:o?`Every teen number is made of a ten and s
71ome ones. Here the ${numberWord(m)} ones are shown, so the missing part must be the ten.`:"Every teen number is made of a ten and some ones. Here the ten is shown, so the missing part is the ones."},{visual_data:{type:"ten_frame_and_ones",tens:1,ones:m},animation:"highlight_ten",caption:`Ten, then ${m} more.`,spoken:`Let's build ${numberWord(s)} with counters. The full ten-frame is the ten, and there are ${numberWord(m)} more counters next to it.`},{visual_data:{type:"equation_display",terms:b},animation:"write_answer",caption:`${b[0]} + ${b[2]} = ${s}.`,spoken:`So the missing number is ${numberWord(h)}. ${capitalised(numberWord(Number(b[0])))} plus ${numberWord(Number(b[2]))} equals ${numberWord(s)}.`}]})}},COMPARE$6=/^Which is (greater|bigger|smaller|less): (\d+) or (\d+)\?$/,STORY_COMPARE=/^\w+ has (\d+)\. \w+ has (\d+)\. Bigger number\?$/,CLAIM_COMPARE=/^\w+ says (\d+) is (bigger|smaller|greater|less) than (\d+)\. True or false\?$/;function compareSymbolWord(n){return n==="<"?"less than":n===">"?"more than":"equal to"}function distinctTeens(n,t){return n>=11&&n<=19&&t>=11&&t<=19&&n!==t}function comparePairFromVisual(n){if(!n)return null;if(n.type==="comparison"&&Number.isInteger(n.left)&&Number.isInteger(n.right)){const t=n.symbol==="<"||n.symbol===">"||n.symbol==="="?n.symbol:null;return{left:n.left,right:n.right,statedSymbol:t}}if(n.type==="equation_display"&&Array.isArray(n.terms)&&n.terms.length===3){const[t,e,s]=n.terms;if(Number.isInteger(t)&&Number.isInteger(s)&&(e==="<"||e===">"||e==="="))return{left:t,right:s,statedSymbol:e}}return null}function buildSymbolDemo(n,t,e,s){if(!distinctTeens(t,e))return null;const o=t<e?"<":">";if(o!==n.correctAnswer.trim())return null;const r=t-10,a=e-10;return buildExample({generatorId:s,question:n,title:`Which symbol goes between ${t} and ${e}?`,computed:{left:t,right:e,onesL:r,onesR:a},steps:[{visual_data:{type:"comparison",left:t,right:e,symbol:null},animation:"show_numbers",caption:`Which symbol goes between ${t} and ${e}?`,spoken:`To choose the symbol, we need to know which number is bigger. ${capitalised(numberWord(t))} and ${numberWord(e)} both have one ten, so we look at the ones digits.`},{visual_data:{type:"comparison",left:t,right:e,symbol:null},animation:"highlight_ones",caption:`Ones: ${r} and ${a}.`,spoken:`The ones digits are ${numberWord(r)} and ${numberWord(a)}. ${capitalised(numberWord(Math.max(r,a)))} is more than ${numberWord(Math.min(r,a))}, so ${numberWord(Math.max(t,e))} is bigger.`},{visual_data:{type:"comparison",left:t,right:e,symbol:o},animation:"write_symbol",caption:`${t} ${o} ${e}`,spoken:`So ${numberWord(t)} is ${compareSymbolWord(o)} ${numberWord(e)}. The open end of the symbol faces the bigger number.`}]})}function buildTrueFalseDemo$1(n,t,e,s,o){if(s===null||t<11||t>19||e<11||e>19)return null;const r=t<e?"<":t>e?">":"=",a=s===r?"True":"False";if(a!==n.correctAnswer.trim())return null;const i=t-10,l=e-10;return buildExample({generatorId:o,question:n,title:`Is ${t} ${s} ${e} true?`,computed:{left:t,right:e,onesL:i,onesR:l},steps:[{visual_data:{type:"comparison",left:t,right:e,symbol:s},animation:"show_numbers show_statement",caption:`Is ${t} ${s} ${e}?`,spoken:`Is ${numberWord(t)} ${compareSymbolWord(s)} ${numberWord(e)}? Let's check the ones digits.`},{visual_data:{type:"comparison",left:t,right:e,symbol:null},animation:"highlight_ones",caption:t===e?`Both have ${i} ones — equal.`:`Both start with a one. Ones: ${i} and ${l}.`,spoken:t===e?`Both have one ten and ${numberWord(i)} ones, so ${numberWord(t)} and ${numberWord(e)} are equal.`:`Both ${numberWord(t)} and ${numberWord(e)} start with a one. So compare the ones: ${numberWord(i)} and ${numberWord(l)}.`},a==="False"?{visual_data:{type:"comparison",left:t,right:e,symbol:r,crossed_symbol:s},animation:"write_symbol",caption:`${t} ${s} ${e} is False because ${t} ${r} ${e}.`,spoken:`${capitalised(numberWord(t))} is ${compareSymbolWord(r)} ${numberWord(e)}, not ${compareSymbolWord(s)}. So the statement is false, and we cross it out.`}:{visual_data:{type:"comparison",left:t,right:e,symbol:r},animation:"write_symbol",caption:`${t} ${s} ${e} is True.`,spoken:`${capitalised(numberWord(t))} is ${compareSymbolWord(r)} ${numberWord(e)}. The statement said ${compareSymbolWord(s)}, so it is true.`}]})}const teenComparison$1={id:"NPV.P3.M03.compare-teens.v1",microSkillId:"NPV.P3.M03",forms:["teen_comparison_greater","teen_comparison_smaller","comparison","true_false_check"],generate(n){const t=normaliseStem(n.stem).match(COMPARE$6),e=t?null:normaliseStem(n.stem).match(STORY_COMPARE);if(t||e){const r=t?t[1]==="greater"||t[1]==="bigger":!0,a=Number(t?t[2]:e[1]),i=Number(t?t[3]:e[2]);if(!distinctTeens(a,i))return null;const l=Math.max(a,i),u=Math.min(a,i),d=r?l:u;
71if(d!==answerInt(n))return null;const m=a-10,h=i-10,f=a<i?"<":">";return buildExample({generatorId:this.id,question:n,title:`Which is ${r?"bigger":"smaller"}, ${a} or ${i}?`,computed:{left:a,right:i,onesL:m,onesR:h,answer:d},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_numbers",caption:`Which is ${r?"bigger":"smaller"}, ${a} or ${i}?`,spoken:`${capitalised(numberWord(a))} or ${numberWord(i)} — which is ${r?"bigger":"smaller"}? Both have one ten, so the tens are the same. That means we look at the ones digits.`},{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"highlight_ones",caption:`Ones: ${m} and ${h}.`,spoken:`The ones digits are ${numberWord(m)} and ${numberWord(h)}. ${capitalised(numberWord(r?Math.max(m,h):Math.min(m,h)))} is ${r?"more":"less"} than ${numberWord(r?Math.min(m,h):Math.max(m,h))}, so ${numberWord(d)} is ${r?"bigger":"smaller"}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:f},animation:"write_symbol",caption:`${a} ${f} ${i}`,spoken:`${capitalised(numberWord(a))} is ${a<i?"less":"more"} than ${numberWord(i)}. ${capitalised(numberWord(d))} is the ${r?"bigger":"smaller"} one.`}]})}const s=comparePairFromVisual(n.visualData);if(s){const r=n.correctAnswer.trim();return r==="<"||r===">"||r==="="?buildSymbolDemo(n,s.left,s.right,this.id):r==="True"||r==="False"?buildTrueFalseDemo$1(n,s.left,s.right,s.statedSymbol,this.id):null}const o=normaliseStem(n.stem).match(CLAIM_COMPARE);if(o){const r=Number(o[1]),a=o[2],i=Number(o[3]);return buildTrueFalseDemo$1(n,r,i,a==="bigger"||a==="greater"?">":"<",this.id)}return null}},skipCount={id:"MD.P1.M02.skip-count.v1",microSkillId:"MD.P1.M02",forms:["","symbolic_numeric_entry","sequence_fill_gap","number_line_identify","equation_completion"],generate(n){const t=parseSequence(n.stem,[2,5,10]);return!t||t.missing!==answerInt(n)?null:buildSkipCountNumberLine({generatorId:this.id,q:n,values:t.values,step:t.step,gapIndex:t.blankIndex,missing:t.missing,toWords:numberWord})}},SHARE=/^Share (\d+) ?([a-z]*) equally between (\d+) ?[a-z]*\. How many (?:in )?each ?[a-z]*\?$/,SHARE_EMOJI={apples:"🍎",grapes:"🍇",cookies:"🍪",strawberries:"🍓",marbles:"🔵",pencils:"✏️",sweets:"🍬",stickers:"⭐"},equalSharing={id:"MD.P1.M03.sharing.v1",microSkillId:"MD.P1.M03",forms:["halving","context_calculation"],generate(n){const t=normaliseStem(n.stem).match(SHARE);if(!t)return null;const e=Number(t[1]),s=Number(t[3]);if(s<2||s>3||e<s||e>12||e%s!==0)return null;const o=e/s;if(o!==answerInt(n))return null;const r=SHARE_EMOJI[t[2]]??"🔵",a=`${Array(s).fill(o).join(" + ")} = ${e}.`,i=a.length<=72?a:`${e} shared into ${s} = ${o} each.`;return buildExample({generatorId:this.id,question:n,title:`How do we share ${e} equally into ${s} groups?`,computed:{total:e,groups:s,each:o,answer:o},steps:[{visual_data:{type:"objects_grid",emoji:r,count:e,group_size:e,hide_subtotals:!0},animation:"show_whole",caption:"Share them into equal groups.",spoken:`We have ${numberWord(e)} to share equally between ${numberWord(s)} groups.`},{visual_data:{type:"objects_grid",emoji:r,count:s,group_size:1,mode:"share"},animation:"deal_one_each",caption:"Give one to each group.",spoken:"Deal them out one at a time — one for each group, and keep going."},{visual_data:{type:"objects_grid",emoji:r,count:e,group_size:o,mode:"share"},animation:"share_result",caption:i,spoken:`Every group ends up with ${numberWord(o)}. ${capitalised(numberWord(e))} shared into ${numberWord(s)} equal groups — ${numberWord(o)} each.`}]})}},NPV_MD_GENERATORS=[countObjectsToTeen,countOnByOne,countBackByOne,oneMoreOneLess,teenFromTenAndOnes,teenPartition,teenComparison$1,skipCount,equalSharing];function unitHops(n,t,e){return Array.from({length:t},(s,o)=>({from:n+e*o,to:n+e*(o+1),label:e>0?"+1":"−1"}))}function buildColumnSubtract(n){const{generatorId:t,q:e,a:s,b:o,expected:r,story:a}=n;if(s<10||s>99||o<10||o>99||o>=s)return null;const i=s%10,l=o%10;if(i>=l)return null;const u=s-o;if(r===null||u!==r)return null;const d=Math.floor(s/10),m=Math.floor(o/10),h=d-1,f=i+10,g=f-l,b=h-m,x={type:"column_method",operation:"subtract",top:s,bottom:o},y=numberToWords,E=k=>k===1?"one ten":`${y(k)} tens`;return buildExample({generatorId:t,question:e,title:n.title??`How do we work out ${s} − ${o}?`,computed:{a:s,b:o,onesA:i,onesB:l,tensA:d,tensB:m,tensLeft:h,onesNow:f,onesResult:g,tensResult:b,result:u,answer:u},steps:[{visual_data:x,animation:"stack_columns",caption:`Tens under tens, ones under ones. ${i} take away ${l}?`,spoken:`${capitalised(y(s))} minus ${y(o)}. We put tens under tens and ones under ones, and start with the ones. ${capitalised(y(i))} take away ${y(l)}? There aren't enough ones.`},{visual_data:x,animation:"exchange_ten",caption:`Exchange a ten: now ${f} − ${l} = ${g}
71 in the ones.`,spoken:`So we exchange one ten for ten ones. Now there ${h===1?"is one ten":`are ${y(h)} tens`} and ${y(f)} ones — the same amount, just regrouped. ${capitalised(y(f))} take away ${y(l)} leaves ${y(g)}.`},{visual_data:{...x,keep_exchange:!0},animation:"complete_difference",caption:`Then the tens: ${h} − ${m} = ${b}. So ${s} − ${o} = ${u}.`,spoken:`Then the tens: ${E(h)} take away ${E(m)} leaves ${b===0?"no tens":E(b)}. So ${y(s)} minus ${y(o)} is ${y(u)}.${a?` ${a.spoken}`:""}`}]})}function buildColumnAdd(n){const{generatorId:t,q:e,a:s,b:o,expected:r,lead:a,story:i}=n;if(s<10||s>99||o<10||o>99)return null;const l=s%10,u=o%10,d=l+u;if(d<10)return null;const m=s+o;if(m>99||r===null||m!==r)return null;const h=Math.floor(s/10),f=Math.floor(o/10),g=d-10,b=h+f+1,x={type:"column_method",operation:"add",top:s,bottom:o},y=numberToWords,E=k=>k===1?"one ten":`${y(k)} tens`;return buildExample({generatorId:t,question:e,title:n.title??`How do we work out ${s} + ${o}?`,computed:{a:s,b:o,onesA:l,onesB:u,onesSum:d,onesLeft:g,tensA:h,tensB:f,one:1,tensSum:b,total:m,answer:m},steps:[{visual_data:x,animation:"stack_columns",caption:`${a?`${a.caption} `:""}Tens under tens. Add the ones first.`,spoken:`${a?a.spoken:`${capitalised(y(s))} plus ${y(o)}.`} We put tens under tens and ones under ones, and add the ones first.`},{visual_data:x,animation:"carry_the_ten",caption:`${l} + ${u} = ${d}. Carry one ten to the tens.`,spoken:g===0?`${capitalised(y(l))} and ${y(u)} make ten, exactly one ten. We write zero in the ones, and the ten is carried to the tens.`:`${capitalised(y(l))} and ${y(u)} make ${y(d)}. That's one ten and ${y(g)} left over. The ${y(g)} stays in the ones, and the ten is carried to the tens.`},{visual_data:x,animation:"complete_sum",caption:`Tens: ${h} + ${f} + 1 = ${b}. So ${s} + ${o} = ${m}.`,spoken:`Now the tens column: ${y(h)} and ${y(f)}, and the one we carried, make ${E(b)}. So ${y(s)} plus ${y(o)} is ${y(m)}.${i?` ${i.spoken}`:""}`}]})}function buildCountOnGap(n){const{generatorId:t,q:e,high:s,low:o,expected:r,title:a}=n;if(o<0||s>99||s<=o)return null;const i=s-o;if(r===null||i!==r)return null;const l=Math.ceil(o/10)*10;if(l>=s)return null;const u=l-o,d=Math.floor(s/10)*10,m=d-l,h=s-d,f=[];if(u>0&&f.push({from:o,to:l}),m>0&&f.push({from:l,to:d}),h>0&&f.push({from:d,to:s}),f.length<2)return null;const g=f.map(F=>({...F,label:`+${F.to-F.from}`})),b=f.map(F=>F.to-F.from),x=o,y=s,E=numberToWords,k=F=>({type:"number_line",min:x,max:y,marked:[o,s],hops:F,unknown:null}),T={high:s,low:o,missing:i,answer:i};f.forEach((F,D)=>{T[`to${D+1}`]=F.to,T[`jump${D+1}`]=F.to-F.from});const[A,...N]=g,S=N.map(F=>`+${F.to-F.from} to ${F.to}`).join(", "),M=N.map(F=>`${E(F.to-F.from)} more to ${E(F.to)}`).join(", then "),L=b.join(" + "),P=b.map(E).join(" and ");return buildExample({generatorId:t,question:e,title:a,computed:T,steps:[{visual_data:k([]),animation:"show_start",caption:`Count on from ${o} up to ${s}.`,spoken:`${capitalised(E(s))} take away something leaves ${E(o)}. To find it, we count on from ${E(o)} up to ${E(s)} and add up the jumps.`},{visual_data:k([A]),animation:"bridge",caption:`${o} + ${b[0]} = ${A.to}. That's ${b[0]} so far.`,spoken:`${u>0?"Jump to the next ten first. ":"Jump the tens first. "}${capitalised(E(o))} and ${E(b[0])} make ${E(A.to)}. That's ${E(b[0])} so far.`},{visual_data:k(g),animation:"land",caption:`Then ${S}. ${L} = ${i}.`,spoken:`Then ${M}. We jumped ${P}, which make ${E(i)}. So the missing number is ${E(i)}.`}]})}const ADD_EQ$4=/^(\d+) \+ (\d+) = __$/,ADD_WHAT$2=/^What is (\d+) \+ (\d+)\?$/,ADD_Q=/^(\d+) \+ (\d+) = \?$/,ADD_WHICH=/^Which equals (\d+) \+ (\d+)\?$/,ADD_REVERSED=/^__ = (\d+) \+ (\d+)$/,COUNT_ON=/^Start at (\d+) and count on (\d+)$/,MISS_SECOND=/^(\d+) \+ __ = (\d+)$/,MISS_SECOND_WHAT=/^(\d+) \+ __ = (\d+)\. What is missing\?$/,MISS_FIRST=/^__ \+ (\d+) = (\d+)$/,ADD_CUE=/\b(more|gets?|joins?|added|sit down)\b/i,SUB_CUE=/\b(gives? away|poured? out|drive away|got off|gave away|leaves?|less|fewer|takes? away|difference|how many more|how much (?:taller|older|heavier|longer))\b/i,TWO_NUMBERS=/(\d+)\D+(\d+)/,isStart=n=>Number.isInteger(n)&&n>=10&&n<=98,isCount=n=>Number.isInteger(n)&&n>=1&&n<=9;function parseWordProblem(n){const t=normaliseStem(n);if(!ADD_CUE.test(t)||SUB_CUE.test(t))return null;const e=t.match(TWO_NUMBERS);if(!e||(t.match(/\d+/g)??[]).length!==2)return null;const s=Number(e[1]),o=Number(e[2]);return{kind:"sum",a:s,b:o}}function parseStem(n){const t=matchInts(n.stem,ADD_EQ$4)??matchInts(n.stem,ADD_WHAT$2)??matchInts(n.stem,ADD_Q)??matchInts(n.stem,ADD_WHICH)??matchInts(n.stem,ADD_REVERSED)??matchInts(n.stem,COUNT_ON
71);if(t)return{kind:"sum",a:t[0],b:t[1]};const e=matchInts(n.stem,MISS_SECOND)??matchInts(n.stem,MISS_SECOND_WHAT);if(e){const[o,r]=e;return{kind:"miss_second",a:o,c:r,m:r-o}}const s=matchInts(n.stem,MISS_FIRST);if(s){const[o,r]=s;return{kind:"miss_first",s:r-o,b:o,c:r}}return parseWordProblem(n.stem)}function lineBounds$1(n,t){const e=Math.floor(n/10)*10,s=Math.max(e+10,Math.ceil(t/10)*10);return{min:e,max:s}}function countingWordsDown(n,t){const e=[];for(let s=n;s>=t;s--)e.push(numberToWords(s));return e.join(", ")}const asP4M01Generator={id:"AS.P4.M01.count-on-ones.v1",microSkillId:"AS.P4.M01",forms:["symbolic_calculation","equation_completion","varied_symbolic_calculation","missing_addend","symbolic_choice","context_calculation"],generate(n){const t=parseStem(n);if(!t)return null;const e=answerInt(n);if(e===null)return null;if(t.kind==="sum"){const{a,b:i}=t;let l,u,d=!1;if(isStart(a)&&isCount(i))l=a,u=i;else if(isStart(i)&&isCount(a))l=i,u=a,d=!0;else return null;const m=l+u;return m!==e?null:buildCountOn$1(this.id,n,l,u,m,d?{origA:a,origB:i}:void 0)}if(t.kind==="miss_second"){const{a,c:i,m:l}=t;return!isStart(a)||!isCount(l)||i<=a||l!==e?null:buildMissingSecond(this.id,n,a,i,l)}const{s,b:o,c:r}=t;return!isStart(s)||!isCount(o)||r<=o||s!==e?null:buildMissingFirst(this.id,n,s,o,r)}};function buildCountOn$1(n,t,e,s,o,r){const{min:a,max:i}=lineBounds$1(e,o),l=unitHops(e,s,1),u=Array.from({length:s},(E,k)=>e+1+k),d=u.join(", "),m={a:e,b:s,total:o,answer:o};u.forEach((E,k)=>{m[`on${k+1}`]=E}),r&&(m.origA=r.origA,m.origB=r.origB);const h=r?`${r.origA} + ${r.origB} is the same as ${e} + ${s}, so start at ${e}.`:`Start at ${e} on the line.`,f=r?`${capitalised(numberToWords(r.origA))} plus ${numberToWords(r.origB)} is the same as ${numberToWords(e)} plus ${numberToWords(s)}. We start at ${numberToWords(e)} and count on ${s===1?"one":`${numberToWords(s)} ones`}.`:`${capitalised(numberToWords(e))} plus ${numberToWords(s)}. We start at ${numberToWords(e)} and count on ${s===1?"one":`${numberToWords(s)} ones`}.`,g=r?`${o} = ${r.origA} + ${r.origB}.`:`${e} + ${s} = ${o}.`,b=e-e%10,x=b>0&&e%10+s<10?` Only the ones changed: ${numberToWords(e%10)} and ${numberToWords(s)} make ${numberToWords(e%10+s)}, and the ${numberToWords(b)} stays.`:"",y=r?`We land on ${numberToWords(o)}. So ${numberToWords(r.origA)} plus ${numberToWords(r.origB)} is ${numberToWords(o)}.${x}`:`We land on ${numberToWords(o)}. So ${numberToWords(e)} plus ${numberToWords(s)} is ${numberToWords(o)}.${x}`;return buildExample({generatorId:n,question:t,title:`How do we work out ${e} + ${s}?`,computed:m,steps:[{visual_data:{type:"number_line",min:a,max:i,marked:[e],hops:[],unknown:null},animation:"show_start",caption:h,spoken:f},{visual_data:{type:"number_line",min:a,max:i,marked:[e],hops:l,unknown:null},animation:"count_on",caption:`Count on ${s}: ${d}.`,spoken:`To add ${numberToWords(s)}, we count on from ${numberToWords(e)}, one hop at a time: ${countingWords(e+1,o)}.`},{visual_data:{type:"number_line",min:a,max:i,marked:[e],hops:l,unknown:o},animation:"land",caption:g,spoken:y}]})}function buildMissingSecond(n,t,e,s,o){const{min:r,max:a}=lineBounds$1(e,s),i=unitHops(e,o,1),l=Array.from({length:o},(m,h)=>e+1+h),u=l.join(", "),d={a:e,c:s,m:o,answer:o};return l.forEach((m,h)=>{d[`on${h+1}`]=m}),buildExample({generatorId:n,question:t,title:`How do we work out ${e} + __ = ${s}?`,computed:d,steps:[{visual_data:{type:"number_line",min:r,max:a,marked:[e],hops:[],unknown:null},animation:"show_start",caption:`Start at ${e}. Count on to ${s}.`,spoken:`${capitalised(numberToWords(e))} plus something equals ${numberToWords(s)}. We start at ${numberToWords(e)} and count on until we reach ${numberToWords(s)}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[e],hops:i,unknown:null},animation:"count_on",caption:`Count on to ${s}: ${u}.`,spoken:`${capitalised(countingWords(e+1,s))}. That is ${numberToWords(o)} ${o===1?"step":"steps"}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[e],hops:i,unknown:null},animation:"land",caption:`${e} + ${o} = ${s}.`,spoken:`We counted on ${numberToWords(o)} to get from ${numberToWords(e)} to ${numberToWords(s)}. So the missing number is ${numberToWords(o)}.`}]})}function buildMissingFirst(n,t,e,s,o){const{min:r,max:a}=lineBounds$1(e,o),i=unitHops(o,s,-1),l={from:e,to:o,label:`+${s}`},u=Array.from({length:s},(h,f)=>o-1-f),d=u.join(", "),m={s:e,b:s,c:o,answer:e};return u.forEach((h,f)=>{m[`back${f+1}`]=h}),buildExample({generatorId:n,question:t,title:`How do we work out __ + ${s} = ${o}?`,computed:m,steps:[{visual_data:{type:"number_line",min:r,max:a,marked:[o],hops:[],unknown:null},animation:"show_start",caption:`Counting on ${s} lands on ${o}
71.`,spoken:`Something plus ${numberToWords(s)} equals ${numberToWords(o)}. Counting on ${numberToWords(s)} lands on ${numberToWords(o)}. Where did we start?`},{visual_data:{type:"number_line",min:r,max:a,marked:[o],hops:i,unknown:null},animation:"count_back",caption:`Count back ${s} from ${o}: ${d}.`,spoken:`To find the start, we undo the adding: count back ${numberToWords(s)} from ${numberToWords(o)}. ${capitalised(countingWordsDown(o-1,e))}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[o],hops:[l],unknown:e},animation:"land",caption:`${e} + ${s} = ${o}.`,spoken:`We started at ${numberToWords(e)}. Check it: ${numberToWords(e)} plus ${numberToWords(s)} is ${numberToWords(o)}. So the missing number is ${numberToWords(e)}.`}]})}const GENERATOR_ID$9="AS.P4.M02.bridge-through-ten.v1";function gapToNextTen$2(n){return(10-n%10)%10}function bridgeSplit(n,t){if(n<10||n>999)return null;const e=gapToNextTen$2(n);if(e<1||e>9||t<=e)return null;const s=t-e;if(s<1||s>9)return null;const o=n+e;return o>999?null:{gap:e,rest:s,nextTen:o}}function twoStepFrames(n,t,e,s,o){const r={type:"two_step",start:n,step1_add:t,step2_add:e};return[{...r,step1_result:null,final_result:null},{...r,step1_result:s,final_result:null},{...r,step1_result:s,final_result:o}]}function forwardDemo(n,t,e){const s=t+e;if(s!==answerInt(n))return null;const o=bridgeSplit(t,e);if(!o)return null;const{gap:r,rest:a,nextTen:i}=o,l=t-2,u=s+2,d={from:t,to:i,label:`+${r}`},m={from:i,to:s,label:`+${a}`},h={type:"number_line",min:l,max:u,marked:[t,i]};return buildExample({generatorId:GENERATOR_ID$9,question:n,title:`How do we work out ${t} + ${e}?`,computed:{start:t,add:e,gap:r,rest:a,nextTen:i,answer:s},steps:[{visual_data:{...h,hops:[],unknown:null,pulse_steps:[1],conclude_label:`${e} = ${r} + ${a}`},animation:"show_split",caption:`${t} needs ${r} more to make ${i}. So split ${e} into ${r} and ${a}.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(e)}. ${capitalised(numberToWords(t))} is very close to ${numberToWords(i)} — it only needs ${numberToWords(r)} more. So we split the ${numberToWords(e)} into ${numberToWords(r)} and ${numberToWords(a)}.`},{visual_data:{...h,hops:[d],unknown:null},animation:"bridge_to_ten",caption:`Jump ${r} to ${i}.`,spoken:`First we jump ${numberToWords(r)}, from ${numberToWords(t)} to ${numberToWords(i)}. Now we are on a ten, and adding to a ten is easy.`},{visual_data:{...h,hops:[d,m],unknown:s},animation:"add_remainder",caption:`Jump the ${a} that's left: ${i} + ${a} = ${s}. So ${t} + ${e} = ${s}.`,spoken:`Then we jump the ${numberToWords(a)} that is left, from ${numberToWords(i)} to ${numberToWords(s)}. ${capitalised(numberToWords(r))} and ${numberToWords(a)} make the ${numberToWords(e)} we had to add. So ${numberToWords(t)} plus ${numberToWords(e)} equals ${numberToWords(s)}.`}]})}function missingAddendDemo$1(n,t,e){const s=e-t;if(s!==answerInt(n))return null;const o=bridgeSplit(t,s);if(!o)return null;const{gap:r,rest:a,nextTen:i}=o,l=Math.min(t,e)-2,u=Math.max(t,e)+2,d={from:t,to:i,label:`+${r}`},m={from:i,to:e,label:`+${a}`},h={start:t,target:e,gap:r,rest:a,nextTen:i,answer:s};return buildExample({generatorId:GENERATOR_ID$9,question:n,title:`What do we add to ${t} to make ${e}?`,computed:h,steps:[{visual_data:{type:"number_line",min:l,max:u,marked:[t],hops:[],unknown:null},animation:"show_split",caption:`Count on from ${t} to ${e}.`,spoken:`${capitalised(numberToWords(t))} plus something makes ${numberToWords(e)}. We count on from ${numberToWords(t)} to find it.`},{visual_data:{type:"number_line",min:l,max:u,marked:[t],hops:[d],unknown:null},animation:"bridge_to_ten",caption:`${t} + ${r} = ${i}. That's ${r} so far.`,spoken:`Counting on to a ten first makes it easy. ${capitalised(numberToWords(t))} plus ${numberToWords(r)} makes ${numberToWords(i)}. That's ${numberToWords(r)} counted so far.`},{visual_data:{type:"number_line",min:l,max:u,marked:[t],hops:[d,m],unknown:null},animation:"add_remainder",caption:`${i} + ${a} = ${e}. ${r} + ${a} = ${s}.`,spoken:`Now count on the rest: ${numberToWords(i)} plus ${numberToWords(a)} makes ${numberToWords(e)}. ${capitalised(numberToWords(r))} and ${numberToWords(a)} make ${numberToWords(s)}. The missing number is ${numberToWords(s)}.`}]})}function missingFirstDemo(n,t,e){if(t<10||t>99)return null;const s=t%10;if(s<1||s>9||e<=s)return null;const o=e-s;if(o<1||o>9)return null;const r=t-s,a=r-o;if(a!==answerInt(n))return null;const[i,l,u]=twoStepFrames(t,-s,-o,r,a);return buildExample({generatorId:GENERATOR_ID$9,question:n,title:`What number plus ${e} makes ${t}?`,computed:{start:a,target:t,addend:e,onesT:s,rest:o,tenFloor:r,answer:a},steps:[{visual_data:i,animation:"show_split",caption:`Count back ${e} from ${t}. First drop to ${r}
71.`,spoken:`Something plus ${numberToWords(e)} makes ${numberToWords(t)}. We count back from ${numberToWords(t)}, first down to the ten below.`},{visual_data:l,animation:"bridge_to_ten",caption:`${t} − ${s} = ${r}. That is ${s} back so far.`,spoken:`Stopping at a ten makes counting back easy. ${capitalised(numberToWords(t))} minus ${numberToWords(s)} is ${numberToWords(r)}. That is ${numberToWords(s)} counted back so far.`},{visual_data:u,animation:"add_remainder",caption:`${r} − ${o} = ${a}. So the missing number is ${a}.`,spoken:`We still have ${numberToWords(o)} left to count back. ${capitalised(numberToWords(r))} minus ${numberToWords(o)} is ${numberToWords(a)}. So the missing number is ${numberToWords(a)}.`}]})}const FORWARD_RE=/^(\d+) \+ (\d+) = __$/,MISSING_ADDEND_RE=/^(\d+) \+ __ = (\d+)$/,MISSING_FIRST_RE=/^__ \+ (\d+) = (\d+)$/,asP4M02Generator={id:GENERATOR_ID$9,microSkillId:"AS.P4.M02",forms:["bridging_through_10_addition","missing_number_bridging","missing_addend","symbolic_choice","context_calculation"],generate(n){const t=normaliseStem(n.stem),e=t.match(FORWARD_RE);if(e)return forwardDemo(n,Number(e[1]),Number(e[2]));const s=t.match(MISSING_ADDEND_RE);if(s)return missingAddendDemo$1(n,Number(s[1]),Number(s[2]));const o=t.match(MISSING_FIRST_RE);if(o)return missingFirstDemo(n,Number(o[2]),Number(o[1]));if(n.mathematicalForm==="context_calculation"){const r=(t.match(/\d+/g)??[]).map(Number);if(r.length!==2)return null;const a=Math.max(r[0],r[1]),i=Math.min(r[0],r[1]);return i<1||i>9?null:forwardDemo(n,a,i)??missingFirstDemo(n,a,i)}return null}},SUB_EQ$3=/^(\d+) - (\d+) = __$/,SUB_REV$1=/^__ = (\d+) - (\d+)$/,SUB_MISSING_SUBTRAHEND=/^(\d+) - __ = (\d+)$/,SUB_MISSING_MINUEND=/^__ - (\d+) = (\d+)$/,SUB_STORY_GIVES_AWAY=/^[A-Z][a-z]+ has (\d+) [a-z]+\. (?:[Ss]he|[Hh]e) gives (\d+) to (?:her|his) friend\. How many [a-z]+ does (?:she|he) have left\?$/,SUB_CLAIM$2=/^[A-Z][a-z]+ says (\d+) - (\d+) = (\d+)\. Is (?:he|she) right\?$/,GENERATOR_ID$8="AS.P4.M03.count-back-ones.v1";function buildCountBack(n,t,e,s){if(t<10||t>98||e<1||e>9||t%10<e)return null;const o=t-e;if(o!==s)return null;const r=Math.floor(t/10)*10,a=r+10,i=unitHops(t,e,-1),l=t-t%10,u=Array.from({length:e},(g,b)=>t-1-b),d=u.join(", "),m=u.map(numberToWords).join(", "),h={a:t,b:e,result:o,answer:o};u.forEach((g,b)=>{h[`on${b+1}`]=g});const f=l>0?` Only the ones changed: ${numberToWords(t%10)} take away ${numberToWords(e)} leaves ${numberToWords(o%10)}, and the ${numberToWords(l)} stays.`:"";return buildExample({generatorId:GENERATOR_ID$8,question:n,title:`How do we work out ${t} − ${e}?`,computed:h,steps:[{visual_data:{type:"number_line",min:r,max:a,marked:[t],hops:[],unknown:null},animation:"show_start",caption:`Start at ${t} on the line.`,spoken:`We want ${numberToWords(t)} minus ${numberToWords(e)}. Taking away ${e===1?"one":`${numberToWords(e)} ones`} is the same as counting back ${e===1?"one":`${numberToWords(e)} ones`}, so we start at ${numberToWords(t)} on the number line.`},{visual_data:{type:"number_line",min:r,max:a,marked:[t],hops:i,unknown:null},animation:"count_back",caption:`Count back ${e}: ${d}.`,spoken:`We make one hop back for each one we take away: ${m}. That is ${numberToWords(e)} ${e===1?"hop":"hops"}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[t],hops:i,unknown:o},animation:"land",caption:`${t} − ${e} = ${o}.`,spoken:`We land on ${numberToWords(o)}. So ${numberToWords(t)} minus ${numberToWords(e)} is ${numberToWords(o)}.${f}`}]})}function buildMissingSubtrahend(n,t,e){const s=t-e;if(t<10||t>98||s<1||s>9||t%10<s||s!==answerInt(n))return null;const o=Math.floor(t/10)*10,r=o+10,a=unitHops(e,s,1),i=Array.from({length:s},(m,h)=>e+1+h),l=i.join(", "),u=i.map(numberToWords).join(", "),d={minuend:t,result:e,subtrahend:s,answer:s};return i.forEach((m,h)=>{d[`on${h+1}`]=m}),buildExample({generatorId:GENERATOR_ID$8,question:n,title:`How many do we take away from ${t} to get ${e}?`,computed:d,steps:[{visual_data:{type:"number_line",min:o,max:r,marked:[t,e],hops:[],unknown:null},animation:"show_start",caption:`Start at ${e}. Count up to ${t}.`,spoken:`${capitalised(numberToWords(t))} take away a missing number makes ${numberToWords(e)}. To find it, we count up from ${numberToWords(e)} to ${numberToWords(t)} and count the steps.`},{visual_data:{type:"number_line",min:o,max:r,marked:[t,e],hops:a,unknown:null},animation:"count_on",caption:`Count up to ${t}: ${l}.`,spoken:`One hop for each step: ${u}. We stop when we reach ${numberToWords(t)}.`},{visual_data:{type:"number_line",min:o,max:r,marked:[t,e],hops:a,unknown:null},animation:"land",caption:`That is ${s} ${s===1?"hop":"hops"}, so ${t} − ${s} = ${e}.`,spoken:`We counted ${s===1?"one step":`${numberToWords(s)} steps`}, so ${numberToWords(t)} take away ${numberToWords(s)} is ${numberToWords(e)}. The missing number is ${numberToWords(s)}.`}]})}function buildMissingMinuend(n,t,e){const s=e+t;if(t<1||t>9||s<10||s>98||s%10<t||s!==answerInt(n))return null;const o=Math.floor(e/10)*10,r=o+10,a=unitHops(e,t,1),i=Array.from({length:t},(h,f)=>e+1+f),l=i.join(", "),u=i.map(numberToWords).join(", "),d={minuend:s,subtrahend:t,result:e,answer:s};
71i.forEach((h,f)=>{d[`on${f+1}`]=h});const m=t===1?"one":`${numberToWords(t)} ones`;return buildExample({generatorId:GENERATOR_ID$8,question:n,title:`What number take away ${t} makes ${e}?`,computed:d,steps:[{visual_data:{type:"number_line",min:o,max:r,marked:[e],hops:[],unknown:null},animation:"show_start",caption:`We landed on ${e} after taking ${t} away.`,spoken:`Something take away ${numberToWords(t)} makes ${numberToWords(e)}. Adding undoes taking away, so to find the start we add the ${m} back on.`},{visual_data:{type:"number_line",min:o,max:r,marked:[e],hops:a,unknown:null},animation:"count_on",caption:`Add ${t} back: ${l}.`,spoken:`We start at ${numberToWords(e)} and hop on ${numberToWords(t)}: ${u}.`},{visual_data:{type:"number_line",min:o,max:r,marked:[e],hops:a,unknown:s},animation:"land",caption:`Start was ${s}, so ${s} − ${t} = ${e}.`,spoken:`We land on ${numberToWords(s)}, so that was the start. Check: ${numberToWords(s)} take away ${numberToWords(t)} is ${numberToWords(e)}. The missing number is ${numberToWords(s)}.`}]})}function buildSubtractionClaimCheck(n,t,e,s){if(t<10||t>98||e<1||e>9||t%10<e)return null;const o=t-e;if((o===s?"Yes":"No")!==n.correctAnswer.trim())return null;const a=Math.floor(t/10)*10,i=a+10,l=unitHops(t,e,-1),u=Array.from({length:e},(f,g)=>t-1-g),d=u.join(", "),m=u.map(numberToWords).join(", "),h={a:t,b:e,stated:s,actual:o};return u.forEach((f,g)=>{h[`on${g+1}`]=f}),buildExample({generatorId:GENERATOR_ID$8,question:n,title:`Is ${t} − ${e} = ${s} correct?`,computed:h,steps:[{visual_data:{type:"number_line",min:a,max:i,marked:[t],hops:[],unknown:null},animation:"show_start",caption:`Someone says ${t} − ${e} = ${s}. Let's check.`,spoken:`Someone says ${numberToWords(t)} minus ${numberToWords(e)} equals ${numberToWords(s)}. Let's work it out ourselves first.`},{visual_data:{type:"number_line",min:a,max:i,marked:[t],hops:l,unknown:null},animation:"count_back",caption:`Count back ${e} from ${t}: ${d}.`,spoken:`We make one hop back for each one we take away: ${m}.`},{visual_data:{type:"number_line",min:a,max:i,marked:[t],hops:l,unknown:o},animation:"land",caption:o===s?`We land on ${o}. ${t} − ${e} = ${o}. That's right.`:`We land on ${o}, not ${s}. ${t} − ${e} = ${o}. That's wrong.`,spoken:o===s?`We land on ${numberToWords(o)}. So ${numberToWords(t)} minus ${numberToWords(e)} really is ${numberToWords(o)}. The claim is right.`:`We land on ${numberToWords(o)}, not ${numberToWords(s)}. So ${numberToWords(t)} minus ${numberToWords(e)} is ${numberToWords(o)}. The claim is wrong.`}]})}const asP4M03Generator={id:GENERATOR_ID$8,microSkillId:"AS.P4.M03",forms:["symbolic_calculation","symbolic_choice","equation_completion","missing_addend","missing_number_bridging","subtraction_story_context","check_reasoning_probe"],generate(n){const t=matchInts(n.stem,SUB_EQ$3)??matchInts(n.stem,SUB_REV$1);if(t){const a=answerInt(n);return a===null?null:buildCountBack(n,t[0],t[1],a)}const e=matchInts(n.stem,SUB_MISSING_SUBTRAHEND);if(e)return buildMissingSubtrahend(n,e[0],e[1]);const s=matchInts(n.stem,SUB_MISSING_MINUEND);if(s)return buildMissingMinuend(n,s[0],s[1]);const o=matchInts(n.stem,SUB_STORY_GIVES_AWAY);if(o){const a=answerInt(n);return a===null?null:buildCountBack(n,o[0],o[1],a)}const r=matchInts(n.stem,SUB_CLAIM$2);return r?buildSubtractionClaimCheck(n,r[0],r[1],r[2]):null}},ADD_EQ$3=/^(\d+) \+ (\d+) = __$/,ADD_WHAT$1=/^What is (\d+) \+ (\d+)\?$/,ADD_REV=/^__ = (\d+) \+ (\d+)$/,START_JUMP=/^Start at (\d+)\. Jump on (\d+)\. Where do you land\?$/,BEADS=/^(\d+) red beads and (\d+) blue beads\. How many (?:beads )?(?:are there )?altogether\?$/,BUS=/^A bus has (\d+) seats downstairs and (\d+) upstairs\. Total\?$/,SAM_SCORES=/^Sam scores (\d+) points, then (\d+) more\. Total points\?$/,MISSING_RIGHT$1=/^(\d+) \+ __ = (\d+)$/,MISSING_LEFT$1=/^__ \+ (\d+) = (\d+)$/,THREE_ADD=/^(\d+) \+ (\d+) \+ (\d+) = __$/,BALANCE=/^(\d+) \+ (\d+) = (\d+) \+ __$/;function buildAddTensThenOnes(n,t,e,s){if(t<10||t>88||e<10||e>88)return null;const o=t%10,r=Math.floor(e/10)*10,a=e%10;if(o+a>=10)return null;const i=t+r,l=t+e;if(l!==answerInt(n))return null;const u=Math.floor(t/10)*10,d=Math.ceil(l/10)*10,m={from:t,to:i,label:`+${r}`},h={a:t,b:e,bTens:r,bOnes:a,mid:i,total:l,answer:l};if(a===0){const g=r/10,b=Array.from({length:g},(T,A)=>({from:t+10*A,to:t+10*(A+1),label:"+10"})),x=b.map(T=>T.to);x.forEach((T,A)=>{h[`ten${A+1}`]=T});const y=o===0,E=t/10,k=l/10;return y&&(h.startTens=E,h.tensCount=g,h.totalTens=k),buildExample({generatorId:asP4M04Generator.id,question:n,title:s,computed:h,steps:[{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:[],unknown:null},animation:"show_start",caption:`Start at ${t}. Add the tens.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(e)}. ${capitalised(numberToWords(e))} is ${g===1?"one ten":`${numberToWords(g)} tens`}, so we count on in tens.`},{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:b,unknown:null},animation:"add_tens",caption:`Count on ${g} ${g===1?"ten":"tens"}: ${x.join(", ")}.`,spoken:`One jump for each ten: ${x.map(numberToWords).join(", ")}.`},{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:b,unknown:l},animation:"add_ones",caption:y?`${E} tens + ${g} tens = ${k} tens = ${l}.`:`${t} + ${e} = ${l}.`,spoken:y?`${capitalised(numberToWords(E))} tens and ${numberToWords(g)} ${g===1?"ten":"tens"} make ${numberToWords(k)} tens. So ${numberToWords(t)} plus ${numberToWords(e)} is ${numberToWords(l)}.`:`So ${numberToWords(t)} plus ${numberToWords(e)} is ${numberToWords(l)}.`}]})}const f={from:i,to:l,label:`+${a}`};return buildExample({generatorId:asP4M04Generator.id,question:n,title:s,computed:h,steps:[{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:[],unknown:null},animation:"show_start",caption:`Start at ${t}. Add the tens first.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(e)}. We start at ${numberToWords(t)} and add the tens first.`},{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:[m],unknown:null},animation:"add_tens",caption:`Add ${r}: ${t} → ${i}.`,spoken:`${capitalised(numberToWords(e))} is ${numberToWords(r)} and ${numberToWords(a)}. Add the ${numberToWords(r)} first: ${numberToWords(t)} jumps to ${numberToWords(i)}.`},{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:[m,f],unknown:l},animation:"add_ones",caption:`Then add ${a}: ${i} → ${l}.`,spoken:`Now add the ${numberToWords(a)}. ${capitalised(numberToWords(i))} jumps to ${numberToWords(l)}. So ${numberToWords(t)} plus ${numberToWords(e)} is ${numberToWords(l)}.`}]})}function buildCountOn(n,t,e,s,o,r){const a=e-t;
71if(t<10||t>88||a<10||a>88)return null;const i=t%10,l=Math.floor(a/10)*10,u=a%10;if(i+u>=10||u<1)return null;const d=t+l;if(a!==answerInt(n))return null;const m=Math.floor(t/10)*10,h=Math.ceil(e/10)*10,f={from:t,to:d,label:`+${l}`},g={from:d,to:e,label:`+${u}`},b={start:t,target:e,jumpTens:l,jumpOnes:u,mid:d,answer:a};return buildExample({generatorId:asP4M04Generator.id,question:n,title:s,computed:b,steps:[{visual_data:{type:"number_line",min:m,max:h,marked:[t],hops:[],unknown:null},animation:"show_start",caption:`Start at ${t}. Count on to ${e}.`,spoken:r??`${o?`${o} `:""}${capitalised(numberToWords(t))} plus what makes ${numberToWords(e)}? Start at ${numberToWords(t)} and count on.`},{visual_data:{type:"number_line",min:m,max:h,marked:[t],hops:[f],unknown:null},animation:"add_tens",caption:`Add the tens: ${t} → ${d}.`,spoken:`Count on the tens first: ${numberToWords(t)} jumps to ${numberToWords(d)}, a jump of ${numberToWords(l)}.`},{visual_data:{type:"number_line",min:m,max:h,marked:[t],hops:[f,g],unknown:null},animation:"add_ones",caption:`Then ${u} more: ${t} + ${a} = ${e}.`,spoken:`Then ${numberToWords(u)} more reaches ${numberToWords(e)}. The whole jump is ${numberToWords(a)}, so the missing number is ${numberToWords(a)}.`}]})}function buildThreeAddends(n,t,e,s,o){if(t<1||e<1||s<1)return null;const r=t+e,a=t+e+s;if(t>88||a>99||a!==answerInt(n))return null;const i=Math.floor(t/10)*10,l=Math.ceil(a/10)*10,u={from:t,to:r,label:`+${e}`},d={from:r,to:a,label:`+${s}`},m={x:t,y:e,z:s,s1:r,total:a,answer:a};return buildExample({generatorId:asP4M04Generator.id,question:n,title:o,computed:m,steps:[{visual_data:{type:"number_line",min:i,max:l,marked:[t],hops:[],unknown:null},animation:"show_start",caption:`Start at ${t}. Add them one at a time.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(e)} plus ${numberToWords(s)}. Start at ${numberToWords(t)} and add them one at a time.`},{visual_data:{type:"number_line",min:i,max:l,marked:[t],hops:[u],unknown:null},animation:"add_tens",caption:`Add ${e}: ${t} → ${r}.`,spoken:`Add ${numberToWords(e)}: ${numberToWords(t)} jumps to ${numberToWords(r)}.`},{visual_data:{type:"number_line",min:i,max:l,marked:[t],hops:[u,d],unknown:a},animation:"add_ones",caption:`Then add ${s}: ${r} → ${a}.`,spoken:`Then add ${numberToWords(s)}: ${numberToWords(r)} jumps to ${numberToWords(a)}. So the total is ${numberToWords(a)}.`}]})}const WHICH_SUM_LARGER=/^Which sum is larger: (\d+) \+ (\d+) or (\d+) \+ (\d+)\?$/,IS_SAME_SUM=/^Is (\d+) \+ (\d+) the same as (\d+) \+ (\d+)\?$/;function buildSumComparison(n,t,e,s,o,r){if([t,e,s,o].some(S=>S<1||S>99))return null;const a=t+e,i=s+o,l=`${t} + ${e}`,u=`${s} + ${o}`,d=Math.floor(e/10)*10,m=e%10,h=t+d,f=Math.floor(o/10)*10,g=o%10,b=s+f,x=m===0&&g===0?`${l} = ${a}. ${u} = ${i}.`:`${t}+${d}=${h}, ${h}+${m}=${a}. ${s}+${f}=${b}, ${b}+${g}=${i}.`,y=m===0&&g===0?`${capitalised(numberToWords(t))} plus ${numberToWords(e)} is ${numberToWords(a)}. ${capitalised(numberToWords(s))} plus ${numberToWords(o)} is ${numberToWords(i)}.`:`${capitalised(numberToWords(t))} plus ${numberToWords(d)} is ${numberToWords(h)}, plus ${numberToWords(m)} is ${numberToWords(a)}. ${capitalised(numberToWords(s))} plus ${numberToWords(f)} is ${numberToWords(b)}, plus ${numberToWords(g)} is ${numberToWords(i)}.`;let E,k,T;if(r==="larger")if(a===i){if(!/equal/i.test(n.correctAnswer))return null;T="=",E=`${a} = ${i}. They are equal.`,k=`Both sums make ${numberToWords(a)}, so they are equal.`}else{const S=a>i?l:u;if(S!==n.correctAnswer)return null;T=a>i?">":"<",E=`${a} and ${i}: ${S} is larger.`,k=`${capitalised(numberToWords(Math.max(a,i)))} is more than ${numberToWords(Math.min(a,i))}, so the ${a>i?"first":"second"} sum is the larger one.`}else{if(a!==i||!/^Yes/i.test(n.correctAnswer)&&!/\bsame\b/i.test(n.correctAnswer))return null;T="=",E=`Both make ${a}. Yes, the same.`,k=`Both sums make ${numberToWords(a)}, so yes — they are the same amount.`}const A={t1:a,t2:i,a2Tens:d,a2Ones:m,mid1:h,b2Tens:f,b2Ones:g,mid2:b,answer:a},N=r==="larger"?"Which sum is larger?":"Are the sums the same?";return buildExample({generatorId:asP4M04Generator.id,question:n,title:N,computed:A,steps:[{visual_data:{type:"equation_display",terms:[l,"?",u]},animation:"compare_show",caption:r==="larger"?"Compare the two sums.":"Are these the same?",spoken:`We have ${numberToWords(t)} plus ${numberToWords(e)} and ${numberToWords(s)} plus ${numberToWords(o)}. Work out each total first.`},{visual_data:{type:"equation_display",terms:[a,"?",i]},animation:"compare_totals",caption:x,spoken:y},{visual_data:{type:"equation_display",terms:[a,T,i]},animation:"compare_result",caption:E,spoken:k}]})}const asP4M04Generator={id:"AS.P4.M04.add-tens-then-ones.v1",microSkillId:"AS.P4.M04",forms:["symbolic_calculation","addition","symbolic_choice","varied_symbolic_calculation","missing_addend","equation_completion","context_calculation","comparison_statement"],generate(n){const t=normaliseStem(n.stem),e=matchInts(t,WHICH_SUM_LARGER);if(e)return buildSumComparison(n,e[0],e[1],e[2],e[3],"larger");const s=matchInts(t,IS_SAME_SUM);if(s)return buildSumComparison(n,s[0],s[1],s[2],s[3],"same");const o=matchInts(t,ADD_EQ$3)??matchInts(t,ADD_WHAT$1)??matchInts(t,ADD_REV)??matchInts(t,START_JUMP)??matchInts(t,BEADS)??matchInts(t,BUS)??matchInts(t,SAM_SCORES);if(o){const[u,d]=o;return buildAddTensThenOnes(n,u,d,`How do we work out ${u} + ${d}?`)}
71const r=matchInts(t,MISSING_RIGHT$1);if(r){const[u,d]=r;return buildCountOn(n,u,d,`How do we work out ${u} + __ = ${d}?`)}const a=matchInts(t,MISSING_LEFT$1);if(a){const[u,d]=a,m=`Something plus ${numberToWords(u)} makes ${numberToWords(d)}. We can add in any order, so start at ${numberToWords(u)} and count on.`;return buildCountOn(n,u,d,`How do we work out __ + ${u} = ${d}?`,void 0,m)}const i=matchInts(t,BALANCE);if(i){const[u,d,m]=i,h=u+d,f=`Both sides must be equal. ${capitalised(numberToWords(u))} plus ${numberToWords(d)} is ${numberToWords(h)}.`;return buildCountOn(n,m,h,`How do we balance ${u} + ${d} = ${m} + __?`,f)}const l=matchInts(t,THREE_ADD);if(l){const[u,d,m]=l;return buildThreeAddends(n,u,d,m,`How do we work out ${u} + ${d} + ${m}?`)}return null}},SUB_EQ$2=/^(\d+) - (\d+) = __$/,SUB_CHOICE=/^(\d+) - (\d+) = \?$/,COUNT_BACK=/^Start at (\d+) and count back (\d+)$/,SUB_REVERSED=/^__ = (\d+) - (\d+)$/,WHICH_EQUALS=/^Which equals (\d+) - (\d+)\?$/,MISSING_SUBTRAHEND=/^(\d+) - __ = (\d+)$/,MISSING_MINUEND=/^__ - (\d+) = (\d+)$/,WORD_GIVES_AWAY=/^[A-Z][a-z]+ has (\d+) [a-z]+\. (?:He|She|They) gives away (\d+)(?:\. How many [a-z]+ are left\?)?$/,WORD_POURED_OUT=/^A [a-z]+ holds (\d+) ml\. (\d+) ml is poured out(?:\. How much is left\?)?$/,WORD_DRIVE_AWAY=/^There are (\d+) [a-z]+\. (\d+) drive away(?:\. How many are left\?)?$/,WORD_SHAPES=[WORD_GIVES_AWAY,WORD_POURED_OUT,WORD_DRIVE_AWAY];function gapToNextTen$1(n){return(10-n%10)%10}function buildBridgeBack(n,t,e,s,o,r,a){if(e<11||e>98||s<1||s>9)return null;const i=e%10;if(i<1||i>=s)return null;const l=e-i,u=i,d=s-u;if(e-s!==o)return null;const m=Math.min(l-5,o-2,e-2),h=Math.max(l+5,o+2,e+2),f={from:e,to:l,label:`−${u}`},g={from:l,to:o,label:`−${d}`},b={a:e,b:s,onesDigit:i,tenBelow:l,hop1:u,hop2:d,result:o,answer:o},x=`Start at ${e}. Count back to the ten.`,y=`${capitalised(numberToWords(e))} minus ${numberToWords(s)}. We start at ${numberToWords(e)} and count back to the ten first.`,E=`Back to ${l}: that's ${u}.`,k=`${capitalised(numberToWords(s))} is ${numberToWords(u)} and ${numberToWords(d)}. Jump back ${numberToWords(u)} to ${numberToWords(l)}, the ten below.`,T=`Then back ${d} more: ${l} → ${o}.`,A=`We still have ${numberToWords(d)} left to take away, so we jump back ${numberToWords(d)} more, from ${numberToWords(l)} to ${numberToWords(o)}. So ${numberToWords(e)} minus ${numberToWords(s)} is ${numberToWords(o)}.`;return buildExample({generatorId:t,question:n,title:r,computed:b,steps:[{visual_data:{type:"number_line",min:m,max:h,marked:[e],hops:[],unknown:null},animation:"show_start",caption:x,spoken:y},{visual_data:{type:"number_line",min:m,max:h,marked:[e],hops:[f],unknown:null},animation:"back_to_ten",caption:E,spoken:k},{visual_data:{type:"number_line",min:m,max:h,marked:[e],hops:[f,g],unknown:o},animation:"back_rest",caption:T,spoken:A}]})}function buildAddBack(n,t,e,s,o,r){if(e<0||e>99)return null;const a=gapToNextTen$1(e);if(a<1||a>9||s<=a)return null;const i=s-a,l=e+a;if(l+i!==o||o!==answerInt(n))return null;const u=Math.min(e,l,o)-2,d=Math.max(e,l,o)+2,m={from:e,to:l,label:`+${a}`},h={from:l,to:o,label:`+${i}`};return buildExample({generatorId:t,question:n,title:r,computed:{c:e,b:s,gap:a,rest:i,nextTen:l,missing:o,answer:o},steps:[{visual_data:{type:"number_line",min:u,max:d,marked:[e],hops:[],unknown:null},animation:"show_start",caption:`Start at ${e} and count on ${s} to find the missing number.`,spoken:`Something minus ${numberToWords(s)} makes ${numberToWords(e)}. We start at ${numberToWords(e)} and count on to find it.`},{visual_data:{type:"number_line",min:u,max:d,marked:[e],hops:[m],unknown:null},animation:"bridge_to_ten",caption:`${e} + ${a} = ${l}. That's ${a} so far.`,spoken:`Counting on to a ten first makes it easy. ${capitalised(numberToWords(e))} plus ${numberToWords(a)} makes ${numberToWords(l)}. That's ${numberToWords(a)} counted so far.`},{visual_data:{type:"number_line",min:u,max:d,marked:[e],hops:[m,h],unknown:o},animation:"land",caption:`${l} + ${i} = ${o}. So the missing number is ${o}.`,spoken:`Now count on the rest: ${numberToWords(l)} plus ${numberToWords(i)} makes ${numberToWords(o)}. So the missing number is ${numberToWords(o)}.`}]})}
71const asP4M05Generator={id:"AS.P4.M05.bridge-back-ten.v1",microSkillId:"AS.P4.M05",forms:["symbolic_calculation","symbolic_choice","varied_symbolic_calculation","missing_number_bridging","missing_addend","equation_completion","subtraction_story_context"],generate(n){const t=normaliseStem(n.stem),e=[[SUB_EQ$2,(s,o)=>`How do we work out ${s} − ${o}?`],[SUB_CHOICE,(s,o)=>`How do we work out ${s} − ${o}?`],[COUNT_BACK,(s,o)=>`How do we work out ${s} − ${o}?`],[SUB_REVERSED,(s,o)=>`How do we work out ${s} − ${o}?`],[WHICH_EQUALS,(s,o)=>`How do we work out ${s} − ${o}?`]];for(const[s,o]of e){const r=matchInts(t,s);if(!r)continue;const[a,i]=r,l=a-i;return l!==answerInt(n)?null:buildBridgeBack(n,this.id,a,i,l,o(a,i))}for(const s of WORD_SHAPES){const o=matchInts(t,s);if(!o)continue;const[r,a]=o,i=r-a;return i!==answerInt(n)?null:buildBridgeBack(n,this.id,r,a,i,`How do we work out ${r} − ${a}?`)}{const s=matchInts(t,MISSING_SUBTRAHEND);if(s){const[o,r]=s;return o-r<1||o-r>9?null:buildCountOnGap({generatorId:this.id,q:n,high:o,low:r,expected:answerInt(n),title:`Missing number: ${o} − __ = ${r}.`})}}{const s=matchInts(t,MISSING_MINUEND);if(s){const[o,r]=s,a=r+o;return a!==answerInt(n)?null:buildAddBack(n,this.id,r,o,a,`Missing number: __ − ${o} = ${r}.`)}}return null}},ADD_EQ$2=/^(\d+) \+ (\d+) = (?:__|\?)$/,ADD_EQ_REV=/^(?:__|\?) = (\d+) \+ (\d+)$/,ADD_EQ_TRIPLE=/^(\d+) \+ (\d+) \+ (\d+) = (?:__|\?)$/,ADD_STORY_MON_TUE=/^[A-Z][a-z]+ collects (\d+) [a-z]+ on Monday and (\d+) more on Tuesday\. How many [a-z]+ does (?:he|she) have altogether\?$/,ADD_CLAIM$1=/^[A-Z][a-z]+ says (\d+) \+ (\d+) = (\d+)\. Is (?:he|she) right\?$/,ADD_MISSING_ADDEND=/^(\d+) \+ __ = (\d+)$/;function parseAddends(n){const t=matchInts(n,ADD_EQ$2);if(t)return[t[0],t[1]];const e=matchInts(n,ADD_EQ_REV);return e?[e[0],e[1]]:null}function parseTripleWithZero(n){const t=matchInts(n,ADD_EQ_TRIPLE);if(!t)return null;const e=t.filter(a=>a!==0);if(e.length!==2)return null;const[s,o]=e,r=t.join(" + ");return{a:s,b:o,note:{caption:`Adding 0: ${r} = ${s} + ${o}.`,spoken:`Adding zero changes nothing, so ${numberToWords(t[0])} plus ${numberToWords(t[1])} plus ${numberToWords(t[2])} is the same as ${numberToWords(s)} plus ${numberToWords(o)}.`}}}function buildMakeTen(n,t,e,s){if(t<10||t>99||e<10||e>99||t%10+e%10<10)return null;const o=10-t%10,r=e-o,a=t+o,i=t+e;if(i!==answerInt(n))return null;const l={a:t,b:e,step1Add:o,step2Add:r,step1Result:a,total:i,answer:i},u={type:"two_step",start:t};return buildExample({generatorId:asP4M06Generator.id,question:n,title:`How do we work out ${t} + ${e}?`,computed:l,steps:[{visual_data:{...u,step1_add:o,step1_result:null,step2_add:r,final_result:null},animation:"show_split",caption:`${s?`${s.caption} `:""}Split ${e} into ${o} + ${r} to reach ${a}.`,spoken:`${s?`${s.spoken} `:`${capitalised(numberToWords(t))} plus ${numberToWords(e)}. `}We split ${numberToWords(e)} into ${numberToWords(o)} and ${numberToWords(r)} to reach the next ten.`},{visual_data:{...u,step1_add:o,step1_result:a,step2_add:r,final_result:null},animation:"bridge",caption:`${t} + ${o} = ${a}.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(o)} equals ${numberToWords(a)}. We have reached the ten.`},{visual_data:{...u,step1_add:o,step1_result:a,step2_add:r,final_result:i},animation:"add_rest",caption:`${a} + ${r} = ${i}. So ${t} + ${e} = ${i}.`,spoken:`Now ${numberToWords(a)} plus ${numberToWords(r)} equals ${numberToWords(i)}. So ${numberToWords(t)} plus ${numberToWords(e)} is ${numberToWords(i)}.`}]})}function buildMakeTenClaimCheck(n,t,e,s){if(t<10||t>99||e<10||e>99||t%10+e%10<10)return null;const o=10-t%10,r=e-o,a=t+o,i=t+e;if((i===s?"Yes":"No")!==n.correctAnswer.trim())return null;const u={a:t,b:e,step1Add:o,step2Add:r,step1Result:a,total:i,stated:s},d={type:"two_step",start:t};return buildExample({generatorId:asP4M06Generator.id,question:n,title:`Is ${t} + ${e} = ${s} correct?`,computed:u,steps:[{visual_data:{...d,step1_add:o,step1_result:null,step2_add:r,final_result:null},animation:"show_split",caption:`Someone says ${t} + ${e} = ${s}
71. Let's check.`,spoken:`Someone says ${numberToWords(t)} plus ${numberToWords(e)} equals ${numberToWords(s)}. Let's work it out ourselves first. We split ${numberToWords(e)} into ${numberToWords(o)} and ${numberToWords(r)} to reach the next ten.`},{visual_data:{...d,step1_add:o,step1_result:a,step2_add:r,final_result:null},animation:"bridge",caption:`${t} + ${o} = ${a}.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(o)} equals ${numberToWords(a)}. We have reached the ten.`},{visual_data:{...d,step1_add:o,step1_result:a,step2_add:r,final_result:i},animation:"add_rest",caption:i===s?`${a} + ${r} = ${i}. So ${t} + ${e} = ${i}. That's right.`:`${a} + ${r} = ${i}, not ${s}. So ${t} + ${e} = ${i}. That's wrong.`,spoken:i===s?`Now ${numberToWords(a)} plus ${numberToWords(r)} equals ${numberToWords(i)}. So ${numberToWords(t)} plus ${numberToWords(e)} really is ${numberToWords(i)}. The claim is right.`:`Now ${numberToWords(a)} plus ${numberToWords(r)} equals ${numberToWords(i)}, not ${numberToWords(s)}. So ${numberToWords(t)} plus ${numberToWords(e)} is ${numberToWords(i)}. The claim is wrong.`}]})}function buildMissingAddend(n,t,e){if(t<10||t>99||e<10||e>99)return null;const s=e-t;if(s<10||s>99||t%10+s%10<10)return null;const o=10-t%10,r=s-o;if(r<0)return null;const a=t+o;if(s!==answerInt(n))return null;const i=Math.min(t,e)-2,l=Math.max(t,e)+2,u={from:t,to:a,label:`+${o}`},d={from:a,to:e,label:`+${r}`},m={a:t,total:e,missing:s,step1Add:o,step2Add:r,step1Result:a,answer:s};return buildExample({generatorId:asP4M06Generator.id,question:n,title:`What number added to ${t} makes ${e}?`,computed:m,steps:[{visual_data:{type:"number_line",min:i,max:l,marked:[t],hops:[],unknown:null},animation:"show_split",caption:`Count on from ${t} to ${e}.`,spoken:`${capitalised(numberToWords(t))} plus something makes ${numberToWords(e)}. We count on from ${numberToWords(t)} to find it.`},{visual_data:{type:"number_line",min:i,max:l,marked:[t],hops:[u],unknown:null},animation:"bridge",caption:`${t} + ${o} = ${a}. That's ${o} so far.`,spoken:`${capitalised(numberToWords(t))} plus ${numberToWords(o)} makes ${numberToWords(a)}. That's ${numberToWords(o)} counted so far.`},{visual_data:{type:"number_line",min:i,max:l,marked:[t],hops:[u,d],unknown:null},animation:"add_rest",caption:`${a} + ${r} = ${e}. ${o} + ${r} = ${s}.`,spoken:`${capitalised(numberToWords(a))} plus ${numberToWords(r)} makes ${numberToWords(e)}. ${capitalised(numberToWords(o))} and ${numberToWords(r)} make ${numberToWords(s)}. The missing number is ${numberToWords(s)}.`}]})}function plainSum(n,t,e,s){if(n.visualData?.type!=="two_step"){const o=buildColumnAdd({generatorId:asP4M06Generator.id,q:n,a:t,b:e,expected:answerInt(n),lead:s});if(o)return o}return buildMakeTen(n,t,e,s)}const tensOf=n=>n-n%10;function buildNearDouble(n,t,e){if(Math.abs(t-e)!==1)return null;const s=Math.min(t,e),o=s*2,r=t+e;if(s<10||r>198||r!==answerInt(n))return null;const a={type:"two_step",start:s,step1_add:s,step2_add:1},i=numberToWords;return buildExample({generatorId:asP4M06Generator.id,question:n,title:`How do we work out ${t} + ${e}?`,computed:{a:t,b:e,small:s,double:o,one:1,total:r,answer:r,tens:tensOf(s),tensDouble:2*tensOf(s),ones:s%10,onesDouble:2*(s%10)},steps:[{visual_data:{...a,step1_result:null,final_result:null},animation:"show_split",caption:`${t} and ${e} are nearly the same — a near double.`,spoken:`${capitalised(i(t))} plus ${i(e)}. These two numbers are only one apart, so we can use a double.`},{visual_data:{...a,step1_result:o,final_result:null},animation:"bridge",caption:s%10*2>=10?`Double ${s}: ${tensOf(s)} + ${tensOf(s)} = ${2*tensOf(s)}, ${s%10} + ${s%10} = ${2*(s%10)}, so ${o}.`:`Double ${s}: ${s} + ${s} = ${o}.`,spoken:s%10*2>=10?`Double ${i(s)}: double ${i(tensOf(s))} is ${i(2*tensOf(s))}, and double ${i(s%10)} is ${i(2*(s%10))}. ${capitalised(i(2*tensOf(s)))} and ${i(2*(s%10))} make ${i(o)}.`:`Double ${i(s)} is ${i(o)}: ${i(s)} and ${i(s)} make ${i(o)}.`},{visual_data:{...a,step1_result:o,final_result:r},animation:"add_rest",caption:`Then 1 more for the extra: ${o} + 1 = ${r}.`,spoken:`${capitalised(i(Math.max(t,e)))} is one more than ${i(s)}, so we add one more: ${i(o)} and one make ${i(r)}. So ${i(t)} plus ${i(e)} is ${i(r)}.`}]})}const asP4M06Generator={id:"AS.P4.M06.make-ten.v1",microSkillId:"AS.P4.M06",forms:["symbolic_calculation","equation_completion","symbolic_choice","near_double","varied_symbolic_calculation","context_calculation","check_reasoning_probe","missing_addend"],generate(n){const t=parseAddends(n.stem);if(t){if(n.mathematicalForm==="near_double"){const a=buildNearDouble(n,t[0],t[1]);if(a)return a}return plainSum(n,t[0],t[1])}const e=parseTripleWithZero(n.stem);if(e)return plainSum(n,e.a,e.b,e.note);const s=matchInts(n.stem,ADD_STORY_MON_TUE);if(s)return plainSum(n,s[0],s[1]);const o=matchInts(n.stem,ADD_CLAIM$1);if(o)return buildMakeTenClaimCheck(n,o[0],o[1],o[2]);const r=matchInts(n.stem,ADD_MISSING_ADDEND);return r?buildMissingAddend(n,r[0],r[1]):null}},GENERATOR_ID$7="AS.P4.M07.bridge-back-ten.v1",SUB_EQ$1=/^(\d+) - (\d+) = __$/,SUB_Q=/^(\d+) - (\d+) = \?$/,SUB_WHICH=/^Which equals (\d+) - (\d+)\?$/,SUB_REV=/^__ = (\d+) - (\d+)$/,MISS_SUB=/^(\d+) - __ = (\d+)$/,MISS_MIN=/^__ - (\d+) = (\d+)$/,WORD_STICKERS=/^\w+ had (\d+) stickers\. \w+ gave away (\d+)(?:\. How many are left\?)?$/,WORD_BUS=/^A bus held (\d+) people\. (\d+) got off(?:\. How many are still on\?)?$/;function gapToNextTen(n){return(10-n%10)%10}function lineBounds(n,t){return{min:Math.min(t,n)-3,max:Math.max(t,n)+3}}function bridgeBackDemo(n,t,e){if(t<10||t>99||e<10||e>99||e>=t)return null;const s=t-e;if(s!==answerInt(n))return null;const o=t%10,r=e%10,a=`How do we work out ${t} − ${e}?`;if(o>=1&&o<r){const i=t-o,l=e-o,u={a:t,b:e,onesA:o,rest:l,tenBoundary:i,result:s,answer:s};let d,m;if(l>=10){const h=l-l%10,f=l%10,g=i-h;
71u.tensPart=h,u.onesPart=f,u.mid=g,d=`${i} − ${h} = ${g}, then ${g} − ${f} = ${s}. So ${t} − ${e} = ${s}.`,m=`Now ${numberToWords(i)} minus ${numberToWords(h)} is ${numberToWords(g)}, then minus ${numberToWords(f)} more is ${numberToWords(s)}. So ${numberToWords(t)} minus ${numberToWords(e)} is ${numberToWords(s)}.`}else d=`${i} − ${l} = ${s}. So ${t} − ${e} = ${s}.`,m=`Now ${numberToWords(i)} minus ${numberToWords(l)} equals ${numberToWords(s)}. So ${numberToWords(t)} minus ${numberToWords(e)} is ${numberToWords(s)}.`;return buildExample({generatorId:GENERATOR_ID$7,question:n,title:a,computed:u,steps:[{visual_data:{type:"two_step",start:t,step1_add:-o,step1_result:null,step2_add:-l,final_result:null},animation:"show_split",caption:`Split ${e} into ${o} + ${l} to reach ${i}.`,spoken:`${capitalised(numberToWords(t))} minus ${numberToWords(e)}. We split ${numberToWords(e)} into ${numberToWords(o)} and ${numberToWords(l)} to step back to the ten.`},{visual_data:{type:"two_step",start:t,step1_add:-o,step1_result:i,step2_add:-l,final_result:null},animation:"bridge",caption:`${t} − ${o} = ${i}.`,spoken:`${capitalised(numberToWords(t))} minus ${numberToWords(o)} equals ${numberToWords(i)}. We have reached the ten.`},{visual_data:{type:"two_step",start:t,step1_add:-o,step1_result:i,step2_add:-l,final_result:s},animation:"sub_rest",caption:d,spoken:m}]})}return null}function missingMinuendDemo(n,t,e){if(t<10||t>99||e<0)return null;const s=gapToNextTen(e);if(s<1||s>9||t<=s)return null;const o=t-s,r=e+s,a=r+o;if(a<10||a>99||a!==answerInt(n))return null;const{min:i,max:l}=lineBounds(a,e),u={from:e,to:r,label:`+${s}`},d={from:r,to:a,label:`+${o}`},m={b:t,target:e,gap:s,rest:o,nextTen:r,missing:a,answer:a};return buildExample({generatorId:GENERATOR_ID$7,question:n,title:`Missing number: __ − ${t} = ${e}.`,computed:m,steps:[{visual_data:{type:"number_line",min:i,max:l,marked:[e],hops:[],unknown:null},animation:"show_start",caption:`Start at ${e} and count on ${t} to find the missing number.`,spoken:`Something minus ${numberToWords(t)} makes ${numberToWords(e)}. We start at ${numberToWords(e)} and count on to find it.`},{visual_data:{type:"number_line",min:i,max:l,marked:[e],hops:[u],unknown:null},animation:"bridge",caption:`${e} + ${s} = ${r}. That's ${s} so far.`,spoken:`Counting on to a ten first makes it easy. ${capitalised(numberToWords(e))} plus ${numberToWords(s)} makes ${numberToWords(r)}. That's ${numberToWords(s)} counted so far.`},{visual_data:{type:"number_line",min:i,max:l,marked:[e],hops:[u,d],unknown:a},animation:"land",caption:`${r} + ${o} = ${a}. So the missing number is ${a}.`,spoken:`Now count on the rest: ${numberToWords(r)} plus ${numberToWords(o)} makes ${numberToWords(a)}. So the missing number is ${numberToWords(a)}.`}]})}function plainDifference(n,t,e,s){return n.visualData?.type==="two_step"?bridgeBackDemo(n,t,e):buildColumnSubtract({generatorId:GENERATOR_ID$7,q:n,a:t,b:e,expected:answerInt(n),story:s})}const asP4M07Generator={id:GENERATOR_ID$7,microSkillId:"AS.P4.M07",forms:["symbolic_calculation","symbolic_choice","varied_symbolic_calculation","equation_completion","missing_addend","context_calculation"],generate(n){const t=normaliseStem(n.stem);let e;if((e=t.match(SUB_EQ$1))||(e=t.match(SUB_Q))||(e=t.match(SUB_WHICH))||(e=t.match(SUB_REV)))return plainDifference(n,Number(e[1]),Number(e[2]));if(e=t.match(MISS_SUB)){const s=Number(e[1]),o=Number(e[2]);return buildCountOnGap({generatorId:GENERATOR_ID$7,q:n,high:s,low:o,expected:answerInt(n),title:`Missing number: ${s} − __ = ${o}.`})}if(e=t.match(MISS_MIN))return missingMinuendDemo(n,Number(e[1]),Number(e[2]));if(e=t.match(WORD_STICKERS)){const s=Number(e[1])-Number(e[2]);return plainDifference(n,Number(e[1]),Number(e[2]),{spoken:`So ${s>=0&&s<=999?numberToWords(s):""} stickers are left.`})}if(e=t.match(WORD_BUS)){const s=Number(e[1])-Number(e[2]);return plainDifference(n,Number(e[1]),Number(e[2]),{spoken:`So ${s>=0&&s<=999?numberToWords(s):""} people are still on the bus.`})}return null}},DIFF_BETWEEN=/^What is the difference between (\d+) and (\d+)\?$/,HOW_MANY_MORE=/^How many more is (\d+) than (\d+)\?$/,HOW_MANY_FEWER=/^How many fewer is (\d+) than (\d+)\?$/,HOW_MUCH_LONGER=/^How much longer is (\d+) [a-z]+ than (\d+) [a-z]+\?$/,HOW_MUCH_TALLER=/^How much taller is (\d+) [a-z]+ than (\d+) [a-z]+\?$/,HOW_MUCH_HEAVIER=/^How much heavier is (\d+) [a-z]+ than (\d+) [a-z]+\?$/,WP_HAS_DIFFERENCE=/^[A-Z][a-z]+ has (\d+) [a-z]+, [A-Z][a-z]+ has (\d+)\. Difference\?$/,WP_HAS_HOW_MANY_MORE=/^[A-Z][a-z]+ has (\d+) [a-z]+, [A-Z][a-z]+ has (\d+)\. How many more has [A-Z][a-z]+\?$/,WP_IS_HOW_MUCH_OLDER=/^[A-Z][a-z]+ is (\d+), [A-Z][a-z]+ is (\d+)\. How much older is [A-Z][a-z]+\?$/,WP_WEIGHS_HOW_MUCH_HEAVIER=/^A [a-z]+ weighs (\d+) [a-z]+, a [a-z]+ (\d+) [a-z]+\. How much heavier(?: is the [a-z]+)?\?$/,WP_IS_HOW_MUCH_TALLER=/^A [a-z]+ is (\d+) [a-z]+, a [a-z]+ (\d+) [a-z]+\. How much taller(?: is the [a-z]+)?\?$/,WP_HOLDS_HOW_MANY_MORE_FIT=/^A [a-z]+ holds (\d+), a [a-z]+ (\d+)\. How many more fit(?: on the [a-z]+)?\?$/,WP_READ_HOW_MANY_MORE=/^[A-Z][a-z]+ read (\d+) [a-z]+, [A-Z][a-z]+ (\d+)\. How many more did [A-Z][a-z]+ read\?$/,WP_SCORED_HOW_MANY_MORE=/^[A-Z][a-z]+ scored (\d+), [A-Z][a-z]+ scored (\d+)\. How many more did [A-Z][a-z]+ score\?$/,DIFF_SHAPES=[DIFF_BETWEEN,HOW_MANY_MORE,HOW_MANY_FEWER,HOW_MUCH_LONGER,HOW_MUCH_TALLER,HOW_MUCH_HEAVIER,WP_HAS_DIFFERENCE,WP_HAS_HOW_MANY_MORE,WP_IS_HOW_MUCH_OLDER,WP_WEIGHS_HOW_MUCH_HEAVIER,WP_IS_HOW_MUCH_TALLER,WP_HOLDS_HOW_MANY_MORE_FIT,WP_READ_HOW_MANY_MORE,WP_SCORED_HOW_MANY_MORE],WHICH_SUM_DIFFERENCE=/^Which sum finds the difference between (\d+) and (\d+)\?$/,WHICH_SUM_HOW_MUCH_MORE=/^Which sum finds how much more (\d+) is than (\d+)\?$/,WHICH_SUM_SHAPES=[WHICH_SUM_DIFFERENCE,WHICH_SUM_HOW_MUCH_MORE],DIFF_CLAIM=/^[A-Z][a-z]+ says the difference between (\d+) and (\d+) is (\d+)\. Is (?:he|she) right\?$/;function parseDiffPair(n){for(const t of DIFF_SHAPES){const e=matchInts(n,t);if(e)return[e[0],e[1]]}return null}function parseWhichSumPair(n){for(const t of WHICH_SUM_SHAPES){const e=matchInts(n,t);if(e)return[e[0],e[1]]}return null}function normaliseSubtraction(n){return n.replace(/−/g,"-").replace(/\s+/g," ").trim()}function computeDiffHops(n,t){const e=(Math.floor(n/10)+1)*10,s=e<t,o=s?e-n:t-n,r=s?t-e:0,a=o+r,i=n-2,l=t+2;return{nextTen:e,bridges:s,hop1:o,hop2:r,diff:a,min:i,max:l}}function buildDiffClaimCheck(n,t,e,s){const o=Math.min(t,e),r=Math.max(t,e);if(o<10||r>99)return null;const{nextTen:a,bridges:i,hop1:l,hop2:u,diff:d,min:m,max:h}=computeDiffHops(o,r);if((d===s?"Yes":"No")!==n.correctAnswer.trim())return null;const g={from:o,to:a,label:`+${l}`},b={from:a,to:r,label:`+${u}`},x={from:o,to:r,label:`+${d}`},y=i?[g,b]:[x],E={smaller:o,larger:r,nextTen:a,hop1:l,hop2:u,diff:d,stated:s},k=i?`Count on: ${o} → ${a} is ${l}, then ${a} → ${r} is ${u}.`:`Count on from ${o} to ${r}.`,T=i?`First we jump from ${numberToWords(o)} up to ${numberToWords(a)}, the next ten — that's ${numberToWords(l)}. Then from ${numberToWords(a)} to ${numberToWords(r)} is ${numberToWords(u)} more.`:`We count straight on from ${numberToWords(o)} to ${numberToWords(r)} — no ten to bridge.`;return buildExample({generatorId:asP4M08Generator.id,question:n,title:`Is the difference between ${r} and ${o} ${s}?`,computed:E,steps:[{visual_data:{type:"number_line",min:m,max:h,marked:[o,r],hops:[],unknown:null},animation:"show_points",caption:`Someone says the difference between ${r} and ${o} is ${s}
71. Let's check.`,spoken:`Someone says the difference between ${numberToWords(r)} and ${numberToWords(o)} is ${numberToWords(s)}. Let's work it out ourselves first, counting on from the smaller number.`},{visual_data:{type:"number_line",min:m,max:h,marked:[o],hops:y,unknown:null},animation:"hop_to_target",caption:k,spoken:T},{visual_data:{type:"number_line",min:m,max:h,marked:[o],hops:y,unknown:null},animation:"hop_to_target",caption:i?d===s?`${l} + ${u} = ${d}. The difference really is ${d}. That's right.`:`${l} + ${u} = ${d}, not ${s}. The real difference is ${d}. That's wrong.`:d===s?`${o} → ${r} is ${d}. The difference really is ${d}. That's right.`:`${o} → ${r} is ${d}, not ${s}. The real difference is ${d}. That's wrong.`,spoken:d===s?`Our counting on gives ${numberToWords(d)}, the same as the claim. The difference really is ${numberToWords(d)}, so the claim is right.`:`Our counting on gives ${numberToWords(d)}, not ${numberToWords(s)}. The real difference is ${numberToWords(d)}, so the claim is wrong.`}]})}const asP4M08Generator={id:"AS.P4.M08.count-on-difference.v1",microSkillId:"AS.P4.M08",forms:["comparison_statement","subtraction_story_context","context_reasoning_judgement","check_reasoning_probe"],generate(n){const t=matchInts(n.stem,DIFF_CLAIM);if(t)return buildDiffClaimCheck(n,t[0],t[1],t[2]);const e=normaliseStem(n.stem).match(BAR_DIFF);if(e&&n.visualData?.type==="measure_bars"){const[,L,P,F]=e,D=findBarByColour(n.visualData.bars,P),U=findBarByColour(n.visualData.bars,F);return!D||!U?null:buildBarDiffDemo(n,L,D,U)}const s=resolvePair$1(n);if(!s)return null;const{x:o,y:r,whichSum:a}=s,i=Math.min(o,r),l=Math.max(o,r);if(i<10||l>99)return null;const u=(Math.floor(i/10)+1)*10;if(u<=i)return null;const d=u<l,m=d?u-i:l-i,h=d?l-u:0,f=m+h,g=i-2,b=l+2,x={from:i,to:u,label:`+${m}`},y={from:u,to:l,label:`+${h}`},E={from:i,to:l,label:"?"},k={from:i,to:l,label:`+${f}`},T=d?[x,y]:[k],A=a?{smaller:i,larger:l,nextTen:u,hop1:m,hop2:h,diff:f}:{smaller:i,larger:l,nextTen:u,hop1:m,hop2:h,diff:f,answer:f};let N="",S="";if(d)if(h>=10&&h%10!==0){const L=h-h%10,P=h%10,F=u+L;A.tensPart=L,A.onesPart=P,A.midB=F,N=`${u} + ${L} = ${F}, then ${F} + ${P} = ${l}. So ${m} + ${h} = ${f}.`,S=`Then ${numberToWords(u)} plus ${numberToWords(L)} is ${numberToWords(F)}, then plus ${numberToWords(P)} more is ${numberToWords(l)}. ${capitalised(numberToWords(m))} and ${numberToWords(h)} make ${numberToWords(f)}. The difference is ${numberToWords(f)}.`}else N=`Then ${u} → ${l} is ${h}. So ${m} + ${h} = ${f}.`,S=`Then from ${numberToWords(u)} to ${numberToWords(l)} is ${numberToWords(h)} more. ${capitalised(numberToWords(m))} and ${numberToWords(h)} make ${numberToWords(f)}. The difference is ${numberToWords(f)}.`;const M=a?{visual_data:{type:"number_line",min:g,max:b,marked:[i],hops:T,unknown:null},animation:"hop_to_target",caption:`We counted on ${f} in all. Take away finds the same: ${l} − ${i}.`,spoken:`${capitalised(numberToWords(m))}${d?` and ${numberToWords(h)}`:""} make${d?"":"s"} ${numberToWords(f)} counted on. Take away finds the same amount, so the sum that finds it is ${numberToWords(l)} take away ${numberToWords(i)}.`}:d?{visual_data:{type:"number_line",min:g,max:b,marked:[i],hops:T,unknown:null},animation:"hop_to_target",caption:N,spoken:S}:{visual_data:{type:"number_line",min:g,max:b,marked:[i],hops:T,unknown:null},animation:"hop_to_target",caption:`${i} → ${l} is ${f}. The difference is ${f}.`,spoken:`That jump is ${numberToWords(f)} long, so the gap between the two numbers is ${numberToWords(f)}. The difference is ${numberToWords(f)}.`};return buildExample({generatorId:this.id,question:n,title:`What is the difference between ${l} and ${i}?`,computed:A,steps:[{visual_data:{type:"number_line",min:g,max:b,marked:[i,l],hops:[],unknown:null},animation:"show_points",caption:`Find ${i} and ${l} on the line.`,spoken:`Here is ${numberToWords(i)}, and here is ${numberToWords(l)} on the number line. We count on from the smaller number.`},d?{visual_data:{type:"number_line",min:g,max:b,marked:[i],hops:[x],unknown:null},animation:"hop_to_ten",caption:`Jump to the next ten: ${i} → ${u} is ${m}.`,spoken:`First we jump from ${numberToWords(i)} up to ${numberToWords(u)}, the next ten. That jump is ${numberToWords(m)}.`}:{visual_data:{type:"number_line",min:g,max:b,marked:[i],hops:[E],unknown:null},animation:"hop_to_ten",caption:`Count on from ${i} to ${l}.`,spoken:`We count straight on from ${numberToWords(i)} to ${numberToWords(l)} — no ten to bridge. How big is the jump?`},M]})}},BAR_DIFF=/^How much (longer|taller|heavier) is the ([a-z]+) (?:bar|ribbon) than the ([a-z]+) (?:bar|ribbon|one)\?$/;function findBarByColour(n,t){const e=n.filter(s=>s.label.toLowerCase()===t.toLowerCase());return e.length===1?e[0]:null}function buildBarDiffDemo(n,t,e,s){if(e.length<=s.length)return null;const o=e.length-s.length;if(o!==answerInt(n))return null;const r=[{label:e.label,length:e.length,color:e.color},{label:s.label,length:s.length,color:s.color}],a={subj:e.length,ref:s.length,diff:o,answer:o},i=numberToWords(e.length),l=numberToWords(s.length),u=numberToWords(o),{nextTen:d,bridges:m,hop1:h,hop2:f}=computeDiffHops(s.length,e.length);a.nextTen=d,a.hop1=h,a.hop2=f;const g=m?`From ${s.length}: +${h} to ${d}, +${f} to ${e.length}. ${h} + ${f} = ${o}.`:`From ${s.length}, count up ${o} to ${e.length}.`,b=m?`The difference is the part that sticks out past the other one. We count up from the ${s.label} end at ${l}: ${numberToWords(h)} to ${numberToWords(d)}, then ${numberToWords(f)} more to ${i}. ${capitalised(numberToWords(h))} and ${numberToWords(f)} make ${u}, so the ${e.label} one is ${u} ${t}.`:`The difference is the part that sticks out past the other one. We count up from the ${s.label} end at ${l} to ${i}: that's ${u}, so the ${e.label} one is ${u} ${t}.`;return buildExample({generatorId:asP4M08Generator.id,question:n,title:`How much ${t} is the ${e.label} than the ${s.label}?`,computed:a,steps:[{visual_data:{type:"measure_bars",bars:r,show_values:!1},animation:"show_object",caption:"Measure each one from the start.",spoken:"Two of them, measured from the same start. To read each one, follow it to where it ends."},{visual_data:{type:"measure_bars",bars:r,show_values:!0},animation:"count_cm",caption:`${e.label} reaches ${e.length}. ${s.label} reaches ${s.length}.`,spoken:`The ${e.label} one reaches ${i}. The ${s.label} one reaches ${l}.`},{visual_data:m?{type:"number_line",min:Math.floor(s.length/5)*5,max:Math.ceil(e.length/5)*5,step:5,marked:[s.length,e.length],hops:[{from:s.length,to:d,label:`+${h}`}
71,{from:d,to:e.length,label:`+${f}`}],unknown:null,conclude_label:`${h} + ${f} = ${o}`}:{type:"measure_bars",bars:r,show_values:!0,diff:o},animation:m?"count_on":"write_length",caption:g,spoken:b}]})}function resolvePair$1(n){const t=parseDiffPair(n.stem);if(t){const[s,o]=t,r=Math.max(s,o)-Math.min(s,o),a=answerInt(n);return a===null||r!==a?null:{x:s,y:o,whichSum:!1}}const e=parseWhichSumPair(n.stem);if(e){const[s,o]=e,r=Math.min(s,o),i=`${Math.max(s,o)} - ${r}`;return normaliseSubtraction(n.correctAnswer)!==i?null:{x:s,y:o,whichSum:!0}}return null}const BUILD_UP=/^(\d+) tens? and (\d+) ones? make (?:__|which number\?)$/,TENS_HIDDEN=/^(\d+) = __ tens and (\d+) ones?$/,ONES_HIDDEN=/^(\d+) = (\d+) tens? and __ ones?$/,HOW_MANY_TENS=/^How many tens are in (\d+)\?$/,WHICH_PARTITION=/^Which partition matches (\d+)\?$/;function tensLabel(n){return`${n} ten${n===1?"":"s"}`}function onesLabel(n){return`${n} one${n===1?"":"s"}`}function tensWords$1(n){return`${numberToWords(n)} ten${n===1?"":"s"}`}function onesWords$1(n){return`${numberToWords(n)} one${n===1?"":"s"}`}function parseAcceptedTensCount(n){const t=n.trim(),e=Number(t);if(Number.isInteger(e))return e;const s=t.match(/^(\d+)\s*tens?$/i);return s?Number(s[1]):null}const npvP4M01Generator={id:"NPV.P4.M01.tens-and-ones.v1",microSkillId:"NPV.P4.M01",forms:["ten_and_ones_to_numeral","numeral_to_ten_and_ones"],generate(n){let t;const e=matchInts(n.stem,BUILD_UP),s=e?null:matchInts(n.stem,TENS_HIDDEN),o=e||s?null:matchInts(n.stem,ONES_HIDDEN),r=e||s||o?null:matchInts(n.stem,HOW_MANY_TENS),a=e||s||o||r?null:matchInts(n.stem,WHICH_PARTITION);if(e){const[L,P]=e;t=10*L+P}else if(s)t=s[0];else if(o)t=o[0];else if(r)t=r[0];else if(a)t=a[0];else return null;if(t<10||t>99)return null;const i=Math.floor(t/10),l=t%10,u=i*10;if(a){const L=`${tensLabel(i)} and ${onesLabel(l)}`;if(n.correctAnswer.trim()!==L)return null}else{const L=e?t:s||r?i:l,P=r?parseAcceptedTensCount(n.correctAnswer):answerInt(n);if(L!==P)return null}const d=l*10+i,m={whole:t,tens:i,ones:l,tensPart:u,answer:a?i:e?t:s||r?i:l,...a?{trapWhole:d}:{}},h=l===0,f={type:"part_whole",whole:t,parts:[u,l]};if(e){const L={type:"part_whole",whole:u,parts:[u,null]},P={type:"part_whole",whole:u,parts:[u,l]},F={type:"part_whole",whole:t,parts:[u,l]};return buildExample({generatorId:this.id,question:n,title:`How do we build ${t} from tens and ones?`,computed:m,steps:[{visual_data:L,animation:"build_tens",caption:`${tensLabel(i)} make ${u}.`,spoken:`We start with the tens. Every ten is worth ten, so ${tensWords$1(i)} ${i===1?"makes":"make"} ${numberToWords(u)}.`},{visual_data:P,animation:"add_ones",caption:h?"And no extra ones.":`And ${onesLabel(l)}.`,spoken:h?"Now the ones. There are no extra ones to add on.":`Now the ones. Each one is worth just one, so we add on ${numberToWords(l)} more.`},{visual_data:F,animation:"join_whole",caption:`${u} and ${l} join to make ${t}.`,spoken:`Put them together: ${numberToWords(u)} and ${numberToWords(l)} make ${numberToWords(t)}. So ${tensWords$1(i)} and ${onesWords$1(l)} is ${numberToWords(t)}.`}]})}if(s&&!h){const L=Array.from({length:i},(P,F)=>numberToWords(10*(F+1))).join(", ");return buildExample({generatorId:this.id,question:n,title:`How many tens are in ${t}?`,computed:m,steps:[{visual_data:{type:"part_whole",whole:t,parts:[null,l]},animation:"highlight_unknown",caption:`${t} = ? tens and ${onesLabel(l)}.`,spoken:`${capitalised(numberToWords(t))} is some tens and ${onesWords$1(l)}. We know the ones, so the rest of it must be tens.`},{visual_data:f,animation:"fill_part",caption:`Take away the ${onesLabel(l)}: ${u} is left.`,spoken:`Take the ${onesWords$1(l)} away from ${numberToWords(t)}. ${capitalised(numberToWords(u))} is left, and that part is all tens.`},{visual_data:{type:"ten_frame_and_ones",tens:i,ones:l},animation:"reveal_total",caption:`${u} is ${tensLabel(i)}. So ${tensLabel(i)} and ${onesLabel(l)}.`,spoken:`Count the tens: ${L}. ${capitalised(numberToWords(u))} is ${tensWords$1(i)}, and the first digit of ${numberToWords(t)} says so too. So ${numberToWords(t)} is ${tensWords$1(i)} and ${onesWords$1(l)}.`}]})}const g={type:"part_whole",whole:t,parts:[u,null]},b={type:"part_whole",whole:t,parts:[u,0]},x=h?`${t} is all tens — no ones left over.`:`${t} splits into ${u} and some ones.`,y=h?`Let's split ${numberToWords(t)} into tens and ones. It's all tens — there are no ones left over.`:`Let's split ${numberToWords(t)} into tens and ones. We start with the tens, because they are the biggest part: the tens part is ${numberToWords(u)}.`,E=h?b:g,k=h?"0 ones are left.":"How many ones are left?",T=h?"There are no ones left over — just the tens.":`The ones are whatever is left after the tens. After ${numberToWords(u)}, how many more to reach ${numberToWords(t)}?`,A=`${tensLabel(l)} and ${onesLabel(i)}`,N=`${tensWords$1(l)} and ${onesWords$1(i)}`,S=a?`${u} and ${l} = ${tensLabel(i)} and ${onesLabel(l)}. Not ${A} (${d}).`:`${u} and ${l} — that's ${tensLabel(i)} and ${onesLabel(l)}.`,M=a?`${capitalised(onesWords$1(l))} ${l===1?"is":"are"} left. ${capitalised(numberToWords(u))} and ${numberToWords(l)} make ${numberToWords(t)}, so ${numberToWords(t)} is ${tensWords$1(i)} and ${onesWords$1(l)}. Not ${N} — that would be ${numberToWords(d)}.`:h?`So ${numberToWords(t)} is ${tensWords$1(i)} and zero ones — every part of it is tens.`:`${capitalised(onesWords$1(l))} ${l===1?"is":"are"} left. ${capitalised(numberToWords(u))} and ${numberToWords(l)} make ${numberToWords(t)}, so ${numberToWords(t)} is ${tensWords$1(i)} and ${onesWords$1(l)}.`;return buildExample({generatorId:this.id,question:n,title:`How do we split ${t} into tens and ones?`,computed:m,steps:[{visual_data:g,animation:"highlight_unknown",caption:x,spoken:y},{visual_data:E,animation:"count_up",caption:k,spoken:T},{visual_data:f,animation:"fill_part",caption:S,spoken:M}]})}},TENS_ONES=/^(\d+) tens? and (\d+) ones? make __$/,ONES_TENS=/^(\d+) ones? and (\d+) tens? make __$/,NAMED$2=/^[A-Za-z]+ (?:has|stacks) (\d+) tens? and (\d+) ones?\. What number\?$/,PARTITION=/^(\d+) \+ (\d+) = __$/,NAMED_MONEY=/^[A-Za-z]+ saves (\d+) ten-pound notes and (\d+) one-pound coins in (?:her|his) money box\. How much money has (?:she|he) saved, in pounds\?$/,LOCKER_MADE_FROM=/^The lockers at swimming club are numbered using tens and ones\. Which locker number is made from (\d+) tens and (\d+) ones\?$/,NAMED_SHEETS=/^[A-Za-z]+ collects stickers in sheets of ten\. (?:He|She) has (\d+) full sheets and (\d+) loose stickers\. How many stickers does [A-Za-z]+ have altogether\?$/,NAMED_MAKES=/^[A-Za-z]+ makes a number from (\d+) tens? and (\d+) ones?\. What number is it\?$/,COMPOSE_CLAIM=/^([A-Za-z]+) says the number made from (\d+) tens and (\d+) ones is (\d+)\. ([A-Za-z]+) says it'?s (\d+)\. Who is correct\?$/;function readTensOnes(n){const t=matchInts(n.stem,TENS_ONES)??matchInts(n.stem,NAMED$2)??matchInts(n.stem,NAMED_MONEY)??matchInts(n.stem,LOCKER_MADE_FROM)??matchInts(n.stem,NAMED_SHEETS)??matchInts(n.stem,NAMED_MAKES);if(t)return{tens:t[0],ones:t[1]};const e=matchInts(n.stem,ONES_TENS);if(e)return{tens:e[1],ones:e[0]};const s=matchInts(n.stem,PARTITION);if(s){const[r,a]=s;return r%10!==0?null:{tens:r/10,ones:a}}const o=n.visualData;return o&&o.type==="ten_frame_and_ones"&&Number.isInteger(o.tens)&&Number.isInteger(o.ones)&&/^how many .*\?$/.test(normaliseStem(n.stem).toLowerCase())?{tens:o.tens,ones:o.ones}:null}function buildComposeClaimCheck(n,t,e,s,o,r,a){if(e<1||e>9||s<0||s>9)return null;const i=e*10+s,l=s*10+e,u=[o,a].sort((b,x)=>b-x),d=[i,l].sort((b,x)=>b-x);if(u[0]!==d[0]||u[1]!==d[1])return null;const m=o===i?t:a===i?r:null;if(m===null||m!==n.correctAnswer.trim())return null;const h=e*10,f={tens:e,ones:s,total:i,statedA:o,statedB:a,tensValue:h},g=numberToWords(e);return buildExample({generatorId:"NPV.P4.M02.compose-tens-ones.v1",question:n,title:`${t} says ${o}. ${r} says ${a}. Who is right?`,computed:f,steps:[{visual_data:{type:"ten_frame_and_ones",tens:e,ones:s},animation:"show_objects",caption:`${t} says ${o}. ${r} says ${a}. Let's build it and check.`,spoken:`${t} says the number is ${numberToWords(o)}. ${r} says it's ${numberToWords(a)}. Tens are always written first, then ones — let's build it to see.`},{visual_data:{type:"ten_frame_and_ones",tens:e,ones:s},animation:"highlight_ten",caption:`${e} full ${e===1?"ten":"tens"} hold ${h}.`,spoken:`${capitalised(g)} full ${e===1?"ten-frame holds":"ten-frames hold"} ${numberToWords(h)}. That's the tens, written first.`},{visual_data:{type:"ten_frame_and_ones",tens:e,ones:s},animation:"reveal_total",caption:`${h} + ${s} = ${i}. ${m} is correct.`,spoken:`${capitalised(numberToWords(h))} and ${numberToWords(s)} more make ${numberToWords(i)}. ${m} is correct.`}]})}const npvP4M02Generator={id:"NPV.P4.M02.compose-tens-ones.v1",microSkillId:"NPV.P4.M02",forms:["ten_and_ones_to_numeral","partition_equation","set_to_numeral","context_calculation","context_reasoning_judgement"],generate(n){const t=COMPOSE_CLAIM.exec(normaliseStem(n.stem));if(t)return buildComposeClaimCheck(n,t[1],Number(t[2]),Number(t[3]),Number(t[4]),t[5],Number(t[6]));const e=readTensOnes(n);if(!e)return null;const{tens:s,ones:o}=e;if(s<1||s>9||o<0||o>20)return null;const r=s*10,a=r+o;if(a<10||a>99||a!==answerInt(n))return null;const i=Math.floor(o/10),l=o%10,u=r+10,d=r+10*i,m=i===0?Array.from({length:o},(F,D)=>r+1+D):[],h={tens:s,ones:o,tensValue:r,total:a,answer:a};m.forEach((F,D)=>{h[`on${D+1}`]=F}),i>0&&(h.midValue=u,h.finalTensValue=d,h.remainder=l,h.regroups=i,h.regroupTen=10);const f=s===1,g=numberToWords(s),b=numberToWords(r),x=o===0,y=x?`${s} full ${f?"ten":"tens"} — no extra ones.`:`${s} full ${f?"ten":"tens"} and ${o} ${o===1?"one":"ones"}.`,E=f?`The ten-frame holds ${r}.`:`The ${s} ten-frames hold ${r}.`,k=x?`${r} with no extra ones is ${a}.`:i===2?`${r} + 10 = ${u}. ${u} + 10 = ${a}.`:i===1?l>0?`${r} + 10 = ${u}, + ${l} = ${a}.`:`${r} + 10 = ${a}.`:`${r} + ${o} = ${a}.`,T=f?"one full ten-frame":`${g} full ten-frames`,A=x?"no extra ones":`${numberToWords(o)} extra ${o===1?"one":"ones"}`,N=`Each full ten-frame is one ten, and each loose dot is one. Here there ${f?"is":"are"} ${T} and ${A}.`,S=Array.from({length:s},(F,D)=>numberToWords(10*(D+1))).join(", "),M=f?"We start with the tens, because they are worth the most. A full frame holds ten, so the tens make ten.":`We start with the tens, because they are worth the most. Each full frame holds ten, so we count them in tens: ${S}. The full frames hold ${b}.`,L=x?`There are no extra ones to add, so the number is just the tens: ${numberToWords(a)}.`:i===2?`Ten of the ones make a new ten: ${b} and ten more is ${numberToWords(u)}. The next ten ones make another new ten: ${numberToWords(u)} and ten more is ${numberToWords(a)}.`:i===1?`Ten of the ones make a new ten: ${b} and ten more is ${numberToWords(u)}. `+(l>0?`Then ${numberToWords(l)} more make ${numberToWords(a)}.`:`That's ${numberToWords(a)} altogether.`):`Now we count on the ones from ${b}: ${countingWords(r+1,a)}. So there are ${numberToWords(a)} altogether.`,P=`How many: ${s} ${f?"ten":"tens"} and ${o} ${o===1?"one":"ones"}?`;if(i>0){const F=s+i;h.newTens=F;const D={type:"ten_frame_and_ones",tens:F,ones:l,regrouped:i},U=Array.from({length:F},(V,O)=>numberToWords(10*(O+1))).join(", "),q=l===0?"no ones left over":`${numberToWords(l)} ${l===1?"one":"ones"} left over`;return buildExample({generatorId:this.id,question:n,title:P,computed:h,steps:[{visual_data:{type:"ten_frame_and_ones",tens:s,ones:o},animation:"show_objects",caption:`${s} full ${f?"ten":"tens"} and ${o} ones: too many ones!`,spoken:`Each full ten-frame is one ten, and each loose dot is one. Here there ${f?"is":"are"} ${T} and ${numberToWords(o)} loose ones. That's too many ones to write in one place, so we make a new ten.`},{visual_data:D,animation:"highlight_ten",caption:i===1?`10 ones fill a new ten. Now ${F} tens and ${l} ${l===1?"one":"ones"}.`:`20 ones fill 2 new tens. Now ${F} tens and ${l} ones.`,spoken:i===1?`Ten of the loose ones fill a new ten-frame. Now there are ${numberToWords(F)} tens and ${q}.`:`Ten loose ones fill a new ten-frame, and ten more fill another. Now there are ${numberToWords(F)} tens and ${q}.`},{visual_data:D,animation:"reveal_total",caption:l>0?`${d} + ${l} = ${a}.`:`${F} tens = ${a}.`,spoken:`Count the tens: ${U}. `+(l>0?`Then ${numberToWords(l)} more ${l===1?"makes":"make"} ${numberToWords(a)}.`:`That's ${numberToWords(a)} altogether.`)}]})}return buildExample({generatorId:this.id,question:n,title:P,computed:h,steps:[{visual_data:{type:"ten_frame_and_ones",tens:s,ones:o},animation:"show_objects",caption:y,spoken:N},{visual_data:{type:"ten_frame_and_ones",tens:s,ones:o},animation:"highlight_ten",caption:E,spoken:M},{visual_data:{type:"ten_frame_and_ones",tens:s,ones:o},animation:"reveal_total",caption:k,spoken:L}]})}},SYMBOL_CMP=/^(\d+) __ (\d+)$/,GREATER=/^Which is greater: (\d+) or (\d+)\?$/,S
71MALLER=/^Which is smaller: (\d+) or (\d+)\?$/,WP_SCORED=/^([A-Z][a-z]+) scored (\d+), ([A-Z][a-z]+) scored (\d+)\. Who scored more\?$/,WP_HOLDS=/^A box holds (\d+) pens, another holds (\d+)\. Which holds more\?$/,WP_FISH_TANK=/^One fish tank holds (\d+) litres of water\. Another tank holds (\d+) litres\. How many litres are in the bigger tank\?$/,WP_HAS_CARDS=/^([A-Z][a-z]+) has (\d+) trading cards\. ([A-Z][a-z]+) has ([a-z]+(?:-[a-z]+)?) trading cards\. Who has more cards\?$/,ONES_DIGIT_CLAIM=/^[A-Z][a-z]+ says (\d+) is bigger than (\d+) because (?:the ones digit )?(\d+) is bigger than (?:the ones digit )?(\d+)\. Is (?:he|she) (?:correct|right)\?$/,TENS_RULE_ALWAYS_BIGGER=/^(?:A|Any) two-digit number with (\d+) tens is (?:always )?bigger than (?:a|any) two-digit number with (\d+) tens(?:, no matter what the ones digits are)?\. True or false\?$/,WORD_ONES$1=["zero","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"],WORD_TENS$1=["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"];function wordToNumber(n){const t=n.toLowerCase().trim(),e=WORD_ONES$1.indexOf(t);if(e>=0)return e;const[s,o]=t.split("-"),r=WORD_TENS$1.indexOf(s);if(r<2)return null;if(!o)return r*10;const a=WORD_ONES$1.indexOf(o);return a<1||a>9?null:r*10+a}function symbolFor$1(n,t){return n<t?"<":n>t?">":"="}function tensPhrase$1(n){return`${numberToWords(n)} ${n===1?"ten":"tens"}`}function onesPhrase(n){return`${numberToWords(n)} ${n===1?"one":"ones"}`}function buildOnesDigitClaimCheck(n,t,e,s,o){if(t<10||t>99||e<10||e>99||t===e||s!==t%10||o!==e%10)return null;const r=Math.floor(t/10),a=Math.floor(e/10),i=t>e?t:e,l=t>e?e:t,u=t>e?"Yes":"No";if(u!==n.correctAnswer.trim())return null;const d=symbolFor$1(t,e),m={a:t,b:e,onesClaimA:s,onesClaimB:o,tensA:r,tensB:a},h=Math.max(r,a),f=Math.min(r,a);return buildExample({generatorId:npvP4M03Generator.id,question:n,title:`Is ${t} really bigger than ${e}?`,computed:m,steps:[{visual_data:{type:"comparison",left:t,right:e,symbol:null},animation:"show_numbers",caption:`Someone says ${t} > ${e} because ${s} > ${o}. Check the tens.`,spoken:`Someone says ${numberToWords(t)} is bigger than ${numberToWords(e)} because the ones digit is bigger. But we always compare the tens first, not the ones.`},{visual_data:{type:"comparison",left:t,right:e,symbol:null},animation:"highlight_tens",caption:`${t} has ${r} tens. ${e} has ${a} tens.`,spoken:`So let's look at the tens. ${capitalised(numberToWords(t))} has ${tensPhrase$1(r)}, and ${numberToWords(e)} has ${tensPhrase$1(a)}.`},{visual_data:{type:"comparison",left:t,right:e,symbol:d},animation:"write_symbol highlight_tens",caption:u==="Yes"?`${h} tens beats ${f} tens. ${i} is right, but not because of the ones.`:`${h} tens beats ${f} tens, so ${i} is bigger, not ${l}. Wrong.`,spoken:u==="Yes"?`${capitalised(tensPhrase$1(h))} is more than ${tensPhrase$1(f)}, so ${numberToWords(i)} really is bigger. The claim's answer is right, but comparing ones digits is not the reason why.`:`${capitalised(tensPhrase$1(h))} is more than ${tensPhrase$1(f)}, so ${numberToWords(i)} is bigger, not ${numberToWords(l)}. The claim is wrong.`}]})}function buildTensRuleCheck(n,t,e){if(t<1||t>9||e<1||e>9||t===e)return null;const s=t*10,o=e*10+9,r=s>o;if((r?"True":"False")!==n.correctAnswer.trim())return null;const i=symbolFor$1(s,o),l={tensBig:t,tensSmall:e,worstBig:s,worstSmall:o};return buildExample({generatorId:npvP4M03Generator.id,question:n,title:`Is a ${t}-tens number always bigger than a ${e}-tens number?`,computed:l,steps:[{visual_data:{type:"comparison",left:s,right:o,symbol:null},animation:"show_numbers",caption:`Worst case: smallest ${t}-tens is ${s}, biggest ${e}-tens is ${o}.`,spoken:`To check if this is always true, try the hardest case: the smallest number with ${tensPhrase$1(t)}, and the biggest number with ${tensPhrase$1(e)}.`},{visual_data:{type:"comparison",left:s,right:o,symbol:null},animation:"highlight_tens",caption:`${s} has ${t} tens. ${o} has ${e} tens.`,spoken:`Now compare their tens, because the tens decide which is bigger. ${capitalised(numberToWords(s))} has ${tensPhrase$1(t)}, and ${numberToWords(o)} has ${tensPhrase$1(e)}.`},{visual_data:{type:"comparison",left:s,right:o,symbol:i},animation:"write_symbol highlight_tens",caption:r?`${s} ${i} ${o}, even in the worst case. So the rule is always true.`:`${s} ${i} ${o} in this case, so the rule is not always true. False.`,spoken:r?`Even the smallest ${tensPhrase$1(t)} number beats the biggest ${tensPhrase$1(e)} number. So a ${numberToWords(t)}-tens number is always bigger. This is true.`:`Here the ${tensPhrase$1(e)} number is bigger, so it is not always true. This is false.`}]})}const npvP4M03Generator={id:"NPV.P4.M03.compare-tens-then-ones.v1",microSkillId:"NPV.P4.M03",forms:["comparison","comparison_statement","context_reasoning_judgement","true_false_check"],generate(n){const t=matchInts(n.stem,ONES_DIGIT_CLAIM);if(t)return buildOnesDigitClaimCheck(n,t[0],t[1],t[2],t[3]);const e=matchInts(n.stem,TENS_RULE_ALWAYS_BIGGER);if(e)return buildTensRuleCheck(n,e[0],e[1]);const s=normaliseStem(n.stem);let o,r,a,i=null,l=null;const u=s.match(SYMBOL_CMP),d=u?null:s.match(GREATER),m=u||d?null:s.match(SMALLER),h=u||d||m?null:s.match(WP_SCORED),f=u||d||m||h?null:s.match(WP_HOLDS),g=u||d||m||h||f?null:s.match(WP_FISH_TANK),b=u||d||m||h||f||g?null:s.match(WP_HAS_CARDS);if(u)o=Number(u[1]),r=Number(u[2]),a="symbol";
71else if(d)o=Number(d[1]),r=Number(d[2]),a="greater";else if(m)o=Number(m[1]),r=Number(m[2]),a="smaller";else if(h)o=Number(h[2]),r=Number(h[4]),a="greater",i=o>r?h[1]:r>o?h[3]:null,l=`Who scored more: ${h[1]} or ${h[3]}?`;else if(f)o=Number(f[1]),r=Number(f[2]),a="greater",l=`Which holds more: ${o} or ${r}?`;else if(g)o=Number(g[1]),r=Number(g[2]),a="greater",l=`How many litres are in the bigger tank: ${o} or ${r}?`;else if(b){const se=wordToNumber(b[4]);if(se===null)return null;o=Number(b[2]),r=se,a="greater",i=o>r?b[1]:r>o?b[3]:null,l=`Who has more cards: ${b[1]} or ${b[3]}?`}else return null;if(o<10||o>99||r<10||r>99||a!=="symbol"&&o===r)return null;const x=symbolFor$1(o,r);if(a==="greater"){const se=Math.max(o,r);if(i!==null){if(i!==n.correctAnswer.trim())return null}else if(se!==answerInt(n))return null}else if(a==="smaller"){if(Math.min(o,r)!==answerInt(n))return null}else if(x!==n.correctAnswer)return null;const y=Math.floor(o/10),E=Math.floor(r/10),k=o%10,T=r%10,A=y!==E,N=Math.max(o,r),S=Math.min(o,r),M={left:o,right:r,tensLeft:y,tensRight:E,onesLeft:k,onesRight:T,bigger:N,smaller:S,...i!==null?{}:{answer:a==="greater"?N:a==="smaller"?S:x==="<"?-1:x===">"?1:0}},L=i??numberToWords(a==="smaller"?S:N),P=i??String(a==="smaller"?S:N),F=numberToWords(a==="smaller"?N:S),D=g!==null,U=a==="smaller"?"smaller":"bigger",q=a==="smaller"?"smaller":"greater",V=l??(a==="smaller"?`Which is smaller: ${o} or ${r}?`:a==="greater"?`Which is greater: ${o} or ${r}?`:`Which is bigger: ${o} or ${r}?`),O=`Let's compare ${numberToWords(o)} and ${numberToWords(r)}. We look at the tens first, because tens are worth more than ones — even nine ones is less than one ten.`,z=y>E?y:E,K=y>E?E:y,Z=A?`${z} tens beats ${K} tens.`:"Tens are equal — check the ones.",Q=A?`${capitalised(numberToWords(o))} has ${tensPhrase$1(y)}, and ${numberToWords(r)} has ${tensPhrase$1(E)}. ${capitalised(tensPhrase$1(z))} is more than ${tensPhrase$1(K)}, so ${L} is ${U}.`:`Both have ${tensPhrase$1(y)}, the same. The tens can't tell them apart, so we check the ones.`;let B=a==="symbol"?A?`${o} ${x} ${r}.`:x==="="?`${k} = ${T}, so ${o} = ${r}.`:`${k} ${x} ${T}, so ${o} ${x} ${r}.`:D?`${P} is ${q}. So the bigger tank holds ${P} litres.`:`${P} is ${q}. ${o} ${x} ${r}.`,G;if(x==="=")G=`${k===1?"One each":`${capitalised(onesPhrase(k))} each`}, the same too. So ${numberToWords(o)} equals ${numberToWords(r)}.`;else if(A)G=a==="symbol"?`So ${numberToWords(o)} is ${x==="<"?"less":"greater"} than ${numberToWords(r)}. The open side of the sign always faces the bigger number.`:`The tens decide it, so ${L} is ${q} than ${F}.${D?"":` The answer is ${L}.`}`;else{const se=Math.max(k,T),te=Math.min(k,T),oe=`${capitalised(onesPhrase(se))} is more than ${onesPhrase(te)}.`;G=a==="symbol"?`${oe} So ${numberToWords(o)} is ${x==="<"?"less":"greater"} than ${numberToWords(r)}. The open side of the sign always faces the bigger number.`:`${oe} So ${L} is ${q}.${D?"":` The answer is ${L}.`}`}D&&a!=="symbol"&&(G=`${G} So the bigger tank holds ${L} litres.`);const X=b?b[4].toLowerCase():null,ne=X!==null&&b?{visual_data:{type:"comparison",left:o,right:r,symbol:null,right_text:X},animation:"show_numbers",caption:`Write ${b[3]}'s number in digits: ${r}.`,spoken:`${b[3]}'s number is written as a word: ${X}. That is ${tensPhrase$1(E)} and ${onesPhrase(T)}, so in digits we write ${numberToWords(r)}. Now we can compare ${numberToWords(o)} and ${numberToWords(r)}, tens first.`}:{visual_data:{type:"comparison",left:o,right:r,symbol:null},animation:"show_numbers",caption:"Compare the tens first.",spoken:O};return buildExample({generatorId:this.id,question:n,title:V,computed:M,steps:[ne,{visual_data:{type:"comparison",left:o,right:r,symbol:null},animation:"highlight_tens",caption:Z,spoken:Q},{visual_data:{type:"comparison",left:o,right:r,symbol:x},animation:A?"write_symbol highlight_tens":"write_symbol",caption:B,spoken:G}]})}},FWD_IS=/^10 (more|less) than (\d+) is __$/,FWD_WHAT=/^What is 10 (more|less) than (\d+)\?$/,FWD_WHICH=/^Which number is 10 (more|less) than (\d+)\?$/,REVERSE=/^10 (more|less) than (?:what number|__) 
71is (\d+)\??$/,SYMBOLIC$2=/^(\d+) ([+-]) 10 = __$/,HOP_FROM=/^Hop 10 (more|less) from (\d+)\. Where do you land\?$/,HOP_LANDS=/^A hop of 10 (more|less) lands on (\d+)\. Where did it start\?$/,DOUBLE_HOP=/^Start at (\d+)\. Add 10, then add 10 more\. What number do you reach\?$/;function tensWords(n){return`${numberToWords(n)} ten${n===1?"":"s"}`}function onesWords(n){return`${numberToWords(n)} one${n===1?"":"s"}`}function buildDoubleHopExample(n,t){if(t<10||t>79)return null;const e=t+10,s=t+20;if(s!==answerInt(n))return null;const o=Math.floor(t/10)*10,r=t%10,a=o+10,i=o+20,l={start:t,mid:e,result:s,answer:s,tensVal:o,ones:r,midTens:a,resultTens:i},u={type:"part_whole",whole:t,parts:[o,r]},d={type:"part_whole",whole:e,parts:[a,r]},m={type:"part_whole",whole:s,parts:[i,r]},h=r===0?`${capitalised(numberToWords(t))} is ${tensWords(Math.floor(t/10))}.`:`${capitalised(numberToWords(t))} is ${tensWords(Math.floor(t/10))} and ${onesWords(r)}.`;return buildExample({generatorId:"NPV.P4.M04.move-the-tens.v1",question:n,title:`Start at ${t}. Add 10, then add 10 more.`,computed:l,steps:[{visual_data:u,animation:"split_tens_ones",caption:`${t} is ${o} and ${r}.`,spoken:h},{visual_data:d,animation:"add_ten",caption:`10 more: the tens go ${o} → ${a}. Now ${e}.`,spoken:`Ten more is one more ten. ${capitalised(numberToWords(o))} becomes ${numberToWords(a)}, so we're at ${numberToWords(e)}.`},{visual_data:m,animation:"add_ten",caption:`10 more again: the tens go ${a} → ${i}. Now ${s}.`,spoken:`Ten more again. ${capitalised(numberToWords(a))} becomes ${numberToWords(i)}, so we reach ${numberToWords(s)}. Only the tens digit moves, twice.`}]})}const npvP4M04Generator={id:"NPV.P4.M04.move-the-tens.v1",microSkillId:"NPV.P4.M04",forms:["place_value_adjustment"],generate(n){const t=normaliseStem(n.stem),e=matchInts(n.stem,DOUBLE_HOP);if(e)return buildDoubleHopExample(n,e[0]);let s,o,r,a;const i=t.match(FWD_IS)??t.match(FWD_WHAT)??t.match(FWD_WHICH)??t.match(HOP_FROM),l=i?null:t.match(REVERSE)??t.match(HOP_LANDS),u=i||l?null:t.match(SYMBOLIC$2);
71if(i)s=i[1],o=Number(i[2]),r=s==="more"?o+10:o-10,a=!1;else if(l)s=l[1],r=Number(l[2]),o=s==="more"?r-10:r+10,a=!0;else if(u)s=u[2]==="+"?"more":"less",o=Number(u[1]),r=s==="more"?o+10:o-10,a=!1;else return null;if(o<10||o>99||r<10||r>99)return null;const d=a?o:r;if(d!==answerInt(n))return null;const m=a?r:o,h=a?o:r,f=a?s==="more"?"less":"more":s,g=Math.floor(m/10)*10,b=Math.floor(m/10),x=m%10,y=Math.floor(h/10)*10,E={subject:m,target:h,tens:g,tensDigit:b,ones:x,targetTens:y,answer:d},k={type:"part_whole",whole:m,parts:[g,x]},T={type:"part_whole",whole:h,parts:[y,x]},A=f==="more"?"more":"less",N=x===0?`${capitalised(numberToWords(m))} is ${tensWords(b)}.`:`${capitalised(numberToWords(m))} is ${tensWords(b)} and ${onesWords(x)}.`,S=a?`Ten ${s} than our number makes ${numberToWords(m)}. To find it, work backwards — ten ${A} than ${numberToWords(m)}. ${N}`:`${N} We want ten ${A}.`,M=a?`Work backwards from ${m}.`:`${m} is ${g} and ${x}.`,L=f==="more"?`Ten more is just one more ten. The ${numberToWords(g)} becomes ${numberToWords(y)}.`:`Ten less is just one less ten. The ${numberToWords(g)} becomes ${numberToWords(y)}.`,P=`10 ${A}: the tens go ${g} → ${y}.`,F=x===0?"There were no ones to change.":`The ${onesWords(x)} never changed.`,D=x===0?`${capitalised(numberToWords(y))} on its own is ${numberToWords(h)}.`:`${capitalised(numberToWords(y))} and ${numberToWords(x)} make ${numberToWords(h)}.`,U=`${F} ${D} Only the tens digit moves.`,q=`Ones stay ${x} — only the tens move. So ${h}.`;return buildExample({generatorId:this.id,question:n,title:`What is 10 ${A} than ${m}?`,computed:E,steps:[{visual_data:k,animation:"split_tens_ones",caption:M,spoken:S},{visual_data:T,animation:f==="more"?"add_ten":"remove_ten",caption:P,spoken:L},{visual_data:T,animation:"ones_unchanged",caption:q,spoken:U}]})}},N_PART_BLANK=/^(\d+) = (\d+) \+ __$/,PART_BLANK_N=/^(\d+) \+ __ = (\d+)$/,N_BLANK_PART=/^(\d+) = __ \+ (\d+)$/;function resolveTensTransfer(n,t,e){for(const s of[{tensSide:t,onesSide:e},{tensSide:e,onesSide:t}]){const o=n-s.tensSide;if(o>0&&o%10===0)return{...s,d:o}}return null}const npvP4M05Generator={id:"NPV.P4.M05.partition-many-ways.v1",microSkillId:"NPV.P4.M05",forms:["partition_equation","grouped_partition_model","regrouped_partition_probe"],generate(n){if(!this.forms.includes(n.mathematicalForm??""))return null;let t=null,e=null,s=matchInts(n.stem,N_PART_BLANK);if(s&&([t,e]=s),t===null&&(s=matchInts(n.stem,PART_BLANK_N),s&&([e,t]=s)),t===null&&(s=matchInts(n.stem,N_BLANK_PART),s&&([t,e]=s)),t===null||e===null||t<11||t>99||t%10===0||e<1||e>=t)return null;const o=Math.floor(t/10)*10,r=t%10,a=t-e;if(e===o||a!==answerInt(n)||!resolveTensTransfer(o,e,a))return null;const i=N_BLANK_PART.test(n.stem),l=Math.floor((i?e:a)/10)*10,u=(i?e:a)%10,d=i?t:e,m=i?d-l:d+l,h=i?a:t,f=[...l?[{from:d,to:m,label:`${i?"−":"+"}${l}`}]:[],...u?[{from:m,to:h,label:`${i?"−":"+"}${u}`}]:[]];return buildExample({generatorId:this.id,question:n,title:`Find the missing part of ${t}.`,computed:{whole:t,tens:o,ones:r,part:e,answer:a,jumpTens:l,jumpOnes:u,start:d,middle:m,end:h},steps:[{visual_data:{type:"part_whole",whole:t,parts:i?[null,e]:[e,null]},animation:"pulse_unknown",caption:`${t} = ${i?"?":e} + ${i?e:"?"}.`,spoken:`We know the whole is ${numberToWords(t)} and one part is ${numberToWords(e)}. ${i?"Take the known part away to find what is left.":"Count on from the known part to find how much is missing."}`},{visual_data:{type:"number_line",min:Math.min(d,h),max:Math.max(d,h),marked:[d,m,h],hops:f,unknown:null},animation:i?"count_back":"count_on",caption:i?`${t} − ${l} − ${u} = ?.`:`From ${e} to ${t}: +${l}, then +${u}.`,spoken:i?`Start at ${numberToWords(t)}. Take away ${numberToWords(l)}, then ${numberToWords(u)}. Those jumps remove the whole known part.`:`Start at ${numberToWords(e)}. Add ${numberToWords(l)} to reach ${numberToWords(m)}, then ${numberToWords(u)} to reach ${numberToWords(t)}. Add the two jumps to find the missing part.`},{visual_data:{type:"part_whole",whole:t,parts:i?[a,e]:[e,a]},animation:"many_ways",caption:`${e} + ${a} = ${t}. Missing part: ${a}.`,spoken:`${i?"The amount left":"The two jumps together"} is ${numberToWords(a)}. So the missing part is ${numberToWords(a)}. Both parts still make ${numberToWords(t)}.`}]})}},ROWS_OF=/^([A-Za-z]+) sit in (\d+) rows of (\d+)\. How many ([a-z]+)(?: are there)?\?$/,DOTS_ALTOGETHER=/^How many [a-z]+ (?:are there )?altogether\?$/,ARRANGED_IN_TOTAL=/^A [a-z]+(?: [a-z]+)? has ([a-z]+) arranged in (\d+) rows of (\d+)\. How many \1 are there in total\?$/,ADD_INSTEAD_TF$1=/^This array shows (\d+) rows of (\d+) dots\. So it has (\d+) \+ (\d+) dots altogether\. True or False\?$/;function pluralNumberWord$4(n){const t=numberToWords(n);return t.endsWith("x")?`${t}es`:`${t}s`}function readDimensions(n){const t=normaliseStem(n.stem).match(ROW
71S_OF);if(t){const[,o,r,a,i]=t;return o.toLowerCase()!==i.toLowerCase()?null:{rows:Number(r),cols:Number(a)}}const e=normaliseStem(n.stem).match(ARRANGED_IN_TOTAL);if(e){const[,,o,r]=e;return{rows:Number(o),cols:Number(r)}}const s=n.visualData;return DOTS_ALTOGETHER.test(normaliseStem(n.stem))&&s&&s.type==="array"&&Number.isInteger(s.rows)&&Number.isInteger(s.cols)?{rows:s.rows,cols:s.cols}:null}function handleAdditionMisconception$1(n){const t=normaliseStem(n.stem).match(ADD_INSTEAD_TF$1);if(!t)return null;const[e,s,o,r]=t.slice(1),a=Number(e),i=Number(s),l=Number(o),u=Number(r);if(l!==a||u!==i||a<2||a>10||i<1||i>10)return null;const d=a*i,m=l+u,h=d===m?"True":"False";if(h!==n.correctAnswer.trim())return null;const f=Array.from({length:a-1},(E,k)=>(k+1)*i),g=f.join(", "),b=numberToWords(i),x=pluralNumberWord$4(i),y={rows:a,cols:i,product:d,claimSum:m};return f.forEach((E,k)=>{y[`run${k+1}`]=E}),buildExample({generatorId:mdP2M01Generator.id,question:n,title:`Is ${a} + ${i} the same as ${a} rows of ${i}?`,computed:y,steps:[{visual_data:{type:"array",rows:a,cols:i},animation:"array_build",caption:`${a} equal rows. Each row has ${i}.`,spoken:`${capitalised(numberToWords(a))} rows of dots. Each row has the same number — ${b} dots in every row.`},{visual_data:{type:"array",rows:a,cols:i},animation:"array_skip_count",caption:`Count the rows in ${x}: ${g}…`,spoken:`We count the rows in ${x}, not add them. One row is ${b}. Two rows is ${numberToWords(i*2)}.`},{visual_data:{type:"array",rows:a,cols:i},animation:"array_product",caption:h==="True"?`${a} rows of ${i} = ${d}. That matches ${a} + ${i}. True.`:`${a} rows of ${i} = ${d}, not ${m}. False.`,spoken:h==="True"?`${capitalised(numberToWords(a))} rows of ${b} is ${numberToWords(d)}, which happens to match adding them. True.`:`${capitalised(numberToWords(a))} rows of ${b} is ${numberToWords(d)}, not ${numberToWords(m)}. Adding the rows and columns is not the same as multiplying them. False.`}]})}function parseOptionArray(n){const t=n.trim().match(/^(\d+) rows of (\d+)$/);return t?{rows:Number(t[1]),cols:Number(t[2])}:null}const MOST_DOTS=/^Which array has the most dots altogether\?$/,SHOWS_N_DOTS=/^Which array shows (\d+) dots altogether\?$/;function handleShowsNDots(n){const t=matchInts(n.stem,SHOWS_N_DOTS);if(!t)return null;const[e]=t;if(!n.options||n.options.length<2)return null;const s=n.options.map(g=>({label:g,dims:parseOptionArray(g)}));if(s.some(g=>!g.dims))return null;const o=s.filter(g=>g.dims.rows*g.dims.cols===e);if(o.length!==1)return null;const r=o[0];if(r.label.trim()!==n.correctAnswer.trim())return null;const{rows:a,cols:i}=r.dims;if(a<2||a>10||i<1||i>10)return null;const l=a*i;if(l!==e)return null;const u=Array.from({length:a-1},(g,b)=>(b+1)*i),d=u.join(", "),m=numberToWords(i),h=pluralNumberWord$4(i),f={rows:a,cols:i,product:l,target:e};return u.forEach((g,b)=>{f[`run${b+1}`]=g}),buildExample({generatorId:mdP2M01Generator.id,question:n,title:`Which array shows ${e}?`,computed:f,steps:[{visual_data:{type:"array",rows:a,cols:i},animation:"array_build",caption:`${a} equal rows. Each row has ${i}.`,spoken:`${capitalised(numberToWords(a))} rows of dots. Each row has the same number — ${m} dots in every row.`},{visual_data:{type:"array",rows:a,cols:i},animation:"array_skip_count",caption:`Count the rows in ${h}: ${d}…`,spoken:`We count the rows in ${h}. One row is ${m}. Two rows is ${numberToWords(i*2)}.`},{visual_data:{type:"array",rows:a,cols:i},animation:"array_product",caption:`${a} rows of ${i} = ${l}. That's the one!`,spoken:`${capitalised(numberToWords(a))} rows of ${m} is ${numberToWords(l)} dots altogether — that's the array we want.`}]})}function handleMostDots(n){if(!MOST_DOTS.test(normaliseStem(n.stem))||!n.options||n.options.length!==3)return null;const t=n.options.map(d=>({label:d,dims:parseOptionArray(d)}));if(t.some(d=>!d.dims))return null;const e=t.map(d=>({...d,product:d.dims.rows*d.dims.cols}));if(e.some(d=>d.dims.rows<1||d.dims.rows>10||d.dims.cols<1||d.dims.cols>10))return null;const s=Math.max(...e.map(d=>d.product)),o=e.filter(d=>d.product===s);if(o.length!==1)return null;const r=o[0];if(r.label.trim()!==n.correctAnswer.trim())return null;const i=[...e.filter(d=>d!==r),r],l={};
71i.forEach((d,m)=>{l[`rows${m+1}`]=d.dims.rows,l[`cols${m+1}`]=d.dims.cols,l[`product${m+1}`]=d.product});const u=i.map((d,m)=>{const{rows:h,cols:f}=d.dims,g=m===i.length-1;return{visual_data:{type:"array",rows:h,cols:f},animation:"array_product",caption:g?`${h} rows of ${f} = ${d.product}. Biggest — that's the answer!`:`${h} rows of ${f} = ${d.product}.`,spoken:g?`${capitalised(numberToWords(h))} rows of ${numberToWords(f)} is ${numberToWords(d.product)} — the biggest total, so that's the array we want.`:`${m===0?"To find the biggest, we work out each array's total.":"Now the next array."} ${capitalised(numberToWords(h))} rows of ${numberToWords(f)} is ${numberToWords(d.product)}.`}});return buildExample({generatorId:mdP2M01Generator.id,question:n,title:"Which array has the most dots?",computed:l,steps:u})}const mdP2M01Generator={id:"MD.P2.M01.array-skip-count.v1",microSkillId:"MD.P2.M01",forms:["count_equal_groups","context_calculation","true_false_check","comparison_statement"],generate(n){if(n.mathematicalForm==="true_false_check")return handleAdditionMisconception$1(n);if(n.mathematicalForm==="comparison_statement")return handleShowsNDots(n)??handleMostDots(n);const t=readDimensions(n);if(!t)return null;const{rows:e,cols:s}=t;if(e<2||e>10||s<1||s>10)return null;const o=e*s;if(o!==answerInt(n))return null;const r=Array.from({length:e-1},(m,h)=>(h+1)*s),a=r.join(", "),i=numberToWords(s),l=pluralNumberWord$4(s),u=s===1?"one dot in every row":`${i} dots in every row`,d={rows:e,cols:s,product:o,answer:o};return r.forEach((m,h)=>{d[`run${h+1}`]=m}),buildExample({generatorId:this.id,question:n,title:`How many in ${e} rows of ${s}?`,computed:d,steps:[{visual_data:{type:"array",rows:e,cols:s},animation:"array_build",caption:`${e} equal rows. Each row has ${s}.`,spoken:`${capitalised(numberToWords(e))} rows of dots. Each row has the same number — ${u}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_skip_count",caption:`Count the rows in ${l}: ${a}…`,spoken:`We count the rows in ${l}. One row is ${i}. Two rows is ${numberToWords(s*2)}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_product",caption:`${e} rows of ${s} = ${o}.`,spoken:`${capitalised(numberToWords(e))} rows of ${i} is ${numberToWords(o)} dots altogether. ${capitalised(numberToWords(e))} ${l} make ${numberToWords(o)}.`}]})}},ROWS_DOTS_EQ=/^(\d+) rows of (\d+) dots\. (\d+) × (\d+) = __$/,TIMES_EQ$1=/^(\d+) × (\d+) = __$/,MISS_RIGHT$1=/^This array shows (\d+) × __ = (\d+)$/,MISS_LEFT$1=/^This array shows __ × (\d+) = (\d+)$/,CHOICE_STEM=/^Which multiplication matches this array\?$/,CHOICE_LABEL_BARE=/^(\d+) × (\d+)$/,CHOICE_LABEL_EQ=/^(\d+) × (\d+) = (\d+)$/,CONTEXT_TIMES_EQ=/^There are (\d+) [a-z]+, each holding (\d+) [a-z]+\. (\d+) × (\d+) = __$/,ADD_INSTEAD_TF=/^This array shows (\d+) rows of (\d+) dots\. It can be written as (\d+) \+ (\d+) = (\d+)\. True or False\?$/;function pluralNumberWord$3(n){const t=numberToWords(n);return t.endsWith("x")?`${t}es`:`${t}s`}function parseArray(n){const t=matchInts(n.stem,ROWS_DOTS_EQ);if(t){const[a,i,l,u]=t;return a!==l||i!==u?null:{rows:a,cols:i,expectedAnswer:a*i}}const e=matchInts(n.stem,CONTEXT_TIMES_EQ);if(e){const[a,i,l,u]=e;return l!==a||u!==i?null:{rows:a,cols:i,expectedAnswer:a*i}}const s=matchInts(n.stem,TIMES_EQ$1);if(s){const[a,i]=s;return{rows:a,cols:i,expectedAnswer:a*i}}const o=matchInts(n.stem,MISS_RIGHT$1);if(o){const[a,i]=o;if(a===0||i%a!==0)return null;const l=i/a;return{rows:a,cols:l,expectedAnswer:l}}const r=matchInts(n.stem,MISS_LEFT$1);if(r){const[a,i]=r;if(a===0||i%a!==0)return null;const l=i/a;return{rows:l,cols:a,expectedAnswer:l}}return null}function parseChoiceArray(n){if(!normaliseStem(n.stem).match(CHOICE_STEM))return null;const t=n.visualData;if(!t||t.type!=="array")return null;const{rows:e,cols:s}=t;if(!Number.isInteger(e)||!Number.isInteger(s))return null;const o=normaliseStem(n.correctAnswer),r=o.match(CHOICE_LABEL_BARE),a=o.match(CHOICE_LABEL_EQ);if(a){const[i,l,u]=a.slice(1).map(Number);if(i!==e||l!==s||u!==e*s)return null}else if(r){const[i,l]=r.slice(1).map(Number);if(i!==e||l!==s)return null}else return null;return{rows:e,cols:s}}function buildArrayExample(n,t,e,s,o){const r=e*s,a=Array.from({length:e},(h,f)=>(f+1)*s),i=a.join(", "),l=numberToWords(s),u=pluralNumberWord$3(s),d=s*2,m={rows:e,cols:s,product:r,answer:o,twoRows:d};return a.forEach((h,f)=>{m[`run${f+1}`]=h}),buildExample({generatorId:n,question:t,title:"How do we write this array as a multiplication?",computed:m,steps:[{visual_data:{type:"array",rows:e,cols:s},animation:"array_build",caption:`${e} rows. Each row has ${s}.`,spoken:`${capitalised(numberToWords(e))} rows of dots. Each row has the same number — ${l}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_skip_count",caption:`Count in ${u}: ${i}.`,spoken:`We count in ${u}. One row is ${l}. Two rows is ${numberToWords(d)}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_product",caption:`${e} rows of ${s} = ${r}. That's ${e} × ${s}.`,spoken:`${capitalised(numberToWords(e))} rows of ${l} is ${numberToWords(r)}. We write that as ${numberToWords(e)} times ${l} equals ${numberToWords(r)}.`}]})}function handleAdditionMisconception(n){const t=matchInts(n.stem,ADD_INSTEAD_TF);if(!t)return null;const[e,s,o,r,a]=t;if(o!==e||r!==s||a!==o+r||e<2||e>10||s<1||s>10)return null;const i=e*s,l=i===a?"True":"False";if(l!==n.correctAnswer.trim())return null;const u=Array.from({length:e},(g,b)=>(b+1)*s),d=u.join(", "),m=numberToWords(s),h=pluralNumberWord$3(s),f={rows:e,cols:s,product:i,claimSum:a};return u.forEach((g,b)=>{f[`run${b+1}`]=g}),buildExample({generatorId:mdP2M02Generator.id,question:n,title:`Can ${e} rows of ${s} be written as ${e} + ${s}?`,computed:f,steps:[{visual_data:{type:"array",rows:e,cols:s},animation:"array_build",caption:`${e} rows. Each row has ${s}.`,spoken:`${capitalised(numberToWords(e))} rows of dots. Each row has the same number — ${m}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_skip_count",caption:`Count in ${h}: ${d}.`,spoken:`We count in ${h}, not add them. One row is ${m}. Two rows is ${numberToWords(s*2)}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_product",caption:l==="True"?`${e} × ${s} = ${i}. That matches ${e} + ${s}. True.`:`${e} × ${s} = ${i}, not ${a}. False.`,spoken:l==="True"?`${capitalised(numberToWords(e))} rows of ${m} is ${numberToWords(i)}, which happens to match adding them. True.`:`${capitalised(numberToWords(e))} rows of ${m} is ${numberToWords(i)}, not ${numberToWords(a)}. Writing it as an addition is not the same as a times. False.`}]})}const mdP2M02Generator={id:"MD.P2.M02.array-to-equation.v1",microSkillId:"MD.P2.M02",forms:["equation_completion","symbolic_numeric_entry","symbolic_choice","true_false_check","context_calculation"],generate(n){if(n.mathematicalForm==="true_false_check")return handleAdditionMisconception(n);let t,e,s;const o=parseArray(n);if(o){if({rows:t,cols:e}=o,s=o.expectedAnswer,s!==answerInt(n))return null}else{const r=parseChoiceArray(n);if(!r)return null;({rows:t,cols:e}=r),s=t*e}return t<2||t>10||e<1||e>10?null:buildArrayExample(this.id,n,t,e,s)}},mdP3M05ContextAliasGenerator={...mdP2M02Generator,id:"MD.P3.M05.context-multiplication.v1",microSkillId:"MD.P3.M05",forms:["context_calculation"]},GEN_ID$8="MD.P2.M03.commutativity.v1",EQ_BLANK_RIGHT=/^(\d+) × (\d+) = (\d+) × __$/,EQ_BLANK_MID=/^(\d+) × (\d+) = __ × (\d+)$/,EQ_BLANK_LEFT=/^__ × (\d+) = (\d+) × (\d+)$/,ARRAY_FLIP=/^This array shows (\d+) × (\d+)\. What is (\d+) × (\d+)\?$/,TF_MULT=/^(\d+) × (\d+) = (\d+) × (\d+)\. True or false\?$/,TF_ADD=/^(\d+) × (\d+) = (\d+) \+ (\d+)\. True or false\?$/,SC_EQUALS=/^Which (?:sum|multiplication) equals (\d+) × (\d+)\?$/,CS_DOES=/^Does (\d+) × (\d+) equal (\d+) × (\d+)\?$/,CS_CONTEXT_TRAYS=/^A [a-z]+ arranges (\d+) [a-z]+ with (\d+) [a-z]+ on each [a-z]+\. Does that give the same total as (\d+) [a-z]+ with (\d+) [a-z]+ on each [a-z]+\?$/;function pluralNumberWord$2(n){const t=numberToWords(n);return t.endsWith("x")?`${t}es`:`${t}s`}function arrayDimsFromVisual(n){const t=n.visualData;return!t||t.type!=="array"||!Number.isInteger(t.rows)||!Number.isInteger(t.cols
71)?null:{rows:t.rows,cols:t.cols}}function countRunData(n,t){const e=n*t,s=Array.from({length:n},(o,r)=>(r+1)*t);return{product:e,multiples:s,seq:s.join(", "),seqWords:s.map(numberToWords).join(", "),bPlural:pluralNumberWord$2(t),aWord:numberToWords(n),bWord:numberToWords(t)}}function arrayDemo(n){const{a:t,b:e}=n,{product:s,seq:o,seqWords:r,bPlural:a,aWord:i,bWord:l,multiples:u}=countRunData(t,e),d={a:t,b:e,product:s,...n.extraComputed??{}};u.forEach((h,f)=>{d[`run${f+1}`]=h});const m=()=>({type:"array",rows:t,cols:e});return buildExample({generatorId:GEN_ID$8,question:n.q,title:n.title,computed:d,steps:[{visual_data:m(),animation:n.animations[0],caption:`${t} rows of ${e} dots.`,spoken:`${capitalised(i)} rows of ${l} dots. Every row has ${l} dots, so we can count them in ${a}.`},{visual_data:m(),animation:n.animations[1],caption:`Count in ${a}: ${o}.`,spoken:`Let's count in ${a}, one row at a time: ${r}. ${capitalised(i)} ${a} make ${numberToWords(s)}.`},{visual_data:m(),animation:n.animations[2],caption:n.step3Caption,spoken:n.step3Spoken}]})}const FLIP_ANIM=["commute_build","commute_count","commute_flip"],COMPARE_ANIM=["array_build","array_count","array_total"];function handleTrueFalse(n){const t=matchInts(n.stem,TF_MULT),e=matchInts(n.stem,TF_ADD);let s,o,r,a,i,l;if(t)[s,o,r,a]=t,i=r*a,l="×";else if(e)[s,o,r,a]=e,i=r+a,l="+";else return null;if(s<2||s>10||o<2||o>10||r<1||r>10||a<1||a>10)return null;const u=s*o,d=u===i?"True":"False";if(d!==n.correctAnswer.trim()||d==="True"&&!(l==="×"&&r===o&&a===s))return null;const{aWord:h,bWord:f}=countRunData(s,o),g=numberToWords(u);if(d==="True")return arrayDemo({q:n,a:s,b:o,animations:FLIP_ANIM,title:`Is ${s} × ${o} = ${o} × ${s} true?`,step3Caption:`Swap to ${o} rows of ${s} — still ${u}. True!`,step3Spoken:`If we turn the dots round, ${h} rows of ${f} become ${f} rows of ${h}. Turning them round doesn't change how many dots there are, so both make ${g}. So it is true.`});const b=numberToWords(r),x=numberToWords(i),y=l==="×"?`But ${b} ${pluralNumberWord$2(a)} make ${x}, not ${g}. So it is false.`:`But ${b} add ${numberToWords(a)} makes only ${x}, not ${g}. So it is false.`;return arrayDemo({q:n,a:s,b:o,animations:COMPARE_ANIM,title:`Is ${s} × ${o} = ${r} ${l} ${a} true?`,step3Caption:`${r} ${l} ${a} makes ${i}, not ${u}. False.`,step3Spoken:y,extraComputed:{rhs:i}})}function handleSymbolicChoice(n){const t=matchInts(n.stem,SC_EQUALS);if(!t)return null;const[e,s]=t;if(e<2||e>10||s<2||s>10||`${s} × ${e}`!==n.correctAnswer.trim())return null;const{aWord:r,bWord:a,product:i}=countRunData(e,s),l=numberToWords(i),u=/multiplication/.test(n.stem)?"multiplication":"sum";return arrayDemo({q:n,a:e,b:s,animations:FLIP_ANIM,title:`Which ${u} equals ${e} × ${s}?`,step3Caption:`${s} × ${e} also makes ${i}. Match!`,step3Spoken:`Turn the dots round and we get ${a} rows of ${r}. They are the same dots, so they make the same total, ${l}. So ${a} times ${r} is the one that matches.`})}const ODD_ONE_OUT=/^Which does not equal (\d+) × (\d+)\?$/;function parseOptionMult(n){const t=n.trim().match(/^(\d+) × (\d+)$/);return t?{p:Number(t[1]),q:Number(t[2])}:null}function handleOddOneOut(n){const t=matchInts(n.stem,ODD_ONE_OUT);if(!t)return null;const[e,s]=t;if(e<2||e>10||s<2||s>10)return null;const o=e*s;if(!n.options||n.options.length<2)return null;const r=n.options.map(S=>({label:S,mult:parseOptionMult(S)}));if(r.some(S=>!S.mult))return null;const a=r.filter(S=>S.mult.p*S.mult.q!==o);if(a.length!==1)return null;const i=a[0];if(i.label.trim()!==n.correctAnswer.trim())return null;const{p:l,q:u}=i.mult;if(l<1||l>10||u<1||u>10)return null;const d=l*u,{aWord:m,bWord:h,product:f,seq:g,seqWords:b,bPlural:x}=countRunData(e,s),y=numberToWords(f),E=r.filter(S=>S!==i).map(S=>S.mult);if(E.some(({p:S,q:M})=>S<1||S>10||M<1||M>10))return null;const k=E.find(({p:S,q:M})=>!(S===s&&M===e)&&!(S===e&&M===s))??E[0];if(!k)return null;const T={};E.forEach(({p:S,q:M},L)=>{T[`m${L}p`]=S,T[`m${L}q`]=M});const A=E.map(({p:S,q:M})=>`${S} × ${M} = ${f}`).join(". "),N=E.map(({p:S,q:M})=>`${numberToWords(S)} times ${numberToWords(M)} is ${y}`).join(", and ");return buildExample({generatorId:GEN_ID$8,question:n,title:`Which does not equal ${e} × ${s}?`,computed:{ta:e,tb:s,product:f,op:l,oq:u,oddProduct:d,...T},steps:[{visual_data:{type:"array",rows:e,cols:s},animation:"array_count_all",caption:`${e} rows of ${s}. Count in ${x}
71: ${g}.`,spoken:`${capitalised(m)} rows of ${h} dots. Count in ${x}, one row at a time: ${b}. ${capitalised(m)} ${x} make ${y}.`},{visual_data:{type:"array",rows:k.p,cols:k.q},animation:"array_count_all",caption:`${A}. ${E.length>1?"They match.":"It matches."}`,spoken:`Now work out each option. ${capitalised(N)}. ${E.length>1?"Those match":"That matches"}.`},{visual_data:{type:"array",rows:l,cols:u},animation:"array_total",caption:`${l} rows of ${u} = ${d}, not ${f}. That's the odd one!`,spoken:`${capitalised(numberToWords(l))} rows of ${numberToWords(u)} makes ${numberToWords(d)}, not ${y}. That one does not equal ${m} times ${h}.`}]})}function handleComparison(n){const t=matchInts(n.stem,CS_DOES);if(!t)return null;const[e,s,o,r]=t;if(e<2||e>10||s<2||s>10||o<1||o>10||r<1||r>10)return null;const a=e*s,i=o*r,l=a===i?"Yes":"No";if(l!==n.correctAnswer.trim()||l==="Yes"&&!(o===s&&r===e))return null;const{aWord:d,bWord:m}=countRunData(e,s),h=numberToWords(a);if(l==="Yes")return arrayDemo({q:n,a:e,b:s,animations:FLIP_ANIM,title:`Does ${e} × ${s} equal ${s} × ${e}?`,step3Caption:`${s} rows of ${e} is also ${a}. Yes!`,step3Spoken:`Swap to ${m} rows of ${d}. Turning the dots round doesn't change how many there are, so both make ${h}. So yes, they are equal.`});const f=numberToWords(o),g=numberToWords(i);return arrayDemo({q:n,a:e,b:s,animations:COMPARE_ANIM,title:`Does ${e} × ${s} equal ${o} × ${r}?`,step3Caption:`${o} × ${r} makes ${i}, not ${a}. No.`,step3Spoken:`But ${f} ${pluralNumberWord$2(r)} make ${g}, not ${h}. So no, they are not equal.`,extraComputed:{rhs:i}})}function handleContextTrays(n){const t=matchInts(n.stem,CS_CONTEXT_TRAYS);if(!t)return null;const[e,s,o,r]=t;return e<2||e>10||s<2||s>10||o!==s||r!==e||n.correctAnswer.trim()!=="Yes"?null:arrayDemo({q:n,a:e,b:s,animations:FLIP_ANIM,title:`Does ${e} trays of ${s} give the same total as ${s} trays of ${e}?`,step3Caption:`${s} rows of ${e} is also ${e*s}. Yes!`,step3Spoken:`Swap to ${numberToWords(s)} rows of ${numberToWords(e)}. Turning the dots round doesn't change how many there are, so both make ${numberToWords(e*s)}. So yes, the total is the same.`})}const mdP2M03Generator={id:GEN_ID$8,microSkillId:"MD.P2.M03",forms:["equation_completion","count_equal_groups","true_false_check","symbolic_choice","comparison_statement"],generate(n){if(n.mathematicalForm==="true_false_check")return handleTrueFalse(n);if(n.mathematicalForm==="symbolic_choice")return handleSymbolicChoice(n)??handleOddOneOut(n);if(n.mathematicalForm==="comparison_statement")return handleContextTrays(n)??handleComparison(n);let t,e,s;const o=matchInts(n.stem,EQ_BLANK_RIGHT),r=matchInts(n.stem,EQ_BLANK_MID),a=o||r?null:matchInts(n.stem,EQ_BLANK_LEFT),i=matchInts(n.stem,ARRAY_FLIP);if(o){const[x,y,E]=o;if(E!==y)return null;t=x,e=y,s=x}else if(r){const[x,y,E]=r;if(E!==x)return null;t=x,e=y,s=y}else if(a){const[x,y,E]=a;if(y!==x)return null;t=E,e=x,s=E}else if(i){const[x,y,E,k]=i;if(E!==y||k!==x)return null;const T=arrayDimsFromVisual(n);if(T&&(T.rows!==x||T.cols!==y))return null;t=x,e=y,s=x*y}else return null;if(t<2||t>10||e<2||e>10)return null;const l=t*e;if(s!==answerInt(n))return null;const u=Array.from({length:t},(x,y)=>(y+1)*e),d=u.join(", "),m=u.map(numberToWords).join(", "),h=pluralNumberWord$2(e),f=numberToWords(t),g=numberToWords(e),b={a:t,b:e,product:l,answer:s};return u.forEach((x,y)=>{b[`run${y+1}`]=x}),buildExample({generatorId:this.id,question:n,title:`Why does ${t} × ${e} = ${e} × ${t}?`,computed:b,steps:[{visual_data:{type:"array",rows:t,cols:e},animation:"commute_build",caption:`${t} rows of ${e} dots.`,spoken:`${capitalised(f)} rows of ${g} dots. Every row has ${g} dots, so we can count them in ${h}.`},{visual_data:{type:"array",rows:t,cols:e},animation:"commute_count",caption:`Count in ${h}: ${d}.`,spoken:`Let's count in ${h}, one row at a time: ${m}. ${capitalised(f)} ${h} make ${numberToWords(l)}.`},{visual_data:{type:"array",rows:t,cols:e},animation:"commute_flip",caption:`${t} rows of ${e} OR ${e} rows of ${t} — both ${l}.`,spoken:`The same number of dots can be ${f} rows of ${g}, or ${g} rows of ${f}. Both make ${numberToWords(l)}. So ${f} times ${g} equals ${g} times ${f}.`}]})}},DIV_EQ$1=/^(\d+) ÷ (\d+) = __$/,FACT_SO=/^(\d+) × (\d+) = (\d+), so (\d+) ÷ (\d+) = __$/,FACT_AN
71D=/^(\d+) × (\d+) = (\d+) and (\d+) ÷ (\d+) = __$/,ARRAY_SHOWS=/^This array shows (\d+) × (\d+) = (\d+)\. What is (\d+) ÷ (\d+)\?$/,CONTEXT_KNOWS=/^[A-Za-z]+ knows (\d+) × (\d+) = (\d+)\. What is (\d+) ÷ (\d+)\?$/,CONTEXT_SO_SHARE=/^(\d+) × (\d+) = (\d+)\. So if (\d+) [a-z]+ are shared equally between (\d+) friends, how many [a-z]+ does each friend get\?$/;function parseDivision(n){const t=normaliseStem(n),e=t.match(DIV_EQ$1);if(e){const o=Number(e[1]),r=Number(e[2]);return r===0||o%r!==0?null:{c:o,d:r}}const s=t.match(FACT_SO)??t.match(FACT_AND)??t.match(ARRAY_SHOWS)??t.match(CONTEXT_KNOWS)??t.match(CONTEXT_SO_SHARE);if(s){const[o,r,a,i,l]=s.slice(1).map(Number);return o*r!==a||a!==i||l===0||i%l!==0?null:{c:i,d:l,fact:[o,r]}}return null}const GENERATOR_ID$6="MD.P2.M04.share-into-rows.v1";function buildDivisionArray(n,t,e,s,o,r,a){if(e<2||e>10||t%e!==0)return null;const i=t/e;if(i<1||i>10)return null;const l=e,u=i,d={dividend:t,divisor:e,quotient:i,rows:l,cols:u,...s},m=numberToWords(t),h=numberToWords(e);return buildExample({generatorId:GENERATOR_ID$6,question:n,title:o,computed:d,steps:[{visual_data:{type:"array",rows:l,cols:u},animation:"divide_show",caption:`Share ${t} into ${e} equal rows.`,spoken:`${capitalised(m)} shared into ${h} equal rows. How many in each row?`},{visual_data:{type:"array",rows:l,cols:u},animation:"divide_group",caption:`${e} equal rows.`,spoken:`We make ${h} equal rows, the same number in each.`},{visual_data:{type:"array",rows:l,cols:u},animation:"divide_take",caption:r,spoken:a}]})}const SKIP_COUNT_WORDS={2:"twos",5:"fives",10:"tens"};function buildSkipCountDivision(n,t,e){const s=SKIP_COUNT_WORDS[e];if(!s||t%e!==0)return null;const o=t/e;if(o<2||o>10)return null;const r=Array.from({length:Math.min(o-1,5)},(d,m)=>(m+1)*e),a={dividend:t,divisor:e,quotient:o,rows:o,cols:e,answer:o};r.forEach((d,m)=>{a[`run${m+1}`]=d});const i=numberToWords(t),l=numberToWords(o),u={type:"array",rows:o,cols:e};return buildExample({generatorId:GENERATOR_ID$6,question:n,title:`How do we work out ${t} ÷ ${e}?`,computed:a,steps:[{visual_data:u,animation:"group_count_show",caption:`${t} ÷ ${e}: how many ${s} make ${t}?`,spoken:`${capitalised(i)} divided by ${numberToWords(e)}. That means: how many ${s} make ${i}? We count up in ${s} and count how many.`},{visual_data:u,animation:"group_count_group",caption:`Count in ${s}: ${r.join(", ")}…`,spoken:`Each row is one ${numberToWords(e)}. Count in ${s}: ${r.map(numberToWords).join(", ")}. Keep going until we reach ${i}.`},{visual_data:u,animation:"group_count_take",caption:`${o} ${s} make ${t}. So ${t} ÷ ${e} = ${o}.`,spoken:`We reach ${i}! That took ${l} ${s}. ${capitalised(l)} ${s} make ${i}, so ${i} divided by ${numberToWords(e)} is ${l}. The times fact and the division use the same three numbers.`}]})}function tryNumericDivision(n){const t=normaliseStem(n.stem).match(DIV_EQ$1);if(t){const d=Number(t[1]),m=Number(t[2]);if(m!==0&&d%m===0&&d/m===answerInt(n)){const h=buildSkipCountDivision(n,d,m);if(h)return h}}const e=parseDivision(n.stem);if(!e)return null;const{c:s,d:o,fact:r}=e;if(o===0||s%o!==0)return null;const a=s/o;if(a!==answerInt(n))return null;const i=numberToWords(s),l=numberToWords(o),u=numberToWords(a);if(r){const[d,m]=r;return buildDivisionArray(n,s,o,{answer:a,factor_a:d,factor_b:m},`How do we work out ${s} ÷ ${o}?`,`${o} rows of ${a}: that's ${d} × ${m} = ${s}. So ${s} ÷ ${o} = ${a}.`,`Each row has ${u}. ${capitalised(l)} rows of ${u} is the fact we were given: ${numberToWords(d)} times ${numberToWords(m)} is ${i}. Turned around, ${i} divided by ${l} is ${u}.`)}return buildDivisionArray(n,s,o,{answer:a},`How do we work out ${s} ÷ ${o}?`,`Each row has ${a}. So ${s} ÷ ${o} = ${a}.`,`Dividing is sharing into equal groups. We shared the ${i} dots equally into ${l} rows, and each row has ${u}. So ${i} divided by ${l} is ${u}.`)}const FACT_SO_DIVIDEND_BLANK=/^(\d+) × (\d+) = (\d+), so __ ÷ (\d+) = (\d+)$/,FACT_SO_DIVISOR_BLANK=/^(\d+) × (\d+) = (\d+), so (\d+) ÷ __ = (\d+)$/;function tryFactSoDividendBlank(n){const t=matchInts(n.stem,FACT_SO_DIVIDEND_BLANK);if(!t)return null;const[e,s,o,r,a]=t;return e*s!==o||r===0||o%r!==0||o/r!==a||o!==answerInt(n)?null:buildDivisionArray(n,o,r,{answer:o},`What is the missing number in __ ÷ ${r} = ${a}?`,`${r} rows of ${a} = ${o}. So the missing number is ${o}.`,`To find the whole, we put the rows back together. ${capitalised(numberToWords(r))} rows of ${numberToWords(a)} makes ${numberToWords(o)}, so the missing number is ${numberToWords(o)}.`)}function tryFactSoDivisorBlank(n){const t=matchInts(n.stem,FACT_SO_DIVISOR_BLANK);if(!t)return null;const[e,s,o,r,a]=t;if(e*s!==o||o!==r||a===0||o%a!==0)return null;const i=o/a;return!(i===e||i===s)||i!==answerInt(n)?null:buildDivisionArray(n,o,i,{answer:i},`What is the missing number in ${o} ÷ __ = ${a}?`,`${i} equal rows of ${a}. So the missing number is ${i}.`,`We shared ${numberToWords(o)} into ${numberToWords(i)} equal rows of ${numberToWords(a)}. So the missing number is ${numberToWords(i)}.`)}const MISC_NOT_HELP=/^Which times fact does NOT help you work out (\d+) ÷ (\d+)\?$/;function parseFactOption(n){const t=n.trim().match(/^(\d+) × (\d+) = (\d+)$/);return t?{p:Number(t[1]),q:Number(t[2]),r:Number(t[3])}:null}function tryMiscNotHelp(n){const t=matchInts(n.stem,MISC_NOT_HELP);if(!t)return null;const[e,s]=t;if(s<2||s>10||e%s!==0)return null;const o=e/s;if(o<1||o>10||!n.options||n.options.length<2)return null;const r=n.options.map(m=>({label:m,fact:parseFactOption(m)}));if(r.some(m=>!m.fact))return null;const a=r.filter(m=>m.fact.p*m.fact.q!==e);if(a.length!==1)return null;const i=a[0];if(i.label.trim()!==n.correctAnswer.trim())return null;const{p:l,q:u,r:d}=i.fact;return d!==l*u||l<1||l>10||u<1||u>
7110?null:buildDivisionArray(n,e,s,{non_help_p:l,non_help_q:u,non_help_r:d},`Which fact does not help with ${e} ÷ ${s}?`,`${s} × ${o} = ${e} helps. ${l} × ${u} = ${d} does not.`,`${capitalised(numberToWords(s))} times ${numberToWords(o)} equals ${numberToWords(e)} — that helps. But ${numberToWords(l)} times ${numberToWords(u)} makes ${numberToWords(d)}, not ${numberToWords(e)}, so it does not help.`)}const FACT_CHOICE=/^Which fact helps you work out (\d+) ÷ (\d+)\?$/;function tryFactChoice(n){const t=normaliseStem(n.stem).match(FACT_CHOICE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(s<2||s>10||e%s!==0)return null;const o=e/s;if(o<1||o>10)return null;const r=normaliseStem(n.correctAnswer),a=`${s} × ${o} = ${e}`,i=`${o} × ${s} = ${e}`;if(r!==a&&r!==i)return null;const l=numberToWords(e),u=numberToWords(s),d=numberToWords(o);return buildDivisionArray(n,e,s,{answer:o},`Which fact helps with ${e} ÷ ${s}?`,`${s} × ${o} = ${e}. So ${e} ÷ ${s} = ${o}.`,`The same ${u} rows of ${d} make ${u} times ${d} equals ${l}. That times fact helps: ${l} divided by ${u} is ${d}.`)}const TF_MULT_MEANS=/^True or false\? (\d+) × (\d+) = (\d+) means (\d+) ÷ (\d+) = (\d+)$/,TF_MULT_IF_THEN=/^True or false\? If (\d+) × (\d+) = (\d+) then (\d+) ÷ (\d+) = (\d+)$/,TF_SUB_HELPS=/^True or false\? (\d+) - (\d+) = (\d+) helps you work out (\d+) ÷ (\d+)$/;function tryTrueFalse(n){const t=normaliseStem(n.stem),e=t.match(TF_MULT_MEANS)??t.match(TF_MULT_IF_THEN);if(e){const[o,r,a,i,l,u]=e.slice(1).map(Number);if(l<2||l>10||i%l!==0)return null;const d=i/l;if(d<1||d>10)return null;const m=o*r===a&&a===i&&d===u&&(l===o||l===r);if((m?"True":"False")!==n.correctAnswer||!m)return null;const f=numberToWords(i),g=numberToWords(l),b=numberToWords(u);return buildDivisionArray(n,i,l,{answer:d,product:a},`Is ${i} ÷ ${l} = ${u} true?`,`${o} × ${r} = ${i} matches ${i} ÷ ${l} = ${u}. True.`,`The ${g} rows show ${numberToWords(o)} times ${numberToWords(r)} equals ${f}. Turned around, ${f} divided by ${g} is ${b}. So the claim is true.`)}const s=t.match(TF_SUB_HELPS);if(s){const o=Number(s[4]),r=Number(s[5]);if(r<2||r>10||o%r!==0)return null;const a=o/r;if(a<1||a>10||n.correctAnswer!=="False")return null;const i=numberToWords(o),l=numberToWords(r),u=numberToWords(a);return buildDivisionArray(n,o,r,{answer:a},`Does taking away help with ${o} ÷ ${r}?`,`Sharing uses ${r} × ${a} = ${o}, not taking away. False.`,`To share ${i} into ${l} rows we use a times fact: ${l} times ${u} equals ${i}. Taking away does not help, so the claim is false.`)}return null}const mdP2M04Generator={id:GENERATOR_ID$6,microSkillId:"MD.P2.M04",forms:["symbolic_calculation","equation_completion","symbolic_numeric_entry","varied_symbolic_calculation","context_calculation","symbolic_choice","true_false_check","misconception_contrast_probe"],generate(n){return tryNumericDivision(n)??tryFactSoDividendBlank(n)??tryFactSoDivisorBlank(n)??tryFactChoice(n)??tryTrueFalse(n)??tryMiscNotHelp(n)}},TIMES_EQ=/^(\d+) × (\d+) = __$/,EQ_TIMES=/^__ = (\d+) × (\d+)$/,MISS_RIGHT=/^(\d+) × __ = (\d+)$/,MISS_LEFT=/^__ × (\d+) = (\d+)$/;function pluralNumberWord$1(n){const t=numberToWords(n);return t.endsWith("x")?`${t}es`:`${t}s`}const SKIP_TABLES=new Set([2,5,10]);function skipCountShape(n,t){return SKIP_TABLES.has(n)?{unit:n,times:t}:{unit:t,times:n}}const COUNT_ALTOGETHER=/^How many [a-z]+ (?:are there )?altogether\?$/,DOTS_IN_ARRAY=/^How many dots (?:are )?in the array\?$/,TF_FACT=/^(\d+) × (\d+) = (\d+)\. True or False\?$/,BAGS_OF$1=/^[A-Za-z]+ come in bags of (\d+)\. [A-Z][a-z]+ has (\d+) [a-z]+\. How many bags does (?:he|she|they) have\?$/,NUMBER_LINE_STEPS=/^This number line counts up in equal steps of (\d+), starting at (\d+)\. What number belongs where the question mark is\?$/,LOTS_OF_CMP=/^Which is greater: (\d+) lots of (\d+), or (\d+) lots of (\d+)\?$/,LARGER_TIMES_CMP=/^Which is larger: (\d+) × (\d+) or (\d+) × (\d+)\?$/,COUNT_JUMPS=/^Count (\d+) jumps of (\d+)\. Where do you land\?$/;function readFactorsFromVisual(n){const t=n.visualData;if(!t)return null;const e=n.stem;if(t.type==="array"&&(DOTS_IN_ARRAY.test(e.trim())||COUNT_ALTOGETHER.test(e.trim()))&&Number.isInteger(t.rows)&&Number.isInteger(t.cols
71))return{rows:t.rows,cols:t.cols,expectedAnswer:t.rows*t.cols,isMissingFactor:!1,missingSide:null};if(t.type==="objects_grid"&&COUNT_ALTOGETHER.test(e.trim())&&Number.isInteger(t.count)&&Number.isInteger(t.group_size)&&t.group_size>0&&t.count%t.group_size===0){const s=t.group_size;return{rows:t.count/t.group_size,cols:s,expectedAnswer:t.count,isMissingFactor:!1,missingSide:null}}return null}function parseFactors(n){const t=matchInts(n.stem,TIMES_EQ);if(t){const[r,a]=t;return{rows:r,cols:a,expectedAnswer:r*a,isMissingFactor:!1,missingSide:null}}const e=matchInts(n.stem,EQ_TIMES);if(e){const[r,a]=e;return{rows:r,cols:a,expectedAnswer:r*a,isMissingFactor:!1,missingSide:null}}const s=matchInts(n.stem,MISS_RIGHT);if(s){const[r,a]=s;if(r===0||a%r!==0)return null;const i=a/r;return{rows:r,cols:i,expectedAnswer:i,isMissingFactor:!0,missingSide:"cols"}}const o=matchInts(n.stem,MISS_LEFT);if(o){const[r,a]=o;if(r===0||a%r!==0)return null;const i=a/r;return{rows:i,cols:r,expectedAnswer:i,isMissingFactor:!0,missingSide:"rows"}}return null}function tryTrueFalseFact(n){const t=matchInts(n.stem,TF_FACT);if(!t)return null;const[e,s,o]=t;if(e<2||e>10||s<1||s>10)return null;const r=e*s,a=r===o?"True":"False";if(a!==n.correctAnswer.trim())return null;const i=Array.from({length:e-1},(h,f)=>(f+1)*s),l=i.join(", "),u=numberToWords(s),d=pluralNumberWord$1(s),m={rows:e,cols:s,product:r,stated:o};return i.forEach((h,f)=>{m[`run${f+1}`]=h}),buildExample({generatorId:mdP2M05Generator.id,question:n,title:`Is ${e} × ${s} = ${o}?`,computed:m,steps:[{visual_data:{type:"array",rows:e,cols:s},animation:"array_build",caption:`${e} rows. Each row has ${s}.`,spoken:`${capitalised(numberToWords(e))} rows of dots. Each row has the same number — ${u}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_skip_count",caption:`Count in ${d}: ${l}…`,spoken:`We count the rows in ${d}. One row is ${u}. Two rows is ${numberToWords(s*2)}.`},{visual_data:{type:"array",rows:e,cols:s},animation:"array_product",caption:a==="True"?`${e} rows of ${s} = ${r}. True.`:`${e} rows of ${s} = ${r}, not ${o}. False.`,spoken:a==="True"?`${capitalised(numberToWords(e))} rows of ${u} is ${numberToWords(r)}. That matches the fact, so it is true.`:`${capitalised(numberToWords(e))} rows of ${u} is ${numberToWords(r)}, not ${numberToWords(o)}. So the fact is false.`}]})}function tryBagsOf(n){const t=matchInts(n.stem,BAGS_OF$1);if(!t)return null;const[e,s]=t;if(e<1||e>10||s%e!==0)return null;const o=s/e;if(o<2||o>10||o!==answerInt(n))return null;const r=o,a=e,i=numberToWords(s),l=numberToWords(e),u=numberToWords(o),d=Array.from({length:Math.min(o-1,4)},(h,f)=>(f+1)*e),m={total:s,bagSize:e,bags:o,rows:r,cols:a,answer:o};return d.forEach((h,f)=>{m[`run${f+1}`]=h}),buildExample({generatorId:mdP2M05Generator.id,question:n,title:`How many bags of ${e} make ${s}?`,computed:m,steps:[{visual_data:{type:"array",rows:r,cols:a},animation:"group_count_show",caption:`${s} sweets, in bags of ${e}.`,spoken:`${capitalised(i)} sweets, sorted into bags of ${l}. How many bags does that make?`},{visual_data:{type:"array",rows:r,cols:a},animation:"group_count_group",caption:`Each row is one bag. Count in ${e}s: ${d.join(", ")}…`,spoken:`Each row is one bag of ${l}. Count up in ${pluralNumberWord$1(e)}: ${d.map(numberToWords).join(", ")}. Keep going until we reach ${i}.`},{visual_data:{type:"array",rows:r,cols:a},animation:"group_count_take",caption:`${o} bags of ${e} make ${s}. So there are ${o} bags.`,spoken:`We reach ${i}! Now count the bags: there are ${u}. So ${i} sweets in bags of ${l} makes ${u} bags.`}]})}function tryNumberLineIdentify(n){const t=matchInts(n.stem,NUMBER_LINE_STEPS);if(!t)return null;const[e,s]=t,o=n.visualData;if(!o||o.type!=="number_line")return null;const{min:r,max:a,marked:i,unknown:l}=o;if(r!==s||!Array.isArray(i)||i.length===0||l==null||!Number.isInteger(l)||e<1||e>100)return null;const u=[r,...i],d=[...u,l];for(let y=1;y<d.length;y++)if(d[y]-d[y-1]!==e)return null;if(l<r||l>a||l!==answerInt(n))return null;const m=u[u.length-1],h={min:r,max:a,step:e,unknown:l,lastKnown:m,answer:l};
71i.forEach((y,E)=>{h[`mark${E+1}`]=y});const f=y=>Array.from({length:y.length-1},(E,k)=>({from:y[k],to:y[k+1],label:`+${e}`})),g=i.map(numberToWords).join(", "),b=numberToWords(e),x=numberToWords(r);return buildExample({generatorId:mdP2M05Generator.id,question:n,title:`Count up in ${e}s from ${r} — what comes next?`,computed:h,steps:[{visual_data:{type:"number_line",min:r,max:a,marked:i,hops:[],unknown:null},animation:"count_show",caption:`Start at ${r}. Count up in equal steps of ${e}.`,spoken:`This number line goes up in equal steps, so every jump is the same. It starts at ${x} and goes up in steps of ${b}.`},{visual_data:{type:"number_line",min:r,max:a,marked:i,hops:f(u),unknown:null},animation:"count_hops",caption:`${i.join(", ")}… each jump is ${e}.`,spoken:`${capitalised(g)}… every jump is the same size, ${b}.`},{visual_data:{type:"number_line",min:r,max:a,marked:i,hops:f(d),unknown:l},animation:"count_land",caption:`${m} + ${e} = ${l}.`,spoken:`${capitalised(numberToWords(m))} plus ${b} more is ${numberToWords(l)}. That is the missing number.`}]})}function tryLotsOfComparison(n){const t=matchInts(n.stem,LOTS_OF_CMP);if(!t)return null;const[e,s,o,r]=t;if(e<2||e>10||s<1||s>10||o<2||o>10||r<1||r>10)return null;const a=e*s,i=o*r;if(a===i)return null;const l=a>i?`${e} lots of ${s}`:`${o} lots of ${r}`;if(l!==n.correctAnswer.trim())return null;const u=Math.max(a,i),d=Math.min(a,i),m=Array.from({length:e},(g,b)=>(b+1)*s),h=Array.from({length:o},(g,b)=>(b+1)*r),f={a1:e,b1:s,t1:a,a2:o,b2:r,t2:i,bigger:u,smaller:d};return m.forEach((g,b)=>{f[`run1_${b+1}`]=g}),h.forEach((g,b)=>{f[`run2_${b+1}`]=g}),buildExample({generatorId:mdP2M05Generator.id,question:n,title:`Which is greater: ${e} lots of ${s}, or ${o} lots of ${r}?`,computed:f,steps:[{visual_data:{type:"array",rows:e,cols:s},animation:"array_count_all",caption:`Count in ${pluralNumberWord$1(s)}: ${m.join(", ")} = ${a}.`,spoken:`First, count in ${pluralNumberWord$1(s)}: ${m.map(numberToWords).join(", ")}. ${capitalised(numberToWords(e))} lots of ${numberToWords(s)} is ${numberToWords(a)}.`},{visual_data:{type:"array",rows:o,cols:r},animation:"array_count_all",caption:`Count in ${pluralNumberWord$1(r)}: ${h.join(", ")} = ${i}.`,spoken:`Now, count in ${pluralNumberWord$1(r)}: ${h.map(numberToWords).join(", ")}. ${capitalised(numberToWords(o))} lots of ${numberToWords(r)} is ${numberToWords(i)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:"write_symbol",caption:`${u} is greater than ${d}. ${l} wins.`,spoken:`${capitalised(numberToWords(u))} is greater than ${numberToWords(d)}. Work out each total first, then compare.`}]})}function tryLargerTimesComparison(n){const t=matchInts(n.stem,LARGER_TIMES_CMP);if(!t)return null;const[e,s,o,r]=t;if(e<2||e>10||s<1||s>10||o<2||o>10||r<1||r>10)return null;const a=e*s,i=o*r,l=a===i,u=l?"They are equal":a>i?`${e} × ${s}`:`${o} × ${r}`;if(u!==n.correctAnswer.trim())return null;const d=skipCountShape(e,s),m=skipCountShape(o,r),h=Array.from({length:d.times},(k,T)=>(T+1)*d.unit),f=Array.from({length:m.times},(k,T)=>(T+1)*m.unit),g=Math.max(a,i),b=Math.min(a,i),x={a1:e,b1:s,t1:a,a2:o,b2:r,t2:i,bigger:g,smaller:b};h.forEach((k,T)=>{x[`run1_${T+1}`]=k}),f.forEach((k,T)=>{x[`run2_${T+1}`]=k});const y=l?`${a} = ${i}. They are equal.`:`${g} is larger than ${b}. ${u} wins.`,E=l?`Both make ${numberToWords(a)}. They are equal.`:`${capitalised(numberToWords(g))} is larger than ${numberToWords(b)}. Work out each total first, then compare.`;return buildExample({generatorId:mdP2M05Generator.id,question:n,title:`Which is larger: ${e} × ${s} or ${o} × ${r}?`,computed:x,steps:[{visual_data:{type:"array",rows:d.times,cols:d.unit},animation:"array_count_all",caption:`${e} × ${s}: count in ${pluralNumberWord$1(d.unit)}: ${h.join(", ")}.`,spoken:`First, ${numberToWords(e)} times ${numberToWords(s)}. Count in ${pluralNumberWord$1(d.unit)}: ${h.map(numberToWords).join(", ")}. ${capitalised(numberToWords(e))} times ${numberToWords(s)} is ${numberToWords(a)}.`},{visual_data:{type:"array",rows:m.times,cols:m.unit},animation:"array_count_all",caption:`${o} × ${r}: count in ${pluralNumberWord$1(m.unit)}: ${f.join(", ")}.`,spoken:`Now, ${numberToWords(o)} times ${numberToWords(r)}. Count in ${pluralNumberWord$1(m.unit)}: ${f.map(numberToWords).join(", ")}. ${capitalised(numberToWords(o))} times ${numberToWords(r)} is ${numberToWords(i)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:l?"=":a>i?">":"<"},animation:"write_symbol",caption:y,spoken:E}]})}function tryCountJumps(n){const t=matchInts(n.stem,COUNT_JUMPS);if(!t)return null;const[e,s]=t;if(s<1||s>100||e<1||e>10)return null;const o=n.visualData;if(!o||o.type!=="number_line")return null;const{min:r,max:a,marked:i,unknown:l}=o;if(!Array.isArray(i)||i.length===0||l==null||!Number.isInteger(l))return null;const u=i[0];if(r!==u||u+e*s!==l||l<r||l>a||l!==answerInt(n))return null;const d=Array.from({length:e},(y,E)=>u+(E+1)*s),m={min:r,max:a,step:s,count:e,start:u,unknown:l,answer:l};d.forEach((y,E)=>{m[`land${E+1}`]=y});const h=y=>Array.from({length:y},(E,k)=>({from:u+k*s,to:u+(k+1)*s,label:`+${s}`})),f=d.map(numberToWords).join(", "),g=numberToWords(s),b=numberToWords(u),x=numberToWords(e);return buildExample({generatorId:mdP2M05Generator.id,question:n,title:`Count ${e} jumps of ${s} — where do you land?`,computed:m,steps:[{visual_data:{type:"number_line",min:r,max:a,marked:[u],hops:[],unknown:null},animation:"count_show",caption:`Start at ${u}. Jump ${e} times of ${s}.`,spoken:`We start at ${b} and make ${x} jumps, each one ${g}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[u],hops:h(e-1),unknown:null},animation:"count_hops",caption:`${d.slice(0,-1).join(", ")}… each jump is ${s}.`,spoken:`${capitalised(f.split(", ").slice(0,-1).join(", "))}… every jump is the same size, ${g}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[u],hops:h(e),unknown:l},animation:"count_land",caption:`${e} jumps of ${s} lands on ${l}.`,spoken:`${capitalised(x)} jumps of ${g}: we land on ${numberToWords(l)}.`}]})}function buildBigFactorLineDemo(n,t,e,s){const o=numberToWords(t),r=numberToWords(e),a=numberToWords(s),i=[t,t*2,t*3],l={known:t,product:s,jumps:e,answer:e};
71i.forEach((m,h)=>{l[`run${h+1}`]=m});const u=m=>Array.from({length:m},(h,f)=>({from:f*t,to:(f+1)*t,label:`+${t}`})),d=(m,h)=>({type:"number_line",min:0,max:s,step:t,marked:[0],hops:m,unknown:h});return buildExample({generatorId:mdP2M05Generator.id,question:n,title:`${t} × what makes ${s}?`,computed:l,steps:[{visual_data:d([],null),animation:"count_show",caption:`Count up in ${t}s until you reach ${s}.`,spoken:`${capitalised(o)} times what number makes ${a}? Count up in ${o}s until you reach ${a} — and count the jumps as you go.`},{visual_data:d(u(e-1),null),animation:"count_hops",caption:`${i.join(", ")} … keep jumping in ${t}s.`,spoken:`Every jump adds ${o}: ${i.map(numberToWords).join(", ")} … Keep going until ${a} lands.`},{visual_data:d(u(e),s),animation:"count_land",caption:`${e} jumps of ${t} reach ${s}. So ${t} × ${e} = ${s}.`,spoken:`It takes ${r} jumps to reach ${a}. So ${o} times ${r} equals ${a} — the missing number is ${r}.`}]})}function buildFindColsDemo(n,t,e,s){const o=numberToWords(s),r=numberToWords(t),a=numberToWords(e),i={rows:t,cols:e,product:s,answer:e};return buildExample({generatorId:mdP2M05Generator.id,question:n,title:`How do we work out ${t} × __ = ${s}?`,computed:i,steps:[{visual_data:{type:"array",rows:t,cols:e},animation:"divide_show",caption:`Share ${s} into ${t} equal rows.`,spoken:`${capitalised(o)} shared into ${r} equal rows. How many in each row?`},{visual_data:{type:"array",rows:t,cols:e},animation:"divide_group",caption:`${t} equal rows.`,spoken:`We make ${r} equal rows, the same number in each.`},{visual_data:{type:"array",rows:t,cols:e},animation:"divide_take",caption:`Each row has ${e}. So ${t} × ${e} = ${s}.`,spoken:`Each of the ${r} rows has ${a}. So ${r} times ${a} equals ${o}.`}]})}function buildFindRowsDemo(n,t,e,s,o="left"){const r=Array.from({length:t-1},(m,h)=>(h+1)*e),a=r.join(", "),i=numberToWords(e),l=pluralNumberWord$1(e),u=numberToWords(t),d={rows:t,cols:e,product:s,answer:t};return r.forEach((m,h)=>{d[`run${h+1}`]=m}),buildExample({generatorId:mdP2M05Generator.id,question:n,title:`How many ${l} make ${s}?`,computed:d,steps:[{visual_data:{type:"array",rows:t,cols:e},animation:"array_build",caption:`Each row has ${e}. Build rows to reach ${s}.`,spoken:`Each row has ${i}. We build rows until we reach ${numberToWords(s)}.`},{visual_data:{type:"array",rows:t,cols:e},animation:"array_skip_count",caption:`Count in ${l}: ${a}…`,spoken:`We count in ${l}. One row is ${i}. Two rows is ${numberToWords(e*2)}.`},{visual_data:{type:"array",rows:t,cols:e,...o==="right"?{conclude_label:`${e} × ${t} = ${s}`}:{}},animation:"array_product",caption:o==="right"?`That's ${t} rows to reach ${s}. So ${e} × __ = ${s}, __ = ${t}.`:`That's ${t} rows to reach ${s}. So __ × ${e} = ${s}, __ = ${t}.`,spoken:`That took ${u} rows to reach ${numberToWords(s)}. So the missing number is ${u}.`}]})}const mdP2M05Generator={id:"MD.P2.M05.array-skip-count.v1",microSkillId:"MD.P2.M05",forms:["fact_symbolic_retrieval","symbolic_choice","equation_completion","count_equal_groups","true_false_check","context_calculation","number_line_identify","comparison_statement"],generate(n){if(n.mathematicalForm==="true_false_check")return tryTrueFalseFact(n);if(n.mathematicalForm==="number_line_identify")return tryNumberLineIdentify(n);if(n.mathematicalForm==="comparison_statement")return tryLotsOfComparison(n)??tryLargerTimesComparison(n);if(n.mathematicalForm==="context_calculation"){const M=tryBagsOf(n);if(M)return M}if(n.mathematicalForm==="count_equal_groups"){const M=tryCountJumps(n);if(M)return M}const t=parseFactors(n),e=t??readFactorsFromVisual(n);if(!e)return null;const{rows:s,cols:o,expectedAnswer:r,isMissingFactor:a,missingSide:i}=e;if(a&&i){const M=i==="cols"?o:s,L=i==="cols"?s:o;if(M>10&&M<=15&&[2,5,10].includes(L)&&M===r&&r===answerInt(n))return buildBigFactorLineDemo(n,L,M,s*o)}if(s<2||s>10||o<1||o>10)return null;const l=s*o;if(r!==answerInt(n))return null;if(a){if(i==="cols"&&SKIP_TABLES.has(s))return buildFindRowsDemo(n,o,s,l,"right");if(i==="cols")return buildFindColsDemo(n,s,o,l);if(i==="rows")return buildFindRowsDemo(n,s,o,l)}const u=s,d=o;let{unit:m,times:h}=t?skipCountShape(u,d):{unit:d,times:u};h<2&&({unit:m,times:h}={unit:d,times:u});const f=Array.from({length:h-1},(M,L)=>(L+1)*m),g=f.join(", "),b=numberToWords(m),x=pluralNumberWord$1(m),y=m===1?"one dot":`${b} dots`,E=m*2,k={rows:h,cols:m,a:u,b:d,product:l,answer:r,twoRows:E};f.forEach((M,L)=>{k[`run${L+1}`]=M});const T={type:"array",rows:h,cols:m},A=numberToWords(h),N=numberToWords(u),S=numberToWords(d);return buildExample({generatorId:this.id,question:n,title:`What is ${u} × ${d}?`,computed:k,steps:[{visual_data:T,animation:"array_build",caption:`${h} rows. Each row has ${m}.`,spoken:`${capitalised(N)} times ${S}: ${A} rows of dots. Each row has the same number — ${y}.`},{visual_data:T,animation:"array_skip_count",caption:`Count in ${x}
71: ${g}…`,spoken:`We count the rows in ${x}, ${A} times. One row is ${b}. Two rows is ${numberToWords(E)}.`},{visual_data:{...T,conclude_label:`${u} × ${d} = ${l}`},animation:"array_product",caption:`${h} rows of ${m} = ${l}. So ${u} × ${d} = ${l}.`,spoken:`${capitalised(A)} rows of ${b} is ${numberToWords(l)}. ${capitalised(A)} ${x} make ${numberToWords(l)}.`}]})}},BLUE$6="#2563eb";function leadingVerdict(n){const t=/^\s*(yes|no)\b/i.exec(normaliseStem(n));return t?t[1].toLowerCase():null}function halfJudgmentFromVisual(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t;return e.total_parts!==2||typeof e.equal!="boolean"?null:{shape:e.shape,equal:e.equal}}const fdpP1M01Generator={id:"FDP.P1.M01.is-this-half.v1",microSkillId:"FDP.P1.M01",forms:["comparison_statement","visual_quantity_to_symbol","misconception_contrast_probe"],generate(n){const t=halfJudgmentFromVisual(n);if(!t)return null;const{shape:e,equal:s}=t,o=leadingVerdict(n.correctAnswer);if(!o||s&&o!=="yes"||!s&&o!=="no")return null;const r={total_parts:2},a={type:"fraction_shape",shape:e,total_parts:2,shaded_parts:0,color:BLUE$6,equal:s},i=s?"Both parts are the same size.":"But the parts are different sizes.",l=s?"Look at the two parts. They are exactly the same size.":"Look at the two parts. One is bigger than the other — they are not the same size.",u=s?"Two equal parts — yes.":"These aren't equal — no.",d=s?"Halves must be equal. These two parts are the same size, so yes — this is cut in half.":"Halves must be equal. These two parts are not the same size, so no — this is not cut in half.";return buildExample({generatorId:fdpP1M01Generator.id,question:n,title:`Is this ${e} cut in half?`,computed:r,steps:[{visual_data:a,animation:"show_parts",caption:`This ${e} is cut into 2 parts.`,spoken:`This ${e} is cut into ${numberToWords(2)} parts. Let's check if they are the same size.`},{visual_data:a,animation:"compare_parts",caption:i,spoken:l},{visual_data:a,animation:"conclude",caption:`Halves must be equal. ${u}`,spoken:d}]})}},BLUE$5="#2563eb",CHECK_MARK="#f59e0b",SMALL_MARK="#dc2626",BIG_MARK="#16a34a";function normaliseVerdict$1(n){return normaliseStem(n).toLowerCase()}function verdictMatches(n,t){const e=normaliseVerdict$1(n);return t?e==="yes"||e==="true":e==="no"||e==="false"}function verdictWord(n,t){const e=["true","false"].includes(normaliseVerdict$1(n));return t?e?{headline:"True",spoken:"true"}:{headline:"yes",spoken:"yes"}:e?{headline:"False",spoken:"false"}:{headline:"no",spoken:"no"}}function equalPartsFromVisual(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t;return e.total_parts!==2&&e.total_parts!==4||typeof e.equal!="boolean"?null:{shape:e.shape,totalParts:e.total_parts,equal:e.equal,shadedParts:e.shaded_parts??0,shadedColor:e.color}}const fdpP1M02Generator={id:"FDP.P1.M02.equal-parts.v1",microSkillId:"FDP.P1.M02",forms:["visual_quantity_to_symbol","comparison_statement","misconception_contrast_probe"],generate(n){const t=equalPartsFromVisual(n);if(!t)return null;const{shape:e,totalParts:s,equal:o,shadedParts:r,shadedColor:a}=t;if(!verdictMatches(n.correctAnswer,o))return null;const i=verdictWord(n.correctAnswer,o),l={total_parts:s},u={type:"fraction_shape",shape:e,total_parts:s,shaded_parts:r,color:r>0?a:BLUE$5,equal:o},d={...u,show_part_numbers:!0},m=o?{...u,part_marks:{0:CHECK_MARK,1:CHECK_MARK}}:{...u,part_marks:{0:SMALL_MARK,[s-1]:BIG_MARK}},h=r>0&&r<s,f=o?`These two parts are the same size.${h?" Colour doesn't change size.":""}`:"The red part is smaller. The green part is bigger.",g=o?`Look closely — these two parts are exactly the same size.${h?" One part is coloured, but colour does not change size — all parts are still equal.":""}`:"The red part is smaller, and the green part is bigger — they are not the same size.",b=o?`Equal parts — ${i.headline}.`:`Not equal — ${i.headline}.`,x=o&&n.options?.some(A=>/only some/i.test(A)),y=numberToWords(s),E=x?`ALL ${y} parts equal, not just some. ${b}`:`Equal parts are the same size. ${b}`,k=o?`Equal parts must be the same size. These all are, so ${i.spoken} — this is cut into equal parts.${x?` All ${y} parts are equal, not just some.`:""}`:`Equal parts must be the same size. These are not, so ${i.spoken} — this is not cut into equal parts.`,T={...u,conclude_label:i.headline};return buildExample({generatorId:fdpP1M02Generator.id,question:n,title:`Is this ${e} cut into equal parts?`,computed:l,steps:[{visual_data:d,animation:"show_parts",caption:`This ${e} is cut into ${s} parts.`,spoken:`This ${e} is cut into ${numberToWords(s)} parts. Let's check if they are the same size.`},{visual_data:m,animation:"compare_parts",caption:f,spoken:g},{visual_data:T,animation:"conclude",caption:E,spoken:k}]})}},GENERATOR_ID$5="FDP.P2.M01.name-shaded-fraction.v1",FRACTION_WORDS$2={"1/2":"one half","1/4":"one quarter","3/4":"three quarters","2/4":"two quarters"};function shadedShapeFromVisual$2(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t;return e.equal===!1||e.total_parts!==2&&e.total_parts!==4||e.shaded_parts!==1?null:{shape:e.shape,total:e.total_parts,shaded:e.shaded_parts}}function buildNameShadedDemo$1(n,t,e,s){const o=`${s}/${e}`;if(o!==normaliseStem(n.correctAnswer))return null;const r=FRACTION_WORDS$2[o];if(!r)return null;const a={total:e,shaded:s},i={type:"fraction_shape",shape:t,total_parts:e,shaded_parts:0,color:n.visualData.color},l={...i,shaded_parts:s},u=numberToWords(e),d=numberToWords(s);return buildExample({generatorId:GENERATOR_ID$5,question:n,title:"What fraction of this shape is shaded?",computed:a,steps:[{visual_data:i,animation:"show_parts",caption:`This ${t} has ${e} equal parts.`,spoken:`This ${t} is cut into ${u} equal parts.`},{visual_data:l,animation:"shade_part",caption:`${s} of the ${e} parts is shaded.`,spoken:`${capitalised(d)} part is shaded. That's ${d} out of ${u} equal parts.`},{visual_data:l,animation:"write_fraction",caption:`${s} out of ${e} — that's ${r}.`,spoken:`${capitalised(d)} out of ${u} equal parts. We call that ${r}.`}]})}const fdpP2M01Generator$1={id:GENERATOR_ID$5,microSkillId:"FDP.P2.M01",forms:["visual_quantity_to_symbol","numeral_to_set_match","fraction_identification","misconception_contrast_probe",""],generate(n){const t=shadedShapeFromVisual$2(n);return t?buildNameShadedDemo$1(n,t.shape,t.total,t.shaded):null}},GENERATOR_ID$4="FDP.P2.M02.recognise-quarter.v1",FRACTION_WORDS$1={"1/2":"one half","1/4":"one quarter","3/4":"three quarters","2/4":"two quarters"};function reducedFraction$1(n,t){const e=gcd$8(n,t);return`${n/e}/${t/e}`}function gcd$8(n,t){return t===0?n:gcd$8(t,n%t)}function shadedShapeFromVisual$1(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t;return e.equal===!1||e.total_parts!==2&&e.total_parts!==4||!Number.isInteger(e.shaded_parts)||e.shaded_parts<1||e.shaded_parts>=e.total_parts?null:{shape:e.shape,total:e.total_parts,shaded:e.shaded_parts}}function frames(n,t,e,s){const o={type:"fraction_shape",shape:t,total_parts:e,shaded_parts:0,color:n.visualData.color};return{unshaded:o,shadedPic:{...o,shaded_parts:s}}}function buildNameShadedDemo(n,t,e,s,o){const r=FRACTION_WORDS$1[o];if(!r)return null;const a={total:e,shaded:s},{unshaded:i,shadedPic:l}=frames(n,t,e,s),u=numberToWords(e),d=numberToWords(s),m=s===1?"is":"are";return buildExample({generatorId:GENERATOR_ID$4,question:n,title:"What fraction is shaded?",computed:a,steps:[{visual_data:i,animation:"show_parts",caption:`This ${t} has ${e} equal parts.`,spoken:`To name a fraction, first we count the equal parts. This ${t} is cut into ${u} equal parts.`},{visual_data:l,animation:"shade_part",caption:`${s} of the ${e} parts ${m} shaded.`,spoken:`Next we count the parts that are shaded. ${capitalised(d)} of the ${u} equal parts ${m} shaded.`},{visual_data:l,animation:"write_fraction",caption:`${s} out of ${e} — that's ${r}.`,spoken:`${capitalised(d)} out of ${u} equal parts. We call that ${r}.`}]})}function buildQuarterClaimDemo(n,t,e,s){const o=reducedFraction$1(s,e),r=FRACTION_WORDS$1[`${s}/${e}`];if(!r)return null;const a=o==="1/4"?"True":"False";if(a!==normaliseStem(n.correctAnswer))return null;const i={total:e,shaded:s},{unshaded:l,shadedPic:u}=frames(n,t,e,s),d=numberToWords(e),m=numberToWords(s),h=s===1?"is":"are",f=a==="True",g=`${s} out of ${e} is ${r} — ${f?"true":"false"}.`,b=f?`${capitalised(m)} out of ${d} is ${r}. So one quarter is shaded — that's true.`:`${capitalised(m)} out of ${d} is ${r}, not one quarter. So that's false.`;return buildExample({generatorId:GENERATOR_ID$4,question:n,title:"Is one quarter shaded?",computed:i,steps:[{visual_data:l,animation:"show_parts",caption:`This ${t} has ${e} equal parts.`,spoken:`To name a fraction, first we count the equal parts. This ${t} is cut into ${d} equal parts.`},{visual_data:u,animation:"shade_part",caption:`${s} of the ${e} parts ${h} shaded.`,spoken:`Next we count the parts that are shaded. ${capitalised(m)} of the ${d} equal parts ${h} shaded.`},{visual_data:u,animation:"write_fraction",caption:g,spoken:b}]})}const QUARTER_CLAIM=/^true or false: one quarter of this shape is shaded$/i,fdpP2M02Generator$1={id:GENERATOR_ID$4,microSkillId:"FDP.P2.M02",forms:["fraction_identification","visual_quantity_to_symbol"],generate(n){const t=shadedShapeFromVisual$1(n);if(!t)return null;const{shape:e,total:s,shaded:o}=t,r=normaliseStem(n.stem);if(QUARTER_CLAIM.test(r))return buildQuarterClaimDemo(n,e,s,o);const a=normaliseStem(n.correctAnswer),i=`${o}/${s}`,l=reducedFraction$1(o,s);return a===i?buildNameShadedDemo(n,e,s,o,i):a===l?buildNameShadedDemo(n,e,s,o,l):null}},HALF_OF_IS=/^Half of (\d+) is __$/,HALF_OF_MAKES=/^Half of (\d+) makes __$/,HALF_OF_WHICH=/^Half of (\d+) is which number\?$/,HALF_FRACTION=/^1\/2 of (\d+) = __$/,HALF_OF_BLANK_IS=/^Half of __ is (\d+)$/,HALF_OF_NOUN=/^Half of (\d+) [a-z]+ is __$/,HALF_OF_PENCE=/^Half of (\d+) pence is __ pence$/,CAKES_SHARED=/^(\d+) [a-z]+ shared by (\d+)\. Each gets __$/,HALF_WAY=/^Half way from (\d+) to (\d+) is __$/,HALF_OF_THESE=/^Half of these ([a-z]+) is __$/,HALF_COMPARE=/^Which is larger: half of (\d+) or half of (\d+)\?$/,GENERATOR_ID$3="FDP.P2.M03.half-two-groups.v1";function dotsPhrase$1(n){return`${numberToWords(n)} ${n===1?"dot":"dots"}`}function nounPhrase(n,t){const e=t.replace(/s$/,"");return`${numberToWords(n)} ${n===1?e:t}`}function splitTensOnes(n){const t=Math.floor(n/10)*10;return{tens:t,ones:n-t}}function deriveHalfNarration(n,t){const{tens:e,ones:s}=splitTensOnes(n),o=e/2,r=s/2;if(s===0){const a=e/10;if(a%2===0){const u=a/2;return{caption:`${n} is ${a} tens. Half of ${a} tens is ${u} tens: ${o}.`,spoken:`${capitalised(numberToWords(n))} is ${numberToWords(a)} tens. Half of ${numberToWords(a)}
71 tens is ${numberToWords(u)} tens: ${numberToWords(o)}.`,tens:e,ones:s,halfTens:o,halfOnes:r,extra:{tensCount:a,halfTensCount:u}}}const i=e-10,l=i/2;return{caption:`Half of ${i} is ${l}, half of 10 is 5: ${t}.`,spoken:`Split ${numberToWords(n)} into ${numberToWords(i)} and ten. Half of ${numberToWords(i)} is ${numberToWords(l)}, and half of ten is five: ${numberToWords(t)}.`,tens:e,ones:s,halfTens:o,halfOnes:r,extra:{evenPart:i,halfEvenPart:l,tenPart:10,halfTenPart:5}}}return{caption:`Half of ${e} is ${o}, half of ${s} is ${r}: ${t}.`,spoken:`Half of ${numberToWords(n)} is half of ${numberToWords(e)} plus half of ${numberToWords(s)}: ${numberToWords(o)} plus ${numberToWords(r)} is ${numberToWords(t)}.`,tens:e,ones:s,halfTens:o,halfOnes:r,extra:{}}}function buildHalvingArray(n,t){if(t%2!==0)return null;const e=t/2;if(e<1||e>10||e!==answerInt(n))return null;const s=2,o=e,r={whole:t,half:e,rows:s,cols:o,answer:e},a=numberToWords(t),i=numberToWords(e);return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`What is half of ${t}?`,computed:r,steps:[{visual_data:{type:"array",rows:s,cols:o},animation:"half_show",caption:`Half of ${t}.`,spoken:`Half of ${a}. Half means two equal groups, the same in each.`},{visual_data:{type:"array",rows:s,cols:o},animation:"half_group",caption:`Share ${t} into 2 equal groups.`,spoken:`We share the ${a} dots into two equal groups. Each row is one group.`},{visual_data:{type:"array",rows:s,cols:o},animation:"half_take",caption:`One group is ${e}. So ½ of ${t} = ${e}.`,spoken:`Each group has ${dotsPhrase$1(e)}. One group is one half. So half of ${a} is ${i}.`}]})}function buildHalvingLine(n,t,e){if(t%2!==0||t<2||t>100)return null;const s=t/2;if(s!==answerInt(n))return null;const o=0,r=t,a=t>40?10:void 0,i=e?` ${e}`:"",l={from:0,to:s,label:`${s}`},u={from:s,to:t,label:`${s}`},d=numberToWords(t),m=numberToWords(s),h=deriveHalfNarration(t,s),f={whole:t,half:s,answer:s,tens:h.tens,ones:h.ones,halfTens:h.halfTens,halfOnes:h.halfOnes,...h.extra};return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`What is half of ${t}${i}?`,computed:f,steps:[{visual_data:{type:"number_line",min:o,max:r,step:a,marked:[0,t],hops:[],unknown:null},animation:"show_span",caption:`Half of ${t}${i}.`,spoken:`Half of ${d}${i}. Half is the number right in the middle of zero and ${d}.`},{visual_data:{type:"number_line",min:o,max:r,step:a,marked:[0,t],hops:[l],unknown:null},animation:"split_span",caption:h.caption,spoken:`We split zero to ${d} into two equal halves. ${h.spoken}`},{visual_data:{type:"number_line",min:o,max:r,step:a,marked:[0,t],hops:[l,u],unknown:s},animation:"land_middle",caption:`Each half is ${s}${i}. So ½ of ${t} = ${s}.`,spoken:`Both halves are the same and meet at ${m}. So half of ${d} is ${m}.`}]})}function buildReverseHalf(n,t){if(t<1||t>10)return null;const e=t*2;if(e!==answerInt(n))return null;const s=t,o={whole:e,half:t,rows:2,cols:s,answer:e},r=numberToWords(e),a=numberToWords(t);return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`Half of what is ${t}?`,computed:o,steps:[{visual_data:{type:"array",rows:1,cols:s},animation:"reverse_show",caption:`We know half is ${t}.`,spoken:`We are told half is ${a}. That's one group.`},{visual_data:{type:"array",rows:2,cols:s},animation:"half_group",caption:`Duplicate it: two equal groups of ${t}.`,spoken:`To find the whole, we duplicate the group — two equal groups of ${a}.`},{visual_data:{type:"array",rows:2,cols:s},animation:"half_take",caption:`Two groups of ${t}. So the whole is ${e}.`,spoken:`If half is ${a}, the whole is two groups of ${a}. So the whole is ${r}.`}]})}function buildShareArray(n,t,e){if(e<1||e>10||t%e!==0)return null;const s=t/e;if(s<1||s>10||s!==answerInt(n))return null;const o=e,r=s,a={total:t,shares:e,quotient:s,rows:o,cols:r,answer:s},i=numberToWords(t),l=numberToWords(e),u=numberToWords(s);return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`Share ${t} into ${e}`,computed:a,steps:[{visual_data:{type:"array",rows:o,cols:r},animation:"share_show",caption:`Share ${t} into ${e} equal groups.`,spoken:`We share ${i} into ${l} equal groups, the same in each.`},{visual_data:{type:"array",rows:o,cols:r},animation:"share_group",caption:`${e} equal rows, ${t} shared out.`,spoken:`Each row is one group. We share the ${i} out, the same number in every group.`},{visual_data:{type:"array",rows:o,cols:r},animation:"share_take",caption:`Each group gets ${s}.`,spoken:`Each group gets ${u}. So ${i} shared by ${l} is ${u}.`}]})}function buildHalfwayLine(n,t,e){if(t>=e||(t+e)%2!==0)return null;const s=(t+e)/2;if(e-t>40||s!==answerInt(n))return null;const o=t,r=e,a=s-t,i={from:t,to:s,label:`${a}`},l={from:s,to:e,label:`${a}`},u=numberToWords(t),d=numberToWords(e),m=numberToWords(s),h=deriveHalfNarration(e-t,a),f={a:t,b:e,mid:s,halfSpan:a,answer:s,tens:h.tens,ones:h.ones,halfTens:h.halfTens,halfOnes:h.halfOnes,...h.extra};return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`Half way from ${t} to ${e}?`,computed:f,steps:[{visual_data:{type:"number_line",min:o,max:r,marked:[t,e],hops:[],unknown:null},animation:"show_span",caption:`Half way from ${t} to ${e}.`,spoken:`${capitalised(u)} to ${d}. Half way is the mark right in the middle, the same distance from each end.`},{visual_data:{type:"number_line",min:o,max:r,marked:[t,e],hops:[i],unknown:null}
71,animation:"split_span",caption:h.caption,spoken:`We split the line into two equal jumps. ${h.spoken}`},{visual_data:{type:"number_line",min:o,max:r,marked:[t,e],hops:[i,l],unknown:s},animation:"land_middle",caption:`Both jumps meet at ${s}.`,spoken:`Both jumps are the same, and they meet at ${m}. So half way from ${u} to ${d} is ${m}.`}]})}function buildVisualCountThenShare(n,t,e,s,o){const r=t*e;if(r%2!==0)return null;const a=r/2;if(a<1||a>10||a!==answerInt(n))return null;const i={count:r,half:a,rows:2,cols:a,answer:a},l=numberToWords(r),u=numberToWords(a);return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`Half of these ${o}?`,computed:i,steps:[{visual_data:{type:"array",rows:t,cols:e,emoji:s},animation:"count_show",caption:`Count the ${o}: ${r}.`,spoken:`Let's count the ${o}. There are ${l}.`},{visual_data:{type:"array",rows:2,cols:a,emoji:s},animation:"half_group",caption:`Share the ${r} ${o} into 2 equal groups.`,spoken:`Half means two equal groups, so we share the ${l} ${o} out, one at a time, until both groups are the same.`},{visual_data:{type:"array",rows:2,cols:a,emoji:s},animation:"half_take",caption:`One group is ${a}. So half of ${r} = ${a}.`,spoken:`Each group has ${nounPhrase(a,o)}. One group is one half. So half of ${l} is ${u}.`}]})}function buildHalfCompare(n,t,e){if(t%2!==0||e%2!==0||t<2||t>60||e<2||e>60)return null;const s=t/2,o=e/2,r=`Half of ${t}`,a=`Half of ${e}`,i=s>o?r:o>s?a:"They are the same";if(i!==n.correctAnswer.trim())return null;const l={left:t,right:e,leftHalf:s,rightHalf:o},u=numberToWords(t),d=numberToWords(e),m=numberToWords(s),h=numberToWords(o),f=s===o,g=s>o?`half of ${u}
71`:`half of ${d}`;return buildExample({generatorId:GENERATOR_ID$3,question:n,title:`Which is larger: half of ${t} or half of ${e}?`,computed:l,steps:[{visual_data:{type:"measure_bars",bars:[{label:`${t}`,length:t,color:"#2563eb"},{label:`${e}`,length:e,color:"#f59e0b"}],unit:""},animation:"compare_show",caption:`Half of ${t} or half of ${e}?`,spoken:`We compare half of ${u} and half of ${d}. Let's find each half first.`},{visual_data:{type:"measure_bars",bars:[{label:r,length:s,color:"#2563eb"},{label:a,length:o,color:"#f59e0b"},{label:"ruler",length:Math.max(t,e),color:"#94a3b8"}],unit:"",show_values:!0},animation:"compare_group",caption:`Half of ${t} is ${s}. Half of ${e} is ${o}.`,spoken:`To halve a number, we split it into two equal parts. Half of ${u} is ${m}, and half of ${d} is ${h}.`},{visual_data:{type:"measure_bars",bars:[{label:r,length:s,color:"#2563eb"},{label:a,length:o,color:"#f59e0b"},{label:"ruler",length:Math.max(t,e),color:"#94a3b8"}],unit:"",show_values:!0,compare:f?"same":void 0},animation:"compare_take",caption:f?"They are the same.":`${i} is larger.`,spoken:f?`Both halves are ${m}. They are the same.`:`Now compare the two halves: ${numberToWords(Math.max(s,o))} is more than ${numberToWords(Math.min(s,o))}. So ${g} is larger.`}]})}const fdpP2M03Generator={id:GENERATOR_ID$3,microSkillId:"FDP.P2.M03",forms:["equal_sharing","symbolic_calculation","equation_completion","context_calculation","number_line_location","visual_quantity_to_symbol","comparison_statement"],generate(n){const t=normaliseStem(n.stem),e=t.match(HALF_COMPARE);if(e)return buildHalfCompare(n,Number(e[1]),Number(e[2]));const s=t.match(HALF_OF_PENCE);if(s){const u=Number(s[1]);return buildHalvingArray(n,u)??buildHalvingLine(n,u,"pence")}const o=t.match(HALF_OF_IS)??t.match(HALF_OF_MAKES)??t.match(HALF_OF_WHICH)??t.match(HALF_FRACTION)??t.match(HALF_OF_NOUN);if(o){const u=Number(o[1]);return buildHalvingArray(n,u)??buildHalvingLine(n,u)}const r=t.match(HALF_OF_BLANK_IS);if(r)return buildReverseHalf(n,Number(r[1]));const a=t.match(CAKES_SHARED);if(a)return buildShareArray(n,Number(a[1]),Number(a[2]));const i=t.match(HALF_WAY);if(i)return buildHalfwayLine(n,Number(i[1]),Number(i[2]));const l=t.match(HALF_OF_THESE);if(l){const u=n.visualData;if(!u||u.type!=="array")return null;const{rows:d,cols:m,emoji:h}=u;return!Number.isInteger(d)||!Number.isInteger(m)?null:buildVisualCountThenShare(n,d,m,h,l[1])}return null}},QUARTER_BLANK=/^A quarter of (\d+) is __$/,QUARTER_COLON=/^A quarter of (\d+) is:$/,FRACTION_EQ=/^1\/4 of (\d+) = __$/,SHOWN_DIV=/^A quarter of (\d+) is (\d+) ÷ (\d+) = __$/,QUARTER_HAS_ARE=/^(?:A |An |The )?\w+ has (\d+) \w+\. A quarter are \w+\. How many (?:\w+ )?(?:are )?\w+\?$/,QUARTER_THERE_ARE=/^(?:There are )?(\d+) \w+(?: on a \w+)?\. A quarter are \w+\. How many (?:\w+ )?(?:are )?\w+\?$/,QUARTER_GIVES_AWAY=/^\w+ has (\d+) \w+\. (?:He|She|They) gives a quarter away\. How many (?:given|are given|does (?:he|she|they) give away)\?$/,HALF_THEN_QUARTER=/^Half of (\d+) is (\d+)\. So a quarter of (\d+) is:$/,SHARE_INTO=/^Share (\d+) into (\d+) equal groups\. How many (?:are )?(?:in each group|each|per group)\?$/,SHARE_UNITS=/^(\d+) \w+ (?:split|shared) into (\d+) equal \w+\. How many (?:\w+ )?(?:are )?(?:in one(?: \w+)?|in each(?: \w+)?|each|per \w+)\?$/,SHARE_IN_TEAMS=/^(\d+) \w+ in (\d+) equal \w+\. How many (?:\w+ )?(?:are )?(?:in one(?: \w+)?|in each(?: \w+)?|each|per \w+)\?$/,SHARE_FRIENDS=/^(\d+) \w+ share (\d+) \w+ equally\. How many (?:(?:\w+ )?each|(?:\w+ )?does each \w+ get)\?$/,CUT_INTO_PIECES=/^A [a-z]+ is (\d+) cm long\. It is cut into (\d+) equal pieces\. How long is one piece\?$/,CLAIM_SHARE_INTO=/^[A-Z][a-z]+ shares (\d+) [a-z]+ into (\d+) equal [a-z]+\. (?:He|She|They) says each [a-z]+ has (\d+) [a-z]+\. Is (?:he|she|they) right\?$/,QUARTER_COMPARE=/^Which is bigger: a quarter of (\d+) or a quarter of (\d+)\?$/,EQUIV_QUARTER=/^Which gives the same as a quarter of (\d+)\?$/,OPTION_HALF=/^Half of (\d+)$/,OPTION_QUARTER=/^A quarter of (\d+)$/;function parseQuarterOf(n){const t=normaliseStem(n),e=t.match(QUARTER_BLANK);if(e)return Number(e[1]);const s=t.match(QUARTER_COLON);if(s)return Number(s[1]);const o=t.match(FRACTION_EQ);if(o)return Number(o[1]);const r=t.match(SHOWN_DIV);if(r){const[d,m,h]=r.slice(1).map(Number);return m!==d||h!==4?null:d}const a=t.match(QUARTER_HAS_ARE);if(a)return Number(a[1]);const i=t.match(QUARTER_THERE_ARE);if(i)return Number(i[1]);const l=t.match(QUARTER_GIVES_AWAY);if(l)return Number(l[1]);const u=t.match(HALF_THEN_QUARTER);if(u){const[d,m,h]=u.slice(1).map(Number);return d!==h||m*2!==d?null:d}return null}function parseShareInto(n){const t=normaliseStem(n),e=t.match(SHARE_INTO);if(e)return{total:Number(e[1]),groups:Number(e[2])};const s=t.match(SHARE_UNITS);if(s)return{total:Number(s[1]),groups:Number(s[2])};const o=t.match(SHARE_IN_TEAMS);if(o)return{total:Number(o[1]),groups:Number(o[2])};const r=t.match(SHARE_FRIENDS);if(r)return{total:Number(r[2]),groups:Number(r[1])};const a=t.match(CUT_INTO_PIECES);return a?{total:Number(a[1]),groups:Number(a[2])}:null}function dotsPhrase(n){return`${numberToWords(n)} ${n===1?"dot":"dots"}`}function buildQuarterDemo(n,t){if(t%4!==0)return null;const e=t/4;if(e<1||e>10||e!==answerInt(n))return null;const s=4,o=e,r={quantity:t,groups:4,answer:e,rows:s,cols:o},a=numberToWords(t),i=numberToWords(e);return buildExample({generatorId:fdpP2M04Generator.id,question:n,title:`What is a quarter of ${t}?`,computed:r,steps:[{visual_data:{type:"array",rows:s,cols:o},animation:"quarter_show",caption:`A quarter of ${t}.`,spoken:`A quarter of ${a}. A quarter means four equal groups, the same in each.`},{visual_data:{type:"array",rows:s,cols:o},animation:"quarter_group",caption:`Share ${t} into 4 equal groups.`,spoken:`We share the ${a} dots into four equal groups. Each row is one group.`},{visual_data:{type:"array",rows:s,cols:o},animation:"quarter_take",caption:`One group is ${e}. So ¼ of ${t} = ${e}.`,spoken:`Each group has ${dotsPhrase(e)}. One group is one quarter. So a quarter of ${a} is ${i}.`}
71]})}function buildShareDemo(n,t,e){if(e===0||t%e!==0)return null;const s=t/e;if(e<2||e>10||s<1||s>10||s!==answerInt(n))return null;const o=e,r=s,a={total:t,groups:e,answer:s,rows:o,cols:r},i=numberToWords(t),l=numberToWords(e),u=numberToWords(s);return buildExample({generatorId:fdpP2M04Generator.id,question:n,title:`Share ${t} into ${e} equal groups`,computed:a,steps:[{visual_data:{type:"array",rows:o,cols:r},animation:"divide_show",caption:`Share ${t} into ${e} equal groups.`,spoken:`We share ${i} into ${l} equal groups. How many in each group?`},{visual_data:{type:"array",rows:o,cols:r},animation:"divide_group",caption:`${e} equal groups.`,spoken:`We make ${l} equal groups, the same number in each. Each row is one group.`},{visual_data:{type:"array",rows:o,cols:r},animation:"divide_take",caption:`Each group has ${s}. So ${t} ÷ ${e} = ${s}.`,spoken:`We shared the ${i} dots equally into ${l} groups, and each group has ${u}. So ${i} shared into ${l} is ${u}. Sharing is splitting into equal groups.`}]})}function buildShareClaimCheck(n,t,e,s){if(e<2||e>10||t%e!==0)return null;const o=t/e;if(o<1||o>10)return null;const r=o===s?"True":"False";if(r!==n.correctAnswer.trim())return null;const a=e,i=o,l={total:t,groups:e,real:o,claimed:s,rows:a,cols:i},u=numberToWords(t),d=numberToWords(e),m=numberToWords(o),h=numberToWords(s);return buildExample({generatorId:fdpP2M04Generator.id,question:n,title:`Is ${s} right for sharing ${t} into ${e}?`,computed:l,steps:[{visual_data:{type:"array",rows:a,cols:i},animation:"divide_show",caption:`Someone says each bag has ${s}. Let's check.`,spoken:`Someone says each bag has ${h}. Let's share ${u} ourselves first, into ${d} equal bags.`},{visual_data:{type:"array",rows:a,cols:i},animation:"divide_group",caption:`${e} equal bags.`,spoken:`We make ${d} equal bags, the same number in each.`},{visual_data:{type:"array",rows:a,cols:i},animation:"divide_take",caption:r==="True"?`Each bag has ${o}. That matches ${s}. True.`:`Each bag has ${o}, not ${s}. False.`,spoken:r==="True"?`Each bag really does have ${m}. The claim is true.`:`Each bag has ${m}, not ${h}. The claim is false.`}]})}function buildHalfQuarterProbe(n,t,e){if(e*2!==t||t%4!==0)return null;const s=t/4;if(s<1||s>10||e<1||e>10||s!==answerInt(n))return null;const o={quantity:t,half:e,quarter:s,rows:4,cols:s,answer:s},r=numberToWords(t),a=numberToWords(e),i=numberToWords(s);return buildExample({generatorId:fdpP2M04Generator.id,question:n,title:`Half of ${t} is ${e}. So a quarter of ${t} is:`,computed:o,steps:[{visual_data:{type:"array",rows:2,cols:e},animation:"half_show",caption:`Half of ${t} is ${e}.`,spoken:`We're told half of ${r} is ${a}.`},{visual_data:{type:"array",rows:4,cols:s},animation:"quarter_group",caption:`A quarter is half of a half: halve ${e} again.`,spoken:`A quarter is half of a half. We halve the ${a} again into two equal groups.`},{visual_data:{type:"array",rows:4,cols:s},animation:"quarter_take",caption:`One group is ${s}. So ¼ of ${t} = ${s}, not ${e}.`,spoken:`Each of those groups has ${dotsPhrase(s)}. So a quarter of ${r} is ${i}, not ${a}.`}]})}function buildQuarterCompare(n,t,e){if(t%4!==0||e%4!==0||t<4||t>80||e<4||e>80)return null;const s=t/4,o=e/4,r=`A quarter of ${t}`,a=`A quarter of ${e}`,i=s>o?r:o>s?a:"They are equal";if(i!==n.correctAnswer.trim())return null;const l={left:t,right:e,leftQ:s,rightQ:o},u=numberToWords(t),d=numberToWords(e),m=numberToWords(s),h=numberToWords(o),f=s===o,g=s>o?`a quarter of ${u}
71`:`a quarter of ${d}`;return buildExample({generatorId:fdpP2M04Generator.id,question:n,title:`Which is bigger: a quarter of ${t} or a quarter of ${e}?`,computed:l,steps:[{visual_data:{type:"measure_bars",bars:[{label:`${t}`,length:t,color:"#2563eb"},{label:`${e}`,length:e,color:"#f59e0b"}],unit:""},animation:"compare_show",caption:`A quarter of ${t} or a quarter of ${e}?`,spoken:`We compare a quarter of ${u} and a quarter of ${d}. Let's find each quarter first.`},{visual_data:{type:"measure_bars",bars:[{label:r,length:s,color:"#2563eb"},{label:a,length:o,color:"#f59e0b"}],unit:"",show_values:!0},animation:"compare_group",caption:`A quarter of ${t} is ${s}. A quarter of ${e} is ${o}.`,spoken:`To find a quarter, we share into four equal parts. A quarter of ${u} is ${m}, and a quarter of ${d} is ${h}.`},{visual_data:{type:"measure_bars",bars:[{label:r,length:s,color:"#2563eb"},{label:a,length:o,color:"#f59e0b"}],unit:"",show_values:!0,compare:f?"same":void 0},animation:"compare_take",caption:f?"They are equal.":`${i} is bigger.`,spoken:f?`Both quarters are ${m}. They are equal.`:`Now compare the two quarters: ${numberToWords(Math.max(s,o))} is more than ${numberToWords(Math.min(s,o))}. So ${g} is bigger.`}]})}function buildEquivQuarter(n,t){if(t%4!==0)return null;const e=t/4;if(e<1||e>20)return null;const o=(n.options??[]).find(m=>m===n.correctAnswer.trim());if(!o)return null;const r=o.match(OPTION_HALF),a=o.match(OPTION_QUARTER);let i=null;if(r){const m=Number(r[1]);m%2===0&&(i=m/2)}else if(a){const m=Number(a[1]);m%4===0&&(i=m/4)}if(i===null||i!==e)return null;const l={quantity:t,target:e,acceptedValue:i},u=numberToWords(t),d=numberToWords(e);return buildExample({generatorId:fdpP2M04Generator.id,question:n,title:`Which gives the same as a quarter of ${t}?`,computed:l,steps:[{visual_data:{type:"measure_bars",bars:[{label:`A quarter of ${t}`,length:e,color:"#2563eb"}],unit:""},animation:"compare_show",caption:`A quarter of ${t} is ${e}.`,spoken:`First find the quarter. A quarter means four equal parts, so a quarter of ${u} is ${d}.`},{visual_data:{type:"measure_bars",bars:[{label:`A quarter of ${t}`,length:e,color:"#2563eb"},{label:o,length:i,color:"#f59e0b"}],unit:"",show_values:!0},animation:"compare_group",caption:`${o} is ${i}.`,spoken:`Now work out the answer choice the same way. ${digitsToWords(o)} is ${numberToWords(i)}.`},{visual_data:{type:"measure_bars",bars:[{label:`A quarter of ${t}`,length:e,color:"#2563eb"},{label:o,length:i,color:"#f59e0b"}],unit:"",show_values:!0,compare:"same"},animation:"compare_take",caption:`Both equal ${e}. So ${o} gives the same.`,spoken:`Both are ${d}, so they are the same amount. That means ${digitsToWords(o).charAt(0).toLowerCase()}${digitsToWords(o).slice(1)} gives the same as a quarter of ${u}.`}]})}const fdpP2M04Generator={id:"FDP.P2.M04.quarter-of-array.v1",microSkillId:"FDP.P2.M04",forms:["equal_sharing","visual_quantity_to_symbol","equation_completion","context_calculation","varied_symbolic_calculation","misconception_contrast_probe","comparison_statement","alternate_reasoning_prompt"],generate(n){const t=matchInts(n.stem,CLAIM_SHARE_INTO);if(t)return buildShareClaimCheck(n,t[0],t[1],t[2]);const e=matchInts(n.stem,QUARTER_COMPARE);if(e)return buildQuarterCompare(n,e[0],e[1]);const s=matchInts(n.stem,EQUIV_QUARTER);if(s)return buildEquivQuarter(n,s[0]);const o=matchInts(n.stem,HALF_THEN_QUARTER);if(o){const[i,l,u]=o;return i!==u?null:buildHalfQuarterProbe(n,i,l)}const r=parseQuarterOf(n.stem);if(r!==null)return buildQuarterDemo(n,r);const a=parseShareInto(n.stem);return a?buildShareDemo(n,a.total,a.groups):null}},PULSE$1="#16a34a";function shadedMarks$1(n){const t={};for(let e=0;e<n;e++)t[e]=PULSE$1;return t}function readShape(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const{shape:e,total_parts:s,shaded_parts:o}=t;return s!==2&&s!==3&&s!==4||!Number.isInteger(o)||o<1||o>=s||e!=="circle"&&e!=="square"&&e!=="rectangle"?null:{shape:e,total:s,shaded:o,color:t.color}}function partNoun$1(n,t){const e=n===2?"half":n===3?"third":"quarter";return t?n===2?"halves":`${e}s`:e}function fractionWords$6(n,t){return`${numberToWords(n)} ${partNoun$1(t,n!==1)}`}function picture$1(n,t={}){return{type:"fraction_shape",shape:n.shape,total_parts:n.total,shaded_parts:n.shaded,color:n.color,...t}}function halfRelation(n,t){const e=n*2;return e<t?-1:e>t?1:0}function halfVerdict(n){return n<0?"Less than half":n>0?"More than half":"Exactly half"}function buildNameFractionDemo$1(n,t){const{total:e,shaded:s}=t,o=s===1?"is":"are";return buildExample({generatorId:fdpP3M01Generator.id,question:n,title:"What fraction is shaded?",computed:{total:e,shaded:s,answer:s},steps:[{visual_data:picture$1(t,{show_part_numbers:!0}),animation:"show_parts",caption:`This shape has ${e} equal parts.`,spoken:`Here is a shape split into ${numberToWords(e)} equal parts. The number of equal parts goes on the bottom of the fraction, so the bottom number is ${numberToWords(e)}.`},{visual_data:picture$1(t,{show_part_numbers:!0,part_marks:shadedMarks$1(s)}),animation:"shade_part",caption:`${s} of the ${e} parts ${o} shaded.`,spoken:`Now we count the shaded parts: ${numberToWords(s)} of the ${numberToWords(e)} parts ${o} shaded. The shaded parts go on the top, so the top number is ${numberToWords(s)}.`},{visual_data:picture$1(t,{part_marks:shadedMarks$1(s)}),animation:"write_fraction",caption:`So the fraction shaded is ${s}/${e}
71.`,spoken:`So the fraction shaded is ${fractionWords$6(s,e)}, written ${numberToWords(s)} over ${numberToWords(e)}.`}]})}function buildHalfBenchmarkDemo(n,t,e){const{total:s,shaded:o}=t,r=halfRelation(o,s),a=e.toLowerCase(),i=o===1?"is":"are";return buildExample({generatorId:fdpP3M01Generator.id,question:n,title:"Is it more or less than half?",computed:{total:s,shaded:o,answer:r},steps:[{visual_data:picture$1(t),animation:"show_parts",caption:`${o} of the ${s} parts ${i} shaded.`,spoken:`This shape has ${numberToWords(s)} equal parts, and ${numberToWords(o)} ${i} shaded.`},{visual_data:picture$1(t,{half_line:!0}),animation:"shade_part",caption:"The line marks half of the shape.",spoken:"This line shows exactly half of the shape."},{visual_data:picture$1(t,{half_line:!0,benchmark_verdict:e}),animation:"write_fraction",caption:`${o}/${s} is ${a}.`,spoken:`${capitalised(fractionWords$6(o,s))} ${r===0?"covers exactly half":r<0?"does not reach half":"covers more than half"}. So it is ${a}.`}]})}function buildStoryFractionDemo(n,t,e){const{total:s,shaded:o}=t,r=o===1?"is":"are";return buildExample({generatorId:fdpP3M01Generator.id,question:n,title:"What fraction of the whole?",computed:{total:s,shaded:o,answer:o},steps:[{visual_data:picture$1(t,{show_part_numbers:!0}),animation:"show_parts",caption:`The whole is split into ${s} equal parts.`,spoken:`The whole is split into ${numberToWords(s)} equal parts. The number of equal parts goes on the bottom of the fraction, so the bottom number is ${numberToWords(s)}.`},{visual_data:picture$1(t,{show_part_numbers:!0,part_marks:shadedMarks$1(o)}),animation:"shade_part",caption:`${o} of the ${s} parts ${r} ${e}.`,spoken:`${capitalised(numberToWords(o))} of the ${numberToWords(s)} equal parts ${r} ${e}. Those parts go on the top, so the top number is ${numberToWords(o)}.`},{visual_data:picture$1(t,{part_marks:shadedMarks$1(o)}),animation:"write_fraction",caption:`So the fraction is ${o}/${s}.`,spoken:`So the fraction is ${fractionWords$6(o,s)}, written ${numberToWords(o)} over ${numberToWords(s)}.`}]})}function storyActionWord(n){const t=n.toLowerCase();return t.startsWith("colour")?"coloured":t.startsWith("eat")?"eaten":t.startsWith("us")?"used":t.startsWith("shad")?"shaded":(t.startsWith("tak"),"taken")}function buildMissingTopDemo(n,t){const{total:e,shaded:s}=t,o=s===1?"is":"are";return buildExample({generatorId:fdpP3M01Generator.id,question:n,title:"What is the missing top number?",computed:{total:e,shaded:s,answer:s},steps:[{visual_data:picture$1(t,{show_part_numbers:!0}),animation:"show_parts",caption:`This shape has ${e} equal parts.`,spoken:`Here is a shape split into ${numberToWords(e)} equal parts. The number of equal parts goes on the bottom of the fraction.`},{visual_data:picture$1(t,{show_part_numbers:!0,part_marks:shadedMarks$1(s)}),animation:"shade_part",caption:`Count the shaded parts: ${s}.`,spoken:`Now we count the shaded parts, because they give the top number. ${capitalised(numberToWords(s))} of the parts ${o} shaded.`},{visual_data:picture$1(t,{part_marks:shadedMarks$1(s)}),animation:"write_fraction",caption:`The fraction is ${s}/${e}. Top number: ${s}.`,spoken:`The fraction shaded is ${fractionWords$6(s,e)}, so the missing top number is ${numberToWords(s)}.`}]})}const NAME_FRAC=/^What fraction of this shape is shaded\?$/,WHICH_SENTENCE=/^Look at the shaded shape\. Which sentence is true\?$/,HALF_MC=/^Is more than half, exactly half, or less than half of this shape shaded\?$/,HALF_TF=/^True or false\? (More|Less) than half of this shape is shaded$/,HALF_CLAIM=/^[A-Z][a-z]+ coloured (\d+) of the (\d+) equal parts of this shape\. (?:He|She|They) says (?:he|she|they) coloured (more|less) than half\. Is (?:he|she|they) right\?$/,STORY_FRAC=/^A [a-z]+ is (?:cut into|split into) (\d+) equal [a-z]+\. [A-Z][a-z]+ ([a-z]+) (\d+)[^.]*\. What fraction of the [a-z]+ did [A-Z][a-z]+ [a-z]+\?$/,MISSING_TOP=/What is the missing top number\?$/,fdpP3M01Generator={id:"FDP.P3.M01.name-shaded-fraction.v1",microSkillId:"FDP.P3.M01",forms:["comparison_statement","context_calculation","misconception_contrast_probe","alternate_reasoning_prompt","equation_completion"],generate(n){const t=readShape(n);if(!t)return null;const e=normaliseStem(n.stem),{total:s,shaded:o}=t;if(MISSING_TOP.test(e))return answerInt(n)!==o?null:buildMissingTopDemo(n,t);if(NAME_FRAC.test(e))return n.correctAnswer.trim()!==`${o}/${s}`?null:buildNameFractionDemo$1(n,t);if(WHICH_SENTENCE.test(e)){const a=`${o} of the ${s} equal parts ${o===1?"is":"are"} shaded`;return n.correctAnswer.trim()!==a?null:buildNameFractionDemo$1(n,t)}if(HALF_MC.test(e)){const r=halfVerdict(halfRelation(o,s));return n.correctAnswer.trim()!==r?null:buildHalfBenchmarkDemo(n,t,r)}{const r=e.match(HALF_TF);if(r){const a=halfRelation(o,s),u=(r[1]==="More"?a>0:a<0)?"True":"False";return n.correctAnswer.trim()!==u?null:buildHalfBenchmarkDemo(n,t,halfVerdict(a))}}{const r=e.match(HALF_CLAIM);if(r){if(Number(r[1])!==o||Number(r[2])!==s)return null;const a=halfRelation(o,s),l=r[3]==="more"?a>0:a<0;return isNegative(n.correctAnswer)!==!l?null:buildHalfBenchmarkDemo(n,t,halfVerdict(a))}}{const r=e.match(STORY_FRAC);if(r)return Number(r[1])!==s||Number(r[3])!==o||n.correctAnswer.trim()!==`${o}/${s}`?null:buildStoryFractionDemo(n,t,storyActionWord(r[2]))}return null}},BLUE$4="#2563eb",PULSE="#0f766e";function shadedMarks(n){const t={};for(let e=0;e<n;e++)t[e]=PULSE;return t}const NUM_LEFT=/^__\/(\d+) = (\d+)\/(\d+)$/,DEN_LEFT=/^(\d+)\/__ = (\d+)\/(\d+)$/,NUM_RIGHT=/^(\d+)\/(\d+) = __\/(\d+)$/,DEN_RIGHT=/^(\d+)\/(\d+) = (\d+)\/__$/;function solve$1(n,t){if(t===0||n%t!==0)return null;const e=n/t;return e>=1?e:null}function parsePair(n){let t;if(t=n.match(NUM_LEFT)){const[e,s,o]=[Number(t[1]),Number(t[2]),Number(t[3])],r=solve$1(e*s,o);return r===null?null:{leftN:r,leftD:e,rightN:s,rightD:o,blank:r,blankField:"leftN"}}if(t=n.match(DEN_LEFT)){const[e,s,o]=[Number(t[1]),Number(t[2]),Number(t[3])],r=solve$1(e*o,s);return r===null?null:{leftN:e,leftD:r,rightN:s,rightD:o,blank:r,blankField:"leftD"}}if(t=n.match(NUM_RIGHT)){const[e,s,o]=[Number(t[1]),Number(t[2]),Number(t[3])],r=solve$1(e*o,s);return r===null?null:{leftN:e,leftD:s,rightN:r,rightD:o,blank:r,blankField:"rightN"}}if(t=n.match(DEN_RIGHT)){const[e,s,o]=[Number(t[1]),Number(t[2]),Number(t[3])],r=solve$1(s*o,e);return r===null?null:{leftN:e,leftD:s,rightN:o,rightD:r,blank:r,blankField:"rightD"}}return null}function partNoun(n,t){const e=n===2?"half":n===3?"third":"quarter";return t?n===2?"halves":`${e}s`:e}function fractionPhrase(n,t){return`${numberToWords(n)} ${partNoun(t,n!==1)}`}const COUNT_SHADED=/^(?:.*\bshaded\b.*\. )?how many (halves|thirds|quarters)(?: are shaded| cover it| does it cover| are the same as the shaded half| cover the shaded half)?\??(?: __)?$/i,COUNT_PARTS_LEAD=/^\d+ equal parts\. how many (halves|thirds|quarters) are shaded\??(?: __)?$/i,FRAC_SHADED=/^which fraction (?:is shaded|matches the shaded part|shows the shaded part|shows the shaded shape)\??$/i,FRAC_SAME=/^.*\bshaded\b.*\. which fraction is the same\??$/i,TRUE_FRACTION=/^a child says this shows (\d+)\/(\d+)\. what is the true fraction\??$/i,WORD_MEANS_SAME=/which of these means the same as the shaded amount\??$/i;function unitDenominator(n){const t=n.toLowerCase();return t==="halves"?2:t==="thirds"?3:4}function gcd$7(n,t){return t===0?n:gcd$7(t,n%t)}function unitFractionWord(n,t){const e=gcd$7(n,t),s=n/e,o=t/e;return s!==1?null:o===2?"A half":o===3?"A third":o===4?"A quarter":null}function fractionShapeFromVisual(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const{total_parts:e,shaded_parts:s}=t;return e!==2&&e!==3&&e!==4||!Number.isInteger(s)||s<1||s>=e?null:{total:e,shaded:s}}function buildShadedShapeDemo(n,t,e,s,o){let r=null;if(s==="count"){if(e!==answerInt(n))return null}else if(s==="fraction"){if(`${e}/${t}`!==normaliseStem(n.correctAnswer))return null}else if(r=unitFractionWord(e,t),r===null||r!==n.correctAnswer.trim())return null;const a=t===2*e,i={total:t,shaded:e,answer:e},l=partNoun(t,!0),u=partNoun(t,e!==1),d=e===1?"is":"are",m=o?`1 shaded part would be ${o.claimedN}/${o.claimedD} — here ${e} are shaded, so ${e}/${t}.`:s==="count"?`${e} ${u} ${d} shaded.`:s==="fraction"?`Shaded part is ${e}/${t}.`:`${e}/${t} is the same as ${r.toLowerCase()}.`,h=`${capitalised(fractionPhrase(e,t))} ${d} shaded.`,f=o?`One shaded part would be ${fractionPhrase(o.claimedN,o.claimedD)}. But ${fractionPhrase(e,t)} ${d} really shaded, so the true fraction is ${fractionPhrase(e,t)}.`:s==="word"?`${h} That is the same amount as ${r.toLowerCase()}.`:a?`${h} That covers the same as one half — one half is ${fractionPhrase(e,t)}.`:`${h} So ${fractionPhrase(e,t)} of the shape ${e===1?"is":"are"} shaded.`,g=n.visualData.color,b={type:"fraction_shape",shape:n.visualData.shape,total_parts:t,shaded_parts:e,color:g};return buildExample({generatorId:fdpP3M02Generator.id,question:n,title:"What fraction of this shape is shaded?",computed:i,steps:[{visual_data:b,animation:"show_half",caption:"Part of this shape is shaded.",spoken:"Look at this shape — some of it is shaded. Let's count the equal parts."},{visual_data:{...b,show_part_numbers:!0},animation:"split_quarters",caption:`The shape has ${t} equal ${l}.`,spoken:`The shape is split into ${numberToWords(t)} equal parts — ${l}.`},{visual_data:{...b,show_part_numbers:!0,part_marks:shadedMarks(e),...o?{}:s==="count"?{conclude_label:`${e} ${u}`}:s==="word"?{conclude_label:r}:{}},animation:"write_fraction",caption:m,spoken:f}]})}function tryShadedShapeDemo(n){const t=normaliseStem(n.stem),e=fractionShapeFromVisual(n);if(!e)return null;const s=t.match(COUNT_SHADED)??t.match(COUNT_PARTS_LEAD);if(s)return unitDenominator(s[1])!==e.total?null:buildShadedShapeDemo(n,e.total,e.shaded,"count");if(FRAC_SHADED.test(t)||FRAC_SAME.test(t))return buildShadedShapeDemo(n,e.total,e.shaded,"fraction");{const o=t.match(TRUE_FRACTION);if(o)return buildShadedShapeDemo(n,e.total,e.shaded,"fraction",{claimedN:Number(o[1]),claimedD:Number(o[2])})}return WORD_MEANS_SAME.test(t)?buildShadedShapeDemo(n,e.total,e.shaded,"word"):null}function tryEquationDemo(n){{const t=parsePair(normaliseStem(n.stem));if(!t)return null;const{leftN:e,leftD:s,rightN:o,rightD:r,blank:a,blankField:i}=t,l=[2,3,4];if(!l.includes(s)||!l.includes(r)||s===r)return null;const u=s<r,d=u?e:o,m=u?s:r,h=u?o:e,f=u?r:s;if(f%m!==0)return null;const g=f/m;if(g<2||d<1||d>=m||h!==d*g||e*r!==s*o||a!==answerInt(n))return null;const b={simpleN:d,simpleD:m,compN:h,compD:f,k:g,answer:a},x=d===1&&m===2?"half":fractionPhrase(d,m),y=i==="leftN"||i==="rightN",E=y?`So the missing top number is ${a}.`:`So the missing number is ${a}.`,k=y?`So the missing top number is ${numberToWords(a)}.`:`So the missing number is ${numberToWords(a)}.`,T=f===4?{split_columns:!0}:{};return buildExample({generatorId:fdpP3M02Generator.id,question:n,title:`Why is ${d}/${m} the same as ${h}/${f}?`,computed:b,steps:[{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:m,shaded_parts:d,color:BLUE$4},animation:"show_half",caption:`${capitalised(x)} of this shape is shaded.`,spoken:`${capitalised(x)} of this shape is shaded — ${numberToWords(d)} of ${numberToWords(m)} equal parts.`},{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:f,shaded_parts:h,c
71olor:BLUE$4,...T},animation:"split_quarters",caption:`Split each ${partNoun(m,!1)} into ${g} — now ${partNoun(f,!0)}.`,spoken:`We split each ${partNoun(m,!1)} into ${numberToWords(g)}. Now the shape has ${numberToWords(f)} equal parts — ${partNoun(f,!0)}.`},{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:f,shaded_parts:h,color:BLUE$4,...T},animation:"write_fraction",caption:`Same part is ${h}/${f}. So ${d}/${m} = ${h}/${f}. ${E}`,spoken:`The shaded part now covers ${fractionPhrase(h,f)}. So ${fractionPhrase(d,m)} is the same as ${fractionPhrase(h,f)}. ${k}`}]})}}const HALF_QUARTERS_SWEETS_CLAIM=/^There are (\d+) [a-z]+ in a [a-z]+\. (\d+) [a-z]+ is half the [a-z]+\. [A-Z][a-z]+ says (\d+) quarters of the [a-z]+ is also (\d+) [a-z]+\. Is (?:he|she|they) right\?$/,QUARTERS_EQUAL_HALF_LENGTH_CLAIM=/^A [a-z]+ is (\d+) cm long\. It is cut into quarters\. [A-Z][a-z]+ colours in (\d+) quarters and says that is the same length as half the [a-z]+\. Is (?:he|she|they) right\?$/,HALF_EQ_QUARTERS=/^(\d+) half(?:ves)? = __ quarters$/,QUARTERS_EQ_HALF=/^(\d+) quarters = __ half(?:ves)?$/,NOT_SAME_AS_HALF=/^Which fraction is NOT the same as 1\/2\?$/,SAME_AS_WHICH=/^(\d+)\/(\d+) is the same as which fraction\?$/,WHICH_SAME_AS=/^Which is the same as (\d+)\/(\d+)\?$/,IS_SAME_AMOUNT=/^Is (\d+)\/(\d+) the same amount as (\d+)\/(\d+)\?$/,WHO_ATE_SAME=/^([A-Z][a-z]+) (ate|drank) (\d+)\/(\d+)\. ([A-Z][a-z]+) (ate|drank) (\d+)\/(\d+)\. Who (ate|drank) more\?$/,CAKE_HALF=/^A cake has (\d+) slices\. Half is eaten\. How many slices\? __$/,CAKE_HALF_EATEN=/^A cake has (\d+) equal slices\. Half is eaten\. How many slices are eaten\?$/,RIBBON_QUARTERS_HALF=/^A (\d+) cm [a-z]+ is cut into quarters\. [A-Z][a-z]+ says (\d+) quarters is the same length as half the [a-z]+\. Is (?:he|she|they) right\?$/,SAME_AS_SHADED=/^which fraction is the same as the amount shaded\??$/i,WHICH_SHADED_FRAC=/^which fraction (?:is shaded|matches the shaded part|shows the shaded part|shows the shaded shape)\??$/i;function isHalfFamily(n,t){return n===1&&t===2||n===2&&t===4}function halfFamilyPartner(n,t){return n===1&&t===2?"2/4":n===2&&t===4?"1/2":null}function buildHalfQuartersDemo(n,t,e,s,o,r="rectangle",a=BLUE$4,i={},l,u=2){const d={simpleN:1,simpleD:2,compN:2,compD:4,k:2,answer:t,...i},m=r==="rectangle"?{split_columns:!0}:{};return buildExample({generatorId:fdpP3M02Generator.id,question:n,title:e,computed:d,steps:[{visual_data:{type:"fraction_shape",shape:r,total_parts:2,shaded_parts:1,color:a},animation:"show_half",caption:"1 of 2 parts is shaded — a half.",spoken:"Half of this shape is shaded — one of two equal parts."},{visual_data:{type:"fraction_shape",shape:r,total_parts:4,shaded_parts:2,color:a,...m},animation:"split_quarters",caption:"Split each half in 2 — now 4 parts.",spoken:"We split each half into two. Now the shape has four equal parts — quarters."},{visual_data:{type:"fraction_shape",shape:r,total_parts:4,shaded_parts:u,color:a,...m,...l?{conclude_label:l}:{}},animation:"write_fraction",caption:s,spoken:o}]})}function buildHalfQuartersSweetsClaim(n,t,e,s,o){if(t%4!==0||e!==t/2)return null;const r=t/4,a=r*s,i=a===o?"True":"False";if(i!==n.correctAnswer.trim())return null;const l={total:t,statedHalf:e,quarterValue:r,claimedQuarters:s,claimedTotal:a,claimedAmount:o},u=numberToWords(t),d=numberToWords(e),m=numberToWords(a),h=numberToWords(o);return buildExample({generatorId:fdpP3M02Generator.id,question:n,title:`Is ${s} quarters of ${t} really ${o}?`,computed:l,steps:[{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:2,shaded_parts:1,color:BLUE$4},animation:"show_half",caption:`Half of ${t} is ${e}.`,spoken:`Half of ${u} is ${d} — that is the same as two quarters.`},{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:4,shaded_parts:2,color:BLUE$4,split_columns:!0},animation:"split_quarters",caption:`${s} quarters of ${t} is ${a}.`,spoken:`Each quarter of ${u} is ${numberToWords(r)}, so two quarters is ${m}.`},{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:4,shaded_parts:2,color:BLUE$4,split_columns:!0},animation:"write_fraction",caption:i==="True"?`${a} matches ${o}. True.`:`${a} does not match ${o}. False.`,spoken:i==="True"?`Two quarters really is ${h}, the same as half. The claim is true.`:`Two quarters is ${m}, not ${h}. The claim is false.`}]})}function buildQuartersEqualHalfLength(n,t,e){if(t%4!==0)return null;const s=t/4,o=s*e,r=t/2,a=o===r?"True":"False";if(a!==n.correctAnswer.trim())return null;
71const i={total:t,quarterValue:s,claimedQuarters:e,claimedLength:o,halfValue:r},l=numberToWords(t),u=numberToWords(r),d=numberToWords(o);return buildExample({generatorId:fdpP3M02Generator.id,question:n,title:`Is ${e} quarters of ${t} cm the same as half?`,computed:i,steps:[{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:2,shaded_parts:1,color:BLUE$4},animation:"show_half",caption:`Half of ${t} cm is ${r} cm.`,spoken:`Half of ${l} centimetres is ${u} centimetres.`},{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:4,shaded_parts:e,color:BLUE$4,split_columns:!0},animation:"split_quarters",caption:`${e} quarters of ${t} cm is ${o} cm.`,spoken:`Each quarter of ${l} centimetres is ${numberToWords(s)}, so ${numberToWords(e)} quarters is ${d} centimetres.`},{visual_data:{type:"fraction_shape",shape:"rectangle",total_parts:4,shaded_parts:e,color:BLUE$4,split_columns:!0},animation:"write_fraction",caption:a==="True"?`${o} cm matches half, ${r} cm. True.`:`${o} cm does not match half, ${r} cm. False.`,spoken:a==="True"?"That really is the same length as half the ribbon. The claim is true.":"That is not the same length as half the ribbon. The claim is false."}]})}function tryEquivalenceTails(n){const t=normaliseStem(n.stem);{const e=t.match(HALF_QUARTERS_SWEETS_CLAIM);if(e){const[s,o,r,a]=e.slice(1).map(Number);return buildHalfQuartersSweetsClaim(n,s,o,r,a)}}{const e=t.match(QUARTERS_EQUAL_HALF_LENGTH_CLAIM);if(e){const[s,o]=e.slice(1).map(Number);return buildQuartersEqualHalfLength(n,s,o)}}{const e=t.match(HALF_EQ_QUARTERS);if(e)return Number(e[1])!==1||answerInt(n)!==2?null:buildHalfQuartersDemo(n,2,"How many quarters is 1 half?","1 half = 2 quarters.","One half is the same as two quarters. So one half equals two quarters.","rectangle",BLUE$4,{},"1 half = 2 quarters")}{const e=t.match(QUARTERS_EQ_HALF);if(e)return Number(e[1])!==2||answerInt(n)!==1?null:buildHalfQuartersDemo(n,1,"How many halves is 2 quarters?","2 quarters = 1 half.","Two quarters cover the same as one half. So two quarters equals one half.","rectangle",BLUE$4,{},"2 quarters = 1 half")}if(NOT_SAME_AS_HALF.test(t)&&n.correctAnswer==="3/4")return buildHalfQuartersDemo(n,2,"Which is NOT the same as 1/2?","2/4 is one half. 3/4 covers more — the odd one out.","Two quarters covers the same amount as one half — they match. Now shade three quarters: that covers three parts, more than a half. So three quarters is the one that is not the same.","rectangle",BLUE$4,{oddN:3,oddD:4},"3/4 is more than 1/2",3);{const e=t.match(SAME_AS_WHICH);if(e){const[s,o]=[Number(e[1]),Number(e[2])],r=halfFamilyPartner(s,o);return r===null||r!==n.correctAnswer?null:buildHalfQuartersDemo(n,2,`What is the same as ${s}/${o}?`,`${s}/${o} is the same as ${r}.`,`${capitalised(fractionPhrase(s,o))} covers the same amount as ${fractionPhrase(...partnerNums(r))}. So they are equal.`,"rectangle",BLUE$4,{},r)}}{const e=t.match(WHICH_SAME_AS);if(e){const[s,o]=[Number(e[1]),Number(e[2])],r=halfFamilyPartner(s,o);return r===null||r!==n.correctAnswer?null:buildHalfQuartersDemo(n,2,`What is the same as ${s}/${o}?`,`${r} is the same as ${s}/${o}.`,`${capitalised(fractionPhrase(...partnerNums(r)))} covers the same amount as ${fractionPhrase(s,o)}. So they are equal.`,"rectangle",BLUE$4,{},r)}}{const e=t.match(IS_SAME_AMOUNT);if(e){const[s,o,r,a]=[Number(e[1]),Number(e[2]),Number(e[3]),Number(e[4])],i=s*a===o*r,l=isHalfFamily(s,o)&&isHalfFamily(r,a);return!i||!l||!/(^Yes|\bsame\b)/i.test(n.correctAnswer)||/\bNo\b/.test(n.correctAnswer)?null:buildHalfQuartersDemo(n,2,`Is ${s}/${o} the same as ${r}/${a}?`,`${s}/${o} and ${r}/${a} match. Yes.`,`${capitalised(fractionPhrase(s,o))} covers exactly the same amount as ${fractionPhrase(r,a)}. So yes, they are the same amount.`,"rectangle",BLUE$4,{},"Yes")}}{const e=t.match(WHO_ATE_SAME);if(e){const[s,o]=[Number(e[3]),Number(e[4])],[r,a]=[Number(e[7]),Number(e[8])],i=s*a===o*r,l=isHalfFamily(s,o)&&isHalfFamily(r,a);return!i||!l||e[2]!==e[6]||e[2]!==e[9]||!/\bsame\b/i.test(n.correctAnswer)?null:buildHalfQuartersDemo(n,2,`Who ${e[9]} more?`,`${s}/${o} and ${r}/${a} are equal.`,`${capitalised(fractionPhrase(s,o))} covers the same amount as ${fractionPhrase(r,a)}. So they ${e[9]} the same.`,"rectangle",BLUE$4,{},"The same")}}{const e=t.match(CAKE_HALF);if(e)return Number(e[1])!==4||answerInt(n)!==2?null:buildHalfQuartersDemo(n,2,"Half of 4 slices?","Half of 4 slices is 2 slices.","Half of the four slice
71s is two — one half is two quarters, so two slices are eaten.","circle")}{const e=t.match(CAKE_HALF_EATEN);if(e)return Number(e[1])!==4||answerInt(n)!==2?null:buildHalfQuartersDemo(n,2,"Half of 4 slices?","Half of 4 slices is 2 slices.","Half of the four slices is two — one half is two quarters, so two slices are eaten.","circle")}{const e=t.match(RIBBON_QUARTERS_HALF);if(e)return buildQuartersEqualHalfLength(n,Number(e[1]),Number(e[2]))}if(SAME_AS_SHADED.test(t)){const e=fractionShapeFromVisual(n);if(e&&e.total===4&&e.shaded===2&&n.correctAnswer==="1/2"){const s=n.visualData;return buildHalfQuartersDemo(n,2,"Which fraction is shaded?","2 of 4 shaded is the same as 1/2.","Two of the four parts are shaded — that is two quarters, which is the same as one half.",s.shape==="circle"?"circle":"rectangle",s.color,{},"1/2")}}if(WHICH_SHADED_FRAC.test(t)||FRAC_SAME.test(t)){const e=fractionShapeFromVisual(n);if(e&&e.total===4&&e.shaded===2&&n.correctAnswer==="1/2"){const s=n.visualData;return buildHalfQuartersDemo(n,2,"Which fraction is shaded?","2 of 4 shaded is the same as 1/2.","Two of the four parts are shaded — that is two quarters, which is the same as one half.",s.shape==="circle"?"circle":"rectangle",s.color,{},"1/2")}}return null}const UNEQUAL_HALF_CLAIM=/^This [a-z]+ is cut into (\d+) pieces that are not all the same size\. [A-Z][a-z]+ shades (\d+) of the pieces and says that shows a half\. Is (?:he|she|they) right\?$/;function buildUnequalPartsProbe(n,t,e){if(t!==4||e<1||e>=t||n.correctAnswer.trim()!=="False")return null;const s=n.visualData;if(s&&s.type==="fraction_shape"&&(s.total_parts!==t||s.shaded_parts!==e||s.equal!==!1))return null;const o={total:t,shaded:e};return buildExample({generatorId:fdpP3M02Generator.id,question:n,title:"Are these pieces really a half?",computed:o,steps:[{visual_data:{type:"fraction_shape",shape:"square",total_parts:4,shaded_parts:e,color:BLUE$4,equal:!1},animation:"show_half",caption:`${e} of ${t} pieces shaded — but are they equal?`,spoken:"Look closely at these pieces — they are not all the same size."},{visual_data:{type:"fraction_shape",shape:"square",total_parts:4,shaded_parts:e,color:BLUE$4,equal:!0},animation:"split_quarters",caption:`If the ${t} parts were equal, ${e} WOULD be a half.`,spoken:"If the parts were equal in size, shading two of four would be a genuine half."},{visual_data:{type:"fraction_shape",shape:"square",total_parts:4,shaded_parts:e,color:BLUE$4,equal:!1,conclude_label:"Not equal — not a half"},animation:"write_fraction",caption:"Unequal parts — not really a half. False.",spoken:"A fraction needs equal parts. Since these pieces are not equal, shading them is not really a half. So the claim is false."}]})}function partnerNums(n){const[t,e]=n.split("/").map(Number);return[t,e]}const fdpP3M02Generator={id:"FDP.P3.M02.split-each-part.v1",microSkillId:"FDP.P3.M02",forms:["equation_completion","fraction_identification","numeral_to_set_match","visual_quantity_to_symbol","misconception_contrast_probe","comparison_statement","context_calculation"],generate(n){const t=matchInts(n.stem,UNEQUAL_HALF_CLAIM);if(t){const[e,s]=t;return buildUnequalPartsProbe(n,e,s)}return tryEquationDemo(n)??tryShadedShapeDemo(n)??tryEquivalenceTails(n)}},GEN_ID$7="MEA.P3.M03.next-hour.v1";function clockFromVisual$3(n){const t=n.visualData;if(!t||t.type!=="clock")return null;const{hour:e,minute:s}=t;return!Number.isInteger(e)||e<1||e>12||!Number.isInteger(s)||s<0||s>59?null:{hour:e,minute:s}}function adjacentHour(n,t){const e=n.toLowerCase();return/\bcomes (after|next)\b|\bone hour later\b/.test(e)?{adjacent:t%12+1,dir:"after"}:/\bcomes before\b|\bone hour earlier\b/.test(e)?{adjacent:(t+10)%12+1,dir:"before"}:null}function buildAdjacentHourDemo(n,t,e,s){const o={type:"clock",hour:t,minute:0},r={type:"clock",hour:e,minute:0},a=numberToWords(t),i=numberToWords(e),l={hour:t,adjacent:e,answer:e},u=s==="after"?"later":"earlier",d=s==="after"?"on":"back";return buildExample({generatorId:GEN_ID$7,question:n,title:s==="after"?"What hour comes next?":"What hour comes before?",computed:l,steps:[{visual_data:o,animation:"highlight_minute_hand",caption:"The long hand points straight up.",spoken:"Look at the long hand. It points straight up at twelve, so it's an o'clock t
71ime."},{visual_data:o,animation:"highlight_hour_hand",caption:`The short hand points to ${t}. It's ${t} o'clock.`,spoken:`The short hand points at the ${a}. So the clock shows ${a} o'clock.`},{visual_data:r,animation:s==="after"?"advance_hour":"advance_back_hour",caption:`One hour ${u}: ${e} o'clock.`,spoken:`Count ${d} one hour from ${a} o'clock — watch the short hand move. One hour ${u}, it's ${i} o'clock.`}]})}const HOUR_CLAIM=/says\s+(\d{1,2})\s*o'?clock\s+is\s+(?:right\s+)?(before|after|next)\s+(\d{1,2})\b/i,CLAIM_RIGHT=/^yes,?\s*(?:he'?s|she'?s|they'?re)?\s*right$/i,CLAIM_WRONG=/^no,?\s*(?:he'?s|she'?s|they'?re)?\s*wrong$/i;function parseHourClaim(n){const t=HOUR_CLAIM.exec(n);if(!t)return null;const e=Number(t[1]),s=t[2].toLowerCase()==="before"?"before":"after",o=Number(t[3]);return!Number.isInteger(e)||e<1||e>12||!Number.isInteger(o)||o<1||o>12?null:{claimedAdjacent:e,direction:s,statedHour:o}}function buildHourClaimDemo(n,t,e,s){const o=s==="before"?(t+10)%12+1:t%12+1,r=e===o,a=n.correctAnswer.trim();if(r){if(!CLAIM_RIGHT.test(a))return null}else if(!CLAIM_WRONG.test(a))return null;const i={type:"clock",hour:t,minute:0},l=numberToWords(t),u=numberToWords(e),d=numberToWords(o),m={hour:t,claimedAdjacent:e,actualAdjacent:o,answer:o},h=T=>(T+11)%12+1,f=s==="before"?[h(t-2),o,t]:[t,o,h(t+2)],b={type:"sequence",values:f.map((T,A)=>A===1?null:T),step:1,hide_hop:!0},x={type:"sequence",values:[...f],step:1,hide_hop:!0,emphasis:1},y=f.map(T=>numberToWords(T)).join(", "),E=r?`Right ${s} ${t} is ${o} — ${a}.`:`Right ${s} ${t} is ${o}, not ${e} — ${a}.`,k=r?`Say the hours in order: ${y}. The hour right ${s} ${l} is ${d}, just as was said. ${a}.`:`Say the hours in order: ${y}. The hour right ${s} ${l} is ${d}, not ${u}. ${a}.`;return buildExample({generatorId:GEN_ID$7,question:n,title:"Was the claim right?",computed:m,steps:[{visual_data:i,animation:"highlight_hour_hand",caption:`The clock shows ${t} o'clock.`,spoken:`First read the real clock. The long hand points straight up and the short hand points at the ${l} — ${l} o'clock.`},{visual_data:b,animation:"show_hour_order",caption:`Which hour comes right ${s} ${t}?`,spoken:`The hours go in order, one at a time. Which hour sits right ${s} ${l} — the one next to it, with nothing in between?`},{visual_data:x,animation:"reveal_hour",caption:E,spoken:k}]})}const ROUTINE_ORDER=["breakfast","lunch","dinner"],ROUTINE_EMOJI={breakfast:"🍳",lunch:"🥪",dinner:"🍽️"},ROUTINE_QUERY=/what comes right (before|after) (\w+)/i;function parseRoutineQuery(n){const t=ROUTINE_QUERY.exec(normaliseStem(n));if(!t)return null;const e=t[1].toLowerCase()==="before"?"before":"after",s=t[2].toLowerCase();return ROUTINE_ORDER.includes(s)?{direction:e,anchor:s}:null}const ROUTINE_GEN_ID="MEA.P3.M03.daily-routine.v1";function buildRoutineDemo(n,t,e){const s=ROUTINE_ORDER.indexOf(e),o=t==="before"?s-1:s+1;if(o<0||o>=ROUTINE_ORDER.length)return null;const r=ROUTINE_ORDER[o];if(capitalised(r)!==n.correctAnswer.trim())return null;const a=ROUTINE_ORDER.map((x,y)=>y===o?null:y+1),i=ROUTINE_ORDER.map((x,y)=>y===o?null:ROUTINE_EMOJI[x]),l=ROUTINE_ORDER.map((x,y)=>y+1),u=ROUTINE_ORDER.map(x=>ROUTINE_EMOJI[x]),d={position:o+1},m=e.toLowerCase(),h=capitalised(r),f={type:"sequence",values:a,step:1,hide_hop:!0,labels:i},g={...f,emphasis:s},b={type:"sequence",values:l,step:1,hide_hop:!0,labels:u};return buildExample({generatorId:ROUTINE_GEN_ID,question:n,title:`What comes right ${t} ${m}?`,computed:d,steps:[{visual_data:f,animation:"show_routine",caption:"Every day follows the same order.",spoken:"Think about your day. Breakfast, then lunch, then dinner, in the same order every time."},{visual_data:g,animation:"point_to_anchor",caption:`What comes right ${t} ${m}?`,spoken:`We want the meal right ${t} ${m} — the one next to it, with nothing in between.`},{visual_data:b,animation:"reveal_meal",caption:`${h} comes right ${t} ${m}.`,spoken:`Breakfast, then lunch, then dinner. ${h} comes right ${t} ${m}.`}]})}const meaP3M03Generator={id:GEN_ID$7,microSkillId:"MEA.P3.M03",forms:["time_vocabulary"],generate(n){const t=clockFromVisual$3(n);if(t){const{hour:s,minute:o}=t;if(o!==0)return null;const r=adjacentHour(normaliseStem(n.stem),s);if(r)return`${r.adjacent} o'clock`!==n.correctAnswer.trim()?null:buildAdjacentHourDemo(n,s,r.adjacent,r.dir);const a=parseHourClaim(normaliseStem(n.stem));return a&&a.statedHour===s?buildHourClaimDemo(n,s,a.claimedAdjacent,a.direction):null}const e=parseRoutineQuery(n.stem);return e?buildRoutineDemo(n,e.direction,e.anchor):null}},PENCE$1={"1p":1,"2p":2,"5p":5,"10p":10,"20p":20,"50p":50,"£1":100,"£2":200};function penceLabel$1(n){return n>=100&&n%100===0?`£${n/100}`:`${n}p`}function valueWords(n){if(n>=100&&n%100===0){const t=n/100;return`${numberToWords(t)} pound${t===1?"":"s"}`}return n===1?"one penny":`${numberToWords(n)} pence`}function singleCoinFromVisual(n){const t=n.visualData;if(!t||t.type!=="coin_set")return null;const e=t.coins;if(!Array.isArray(e)||e.length!==1)return null;const s=e[0],o=PENCE$1[s];return o===void 0?null:{denom:s,pence:o}}function coinCompareFromVisual(n){const t=n.visualData;if(!t||t.type!=="coin_set")return null;const e=t.coins;if(!Array.isArray(e)||e.length!==2)return null;const[s,o]=e,r=PENCE$1[s],a=PENCE$1[o];return r===void 0||a===void 0?null:{a:s,b:o,pa:r,pb:a}}function coinCompareDemo(n,t){const e=coinCompareFromVisual(n);if(!e)return null;const{a:s,b:o,pa:r,pb:a}=e;if(r===a)return null;const i=r>a?s:o,l=Math.max(r,a),u=Math.min(r,a),d=penceLabel$1(l);if(d!==n.correctAnswer.trim())return null;
71const m={type:"coin_set",coins:[s,o],compare:!0};return buildExample({generatorId:t,question:n,title:"Which coin is worth more?",computed:{pa:r,pb:a,winner:l,answer:l},steps:[{visual_data:{...m},animation:"show_coin",caption:"Compare the two coins.",spoken:"Every coin tells you what it's worth. Let's find each coin's value."},{visual_data:{...m},animation:"highlight_value",caption:"Read the number on each coin.",spoken:`One coin says ${valueWords(r)}, the other says ${valueWords(a)}.`},{visual_data:{...m},animation:"write_value",caption:`${d} is worth more.`,spoken:`${capitalised(valueWords(l))} is more than ${valueWords(u)}, so that coin is worth more.`+(COIN_SIZE_PX[i]<COIN_SIZE_PX[i===s?o:s]?" It's the smaller coin, but it's worth more.":"")}]})}const CLAIM_PENCE=/worth (\d+)p\b/i,CLAIM_POUND=/worth £(\d+)\b/i;function coinClaimDemo(n,t,e){const s=n.correctAnswer.trim(),o=/^Yes\b/i.test(s),r=/^No\b/i.test(s);if(!o&&!r)return null;const{denom:a,pence:i}=t,l=n.stem.match(CLAIM_PENCE),u=n.stem.match(CLAIM_POUND);let d=null;if(l?d=Number(l[1]):u&&(d=Number(u[1])*100),d===null||!Number.isInteger(d)||d<=0)return null;const m=penceLabel$1(i),h=penceLabel$1(d),f=d===i?"Yes":"No";if(f==="Yes"!==o||f==="No"&&s!=="No"&&!s.includes(m))return null;const g=i>=100&&i%100===0,b=g?i/100:i,x={face:b,pence:i,claimedPence:d,answer:i},y={type:"coin_set",coins:[a]},E=g?`Someone says this coin is worth ${valueWords(d)}. Find the pound sign on the coin — it shows ${valueWords(i)}.`:`Someone says this coin is worth ${valueWords(d)}. Find the number on the coin — it says ${numberToWords(b)}.`,k=f==="Yes"?`Yes — it's worth ${m}.`:`No — it's worth ${m}, not ${h}.`,T=f==="Yes"?`${capitalised(valueWords(i))}. That matches the claim, so yes.`:`${capitalised(valueWords(i))}, not ${valueWords(d)}. So no.`;return buildExample({generatorId:e,question:n,title:`Is this coin worth ${h}?`,computed:x,steps:[{visual_data:{...y},animation:"show_coin",caption:"Look closely at the coin.",spoken:"Let's check the coin ourselves before deciding."},{visual_data:{...y},animation:"highlight_value",caption:g?`The £ sign means pounds: £${b}.`:`The number ${b} is how many pence.`,spoken:E},{visual_data:{...y},animation:"write_value",caption:k,spoken:T}]})}const meaP4M01Generator={id:"MEA.P4.M01.name-the-coin.v1",microSkillId:"MEA.P4.M01",forms:["coin_recognition"],generate(n){const t=coinCompareDemo(n,this.id);if(t)return t;const e=singleCoinFromVisual(n);if(!e)return null;const{denom:s,pence:o}=e,r=coinClaimDemo(n,e,this.id);if(r)return r;const a=penceLabel$1(o);if(a!==n.correctAnswer.trim())return null;const i=o>=100&&o%100===0,l=i?o/100:0,u=i?l:o,d={face:u,pence:o,answer:o},m=i?`The £ sign means pounds: £${l}.`:`The number ${u} is how many pence.`,h=`This coin is worth ${a}.`,f=i?`Find the pound sign on the coin — it shows ${valueWords(o)}.`:`Find the number on the coin — it says ${numberToWords(u)}. That tells you how many pence the coin is worth.`,g=i?`${capitalised(valueWords(o))}. The coin tells you its value.`:`${capitalised(valueWords(o))}. Pence is our small money, and we write it with a little p.`,b={type:"coin_set",coins:[s]};return buildExample({generatorId:this.id,question:n,title:"How much is this coin worth?",computed:d,steps:[{visual_data:{...b},animation:"show_coin",caption:"Look closely at the coin.",spoken:"Here's a coin. Every coin tells you what it's worth."},{visual_data:{...b},animation:"highlight_value",caption:m,spoken:f},{visual_data:{...b},animation:"write_value",caption:h,spoken:g}]})}},PENCE={"1p":1,"2p":2,"5p":5,"10p":10,"20p":20,"50p":50,"£1":100,"£2":200};function penceLabel(n){return n>=100&&n%100===0?`£${n/100}`:`${n}p`}function coinValue(n){return PENCE[n]??null}function orderedValues(n){if(!Array.isArray(n)||n.length<1||n.length>4)return null;const t=[];for(const e of n){const s=coinValue(e);if(s===null)return null;t.push({denom:e,value:s})}return t.sort((e,s)=>s.value-e.value),t}function greedySubset(n,t){const e=n.map(coinValue);if(e.some(a=>a===null))return null;const s=n.map((a,i)=>({denom:a,value:e[i]})).sort((a,i)=>i.value-a.value),o=[];let r=t;for(const a of s)a.value<=r&&(o.push(a.denom),r-=a.value);return r===0?o:null}function runningTotals(n){const t=[];let e=0;
71for(const s of n)e+=s,t.push(e);return t}function buildCountUpDemo(n,t,e,s){const o=Number(n.correctAnswer.trim());if(!Number.isInteger(o)||o!==e||e<1||e>999)return null;const r=t.map(T=>T.denom),a=t.map(T=>T.value),i=runningTotals(a),l=a[0],u={total:e,answer:e};i.forEach((T,A)=>{u[`r${A}`]=T});const d=t.length===1,m=capitalised(numberToWords(t.length)),h=d?"Look at the coin.":`${m} coins to add up.`,f=d?`Just one coin: ${penceLabel(e)}.`:`Count on: ${i.join(", ")}.`,g=`Altogether that's ${penceLabel(e)}.`,b=a.every(T=>T===l),x=d?"There is just one coin. The number on it tells us what it is worth, and that is the total.":"The number on each coin tells us what it is worth. We add up what the coins are worth, not how many coins there are."+(b?"":" We start with the coin worth the most, not the one that looks biggest, because it's easiest to count on from."),y=d?`The coin is worth ${penceWords$1(e)}.`:`We start at ${numberToWords(l)}, then count on what each coin is worth. `+a.slice(1).map((T,A)=>`${capitalised(numberToWords(T))} more makes ${numberToWords(i[A+1])}.`).join(" "),E=`So altogether the coins are worth ${penceWords$1(e)}.`,k={type:"coin_set",coins:r};return buildExample({generatorId:meaP4M02Generator.id,question:n,title:s?`How do we pay ${penceLabel(e)}?`:"How much is this altogether?",computed:u,steps:[s??{visual_data:{...k,coins:[...r]},animation:"show_coins",caption:h,spoken:x},{visual_data:{...k,coins:[...r]},animation:"count_on",caption:f,spoken:y},{visual_data:{...k,coins:[...r]},animation:"write_total",caption:g,spoken:E}]})}function penceWords$1(n){if(n>=100&&n%100===0){const t=n/100;return`${numberToWords(t)} pound${t===1?"":"s"}`}return n===1?"one penny":`${numberToWords(n)} pence`}function tryCoinTotal(n){const t=n.visualData;if(!t||t.type!=="coin_set")return null;const e=orderedValues(t.coins);if(!e)return null;const s=e.reduce((o,r)=>o+r.value,0);return buildCountUpDemo(n,e,s)}const CLAIM_TOTAL=/=\s*(\d+)p\b/i;function tryCoinTotalClaim(n){const t=n.visualData;if(!t||t.type!=="coin_set")return null;const e=n.correctAnswer.trim();if(e!=="True"&&e!=="False")return null;const s=orderedValues(t.coins);if(!s)return null;const o=s.reduce((S,M)=>S+M.value,0);if(o<1||o>999)return null;const r=n.stem.match(CLAIM_TOTAL);if(!r)return null;const a=Number(r[1]);if(!Number.isInteger(a)||a<0)return null;const i=a===o?"True":"False";if(i!==e)return null;const l=s.map(S=>S.denom),u=s.map(S=>S.value),d=runningTotals(u),m=u[0],h=s.length===1,f={total:o,claimed:a,answer:o};d.forEach((S,M)=>{f[`r${M}`]=S});const g=capitalised(numberToWords(s.length)),b=h?"Look at the coin.":`${g} coins to add up.`,x=h?`Just one coin: ${penceLabel(o)}.`:`Count on: ${d.join(", ")}.`,y=i==="True"?`Altogether that's ${penceLabel(o)} — so True.`:`Altogether that's ${penceLabel(o)}, not ${penceLabel(a)} — so False.`,E=u.every(S=>S===m),k=h?"To check the claim, we read the number on the coin — it tells us what the coin is worth.":"To check the claim, we add up what the coins are worth, not how many coins there are."+(E?"":" We start with the coin worth the most, not the one that looks biggest, because it's easiest to count on from."),T=h?`The coin is worth ${penceWords$1(o)}.`:`We start at ${numberToWords(m)}, then count on what each coin is worth. `+u.slice(1).map((S,M)=>`${capitalised(numberToWords(S))} more makes ${numberToWords(d[M+1])}.`).join(" "),A=i==="True"?`Altogether the coins make ${penceWords$1(o)}. That matches the claim, so it's true.`:`Altogether the coins make ${penceWords$1(o)}, not ${penceWords$1(a)}. So it's false.`,N={type:"coin_set",coins:l};return buildExample({generatorId:meaP4M02Generator.id,question:n,title:"Check the claim: is it right?",computed:f,steps:[{visual_data:{...N,coins:[...l]},animation:"show_coins",caption:b,spoken:k},{visual_data:{...N,coins:[...l]},animation:"count_on",caption:x,spoken:T},{visual_data:{...N,coins:[...l]},animation:"write_total",caption:y,spoken:A}]})}function tryCoinTap(n){const t=n.visualData;if(!t||t.type!=="coin_tap")return null;const{target:e,tray:s}=t;if(!Number.isInteger(e)||e<1)return null;const o=greedySubset(s,e);if(!o)return null;const r=orderedValues(o);if(!r)return null;const a=s.map(b=>b),i=r[0].value,u=s.length-o.length>0?" The other coins stay on the tray.":"",d=r.slice(1).map(b=>penceWords$1(b.value)),m=d.length<=1?d.join(""):`${d.slice(0,-1).join(", ")} and ${d[d.length-1]}`,h=r.length===1?`We need ${penceWords$1(e)}. One coin makes it on its own — the coin worth the most that fits.${u}`:`We need ${penceWords$1(e)} — but not every coin. Take the coin worth the most that fits: ${penceWords$1(i)}. Then ${m} ${d.length===1?"makes":"make"} it up to ${penceWords$1(e)}.${u}`,f=[];for(const b of r){const x=a.findIndex((y,E)=>y===b.denom&&!f.includes(E));x>=0&&f.push(x)}const g={visual_data:{type:"coin_set",coins:a,chosen:f.sort((b,x)=>b-x)},animation:"show_tray",caption:`Choose coins to make ${e}p.`,spoken:h};return buildCountUpDemo(n,r,e,g)}const meaP4M02Generator={id:"MEA.P4.M02.count-the-coins.v1",microSkillId:"MEA.P4.M02",forms:["coin_total","coin_tap"],generate(n){return tryCoinTotal(n)??tryCoinTap(n)??tryCoinTotalClaim(n)}},SUB_CHANGE=/^(\d+)p - (\d+)p = __ p change$/,COSTS_CHANGE=/^A (\w+) costs (\d+)p\. (?:You pay|Pay) (\d+)p\. (?:Change\?|How much change do you get\?)$/,PAY_COST_CHANGE=/^Pay (\d+)p, cost (\d+)p\. Change = __ p$/,PAY_FOR_CHANGE=/^Pay (\d+)p for an? (\d+)p (\w+)\. How much change(?: do you get)?\?$/,TWO_DIFF=/^You buy an? (\d+)p (\w+) and an? (\d+)p (\w+)\. You pay (\d+)p\. How much change do you get\?$/,TWO_SAME=/^You buy two (\d+)p (\w+)\. You pay (\d+)p\. How much change do you get\?$/,CLAIM_CHANGE=/^[A-Z][a-z]+ pays for an? (\d+)p (\w+) with a (\d+)p coin\. (?:He|She) says (?:his|her) change is (\d+)p\. Is (?:he|she) right\?$/,TWO_ITEM_CONTEXT=/^At the school book fair, an? (\w+) costs (\d+)p and an? (\w+) costs (\d+)p\. You pay for both with a (\d+)p coin\. How much change do you get\?$/,REVERSE_COST=/^[A-Z][a-z]+ pays with a (\d+)p coin for (?:his|her|their) (\w+) and gets (\d+)p change\. How much did the \2 cost\?$/,REVERSE_SYMBOLIC_COST=/^Change is (\d+)p\. Pay (\d+)p\. Cost = __ p$/,PAY_FOR_EXPR_CHANGE=/^Pay (\d+)p for an? (\d+)p (\w+)\. Which is the change\?$/,SHOP_COMPARE=/^Shop A sells an? .+ for (\d+)p\. Shop B sells the same .+ for (\d+)p\. You pay with a (\d+)p coin at each shop\. Which shop gives you more change\?$/,PAYS_FOR_COSTING=/^[A-Z][a-z]+ pays (\d+)p for an? (.+?) costing (\d+)p\. How much change does (?:he|she) get\?$/,SHOP_AB_COMPARE=/^An? .+ costs (\d+)p in Shop A and (\d+)p in Shop B\. You pay (\d+)p at each\. Which shop gives more change\?$/,TWO_ITEM_PLAIN=/^An? (\w+) costs (\d+)p and an? (\w+) costs (\d+)p\. You pay for both with (\d+)p\. How much change do you get\?$/;function penceWords(n){return n===1?"one penny":`${numberToWords(n)} pence`}function changeAnswer(n){const t=answerInt(n);if(t!==null)return t;const e=n.correctAnswer.trim().replace(/p$/i,"").trim(),s=Number(e);return Number.isInteger(s)?s:null}const GENERATOR_ID$2="MEA.P4.M03.count-up-change.v1";
71function buildTwoItemChange(n,t,e,s){if(t<1||e<1||![10,20,50].includes(s))return null;const o=t+e;if(o<=0||o>=s||s>99)return null;const r=s-o;if(r!==changeAnswer(n))return null;const a=0,i=Math.ceil((s+5)/5)*5,l={from:0,to:t,label:`+${t}`},u={from:t,to:o,label:`+${e}`},d={from:o,to:s,label:`+${r}`},m={c1:t,c2:e,total:o,pay:s,change:r,min:a,max:i,answer:r};return buildExample({generatorId:GENERATOR_ID$2,question:n,title:`How much change from ${s}p?`,computed:m,steps:[{visual_data:{type:"number_line",min:a,max:i,marked:[0],hops:[l,u],unknown:null},animation:"show_points",caption:`The two items cost ${t} + ${e} = ${o}p.`,spoken:`You pay for both items, so first add the two prices. ${capitalised(penceWords(t))} and ${penceWords(e)} make ${penceWords(o)} altogether.`},{visual_data:{type:"number_line",min:a,max:i,marked:[o],hops:[d],unknown:null},animation:"count_up",caption:`Count up ${o} → ${s}: that's ${r}.`,spoken:`You pay ${penceWords(s)}. The change is the gap between the cost and what you pay, so we count up from ${numberToWords(o)} to ${numberToWords(s)}. That hop is ${numberToWords(r)}.`},{visual_data:{type:"number_line",min:a,max:i,marked:[o],hops:[d],unknown:null},animation:"land",caption:`The change is ${r}p.`,spoken:`The gap is ${numberToWords(r)}. So your change is ${penceWords(r)}.`}]})}function buildChangeClaimCheck(n,t,e,s,o){if(t<1||s<=t||s>99||o<0||o>99)return null;const r=s-t,a=r===o;if((a?`Yes, ${o}p is right`:`No, it should be ${r}p`)!==n.correctAnswer.trim())return null;const l=Math.max(0,Math.floor((t-2)/5)*5),u=Math.ceil((s+5)/5)*5,d={from:t,to:s,label:`+${r}`},m={cost:t,pay:s,claimedChange:o,actualChange:r,min:l,max:u};return buildExample({generatorId:GENERATOR_ID$2,question:n,title:`Is the change from ${s}p really ${o}p?`,computed:m,steps:[{visual_data:{type:"number_line",min:l,max:u,marked:[t,s],hops:[],unknown:null},animation:"show_points",caption:`The ${e} costs ${t}p, paid with ${s}p. Claim: ${o}p change.`,spoken:`The ${e} costs ${penceWords(t)}, paid with ${penceWords(s)}. Someone says the change is ${penceWords(o)}. Check by counting up.`},{visual_data:{type:"number_line",min:l,max:u,marked:[t],hops:[d],unknown:null},animation:"count_up",caption:`Count up ${t} → ${s}: that's ${r}.`,spoken:`Hop up from ${numberToWords(t)} to ${numberToWords(s)}. That hop is ${numberToWords(r)}.`},{visual_data:{type:"number_line",min:l,max:u,marked:[t],hops:[d],unknown:null},animation:"land",caption:a?`The change really is ${r}p — right.`:`The change is ${r}p, not ${o}p — wrong.`,spoken:a?`The real change is ${penceWords(r)}. The claim is right.`:`The real change is ${penceWords(r)}, not ${numberToWords(o)}. The claim is wrong.`}]})}function buildCostFromChange(n,t,e,s){if(s<0||t<=s||t>99)return null;const o=t-s;if(o!==changeAnswer(n))return null;const r=Math.max(0,Math.floor((o-2)/5)*5),a=Math.ceil((t+5)/5)*5,i={from:t,to:o,label:`-${s}`},l={pay:t,change:s,cost:o,min:r,max:a,answer:o};return buildExample({generatorId:GENERATOR_ID$2,question:n,title:`What did the ${e} cost?`,computed:l,steps:[{visual_data:{type:"number_line",min:r,max:a,marked:[t],hops:[],unknown:null},animation:"show_points",caption:`Paid ${t}p for the ${e}, got ${s}p change.`,spoken:`The ${e} was paid for with ${penceWords(t)}, and the change back was ${penceWords(s)}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[t,o],hops:[i],unknown:null},animation:"count_back",caption:`Count back ${s} from ${t}: lands on ${o}.`,spoken:`The cost is what you paid take away the change, so we count back ${numberToWords(s)} from ${numberToWords(t)}. That lands on ${numberToWords(o)}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[t,o],hops:[i],unknown:null},animation:"land",caption:`So the ${e} cost ${o}p.`,spoken:`So the ${e} cost ${penceWords(o)}.`}]})}function buildCostFromChangeSymbolic(n,t,e){if(t<0||e<=t||e>99)return null;const s=e-t;if(s!==changeAnswer(n))return null;const o=Math.max(0,Math.floor((s-2)/5)*5),r=Math.ceil((e+5)/5)*5,a={from:e,to:s,label:`-${t}`};return buildExample({generatorId:GENERATOR_ID$2,question:n,title:"What was the cost?",computed:{pay:e,change:t,cost:s,min:o,max:r,answer:s},steps:[{visual_data:{type:"number_line",min:o,max:r,marked:[e],hops:[],unknown:null},animation:"show_points",caption:`Change is ${t}p. You paid ${e}p.`,spoken:`The change was ${penceWords(t)}. You paid ${penceWords(e)}.`},{visual_data:{type:"number_line",min:o,max:r,marked:[e,s],hops:[a],unknown:null},animation:"count_back",caption:`Count back ${t} from ${e}: lands on ${s}.`,spoken:`The cost is what you paid take away the change, so we count back ${numberToWords(t)} from ${numberToWords(e)}. That lands on ${numberToWords(s)}.`},{visual_data:{type:"number_line",min:o,max:r,marked:[e,s],hops:[a],unknown:null},animation:"land",caption:`So the cost is ${s}
71p.`,spoken:`So the cost is ${penceWords(s)}.`}]})}function buildExpressionChange(n,t,e,s){if(e<1||t<=e||t>99)return null;const o=t-e;if(`${t}p − ${e}p`!==n.correctAnswer.trim())return null;const a=Math.ceil((t+5)/5)*5,i={from:t,to:o,label:`-${e}`},l=Math.max(0,Math.floor((Math.min(e,o)-2)/5)*5),u={cost:e,pay:t,change:o,min:l,max:a};return buildExample({generatorId:GENERATOR_ID$2,question:n,title:`Which expression gives the change from ${t}p?`,computed:u,steps:[{visual_data:{type:"number_line",min:l,max:a,marked:[e,t],hops:[],unknown:null},animation:"show_points",caption:`The ${s} costs ${e}p, you pay ${t}p.`,spoken:`The ${s} costs ${penceWords(e)}. You pay ${penceWords(t)}.`},{visual_data:{type:"number_line",min:l,max:a,marked:[t],hops:[i],unknown:null},animation:"count_back",caption:`The change is what you pay take away the cost: ${t}p − ${e}p.`,spoken:`The change is what you paid, take away the cost. So we start at ${numberToWords(t)} and hop back ${numberToWords(e)}: ${penceWords(t)} take away ${penceWords(e)}.`},{visual_data:{type:"number_line",min:l,max:a,marked:[t,o],hops:[i],unknown:null},animation:"land",caption:`${t}p − ${e}p = ${o}p.`,spoken:`${capitalised(penceWords(t))} take away ${penceWords(e)} is ${penceWords(o)}. That is the change.`}]})}function buildShopChangeCompare(n,t,e,s){if(t<1||e<1||s<=t||s<=e||s>99)return null;const o=s-t,r=s-e;if(o===r)return null;const a=o>r?"Shop A":"Shop B";if(a!==n.correctAnswer.trim())return null;const i=Math.max(o,r),l=Math.min(o,r),u=Math.max(0,Math.floor((Math.min(t,e)-2)/5)*5),d=Math.ceil((s+5)/5)*5,m={from:t,to:s,label:`+${o}`},h={from:e,to:s,label:`+${r}`},f={costA:t,costB:e,pay:s,changeA:o,changeB:r,bigger:i,smaller:l};return buildExample({generatorId:GENERATOR_ID$2,question:n,title:`Which shop gives more change from ${s}p?`,computed:f,steps:[{visual_data:{type:"number_line",min:u,max:d,marked:[t],hops:[m],unknown:null},animation:"show_points",caption:`Shop A: cost ${t}p, pay ${s}p. Change is ${o}p.`,spoken:`Shop A costs ${numberToWords(t)} pence. Paying ${numberToWords(s)} pence gives ${penceWords(o)} change.`},{visual_data:{type:"number_line",min:u,max:d,marked:[e],hops:[h],unknown:null},animation:"count_up",caption:`Shop B: cost ${e}p, pay ${s}p. Change is ${r}p.`,spoken:`Shop B costs ${numberToWords(e)} pence. Paying ${numberToWords(s)} pence gives ${penceWords(r)} change.`},{visual_data:{type:"comparison",left:o,right:r,symbol:o>r?">":"<"},animation:"write_symbol",caption:`${i}p > ${l}p — ${a} gives more change.`,spoken:`${capitalised(numberToWords(i))} pence is more than ${numberToWords(l)} pence, so ${a} gives more change.`}]})}const meaP4M03Generator={id:GENERATOR_ID$2,microSkillId:"MEA.P4.M03",forms:["varied_symbolic_calculation","context_calculation","symbolic_numeric_entry","symbolic_choice","context_reasoning","misconception_contrast_probe","comparison_statement"],generate(n){if(!this.forms.includes(n.mathematicalForm??""))return null;const t=normaliseStem(n.stem),e=t.match(CLAIM_CHANGE);if(e)return buildChangeClaimCheck(n,Number(e[1]),e[2],Number(e[3]),Number(e[4]));const s=t.match(TWO_ITEM_CONTEXT);if(s)return buildTwoItemChange(n,Number(s[2]),Number(s[4]),Number(s[5]));const o=t.match(REVERSE_COST);if(o)return buildCostFromChange(n,Number(o[1]),o[2],Number(o[3]));const r=t.match(REVERSE_SYMBOLIC_COST);if(r)return buildCostFromChangeSymbolic(n,Number(r[1]),Number(r[2]));const a=t.match(PAY_FOR_EXPR_CHANGE);if(a)return buildExpressionChange(n,Number(a[1]),Number(a[2]),a[3]);const i=t.match(SHOP_COMPARE);if(i)return buildShopChangeCompare(n,Number(i[1]),Number(i[2]),Number(i[3]));const l=t.match(SHOP_AB_COMPARE);if(l)return buildShopChangeCompare(n,Number(l[1]),Number(l[2]),Number(l[3]));const u=t.match(TWO_ITEM_PLAIN);if(u)return buildTwoItemChange(n,Number(u[2]),Number(u[4]),Number(u[5]));const d=t.match(TWO_DIFF);if(d)return buildTwoItemChange(n,Number(d[1]),Number(d[3]),Number(d[5]));const m=t.match(TWO_SAME);if(m){const K=Number(m[1]);return buildTwoItemChange(n,K,K,Number(m[3]))}let h,f,g;const b=t.match(SUB_CHANGE),x=t.match(COSTS_CHANGE),y=t.match(PAY_COST_CHANGE),E=t.match(PAY_FOR_CHANGE),k=t.match(PAYS_FOR_COSTING);if(b)f=Number(b[1]),h=Number(b[2]),g=null;else if(x)g=x[1],h=Number(x[2]),f=Number(x[3]);else if(y)f=Number(y[1]),h=Number(y[2]),g=null;else if(E)f=Number(E[1]),h=Number(E[2]),g=E[3];else if(k)f=Number(k[1]),g=k[2],h=Number(k[3]);else return null;if(h<1||f<=h||f>99)return null;const T=f-h;if(T!==changeAnswer(n))return null;const A=Math.max(0,Math.floor((T-2)/5)*5),N=Math.ceil((f+5)/5)*5,S=h%10,M=h>10&&h<20&&S>0,L=f-10,P=Math.floor(f/10)*10,F=!M&&h<10&&f%10!==0&&T<P,D=f-P,U=P-T,q=M?[{from:f,to:L,label:"-10"},{from:L,to:T,label:`-${S}`}]:F?[{from:f,to:P,label:`-${D}`},{from:P,to:T,label:`-${U}`}]:[{from:f,to:T,label:`-${h}`}],V=F?{cost:h,pay:f,change:T,min:A,max:N,answer:T,ten:P,to_ten:D,past_ten:U}:{cost:h,pay:f,change:T,min:A,max:N,answer:T},O=g===null?"It costs":`The ${g} costs`,z=M?`Start at ${numberToWords(f)} and take away ${numberToWords(h)}
71: take away ten first — that's ${numberToWords(L)} — then ${numberToWords(S)} more. We land on ${numberToWords(T)}.`:F?`Start at ${numberToWords(f)} and take away ${numberToWords(h)}: take away ${numberToWords(D)} to get to ${numberToWords(P)}, then ${numberToWords(U)} more. We land on ${numberToWords(T)}.`:`Start at ${numberToWords(f)} and hop back ${numberToWords(h)}. We land on ${numberToWords(T)}.`;return buildExample({generatorId:this.id,question:n,title:`How do we find the change from ${f}p?`,computed:V,steps:[{visual_data:{type:"number_line",min:A,max:N,marked:[f],hops:[],unknown:null},animation:"show_points",caption:`You pay ${f}p. It costs ${h}p.`,spoken:`${O} ${penceWords(h)}. You pay ${penceWords(f)}. The change is what is left, so we start at ${numberToWords(f)} and take away the cost.`},{visual_data:{type:"number_line",min:A,max:N,marked:[f],hops:q,unknown:null},animation:"count_back",caption:`Take away ${h}: ${f} → ${T}.`,spoken:z},{visual_data:{type:"number_line",min:A,max:N,marked:[f,T],hops:q,unknown:null},animation:"land",caption:`${f}p − ${h}p = ${T}p change.`,spoken:`${capitalised(numberToWords(f))} take away ${numberToWords(h)} leaves ${numberToWords(T)}. So your change is ${penceWords(T)}.`}]})}},CHOICE_LABEL=/^(\d+) (g|kg)$/,UNIT_STEM=/^How many (grams|kilograms) does the scale show\?$/,CLAIM_READING=/^[A-Z][a-z]+ weighs an? (\w+) on the kitchen scale\. (?:He|She) says it weighs (\d+) (g|kg)\. Is (?:he|she) right\?$/,THRESHOLD=/^A recipe needs (\d+) (g|kg) of (\w+)\. The scale shows how much \3 has been weighed so far\. Is there enough \3 yet\?$/,MORE_OR_LESS=/^Is the mass more or less than (\d+) (g|kg)\?$/,HOW_HEAVY=/^How heavy is the (\w+), in (grams|kilograms)\?$/,NICE_INTERVALS=[200,100,50,25,20,10,5,2,1];function chooseInterval(n){for(const t of NICE_INTERVALS)if(n%t===0&&n/t<=5)return t;return null}function chooseMax(n,t){let e=Math.max(5,Math.ceil(n/t));return e*t<=n&&(e+=1),e*t}function massWords(n,t){const e=t==="kg"?"kilogram":"gram";return`${numberToWords(n)} ${e}${n===1?"":"s"}`}function dialFromVisual(n){const t=n.visualData;if(!t||t.type!=="mass_scale")return null;const{reading:e,max:s,unit:o,interval:r}=t;return!Number.isInteger(e)||!Number.isInteger(s)||o!=="g"&&o!=="kg"?null:{reading:e,max:s,unit:o,interval:r}}function unlabelledBracket(n,t,e){const s=e&&e>0?e:Math.max(1,Math.round(t/5)),o=Math.round(t/s);if(o+1<=8||n%s!==0)return null;const r=n/s;return r%2!==1||r>=o?null:{lower:n-s,upper:n+s,span:2*s,mark:s}}function markValueStep(n,t,e){const s=e?`The needle is between the labelled numbers ${numberToWords(t.lower)} and ${numberToWords(t.upper)}. `:"";return{visual_data:{...n},animation:"mark_value",caption:`From ${t.lower} to ${t.upper} is ${t.span}: each mark is ${t.mark}.`,spoken:`${s}From ${numberToWords(t.lower)} to ${numberToWords(t.upper)} is ${numberToWords(t.span)}. The marks split it into two equal steps, so each mark is worth ${numberToWords(t.mark)}.`}}function withCountOn(n,t){const e=n[0].visual_data,s=unlabelledBracket(e.reading,e.max,e.interval);if(!s)return{steps:n,computed:t};const o=e.unit,[r,,a]=n;return{steps:[r,markValueStep({...e,unit:o},s,!0),{...a,animation:"count_on",caption:`Count on: ${s.lower}, ${e.reading}. ${a.caption}`,spoken:`Count on one mark from ${numberToWords(s.lower)}: ${numberToWords(s.lower)}, ${numberToWords(e.reading)}. ${a.spoken}`}],computed:{...t,lower:s.lower,upper:s.upper,span:s.span,mark:s.mark}}}const GENERATOR_ID_CLAIM="MEA.P4.M05.read-the-dial.v1";function buildReadingClaimCheck(n,t,e,s){const o=dialFromVisual(n);if(!o||o.unit!==s)return null;const{reading:r,max:a,unit:i,interval:l}=o,u=r===e;if((u?`Yes, ${e} ${i} is right`:`No, it is ${r} ${i}`)!==n.correctAnswer.trim())return null;const m={type:"mass_scale",reading:r,max:a,unit:i,interval:l},h={reading:r,claimedReading:e,max:a},f=withCountOn([{visual_data:{...m},animation:"show_dial",caption:`Someone says the ${t} weighs ${e} ${i}. Check the needle.`,spoken:`Someone says the ${t} weighs ${massWords(e,i)}. Find where the needle really points.`},{visual_data:{...m}
71,animation:"sweep",caption:"Follow the needle round…",spoken:"Follow the needle as it points round the dial."},{visual_data:{...m},animation:"read",caption:u?`It points to ${r} — the claim is right.`:`It points to ${r}, not ${e} — wrong.`,spoken:u?`The needle points to ${massWords(r,i)}. The claim is right.`:`The needle points to ${massWords(r,i)}, not ${massWords(e,i)}. The claim is wrong.`}],h);return buildExample({generatorId:GENERATOR_ID_CLAIM,question:n,title:`Is the ${t} really ${e} ${i}?`,computed:f.computed,steps:f.steps})}function buildThresholdCompare(n,t,e,s){const o=dialFromVisual(n);if(!o||o.unit!==e)return null;const{reading:r,max:a,unit:i,interval:l}=o;if((r>t?`Yes, ${t} ${i} or more`:r===t?`Exactly ${t} ${i} so far`:`No, less than ${t} ${i} so far`)!==n.correctAnswer.trim())return null;const d={type:"mass_scale",reading:r,max:a,unit:i,interval:l},m={reading:r,threshold:t,max:a},h=r>t?"more than":r===t?"exactly":"less than",f=withCountOn([{visual_data:{...d},animation:"show_dial",caption:`The recipe needs ${t} ${i}. Read what's on the scale so far.`,spoken:`The recipe needs ${massWords(t,i)} of ${s}. Find where the needle points so far.`},{visual_data:{...d},animation:"sweep",caption:"Follow the needle round…",spoken:"Follow the needle as it points round the dial."},{visual_data:{...d},animation:"read",caption:`It shows ${r} — ${h} ${t} ${i}.`,spoken:`The needle points to ${massWords(r,i)} — ${h} the ${massWords(t,i)} needed.`}],m);return buildExample({generatorId:GENERATOR_ID_CLAIM,question:n,title:`Is there enough ${s} yet?`,computed:f.computed,steps:f.steps})}function buildMoreOrLessCompare(n,t,e){const s=dialFromVisual(n);if(!s||s.unit!==e)return null;const{reading:o,max:r,unit:a,interval:i}=s;if((o>t?`More than ${t} ${a}`:o===t?`Exactly ${t} ${a}`:`Less than ${t} ${a}`)!==n.correctAnswer.trim())return null;const u={type:"mass_scale",reading:o,max:r,unit:a,interval:i},d={reading:o,threshold:t,max:r},m=o>t?"more than":o===t?"exactly":"less than",h=withCountOn([{visual_data:{...u},animation:"show_dial",caption:"Read what the scale shows so far.",spoken:"Find where the needle is pointing."},{visual_data:{...u},animation:"sweep",caption:"Follow the needle round…",spoken:"Follow the needle as it points round the dial."},{visual_data:{...u},animation:"read",caption:`It shows ${o} — ${m} ${t} ${a}.`,spoken:`The needle points to ${massWords(o,a)} — ${m} ${massWords(t,a)}.`}],d);return buildExample({generatorId:GENERATOR_ID_CLAIM,question:n,title:`Is the mass more or less than ${t} ${a}?`,computed:h.computed,steps:h.steps})}function buildHowHeavy(n,t,e){const s=e==="kilograms"?"kg":"g",o=dialFromVisual(n);if(!o||o.unit!==s)return null;const{reading:r,max:a,interval:i}=o;if(r!==answerInt(n))return null;const l={type:"mass_scale",reading:r,max:a,unit:s,interval:i},u={reading:r,max:a,answer:r},d=withCountOn([{visual_data:{...l},animation:"show_dial",caption:`Find where the ${t}'s needle points.`,spoken:`The ${t} is on the scale. Find where the needle is pointing.`},{visual_data:{...l},animation:"sweep",caption:"Follow the needle round…",spoken:"Follow the needle as it points round the dial."},{visual_data:{...l},animation:"read",caption:`It points to ${r}. The ${t} weighs ${r} ${s}.`,spoken:`The needle points to ${numberToWords(r)}. The ${t} weighs ${massWords(r,s)}.`}],u);return buildExample({generatorId:GENERATOR_ID_CLAIM,question:n,title:`How heavy is the ${t}?`,computed:d.computed,steps:d.steps})}const meaP4M05Generator={id:"MEA.P4.M05.read-the-dial.v1",microSkillId:"MEA.P4.M05",forms:["symbolic_choice","misconception_contrast_probe","symbolic_numeric_entry","comparison_statement","context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_READING);if(t)return buildReadingClaimCheck(n,t[1],Number(t[2]),t[3]);const e=normaliseStem(n.stem).match(THRESHOLD);if(e)return buildThresholdCompare(n,Number(e[1]),e[2],e[3]);const s=normaliseStem(n.stem).match(MORE_OR_LESS);if(s)return buildMoreOrLessCompare(n,Number(s[1]),s[2]);const o=normaliseStem(n.stem).match(HOW_HEAVY);if(o)return buildHowHeavy(n,o[1],o[2]);let r,a,i=null,l=null;const u=n.stem.trim().match(UNIT_STEM),d=u?null:n.correctAnswer.trim().match(CHOICE_LABEL),m=n.visualData&&n.visualData.type==="mass_scale"?n.visualData:null;if(u){const y=answerInt(n);if(y===null)return null;r=y,a=u[1]==="kilograms"?"kg":"g"}else if(d){if(r=Number(d[1]),a=d[2],`${r} ${a}`!==n.correctAnswer.trim())return null}else if(m&&Number.isInteger(m.reading)&&(m.unit==="g"||m.unit==="kg")){if(r=m.reading,a=m.unit,answerInt(n)!==r)return null;i=Number.isInteger(m.max)?m.max:null,l=Number.isInteger(m.interval)?m.interval:null}else return null;if(i===null&&m&&m.reading===r&&m.unit===a&&(i=Number.isInteger(m.max)?m.max:null,l=Number.isInteger(m.interval)?m.interval:null),!Number.isInteger(r)||r<1||r>999)return null;let h,f;if(i!==null&&l!==null&&l>0&&i>r&&r%l===0)h=l,f=i;else{const y=chooseInterval(r);
71if(y===null)return null;h=y,f=chooseMax(r,h)}if(f>1e3)return null;if(d){if(`${r} ${a}`!==n.correctAnswer.trim())return null}else if(r!==answerInt(n))return null;const g={reading:r,max:f,interval:h,answer:r},b={type:"mass_scale",reading:r,max:f,unit:a,interval:h},x=unlabelledBracket(r,f,h);if(x){const{lower:y,upper:E}=x;return buildExample({generatorId:this.id,question:n,title:"What does the scale show?",computed:{...g,lower:y,upper:E,span:x.span,mark:x.mark},steps:[{visual_data:{...b},animation:"show_dial_bracket",caption:`The needle is between ${y} and ${E}.`,spoken:`A kitchen scale. First find the two labelled numbers the needle is between: ${numberToWords(y)} and ${numberToWords(E)}.`},markValueStep(b,x,!1),{visual_data:{...b},animation:"count_on",caption:`Count on from ${y}: ${y}, ${r}. The mass is ${r} ${a}.`,spoken:`The needle is one mark past ${numberToWords(y)}. Count on one mark: ${numberToWords(y)}, ${numberToWords(r)}. The mass is ${massWords(r,a)}.`}]})}return buildExample({generatorId:this.id,question:n,title:"What does the scale show?",computed:g,steps:[{visual_data:{...b},animation:"show_dial",caption:"Find where the needle points.",spoken:"A kitchen scale. Find where the needle is pointing."},{visual_data:{...b},animation:"sweep",caption:"Follow the needle round…",spoken:"Follow the needle as it points round the dial."},{visual_data:{...b},animation:"read",caption:`It points to ${r}. The mass is ${r} ${a}.`,spoken:`The needle points to ${numberToWords(r)}. The mass is ${massWords(r,a)}.`}]})}},M_TO_CM=/^(\d+) m = __ cm$/,COMPARE$5=/^Which is (longer|shorter): (.+?) or (.+?)\?$/,CM_DIFFERENCE=/^An? (.+?) is (\d+) cm long\. An? (.+?) is (\d+) cm long\. How many cm (longer|shorter) is the (.+?)\?$/,ITEM_LENGTH_COMPARE=/^An? (.+?) is (\d+) cm long\. An? (.+?) is (\d+) cm long\. Which is (shorter|longer)\?$/,ORDER_SHORTEST=/^Order from shortest: (\d+) cm, (\d+) cm, (\d+) cm$/,OWNER_COMPARE=/^(\w+)'s (\w+) is (\d+) cm\. (\w+)'s is (\d+) cm\. Whose is (longer|shorter)\?$/,SUPERLATIVE_OF_THREE=/^Which is the (longest|shortest): (\d+) cm, (\d+) cm,? or (\d+) cm\?$/,SAME_AS_LENGTH=/^Which length is the same as (\d+) cm\?$/,BAR_HOW_LONG=/^How long is the bar in cm\?$/,BAR_HOW_LONG_LONGEST=/^How long is the (longer|longest) bar in cm\?$/,BAR_DIFFERENCE=/^How much (longer|shorter) is the (\w+) bar than the (\w+) bar\?$/,WHICH_IS_ASK=/^Which is (longer|shorter)\?$/,WHICH_TWO_EQUAL=/^Which two bars are equal in length\?$/,CM_PER_M=100;function metresWords(n){return n===1?"one metre":`${numberToWords(n)} metres`}function cmAnswer(n){const t=answerInt(n);if(t!==null)return t;const e=n.correctAnswer.trim().replace(/cm$/i,"").trim(),s=Number(e);return Number.isInteger(s)?s:null}function measureBars(n){const t=n.visualData;if(!t||t.type!=="measure_bars")return null;const e=t;if(!Array.isArray(e.bars)||e.bars.length===0)return null;const s=e.bars.filter(o=>!/ruler/i.test(o.label));return s.length===0||s.some(o=>!Number.isInteger(o.length)||o.length<0)?null:s}function readingBar(n,t,e="#2563eb",s=0){return{label:n,length:t,color:e,start:s}}const meaP5M01Generator={id:"MEA.P5.M01.metres-to-cm.v1",microSkillId:"MEA.P5.M01",forms:["comparison_statement","comparative_length"],generate(n){const t=normaliseStem(n.stem),e=t.match(M_TO_CM);if(e){const d=Number(e[1]);if(d<1||d>3)return null;const m=d*CM_PER_M;if(m!==cmAnswer(n))return null;const h={label:`${d} m`,length:m,color:"#2563eb"},f={metres:d,cm:m,cmPerM:CM_PER_M,answer:m},g=d===1?"One metre is one hundred centimetres.":`${capitalised(metresWords(d))} is ${numberToWords(d)} lots of one hundred.`,b=d===1?"1 m = 100 cm.":`${d} × 100 = ${m}.`;return buildExample({generatorId:this.id,question:n,title:`How many cm is ${d} m?`,computed:f,steps:[{visual_data:{type:"measure_bars",bars:[h],unit:"cm",show_values:!1},animation:"show_object",caption:`Lay ${d} m on the ruler.`,spoken:`${capitalised(metresWords(d))} on a centimetre ruler. One metre is one hundred centimetres.`},{visual_data:{type:"measure_bars",bars:[h],unit:"cm",show_values:!1},animation:"count_cm",caption:b,spoken:g},{visual_data:{type:"measure_bars",bars:[h],unit:"cm",show_values:!0},animation:"write_length",caption:`${d} m = ${m}
71 cm.`,spoken:`So ${metresWords(d)} is ${numberToWords(m)} centimetres.`}]})}const s=t.match(COMPARE$5);if(s){const d=s[1],m=parseSide(s[2]),h=parseSide(s[3]);return m&&h&&!m.isMetre&&!h.isMetre?sameUnitCompareExample(this.id,n,d,m,h):compareExample(this.id,n,d,s[2],s[3])}const o=t.match(CM_DIFFERENCE);if(o)return cmDifferenceExample(this.id,n,{item1:o[1],len1:Number(o[2]),item2:o[3],len2:Number(o[4]),ask:o[5],subject:o[6]});const r=t.match(ITEM_LENGTH_COMPARE);if(r)return itemLengthCompareExample(this.id,n,{item1:r[1],len1:Number(r[2]),item2:r[3],len2:Number(r[4]),ask:r[5]});const a=t.match(ORDER_SHORTEST);if(a)return orderShortestExample(this.id,n,Number(a[1]),Number(a[2]),Number(a[3]));const i=t.match(OWNER_COMPARE);if(i)return ownerCompareExample(this.id,n,{owner1:i[1],item:i[2],len1:Number(i[3]),owner2:i[4],len2:Number(i[5]),ask:i[6]});const l=t.match(SUPERLATIVE_OF_THREE);if(l)return superlativeOfThreeExample(this.id,n,l[1],Number(l[2]),Number(l[3]),Number(l[4]));const u=t.match(SAME_AS_LENGTH);return u?sameAsLengthExample(this.id,n,Number(u[1])):barReadingExample(this.id,n,t)}};function parseSide(n){const t=n.trim(),e=t.match(/^(\d+)\s*(?:metres|metre|m)$/i);if(e){const o=Number(e[1]);return o>=1&&o<=9?{raw:t,cm:o*CM_PER_M,isMetre:!0,count:o}:null}const s=t.match(/^(\d+)\s*cm$/i);if(s){const o=Number(s[1]);return o>=1&&o<=999?{raw:t,cm:o,isMetre:!1,count:o}:null}return null}function compareExample(n,t,e,s,o){const r=parseSide(s),a=parseSide(o);if(!r||!a||r.isMetre===a.isMetre||r.cm===a.cm)return null;const i=r.isMetre?r:a,l=r.cm>a.cm?r:a,u=r.cm>a.cm?a:r,d=e==="longer"?l:u;if(d.raw!==t.correctAnswer.trim())return null;const m=i.cm,h=i===r?a:r,f=h.cm,g=l.cm,b=u.cm,x={metres:i.count,cmMetre:m,cmOther:f},y={label:i.raw,length:m,color:"#2563eb"},E={label:h.raw,length:f,color:"#f59e0b"},k=[y,E],T=d.isMetre?metresWords(d.count):`${numberToWords(d.count)} centimetres`,A=e==="longer"?`${g} cm > ${b} cm, so ${d.raw} is longer.`:`${b} cm < ${g} cm, so ${d.raw} is shorter.`,N=e==="longer"?`${capitalised(numberToWords(g))} centimetres is more than ${numberToWords(b)} centimetres. So ${T} is longer.`:`${capitalised(numberToWords(b))} centimetres is less than ${numberToWords(g)} centimetres. So ${T} is shorter.`;return buildExample({generatorId:n,question:t,title:`Which is ${e}: ${i.raw} or ${h.raw}?`,computed:x,steps:[{visual_data:{type:"measure_bars",bars:[y],unit:"cm",show_values:!0},animation:"show_metre",caption:`${i.raw} is the same as ${m} cm.`,spoken:`${capitalised(metresWords(i.count))} is the same as ${numberToWords(m)} centimetres.`},{visual_data:{type:"measure_bars",bars:k,unit:"cm",show_values:!0},animation:"add_cm",caption:`Same unit: ${m} cm and ${f} cm.`,spoken:"Change the metre into centimetres, so both lengths are in centimetres."},{visual_data:{type:"measure_bars",bars:k,unit:"cm",show_values:!0,compare:e},animation:"compare",caption:A,spoken:N}]})}function sameUnitCompareExample(n,t,e,s,o){if(s.cm===o.cm)return null;const r=s.cm>o.cm?s:o,a=s.cm>o.cm?o:s,i=e==="longer"?r:a;if(i.raw!==t.correctAnswer.trim())return null;const l=r.cm,u=a.cm,d={cmA:s.cm,cmB:o.cm,bigCm:l,smallCm:u},m={label:s.raw,length:s.cm,color:"#2563eb"},h={label:o.raw,length:o.cm,color:"#f59e0b"},f=[m,h],g=`${numberToWords(i.count)} centimetres`,b=e==="longer"?`${l} cm > ${u} cm, so ${i.raw} is longer.`:`${u} cm < ${l} cm, so ${i.raw} is shorter.`,x=e==="longer"?`${capitalised(numberToWords(l))} centimetres is more than ${numberToWords(u)} centimetres. So ${g} is longer.`:`${capitalised(numberToWords(u))} centimetres is less than ${numberToWords(l)} centimetres. So ${g} is shorter.`;return buildExample({generatorId:n,question:t,title:`Which is ${e}: ${s.raw} or ${o.raw}?`,computed:d,steps:[{visual_data:{type:"measure_bars",bars:f,unit:"cm",show_values:!1},animation:"show_object",caption:"Two lengths on the same centimetre ruler.",spoken:"Both lengths are already in centimetres. Read each bar from zero to where it ends."},{visual_data:{type:"measure_bars",bars:f,unit:"cm",show_values:!0},animation:"count_cm",caption:`One is ${s.cm} cm, the other ${o.cm} cm.`,spoken:`One reaches ${numberToWords(s.cm)} centimetres. The other reaches ${numberToWords(o.cm)} centimetres.`},{visual_data:{type:"measure_bars",bars:f,unit:"cm",show_values:!0,compare:e},animation:"compare",caption:b,spoken:x}]})}function sameAsLengthExample(n,t,e){if(e<1||e>999||t.correctAnswer.trim()!==`${e} cm`)return null;const s={label:`${e} cm`,length:e,color:"#2563eb"},o={label:"match",length:e,color:"#f59e0b"},r={cm:e,answer:e},a=numberToWords(e);return buildExample({generatorId:n,question:t,title:`What is the same as ${e} cm?`,computed:r,steps:[{visual_data:{type:"measure_bars",bars:[s],unit:"cm",show_values:!0},animation:"show_object",caption:`${e} cm ends at the ${e} mark.`,spoken:`Here is ${a} centimetres on the ruler. It ends exactly at the ${a} mark.`},{visual_data:{type:"measure_bars",bars:[s,o],unit:"cm",show_values:!0},animation:"count_cm",caption:'"The same as" = ends at the very same mark.',spoken:`A length that is the same as ${a} centimetres must end at the very same mark — not one mark before, not one mark past.`},{visual_data:{type:"measure_bars",bars:[s,o],unit:"cm",show_values:!0,compare:"same"},animation:"write_length",caption:`The same as ${e} cm is ${e} cm.`,spoken:`Both bars end together at ${a}. So the length that is the same as ${a} centimetres is ${a} centimetres.`}]})}function cmDifferenceExample(n,t,e){const{item1:s,len1:o,item2:r,len2:a,ask:i,subject:l}=e;if(o<1||o>999||a<1||a>999)return null;const u=Math.abs(o-a);if(u<1||u!==answerInt(t))return null;const d=l===s?o:l===r?a:null;if(d===null)return null;const m=l===s?a:o;if(i==="longer"&&d<=m||i==="shorter"&&d>=m)return null;const h=o>
71=a?s:r,f=Math.max(o,a),g=o>=a?r:s,b=Math.min(o,a),x={label:capitalised(h),length:f,color:"#2563eb"},y={label:capitalised(g),length:b,color:"#f59e0b"},E=[x,y],k={longer:f,shorter:b,gap:u,answer:u},T=numberToWords(f),A=numberToWords(b),N=numberToWords(u),S=Math.ceil((b+1)/100)*100,M=Math.ceil((b+1)/10)*10,L=columnsNeedExchange(f,b),P=L?b>=100&&S<f?S:M<f&&b%10!==0?M:null:null,F=!L&&f<100&&b>=10;P!==null&&(k.bridge=P,k.part_1=P-b,k.part_2=f-P);const[D,U,q,V]=[Math.floor(f/10),f%10,Math.floor(b/10),b%10];F&&Object.assign(k,{long_tens:D,long_ones:U,short_tens:q,short_ones:V,gap_tens:D-q,gap_ones:U-V});const O=P!==null?`${b} → ${P} → ${f}: ${P-b} + ${f-P} = ${u} cm.`:F?`${D} tens − ${q} tens = ${D-q} tens, ${U} − ${V} = ${U-V}: ${u} cm.`:`${f} − ${b} = ${u} cm.`,z=P!==null?`To take the shorter from the longer, count on from the shorter up to the longer. From ${A} up to ${numberToWords(P)} is ${numberToWords(P-b)}, then on to ${T} is ${numberToWords(f-P)}. ${capitalised(numberToWords(P-b))} and ${numberToWords(f-P)} make ${N}. So the difference is ${N} centimetres.`:F?`Take the shorter from the longer: ${T} take away ${A}. Tens first: ${numberToWords(D)} tens take away ${numberToWords(q)} tens leaves ${numberToWords(D-q)} ${D-q===1?"ten":"tens"}. Then the ones: ${numberToWords(U)} take away ${numberToWords(V)} leaves ${numberToWords(U-V)}. So the difference is ${N} centimetres.`:`Take the shorter from the longer. ${capitalised(T)} take away ${A} is ${N}. So the difference is ${N} centimetres.`;return buildExample({generatorId:n,question:t,title:`How many cm ${i} is the ${l}?`,computed:k,steps:[{visual_data:{type:"measure_bars",bars:E,unit:"cm",show_values:!1},animation:"show_object",caption:"Measure each length from the start of the ruler.",spoken:"Two lengths on a centimetre ruler. Read each one from zero to where it ends."},{visual_data:{type:"measure_bars",bars:E,unit:"cm",show_values:!0},animation:"count_cm",caption:`One is ${f} cm, the other ${b} cm.`,spoken:`The longer one reaches ${T} centimetres. The shorter one reaches ${A} centimetres.`},{visual_data:{type:"measure_bars",bars:E,unit:"cm",show_values:!0,diff:u},animation:"write_length",caption:O,spoken:z}]})}function itemLengthCompareExample(n,t,e){const{item1:s,len1:o,item2:r,len2:a,ask:i}=e;if(o<1||o>999||a<1||a>999||o===a)return null;const l=o>=a?s:r,u=o>=a?r:s,d=Math.max(o,a),m=Math.min(o,a),h=i==="longer"?l:u;if(capitalised(h)!==t.correctAnswer.trim())return null;const f={label:capitalised(l),length:d,color:"#2563eb"},g={label:capitalised(u),length:m,color:"#f59e0b"},b=[f,g],x={longer:d,shorter:m},y=numberToWords(d),E=numberToWords(m);return buildExample({generatorId:n,question:t,title:`Which is ${i}: ${capitalised(s)} or ${capitalised(r)}?`,computed:x,steps:[{visual_data:{type:"measure_bars",bars:b,unit:"cm",show_values:!1},animation:"show_object",caption:"Two lengths on a centimetre ruler.",spoken:"Two lengths on a centimetre ruler. Read each one from zero to where it ends."},{visual_data:{type:"measure_bars",bars:b,unit:"cm",show_values:!0},animation:"count_cm",caption:`One is ${d} cm, the other ${m} cm.`,spoken:`One reaches ${y} centimetres. The other reaches ${E} centimetres.`},{visual_data:{type:"measure_bars",bars:b,unit:"cm",show_values:!0,compare:i},animation:"compare",caption:i==="longer"?`${d} cm > ${m} cm — the ${h} is longer.`:`${m} cm < ${d} cm — the ${h} is shorter.`,spoken:`Compare the two lengths. The ${h} is ${i}.`}]})}function orderShortestExample(n,t,e,s,o){const r=[e,s,o];if(r.some(h=>h<1||h>999))return null;const a=[...r].sort((h,f)=>h-f);if(new Set(a).size!==3)return null;const i=a.map(h=>`${h} cm`).join(", ");if(i!==t.correctAnswer.trim())return null;const l=["#2563eb","#f59e0b","#16a34a"],u=a.map((h,f)=>({label:`${h} cm`,length:h,color:l[f]})),d={shortest:a[0],middle:a[1],longest:a[2]},m=a.map(h=>numberToWords(h));return buildExample({generatorId:n,question:t,title:"Order from shortest.",computed:d,steps:[{visual_data:{type:"measure_bars",bars:u,unit:"cm",show_values:!1},animation:"show_object",caption:"Three lengths on a centimetre ruler.",spoken:"Three lengths on a centimetre ruler. Measure each one from zero to where it ends."},{visual_data:{type:"measure_bars",bars:u,unit:"cm",show_values:!0},animation:"count_cm",caption:`${a[0]} cm, ${a[1]} cm, ${a[2]} cm.`,spoken:`They measure ${m[0]}, ${m[1]}, and ${m[2]} centimetres.`},{visual_data:{type:"measure_bars",bars:u,unit:"cm",show_values:!0,verdict:`Shortest first: ${i}`},animation:"write_length",caption:`Shortest first: ${i}
71.`,spoken:`Line them up shortest first: ${m[0]}, then ${m[1]}, then ${m[2]} centimetres.`}]})}function ownerCompareExample(n,t,e){const{owner1:s,item:o,len1:r,owner2:a,len2:i,ask:l}=e;if(r<1||r>999||i<1||i>999||r===i)return null;const u=r>=i?s:a,d=r>=i?a:s,m=Math.max(r,i),h=Math.min(r,i),f=l==="longer"?u:d;if(f!==t.correctAnswer.trim())return null;const g={label:`${u}'s ${o}`,length:m,color:"#2563eb"},b={label:`${d}'s ${o}`,length:h,color:"#f59e0b"},x=[g,b],y={longer:m,shorter:h},E=numberToWords(m),k=numberToWords(h),T=`${f}'s`;return buildExample({generatorId:n,question:t,title:`Whose ${o} is ${l}?`,computed:y,steps:[{visual_data:{type:"measure_bars",bars:x,unit:"cm",show_values:!1},animation:"show_object",caption:`Two ${o}s on a centimetre ruler.`,spoken:`Two ${o}s on a centimetre ruler. Read each one from zero to where it ends.`},{visual_data:{type:"measure_bars",bars:x,unit:"cm",show_values:!0},animation:"count_cm",caption:`One is ${m} cm, the other ${h} cm.`,spoken:`One reaches ${E} centimetres. The other reaches ${k} centimetres.`},{visual_data:{type:"measure_bars",bars:x,unit:"cm",show_values:!0,compare:l},animation:"compare",caption:`${m} cm ${l==="longer"?">":"<"} ${h} cm — ${T} is ${l}.`,spoken:`Compare the two lengths. ${T} is ${l}.`}]})}function superlativeOfThreeExample(n,t,e,s,o,r){const a=[s,o,r];if(a.some(k=>k<1||k>999))return null;const i=[...a].sort((k,T)=>k-T);if(new Set(i).size!==3)return null;const l=e==="longest"?i[2]:i[0],u=`${l} cm`;if(u!==t.correctAnswer.trim())return null;const d=a.filter(k=>k!==l),m=["#2563eb","#f59e0b","#16a34a"],h=a.map((k,T)=>({label:`${k} cm`,length:k,color:m[T]})),f=k=>String(k).length,g=f(l),b={a:s,b:o,c:r,winner:l,winnerDigits:g},x=e==="longest"&&d.every(k=>f(k)<g),y=x?`${l} has ${g} digits — more than ${d[0]} or ${d[1]}, so it's the longest.`:`${u} is the ${e==="longest"?"biggest":"smallest"} number, so it's the ${e}.`,E=x?`${capitalised(numberToWords(l))} has an extra digit, so it is bigger than the other two, even though they look big too. That makes it the longest.`:`Compare the three numbers. The ${e==="longest"?"biggest":"smallest"} one is the ${e}.`;return buildExample({generatorId:n,question:t,title:`Which is the ${e}?`,computed:b,steps:[{visual_data:{type:"measure_bars",bars:h,unit:"cm",show_values:!1},animation:"show_object",caption:"Three lengths on a centimetre ruler.",spoken:"Three lengths on a centimetre ruler. Measure each one from zero to where it ends."},{visual_data:{type:"measure_bars",bars:h,unit:"cm",show_values:!0},animation:"count_cm",caption:`${s} cm, ${o} cm, ${r} cm.`,spoken:"Read each length in turn."},{visual_data:{type:"measure_bars",bars:h,unit:"cm",show_values:!0,compare:e==="longest"?"longer":"shorter"},animation:"compare",caption:y,spoken:E}]})}function barReadingExample(n,t,e){const s=measureBars(t);if(!s)return null;if(BAR_HOW_LONG.test(e)){if(s.length!==1)return null;const i=s[0],l=i.length;return l<1||l>99||l!==answerInt(t)?null:measureDemo$1(n,t,{title:"How long is the bar?",bar:readingBar(i.label,l,i.color,i.start),len:l,object:i.label})}const o=e.match(BAR_HOW_LONG_LONGEST);if(o){if(s.length<2)return null;const i=[...s].sort((m,h)=>h.length-m.length),l=i[0],u=l.length;if(u<=i[1].length||u<1||u>99||u!==answerInt(t))return null;const d=o[1];return longestBarDemo(n,t,{bars:s,top:l,word:d})}const r=e.match(BAR_DIFFERENCE);if(r){if(s.length!==2)return null;const[,i,l,u]=r,d=s.find(b=>new RegExp(`\\b${l}\\b`,"i").test(b.label)),m=s.find(b=>new RegExp(`\\b${u}\\b`,"i").test(b.label));if(!d||!m||d===m)return null;const h=d.length>=m.length?d:m,f=d.length>=m.length?m:d,g=h.length-f.length;return g<1||h.length>99||g!==answerInt(t)||i==="longer"!=(d===h)?null:measureDiffDemo(n,t,{longer:h,shorter:f,gap:g,word:i,subjColour:l})}const a=e.match(WHICH_IS_ASK);if(a){if(s.length!==2)return null;const[i,l]=s;if(i.length===l.length)return null;const u=a[1],d=i.length>l.length?i:l,m=i.length>l.length?l:i;return(u==="longer"?d:m).label!==t.correctAnswer.trim()?null:barLabelCompareExample(n,t,{longer:d,shorter:m,ask:u})}if(WHICH_TWO_EQUAL.test(e)){if(s.length<2)return null;for(let i=0;i<s.length;i++)for(let l=i+1;l<s.length;l++)if(s[i].length===s[l].length)return`${s[i].label} and ${s[l].label}`!==t.correctAnswer.trim()?null:whichTwoEqualExample(n,t,s,i,l);return null}return null}function barLabelCompareExample(n,t,e){const{longer:s,shorter:o,ask:r}
71=e,a=readingBar(s.label,s.length,s.color,s.start),i=readingBar(o.label,o.length,o.color,o.start),l=[a,i],u=r==="longer"?s:o,d={longer:s.length,shorter:o.length},m=numberToWords(s.length),h=numberToWords(o.length);return buildExample({generatorId:n,question:t,title:`Which is ${r}?`,computed:d,steps:[{visual_data:{type:"measure_bars",bars:l,unit:"cm",show_values:!1},animation:"show_object",caption:"Measure what each one covers.",spoken:"Two objects on a centimetre ruler. Measure what each one covers, start to end."},{visual_data:{type:"measure_bars",bars:l,unit:"cm",show_values:!0},animation:"count_cm",caption:`One covers ${s.length} cm, the other ${o.length} cm.`,spoken:`A length is how many centimetres something covers, so we count each one from end to end. One covers ${m} centimetres. The other covers ${h} centimetres.`},{visual_data:{type:"measure_bars",bars:l,unit:"cm",show_values:!0,compare:r},animation:"compare",caption:`${s.length} cm ${r==="longer"?">":"<"} ${o.length} cm — ${u.label} is ${r}.`,spoken:`Now compare the two lengths: ${m} centimetres is more than ${h} centimetres. So the answer is ${u.label}: it is ${r}.`}]})}function whichTwoEqualExample(n,t,e,s,o){const r=e.filter((d,m)=>m!==s&&m!==o),a=[e[s],e[o],...r].map(d=>readingBar(d.label,d.length,d.color,d.start)),i=e[s].length,l={equalLength:i},u=numberToWords(i);return buildExample({generatorId:n,question:t,title:"Which two bars are equal in length?",computed:l,steps:[{visual_data:{type:"measure_bars",bars:a,unit:"cm",show_values:!1},animation:"show_object",caption:"Measure what each bar covers — not just where it ends.",spoken:"Several bars on a centimetre ruler. Some start further along, so measure what each one covers."},{visual_data:{type:"measure_bars",bars:a,unit:"cm",show_values:!0},animation:"count_cm",caption:"Read every bar's length.",spoken:"Measure each bar's cover in turn."},{visual_data:{type:"measure_bars",bars:a,unit:"cm",show_values:!0,compare:"same"},animation:"compare",caption:`${e[s].label} and ${e[o].label} both cover ${i} cm — equal.`,spoken:`${e[s].label} and ${e[o].label} both cover ${u} centimetres. They are equal.`}]})}function longestBarDemo(n,t,e){const{top:s,word:o}=e,r=e.bars.map(f=>readingBar(f.label,f.length,f.color,f.start)),a=e.bars.filter(f=>f!==s),i=e.bars.some(f=>(f.start??0)>0),l={answer:s.length};e.bars.forEach((f,g)=>{l[`bar${g}`]=f.length,(f.start??0)>0&&(l[`start${g}`]=f.start??0,l[`end${g}`]=(f.start??0)+f.length)});const u=e.bars.map(f=>`${f.label} ${f.length} cm`).join(", "),d=e.bars.map(f=>`${f.label} covers ${numberToWords(f.length)} centimetres`).join(". "),m=a.map(f=>f.length).join(" and "),h=a.map(f=>numberToWords(f.length)).join(" and ");return buildExample({generatorId:n,question:t,title:`How long is the ${o} bar?`,computed:l,steps:[{visual_data:{type:"measure_bars",bars:r,unit:"cm",show_values:!1},animation:"show_object",caption:i?"Measure what each bar covers, not where it ends.":"Measure each bar from zero.",spoken:i?"Every bar on one centimetre ruler. Not every bar starts at zero, so measure what each one covers, from its start to its end.":"Every bar on one centimetre ruler, all starting at zero. Read each one."},{visual_data:{type:"measure_bars",bars:r,unit:"cm",show_values:!0},animation:"count_cm",caption:`${u}.`,spoken:`${d}.`},{visual_data:{type:"measure_bars",bars:r,unit:"cm",show_values:!0,conclude_label:`${s.label}: ${s.length} cm`},animation:"compare",caption:`${s.length} cm is more than ${m} cm, so the ${o} bar is ${s.length} cm.`,spoken:`${capitalised(numberToWords(s.length))} is more than ${h}, so the ${o} bar is the ${s.label}, and it is ${numberToWords(s.length)} centimetres long.`}]})}function measureDemo$1(n,t,e){const{bar:s,len:o,object:r}=e,a={length:o,answer:o},i=numberToWords(o),l=s.start??0;return buildExample({generatorId:n,question:t,title:e.title,computed:a,steps:[{visual_data:{type:"measure_bars",bars:[s],unit:"cm",show_values:!1},animation:"show_object",caption:l>0?"This bar does not start at zero.":"Measure from the start of the ruler.",spoken:l>0?`The ${r} on a centimetre ruler. It does not start at zero, so count what it covers.`:`The ${r} on a centimetre ruler. We measure it from zero.`},{visual_data:{type:"measure_bars",bars:[s],unit:"cm",show_values:!1},animation:"count_cm",caption:`Count what it covers: ${o}.`,spoken:`The length is how many centimetres it covers, from one end to the other. Count them: that makes ${i}.`},{visual_data:{type:"measure_bars",bars:[s],unit:"cm",show_values:!0},animation:"write_length",caption:`The bar is ${o}
71 cm.`,spoken:`So the ${r} is ${i} centimetres long.`}]})}function columnsNeedExchange(n,t){for(let e=n,s=t;e>0||s>0;e=Math.floor(e/10),s=Math.floor(s/10))if(e%10<s%10)return!0;return!1}function measureDiffDemo(n,t,e){const{longer:s,shorter:o,gap:r,word:a,subjColour:i}=e,l=readingBar(s.label,s.length,s.color,s.start),u=readingBar(o.label,o.length,o.color,o.start),d=[l,u],m={longer:s.length,shorter:o.length,gap:r,answer:r},h=(s.start??0)>0||(o.start??0)>0,f=(k,T)=>{const A=k.start??0;return m[`${T}_start`]=A,m[`${T}_end`]=A+k.length,`${k.label}: ${A} → ${A+k.length} = ${k.length} cm`},g=h?`${f(s,"long")}. ${f(o,"short")}.`:null,b=(k,T)=>/\d/.test(k)?T:`The ${k.toLowerCase()}`,x=numberToWords(s.length),y=numberToWords(o.length),E=numberToWords(r);return buildExample({generatorId:n,question:t,title:`How much ${a} is one bar than the other?`,computed:m,steps:[{visual_data:{type:"measure_bars",bars:d,unit:"cm",show_values:!1},animation:"show_object",caption:"Measure what each bar covers, not just where it ends.",spoken:"Two bars on a centimetre ruler. Some may not start at zero, so measure what each one covers."},{visual_data:{type:"measure_bars",bars:d,unit:"cm",show_values:h},animation:"count_cm",caption:g??`One covers ${s.length}, the other ${o.length}.`,spoken:h?`Count each bar from its own start to its own end. ${b(s.label,"One bar")} goes from ${numberToWords(s.start??0)} to ${numberToWords((s.start??0)+s.length)}: that covers ${x} centimetres. ${b(o.label,"The other bar")} goes from ${numberToWords(o.start??0)} to ${numberToWords((o.start??0)+o.length)}: that covers ${y} centimetres.`:`One bar covers ${x} centimetres. The other covers ${y} centimetres.`},{visual_data:{type:"measure_bars",bars:d,unit:"cm",show_values:!0,diff:r},animation:"write_length",caption:`${s.length} − ${o.length} = ${r}, so ${i} is ${r} cm ${a}.`,spoken:`Take the shorter from the longer. ${capitalised(x)} take away ${y} is ${E}. So the ${i} bar is ${E} centimetres ${a}.`}]})}function clockFromVisual$2(n){const t=n.visualData;if(!t||t.type!=="clock")return null;const{hour:e,minute:s}=t;return!Number.isInteger(e)||e<1||e>12||!Number.isInteger(s)||s<0||s>59?null:{hour:e,minute:s}}const CLAIM_OCLOCK=/^\w+ says it's (\d+) o'clock\. Right\?$/i;function buildHandIdDemo(n,t,e){const s={type:"clock",hour:t,minute:0},o=numberToWords(t),r={hour:t,minute:0,answer:t},a={...s,conclude_label:`${t}`};return buildExample({generatorId:e,question:n,title:"The short hand points to which number?",computed:r,steps:[{visual_data:s,animation:"highlight_minute_hand",caption:"Long hand straight up — no minutes past.",spoken:"The long hand is the minute hand. It points straight up to the twelve, so this clock shows an exact hour."},{visual_data:s,animation:"highlight_hour_hand",caption:"Follow the short hand outward.",spoken:"Now we look at the short hand, because it is the hour hand. Follow it out to the number it points at."},{visual_data:a,animation:"write_time",caption:`The short hand points to ${t}.`,spoken:`The short hand points to the ${o}, so the answer is ${o}.`}]})}function buildVerifyOclockDemo(n,t,e,s,o){const r={type:"clock",hour:t,minute:0},a=numberToWords(t),i=numberToWords(e),l={hour:t,minute:0,claimedHour:e,answer:t},u=s==="Yes"?`It's ${t} o'clock — so ${s}.`:`It's ${t} o'clock, not ${e} — so ${s}.`,d=s==="Yes"?`The long hand is at twelve and the short hand is at ${a}, so it is ${a} o'clock. That matches what they said, so yes.`:`The long hand is at twelve and the short hand is at ${a}, so it is ${a} o'clock, not ${i} o'clock. So no.`,m={...r,conclude_label:`${t} o'clock — ${s}`};return buildExample({generatorId:o,question:n,title:`Is it ${e} o'clock?`,computed:l,steps:[{visual_data:r,animation:"highlight_minute_hand",caption:"Check the clock yourself first.",spoken:`Someone says it's ${i} o'clock. Let's read the real clock first. The long hand points straight up — no minutes have passed.`},{visual_data:r,animation:"highlight_hour_hand",caption:`The short hand points to ${t}.`,spoken:`Now the short hand. It is the hour hand, so the number it points to tells us the hour. It points to the ${a}.`},{visual_data:m,animation:"write_time",caption:u,spoken:d}]})}const meaP6M01Generator={id:"MEA.P6.M01.tell-oclock.v1",microSkillId:"MEA.P6.M01",forms:["time_reading"],generate(n){const t=clockFromVisual$2(n);if(!t)return null;const{hour:e,minute:s}=t;if(s!==0)return null;const o=n.correctAnswer.trim();if(/^\d+$/.test(o))return Number(o)!==e?null:buildHandIdDemo(n,e,this.id);if(o==="Yes"||o==="No"){const u=matchInts(n.stem,CLAIM_OCLOCK);if(!u)return null;const[d]=u;if(d<1||d>12)return null;const m=d===e?"Yes":"No";return m!==o?null:buildVerifyOclockDemo(n,e,d,m,this.id)}if(`${e} o'clock`!==o)return null;const a={type:"clock",hour:e,minute:0},i=numberToWords(e),l={hour:e,minute:0,answer:e};return buildExample({generatorId:this.id,question:n,title:"What time does the clock show?",computed:l,steps:[{visual_data:a,animation:"highlight_minute_hand",caption:"Long hand straight up — no minutes past.",spoken:"The long hand is the minute hand. It points straight up to the twelve, so no minutes have gone past yet — that is what o'clock means."},{visual_data:a,animation:"highlight_hour_hand",caption:`The short hand points to ${e}.`,spoken:`Now the short hand. It is the hour hand, so the number it points to tells us the hour. It points to the ${i}.`},{visual_data:a,animation:"write_time",caption:`It's ${e} o'clock.`,spoken:`The long hand is at twelve and the short hand is at ${i}, so the time is ${i} o'clock.`}]})}};function clockFromVisual$1(n){const t=n.visualData;if(!t||t.type!=="clock")return null;const{hour:e,minute:s}=t;return!Number.isInteger(e)||e<1||e>12||!Number.isInteger(s)||s<0||s>59?null:{hour:e,minute:s}}const CLAIM_HALFPAST=/^\w+ says it's half-past (\d+)\. Right\?$/i;function nextHourOf(n){return n===12?1:n+1}function buildHandPositionDemo(n,t,e){const s=nextHourOf(t),o={type:"clock",hour:t,minute:30},r=numberToWords(t),a=numberToWords(s),i={hour:t,minute:30,next:s,answer:t},l={...o,conclude_label:`${t} and ${s}`};return buildExample({generatorId:e,question:n,title:"The short hand is between which two numbers?",computed:i,steps:[{visual_data:o,animation:"highlight_minute_hand",caption:"Long hand straight down — half way round.",spoken:"The long hand is the minute hand. It has gone half way round the clock, down to the six. Half way round means half an hour has gone by — that is why we call it half past."},{visual_data:o,animation:"highlight_hour_hand",caption:`The short hand sits between ${t} and ${s}.`,spoken:`Now the short hand, the hour hand. At half past, it has moved halfway from the ${r} towards the ${a}.`},{visual_data:l,animation:"write_time",caption:`Between ${t} and ${s}
71.`,spoken:`So the short hand is between the ${r} and the ${a}. It has gone past the ${r}, but it hasn't reached the ${a} yet.`}]})}function buildVerifyHalfPastDemo(n,t,e,s,o){const r={type:"clock",hour:t,minute:30},a=numberToWords(t),i=numberToWords(e),l=nextHourOf(t),u=numberToWords(l),d={hour:t,minute:30,claimedHour:e,next:l,answer:t},m=s==="Yes"?`It's half past ${t} — so ${s}.`:`It's half past ${t}, not ${e} — so ${s}.`,h=s==="Yes"?`The long hand is at six, so it is half past. The short hand has gone past the ${a}, so it is half past ${a}. That matches what they said, so yes.`:`The long hand is at six, so it is half past. The short hand has gone past the ${a}, so it is half past ${a}, not half past ${i}. So no.`,f={...r,conclude_label:`half past ${t} — ${s}`};return buildExample({generatorId:o,question:n,title:`Is it half past ${e}?`,computed:d,steps:[{visual_data:r,animation:"highlight_minute_hand",caption:"Check the clock yourself first.",spoken:`Someone says it's half past ${i}. Let's read the real clock first. The long hand has gone half way round, to the six.`},{visual_data:r,animation:"highlight_hour_hand",caption:`The short hand is between ${t} and ${l}.`,spoken:`Now the short hand, because it tells us the hour. It has gone past the ${a}, but it hasn't reached the ${u} yet, so the hour is still ${a}.`},{visual_data:f,animation:"write_time",caption:m,spoken:h}]})}const meaP6M02Generator={id:"MEA.P6.M02.half-past.v1",microSkillId:"MEA.P6.M02",forms:["time_reading"],generate(n){const t=clockFromVisual$1(n);if(!t)return null;const{hour:e,minute:s}=t;if(s!==30)return null;const o=n.correctAnswer.trim(),r=o.match(/^(\d+) and (\d+)$/);if(r){const h=Number(r[1]),f=Number(r[2]);return h!==e||f!==nextHourOf(e)?null:buildHandPositionDemo(n,e,this.id)}if(o==="Yes"||o==="No"){const h=matchInts(n.stem,CLAIM_HALFPAST);if(!h)return null;const[f]=h;if(f<1||f>12)return null;const g=f===e?"Yes":"No";return g!==o?null:buildVerifyHalfPastDemo(n,e,f,g,this.id)}if(`half-past ${e}`!==o)return null;const i={type:"clock",hour:e,minute:30},l=numberToWords(e),u=nextHourOf(e),d=numberToWords(u),m={hour:e,minute:30,next:u,answer:e};return buildExample({generatorId:this.id,question:n,title:"What time does the clock show?",computed:m,steps:[{visual_data:i,animation:"highlight_minute_hand",caption:"Long hand straight down — half way round.",spoken:"The long hand is the minute hand. It has gone half way round the clock, down to the six. Half way round means half an hour has gone by — that is why we call it half past."},{visual_data:i,animation:"highlight_hour_hand",caption:`Short hand between ${e} and ${u} — still ${e}.`,spoken:`Now the short hand, because it tells us the hour. It sits between the ${l} and the ${d}. It hasn't reached the ${d} yet, so the hour is still ${l}.`},{visual_data:i,animation:"write_time",caption:`It's half past ${e}.`,spoken:`The long hand is at six, so it is half past. The short hand has gone past the ${l}, so the time is half past ${l}.`}]})}};function clockFromVisual(n){const t=n.visualData;if(!t||t.type!=="clock")return null;const{hour:e,minute:s}=t;return!Number.isInteger(e)||e<1||e>12||!Number.isInteger(s)||s<0||s>59?null:{hour:e,minute:s}}function nextHour(n){return n%12+1}const LONG_HAND_VOCAB=/^The long hand points to (3|6|9|12)\. What time is it\?$/,NUMERAL_TO_MINUTE={3:15,6:30,9:45,12:0},NUMERAL_TO_LABEL={3:"Quarter past",6:"Half past",9:"Quarter to",12:"O'clock"},SWEEP={3:{shade:{shade_from_minute:0,shade_to_minute:15},caption:"A quarter of the way round from 12.",spoken:"From the twelve round to the three is a quarter of the way round the clock. A quarter of the hour has gone past."},6:{shade:{shade_from_minute:0,shade_to_minute:30},caption:"Half of the way round from 12.",spoken:"From the twelve round to the six is half of the way round the clock. Half of the hour has gone past."},9:{shade:{shade_from_minute:45,shade_to_minute:60},caption:"A quarter of the way still to go to 12.",spoken:"From the nine back up to the twelve is a quarter of the way round. There is a quarter of the hour still to go."},12:{shade:{}
71,caption:"Back at 12, the start of an hour.",spoken:"At the twelve, the long hand is right at the start of the hour."}};function buildLongHandVocabDemo(n,t){const e=NUMERAL_TO_LABEL[t];if(e!==n.correctAnswer.trim())return null;const s=NUMERAL_TO_MINUTE[t],r={type:"clock",hour:1,minute:s},a=Number(t),i={numeral:a,minute:s,clock_top:12};return buildExample({generatorId:meaP6M03Generator.id,question:n,title:`The long hand points to ${t}. What time is it?`,computed:i,steps:[{visual_data:r,animation:"highlight_minute_hand",caption:`Long hand at ${t}.`,spoken:`The long hand points to the ${numberToWords(a)}.`},{visual_data:{...r,...SWEEP[t].shade},animation:"highlight_minute_hand",caption:SWEEP[t].caption,spoken:SWEEP[t].spoken},{visual_data:{...r,...SWEEP[t].shade,conclude_label:e},animation:"write_time",caption:`${e}.`,spoken:`That is always ${e.toLowerCase()}, whatever the hour.`}]})}const meaP6M03Generator={id:"MEA.P6.M03.tell-quarter.v1",microSkillId:"MEA.P6.M03",forms:["time_reading","context_reasoning","misconception_contrast_probe","time_vocabulary"],generate(n){const t=n.stem.trim().match(LONG_HAND_VOCAB);if(t)return buildLongHandVocabDemo(n,t[1]);const e=clockFromVisual(n);if(!e)return null;const{hour:s,minute:o}=e;if(![0,15,30,45].includes(o))return null;const r=o===45?nextHour(s):s,a=o===15?3:o===45?9:o===30?6:12,i=o===0?`${r} o'clock`:o===15?`Quarter past ${r}`:o===30?`half-past ${r}`:`Quarter to ${r}`,l=n.stem.trim();let u;if(l==="The long hand points to which number?"){if(a!==answerInt(n))return null;u="long_hand"}else if(l==="How many minutes past the hour?"||l==="How many minutes past the hour is it?"){if(o!==answerInt(n))return null;u="minutes_past"}else{if(i!==n.correctAnswer.trim())return null;u="label"}const d={type:"clock",hour:s,minute:o},m=numberToWords(s),h=numberToWords(r),f={hour:s,minute:o,namedHour:r,quarterMinutes:15,fiveMinutes:5,longHandNumeral:a,answer:u==="long_hand"?a:u==="minutes_past"?o:r};let g,b,x;const y=u==="long_hand";o===15?(g={animation:"highlight_minute_hand",caption:y?"Long hand a quarter way round the clock.":"Long hand at 3 — a quarter way round.",spoken:y?"The long hand has gone a quarter of the way round the clock. A quarter of the way round is fifteen minutes past.":"The long hand has gone a quarter of the way round the clock, to the three. A quarter of the way round is fifteen minutes past."},b={animation:"highlight_hour_hand",caption:`The short hand is just past ${s}.`,spoken:`Now the short hand, because it tells us the hour. It has only just gone past the ${m}, so the hour is still ${m}.`},x={animation:"write_time",caption:`Quarter past ${s} — 15 minutes past ${s}.`,spoken:`A quarter of the way round means quarter past, and the hour is ${m}. So the time is a quarter past ${m} — fifteen minutes past ${m}.`}):o===45?(g={animation:"highlight_minute_hand",caption:y?"Long hand three quarters round the clock.":"Long hand at 9 — three quarters round.",spoken:y?"The long hand has gone three quarters of the way round. That leaves fifteen minutes until the next hour.":"The long hand has gone three quarters of the way round, to the nine. That leaves fifteen minutes until the next hour."},b={animation:"highlight_hour_hand",caption:`The short hand is almost at ${r}.`,spoken:`Now the short hand. It is almost at the ${h}, but not quite there. At quarter to, we name the hour it is heading towards, so the hour is ${h}.`},x={animation:"write_time",caption:`Quarter to ${r} — 15 minutes to ${r}.`,spoken:`Three quarters of the way round means quarter to, and the short hand is heading to the ${h}. So the time is a quarter to ${h} — fifteen minutes to ${h}.`}):o===0?(g={animation:"highlight_minute_hand",caption:"Long hand straight up — no minutes past.",spoken:y?"The long hand points straight up, so no minutes have gone past — that is what o'clock means.":"The long hand points straight up to the twelve, so no minutes have gone past — that is what o'clock means."},b={animation:"highlight_hour_hand",caption:`The short hand points to ${s}.`,spoken:`Now the short hand, because it tells us the hour. It points at the ${m}.`},x={animation:"write_time",caption:`It's ${s} o'clock.`,spoken:`The long hand is at twelve and the short hand is at ${m}, so the time is ${m} o'clock.`}):(g={animation:"highlight_minute_hand",caption:"The long hand points straight down.",spoken:y?"The long hand points straight down. When it does, it's half past something.":"The long hand points straight down to six. When it does, it's half past something."},b={animation:"highlight_hour_hand",caption:`The short hand has gone past ${s}.`,spoken:`Now the short hand, because it tells us the hour. It has gone past the ${m}, but it hasn't reached the next number yet, so the hour is still ${m}.`},x={animation:"write_time",caption:`It's half past ${s}.`,spoken:`The long hand pointing straight down means half past, and the hour is ${m}. So the time is half past ${m}.`}),y&&new RegExp(`\\b${a}\\b`).test(b.caption)&&(b={animation:"highlight_hour_hand",caption:"The short hand shows the hour.",spoken:"The short hand is the hour hand. This question is about the long hand, so we look back at that one."}),u==="minutes_past"&&(g={animation:"highlight_minute_hand",caption:o===0?"The long hand points straight up.":`The long hand points to ${a}.`,spoken:o===0?"The long hand shows the minutes. It points straight up — it has not passed any numbers yet.":`The long hand shows the minutes. It points to the ${numberToWords(a)}.`},b={animation:"highlight_hour_hand",caption:"Each number it passes counts 5 minutes.",spoken:"Every number the long hand passes counts five minutes. Count in fives round the clock to where it points."});const E=u==="long_hand"?"Which number does the long hand point to?":u==="minutes_past"?"How many minutes past the hour is 
71it?":"What time does the clock show?",k=u==="long_hand"?{animation:"write_time",caption:`The long hand points to ${a}.`,spoken:`So the long hand points to the ${numberToWords(a)}. The answer is ${numberToWords(a)}.`}:u==="minutes_past"?{animation:"write_time",caption:`That is ${o} minutes past the hour.`,spoken:o===0?"It has passed no numbers, so no minutes have gone past. Zero minutes past the hour.":`Counting in fives up to the long hand: ${Array.from({length:o/5},(T,A)=>numberToWords((A+1)*5)).join(", ")}. That is ${numberToWords(o)} minutes past the hour.`}:x;return buildExample({generatorId:this.id,question:n,title:E,computed:f,steps:[{visual_data:d,animation:g.animation,caption:g.caption,spoken:g.spoken},{visual_data:d,animation:b.animation,caption:b.caption,spoken:b.spoken},{visual_data:u==="minutes_past"?{...d,conclude_label:`${o} minutes past`}:u==="long_hand"?{...d,conclude_label:`${a}`}:d,animation:k.animation,caption:k.caption,spoken:k.spoken}]})}},READ_STEMS=new Set(["which number does this tally show?","read the tally. which number is it?","pick the number that matches the tally.","pick the number that matches the tally","maya's bird tally: how many did she see?"]),GROUPS_STEM="how many groups of 5 in this tally?";function tallyCountFromVisual(n){const t=n.visualData;if(!t||t.type!=="tally")return null;const{count:e}=t;return!Number.isInteger(e)||e<1||e>20?null:e}function buildTallyDemo(n,t){if(t!==answerInt(n))return null;const e=Math.floor(t/5),s=t%5,o=e*5,r=Array.from({length:e},(y,E)=>(E+1)*5),a=Array.from({length:s},(y,E)=>o+E+1),i={answer:t,count:t,bundle_size:5};r.forEach((y,E)=>{i[`five_${E+1}`]=y}),a.forEach((y,E)=>{i[`extra_${E+1}`]=y});const l=r.join(", "),u=r.map(y=>numberToWords(y)).join(", "),d=a.join(", "),m=a.map(y=>numberToWords(y)).join(", "),h=`Count the groups of five: ${l}.`,f=s>0?`Then the marks left over: ${d}. The tally shows ${t}.`:`Every mark is in a group. The tally shows ${t}.`,g=e===1?`We count the group of five first, because it's quicker than counting every mark: ${u}.`:`We count the groups first, because counting in fives is quicker than counting every mark: ${u}.`,b=s>0?`Now we count on the single marks left over, one at a time: ${m}. So the tally shows ${numberToWords(t)}.`:`Every mark is in a group of five, with none left over. So the tally shows ${numberToWords(t)}.`,x={type:"tally",count:t};return buildExample({generatorId:dsP1M02Generator.id,question:n,title:capitalised("which number does this tally show?"),computed:i,steps:[{visual_data:x,animation:"show_tally",caption:"Four marks and one across make a group of 5.",spoken:"In a tally, we draw four marks, then one line across them. That makes a group of five, so we can count the tally in fives."},{visual_data:x,animation:"count_groups",caption:h,spoken:g},{visual_data:x,animation:"count_extras",caption:f,spoken:b}]})}function buildGroupsDemo(n,t){const e=Math.floor(t/5);if(e!==answerInt(n))return null;const s=t%5,o=Array.from({length:e},(f,g)=>g+1),r={answer:e,bundles:e,count:t,bundle_size:5,singles:s};o.forEach((f,g)=>{r[`group_${g+1}`]=f});const a=o.join(", "),i=o.map(f=>numberToWords(f)).join(", "),l=e===1?"group":"groups",u=s===1?"mark":"marks",d={type:"tally",count:t,mode:"groups"},m=s>0?`${s} single ${u} — not enough for a full group. That's ${e} ${l}.`:`Every mark is in a group. That's ${e} ${l}.`,h=s>0?`The single marks left over are not a full group. That makes ${numberToWords(e)} ${l}.`:`Every mark sits in a full group. That makes ${numberToWords(e)} ${l}.`;return buildExample({generatorId:dsP1M02Generator.id,question:n,title:"How many groups of 5 in this tally?",computed:r,steps:[{visual_data:d,animation:"show_tally",caption:"Four marks and one across make a group of 5.",spoken:"In a tally, four marks with one line across them make a group of five. We need to find how many full groups there are."},{visual_data:d,animation:"count_groups",caption:`Count the full groups: ${a}.`,spoken:`We count only the finished groups, the ones with a line across: ${i}.`},{visual_data:d,animation:"count_extras",caption:m,spoken:h}]})}const dsP1M02Generator={id:"DS.P1.M02.read-tally.v1",microSkillId:"DS.P1.M02",forms:["tally_read"],generate(n){const t=normaliseStem(n.stem).toLowerCase(),e=tallyCountFromVisual(n);return e===null?null:t===GROUPS_STEM?buildGroupsDemo(n,e):READ_STEMS.has(t)?buildTallyDemo(n,e):null}};function skipCountWords(n,t){return Array.from({length:n},(e,s)=>numberToWords((s+1)*t)).join(", ")}function escapeRe$1(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const DAY_NAMES={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"};function sayLabel(n){const t=DAY_NAMES[n.toLowerCase()];return t?t.charAt(0).toUpperCase()+t.slice(1):n}function labelInStem$2(n,t){const e=n.toLowerCase();if(new RegExp(`\\b${escapeRe$1(e)}\\b`).test(t))return!0;const s=DAY_NAMES[e];return s!==void 0&&new RegExp(`\\b${s}\\b`).test(t)}function pictogramFromVisual(n){const t=n.visualData;if(!t||t.type!=="pictogram")return null;const e=t;if(e.per_symbol!==1&&e.per_symbol!==2||!Array.isArray(e.rows)||e.rows.length===0)return null;for(const s of e.rows)if(typeof s.label!="string"||!Number.isInteger(s.symbols)||s.symbols<1)return null;return e}function buildSingleDemo$1(n,t,e){const s=t.per_symbol,o=e.symbols*s;if(o!==answerInt(n))return null;const r=Array.from({length:e.symbols},(h,f)=>(f+1)*s),a={per_symbol:s,symbols:e.symbols,answer:o};r.forEach((h,f)=>{a[`step_${f+1}`]=h});const i=r.join(", "),l=r.map(h=>numberToWords(h)).join(", "),u=s===2?"twos":"ones",d=[e,...t.rows.filter(h=>h!==e)],m={...t,rows:d};return buildExample({generatorId:dsP2M01Generator.id,question:n,title:`How many did ${e.label} get?`,computed:a,steps:[{visual_data:m,animation:"pic_key",caption:`Each ${t.emoji} stands for ${s}.`,spoken:`Start with the key. Each picture stands for ${numberToWords(s)}, not one.`},{visual_data:m,animation:"pic_find",caption:`The ${e.label} row has ${e.symbols} pictures.`,spoken:`Find the ${sayLabel(e.label)} row. There ${e.symbols===1?"is":"are"} ${numberToWords(e.symbols)} ${e.symbols===1?"picture":"pictures"}, each worth ${numberToWords(s)}.`},{visual_data:m,animation:"pic_count",caption:`Count in ${u}: ${i}. That makes ${o}
71.`,spoken:`Each picture is worth ${numberToWords(s)}, so we count in ${u}, once for each picture: ${l}. So ${sayLabel(e.label)} has ${numberToWords(o)}.`}]})}function buildDifferenceDemo$2(n,t,e,s,o="more"){const r=t.per_symbol,[a,i]=e.symbols>=s.symbols?[e,s]:[s,e],l=a.symbols*r,u=i.symbols*r,d=l-u;if(d!==answerInt(n))return null;const m=a.symbols-i.symbols,h={per_symbol:r,hi_symbols:a.symbols,lo_symbols:i.symbols,hi_value:l,lo_value:u,answer:d},f=[a,i,...t.rows.filter(x=>x!==a&&x!==i)],g={...t,rows:f},b={...t,rows:[{label:"Answer",symbols:m}]};return buildExample({generatorId:dsP2M01Generator.id,question:n,title:`How many ${o} did ${a.label} get than ${i.label}?`,computed:h,steps:[{visual_data:g,animation:"pic_key",caption:`Each ${t.emoji} stands for ${r}.`,spoken:`Read the key first. Each picture stands for ${numberToWords(r)}.`},{visual_data:{...g,count_indices:[0,1]},animation:"pic_find",caption:`${a.label} has ${l}, ${i.label} has ${u}.`,spoken:Number.isInteger(a.symbols)&&Number.isInteger(i.symbols)?`Count each row in ${r===2?"twos":r===1?"ones":numberToWords(r)+"s"}. ${sayLabel(a.label)}: ${skipCountWords(a.symbols,r)}. ${sayLabel(i.label)}: ${skipCountWords(i.symbols,r)}. So ${sayLabel(a.label)} has ${numberToWords(l)}, and ${sayLabel(i.label)} has ${numberToWords(u)}.`:`Count each row in ${r===2?"twos":"ones"}. ${sayLabel(a.label)} has ${numberToWords(l)}, ${sayLabel(i.label)} has ${numberToWords(u)}.`},{visual_data:b,animation:"pic_count",caption:`${l} − ${u} = ${d} ${o}.`,spoken:`To find the difference, we take the smaller number from the larger one: ${numberToWords(l)} take away ${numberToWords(u)} is ${numberToWords(d)}. So the answer is ${numberToWords(d)} ${o}.`}]})}function isSumStem(n){return/\b(altogether|together|in total|in all)\b/.test(n)||/\ball (two|three|four|five)\b/.test(n)}function buildSumDemo$2(n,t,e){const s=t.per_symbol,o=t.rows.filter(h=>labelInStem$2(h.label,e)),r=o.length>=2?o:t.rows;if(r.length>3)return null;const a=r.map(h=>h.symbols*s),i=a.reduce((h,f)=>h+f,0);if(i!==answerInt(n))return null;if(r.length===1)return buildSingleDemo$1(n,t,r[0]);const l={per_symbol:s,answer:i};a.forEach((h,f)=>{l[`val_${f+1}`]=h});const u=t.rows.filter(h=>!r.includes(h)),d={...t,rows:[...r,...u]},m=a.map(h=>numberToWords(h));return buildExample({generatorId:dsP2M01Generator.id,question:n,title:"How many altogether?",computed:l,steps:[{visual_data:d,animation:"pic_key",caption:`Each ${t.emoji} stands for ${s}.`,spoken:`Read the key first. Each picture stands for ${numberToWords(s)}.`},{visual_data:{...d,count_indices:r.map((h,f)=>f)},animation:"pic_find",caption:`Read the rows: ${a.join(", ")}.`,spoken:`Now we need to know how many each row shows, so we count the pictures one row at a time${s===2?", in twos":""}: ${m.join(", ")}.`},{visual_data:{...d,count_indices:r.map((h,f)=>f),total_label:`${a.join(" + ")} = ${i}`},animation:"pic_count",caption:`${a.join(" + ")} = ${i} altogether.`,spoken:r.length===t.rows.length?`To find how many altogether, we add every row together. ${capitalised(m.join(" plus "))} makes ${numberToWords(i)}.`:`To find how many altogether, we add only the rows the question asks about: ${r.map(h=>sayLabel(h.label)).join(" and ")}. ${capitalised(m.join(" plus "))} makes ${numberToWords(i)}.`}]})}function buildSuperlativeDemo$1(n,t,e){if(t.rows.length>3)return null;const s=/\b(most|greatest|more|biggest|highest|largest)\b/.test(e),o=/\b(fewest|least|smallest|lowest|fewer)\b/.test(e);if(s===o)return null;const r=t.rows.map(y=>y.symbols),a=s?Math.max(...r):Math.min(...r),i=t.rows.filter(y=>y.symbols===a);if(i.length!==1)return null;const l=i[0];if(/\d/.test(l.label)||l.label.trim()!==n.correctAnswer.trim())return null;const u=t.per_symbol,d=l.symbols*u,m=[l,...t.rows.filter(y=>y!==l)],h={...t,rows:m},f=m.map(y=>y.symbols*u),g={per_symbol:u,winner_value:d};f.forEach((y,E)=>{g[`row_${E+1}`]=y});const b=s?"most":"fewest",x=f.map(y=>numberToWords(y));return buildExample({generatorId:dsP2M01Generator.id,question:n,title:`Which one has the ${b}?`,computed:g,steps:[{visual_data:h,animation:"pic_key",caption:`Each ${t.emoji} stands for ${u}.`,spoken:`Read the key first. Each picture stands for ${numberToWords(u)}.`},{visual_data:{...h,count_indices:m.map((y,E)=>E)},animation:"pic_find",caption:`Count each row: ${f.join(", ")}.`,spoken:`To compare the rows, we need a number for each one. Count every row: ${x.join(", ")}.`},{visual_data:h,animation:"pic_count",caption:`${l.label} has the ${b}: ${d}.`,spoken:`Now compare those numbers. ${sayLabel(l.label)} has the ${b}, ${numberToWords(d)}, so the answer is ${sayLabel(l.label)}.`}]})}const BEE_PICTURE_COUNT=/^Each ([a-z]+) picture stands for (\d+) [a-z]+\. How many [a-z]+ pictures show (\d+) [a-z]+\?$/i;function tryBeePictureCount(n){const t=normaliseStem(n.stem).match(BEE_PICTURE_COUNT);if(!t)return null;const[,e,s,o]=t,r=Number(s),a=Number(o);if(r!==1&&r!==2||a%r!==0)return null;const i=a/r;if(i<1||i>10)return null;const l=n.correctAnswer.trim().match(/^(\d+)\s+[a-z]+\s+pictures$/i);if(!l||Number(l[1])!==i)return null;const u=/bee/i.test(e)?"🐝":"🔷",d=capitalised(e.endsWith("s")?e:`${e}s`),m={per_symbol:r,target:a,count:i},h=Array.from({length:i},(g,b)=>(b+1)*r);h.forEach((g,b)=>{m[`step_${b+1}`]=g});const f={type:"pictogram",emoji:u,per_symbol:r,key_label:`each ${u} = ${r}`,rows:[{label:d,symbols:i}]};return buildExample({generatorId:dsP2M01Generator.id,question:n,title:`How many pictures show ${a}?`,computed:m,steps:[{visual_data:f,animation:"pic_key",caption:`Each picture stands for ${r}.`,spoken:`Start with the key. Each picture stands for ${numberToWords(r)}, not one.`},{visual_data:f,animation:"pic_find",caption:`To reach ${a}, we need enough pictures.`,spoken:`Each picture adds ${numberToWords(r)}, so we keep adding pictures until the total reaches ${numberToWords(a)}.`},{visual_data:f,animation:"pic_count",caption:`${h.join(", ")} — that's ${i} pictures.`,spoken:`Count in ${r===2?"twos":"ones"} until we reach ${numberToWords(a)}: that takes ${numberToWords(i)} pictures.`}]})}function findSingleRow(n,t){const e=n.rows.filter(s=>labelInStem$2(s.label,t));return e.length===1?e[0]:null}function findDiffRows(n,t){const e=n.rows.filter(s=>labelInStem$2(s.label,t));return e.length===2?[e[0],e[1]]:null}const dsP2M01Generator={id:"DS.P2.M01.read-pictogram.v1",microSkillId:"DS.P2.M01",forms:["context_calculation","symbolic_numeric_entry","symbolic_choice","comparison_statement","misconception_contrast_probe"],generate(n){const t=tryBeePictureCount(n);if(t)return t;const e=pictogramFromVisual(n);if(!e)return null;const s=normaliseStem(n.stem).toLowerCase();if(answerInt(n)===null)return buildSuperlativeDemo$1(n,e,s);if(!/\bhow many\b/.test(s))return null;if(/\b(more|fewer)\b/.test(s)&&/\bthan\b/.test(s)){const a=findDiffRows(e,s);if(!a)return null;const i=/\bfewer\b/.test(s)?"fewer":"more";return buildDifferenceDemo$2(n,e,a[0],a[1],i)}if(isSumStem(s))return buildSumDemo$2(n,e,s);if(/\b(most|fewest|least|who)\b/.test(s))return null;const r=findSingleRow(e,s);return r?buildSingleDemo$1(n,e,r):null}};function escapeRe(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function labelInStem$1(n,t){return new RegExp(`\\b${escapeRe(n.toLowerCase())}\\b`).test(t)}function wordJoin(n){return n.length<=1?n[0]??"":`${n.slice(0,-1).join(", ")} and ${n[n.length-1]}`}function barChartFromVisual(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=t;if(!Array.isArray(e.bars)||e.bars.length===0)return null;for(const s of e.bars)if(typeof s.label!="string"||!Number.isInteger(s.value)||s.value<0)return null;return e}function buildSingleDemo(n,t,e){const s=e.value;if(s!==answerInt(n))return null;const o=t.bars.indexOf(e),r={value:s,answer:s};return buildExample({generatorId:dsP2M02Generator.id,question:n,title:`How many for ${e.label}?`,computed:r,steps:[{visual_data:t,animation:"grow",caption:"See how high each bar reaches.",spoken:"A bar chart. To read a bar, see how high it reaches up the side."},{visual_data:{...t,read_index:o},animation:"read",caption:`The ${e.label} bar reaches ${s}.`,spoken:`Find the ${e.label} bar. Trace the top across to the side: it reaches ${numberToWords(s)}.`},{visual_data:{...t,read_index:o},animation:"compare",caption:`So ${e.label} is ${s}
71.`,spoken:`So the answer is ${numberToWords(s)}.`}]})}function buildDifferenceDemo$1(n,t,e,s,o){const[r,a]=e.value>=s.value?[e,s]:[s,e],i=r.value-a.value;if(i!==answerInt(n))return null;const l=t.bars.indexOf(r),u=t.bars.indexOf(a),d={hi_value:r.value,lo_value:a.value,answer:i},m=o?`${capitalised(o.hiWord)}: ${r.label} reaches ${r.value}.`:`${r.label} reaches ${r.value}.`,h=o?`${capitalised(o.loWord)}: ${a.label} reaches ${a.value}. ${r.value} − ${a.value} = ${i}.`:`${a.label} reaches ${a.value}. ${r.value} − ${a.value} = ${i} more.`;return buildExample({generatorId:dsP2M02Generator.id,question:n,title:`How many more for ${r.label} than ${a.label}?`,computed:d,steps:[{visual_data:t,animation:"grow",caption:"See how high each bar reaches.",spoken:"A bar chart. To read a bar, see how high it reaches up the side."},{visual_data:{...t,read_index:l},animation:"read",caption:m,spoken:`${r.label} reaches up to ${numberToWords(r.value)}.`},{visual_data:{...t,read_index:u},animation:"compare",caption:h,spoken:`${a.label} reaches up to ${numberToWords(a.value)}. ${capitalised(numberToWords(r.value))} take away ${numberToWords(a.value)} is ${numberToWords(i)}. ${capitalised(numberToWords(i))} more for ${r.label}.`}]})}function buildSumDemo$1(n,t,e){const s=t.bars.filter(h=>labelInStem$1(h.label,e)),o=s.length>=2?s:s.length===0?t.bars:[];if(o.length<2||o.length>3)return null;const r=o.reduce((h,f)=>h+f.value,0);if(r!==answerInt(n))return null;const a=o.map(h=>t.bars.indexOf(h)),i={answer:r,total:r};o.forEach((h,f)=>{i[`val_${f+1}`]=h.value});const l=o.map(h=>`${h.label} ${h.value}`),u=o.map(h=>h.value).join(" + "),d=wordJoin(o.map(h=>numberToWords(h.value))),m=h=>l.slice(0,h).join(", ");return buildExample({generatorId:dsP2M02Generator.id,question:n,title:"How many altogether?",computed:i,steps:[{visual_data:{...t,read_index:a[0]},animation:"grow",caption:`${o[0].label} reaches ${o[0].value}.`,spoken:`To find the total, first read how high each bar reaches. ${o[0].label} reaches ${numberToWords(o[0].value)}.`},{visual_data:{...t,read_index:a[a.length-1]},animation:"read",caption:`${m(o.length)}.`,spoken:`Read each bar off the side. ${o.map(h=>`${h.label} reaches ${numberToWords(h.value)}`).join(". ")}.`},{visual_data:{...t,read_index:a[a.length-1]},animation:"add",caption:`${u} = ${r} altogether.`,spoken:`Now add them: ${d} make ${numberToWords(r)} altogether.`}]})}function buildSuperlativeDemo(n,t,e){if(/\bthan\b/.test(e))return null;const s=/\b(most|greatest|highest|largest|tallest)\b/.test(e),o=/\b(least|fewest|lowest|smallest|shortest)\b/.test(e);if(s===o)return null;const r=t.bars.map(g=>g.value),a=s?Math.max(...r):Math.min(...r);if(t.bars.filter(g=>g.value===a).length!==1)return null;const i=t.bars.findIndex(g=>g.value===a),l=t.bars[i];if(l.label.trim()!==n.correctAnswer.trim())return null;const u={...t,read_index:i},d={winner_value:l.value},m=s?"tallest":"shortest",h=s?"highest":"lowest",f=s?"most":"fewest";return buildExample({generatorId:dsP2M02Generator.id,question:n,title:"Which bar wins?",computed:d,steps:[{visual_data:t,animation:"grow",caption:"The taller the bar, the more chose it.",spoken:"The taller a bar is, the more children chose it."},{visual_data:u,animation:"read",caption:`${l.label} reaches ${l.value} — the ${m}.`,spoken:`${l.label} reaches the ${h}, up to ${numberToWords(l.value)}.`},{visual_data:u,animation:"compare",caption:`So ${l.label} had the ${f}.`,spoken:`No other bar is as ${s?"tall":"short"}. So the answer is ${l.label}: it had the ${f}, with ${numberToWords(l.value)}.`}]})}const BAR_CLAIM$1=/\b[A-Z][a-z]+ says more \w+ chose (\w+) than (\w+)\. Is (?:he|she) right\?$/;function buildClaimDemo$1(n,t,e,s){if((e.value>s.value?`Yes, ${e.label.toLowerCase()} has more votes`:e.value===s.value?"No, they have the same number of votes":`No, ${s.label.toLowerCase()} has more votes`)!==n.correctAnswer.trim())return null;const r=t.bars.indexOf(e),a=t.bars.indexOf(s),i={a_value:e.value,b_value:s.value};return buildExample({generatorId:dsP2M02Generator.id,question:n,title:`Did ${e.label} really beat ${s.label}?`,computed:i,steps:[{visual_data:t,animation:"grow",caption:`Someone says more chose ${e.label} than ${s.label}
71. Check it.`,spoken:`Someone says more children chose ${e.label} than ${s.label}. Read both bars off the side to check.`},{visual_data:{...t,read_index:r},animation:"read",caption:`${e.label} reaches ${e.value}.`,spoken:`${e.label} reaches up to ${numberToWords(e.value)}.`},{visual_data:{...t,read_index:a},animation:"compare",caption:e.value>s.value?`${s.label} reaches ${s.value} — lower. The claim is right.`:e.value===s.value?`${s.label} reaches ${s.value} too — the same. Not more.`:`${s.label} reaches ${s.value} — higher! The claim is wrong.`,spoken:e.value>s.value?`${s.label} actually reaches lower. The claim is right.`:e.value===s.value?"Both bars reach the same height, so neither has more. The claim is wrong.":`${s.label} actually reaches higher, not ${e.label}. The claim is wrong.`}]})}const EQUAL_BARS=/^Which [a-z]+ got an equal number\?$/i;function buildEqualPairDemo(n,t,e){if(!EQUAL_BARS.test(e))return null;const s=new Map;for(const h of t.bars){const f=s.get(h.value)??[];f.push(h),s.set(h.value,f)}const o=[...s.values()].filter(h=>h.length===2);if(o.length!==1)return null;const[r,a]=o[0],i=r.value;if(![`${r.label} and ${a.label.toLowerCase()}`,`${a.label} and ${r.label.toLowerCase()}`,`${r.label} and ${a.label}`,`${a.label} and ${r.label}`].includes(n.correctAnswer.trim()))return null;const u=t.bars.indexOf(r),d=t.bars.indexOf(a),m={value:i};return buildExample({generatorId:dsP2M02Generator.id,question:n,title:"Which two are equal?",computed:m,steps:[{visual_data:t,animation:"grow",caption:"See how high each bar reaches.",spoken:"A bar chart. To read a bar, see how high it reaches up the side."},{visual_data:{...t,read_index:u},animation:"read",caption:`${r.label} reaches ${i}.`,spoken:`${r.label} reaches up to ${numberToWords(i)}.`},{visual_data:{...t,read_index:d},animation:"compare",caption:`${a.label} also reaches ${i} — the same height. Equal!`,spoken:`${a.label} reaches the very same height, ${numberToWords(i)}. ${r.label} and ${a.label} are equal.`}]})}const BELOW_REFERENCE=/^Which [a-z]+ got fewer votes than ([a-z]+)\?$/i;function buildBelowReferenceDemo(n,t,e){const s=e.match(BELOW_REFERENCE);if(!s)return null;const o=s[1],r=t.bars.find(m=>m.label.toLowerCase()===o);if(!r)return null;const a=t.bars.filter(m=>m!==r&&m.value<r.value);if(a.length!==1)return null;const i=a[0];if(i.label.trim()!==n.correctAnswer.trim())return null;const l=t.bars.indexOf(r),u=t.bars.indexOf(i),d={ref_value:r.value,winner_value:i.value};return buildExample({generatorId:dsP2M02Generator.id,question:n,title:`Which is below ${r.label}?`,computed:d,steps:[{visual_data:t,animation:"grow",caption:`Dash across from the ${r.label} bar.`,spoken:`First find the ${r.label} bar and dash a line across from its top.`},{visual_data:{...t,read_index:l},animation:"read",caption:`${r.label} reaches ${r.value}.`,spoken:`${r.label} reaches up to ${numberToWords(r.value)}.`},{visual_data:{...t,read_index:u},animation:"compare",caption:`${i.label} reaches ${i.value} — below ${r.label}.`,spoken:`${i.label} reaches only ${numberToWords(i.value)}, below ${r.label}. ${i.label} got fewer.`}]})}function findSingleBar(n,t){const e=n.bars.filter(s=>labelInStem$1(s.label,t));return e.length===1?e[0]:null}function findDiffBars(n,t){const e=n.bars.filter(s=>labelInStem$1(s.label,t));return e.length===2?[e[0],e[1]]:null}const dsP2M02Generator={id:"DS.P2.M02.read-bar-chart.v1",microSkillId:"DS.P2.M02",forms:["symbolic_numeric_entry","symbolic_choice","context_calculation","comparison_statement","misconception_contrast_probe"],generate(n){const t=barChartFromVisual(n);if(!t)return null;const e=normaliseStem(n.stem),s=e.toLowerCase(),o=e.match(BAR_CLAIM$1);if(o){const l=t.bars.find(d=>d.label.toLowerCase()===o[1].toLowerCase()),u=t.bars.find(d=>d.label.toLowerCase()===o[2].toLowerCase());return l&&u?buildClaimDemo$1(n,t,l,u):null}if(EQUAL_BARS.test(s))return buildEqualPairDemo(n,t,s);if(BELOW_REFERENCE.test(s))return buildBelowReferenceDemo(n,t,s);if(answerInt(n)===null)return buildSuperlativeDemo(n,t,s);if(!/^how many\b/.test(s))return null;const r=/\b(more|fewer)\b/.test(s)&&/\bthan\b/.test(s);if(/\b(altogether|together|in total)\b/.test(s))return buildSumDemo$1(n,t,s);
71if(r){const l=/\b(most|greatest|highest|largest|tallest)\b/.test(s),u=/\b(least|fewest|lowest|smallest|shortest)\b/.test(s);if(l&&u){const m=t.bars.map(x=>x.value),h=Math.max(...m),f=Math.min(...m),g=t.bars.filter(x=>x.value===h),b=t.bars.filter(x=>x.value===f);return g.length!==1||b.length!==1?null:buildDifferenceDemo$1(n,t,g[0],b[0],{hiWord:"tallest",loWord:"shortest"})}if(l||u)return null;const d=findDiffBars(t,s);return d?buildDifferenceDemo$1(n,t,d[0],d[1]):null}if(/\b(most|fewest|least|which|who)\b/.test(s))return null;const i=findSingleBar(t,s);return i?buildSingleDemo(n,t,i):null}},GREEN$1="#43A047",SIDES$1={triangle:3,square:4,rectangle:4,pentagon:5,hexagon:6,circle:0},SHAPE_WORDS$3=Object.keys(SIDES$1);function asDrawableShape$3(n){if(!n)return null;const t=n.toLowerCase();return SHAPE_WORDS$3.includes(t)?t:null}function resolveShape$3(n){const t=n.visualData;if(t&&t.type==="shape_svg")return asDrawableShape$3(t.shape);const e=normaliseStem(n.stem).toLowerCase(),s=new Set;for(const o of SHAPE_WORDS$3)new RegExp(`\\b${o}s?\\b`).test(e)&&s.add(o);return s.size===1?[...s][0]:null}function bridgeSubject(n,t){const e=normaliseStem(n).match(/how many (?:straight )?sides does (.+?) have/i);if(!e)return null;const s=e[1].trim();if(/\d/.test(s)||/\b(this|that|the)\b/i.test(s))return null;const o=s.replace(/^(a|an|the)\s+/i,"").toLowerCase();return SHAPE_WORDS$3.includes(o)?null:s}function buildSidesDemo$1(n,t,e,s){const o={type:"shape_svg",shape:t,color:GREEN$1},r=e===1?"side":"sides",a={sides:e,answer:e},i=s?`${capitalised(s)} is a ${t}. Sides are the straight edges.`:`A ${t}. Sides are the straight edges.`,l=s?`${capitalised(s)} is a ${t}. The sides are the straight edges around the outside.`:`Here's a ${t}. The sides are the straight edges around the outside.`;return buildExample({generatorId:geoP3M01Generator.id,question:n,title:"How do we count sides?",computed:a,steps:[{visual_data:o,animation:"show_shape",caption:i,spoken:l},{visual_data:o,animation:"trace_sides",caption:`Count each side: ${countingRun(1,e)}.`,spoken:`Count each side as we go around: ${countingRun(1,e)}.`},{visual_data:o,animation:"write_total_sides",caption:`A ${t} has ${e} ${r}.`,spoken:`${capitalised(numberWord(e))} ${r}. Every ${t} has ${numberWord(e)} ${r}, whatever its size or colour.`}]})}const geoP3M01Generator={id:"GEO.P3.M01.count-sides.v1",microSkillId:"GEO.P3.M01",forms:["shape_property","symbolic_numeric_entry","symbolic_choice"],generate(n){const t=resolveShape$3(n);if(!t)return null;const e=SIDES$1[t];return e<1||e!==answerInt(n)?null:buildSidesDemo$1(n,t,e,bridgeSubject(n.stem))}},ORANGE="#F5A623",VERTICES={triangle:3,square:4,rectangle:4,pentagon:5,hexagon:6,circle:0},SHAPE_WORDS$2=Object.keys(VERTICES);function asDrawableShape$2(n){if(!n)return null;const t=n.toLowerCase();return SHAPE_WORDS$2.includes(t)?t:null}function resolveShape$2(n){const t=n.visualData;if(t&&t.type==="shape_svg")return asDrawableShape$2(t.shape);const e=normaliseStem(n.stem).toLowerCase(),s=new Set;for(const o of SHAPE_WORDS$2)new RegExp(`\\b${o}s?\\b`).test(e)&&s.add(o);return s.size===1?[...s][0]:null}function buildCornersDemo(n,t,e){const s={type:"shape_svg",shape:t,color:ORANGE},o=e===1?"corner":"corners",r={corners:e,answer:e};return buildExample({generatorId:geoP3M02Generator.id,question:n,title:"How do we count corners?",computed:r,steps:[{visual_data:s,animation:"show_shape",caption:`A ${t}. Corners are where two sides meet.`,spoken:`Here's a ${t}. A corner is where two sides meet — the pointy bits.`},{visual_data:s,animation:"count_corners",caption:`Count each corner: ${countingRun(1,e)}.`,spoken:`Count each corner as it lights up: ${countingRun(1,e)}.`},{visual_data:s,animation:"write_total_corners",caption:`A ${t} has ${e} ${o}.`,spoken:`${capitalised(numberWord(e))} ${o}. Every ${t} has ${numberWord(e)} ${o}, whatever its size or colour.`}]})}const geoP3M02Generator={id:"GEO.P3.M02.count-corners.v1",microSkillId:"GEO.P3.M02",forms:["shape_property","symbolic_numeric_entry","symbolic_choice"],generate(n){const t=resolveShape$2(n);if(!t)return null;const e=VERTICES[t];return e<1||e!==answerInt(n)?null:buildCornersDemo(n,t,e)}},BLUE$3="#3b82f6",FACE_COUNT={cube:6,cuboid:6,sphere:1,cylinder:3,cone:2,pyramid:5},SHAPES=Object.keys(FACE_COUNT);function resolveShape$1(n){const t=n.visualData;if(t&&t.type==="shape_3d"){const s=t.shape;if(SHAPES.includes(s))return s}const e=normaliseStem(n.stem).toLowerCase();for(const s of["cuboid","cube","sphere","cylinder","cone","pyramid"])if(new RegExp(`\\b${s}\\b`).test(e))return s;return null}const BOX_SCRIPT={parts:{seen:3,hidden:3},step2Caption:"Count the faces you can see.",step2Spoken:"Count the flat faces you can see — the front, the top and the side.",step3Caption:()=>"Turn it round: 3 more behind. 3 and 3 make 6.",step3Spoken:n=>`Turn it round. There are three more faces hidden behind — count them on. Three you saw and three hidden make six. A ${n} has six faces.`},PYRAMID_SCRIPT={parts:{base:1,sloping:4},step2Caption:"Open it out flat: 1 base and 4 sloping sides.",step2Spoken:"Open it out flat. There is one flat bottom, and four sloping sides fold down around it.",step3Caption:()=>"The bottom and 4 sides make 5 faces.",step3Spoken:()=>"The bottom face and the four sloping faces make five. A pyramid has five faces."},GENERIC_SCRIPT={parts:{},step2Caption:"Count the faces you can see.",step2Spoken:"Count the flat faces you can see, one by one.",step3Caption:(n,t)=>`Turn it round. A ${n} has ${t} faces.`,step3Spoken:(n,t)=>`Now turn the ${n} round to see the hidden faces. A ${n} has ${t} faces in all.`};function scriptFor(n){return n==="cube"||n==="cuboid"?BOX_SCRIPT:n==="pyramid"?PYRAMID_SCRIPT:GENERIC_SCRIPT}function buildFacesDemo(n,t,e){const s=scriptFor(t),o={faces:e,answer:e,...s.parts},r=numberWord(e),a={type:"shape_3d",shape:t,color:BLUE$3};return buildExample({generatorId:geoP3M03Generator.id,question:n,title:`How many faces does a ${t} have?`,computed:o,steps:[{visual_data:a,animation:"show_solid",caption:`This is a ${t}.`,spoken:`This solid is a ${t}. A face is a flat side.`},{visual_data:a,animation:"count_faces",caption:s.step2Caption,spoken:s.step2Spoken},{visual_data:a,animation:"reveal_total",caption:s.step3Caption(t,e),spoken:s.step3Spoken(t,r)}]})}const geoP3M03Generator={id:"GEO.P3.M03.count-faces.v1",microSkillId:"GEO.P3.M03",forms:["shape_property","visual_quantity_to_symbol"],generate(n){const t=resolveShape$1(n);if(!t||t!=="cube"&&t!=="cuboid"&&t!=="pyramid")return null;const e=FACE_COUNT[t],s=answerInt(n);return s===null||e!==s?null:buildFacesDemo(n,t,e)}},SUBSET_STEM=/^How many (flat|square|triangle) faces does this (?:sh
71ape|cube|cuboid|sphere|cylinder|cone|pyramid) have\?$/,SUBSET_COUNT={sphere:{flat:0},cylinder:{flat:2},cone:{flat:1},pyramid:{flat:5,square:1,triangle:4}};function subsetLabel(n,t){return`${n} ${t} ${n===1?"face":"faces"}`}function subsetScript(n,t){if(n==="sphere"&&t==="flat")return{parts:{},step2Caption:"Follow the ring — curved all round, like a ball.",step2Spoken:"Follow the ring all the way round the sphere. It is curved everywhere, like a ball — there is nothing flat to press on the table.",step3Caption:"No flat faces at all: 0.",step3Spoken:"So a sphere has zero flat faces — none at all."};if(n==="cylinder"&&t==="flat")return{parts:{},step2Caption:"Both flat ends light up — top and bottom.",step2Spoken:"Look at the cylinder, like a tin. Its two flat round ends light up — one at the top, one at the bottom. The part between them is curved.",step3Caption:"Top and bottom: 2 flat faces.",step3Spoken:"The top and the bottom make two. A cylinder has two flat faces."};if(n==="cone"&&t==="flat")return{parts:{},step2Caption:"The base lights up — the one flat face.",step2Spoken:"Look at the cone. The flat round face it stands on lights up. The rest curves up to the point — that part is not flat.",step3Caption:"Just the base: 1 flat face.",step3Spoken:"Only the base is flat. A cone has one flat face."};if(n==="pyramid"){const e="5 faces in all: 1 square base, 4 triangles.",s="Open the pyramid out flat and count all its faces — five in all. The base in the middle is the square one; the other four are triangles.";if(t==="square")return{parts:{faces:5,triangles:4},step2Caption:e,step2Spoken:s,step3Caption:"Only the base is square: 1 square face.",step3Spoken:"Only the base is a square. A pyramid has one square face."};if(t==="triangle")return{parts:{faces:5,base:1},step2Caption:e,step2Spoken:s,step3Caption:"The 4 sloping sides are triangles.",step3Spoken:"The four sloping sides are all triangles. A pyramid has four triangle faces."};if(t==="flat")return{parts:{base:1,triangles:4},step2Caption:e,step2Spoken:s,step3Caption:"All 5 are flat: 5 flat faces.",step3Spoken:"Every one of them is flat — the square base and the four triangles. A pyramid has five flat faces."}}return null}const geoP3M03SubsetGenerator={id:"GEO.P3.M03.face-subset.v1",microSkillId:"GEO.P3.M03",forms:["shape_property","visual_quantity_to_symbol"],generate(n){const t=normaliseStem(n.stem).match(SUBSET_STEM);if(!t)return null;const e=t[1],s=resolveShape$1(n);if(!s)return null;const o=SUBSET_COUNT[s]?.[e];if(o===void 0)return null;const r=answerInt(n);if(r===null||o!==r)return null;const a=subsetScript(s,e);if(!a)return null;const i={type:"shape_3d",shape:s,color:BLUE$3,total_label:subsetLabel(o,e)},l=s==="pyramid"&&(e==="triangle"||e==="square")?{...i,count_subset:e==="triangle"?"triangles":"base"}:i,u={count:o,answer:o,...a.parts},d=numberWord(o);return buildExample({generatorId:this.id,question:n,title:`How many ${e} faces does a ${s} have?`,computed:u,steps:[{visual_data:i,animation:"show_solid",caption:`This is a ${s}.`,spoken:`This solid is a ${s}. We are counting its ${e} faces.`},{visual_data:i,animation:"count_faces",caption:a.step2Caption,spoken:a.step2Spoken},{visual_data:l,animation:"reveal_total",caption:a.step3Caption,spoken:`${a.step3Spoken} That is ${d}.`}]})}},FLAT_CLAIM_STEM=/^[A-Z][a-z]+ says (?:his|her) (.+?) has (\d+) flat faces because it is shaped like an? (cube|cuboid|sphere|cylinder|cone|pyramid)\. Is (?:he|she) right\?$/;function realFlatCount(n){return n==="cube"||n==="cuboid"?FACE_COUNT[n]:SUBSET_COUNT[n]?.flat??null}const geoP3M03ClaimGenerator={id:"GEO.P3.M03.flat-face-claim.v1",microSkillId:"GEO.P3.M03",forms:["shape_property"],generate(n){const t=normaliseStem(n.stem).match(FLAT_CLAIM_STEM);if(!t)return null;const e=t[1],s=Number(t[2]),o=t[3],r=n.visualData;if(r&&r.type==="shape_3d"&&r.shape!==o)return null;const a=realFlatCount(o);if(a===null)return null;const i=a===s;if((i?"Yes":"No")!==n.correctAnswer.trim())return null;const u=o==="cube"||o==="cuboid",d=u?BOX_SCRIPT.step2Caption:subsetScript(o,"flat").step2Caption,m=u?BOX_SCRIPT.step2Spoken:subsetScript(o,"flat").step2Spoken,h=numberWord(a),f=numberWord(s),g={type:"shape_3d",shape:o,color:BLUE$3,...u?{}:{total_label:subsetLabel(a,"flat")}},b={actual:a,claimed:s};return buildExample({generatorId:this.id,question:n,title:`Does the ${e} really have ${s} flat faces?`,computed:b,steps:[{visual_data:g,animation:"show_solid",caption:`Someone says the ${e} has ${s}
71 flat faces. Check by counting.`,spoken:`Someone says this ${o}-shaped ${e} has ${f} flat faces. Count the real flat faces to check.`},{visual_data:g,animation:"count_faces",caption:d,spoken:m},{visual_data:g,animation:"reveal_total",caption:i?`A ${o} really has ${a} flat faces — right.`:`A ${o} has ${a} flat faces, not ${s} — wrong.`,spoken:i?`We counted ${h} flat faces, the same as the claim. A ${o} really does have ${h} flat faces, so the claim is right.`:`We counted ${h} flat faces, not ${f}. A ${o} has ${h} flat faces, so the claim is wrong.`}]})}},NAME_FEATURE={cube:{caption:"Six equal square faces — a cube.",spoken:"A cube has six equal square faces."},cuboid:{caption:"Six faces, longer rectangles — a cuboid.",spoken:"A cuboid has six faces too, but they are longer rectangles."},sphere:{caption:"Round all over, like a ball — a sphere.",spoken:"A sphere is round all over, like a ball."},cylinder:{caption:"Two round ends, a curve between — a cylinder.",spoken:"A cylinder has two flat round ends with a curve between them."},cone:{caption:"A round base up to a point — a cone.",spoken:"A cone has a round base and rises to a point."},pyramid:{caption:"A flat base up to a point — a pyramid.",spoken:"A pyramid has a flat base with straight sides and rises to a point."}};function shapeFromLabel(n){const t=n.trim().toLowerCase();return SHAPES.includes(t)?t:null}function contrastSolids(n,t){const e=[];for(const s of t??[]){const o=shapeFromLabel(s);if(o&&o!==n&&!e.includes(o)&&e.push(o),e.length===2)break}for(const s of SHAPES){if(e.length===2)break;s!==n&&!e.includes(s)&&e.push(s)}return[e[0],e[1]]}const geoP3M03NameGenerator={id:"GEO.P3.M03.name-shape.v1",microSkillId:"GEO.P3.M03",forms:["symbolic_choice"],generate(n){const t=SHAPES.find(i=>capitalised(i)===n.correctAnswer.trim());if(!t)return null;const e=n.visualData;if(e&&e.type==="shape_3d"&&e.shape!==t)return null;const[s,o]=contrastSolids(t,n.options),a={type:"shape_3d_name",color:BLUE$3,solids:[s,o,t].map(i=>({shape:i,name:capitalised(i)}))};return buildExample({generatorId:this.id,question:n,title:"What is the name of this shape?",computed:{},steps:[{visual_data:a,animation:"name_first",caption:NAME_FEATURE[s].caption,spoken:`To name a solid, we look for the clue that makes it special. ${NAME_FEATURE[s].spoken}`},{visual_data:a,animation:"name_second",caption:NAME_FEATURE[o].caption,spoken:`Each solid has its own clue, so we can tell them apart. ${NAME_FEATURE[o].spoken}`},{visual_data:a,animation:"name_target",caption:NAME_FEATURE[t].caption,spoken:`${NAME_FEATURE[t].spoken} That clue matches the shape in the question, so this shape is a ${t}.`}]})}},BLUE$2="#2563eb",SIDES={triangle:3,square:4,rectangle:4,pentagon:5,hexagon:6,circle:0},SHAPE_WORDS$1=Object.keys(SIDES),EQUAL_SIDES={triangle:!0,square:!0,rectangle:!1,pentagon:!0,hexagon:!0,circle:!1},LONG_SHORT={triangle:!1,square:!1,rectangle:!0,pentagon:!1,hexagon:!1,circle:!1};function asDrawableShape$1(n){if(!n)return null;const t=n.toLowerCase();return SHAPE_WORDS$1.includes(t)?t:null}function resolveShape(n){const t=n.visualData;if(t&&t.type==="shape_svg")return asDrawableShape$1(t.shape);const e=normaliseStem(n.stem).toLowerCase(),s=new Set;for(const o of SHAPE_WORDS$1)new RegExp(`\\b${o}s?\\b`).test(e)&&s.add(o);return s.size===1?[...s][0]:null}function buildSidesDemo(n,t,e){const s={type:"shape_svg",shape:t,color:BLUE$2},o=e===1?"side":"sides",r={sides:e,answer:e};return buildExample({generatorId:geoP3M05Generator.id,question:n,title:`How many sides does a ${t} have?`,computed:r,steps:[{visual_data:s,animation:"show_shape",caption:`A ${t}. Sides are the straight edges.`,spoken:`This shape is a ${t}. A side is a straight edge. To find how many sides it has, we count each straight edge once.`},{visual_data:s,animation:"trace_sides",caption:`Count each side: ${countingRun(1,e)}.`,spoken:`Put your finger on one side and call it one, then go round the shape: ${countingRun(1,e)}. Stop when you get back to the first side, so no side is counted twice.`},{visual_data:s,animation:"write_total_sides",caption:`A ${t} has ${e} ${o}.`,spoken:`We counted ${numberWord(e)} straight ${e===1?"edge":"edges"}. So a ${t} has ${numberWord(e)} ${o}.`}]})}function buildCircleDemo(n){const t={type:"shape_svg",shape:"circle",color:BLUE$2},e={...t,trace_curve:!0},s={sides:0,answer:0};return buildExample({generatorId:geoP3M05Generator.id,question:n,title:"How many straight sides does a circle have?",computed:s,steps:[{visual_data:t,animation:"show_shape",caption:"A circle. Sides are the straight edges.",spoken:"This shape is a circle. A side is a straight edge."},{visual_data:e,animation:"trace_sides",caption:"Trace all the way round — it curves, no straight edges.",spoken:"Trace all the way round the circle. It curves the whole way — there is not one straight edge to count."},{visual_data:t,animation:"write_total_sides",caption:"A circle has 0 straight sides.",spoken:"There was nothing straight to count. So a circle has zero straight sides."}]})}const GROUP_RULE_STEM=/^This shape goes in a group\. What is the group's rule\?$/;function buildGroupRuleDemo(n,t,e){if(`Shapes with ${e} sides`!==n.correctAnswer.trim())return null;const o={type:"shape_svg",shape:t,color:BLUE$2},r=countingRun(1,e),a={sides:e};return buildExample({generatorId:geoP3M05Generator.id,question:n,title:"What is the group's rule?",computed:a,steps:[{visual_data:o,animation:"show_shape",caption:`A ${t}. Look at its sides.`,spoken:`This shape is a ${t}. Shapes are often put in groups by their sides, so look closely at its sides.`},{visual_data:o,animation:"trace_sides",caption:`Count the sides: ${r}.`,spoken:`Go round the shape and count each straight edge once: ${r}.`},{visual_data:o,animation:"write_total_sides",caption:`${e} sides — the group's rule is shapes with ${e} sides.`,spoken:`This shape has ${numberWord(e)} sides. So the group's rule is shapes with ${numberWord(e)} sides.`}]})}const CORNERS_TO_SIDES=/^A shape has (\d+) corners\. How many sides does it have\?$/,CORNER_EXEMPLAR={3:"triangle",4:"square",5:"pentagon",6:"hexagon"};function buildCornersToSidesDemo(n,t){if(t!==answerInt(n))return null;const e=CORNER_EXEMPLAR[t];if(!e)return null;const s={type:"shape_svg",shape:e,color:BLUE$2},o=countingRun(1,t),r={corners:t,answer:t};return buildExample({generatorId:geoP3M05Generator.id,question:n,title:`A shape has ${t} corners. How many sides?`,computed:r,steps:[{visual_data:s,animation:"show_shape",caption:`This shape has ${t}
71 corners.`,spoken:"A corner is where two sides meet. So if we count the corners, we can find the sides. Mark each corner as you count."},{visual_data:s,animation:"count_corners",caption:`Mark each corner: ${o}.`,spoken:`We mark each corner once as we go round the shape: ${o}. That is ${numberWord(t)} corners.`},{visual_data:s,animation:"trace_sides",caption:`A side sits between each pair of corners: ${o}.`,spoken:`Between each pair of corners there is one side. Going round, we count the sides the same way: ${o}. So ${numberWord(t)} corners make ${numberWord(t)} sides.`}]})}const ANCHOR_TAP_STEM=/^(.+) are all (triangles|squares|rectangles|pentagons|hexagons)\. Tap the shape below that is also an? (triangle|square|rectangle|pentagon|hexagon)$/;function buildAnchorTapDemo(n,t,e){if(capitalised(e)!==n.correctAnswer.trim())return null;const s=n.visualData;if(s&&s.type==="shape_options"){const h=s;if(h.correct!==e||!h.shapes.includes(e))return null}const o=SIDES[e];if(o<1)return null;const r={type:"shape_svg",shape:e,color:BLUE$2},a=countingRun(1,o),i={sides:o};let l,u,d,m;return e==="rectangle"?(i.longSides=2,i.shortSides=2,l="Count the sides: 2 long, 2 short.",u=`Count each side. ${capitalised(a)}. Two are long and two are short.`,d="A rectangle too — 2 long + 2 short sides.",m="This shape also has two long sides and two short sides — it is a rectangle too."):e==="square"?(l=`Count the sides: ${a}. All equal.`,u=`Count each side. ${capitalised(a)}. They are all equal.`,d=`${o} equal sides — a square too.`,m=`${capitalised(numberWord(o))} equal sides — this shape is a square too.`):(l=`Count each side: ${a}.`,u=`Count each straight edge. ${capitalised(a)}.`,d=`${o} sides — a ${e} too.`,m=`This shape has ${numberWord(o)} sides too — it is a ${e}.`),buildExample({generatorId:geoP3M05Generator.id,question:n,title:`Which shape is also a ${e}?`,computed:i,steps:[{visual_data:r,animation:"show_shape",caption:`${t} are all ${e}s.`,spoken:`${t} are all ${e}s in real life. To check if a shape is a ${e} too, we look at its sides.`},{visual_data:r,animation:"trace_sides",caption:l,spoken:u},{visual_data:r,animation:"name_from_sides",caption:d,spoken:m}]})}function parseProperty(n){const t=normaliseStem(n).toLowerCase();if(/\bno straight (?:side|sides|edge|edges)\b/.test(t))return{kind:"noStraight"};if(/\blong sides?\b/.test(t)&&/\bshort sides?\b/.test(t))return{kind:"longShort"};const e=t.match(/\b(\d+) equal sides?\b/);if(e)return{kind:"equal",n:Number(e[1])};const s=t.match(/\b(\d+)[ -]sided\b/)??t.match(/\b(\d+) sides?\b/);return s?{kind:"count",n:Number(s[1])}:null}function satisfies(n,t){switch(t.kind){case"count":return SIDES[n]===t.n;case"equal":return SIDES[n]===t.n&&EQUAL_SIDES[n];case"longShort":return LONG_SHORT[n];case"noStraight":return SIDES[n]===0}}function buildReverseCompareDemo(n,t,e,s){const o=SIDES[t],r=SIDES[e],a={sides:o,targetSides:o,distractorSides:r},i=(b,x,y)=>({shape:b,color:b===t?"#2563eb":"#f59e0b",label:capitalised(b),count:x?SIDES[b]:void 0,highlight:y});let l,u,d,m,h,f,g;return s.kind==="equal"?(l=`Which shape has ${o} equal sides?`,u=`Test the ${e} first: ${r} sides, but not all equal.`,d=`We check each shape against the rule. Try the ${e} first. It has ${numberWord(r)} sides, but two are long and two are short — not equal. Reject it.`,m=`Now the ${t}: ${o} sides, all equal.`,h=`Now try the ${t}. It has ${numberWord(o)} sides, and every one is the same length as the others. That fits the rule.`,f=`${o} equal sides — it is the ${t}.`,g=`Only the ${t} has ${numberWord(o)} sides that are all the same length. So the answer is the ${t}.`):(l="Which shape has 2 long and 2 short sides?",a.longSides=2,a.shortSides=2,u=`Test the ${e} first: ${r} sides, all equal — not this one.`,d=`We check each shape against the rule. Try the ${e} first. All ${numberWord(r)} sides are the same length — that is not two long and two short. Reject it.`,m=`Now the ${t}: 2 long sides and 2 short sides.`,h=`Now try the ${t}. Two sides are long and two are short. That fits the rule.`,f=`2 long + 2 short — it is the ${t}.`,g=`Only the ${t} has two long sides and two short sides. So the answer is the ${t}.`),buildExample({generatorId:geoP3M05Generator.id,question:n,title:l,computed:a,steps:[{visual_data:{type:"shape_svg",shape:t,color:"#2563eb",compare:[i(e,!1,!1),i(t,!1,!1)]},animation:"show_shape",caption:u,spoken:d},{visual_data:{type:"shape_svg",shape:t,color:"#2563eb",compare:[i(e,!0,!1),i(t,!0,!1)]},animation:"trace_sides",caption:m,spoken:h},{visual_data:{type:"shape_svg",shape:t,color:"#2563eb",compare:[i(e,!0,!1),i(t,!0,!0)]},animation:"name_from_sides",caption:f,spoken:g}]})}function buildNoStraightSidesDemo(n,t){const e=SIDES[t],s=countingRun(1,e),o={sides:0,distractorSides:e},r={type:"shape_svg",shape:"circle",color:BLUE$2},a={...r,trace_curve:!0},i={type:"shape_svg",shape:t,color:"#f59e0b"};return buildExample({generatorId:geoP3M05Generator.id,question:n,title:"Which shape has no straight sides?",computed:o,steps:[{visual_data:i,animation:"trace_sides",caption:`Test the ${t} first: ${e} straight sides — ${s}.`,spoken:`Try the ${t} first. Count its straight edges. ${capitalised(s)}. It has straight sides, so it is not the answer.`},{visual_data:a,animation:"trace_sides",caption:"Now the circle: trace all the way round — it curves, no straight edges.",spoken:"Now try the circle. Trace all the way round — it curves the whole way. There is not one straight edge to count."},{visual_data:r,animation:"write_total_sides",caption:"A circle has no straight sides — Circle is the answer.",spoken:"A circle has zero straight sides. So the answer is Circle."}]})}function buildReverseDemo(n,t,e){const s=SIDES[t],o={type:"shape_svg",shape:t,color:BLUE$2},r=countingRun(1,s);let a,i,l,u,d,m,h;const f={sides:s};return e.kind==="equal"?(a=`Which shape has ${s} equal sides?`,i="Look for a shape whose sides are all equal.",l="We want a shape whose sides are all the same length.",u=`Count the sides: ${r}.`,d=`Count each side. ${capitalised(r)}. They are all equal.`,m=`${s} equal sides — it is a ${t}.`,h=`${capitalised(numberWord(s))} equal sides make a ${t}.`):e.kind==="longShort"?(a="Which shape has 2 long and 2 short sides?",f.longSides=2,f.shortSides=2,i="Look for 2 long sides and 2 short sides.",l="We want a shape with two long sides and two short sides.",u=`Count the sides: ${r}.`,d=`Count each side. ${capitalised(r)}. Two are long and two are short.`,m=`2 long + 2 short — it is a ${t}.`,h=`Two long sides and two short sides make a ${t}.`):(a=`Which shape has ${s} sides?`,i="Sides are the straight edges of a shape.",l="Sides are the straight edges around a shape. To find the shape, we count them.",u=`Count the sides: ${r}.`,d=`Go round the shape and count each straight edge once: ${r}.`,m=`${s} sides — it is a ${t}.`,h=`This shape has ${numberWord(s)} sides. It is a ${t}.`),buildExample({generatorId:geoP3M05Generator.id,question:n,title:a,c
71omputed:f,steps:[{visual_data:o,animation:"show_shape",caption:i,spoken:l},{visual_data:o,animation:"trace_sides",caption:u,spoken:d},{visual_data:o,animation:"name_from_sides",caption:m,spoken:h}]})}function generateReverse(n){const t=n.correctAnswer.trim().toLowerCase();if(!SHAPE_WORDS$1.includes(t))return null;const e=parseProperty(n.stem);if(!e)return null;const s=n.options;if(!s||s.length===0)return null;const o=[...new Set(s.map(i=>i.trim().toLowerCase()).filter(i=>SHAPE_WORDS$1.includes(i)))],r=o.filter(i=>satisfies(i,e));if(r.length!==1)return null;const a=r[0];if(a!==t)return null;if(e.kind==="noStraight"){const i=o.find(l=>l!=="circle"&&SIDES[l]>=1);return i?buildNoStraightSidesDemo(n,i):null}return SIDES[a]<1?null:e.kind==="equal"&&a==="square"&&o.includes("rectangle")?buildReverseCompareDemo(n,"square","rectangle",e):e.kind==="longShort"&&a==="rectangle"&&o.includes("square")?buildReverseCompareDemo(n,"rectangle","square",e):buildReverseDemo(n,a,e)}const geoP3M05Generator={id:"GEO.P3.M05.count-sides.v1",microSkillId:"GEO.P3.M05",forms:["shape_property","fact_symbolic_retrieval","visual_quantity_to_symbol","comparison_statement","misconception_contrast_probe","symbolic_choice"],generate(n){const t=normaliseStem(n.stem),e=t.match(ANCHOR_TAP_STEM);if(e){const[,r,a,i]=e;return a.slice(0,-1)===i?buildAnchorTapDemo(n,r,i):null}const s=t.match(CORNERS_TO_SIDES);if(s)return buildCornersToSidesDemo(n,Number(s[1]));if(GROUP_RULE_STEM.test(t)){const r=resolveShape(n);if(!r)return null;const a=SIDES[r];return a<1?null:buildGroupRuleDemo(n,r,a)}const o=resolveShape(n);if(o==="circle")return answerInt(n)===0?buildCircleDemo(n):null;if(o){const r=SIDES[o];if(r>=1&&r===answerInt(n))return buildSidesDemo(n,o,r)}return generateReverse(n)}},TURN_LABEL={quarter:"Quarter turn",half:"Half turn",three_quarter:"Three-quarter turn",full:"Full turn"},CONCLUDE={quarter:{caption:"One quarter turn — four of them make a full turn.",spoken:"It has made one quarter turn. Four of these would take it all the way round, so this is a quarter of a full turn."},half:{caption:"Two quarter turns make a half turn.",spoken:"It has made two quarter turns. Two quarter turns take it halfway round, so it now points the opposite way. That is a half turn."},three_quarter:{caption:"Three quarter turns make a three-quarter turn.",spoken:"It has made three quarter turns — most of the way round, but not all the way. That is a three-quarter turn."},full:{caption:"Four quarter turns make a full turn.",spoken:"It has made four quarter turns, all the way round to where it started. That is a full turn."}},QUARTER_COUNT={quarter:"one",half:"one, two",three_quarter:"one, two, three",full:"one, two, three, four"},START_PHRASE={up:"straight up",down:"straight down",left:"to the left",right:"to the right"};function turnFromVisual(n){const t=n.visualData;return!t||t.type!=="turn"?null:t}const TURN_COMPARE_STEM=/^Which turn is the (biggest|smallest)\?$/;function buildTurnCompareDemo(n,t){const e=t==="smallest"?"quarter":"three_quarter",s=t==="smallest"?"three_quarter":"quarter";if(TURN_LABEL[e]!==n.correctAnswer.trim())return null;const r={type:"turn",turn:e,direction:"clockwise",start:"up"},a={type:"turn",turn:s,direction:"clockwise",start:"up"},i=TURN_LABEL[e].toLowerCase(),l=TURN_LABEL[s].toLowerCase(),u=e==="quarter"?"one quarter of the way round":"three quarter turns, most of the way round",d=s==="quarter"?"one quarter of the way round":"three quarter turns, most of the way round",m=t==="smallest"?"smallest":"biggest";return buildExample({generatorId:geoP4M01Generator.id,question:n,title:`Which turn is the ${t}?`,computed:{},steps:[{visual_data:a,animation:"compare_show",caption:`A ${l} — ${d}.`,spoken:`Here is a ${l}. Watch the arrow turn: the shading shows how far it goes. It is ${d}.`},{visual_data:r,animation:"compare_rotate",caption:`A ${i} — ${u}.`,spoken:`Now here is a ${i}. It is ${u}.`},{visual_data:{...r,compare_turn:s},animation:"compare_name",caption:`${capitalised(m)}: the ${i}.`,spoken:`Compare the two shaded parts. The ${i} sweeps ${t==="smallest"?"less":"more"} of the circle than the ${l}, so the ${m} is the ${i}.`}]})}function buildTurnDemo(n,t){const e=t.start??"up",s=START_PHRASE[e],o=t.direction==="clockwise"?"clockwise":"anticlockwise",r={},a=t;return buildExample({generatorId:geoP4M01Generator.id,question:n,title:"How far has the arrow turned?",computed:r,steps:[{visual_data:a,animation:"show_start",caption:"See where the arrow starts.",spoken:`The arrow starts by pointing ${s}
71. To name the turn, we watch how far round it goes from there.`},{visual_data:a,animation:"rotate",caption:"Watch it turn around the middle.",spoken:`It turns ${o} — ${t.direction==="clockwise"?"the same way as a clock's hands":"the opposite way to a clock's hands"}. Each line it passes is one quarter turn. Let's count them: ${QUARTER_COUNT[t.turn]}.`},{visual_data:a,animation:"name_turn",caption:CONCLUDE[t.turn].caption,spoken:CONCLUDE[t.turn].spoken}]})}const geoP4M01Generator={id:"GEO.P4.M01.name-turn.v1",microSkillId:"GEO.P4.M01",forms:["symbolic_choice","visual_quantity_to_symbol","comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(TURN_COMPARE_STEM);if(t)return buildTurnCompareDemo(n,t[1]);const e=turnFromVisual(n);return!e||TURN_LABEL[e.turn]!==n.correctAnswer.trim()?null:buildTurnDemo(n,e)}},START_BEARING$1={up:0,right:90,down:180,left:270},TURN_DEG={quarter:90,half:180,three_quarter:270,full:360},HEADING_WORD={0:"Up",90:"Right",180:"Down",270:"Left"};function parseDirectionStem(n){const t=normaliseStem(n).toLowerCase(),e=t.match(/\b(?:faces|facing|points|pointing)\s+(up|down|left|right)\b/);if(!e)return null;const s=e[1],o=t.match(/\b(quarter|half|three-quarter)\s+turn\b/);if(!o)return null;const r=o[1]==="three-quarter"?"three_quarter":o[1],a=t.match(/\b(clockwise|anticlockwise)\b/);if(!a&&r!=="half")return null;const i=a!==null,l=a?a[1]:"clockwise";return{start:s,turn:r,direction:l,hadDirection:i}}function landingHeading(n){const t=(n.direction==="clockwise"?1:-1)*TURN_DEG[n.turn],e=((START_BEARING$1[n.start]+t)%360+360)%360;return HEADING_WORD[e]}const TURN_PHRASE={quarter:"a quarter turn",half:"a half turn",three_quarter:"a three-quarter turn",full:"a full turn"};function buildDirectionDemo(n,t){const e=capitalised(t.start),s=landingHeading(t),o={type:"turn",turn:t.turn,direction:t.direction,start:t.start},r=t.hadDirection?` ${t.direction}`:"",a=t.hadDirection?`, going ${t.direction}`:"",i=t.turn==="half"?" A half turn lands you facing the opposite way.":"",l=`${t.hadDirection?t.direction:"half"}_direction`;return buildExample({generatorId:geoP4M01DirectionGenerator.id,question:n,title:"Which way are you facing now?",computed:{},steps:[{visual_data:o,animation:"show_start",caption:`Start facing ${e}.`,spoken:`You start by facing ${t.start}.`},{visual_data:o,animation:"rotate",caption:`Make ${TURN_PHRASE[t.turn]}${r}.`,spoken:`To find where you end up, turn the way the question says. Make ${TURN_PHRASE[t.turn]}${a}.${i}`},{visual_data:o,animation:l,caption:`Now facing ${s}.`,spoken:`Follow the arrow round. You end up facing ${s.toLowerCase()}.`}]})}const geoP4M01DirectionGenerator={id:"GEO.P4.M01.which-way-now.v1",microSkillId:"GEO.P4.M01",forms:["context_calculation"],generate(n){const t=parseDirectionStem(n.stem);if(!t||landingHeading(t)!==n.correctAnswer.trim())return null;const e=turnFromVisual(n);if(e){const s=e.start??"up";if(e.turn!==t.turn||s!==t.start||t.hadDirection&&e.direction!==t.direction)return null}return buildDirectionDemo(n,t)}},Y2_GENERATORS=[asP4M01Generator,asP4M02Generator,asP4M03Generator,asP4M04Generator,asP4M05Generator,asP4M06Generator,asP4M07Generator,asP4M08Generator,npvP4M01Generator,npvP4M02Generator,npvP4M03Generator,npvP4M04Generator,npvP4M05Generator,mdP2M01Generator,mdP2M02Generator,mdP3M05ContextAliasGenerator,mdP2M03Generator,mdP2M04Generator,mdP2M05Generator,fdpP1M01Generator,fdpP1M02Generator,fdpP2M01Generator$1,fdpP2M02Generator$1,fdpP2M03Generator,fdpP2M04Generator,fdpP3M01Generator,fdpP3M02Generator,meaP3M03Generator,meaP4M01Generator,meaP4M02Generator,meaP4M03Generator,meaP4M05Generator,meaP5M01Generator,meaP6M01Generator,meaP6M02Generator,meaP6M03Generator,dsP1M02Generator,dsP2M01Generator,dsP2M02Generator,geoP3M01Generator,geoP3M02Generator,geoP3M03Generator,geoP3M03SubsetGenerator,geoP3M03NameGenerator,geoP3M03ClaimGenerator,geoP3M05Generator,geoP4M01Generator,geoP4M01DirectionGenerator],PLACE_LABELS=["ones","tens","hundreds"];function describeColumnAddition(n,t){const e=[n%10,Math.floor(n/10)%10,Math.floor(n/100)],s=[t%10,Math.floor(t/10)%10,Math.floor(t/100)];let o=0;const r=[],a=[];for(let l=0;l<3;l++){const u=e[l]+s[l]+o,d=u%10,m=u>=10?1:0;r[l]=d;const h=o?" plus the carried one":"";a.push(m?`In the ${PLACE_LABELS[l]}, ${numberToWords(e[l])} and ${numberToWords(s[l])}${h} make ${numberToWords(u)} — write ${numberToWords(d)}, carry a ${PLACE_LABELS[l+1]==="tens"?"ten":"hundred"}
71`:`In the ${PLACE_LABELS[l]}, ${numberToWords(e[l])} and ${numberToWords(s[l])}${h} make ${numberToWords(u)}`),o=m}const i=r[2]*100+r[1]*10+r[0];return{resultDigits:r,total:i,sentences:a}}function describeColumnSubtraction(n,t){const e=[n%10,Math.floor(n/10)%10,Math.floor(n/100)],s=[t%10,Math.floor(t/10)%10,Math.floor(t/100)];let o=0;const r=[],a=[],i=[];for(let u=0;u<3;u++){const d=e[u]-o;if(d===-1)r[u]=9-s[u],a.push(`In the ${PLACE_LABELS[u]}, nine are left after that exchange, and ${numberToWords(s[u])} from nine leaves ${numberToWords(r[u])}`),o=1,i[u]=!0;else if(d<s[u]){const m=d+10;r[u]=m-s[u],a.push(u===0&&e[1]===0?`In the ones, there aren't enough and there are no tens to exchange, so we exchange a hundred for ten tens, then one of those tens for ten ones — that makes ${numberToWords(m)}, and ${numberToWords(s[u])} from that leaves ${numberToWords(r[u])}`:`In the ${PLACE_LABELS[u]}, there aren't enough, so we exchange a ${PLACE_LABELS[u]==="ones"?"ten":"hundred"} from the ${PLACE_LABELS[u+1]} column — that makes ${numberToWords(m)}, and ${numberToWords(s[u])} from that leaves ${numberToWords(r[u])}`),o=1,i[u]=!0}else r[u]=d-s[u],a.push(`In the ${PLACE_LABELS[u]}, ${numberToWords(s[u])} from ${numberToWords(d)} leaves ${numberToWords(r[u])}`),o=0,i[u]=!1}const l=r[2]*100+r[1]*10+r[0];return{resultDigits:r,diff:l,sentences:a,borrows:i}}function columnOperands(n,t){const e=n.visualData;return!e||e.type!=="column_method"||e.operation!==t||!Number.isInteger(e.top)||!Number.isInteger(e.bottom)?null:{top:e.top,bottom:e.bottom}}const columnAddition={id:"AS.P5.M01.column-add.v1",microSkillId:"AS.P5.M01",forms:["symbolic_calculation"],generate(n){const t=columnOperands(n,"add");if(!t)return null;const{top:e,bottom:s}=t;if(e<100||e>999||s<100||s>999)return null;const o=e+s;if(o>999||o!==answerInt(n))return null;const r={top:e%10,bottom:s%10},a=r.top+r.bottom,i=a>=10,l=a%10,u={type:"column_method",operation:"add",top:e,bottom:s},d=Math.floor(e/10)%10,m=Math.floor(s/10)%10,h=d+m+(i?1:0),f=h>=10,g=describeColumnAddition(e,s);return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} + ${s}?`,computed:{top:e,bottom:s,total:o,answer:o,tTop:d,tBot:m,tensSum:h,one:1},steps:[{visual_data:u,animation:"stack_columns",caption:"Line up ones, tens, hundreds. Start with the ones.",spoken:"We stack the numbers by place value — ones under ones, tens under tens, hundreds under hundreds. We always begin at the ones column, on the right."},{visual_data:u,animation:i?"carry_the_ten":"add_ones",caption:i?"The ones make a ten — carry it to the tens.":"Add the ones — under ten, so nothing carries.",spoken:i?`${capitalised(numberToWords(r.top))} ones and ${numberToWords(r.bottom)} ones make ${numberToWords(a)}. That is one ten and ${numberToWords(l)} ones — the ${numberToWords(l)} stays in the ones, and the ten carries across to the tens.`:`${capitalised(numberToWords(r.top))} ones and ${numberToWords(r.bottom)} ones make ${numberToWords(a)}. That is under ten, so we write it in the ones and nothing carries yet.`},{visual_data:u,animation:"complete_sum",caption:`${d} + ${m}${i?" + 1":""} = ${h}${f?" — carry a hundred":""}, then hundreds: ${o}.`,spoken:`${capitalised(g.sentences[1])}. ${capitalised(g.sentences[2])}. The total is ${numberToWordsLong(o)}${i||f?" — and it works because every carried one was really a whole ten, or hundred, kept in its proper column":""}.`}]})}},columnSubtraction={id:"AS.P5.M02.column-sub.v1",microSkillId:"AS.P5.M02",forms:["symbolic_calculation"],generate(n){const t=columnOperands(n,"subtract");if(!t)return null;const{top:e,bottom:s}=t;if(e<100||e>999||s<100||s>999||s>e)return null;const o=e-s;if(o!==answerInt(n))return null;const r=e%10,a=Math.floor(e/10)%10,i=Math.floor(e/100),l=s%10,u=Math.floor(s/10)%10,d=Math.floor(s/100),m=r<l,h=a<u+(m?1:0);if(i<d+(h?1:0))return null;let g;if(!m&&!h?g="zero":m&&!h?g="single":m&&h?g=a!==0?"cascade":"throughZero":g=null,g===null)return null;const b=r-l,x={type:"column_method",operation:"subtract",top:e,bottom:s},y=describeColumnSubtraction(e,s),E=g==="zero"?{animation:"subtract_ones",caption:"Subtract the ones, then work left.",spoken:`In the ones, take ${numberToWords(l)} away from ${numberToWords(r)} — that leaves ${numberToWords(b)}. There are enough ones, so no exchange is needed.`}:g==="throughZero"?{animation:"borrow_through_zero",caption:"Not enough ones, tens are empty — exchange a hundred first.",spoken:"There are not enough ones, and the tens digit is zero, so there is no ten to exchange yet. We exchange one hundred for ten tens first — then one of those tens becomes ten ones."}:{animation:"exchange_ten",caption:"Swap one ten for ten ones — same amount.",spoken:"There are not enough ones, so we exchange. We take one ten from the tens column and turn it into ten ones. The tens have one fewer, the ones have ten more — the number is the same amount, just regrouped so we can subtract."},k=g==="zero"?`Every column already had enough, so we subtract straight down — ones, then tens, then hundreds. The answer is ${numberToWordsLong(o)}.`:g==="single"?`${capitalised(y.sentences[0])}. ${capitalised(y.sentences[1])}. ${capitalised(y.sentences[2])}. The answer is ${numberToWordsLong(o)} — and it works because exchanging never changed how much we had, it only regrouped it.`:g==="cascade"?`${capitalised(y.sentences[0])}. The tens can't spare enough either, so we exchange again. ${capitalised(y.sentences[1])}. ${capitalised(y.sentences[2])}. The answer is ${numberToWordsLong(o)} — exchanging twice never changed how much we had, it only regrouped it.`:`${capitalised(y.sentences[0])}. ${capitalised(y.sentences[1])}. ${capitalised(y.sentences[2])}. The answer is ${numberToWordsLong(o)} — each exchange just regrouped the same amount, it never changed it.`,T=g==="zero"||g==="single"?`Now every column subtracts: ${o}.`:g==="cascade"?`Tens can't spare enough either — exchange again: ${o}.`:`Borrow from the hundreds, then subtract: ${o}.`;return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} − ${s}?`,computed:{top:e,bottom:s,result:o,answer:o},steps:[{visual_data:x,animation:"stack_columns",caption:"Line up the columns. Look at the ones first.",spoken:g==="zero"?"We stack them by place, ones under ones. Starting at the ones, we check whether there are enough to take the bottom ones away.":"We stack them by place, ones under ones. Starting at the ones, there are not enough to take the bottom ones away — but we are not stuck."},{visual_data:x,...E},{visual_data:{...x,keep_exchange:!0},animation:"complete_difference",caption:T,spoken:k}]})}},AS_CLAIM_ADD=/^(\w+) says (\d+) \+ (\d+) = (\d+)\. Is (?:he|she) right\?$/,AS_CLAIM_SUB=/^(\w+) says (\d+) - (\d+) = (\d+)\. Is (?:he|she) right\?$/;function claimCheckArithmetic(n,t,e,s){return{id:n,microSkillId:t,forms:["context_reasoning_judgement"],generate(o){const a=normaliseStem(o.stem).match(e);if(!a)return null;const[,i,l,u,d]=a,m=Number(l),h=Number(u),f=Number(d);if(m<100||m>999||h<100||h>999)return null;const g=s==="+"?m+h:m-h;if(g<0||g>999)return null;const b=o.correctAnswer.trim(),x=g===f;if(x&&!isAffirmative(b)||!x&&!isNegative(b))return null;const y=s==="+"?"add":"subtract",E=x?"=":"≠",k=s==="+"?"total":"answer",T={type:"column_method",operation:s==="+"?"add":"subtract",top:m,bottom:h,show_answer:!0,...s==="−"?{keep_exchange:!0}:{}},N=(s==="+"?describeColumnAddition(m,h):describeColumnSubtraction(m,h)).sentences.map(S=>capitalised(S)).join(". ")+".";return buildExample({generatorId:n,question:o,title:`Is it true that ${m} ${s} ${h} = ${f}?`,computed:{a:m,b:h,claimed:f,truth:g,answer:g},steps:[{visual_data:{type:"equation_display",terms:[m,s,h,"=","__"]},animation:"pulse_unknown",caption:"Work it out for yourself first.",spoken:`Before we trust the claim, let's ${y} the two numbers ourselves.`},{visual_data:T,animation:s==="+"?"complete_sum":"complete_difference",caption:`${m} ${s} ${h} = ${g}.`,spoken:`${N} So the real ${k} is ${numberToWordsLong(g)}.`},{visual_data:{type:"equation_display",terms:[f,E,g]},animation:"compare_claim",caption:`${f} ${E} ${g} — ${i} is ${x?"right":"wrong"}.`,spoken:`${i} said ${numberToWordsLong(f)}, and the real ${k} is ${numberToWordsLong(g)}. So ${i} is ${x?"right":"wrong"}.`}]})}}}const asP5M01ClaimCheck=claimCheckArithmetic("AS.P5.M01.claim-check.v1","AS.P5.M01",AS_CLAIM_ADD,"+"),asP5M02ClaimCheck=claimCheckArithmetic("AS.P5.M02.claim-check.v1","AS.P5.M02",AS_CLAIM_SUB,"−"),AS_WORD_ADD=/^A .+ raises £(\d+) on \w+ and £(\d+) on \w+\. How much money is raised in total\?$/,AS_WORD_SUB=/^There are (\d+) (\w+) for the school fair\. (\d+) \2 are sold in the morning\. How many \2 are left for the afternoon\?$/,asP5M01WordProblem={
71id:"AS.P5.M01.word-problem.v1",microSkillId:"AS.P5.M01",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(AS_WORD_ADD);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<100||e>999||s<100||s>999)return null;const o=e+s;if(o>999||o!==answerInt(n))return null;const r={a:e%10,b:s%10},a=r.a+r.b,i=a>=10,l={type:"column_method",operation:"add",top:e,bottom:s},u=describeColumnAddition(e,s),d=Math.floor(e/10)%10,m=Math.floor(s/10)%10,h=d+m+(i?1:0),f=h>=10;return buildExample({generatorId:this.id,question:n,title:`How much is £${e} and £${s} altogether?`,computed:{a:e,b:s,total:o,answer:o,onesA:r.a,onesB:r.b,onesSum:a,onesLeft:i?a-10:a,tTop:d,tBot:m,tensSum:h,one:1},steps:[{visual_data:l,animation:"stack_columns",caption:"Line up the two amounts by place value.",spoken:"We stack the two amounts by place value, ones under ones, to add them safely."},{visual_data:l,animation:i?"carry_the_ten":"add_ones",caption:i?`${r.a} and ${r.b} make ${a} — ${a-10} stays, 1 ten carries.`:`${r.a} and ${r.b} make ${a}.`,spoken:i?`${capitalised(numberToWords(r.a))} and ${numberToWords(r.b)} make ${numberToWords(a)} — that's one ten and ${numberToWords(a-10)} ones. The ${numberToWords(a-10)} stays in the ones, and one ten carries.`:`${capitalised(numberToWords(r.a))} and ${numberToWords(r.b)} make ${numberToWords(a)}. That is under ten, so nothing carries yet.`},{visual_data:l,animation:"complete_sum",caption:`${d} + ${m}${i?" + 1":""} = ${h}${f?" — carry a hundred":""}. Total raised: £${o}.`,spoken:`${capitalised(u.sentences[1])}. ${capitalised(u.sentences[2])}. The total raised is ${numberToWordsLong(o)} pounds.`}]})}},asP5M02WordProblem={id:"AS.P5.M02.word-problem.v1",microSkillId:"AS.P5.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(AS_WORD_SUB);if(!t)return null;const e=Number(t[1]),s=Number(t[3]);if(e<100||e>999||s<100||s>999||s>e)return null;const o=e-s;if(o!==answerInt(n))return null;const r=e%10,a=Math.floor(e/10)%10,i=s%10,l=Math.floor(s/10)%10,u=r<i,d=a-(u?1:0),m=d<l,h=m?d+10:d,f=h-l,g={type:"column_method",operation:"subtract",top:e,bottom:s},b=describeColumnSubtraction(e,s),x=u?a===0?"borrow_through_zero":"exchange_ten":"subtract_ones";return buildExample({generatorId:this.id,question:n,title:`How many are left from ${e} after ${s} are taken?`,computed:{top:e,bottom:s,result:o,answer:o,effTensTop:d,tensWorking:h,tensResult:f},steps:[{visual_data:g,animation:"stack_columns",caption:"Line up the columns. Look at the ones first.",spoken:"We stack the two amounts by place value, starting at the ones column."},{visual_data:g,animation:x,caption:u?a===0?"Not enough ones, tens are empty — borrow from the hundreds first.":"Not enough ones — exchange a ten.":"Subtract the ones, then work left.",spoken:u?a===0?"There are not enough ones, and the tens digit is zero, so we take a ten from the hundreds into the tens first — now the tens can lend one to the ones.":"There are not enough ones, so we exchange one ten from the tens column for ten more ones.":"There are enough ones, so we subtract straight down."},{visual_data:{...g,keep_exchange:!0},animation:"complete_difference",caption:m?d<0?`Tens are empty and already owe the ones — exchange a hundred: ${h} − ${l} = ${f}. Left over: ${o}.`:`${d} tens can't give ${l} — exchange a hundred: ${h} − ${l} = ${f}. Left over: ${o}.`:`Left over: ${o}.`,spoken:m?`${capitalised(b.sentences[1])}. ${capitalised(b.sentences[2])}. Working through every column, the amount left over is ${numberToWordsLong(o)}.`:`${capitalised(b.sentences[1])}. ${capitalised(b.sentences[2])}. The amount left over is ${numberToWordsLong(o)}.`}]})}},AS_COMPARE_ADD=/^(\w+) adds (\d+) and (\d+)\. (\w+) adds (\d+) and (\d+)\. Who gets the bigger total\?$/,AS_COMPARE_SUB=/^(\w+) has £(\d+) in savings and spends £(\d+)\. (\w+) has £(\d+) in savings and spends £(\d+)\. Who has more money left\?$/;function twoPersonCompare$1(n,t,e,s,o){return{id:n,microSkillId:t,forms:["comparison_statement"],generate(r){const a=normaliseStem(r.stem).match(e);if(!a)return null;const[,i,l,u,d,m,h]=a,f=Number(l),g=Number(u),b=Number(m),x=Number(h);if([f,g,b,x].some(N=>N<100||N>999))return null;const y=s==="+"?f+g:f-g,E=s==="+"?b+x:b-x;if(y<0||E<0||y===E)return null;
71const k=y>E?i:d;if(r.correctAnswer.trim()!==k)return null;const T=(N,S)=>s==="+"?{type:"column_method",operation:"add",top:N,bottom:S,show_answer:!0}:{type:"column_method",operation:"subtract",top:N,bottom:S,show_answer:!0,keep_exchange:!0},A=(N,S)=>{const M=s==="+"?describeColumnAddition(N,S).sentences.some(L=>L.includes("carry")):describeColumnSubtraction(N,S).borrows.some(Boolean);return s==="+"?M?"In columns, carrying where the ones or tens make ten or more":"In columns, adding each place":M?"In columns, exchanging where a column is short":"In columns, subtracting each place"};return buildExample({generatorId:n,question:r,title:`Who ${o} more: ${i} or ${d}?`,computed:{a1:f,b1:g,a2:b,b2:x,v1:y,v2:E,answer:Math.max(y,E)},steps:[{visual_data:T(f,g),animation:"write_answer",caption:`First, ${i}: ${f} ${s} ${g} = ${y}.`,spoken:`First we work out ${i}'s amount, on its own. ${A(f,g)}, that comes to ${numberToWordsLong(y)}.`},{visual_data:T(b,x),animation:"write_answer",caption:`Now, ${d}: ${b} ${s} ${x} = ${E}.`,spoken:`Now we work out ${d}'s amount, the very same way. ${A(b,x)}, that comes to ${numberToWordsLong(E)}.`},{visual_data:{type:"comparison",left:y,right:E,symbol:y>E?">":"<"},animation:"highlight_pattern",caption:`${i}: ${y} · ${d}: ${E} — ${k} has more.`,spoken:`Comparing ${numberToWordsLong(y)} and ${numberToWordsLong(E)}, ${k} comes out with more.`}]})}}}const asP5M01Compare=twoPersonCompare$1("AS.P5.M01.compare.v1","AS.P5.M01",AS_COMPARE_ADD,"+","adds up to"),asP5M02Compare=twoPersonCompare$1("AS.P5.M02.compare.v1","AS.P5.M02",AS_COMPARE_SUB,"−","has left"),AS_MISSING_HUNDREDS=/^A three-digit number has (\d+) tens? and (\d+) ones?, but its hundreds digit is missing\. Added to (\d+), the total is (\d+)\. What is the missing hundreds digit\?$/,AS_MISSING_HUNDREDS_MAKES=/^A three-digit number has (\d+) tens? and (\d+) ones?, but its hundreds digit is missing\. That number added to (\d+) makes (\d+)\. What is the missing hundreds digit\?$/,AS_MISSING_TENS=/^A three-digit number has (\d+) hundreds? and (\d+) ones?, but its tens digit is missing\. When (\d+) is taken away from it, (\d+) is left\. What is the missing tens digit\?$/,asP5M01MissingDigit={id:"AS.P5.M01.missing-hundreds.v1",microSkillId:"AS.P5.M01",forms:["missing_addend"],generate(n){const t=normaliseStem(n.stem).match(AS_MISSING_HUNDREDS)??normaliseStem(n.stem).match(AS_MISSING_HUNDREDS_MAKES);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]),a=r-o;if(a<100||a>999)return null;const i=Math.floor(a/100);return a%100!==e*10+s||i!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`A number plus ${o} makes ${r} — what is its hundreds digit?`,computed:{addend:o,total:r,whole:a,tens:e,ones:s,hundreds:i,answer:i},steps:[{visual_data:{type:"equation_display",terms:["__","+",o,"=",r]},animation:"reframe_subtraction",caption:`Work backwards: number + ${o} = ${r}.`,spoken:`A number plus ${numberToWordsLong(o)} makes ${numberToWordsLong(r)}. To find the missing number, we work backwards from the total: we take away what was added.`},{visual_data:{type:"column_method",operation:"subtract",top:r,bottom:o,show_answer:!0,keep_exchange:!0},animation:"write_answer",caption:`${r} − ${o} = ${a}.`,spoken:`${capitalised(numberToWordsLong(r))} take away ${numberToWordsLong(o)} gives the missing number, ${numberToWordsLong(a)}.`},{visual_data:{type:"equation_display",terms:[a,"=",i*100,"+",e*10,"+",s]},animation:"family",caption:`${a} has ${i} hundreds — the missing digit is ${i}.`,spoken:`Reading off the hundreds digit of ${numberToWordsLong(a)} gives the answer: ${numberToWords(i)}.`}]})}},asP5M02MissingDigit={id:"AS.P5.M02.missing-tens.v1",microSkillId:"AS.P5.M02",forms:["unknown_part_within_whole"],generate(n){const t=normaliseStem(n.stem).match(AS_MISSING_TENS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]),a=r+o;if(a<100||a>999)return null;const i=Math.floor(a/10)%10;return e*100+s!==a-i*10||i!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`A number minus ${o} leaves ${r} — what is its tens digit?`,computed:{subtractor:o,remainder:r,whole:a,hun
71dreds:e,ones:s,tens:i,answer:i},steps:[{visual_data:{type:"equation_display",terms:["__","−",o,"=",r]},animation:"reframe_subtraction",caption:`Work backwards: number − ${o} = ${r}.`,spoken:`A number take away ${numberToWordsLong(o)} leaves ${numberToWordsLong(r)}. To find the missing number, we work backwards from what was left.`},{visual_data:{type:"column_method",operation:"add",top:r,bottom:o,show_answer:!0},animation:"write_answer",caption:`${o} was taken away, so add it back: ${r} + ${o} = ${a}.`,spoken:`${capitalised(numberToWordsLong(o))} was taken away, so we put it back: ${numberToWordsLong(r)} plus ${numberToWordsLong(o)} rebuilds the number, ${numberToWordsLong(a)}.`},{visual_data:{type:"equation_display",terms:[a,"=",e*100,"+",i*10,"+",s]},animation:"family",caption:`${a} has ${i} ${i===1?"ten":"tens"} — the missing digit is ${i}.`,spoken:`Reading off the tens digit of ${numberToWordsLong(a)} gives the answer: ${numberToWords(i)}.`}]})}},TIMES=/^(\d+) (?:×|x|\*) (\d+) = __$/,timesTable={id:"MD.P3.M01.times.v1",microSkillId:"MD.P3.M01",forms:["fact_symbolic_retrieval","equation_completion"],generate(n){const t=matchInts(n.stem,TIMES);if(!t)return null;const[e,s]=t;if(e<2||s<2||e>10||s>10)return null;const o=e*s;if(o!==answerInt(n))return null;const r="🔵";if(e===4&&[4,7,8,9].includes(s)||s===4&&[7,9].includes(e)){const x=e===4?s:e,y=x*2;return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} × ${s}?`,computed:{a:e,b:s,unit:x,two:2,four:4,doubled:y,product:o,answer:o},steps:[{visual_data:{type:"objects_grid",emoji:r,count:x,group_size:x},animation:"show_base_fact",caption:`Start with ${x}; multiplying by 4 means double twice.`,spoken:`Start with one group of ${numberToWords(x)}. To make four equal groups, double it, then double again.`},{visual_data:{type:"objects_grid",emoji:r,count:y,group_size:x},animation:"duplicate_double",caption:`First double: ${x} + ${x} = ${y}.`,spoken:`Double ${numberToWords(x)} to get ${numberToWords(y)}. We now have two equal groups.`},{visual_data:{type:"objects_grid",emoji:r,count:o,group_size:x},animation:"duplicate_double",caption:`Double again: ${y} + ${y} = ${o}.`,spoken:`Double those two groups to make four. ${capitalised(numberToWords(y))} plus ${numberToWords(y)} is ${numberToWords(o)}. So ${numberToWords(e)} times ${numberToWords(s)} is ${numberToWords(o)}.`}]})}const i=e===8&&s===5?5:e===4&&s===3?4:[3,4,8,10].includes(s)?s:[3,4,8].includes(e)?e:s,l=o/i;if(i>10||l>12||o>100)return null;const u=l===8&&[4,5,8].includes(i)||l===6&&i===8,d=l===9&&i===8,m=u?l/2:d?10:l===9&&i===3?8:l>=6&&[3,4,8].includes(i)?5:l===5?4:Math.max(1,l-1),h=m*i,f=Math.abs(l-m),g=f*i,b={type:"objects_grid",emoji:r,count:o,group_size:i};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} × ${s}?`,computed:{a:e,b:s,groups:l,unit:i,baseGroups:m,baseTotal:h,extraGroups:f,extra:g,product:o,answer:o},steps:[{visual_data:{type:"objects_grid",emoji:r,count:h,group_size:i},animation:"build_rows",caption:`Start with ${m} groups of ${i}: ${h}.`,spoken:`Build ${numberToWords(m)} equal groups of ${numberToWords(i)}. Count in ${numberToWords(i)}s: ${Array.from({length:m},(x,y)=>numberToWords((y+1)*i)).join(", ")}. That gives us a known fact to build on.`},{visual_data:{...b,hide_subtotals:!0,hide_total:!0},animation:u?"duplicate_double":"build_rows",caption:u?`Double the ${m} groups.`:d?`Take away one group of ${i}.`:`Add ${f} more group${f===1?"":"s"} of ${i}.`,spoken:u?`Double those groups to make ${numberToWords(l)} groups altogether. Each group still has ${numberToWords(i)}.`:d?`We need one fewer group, so take away one group of ${numberToWords(i)} from ${numberToWords(h)}.`:`We need ${numberToWords(l)} groups, so add ${numberToWords(f)} more group${f===1?"":"s"} of ${numberToWords(i)} to our ${numberToWords(h)} counters.`},{visual_data:b,animation:"write_fact",caption:`${h} ${d?"−":"+"} ${g} = ${o}.`,spoken:`${capitalised(numberToWords(h))} ${d?"take away":"plus"} ${numberToWords(g)} makes ${numberToWords(o)}. So ${numberToWords(e)} times ${numberToWords(s)} is ${numberToWords(o)}.`}]})}};function timesArraySteps(n,t){if(n<2||n>12||t<2||t>12)return null;const e=n*t;if(e>144)return null;const s="🔵";return{product:e,steps:[{type:"objects_grid",emoji:s,count:2*t,group_size:t},{type:"objects_grid",emoji:s,count:e,group_size:t},{type:"objects_grid",emoji:s,count:e,group_size:n}]}}const MD_CLAIM=/^(\w+) says (\d+) (?:×|x|\*) (\d+) = (\d+)\. Is (?:he|she) right\?$/,mdP3M01ClaimCheck={id:"MD.P3.M01.claim-check.v1",microSkillId:"MD.P3.M01",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(MD_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r),u=Math.min(a,i),d=Math.max(a,i);if(u<2||u>12||d<2||d>12)return null;const m=u*d;if(m>144)return null;const h=m===l,f=n.correctAnswer.trim();if(h&&!isAffirmative(f)||!h&&!isNegative(f))return null;const g="🔵",b={type:"objects_grid",emoji:g,count:d,group_size:d},x={type:"objects_grid",emoji:g,count:m,group_size:d,hide_subtotals:!0,hide_total:!0},y={type:"objects_grid",emoji:g,count:m,group_size:d};return buildExample({generatorId:this.id,question:n,title:`Is ${a} × ${i} really ${l}?`,computed:{a,b:i,product:m,claimed:l,answer:m},steps:[{visual_data:b,animation:"build_rows",caption:"Before trusting the claim, let's build it.",spoken:"Before trusting the claim, let's build the array ourselves, one group at a time — starting with the first group."},{visual_data:x,animation:"write_fact",caption:`Build ${a} × ${i} counter by counter.`,spoken:"Every group is the same size. We build the whole array before counting it."},{visual_data:y,animation:"write_fact",caption:`${a} × ${i} = ${m} — ${e} is ${h?"right":"wrong"}.`,spoken:`Counting every counter, ${numberToWords(a)} times ${numberToWords(i)} is really ${numberToWordsLong(m)}. So ${e} is ${h?"right":"wrong"}.`}]})}},MD_WORD=/^Each (\w+) has (\d+) (\w+)\. How many \3 do (\d+) \1s? have altogether\?$/,mdP3M01WordProblem={
71id:"MD.P3.M01.word-problem.v1",microSkillId:"MD.P3.M01",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MD_WORD);if(!t)return null;const e=Number(t[2]),s=Number(t[4]),o=t[3],r=timesArraySteps(Math.min(s,e),Math.max(s,e));if(!r)return null;const{product:a}=r;if(a!==answerInt(n))return null;const l={type:"objects_grid",emoji:"🔵",count:e,group_size:e};return buildExample({generatorId:this.id,question:n,title:`${s} groups of ${e} — how many ${o}?`,computed:{groups:s,per:e,product:a,answer:a},steps:[{visual_data:l,animation:"build_rows",caption:`${e} ${o} each.`,spoken:`Every group has the same number of ${o}. That is what "each" tells us.`},{visual_data:{...r.steps[1],hide_subtotals:!0,hide_total:!0},animation:"write_fact",caption:"Copy the group, again and again.",spoken:"More groups arrive, every one the very same size as the first."},{visual_data:r.steps[1],animation:"write_fact",caption:`${s} × ${e} = ${a} ${o}.`,spoken:`${capitalised(numberToWords(s))} groups of ${numberToWords(e)} makes ${numberToWordsLong(a)} altogether.`}]})}},MD_COMPARE=/^Which is greater: (\d+) (?:×|x|\*) (\d+) or (\d+) (?:×|x|\*) (\d+)\?$/,mdP3M01Compare={id:"MD.P3.M01.compare.v1",microSkillId:"MD.P3.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(MD_COMPARE);if(!t)return null;const[e,s,o,r]=t.slice(1).map(Number);if([e,s,o,r].some(m=>m<2||m>12))return null;const a=e*s,i=o*r;if(a===i)return null;const l=`${e} × ${s}`,u=`${o} × ${r}`,d=a>i?l:u;return d!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:`Which is greater: ${l} or ${u}?`,computed:{a1:e,b1:s,a2:o,b2:r,p1:a,p2:i,answer:Math.max(a,i)},steps:[{visual_data:{type:"objects_grid",emoji:"🔵",count:a,group_size:s},animation:"write_fact",caption:`First, ${l} = ${a}.`,spoken:`First we work out ${numberToWords(e)} groups of ${numberToWords(s)}, on its own — that makes ${numberToWordsLong(a)}.`},{visual_data:{type:"objects_grid",emoji:"🟠",count:i,group_size:r},animation:"write_fact",caption:`Now, ${u} = ${i}.`,spoken:`Now we work out ${numberToWords(o)} groups of ${numberToWords(r)} the very same way — that makes ${numberToWordsLong(i)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:"compare_totals",caption:`${l} = ${a}, ${u} = ${i} — ${d} is greater.`,spoken:`Now compare the two totals: ${numberToWordsLong(Math.max(a,i))} is more than ${numberToWordsLong(Math.min(a,i))}. So ${a>i?`${numberToWords(e)} times ${numberToWords(s)}`:`${numberToWords(o)} times ${numberToWords(r)}`} is the greater one.`}]})}},MD_MISSING_FIRST=/^__ (?:×|x|\*) (\d+) = (\d+)\. What number completes it\?$/,mdP3M01MissingFirst={id:"MD.P3.M01.missing-first.v1",microSkillId:"MD.P3.M01",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem).match(MD_MISSING_FIRST);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<2||e>12||s<1||s>144||s%e!==0)return null;const o=s/e;if(o<1||o>12||o!==answerInt(n))return null;const r="🔵",a={type:"objects_grid",emoji:r,count:e,group_size:e},i={type:"objects_grid",emoji:r,count:s,group_size:e,hide_subtotals:!0,hide_total:!0},l={type:"objects_grid",emoji:r,count:s,group_size:e};return buildExample({generatorId:this.id,question:n,title:`What times ${e} makes ${s}?`,computed:{b:e,product:s,missing:o,answer:o},steps:[{visual_data:a,animation:"build_rows",caption:`We know each group is ${e}. How many groups make ${s}?`,spoken:`We know the size of each group, but not how many groups we need to reach ${numberToWordsLong(s)}.`},{visual_data:i,animation:"write_fact",caption:`Build groups of ${e} until we reach ${s}.`,spoken:`We keep adding groups of ${numberToWords(e)}, skip counting as we go, until we land exactly on ${numberToWordsLong(s)}.`},{visual_data:l,animation:"write_fact",caption:`${o} × ${e} = ${s}.`,spoken:`Counting the groups, it took ${numberToWords(o)} groups of ${numberToWords(e)} to reach ${numberToWordsLong(s)} — so the missing number is ${numberToWords(o)}.`}]})}},DIVIDE=/^(\d+) (?:÷|\/) (\d+) = __$/,divisionFact={id:"MD.P3.M03.fact-family.v1",microSkillId:"MD.P3.M03",forms:["equation_completion","fact_symbolic_retrieval"],generate(n){const t=matchInts(n.stem,DIVIDE);if(!t)return null;const[e,s]=t;if(s<2||s>10||e<s||e>100||e%s!==0)return null;const o=e/s;if(o<1||o>12||o!==answerInt(n))return null;const r={type:"fact_family",rows:s,cols:o};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} ÷ ${s}?`,computed:{D:e,V:s,Q:o,answer:o},steps:[{visual_data:r,animation:"array_mult",caption:`${s} rows of ${o} = ${e}, so ${s} × ${o} = ${e}.`,spoken:`${capitalised(numberToWords(s))} rows of ${numberToWords(o)} dots. ${capitalised(numberToWords(s))} times ${numberToWords(o)} makes ${numberToWordsLong(e)} — that is a times fact we might already know.`},{visual_data:r,animation:"reveal_div",caption:`Share the same ${e} into ${s} rows → ${e} ÷ ${s} = ${o}.`,spoken:`Now share the very same ${numberToWordsLong(e)} dots into ${numberToWords(s)} equal rows. Each row holds ${numberToWords(o)}, so ${numberToWordsLong(e)} divided by ${numberToWords(s)} is ${numberToWords(o)} — the same three numbers rearranged.`},{visual_data:r,animation:"family",caption:`Know ${s} × ${o} → know ${e} ÷ ${s} = ${o}.`,spoken:`Because ${numberToWords(s)} ${numberToWords(o)}s make ${numberToWordsLong(e)}, ${numberToWordsLong(e)} shared into ${numberToWords(s)} is ${numberToWords(o)}. Knowing the times fact tells you the division — one array, two facts.`}]})}};function divFactSteps(n,t){if(t<2||t>10||n<t||n>100||n%t!==0)return null;const e=n/t;return e<1||e>12?null:{Q:e,vd:{type:"fact_family",rows:t,cols:e}}}const MD_DIV_CLAIM=/^(\w+) says (\d+) (?:÷|\/) (\d+) = (\d+)\. Is (?:he|she) right\?$/,mdP3M03ClaimCheck={id:"MD.P3.M03.claim-check.v1",microSkillId:"MD.P3.M03",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(MD_DIV_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r),u=divFactSteps(a,i);if(!u)return null;const{Q:d}=u,m=d===l,h=n.correctAnswer.trim();if(m&&!isAffirmative(h)||!m&&!isNegative(h))return null;const f=i*l,g=b=>({type:"equation_display",terms:b});return buildExample({generatorId:this.id,question:n,title:`Is ${a} ÷ ${i} really ${l}?`,computed:{D:a,V:i,Q:d,claimed:l,claimedProduct:f,answer:d},steps:[{visual_data:g([String(i),"×",String(l),"=","__"]),animation:"pulse_unknown",caption:`Test the claim: ${i} × ${l}?`,spoken:`Before trusting the claim, let's test it — what does ${numberToWords(i)} times ${numberToWords(l)} really make?`},{visual_data:g([String(i),"×",String(l),"=",String(f)]),animation:"write_answer",caption:`${i} × ${l} = ${f}.`,spoken:m?`${capitalised(numberToWords(i))} times ${numberToWords(l)} makes ${numberToWordsLong(f)} — exactly ${numberToWordsLong(a)}, so the claim checks out so far.`:`${capitalised(numberToWords(i))} times ${numberToWords(l)} makes ${numberToWordsLong(f)}, not ${numberToWordsLong(a)} — so the claim doesn't hold.`},{visual_data:g([String(a),"÷",String(i),"=",String(d)]),animation:"family",caption:`${a} ÷ ${i} = ${d} — ${e} is ${m?"right":"wrong"}.`,spoken:`${capitalised(numberToWordsLong(a))}
71 divided by ${numberToWords(i)} is really ${numberToWords(d)}${m?"":`, not ${numberToWordsLong(l)}`}. So ${e} is ${m?"right":"wrong"}.`}]})}},MD_SHARE_WORD=/^(\d+) (\w+) are shared equally between (\d+) friends\. How many \2 does each friend get\?$/,mdP3M03ShareWord={id:"MD.P3.M03.share-word.v1",microSkillId:"MD.P3.M03",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(MD_SHARE_WORD);if(!t)return null;const e=Number(t[1]),s=t[2],o=Number(t[3]),r=divFactSteps(e,o);if(!r)return null;const{Q:a}=r;if(a!==answerInt(n))return null;const i="🔵",l={type:"objects_grid",emoji:i,count:e,group_size:o,scatter:!0},u={type:"objects_grid",emoji:i,count:e,group_size:a,mode:"share",hide_subtotals:!0,hide_total:!0},d={type:"objects_grid",emoji:i,count:e,group_size:a,mode:"share"};return buildExample({generatorId:this.id,question:n,title:`${e} ${s} shared between ${o} friends — how many each?`,computed:{D:e,V:o,Q:a,answer:a},steps:[{visual_data:l,animation:"scatter_show",caption:`${e} ${s}, all together.`,spoken:`${capitalised(numberToWordsLong(e))} ${s}, all together. We're going to share them fairly among ${numberToWords(o)} friends.`},{visual_data:u,animation:"share_contrast",caption:`Deal one round at a time into ${o} equal groups.`,spoken:`We deal them out fairly, round by round, into ${numberToWords(o)} equal groups, until every one has gone.`},{visual_data:d,animation:"family",caption:`${e} ÷ ${o} = ${a} each.`,spoken:`Each group holds ${numberToWords(a)}, so every friend gets ${numberToWords(a)} ${s} each.`}]})}},MD_DIV_COMPARE=/^Which division has the greater answer: (\d+) (?:÷|\/) (\d+) or (\d+) (?:÷|\/) (\d+)\?$/,mdP3M03Compare={id:"MD.P3.M03.compare.v1",microSkillId:"MD.P3.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(MD_DIV_COMPARE);if(!t)return null;const[e,s,o,r]=t.slice(1).map(Number),a=divFactSteps(e,s),i=divFactSteps(o,r);if(!a||!i||a.Q===i.Q)return null;const l=`${e} ÷ ${s}`,u=`${o} ÷ ${r}`,d=a.Q>i.Q?l:u;return d!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:`Which division has the greater answer: ${l} or ${u}?`,computed:{D1:e,V1:s,D2:o,V2:r,q1:a.Q,q2:i.Q,answer:Math.max(a.Q,i.Q)},steps:[{visual_data:a.vd,animation:"reveal_div",caption:`First, ${l} = ${a.Q}.`,spoken:`First we work out ${numberToWordsLong(e)} shared into ${numberToWords(s)} rows — that gives ${numberToWords(a.Q)}.`},{visual_data:i.vd,animation:"reveal_div",caption:`Now, ${u} = ${i.Q}.`,spoken:`Now we work out ${numberToWordsLong(o)} shared into ${numberToWords(r)} rows the very same way — that gives ${numberToWords(i.Q)}.`},{visual_data:{type:"comparison",left:a.Q,right:i.Q,symbol:a.Q>i.Q?">":"<"},animation:"compare_totals",caption:`${l} = ${a.Q}, ${u} = ${i.Q} — ${d} is greater.`,spoken:`Now compare the two answers: ${numberToWords(Math.max(a.Q,i.Q))} is more than ${numberToWords(Math.min(a.Q,i.Q))}. So ${a.Q>i.Q?`${numberToWordsLong(e)} divided by ${numberToWords(s)}`:`${numberToWordsLong(o)}
71 divided by ${numberToWords(r)}`} is the greater one.`}]})}},MD_MISSING_DIVISOR=/^(\d+) (?:÷|\/) __ = (\d+)\. What number goes in the gap\?$/,mdP3M03MissingDivisor={id:"MD.P3.M03.missing-divisor.v1",microSkillId:"MD.P3.M03",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem).match(MD_MISSING_DIVISOR);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(s<1||s>12||e<1||e>100||e%s!==0)return null;const o=e/s;if(o<2||o>10||o!==answerInt(n))return null;const r="🔵",a={type:"objects_grid",emoji:r,count:s,group_size:s},i={type:"objects_grid",emoji:r,count:e,group_size:s,hide_subtotals:!0,hide_total:!0},l={type:"objects_grid",emoji:r,count:e,group_size:s};return buildExample({generatorId:this.id,question:n,title:`${e} divided by what gives ${s}?`,computed:{D:e,Q:s,V:o,answer:o},steps:[{visual_data:a,animation:"build_rows",caption:`We know each group is ${s}. How many groups make ${e}?`,spoken:"We know the size of each group, but not how many groups there are."},{visual_data:i,animation:"write_fact",caption:`Build groups of ${s} until we reach ${e}.`,spoken:`We keep adding groups of ${numberToWords(s)}, until we land exactly on ${numberToWordsLong(e)}.`},{visual_data:l,animation:"family",caption:`${e} ÷ ${o} = ${s}.`,spoken:`It took ${numberToWords(o)} groups of ${numberToWords(s)} to reach ${numberToWordsLong(e)}, so the missing number is ${numberToWords(o)}.`}]})}},REMAINDER_CONTEXT=/^Put (\d+)(?: [a-z]+)? into groups of (\d+)\. How many (?:[a-z]+ )?are left over\?$/,REMAINDER_SYMBOLIC=/^(\d+) (?:÷|\/) (\d+) has a remainder of __$/,REMAINDER_BAGS=/^There are (\d+) [a-z]+ (?:put into|shared into) bags of (\d+)\. How many [a-z]+ are left over\?$/,remainder$1={id:"MD.P3.M04.remainder.v1",microSkillId:"MD.P3.M04",forms:["context_calculation","symbolic_numeric_entry"],generate(n){const t=matchInts(n.stem,REMAINDER_CONTEXT)??matchInts(n.stem,REMAINDER_SYMBOLIC)??matchInts(n.stem,REMAINDER_BAGS);if(!t)return null;const[e,s]=t;if(s<2||s>12||e<1||e>60)return null;const o=Math.floor(e/s),r=e%s;if(o<1||o>10||r<1||r!==answerInt(n))return null;const a="🟠",i={type:"objects_grid",emoji:a,count:e,group_size:s,scatter:!0},l={type:"objects_grid",emoji:a,count:e,group_size:s,scatter:!0,groups_formed:o};return buildExample({generatorId:this.id,question:n,title:`How many are left over when we put ${e} into groups of ${s}?`,computed:{N:e,G:s,groups:o,remainder:r,answer:r},steps:[{visual_data:i,animation:"scatter_show",caption:`${e} dots. Let's make groups of ${s}.`,spoken:`${capitalised(numberToWordsLong(e))} dots, scattered about. Let's make groups of ${numberToWords(s)} and see what is left.`},{visual_data:l,animation:"ring_groups",caption:`${o} full groups made. ${r} left over.`,spoken:`One ring at a time, we make ${numberToWords(o)} full groups of ${numberToWords(s)}. ${capitalised(numberToWords(r))} ${r===1?"dot is":"dots are"} left outside — not enough to make another full group.`},{visual_data:l,animation:"remainder_reveal",caption:`${o} groups, remainder ${r}: ${e} ÷ ${s} = ${o} r${r}.`,spoken:`${capitalised(numberToWords(o))} full groups of ${numberToWords(s)}, with ${numberToWords(r)} left over. The remainder is always smaller than the group size — ${numberToWords(r)} is less than ${numberToWords(s)}.`}]})}};function remainderParts(n,t){if(t<2||t>12||n<1||n>60)return null;const e=Math.floor(n/t),s=n%t;return e<1||e>10?null:{groups:e,r:s}}const MD_REM_CLAIM=/^(\w+) says (\d+) (?:÷|\/) (\d+) = (\d+) remainder (\d+)\. Is (?:he|she) right\?$/,MD_REM_CLAIM_WORDS=/^(\w+) shares (\d+) [a-z]+ into groups of (\d+)\. (?:He|She) says there (?:are|is) (\d+) full groups? with (\d+) left over\. Is (?:he|she) right\?$/,mdP3M04ClaimCheck={id:"MD.P3.M04.claim-check.v1",microSkillId:"MD.P3.M04",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(MD_REM_CLAIM)??normaliseStem(n.stem).match(MD_REM_CLAIM_WORDS);if(!t)return null;const[,e,s,o,r,a]=t,i=Number(s),l=Number(o),u=Number(r),d=Number(a),m=remainderParts(i,l);if(!m)return null;const{groups:h,r:f}=m,g=h===u&&f===d,b=n.correctAnswer.trim();if(g&&!isAffirmative(b)||!g&&!isNegative(b))return null;const x="🟠",y={type:"objects_grid",emoji:x,count:i,group_size:l,scatter:!0},E={type:"objects_grid",emoji:x,count:i,group_size:l,scatter:!0,groups_formed:h};return buildExample({generatorId:this.id,question:n,title:`Is ${i} ÷ ${l} really ${u} remainder ${d}?`,computed:{N:i,G:l,groups:h,r:f,claimedGroups:u,claimedR:d,answer:h},steps:[{visual_data:y,animation:"scatter_show",caption:"Before trusting the claim, let's check.",spoken:"Before trusting the claim, let's ring the groups ourselves and see what is really left over."},{visual_data:E,animation:"ring_groups",caption:`${h} full groups made, ${f} left over.`,spoken:`Ringing full groups one at a time, we make ${numberToWords(h)} full groups with ${numberToWords(f)} left outside.`},{visual_data:E,animation:"remainder_reveal",caption:`${i} ÷ ${l} = ${h} r${f} — ${e} is ${g?"right":"wrong"}
71.`,spoken:`So the true answer is ${numberToWords(h)} remainder ${numberToWords(f)}. That means ${e} is ${g?"right":"wrong"}.`}]})}},MD_ROUND_UP=/^A minibus seats (\d+) children\. (\d+) children need a lift to a football match\. How many minibuses are needed so nobody is left behind\?$/,mdP3M04RoundUp={id:"MD.P3.M04.round-up.v1",microSkillId:"MD.P3.M04",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MD_ROUND_UP);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=remainderParts(s,e);if(!o)return null;const{groups:r,r:a}=o;if(a<1)return null;const i=r+1;if(i!==answerInt(n))return null;const l={type:"objects_grid",emoji:"🟠",count:s,group_size:e,scatter:!0},u={type:"objects_grid",emoji:"🟠",count:s,group_size:e,scatter:!0,groups_formed:r},d={type:"objects_grid",emoji:"🟠",count:s,group_size:e,scatter:!0,groups_formed:r,show_extra_group:!0,total_label:`${i} buses needed (round up).`};return buildExample({generatorId:this.id,question:n,title:`How many minibuses for ${s} children, ${e} seats each?`,computed:{seats:e,children:s,groups:r,r:a,one:1,busesNeeded:i,answer:i},steps:[{visual_data:l,animation:"scatter_show",caption:`${s} children need a lift.`,spoken:`${capitalised(numberToWordsLong(s))} children, scattered about. Let's fill minibuses of ${numberToWords(e)} seats.`},{visual_data:u,animation:"ring_groups",caption:`${r} full minibuses, ${a} children left over.`,spoken:`${capitalised(numberToWords(r))} minibuses fill up completely, but ${numberToWords(a)} ${a===1?"child is":"children are"} left with no seats yet.`},{visual_data:d,animation:"remainder_reveal",caption:`${r} full buses + 1 more for the rest → ${i} buses needed.`,spoken:`Those left over still need a bus of their own, so we round up: ${numberToWords(i)} minibuses are needed, not just ${numberToWords(r)} — nobody can be left behind.`}]})}},MD_REM_COMPARE=/^Which division leaves the bigger remainder: (\d+) (?:÷|\/) (\d+) or (\d+) (?:÷|\/) (\d+)\?$/,mdP3M04Compare={id:"MD.P3.M04.compare.v1",microSkillId:"MD.P3.M04",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(MD_REM_COMPARE);if(!t)return null;const[e,s,o,r]=t.slice(1).map(Number),a=remainderParts(e,s),i=remainderParts(o,r);if(!a||!i||a.r===i.r)return null;const l=`${e} ÷ ${s}`,u=`${o} ÷ ${r}`,d=a.r>i.r?l:u;
71if(d!==n.correctAnswer.trim())return null;const m="🟠",h={type:"objects_grid",emoji:m,count:e,group_size:s,scatter:!0,groups_formed:a.groups},f={type:"objects_grid",emoji:m,count:o,group_size:r,scatter:!0,groups_formed:i.groups};return buildExample({generatorId:this.id,question:n,title:`Which leaves the bigger remainder: ${l} or ${u}?`,computed:{N1:e,G1:s,N2:o,G2:r,r1:a.r,r2:i.r,q1:a.groups,q2:i.groups,answer:Math.max(a.r,i.r)},steps:[{visual_data:h,animation:"ring_groups",caption:`${l}: remainder ${a.r}.`,spoken:`To compare remainders, we find each one first. ${capitalised(numberToWordsLong(e))} into groups of ${numberToWords(s)} leaves ${numberToWords(a.r)} over.`},{visual_data:f,animation:"ring_groups",caption:`${u}: remainder ${i.r}.`,spoken:`${capitalised(numberToWordsLong(o))} into groups of ${numberToWords(r)} leaves ${numberToWords(i.r)} over.`},{visual_data:{type:"comparison",left:a.r,right:i.r,symbol:a.r>i.r?">":"<"},animation:"compare_totals",caption:`${l} = ${a.groups} r${a.r}, ${u} = ${i.groups} r${i.r} — ${d} leaves more.`,spoken:`${capitalised(numberToWordsLong(e))} divided by ${numberToWords(s)} is ${numberToWords(a.groups)} remainder ${numberToWords(a.r)}, and ${numberToWordsLong(o)}
71 divided by ${numberToWords(r)} is ${numberToWords(i.groups)} remainder ${numberToWords(i.r)}. Now compare what is left over: ${numberToWords(Math.max(a.r,i.r))} is more than ${numberToWords(Math.min(a.r,i.r))}. So ${a.r>i.r?`${numberToWordsLong(e)} divided by ${numberToWords(s)}`:`${numberToWordsLong(o)} divided by ${numberToWords(r)}`} leaves the bigger amount over.`}]})}},MD_REVERSE=/^When a number is divided by (\d+), the answer is (\d+) remainder (\d+)\. What is the number\?$/,MD_REVERSE_WORDS=/^A number is shared into groups of (\d+)\. There (?:are|is) (\d+) full groups? and (\d+) left over\. What is the number\?$/,mdP3M04Reverse={id:"MD.P3.M04.reverse.v1",microSkillId:"MD.P3.M04",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem).match(MD_REVERSE)??normaliseStem(n.stem).match(MD_REVERSE_WORDS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(e<2||e>12||s<1||s>10||o<0||o>=e)return null;const r=s*e+o;if(r!==answerInt(n))return null;const a=s*e,i="🟠",l={type:"objects_grid",emoji:i,count:e,group_size:e,scatter:!0,groups_formed:1},u={type:"objects_grid",emoji:i,count:a,group_size:e,scatter:!0,groups_formed:s},d={type:"objects_grid",emoji:i,count:r,group_size:e,scatter:!0,groups_formed:s};return buildExample({generatorId:this.id,question:n,title:`Divided by ${e}, the answer is ${s} remainder ${o} — what's the number?`,computed:{G:e,groups:s,r:o,groupsProduct:a,N:r,answer:r},steps:[{visual_data:l,animation:"show_base_fact",caption:`First rebuild the full groups: ${s} × ${e}.`,spoken:`To undo a division-with-remainder, we first rebuild the whole groups — starting with one group of ${numberToWords(e)}.`},{visual_data:u,animation:"write_multiplication",caption:`${s} × ${e} = ${a}.`,spoken:`${capitalised(numberToWords(s))} times ${numberToWords(e)} makes ${numberToWordsLong(a)} — that is every full group put back together.`},{visual_data:d,animation:"write_answer",caption:`${a} + ${o} = ${r}.`,spoken:`Add the ${numberToWords(o)} left over to the ${numberToWordsLong(a)} in full groups. That gives the original number: ${numberToWordsLong(r)}.`}]})}},PARTITION_A=/^(\d+) = (\d+) \+ __$/,PARTITION_B=/^(\d+) = (\d+) \+ __ \+ (\d+)$/,partition$3={id:"NPV.P5.M02.partition.v1",microSkillId:"NPV.P5.M02",forms:["partition_equation"],generate(n){const t=matchInts(n.stem,PARTITION_A);if(t){const[s,o]=t;if(s<100||s>999||o%100!==0||o!==Math.floor(s/100)*100)return null;const r=s-o;return r<0||r!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`How can we split ${s}?`,computed:{N:s,hundreds:o,rest:r,answer:r},steps:[{visual_data:{type:"part_whole",whole:s,parts:[o,null]},animation:"split_one_way",caption:`${s} is ${o} and some more.`,spoken:`Let's split ${numberToWordsLong(s)} into parts. We start with the hundreds, because they are the biggest part: ${numberToWordsLong(o)}. There is some more left over after that.`},{visual_data:{type:"equation_display",terms:[s,"−",o,"=","__"]},animation:"regroup_split",caption:`Take away the hundreds: ${s} − ${o}.`,spoken:`The missing part is whatever is left once the hundreds are gone, so we take the hundreds away. ${capitalised(numberToWordsLong(s))} take away ${numberToWordsLong(o)}.`},{visual_data:{type:"part_whole",whole:s,parts:[o,r]},animation:"flexible_split",caption:`${s} = ${o} + ${r}.`,spoken:`That leaves ${numberToWordsLong(r)}, so the missing part is ${numberToWordsLong(r)}. ${capitalised(numberToWordsLong(s))} is ${numberToWordsLong(o)} plus ${numberToWordsLong(r)}. Splitting a number never changes its value, only how the parts are grouped.`}]})}const e=matchInts(n.stem,PARTITION_B);if(e){const[s,o,r]=e;if(s<100||s>999||o%100!==0||o!==Math.floor(s/100)*100||r<0||r>9||r!==s%10)return null;const a=s-o-r;return a<0||a%10!==0||a!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`How can we split ${s}?`,computed:{N:s,hundreds:o,ones:r,tens:a,tensDigit:a/10,ten:10,answer:a},steps:[{visual_data:{type:"equation_display",terms:[s,"=",o,"+","__","+",r]},animation:"pulse_unknown",caption:`${s} = ${o} + ? + ${r}.`,spoken:`${capitalised(numberToWordsLong(s))} is made of hundreds, tens and ones. We know the hundreds are ${numberToWordsLong(o)} and the ones are ${numberToWords(r)} — what are the tens?`},{visual_data:{type:"place_value_chart",digits:[o/100,a/10,r],highlight:1,values:{1:"?"}},animation:"highlight_tens",caption:`The tens digit is ${a/10}: ${a/10} tens.`,spoken:`Look at the tens column. Its digit is ${numberToWords(a/10)}, so there are ${numberToWords(a/10)} tens. ${a===0?"Zero means there are no tens in this number.":"Each counter in this column stands for ten."}`},{visual_data:{type:"equation_display",terms:[s,"=",o,"+",a,"+",r]},animation:"write_answer",caption:`${s} = ${o} + ${a} + ${r}.`,spoken:`The tens are ${numberToWordsLong(a)}. ${capitalised(numberToWordsLong(s))} is ${numberToWordsLong(o)} plus ${numberToWordsLong(a)} plus ${numberToWords(r)}. The digit tells us how many of a place there are.`}]})}return null}},NPV302_CLAIM=/^(\w+) says (\d+) can be split as (\d+) hundreds?, (\d+) tens? and (\d+) ones? — and also as (\d+) hundreds?, (\d+) tens? and (\d+) ones?\. Is (?:he|she) right\?$/,npvP5M02Claim={id:"NPV.P5.M02.claim-check.v1",microSkillId:"NPV.P5.M02",forms:["context_reasoning_judgement"],generate(n){const t=normaliseStem(n.stem).match(NPV302_CLAIM);if(!t)return null;const[,e,s,o,r,a,i,l,u]=t,d=Number(s),m=Number(o),h=Number(r),f=Number(a),g=Number(i),b=Number(l),x=Number(u);
71if(d<100||d>999)return null;const y=m*100+h*10+f,E=g*100+b*10+x,k=y===d&&E===d,T=n.correctAnswer.trim();if(k&&!isAffirmative(T)||!k&&!isNegative(T))return null;const A=y!==d?y:E,N=S=>S>9?`${numberToWordsLong(S)} tens make ${numberToWordsLong(S*10)}, so `:"";return buildExample({generatorId:this.id,question:n,title:`Do both splits make ${d}?`,computed:{N:d,v1:y,v2:E,h1x100:m*100,t1x10:h*10,h2x100:g*100,t2x10:b*10,answer:d},steps:[{visual_data:{type:"equation_display",terms:[m*100,"+",h*10,"+",f,"=",y]},animation:"write_answer",caption:`First split: ${m*100} + ${h*10} + ${f} = ${y}.`,spoken:`First, add up the first split's hundreds, tens and ones. ${N(h)?`${capitalised(N(h))}altogether`:"Altogether"} that makes ${numberToWordsLong(y)}.`},{visual_data:{type:"equation_display",terms:[g*100,"+",b*10,"+",x,"=",E]},animation:"write_answer",caption:`Second split: ${g*100} + ${b*10} + ${x} = ${E}.`,spoken:`Now add up the second split the same way. ${N(b)?`${capitalised(N(b))}altogether`:"Altogether"} that makes ${numberToWordsLong(E)}.`},{visual_data:{type:"equation_display",terms:[y,"=",E]},animation:k?"family":"compare_claim",caption:k?`Both splits make ${d} — ${e} is right.`:`${A} does not make ${d} — ${e} is wrong.`,spoken:k?`Both splits total ${numberToWordsLong(d)}, so ${e} is right — a number can be split more than one way.`:`One split does not total ${numberToWordsLong(d)}, so ${e} is wrong.`}]})}},NPV302_WORD=/^A warehouse counts its (\w+) in boxes: (\d+) boxes? of 100, (\d+) boxes? of 10, and (\d+) loose \1\. How many \1 are there in total\?$/,npvP5M02Word={id:"NPV.P5.M02.word-problem.v1",microSkillId:"NPV.P5.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(NPV302_WORD);if(!t)return null;const e=Number(t[2]),s=Number(t[3]),o=Number(t[4]);if(e<1||e>9||s<0||s>9||o<0||o>20)return null;const r=e*100+s*10+o;if(r!==answerInt(n))return null;const a=e*100+s*10;return buildExample({generatorId:this.id,question:n,title:`How many altogether: ${e} boxes of 100, ${s} boxes of 10, and ${o} loose?`,computed:{h:e,t:s,o,subtotal:a,total:r,answer:r},steps:[{visual_data:{type:"equation_display",terms:[e*100,"+",s*10,"+",o,"=","__"]},animation:"pulse_unknown",caption:"Add the boxes: hundreds, then tens, then ones.",spoken:"Each box of a hundred, each box of ten, and each loose one all add together."},{visual_data:{type:"equation_display",terms:[a,"+",o,"=","__"]},animation:"reframe_subtraction",caption:`${a} from the boxes, then the loose ones.`,spoken:`The boxes alone make ${numberToWordsLong(a)}. Now add the loose ones.`},{visual_data:{type:"equation_display",terms:[a,"+",o,"=",r]},animation:"write_answer",caption:`${a} + ${o} = ${r} in total.`,spoken:`Altogether that makes ${numberToWordsLong(r)}.`}]})}},NPV303_COMPARE=/^(\w+)'s (\w+) collection is (\d+) hundreds?, (\d+) tens? and (\d+) ones?\. (\w+)'s collection is (\d+) hundreds?, (\d+) tens? and (\d+) ones?\. Who has more \w+, or are they equal\?$/,npvP5M02Compare={id:"NPV.P5.M02.compare.v1",microSkillId:"NPV.P5.M02",forms:["context_reasoning_judgement"],generate(n){const t=normaliseStem(n.stem).match(NPV303_COMPARE);if(!t)return null;const[,e,,s,o,r,a,i,l,u]=t,d=Number(s),m=Number(o),h=Number(r),f=Number(i),g=Number(l),b=Number(u),x=d*100+m*10+h,y=f*100+g*10+b,E=n.correctAnswer.trim(),k=x===y;if(k){if(!/equal/i.test(E))return null}else{const A=x>y?e:a;if(E!==A)return null}const T=(A,N)=>N>=10?`${numberToWordsLong(A*10)} and ${numberToWordsLong(N)} ones regroups: ${numberToWordsLong(N)} ones is ${numberToWordsLong(Math.floor(N/10))} ${Math.floor(N/10)===1?"ten":"tens"} and ${numberToWordsLong(N%10)}, so `:"";return buildExample({generatorId:this.id,question:n,title:`${e} vs ${a} — who has more, or are they equal?`,computed:{v1:x,v2:y,h1x100:d*100,t1x10:m*10,h2x100:f*100,t2x10:g*10,answer:Math.max(x,y)},steps:[{visual_data:{type:"equation_display",terms:[d*100,"+",m*10,"+",h,"=",x]},animation:"write_answer",caption:`${e}: ${d*100} + ${m*10} + ${h} = ${x}.`,spoken:`First, add up ${e}'s hundreds, tens and ones. ${T(m,h)?`${capitalised(T(m,h))}altogether`:"Altogether"} that makes ${numberToWordsLong(x)}.`},{visual_data:{type:"equation_display",terms:[f*100,"+",g*10,"+",b,"=",y]},animation:"write_answer",caption:`${a}: ${f*100} + ${g*10} + ${b} = ${y}.`,spoken:`Now add up ${a}
71's hundreds, tens and ones the same way. ${T(g,b)?`${capitalised(T(g,b))}altogether`:"Altogether"} that makes ${numberToWordsLong(y)}.`},{visual_data:{type:"sequence",values:[x,y],step:1,hide_hop:!0,emphasis:k?void 0:x>y?0:1},animation:"highlight_pattern",caption:k?`${e} ${x} = ${y} ${a} — they are equal.`:`${e} ${x} vs ${a} ${y} — ${x>y?e:a} has more.`,spoken:k?`Both totals come out the same, ${numberToWordsLong(x)}, so they are equal.`:`Now compare the two totals: ${numberToWordsLong(Math.max(x,y))} is more than ${numberToWordsLong(Math.min(x,y))}, so ${x>y?e:a} has more.`}]})}},NPV304_BUILD=/^A number is made of (\d+) hundreds?, (\d+) tens? and (\d+) ones?\. What is the number\?$/,npvP5M02Build={id:"NPV.P5.M02.digit-value-entry.v1",microSkillId:"NPV.P5.M02",forms:["digit_value_entry"],generate(n){const t=normaliseStem(n.stem).match(NPV304_BUILD);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(e<1||e>9||s<0||s>9||o<0||o>9)return null;const r=e*100+s*10,a=r+o;return a!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${e} hundreds, ${s} tens and ${o} ones — what number is that?`,computed:{h:e,t:s,o,subtotal:r,N:a,answer:a},steps:[{visual_data:{type:"place_value_chart",digits:[e,s,o],digit_text:{1:"?",2:"?"},highlight:0},animation:"pulse_unknown",caption:"Place the parts: hundreds, tens, ones.",spoken:"Every part goes in its own column — hundreds, then tens, then ones."},{visual_data:{type:"place_value_chart",digits:[e,s,o],highlight:1,digit_text:{2:"?"}},animation:"reframe_subtraction",caption:`${s} in the tens column keeps its place.`,spoken:`Write ${numberToWords(s)} in the tens column. ${s===0?"We must keep this zero: it holds the tens place so the ones do not slide into it.":"It tells us how many tens the number has."}`},{visual_data:{type:"place_value_chart",digits:[e,s,o],highlight:2},animation:"write_answer",caption:`Read hundreds, tens, ones: ${a}.`,spoken:`Write ${numberToWords(o)} in the ones column. Read the three places in order: the number is ${numberToWordsLong(a)}.`}]})}},ROUND=/^Round (\d+) to the nearest (10|100)\.?$/,ROUND_WORD=/^A .+ collected (\d+) .+ rounds this to the nearest (10|100) to report it\. What number will .+ show\?$/,rounding={id:"NPV.P5.M04.rounding.v1",microSkillId:"NPV.P5.M04",forms:["rounding_entry"],generate(n){const t=normaliseStem(n.stem),e=t.match(ROUND)??t.match(ROUND_WORD);if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(s<o||s>9999||s%o===0)return null;const r=Math.floor(s/o)*o,a=r+o,i=r+o/2,l=s-r,u=a-s,d=l<u?r:(l>u,a);if(d!==answerInt(n))return null;const m=[{from:s,to:a,label:`${u} away`},{from:s,to:r,label:`${l} away`}].sort((g,b)=>+(g.to===d)-+(b.to===d)),h=o===100?"hundred":"ten",f=l===u;return buildExample({generatorId:this.id,question:n,title:`How do we round ${s} to the nearest ${o}?`,computed:{N:s,lower:r,upper:a,mid:i,dLow:l,dHigh:u,rounded:d,...o===10?{ones:s%10,five:5}:{},answer:d},steps:[{visual_data:{type:"number_line",min:r,max:a,marked:[s],hops:[],unknown:null},animation:"show_start",caption:`${s} sits between ${r} and ${a}.`,spoken:`Let's round ${numberToWordsLong(s)} to the nearest ${h}. First we find the two ${h}s either side of it, because it must round to one of them. It sits between ${numberToWordsLong(r)} and ${numberToWordsLong(a)}.`},{visual_data:o===10?{type:"place_value_chart",digits:String(s).split("").map(Number),highlight:String(s).length-1}:{type:"number_line",min:r,max:a,marked:[s,i],hops:m,unknown:null},animation:"compare_distance",caption:o===10?`Ones digit ${s%10}: ${s%10<5?"below 5, round down":"5 or more, round up"}.`:f?`${i} is halfway — it's a tie.`:`${i} is halfway — compare the gaps.`,spoken:o===10?`Look at the ones digit: ${numberToWords(s%10)}. ${s%10<5?"It is less than five, so round down to the lower ten.":"It is five or more, so round up to the next ten."}`:f?`Halfway between them is ${numberToWordsLong(i)}. ${capitalised(numberToWordsLong(s))} sits exactly halfway — the same distance from both. When a number is exactly halfway, the rule is to round up.`:`To see which ${h}
71 is nearer, we use the halfway point: ${numberToWordsLong(i)}. ${capitalised(numberToWordsLong(s))} comes ${l<u?"before":"after"} halfway, so it is closer to ${numberToWordsLong(d)}. It is ${numberToWordsLong(l)} away from ${numberToWordsLong(r)}, and ${numberToWordsLong(u)} away from ${numberToWordsLong(a)}.`},{visual_data:{type:"number_line",min:r,max:a,marked:[s,i],hops:m,unknown:d},animation:"hop_to_nearer",caption:f?`${s} rounds to ${d} — halfway rounds up.`:`${s} rounds to ${d}.`,spoken:f?`Because ${numberToWordsLong(s)} is exactly halfway, the rule says round up. So ${numberToWordsLong(s)} rounded to the nearest ${h} is ${numberToWordsLong(d)}.`:`We always round to the nearer ${h}, so ${numberToWordsLong(s)} rounded to the nearest ${h} is ${numberToWordsLong(d)}.`}]})}};function computeRounding(n,t){const e=Math.floor(n/t)*t,s=e+t,o=n-e,r=s-n,a=o<=r&&o<r?e:s;return{lower:e,upper:s,rounded:a}}const ROUND_CLAIM=/^(\w+) says (\d+) rounds to (\d+) when rounded to the nearest (10|100)\.? Is (?:he|she) right\?$/,npvP5M04Claim={id:"NPV.P5.M04.claim-check.v1",microSkillId:"NPV.P5.M04",forms:["context_reasoning_judgement"],generate(n){const t=normaliseStem(n.stem).match(ROUND_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r);if(a<l||a>9999)return null;const{lower:u,upper:d,rounded:m}=computeRounding(a,l),h=m===i,f=n.correctAnswer.trim();if(h&&!isAffirmative(f)||!h&&!isNegative(f))return null;const g=l===100?"hundred":"ten",b=u+l/2,x=a-u,y=d-a,E=x===y,k=[{from:a,to:d,label:`${y} away`},{from:a,to:u,label:`${x} away`}].sort((T,A)=>+(T.to===m)-+(A.to===m));return buildExample({generatorId:this.id,question:n,title:`Does ${a} round to ${i}, to the nearest ${l}?`,computed:{N:a,lower:u,upper:d,mid:b,dLow:x,dHigh:y,claimed:i,rounded:m,answer:m},steps:[{visual_data:{type:"number_line",min:u,max:d,marked:[a],hops:[],unknown:null},animation:"show_start",caption:`${a} sits between ${u} and ${d}.`,spoken:`Before trusting the claim, let's round ${numberToWordsLong(a)} ourselves. It sits between ${numberToWordsLong(u)} and ${numberToWordsLong(d)}.`},{visual_data:{type:"number_line",min:u,max:d,marked:[a,b],hops:k,unknown:null},animation:"compare_distance",caption:E?`${b} is halfway — it's a tie.`:`${b} is halfway — compare the gaps.`,spoken:E?`Halfway between them is ${numberToWordsLong(b)}. ${capitalised(numberToWordsLong(a))} sits exactly halfway, so the rule says round up.`:`Halfway between them is ${numberToWordsLong(b)}. ${capitalised(numberToWordsLong(a))} is ${numberToWordsLong(x)} away from ${numberToWordsLong(u)}, and ${numberToWordsLong(y)} away from ${numberToWordsLong(d)}.`},{visual_data:{type:"equation_display",terms:[i,h?"=":"≠",m]},animation:"compare_claim",caption:`${i} ${h?"=":"≠"} ${m} — ${e} is ${h?"right":"wrong"}.`,spoken:E?`${e} said ${numberToWordsLong(i)}. ${capitalised(numberToWordsLong(a))} is exactly halfway, so it really rounds up to ${numberToWordsLong(m)}. So ${e} is ${h?"right":"wrong"}.`:`${e} said ${numberToWordsLong(i)}, and the nearer ${g} — the real rounded value — is ${numberToWordsLong(m)}. So ${e} is ${h?"right":"wrong"}.`}]})}},ROUND_PICK=/^Which of these numbers rounds to (\d+) when rounded to the nearest (10|100): (\d+), (\d+), or (\d+)\?$/,npvP5M04Pick={id:"NPV.P5.M04.pick-which-rounds.v1",microSkillId:"NPV.P5.M04",forms:["rounding_entry"],generate(n){const t=normaliseStem(n.stem).match(ROUND_PICK);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=[Number(t[3]),Number(t[4]),Number(t[5])];if(o.some(u=>u<s||u>9999))return null;const r=o.map(u=>computeRounding(u,s).rounded),a=o.filter((u,d)=>r[d]===e);if(a.length!==1)return null;const i=a[0];if(String(i)!==n.correctAnswer.trim())return null;const l=o.map(u=>computeRounding(u,s));return buildExample({generatorId:this.id,question:n,title:`Which rounds to ${e}, to the nearest ${s}?`,computed:{target:e,unit:s,n1:o[0],n2:o[1],n3:o[2],r1:r[0],r2:r[1],r3:r[2],mid1:Math.floor(o[0]/s)*s+s/2,mid2:Math.floor(o[1]/s)*s+s/2,mid3:Math.floor(o[2]/s)*s+s/2,answer:i},steps:o.map((u,d)=>{const m=Math.floor(u/s)*s,h=m+s,f=m+s/2,g=u<f;return{visual_data:{type:"number_line",min:m,max:h,step:s/2,marked:[m,f,h],unknown:u,hops:[{from:u,to:l[d].rounded,label:g?"round down":"round up"}]},animation:"round_to_nearest",caption:`${u} is ${g?"below":"at or above"} ${f} → ${r[d]}.${d===2?` Only ${i} rounds to ${e}.`:""}`,spoken:`${capitalised(numberToWordsLong(u))} is ${g?"below":"at or above"}
71 the halfway point, ${numberToWordsLong(f)}, so round ${g?"down":"up"} to ${numberToWordsLong(r[d])}.${d===2?` Only ${numberToWordsLong(i)} rounds to ${numberToWordsLong(e)}.`:""}`}})})}},ROUND_PAIR=/^(\d+) and (\d+) both round to the same number when rounded to the nearest (10|100)\.? What is that number\?$/,npvP5M04Pair={id:"NPV.P5.M04.pair-check.v1",microSkillId:"NPV.P5.M04",forms:["rounding_entry"],generate(n){const t=normaliseStem(n.stem).match(ROUND_PAIR);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(e<o||e>9999||s<o||s>9999)return null;const r=computeRounding(e,o),a=computeRounding(s,o);if(r.rounded!==a.rounded||r.rounded!==answerInt(n)||r.lower!==a.lower||r.upper!==a.upper)return null;const{lower:i,upper:l,rounded:u}=r,d=i+o/2,m=o===100?"hundred":"ten",h=u===l?"at or past":"before",f=u===l?"up":"down";return u===l&&(e<d||s<d)||u===i&&(e>=d||s>=d)?null:buildExample({generatorId:this.id,question:n,title:`${e} and ${s} — do they round to the same number?`,computed:{n1:e,n2:s,lower:i,upper:l,mid:d,rounded:u,answer:u},steps:[{visual_data:{type:"number_line",min:i,max:l,marked:[e,s],hops:[],unknown:null},animation:"show_start",caption:`${e} and ${s} both sit between ${i} and ${l}.`,spoken:`${capitalised(numberToWordsLong(e))} and ${numberToWordsLong(s)} both sit somewhere between ${numberToWordsLong(i)} and ${numberToWordsLong(l)} — the two nearest ${m}s.`},{visual_data:{type:"number_line",min:i,max:l,marked:[e,s,d],hops:[{from:e,to:u,label:""},{from:s,to:u,label:""}],unknown:null},animation:"compare_distance",caption:`${d} is halfway — both sit ${h} it, so round ${f}.`,spoken:`Halfway between them is ${numberToWordsLong(d)}. Both ${numberToWordsLong(e)} and ${numberToWordsLong(s)} sit ${h} that halfway mark, so round ${f} to ${numberToWordsLong(u)}.`},{visual_data:{type:"number_line",min:i,max:l,marked:[e,s,d],hops:[],unknown:u},animation:"hop_to_nearer",caption:`Both round ${f} to ${u}.`,spoken:`${capitalised(numberToWordsLong(e))} and ${numberToWordsLong(s)} both round ${f} to ${numberToWordsLong(u)}.`}]})}};function metricConversion(n,t,e,s){const o=new RegExp(`^(\\d+)000 ${s} = __ ${e}$`),r=new RegExp(`^(\\d+) ${e} = __ ${s}$`);return{id:n,microSkillId:t,forms:["varied_symbolic_calculation"],generate(a){const i=normaliseStem(a.stem),l=i.match(o),u=l?null:i.match(r);if(!l&&!u)return null;const d=Number(l?l[1]:u[1]);if(!Number.isInteger(d)||d<1||d>999)return null;const m=d*1e3,h=l?d:m;if(h!==answerInt(a))return null;const f="kilogram",g="gram",b=numberToWords(d),x=`${b} ${d===1?f:`${f}s`}`,y=`${b} ${d===1?"lot":"lots"} of a thousand ${g}s`,E=`${numberToWordsLong(m)} ${g}s`;return buildExample({generatorId:n,question:a,title:`How do we convert between ${e} and ${s}?`,computed:{x:d,small:m,thousand:1e3,one:1,answer:h},steps:[{visual_data:{type:"equation_display",terms:["1",e,"=","1000",s]},animation:"show_base_fact",caption:`1 ${e} is the same as 1000 ${s}.`,spoken:`Kilo means a thousand. So one ${f} is exactly one thousand ${g}s — that is the fact we build every conversion from.`},l?{visual_data:{type:"equation_display",terms:[String(m),s,"=",String(d),e]},animation:"write_answer",caption:`${m} ${s} = ${d} ${e}.`,spoken:`To change ${g}s into ${f}s, we group the ${g}
71s into thousands, because each thousand ${g}s makes one ${f}. ${capitalised(E)} is ${y}. So ${E} is ${x}.`}:{visual_data:{type:"equation_display",terms:[String(d),e,"=",String(m),s]},animation:"write_answer",caption:`${d} ${e} = ${m} ${s}.`,spoken:`To change ${f}s into ${g}s, each ${f} becomes a thousand ${g}s. ${capitalised(x)} is ${y}, which is ${E}.`},{visual_data:l?{type:"equation_display",terms:[String(d),e,"=",String(m),s]}:{type:"equation_display",terms:[String(m),s,"=",String(d),e]},animation:"write_answer",caption:l?`Check it the other way: ${d} ${e} = ${m} ${s}.`:`Check it the other way: ${m} ${s} = ${d} ${e}.`,spoken:l?`Let's check by going back the other way. ${capitalised(x)} is ${y}, which is ${E} — the amount we started with. So the answer is ${b}: ${E} is ${x}.`:`Let's check by going back the other way. ${capitalised(E)} grouped into thousands makes ${x} — the amount we started with. So the answer is ${numberToWordsLong(m)}: ${x} is ${E}.`}]})}}}const massConvert=metricConversion("MEA.P7.M07.mass-convert.v1","MEA.P7.M07","kg","g"),LENGTH_SMALL_TO_BIG=/^(\d+)000 m = __ km$/,LENGTH_BIG_TO_SMALL=/^(\d+) km = __ m$/,lengthConvert={id:"MEA.P7.M01.length-convert.v1",microSkillId:"MEA.P7.M01",forms:["varied_symbolic_calculation"],generate(n){const t=normaliseStem(n.stem),e=t.match(LENGTH_SMALL_TO_BIG),s=e?null:t.match(LENGTH_BIG_TO_SMALL);if(!e&&!s)return null;const o=Number(e?e[1]:s[1]);if(!Number.isInteger(o)||o<1||o>999)return null;const r=o*1e3,a=e?o:r;if(a!==answerInt(n))return null;if(o>=2&&o<=8){const i=Array.from({length:o+1},(h,f)=>f),l=i.slice(1).map(h=>({from:h-1,to:h,label:"1 km"})),u=h=>Object.fromEntries(i.map(f=>[f,f===o?h?`${o} km`:"?":String(f)])),d=(h,f)=>Object.fromEntries(i.filter(g=>g<=h).map(g=>[g,g===o&&f!==null?f:String(g*1e3)])),m={type:"number_line",min:0,max:o,step:1};return buildExample({generatorId:this.id,question:n,title:"How do we convert between km and m?",computed:{x:o,m:r,thousand:1e3,one:1,answer:a},steps:[{visual_data:{...m,hops:[l[0]],tick_labels:u(!!s),under_labels:{0:"0 m",1:"1000"}},animation:"show_base_fact",caption:"1 km is the same as 1000 m.",spoken:"Kilo means a thousand, so one kilometre is exactly one thousand metres. On the top of the line we count kilometres, and underneath we count the metres."},{visual_data:{...m,hops:l,tick_labels:u(!!s),under_labels:e?{...d(o-1,null),[o]:`${r} m`}:{...d(o-1,null),[o]:"?"}},animation:"count_jumps",caption:e?`${r} m is how many jumps of 1000?`:"Each jump adds 1000 m.",spoken:e?`We have ${numberToWordsLong(r)} metres. Every jump of one kilometre is one thousand metres, so we count how many jumps it takes to reach ${numberToWordsLong(r)}.`:"Every jump of one kilometre adds another thousand metres underneath: one thousand, two thousand, and so on, all the way along."},{visual_data:{...m,hops:l,tick_labels:u(!0),under_labels:{...d(o-1,null),[o]:`${r} m`},unknown:o,hide_result_number:!0},animation:"write_answer",caption:e?`${o} jumps, so ${r} m = ${o} km.`:`${o} jumps of 1000 m: ${o} km = ${r} m.`,spoken:e?`It takes ${numberToWords(o)} jumps, so ${numberToWordsLong(r)} metres is ${numberToWords(o)} kilometres.`:`After ${numberToWords(o)} jumps we reach ${numberToWordsLong(r)} metres. So ${numberToWords(o)} kilometres is ${numberToWordsLong(r)} metres.`}]})}return buildExample({generatorId:this.id,question:n,title:"How do we convert between km and m?",computed:{x:o,m:r,thousand:1e3,one:1,answer:a},steps:[{visual_data:{type:"equation_display",terms:["1","km","=","1000","m"]},animation:"show_base_fact",caption:"1 km is the same as 1000 m.",spoken:"Kilo means a thousand. So one kilometre is exactly one thousand metres — that is the fact we build every conversion from."},e?{visual_data:{type:"equation_display",terms:[String(r),"m","=",String(o),"×","1000","m"]},animation:"write_multiplication",caption:`${r} m = ${o} × 1000 m.`,spoken:`${capitalised(numberToWordsLong(r))} metres is ${numberToWords(o)} ${o===1?"lot":"lots"} of one thousand metres.`}:{visual_data:{type:"equation_display",terms:[String(o),"km","=",String(o),"×","1000","m"]},animation:"write_multiplication",caption:`${o} km = ${o} × 1000 m.`,spoken:`${capitalised(numberToWords(o))} ${o===1?"kilometre":"kilometres"} is ${numberToWords(o)} ${o===1?"lot":"lots"} of one thousand metres.`},{visual_data:e?{type:"equation_display",terms:[String(r),"m","=",String(o),"km"]}:{type:"equation_display",terms:[String(o),"km","=",String(r),"m"]},animation:"write_answer",caption:e?`${r} m = ${o} km.`:`${o} km = ${r} m.`,spoken:e?`Each thousand metres is one kilometre, so ${numberToWordsLong(r)} metres is ${numberToWords(o)} ${o===1?"kilometre":"kilometres"}
71.`:`The three zeros of a thousand join onto the ${numberToWords(o)}, so ${numberToWords(o)} ${o===1?"kilometre":"kilometres"} is ${numberToWordsLong(r)} metres.`}]})}},eqKm=n=>({type:"equation_display",terms:n}),MEA_KM_CLAIM=/^(\w+) says (\d+) km is the same as (\d+) m\. Is (?:he|she) right\?$/,meaP7M01ClaimCheck={id:"MEA.P7.M01.claim-check.v1",microSkillId:"MEA.P7.M01",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(MEA_KM_CLAIM);if(!t)return null;const[,e,s,o]=t,r=Number(s),a=Number(o);if(r<1||r>20)return null;const i=r*1e3,l=i===a,u=n.correctAnswer.trim();return l&&!isAffirmative(u)||!l&&!isNegative(u)?null:buildExample({generatorId:this.id,question:n,title:`Is ${r} km really ${a} m?`,computed:{km:r,claimedM:a,realM:i,thousand:1e3,one:1,answer:i},steps:[{visual_data:eqKm(["1","km","=","1000","m"]),animation:"show_base_fact",caption:"Before trusting the claim, let's check the base fact.",spoken:"Kilo means a thousand, so one kilometre is exactly one thousand metres — that is the fact every claim like this is checked against."},{visual_data:eqKm([String(r),"km","=",String(r),"×","1000"]),animation:"write_multiplication",caption:`${r} km is ${r} lots of 1000 m.`,spoken:`${capitalised(numberToWords(r))} kilometres is ${numberToWords(r)} lots of one thousand metres.`},{visual_data:eqKm([String(r),"km","=",String(i),"m","✓",`${e} said ${a} m`,l?"✓":"✗"]),animation:"write_answer",caption:`${r} km = ${i} m ✓. ${e} said ${a} m ${l?"✓":"✗"} — ${e} is ${l?"right":"wrong"}.`,spoken:`So ${numberToWords(r)} kilometres is really ${numberToWordsLong(i)} metres. ${e} claimed a different number, so ${e} is ${l?"right":"wrong"}.`}]})}},MEA_KM_THERE_BACK=/^A cyclist rides (\d+) km to school and then another (\d+) km back home\. How many metres does (?:he|she) ride altogether\?$/,meaP7M01ThereAndBack={id:"MEA.P7.M01.there-and-back.v1",microSkillId:"MEA.P7.M01",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MEA_KM_THERE_BACK);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>20||s<1||s>20)return null;const o=e*1e3,r=s*1e3,a=o+r;return a!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${e} km there, ${s} km back — how many metres altogether?`,computed:{there:e,back:s,thereM:o,backM:r,thousand:1e3,total:a,answer:a},steps:[{visual_data:eqKm([String(e),"km","=",String(o),"m"]),animation:"show_base_fact",caption:`${e} km to school = ${o} m.`,spoken:`${capitalised(numberToWords(e))} kilometres to school is ${numberToWordsLong(o)} metres.`},{visual_data:eqKm([String(o),"m","+",String(r),"m","=","__"]),animation:"write_multiplication",caption:`${o} m + ${r} m = ?`,spoken:`The journey back is another ${numberToWords(s)} kilometres, which is ${numberToWordsLong(r)} metres too. Add both legs together.`},{visual_data:eqKm([String(o),"+",String(r),"=",String(a)]),animation:"write_answer",caption:`${o} + ${r} = ${a} m altogether.`,spoken:`Adding both journeys together makes ${numberToWordsLong(a)} metres altogether.`}]})}},MEA_KM_COMPARE=/^Which is the longer distance: (\d+) m or (\d+) km\?$/,meaP7M01Compare={id:"MEA.P7.M01.compare.v1",microSkillId:"MEA.P7.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(MEA_KM_COMPARE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>2e4||s<1||s>20)return null;const o=s*1e3;if(e===o)return null;const r=`${e} m`,a=`${s} km`,i=e>o?r:a;return i!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:`Which is the longer distance: ${r} or ${a}?`,computed:{metres:e,km:s,kmInM:o,thousand:1e3,answer:Math.max(e,o)},steps:[{visual_data:eqKm([r]),animation:"show_base_fact",caption:`${r} stays as it is.`,spoken:`To compare two lengths, we need them in the same unit. ${capitalised(numberToWordsLong(e))} metres is already in metres, so it stays as it is.`},{visual_data:eqKm([a,"=",String(o),"m"]),animation:"write_multiplication",caption:`${a} = ${o} m.`,spoken:`Each kilometre is ${numberToWordsLong(1e3)} metres, so ${numberToWords(s)} kilometres changes to ${numberToWordsLong(o)} metres.`},{visual_data:eqKm([String(e),"m","vs",String(o),"m"]),animation:"write_answer",caption:`${e} m vs ${o} m — ${i} is longer.`,spoken:`Now both are in metres, so we can compare directly: ${numberToWordsLong(Math.max(e,o))} metres is more than ${numberToWordsLong(Math.min(e,o))} metres. So ${e>o?`${numberToWordsLong(e)} metres`:`${numberToWords(s)} kilometres`} is the longer distance.`}]})}},MEA_KM_REMAINING=/^A delivery route is (\d+) km long in total\. The driver has already travelled (\d+) m\. How many metres are left to travel\?$/,meaP7M01Remaining={id:"MEA.P7.M01.remaining.v1",microSkillId:"MEA.P7.M01",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MEA_KM_REMAINING);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>20)return null;const o=e*1e3;if(s<1||s>=o)return null;const r=o-s;return r!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${e} km route, ${s} m already travelled — how far is left?`,computed:{km:e,travelled:s,totalM:o,thousand:1e3,left:r,answer:r},steps:[{visual_data:eqKm([String(e),"km","=",String(o),"m"]),animation:"show_base_fact",caption:`The whole route is ${e} km = ${o} m.`,spoken:`${capitalised(numberToWords(e))} kilometres in total is ${numberToWordsLong(o)} metres — we change to metres so both amounts match.`},{visual_data:eqKm([String(o),"-",String(s),"=","?"]),animation:"write_multiplication",caption:`${o} − ${s} m already travelled.`,spoken:`The driver has already covered ${numberToWordsLong(s)} metres of that total.`},{visual_data:eqKm([String(o),"-",String(s),"=",String(r)]),animation:"write_answer",caption:`${o} − ${s} = ${r} m left.`,spoken:`Subtracting what has already been travelled leaves ${numberToWordsLong(r)} metres still to go.`}]})}},FRACTION_OF=/^What is (\d+)\/(\d+) of (\d+)\?$/,FRACTION_WORD=/(\d+)\b[^?]*?\b(\d+)\/(\d+) of them[^?]*How many[^?]*\?$/,NOUN_EMOJI$1=[[/strawberr/i,"🍓"],[/cherr/i,"🍒"],[/grape/i,"🍇"],[/kiwi/i,"🥝"],[/apple/i,"🍎"],[/cupcake/i,"🧁"],[/biscuit|cookie/i,"🍪"],[/sticker/i,"⭐"],[/marble/i,"🔵"],[/sweet|candy/i,"🍬"]];function fractionEmoji(n){for(const[t,e]of NOUN_EMOJI$1)if(t.test(n))return e;return"🔵"}const fractionOfAmount$1={id:"FDP.P3.fraction-of-amount.v1",microSkillId:"",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,FRACTION_OF);let e,s,o;if(t)[e,s,o]=t;else{const u=normaliseStem(n.stem).match(FRACTION_WORD);if(!u)return null;o=Number(u[1]),e=Number(u[2]),s=Number(u[3])}if(s<2||s>10||e<1||e>s||o<s||o>60||o%s!==0)return null;const r=o/s;if(r<1||r>12)return null;const a=e*r;if(a!==answerInt(n))return null;const i=fractionEmoji(n.stem),l=Array.from({length:e},(u,d)=>d);return buildExample({generatorId:this.id,question:n,title:`What is ${e}/${s} of ${o}?`,computed:{a:e,b:s,N:o,per:r,groups:s,answer:a},steps:[{visual_data:{type:"objects_grid",emoji:i,count:o,group_size:o,hide_subtotals:!0},animation:"show_whole",caption:`${o}
71 to share.`,spoken:`${capitalised(numberToWords(o))} to share. First, we share them out fairly into equal groups — then we will choose some of the groups.`},{visual_data:{type:"objects_grid",emoji:i,count:o,group_size:r,hide_subtotals:!0},animation:"share_into_groups",caption:`Shared into ${s} equal groups of ${r}.`,spoken:`The bottom number, ${numberToWords(s)}, tells us how many equal groups to share into. ${capitalised(numberToWords(r))} in each of the ${numberToWords(s)} groups.`},{visual_data:{type:"objects_grid",emoji:i,count:o,group_size:r,chosen:l},animation:"choose_groups",caption:`Take ${e} of the ${s} groups: ${a}.`,spoken:`The top number, ${numberToWords(e)}, tells us how many of those groups to take. ${capitalised(numberToWords(e))} ${e===1?"group":"groups"} of ${numberToWords(r)} is ${numberToWords(a)} — that is ${numberToWords(e)} ${fractionWord(e,s)} of ${numberToWords(o)}.`}]})}};function fractionWord(n,t){const s={2:"half",3:"third",4:"quarter",5:"fifth",6:"sixth",8:"eighth",10:"tenth"}[t]??`${numberToWords(t)}th`;return n===1?s:`${s}s`}const fractionM03={...fractionOfAmount$1,id:"FDP.P3.M03.fraction-of-amount.v1",microSkillId:"FDP.P3.M03"},fractionM04={...fractionOfAmount$1,id:"FDP.P3.M04.fraction-of-amount.v1",microSkillId:"FDP.P3.M04"},CLAIM_FRACTION_OF=/^[A-Z][a-z]+ says (\d+)\/(\d+) of (\d+) is (\d+)\. Is (?:he|she|they) right\?$/,fractionOfAmountClaim={id:"FDP.P3.claim-check-fraction-of-amount.v1",microSkillId:"",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_FRACTION_OF);if(!t)return null;const[e,s,o,r]=t.slice(1).map(Number);if(s<2||s>10||e<1||e>s||o<s||o>60||o%s!==0)return null;const a=o/s;if(a<1||a>12)return null;const i=e*a,l=i===r;if(l){if(!isAffirmative(n.correctAnswer))return null}else{if(!isNegative(n.correctAnswer))return null;const h=n.correctAnswer.match(/\d+/);if(h&&Number(h[0])!==i)return null}const u=fractionEmoji(n.stem),d=Array.from({length:e},(h,f)=>f),m={a:e,b:s,N:o,per:a,claimed:r,real:i,answer:l?1:0};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is ${e}/${s} of ${o} equal to ${r}?`,computed:m,steps:[{visual_data:{type:"objects_grid",emoji:u,count:o,group_size:o,hide_subtotals:!0},animation:"show_whole",caption:`${o} in total.`,spoken:`${capitalised(numberToWords(o))} in total. First, we share them out fairly into equal groups.`},{visual_data:{type:"objects_grid",emoji:u,count:o,group_size:a,hide_subtotals:!0},animation:"share_into_groups",caption:`Shared into ${s} equal groups of ${a}.`,spoken:`The bottom number, ${numberToWords(s)}, tells us how many equal groups to share into. ${capitalised(numberToWords(a))} in each group.`},{visual_data:{type:"objects_grid",emoji:u,count:o,group_size:a,chosen:d},animation:"choose_groups",caption:`${e} groups of ${a} is ${i} — the claim is ${l?"correct":"not correct"}.`,spoken:`The top number, ${numberToWords(e)}, tells us how many groups to take. That comes to ${numberToWords(i)}. So the claim is ${l?"correct":"not correct"}.`}]})}},fractionClaimM03={...fractionOfAmountClaim,id:"FDP.P3.M03.claim-check.v1",microSkillId:"FDP.P3.M03"},fractionClaimM04={...fractionOfAmountClaim,id:"FDP.P3.M04.claim-check.v1",microSkillId:"FDP.P3.M04"},COMPARE_FRACTION_OF=/^Which is (bigger|greater|more): (\d+)\/(\d+) of (\d+),? or (\d+)\/(\d+) of (\d+)\?(?: Or are they the same\?)?$/,fractionOfAmountCompare={id:"FDP.P3.compare-fraction-of-amount.v1",microSkillId:"",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(COMPARE_FRACTION_OF);if(!t)return null;const[e,s,o,r,a,i]=t.slice(2).map(Number);for(const[x,y,E]of[[e,s,o],[r,a,i]]){if(y<2||y>10||x<1||x>y||E<y||E>60||E%y!==0)return null;const k=E/y;if(k<1||k>12)return null}const l=o/s,u=i/a,d=e*l,m=r*u,h=fractionEmoji(n.stem);if(d===m){if(!/same/i.test(n.correctAnswer))return null;const x={a1:e,b1:s,N1:o,a2:r,b2:a,N2:i,per1:l,per2:u,real1:d,real2:m,answer:d};return buildExample({generatorId:this.id,question:n,title:`Compare ${e}/${s} of ${o} with ${r}/${a} of ${i}.`,computed:x,steps:[{visual_data:{type:"objects_grid",emoji:h,count:o,group_size:l,chosen:Array.from({length:e},(y,E)=>E)},animation:"show_first",caption:`${o} ÷ ${s} = ${l}, so ${e}/${s} of ${o} is ${d}.`,spoken:`First, share ${numberToWords(o)} into ${numberToWords(s)} equal groups: ${numberToWords(l)} in each. ${capitalised(numberToWords(e))} ${e===1?"group is":"groups make"} ${numberToWords(d)}.`},{visual_data:{type:"objects_grid",emoji:h,count:i,group_size:u,chosen:Array.from({length:r},(y,E)=>E)},animation:"show_second",caption:`${i} ÷ ${a} = ${u}, so ${r}/${a} of ${i} is ${m}.`,spoken:`Next, ${numberToWords(i)} shared into ${numberToWords(a)} equal groups gives ${numberToWords(u)} in each, and ${numberToWords(r)} of those ${r===1?"groups is":"groups make"} ${numberToWords(m)}.`}
71,{visual_data:{type:"comparison",left:d,right:m,symbol:"="},animation:"compare_totals",caption:`${d} = ${m} — they are the same.`,spoken:`Comparing ${numberToWords(d)} with ${numberToWords(m)}, they come to the same amount, so they are the same.`}]})}const f=d>m?`${e}/${s} of ${o}`:`${r}/${a} of ${i}`;if(f!==n.correctAnswer)return null;const g=Math.max(d,m),b={a1:e,b1:s,N1:o,a2:r,b2:a,N2:i,per1:l,per2:u,real1:d,real2:m,answer:g};return buildExample({generatorId:this.id,question:n,title:`Compare ${e}/${s} of ${o} with ${r}/${a} of ${i}.`,computed:b,steps:[{visual_data:{type:"objects_grid",emoji:h,count:o,group_size:l,chosen:Array.from({length:e},(x,y)=>y)},animation:"show_first",caption:`${o} ÷ ${s} = ${l}, so ${e}/${s} of ${o} is ${d}.`,spoken:`First, share ${numberToWords(o)} into ${numberToWords(s)} equal groups: ${numberToWords(l)} in each. ${capitalised(numberToWords(e))} ${e===1?"group is":"groups make"} ${numberToWords(d)}.`},{visual_data:{type:"objects_grid",emoji:h,count:i,group_size:u,chosen:Array.from({length:r},(x,y)=>y)},animation:"show_second",caption:`${i} ÷ ${a} = ${u}, so ${r}/${a} of ${i} is ${m}.`,spoken:`Next, ${numberToWords(i)} shared into ${numberToWords(a)} equal groups gives ${numberToWords(u)} in each, and ${numberToWords(r)} of those ${r===1?"groups is":"groups make"} ${numberToWords(m)}.`},{visual_data:{type:"comparison",left:d,right:m,symbol:d>m?">":"<"},animation:"compare_totals",caption:`${d} ${d>m?">":"<"} ${m} — ${f} wins.`,spoken:`Comparing ${numberToWords(d)} with ${numberToWords(m)}, ${d>m?"the first":"the second"} one is bigger.`}]})}},fractionCompareM03={...fractionOfAmountCompare,id:"FDP.P3.M03.compare.v1",microSkillId:"FDP.P3.M03"},fractionCompareM04={...fractionOfAmountCompare,id:"FDP.P3.M04.compare.v1",microSkillId:"FDP.P3.M04"},FRACTION_REMAINDER_WORD=/(\d+)\b[^?]*?\b(\d+)\/(\d+)[^?]*?(?:left|does the school pay|the rest)[^?]*\?$/,fractionOfAmountRemainder={id:"FDP.P3.fraction-of-amount-remainder.v1",microSkillId:"",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(FRACTION_REMAINDER_WORD);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(o<2||o>10||s<1||s>o||e<o||e>60||e%o!==0)return null;const r=e/o;if(r<1||r>12)return null;const a=s*r,i=e-a;if(i!==answerInt(n))return null;const l=fractionEmoji(n.stem),u=Array.from({length:s},(h,f)=>f),d=Array.from({length:o-s},(h,f)=>s+f),m={a:s,b:o,N:e,per:r,taken:a,remainder:i,answer:i};return buildExample({generatorId:this.id,question:n,title:`${e}, take away ${s}/${o} of it — what is left?`,computed:m,steps:[{visual_data:{type:"objects_grid",emoji:l,count:e,group_size:r,hide_subtotals:!0},animation:"share_into_groups",caption:`${e} shared into ${o} equal groups of ${r}.`,spoken:`${capitalised(numberToWords(e))} shared into ${numberToWords(o)} equal groups. ${capitalised(numberToWords(r))} in each group.`},{visual_data:{type:"objects_grid",emoji:l,count:e,group_size:r,chosen:u},animation:"choose_groups",caption:`${s} of the ${o} groups is taken away: ${a}.`,spoken:`${capitalised(numberToWords(s))} of those groups are taken away, which is ${numberToWords(a)}.`},{visual_data:{type:"objects_grid",emoji:l,count:e,group_size:r,chosen:d,hide_total:!0,total_label:`${i} left`},animation:"show_remainder",caption:`${e} − ${a} = ${i} left.`,spoken:`${capitalised(numberToWords(e))} take away ${numberToWords(a)} leaves ${numberToWords(i)}.`}]})}},fractionRemainderM03={...fractionOfAmountRemainder,id:"FDP.P3.M03.fraction-remainder.v1",microSkillId:"FDP.P3.M03"},fractionRemainderM04={...fractionOfAmountRemainder,id:"FDP.P3.M04.fraction-remainder.v1",microSkillId:"FDP.P3.M04"},INV_NUMBER=/^(\d+)\/(\d+) of a number is (\d+)\. What is the number\?$/,INV_MEASURE=/^(\d+)\/(\d+) of a (\w+) is (\d+) ?(\w+) long\. How long is the whole \3\?$/,INV_EATEN=/^(\w+) has eaten (\d+)\/(\d+) of a (?:packet|bag|box|tub) of (\w+)\. (?:She|He|They|\1) has eaten (\d+) \4\. How many \4 were in the full (?:packet|bag|box|tub)\?$/,INV_CONTAINER=/^A (\w+) contains (\d+) ?(\w+) of \w+\. That is (\d+)\/(\d+) of what the \1 can hold\. How many \3 can the \1 hold when it is completely full\?$/,fractionOfAmountInverse={id:"FDP.P3.fraction-of-amount-inverse.v1",microSkillId:"",forms:["context_calculation","equation_completion"],generate(n){const t=normaliseStem(n.stem);let e,s,o;const r=t.match(INV_NUMBER),a=r?null:t.match(INV_MEASURE),i=r||a?null:t.match(INV_EATEN),l=r||a||i?null:t.match(INV_CONTAINER);if(r)[e,s,o]=[Number(r[1]),Number(r[2]),Number(r[3])];else if(a)e=Number(a[1]),s=Number(a[2]),o=Number(a[4]);else if(i)e=Number(i[2]),s=Number(i[3]),o=Number(i[5]);else if(l)o=Number(l[2]),e=Number(l[4]),s=Number(l[5]);else return null;if(s<2||s>10||e<1||e>=s||o<1||o%e!==0)return null;const u=o/e;if(u<1)return null;const d=u*s;if(d!==answerInt(n)||d>2e3||u>2e3)return null;const m=fractionEmoji(n.stem),h={n:e,d:s,part:o,per:u,whole:d,answer:d},f=e===1;if(u>12||o>12||d>60){const g=b=>({type:"equation_display",terms:b});return buildExample({generatorId:this.id,question:n,title:`${o} is only ${e}/${s} — what is the whole amount?`,computed:h,steps:[{visual_data:g([f?"1 share":`${e} shares`,"=",o]),animation:"show_part",caption:`${o} is ${e}/${s} of the whole — not the whole yet.`,spoken:f?`This isn't the whole amount yet — it is only one of the ${numberToWordsLong(s)} equal shares that make it up.`:`This isn't the whole amount yet — it is only ${numberToWordsLong(e)} of the ${numberToWordsLong(s)} equal shares that make it up.`},{visual_data:g(["1 share","=",u]),animation:f?"one_share":"share_into_groups",caption:f?`${o} is already ONE share of ${u}.`:`Each of the ${e} equal shares is ${u}
71.`,spoken:f?`The top number is one, so this amount already IS one share — ${numberToWordsLong(u)}.`:`The top number, ${numberToWordsLong(e)}, tells us how many equal groups this splits into: ${numberToWordsLong(u)} in each.`},{visual_data:g([u,"×",s,"=",d]),animation:"build_whole",caption:`${s} shares of ${u} make ${d} — that's the whole amount.`,spoken:`The bottom number, ${numberToWordsLong(s)}, tells us the whole is built from ${numberToWordsLong(s)} of those equal shares. ${capitalised(numberToWordsLong(s))} shares of ${numberToWordsLong(u)} makes ${numberToWordsLong(d)}.`}]})}return buildExample({generatorId:this.id,question:n,title:`${o} is only ${e}/${s} — what is the whole amount?`,computed:h,steps:[{visual_data:{type:"objects_grid",emoji:m,count:o,group_size:o,hide_subtotals:!0},animation:"show_part",caption:`${o} is ${e}/${s} of the whole — not the whole yet.`,spoken:f?`This isn't the whole amount yet — it is only one of the ${numberToWordsLong(s)} equal shares that make it up.`:`This isn't the whole amount yet — it is only ${numberToWordsLong(e)} of the ${numberToWordsLong(s)} equal shares that make it up.`},{visual_data:{type:"objects_grid",emoji:m,count:o,group_size:u,hide_subtotals:!0},animation:f?"one_share":"share_into_groups",caption:f?`${o} is already ONE share of ${u}.`:`Share ${o} into ${e} equal groups of ${u}.`,spoken:f?`The top number is one, so this amount already IS one share — ${numberToWordsLong(u)}.`:`The top number, ${numberToWordsLong(e)}, tells us how many equal groups this splits into: ${numberToWordsLong(u)} in each.`},{visual_data:{type:"objects_grid",emoji:m,count:d,group_size:u,chosen:Array.from({length:s},(g,b)=>b),total_label:`${d}`},animation:"build_whole",caption:`${s} shares of ${u} make ${d} — that's the whole amount.`,spoken:`The bottom number, ${numberToWordsLong(s)}, tells us the whole is built from ${numberToWordsLong(s)} of those equal shares. ${capitalised(numberToWordsLong(s))} shares of ${numberToWordsLong(u)} makes ${numberToWordsLong(d)}.`}]})}},fractionInverseM03={...fractionOfAmountInverse,id:"FDP.P3.M03.fraction-of-amount-inverse.v1",microSkillId:"FDP.P3.M03"},fractionInverseM04={...fractionOfAmountInverse,id:"FDP.P3.M04.fraction-of-amount-inverse.v1",microSkillId:"FDP.P3.M04"},Y3_GENERATORS=[columnAddition,columnSubtraction,timesTable,divisionFact,remainder$1,partition$3,rounding,massConvert,lengthConvert,fractionM03,fractionM04,asP5M01ClaimCheck,asP5M01WordProblem,asP5M01Compare,asP5M01MissingDigit,asP5M02ClaimCheck,asP5M02WordProblem,asP5M02Compare,asP5M02MissingDigit,npvP5M02Claim,npvP5M02Word,npvP5M02Compare,npvP5M02Build,npvP5M04Claim,npvP5M04Pick,npvP5M04Pair,mdP3M01ClaimCheck,mdP3M01WordProblem,mdP3M01Compare,mdP3M01MissingFirst,mdP3M03ClaimCheck,mdP3M03ShareWord,mdP3M03Compare,mdP3M03MissingDivisor,mdP3M04ClaimCheck,mdP3M04RoundUp,mdP3M04Compare,mdP3M04Reverse,meaP7M01ClaimCheck,meaP7M01ThereAndBack,meaP7M01Compare,meaP7M01Remaining,fractionClaimM03,fractionClaimM04,fractionCompareM03,fractionCompareM04,fractionRemainderM03,fractionRemainderM04,fractionInverseM03,fractionInverseM04],MICRO_SKILL="AS.P2.M02";function buildDominoDemo(n,t){const e=n.visualData;if(!e||e.type!=="domino_set")return null;const s=e,{target:o,options:r}=s;if(!Number.isInteger(o)||!Array.isArray(r)||r.length<2)return null;const a=r.filter(x=>x.left+x.right===o);if(a.length!==1)return null;const i=a[0];if(!Number.isInteger(i.left)||!Number.isInteger(i.right)||o!==answerInt(n))return null;const l=r.findIndex(x=>x!==i),u=r[l];if(!u||!Number.isInteger(u.left)||!Number.isInteger(u.right))return null;const d=u.left+u.right;if(d===o)return null;const{left:m,right:h}=i,f={left:m,right:h,target:o,answer:o,decoyLeft:u.left,decoyRight:u.right,decoyTotal:d},g={type:"domino_set",target:o,options:r};return buildExample({generatorId:t,question:n,title:"Which domino makes the number?",computed:f,steps:[{visual_data:g,animation:"show_dominoes",caption:"A domino has two ends. Add both ends.",spoken:"A domino has two ends. To check a domino, count both ends and add them together."},{visual_data:{type:"domino_set",target:o,options:r,checking:l},animation:"check_wrong",caption:`${u.left} and ${u.right} make ${d} — not ${o}.`,spoken:`Let's check this domino. ${capitalised(numberWord(u.left))} and ${numberWord(u.right)} make ${numberWord(d)}. That's not ${numberWord(o)}, so it's not the one.`},{visual_data:g,animation:"reveal_pick",caption:`${m} and ${h} make ${o}.`,spoken:`${capitalised(numberWord(m))} and ${numberWord(h)} make ${numberWord(o)}. That is the domino we want.`}]})}const dominoBondsGenerator={id:"AS.P2.M02.domino-bonds.v1",microSkillId:MICRO_SKILL,forms:["domino_bonds"],generate(n){return buildDominoDemo(n,dominoBondsGenerator.id)}};
71function readAddStatement(n){const e=normaliseStem(n.stem).match(/^(?:true or false|is this true or false|is it true or false)[:?]?\s*(\d+)\s*\+\s*(\d+)\s*=\s*(\d+)\.?$/i);if(e)return{a:Number(e[1]),b:Number(e[2]),stated:Number(e[3])};const s=n.visualData;if(s&&s.type==="equation_display"){const o=s.terms;if(o.length===5&&typeof o[0]=="number"&&o[1]==="+"&&typeof o[2]=="number"&&o[3]==="="&&typeof o[4]=="number")return{a:o[0],b:o[2],stated:o[4]}}return null}function buildTrueFalseDemo(n){const t=readAddStatement(n);if(!t)return null;const{a:e,b:s,stated:o}=t;if(![e,s,o].every(f=>Number.isInteger(f)&&f>=0&&f<=30)||s<1)return null;const r=e+s;if(r>30)return null;const a=r===o?"True":"False";if(a!==n.correctAnswer)return null;const i={a:e,b:s,stated:o,total:r,answer:a==="True"?1:0},l={type:"equation_display",terms:[e,"+",s,"=",o]},u={type:"part_whole",whole:r,parts:[e,null]},d={type:"equation_display",terms:[e,"+",s,"=",r],highlight:[4]},m=a==="True"?`${e} and ${s} make ${r}. True.`:`${e} and ${s} make ${r}, not ${o}. False.`,h=a==="True"?`${capitalised(numberWord(e))} and ${numberWord(s)} make ${numberWord(r)}. The statement is true.`:`${capitalised(numberWord(e))} and ${numberWord(s)} make ${numberWord(r)}, not ${numberWord(o)}. The statement is false.`;return buildExample({generatorId:trueFalseBondGenerator.id,question:n,title:"Is the number sentence true?",computed:i,steps:[{visual_data:l,animation:"pulse_unknown",caption:`Someone says ${e} + ${s} = ${o}.`,spoken:`Someone says ${numberWord(e)} plus ${numberWord(s)} equals ${numberWord(o)}. Let's check it.`},{visual_data:u,animation:"reframe_subtraction",caption:`Count on from ${e}: ${countingRun(e+1,r)}.`,spoken:`Start at ${numberWord(e)} and count on ${numberWord(s)}: ${countingRun(e+1,r)}.`},{visual_data:a==="True"?l:d,animation:"write_answer",caption:m,spoken:h}]})}const trueFalseBondGenerator={id:"AS.P2.M02.bond-true-false.v1",microSkillId:MICRO_SKILL,forms:["bond_equation"],generate:buildTrueFalseDemo};function buildMissingPartDemo$1(n){const t=partWholeFromVisual(n);if(!t)return null;const{whole:e,knownPart:s}=t;if(e<=s)return null;const o=e-s;if(![e,s,o].every(l=>Number.isInteger(l)&&l>=0&&l<=30)||o!==answerInt(n))return null;const r={whole:e,knownPart:s,missing:o,answer:o},a={type:"part_whole",whole:e,parts:[s,null]},i={type:"part_whole",whole:e,parts:[s,o]};return buildExample({generatorId:missingPartGenerator.id,question:n,title:"How do we find the missing part?",computed:r,steps:[{visual_data:a,animation:"highlight_unknown",caption:`${e} is made of ${s} and one more part.`,spoken:`The whole is ${numberWord(e)}. One part is ${numberWord(s)}. What is the other part?`},{visual_data:a,animation:"count_up",caption:`Count up from ${s} to ${e}.`,spoken:`Count up from ${numberWord(s)}: ${countingRun(s+1,e)}.`},{visual_data:i,animation:"fill_part",caption:`${s} and ${o} make ${e}.`,spoken:`The missing part is ${numberWord(o)}. ${capitalised(numberWord(s))} and ${numberWord(o)} make ${numberWord(e)}.`}]})}const missingPartGenerator={id:"AS.P2.M02.missing-part.v1",microSkillId:MICRO_SKILL,forms:["visual_quantity_to_symbol"],generate:buildMissingPartDemo$1},asP2M02Y1Generators=[dominoBondsGenerator,trueFalseBondGenerator,missingPartGenerator],as_p2_m03_dominoBondsGenerator={id:"AS.P2.M03.domino-bonds.v1",microSkillId:"AS.P2.M03",forms:["domino_bonds"],generate(n){return buildDominoDemo(n,as_p2_m03_dominoBondsGenerator.id)}},asP2M03Y1Generators=[as_p2_m03_dominoBondsGenerator],nearDoubleTenFrameMixedY1={id:"AS.P3.M01.near-double-ten-frame.v1",microSkillId:"AS.P3.M01",forms:["near_double"],generate(n){const t=n.visualData;if(!t||t.type!=="ten_frame_mixed")return null;const e=t.groups;if(!Array.isArray(e)||e.length!==2)return null;const[s,o]=e;if(!Number.isInteger(s?.count)||!Number.isInteger(o?.count)||Math.abs(s.count-o.count)!==1)return null;const r=Math.min(s.count,o.count),a=Math.max(s.count,o.count);if(r<1||a>15)return null;const i=2*r,l=i+1;if(l>30||l!==answerInt(n))return null;const u=a-r,d={s:r,larger:a,dbl:i,total:l,extra:u,answer:l},m={type:"ten_frame_mixed",groups:[s,o]};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${r} + ${a}?`,computed:d,steps:[{visual_data:m,animation:"show_groups",caption:`${r} and ${a} — nearly the same.`,spoken:`Let's add ${numberWord(r)} and ${numberWord(a)}. These two numbers are nearly the same, so this is a near double.`},{visual_data:m,animation:"show_double",caption:`Double ${r} is ${i}, and 1 more.`,spoken:`${capitalised(numberWord(r))} and ${numberWord(r)} is a double — that makes ${numberWord(i)}. ${capitalised(numberWord(a))} is just one more than ${numberWord(r)}, so there is one extra dot.`},{visual_data:m,animation:"write_total",caption:`${i} and 1 more = ${l}.`,spoken:`${capitalised(numberWord(i))} and one more is ${numberWord(l)}. So ${numberWord(r)} plus ${numberWord(a)} is ${numberWord(l)}. Use the double you know, then add the extra.`}]})}},STORY_TAKE_AWAY=/^(?:[A-Za-z]+ has )?(\d+)\s+\S+\.\s*[A-Za-z]+\s+(?:eats?|ate|gives? away|gave away|gives?|gave|loses?|lost|drops?|dropped|uses?|used|pops?|popped|breaks?|broke|drinks?|drank|spends?|spent)\s+(\d+)(?:\s+away)?\.\s*how many(?:\s+are)?\s+left\??$/i,storyTakeAwayY1={id:"AS.P3.M02.take-away-story.v1",microSkillId:"AS.P3.M02",forms:["equation_completion","misconception_contrast_probe"],generate(n){const t=STORY_TAKE_AWAY.exec(normaliseStem(n.stem));if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<2||e>10||s<1||s>=e)return null;const o=e-s;if(o!==answerInt(n))return null;const r={a:e,b:s,result:o,answer:o};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} - ${s}?`,computed:r,steps:[{visual_data:{type:"take_away",total:e,take_away:0},animation:"show_start",caption:`We have ${e} counters.`,spoken:`${capitalised(numberWord(e))} minus ${numberWord(s)}. We start with ${numberWord(e)} counters and need to take ${numberWord(s)} away.`},{visual_data:{type:"take_away",total:e,take_away:s},animation:"fade_away",caption:`Take ${s} away.`,spoken:`Take ${numberWord(s)} away — ${countingRun(1,s)}.`},{visual_data:{type:"take_away",total:e,take_away:s,result_visible:!0},animation:"show_remaining",caption:`${o} left. ${e} - ${s} = ${o}.`,spoken:`Count what's left. ${capitalised(countingRun(1,o))}. ${capitalised(numberWord(e))} minus ${numberWord(s)} is ${numberWord(o)}.`}]})}},STORY_FIND_MORE=/^(\d+)\s+\S+\.\s*(?:find|finds|gets?|wins?|earns?)\s+(\d+)\s+more\.\s*total\??$/i,storyAdditionBridgingY1={id:"AS.P3.M03.story-addition.v1",microSkillId:"AS.P3.M03",forms:["misconception_contrast_probe"],generate(n){const t=STORY_FIND_MORE.exec(normaliseStem(n.stem));if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<5||e>9||s<2||s>9)return null;const o=e+s;if(o<11||o>18||o!==answerInt(n))return null;const r=10-e,a=s-r;if(a<1)return null;const i={a:e,b:s,gap:r,leftover:a,total:o,answer:o};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} + ${s}?`,computed:i,steps:[{visual_data:{type:"ten_frame_overflow",start:e,adding:s},animation:"highlight_gap",caption:`${e} needs ${r} more to make 10.`,spoken:`${capitalised(numberWord(e))} plus ${numberWord(s)}. Here's one way. ${capitalised(numberWord(e))} needs ${numberWord(r)} more to make ten.`},{visual_data:{type:"ten_frame_overflow",start:e,adding:s},animation:"dots_fly_in",caption:`${r} of the ${s} fly in to fill the frame.`,spoken:`We're adding ${numberWord(s)}. ${capitalised(numberWord(r))} of them fly in to fill the frame and make ten.`},{visual_data:{type:"ten_frame_overflow",start:e,adding:s},animation:"write_total",caption:`10 and ${a} more — that's ${o}.`,spoken:`Now we have ten, plus ${numberWord(a)} more. That's ${numberWord(o)}. ${capitalised(numberWord(e))} plus ${numberWord(s)} equals ${numberWord(o)}.`}]})}};function numberLineSubtractionHop(n){const t=n.visualData;if(!t||t.type!=="number_line")return null;const e=t.hops??[];
71if(e.length!==1)return null;const[s]=e;if(!Number.isInteger(s.from)||!Number.isInteger(s.to))return null;const o=s.label;if(typeof o!="string")return null;const r=o.match(/^-(\d+)$/);if(!r)return null;const a=Number(r[1]),i=s.from;return s.to!==i-a?null:[i,a]}const storySubtractionNumberLineY1={id:"AS.P3.M04.story-number-line.v1",microSkillId:"AS.P3.M04",forms:["subtraction_story_context"],generate(n){const t=numberLineSubtractionHop(n);if(!t)return null;const[e,s]=t;if(e>=11&&e<=19&&s>=2&&s<=9){const o=e-10,r=s-o;if(r>=1){const a=10-r;if(a===answerInt(n)){const i={a:e,b:s,hop1:o,hop2:r,result:a,answer:a};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} - ${s}?`,computed:i,steps:[{visual_data:{type:"number_line",min:0,max:20,marked:[e]},animation:"highlight_start",caption:`Start at ${e}.`,spoken:`${capitalised(numberWord(e))} minus ${numberWord(s)}. We start at ${numberWord(e)} on the number line, and we need to jump back ${numberWord(s)}.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[{from:e,to:10,label:`-${o}`}]},animation:"first_hop",caption:`Jump back ${o} to land on 10.`,spoken:`Take ${numberWord(o)} away first. That lands us exactly on ten.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[{from:e,to:10,label:`-${o}`},{from:10,to:a,label:`-${r}`}],unknown:a},animation:"second_hop",caption:`Jump back ${r} more. ${e} - ${s} = ${a}.`,spoken:`${capitalised(numberWord(o))} and ${numberWord(r)} more makes ${numberWord(s)}. So jump back ${numberWord(r)} from ten — that's ${numberWord(a)}.`}]})}}}if(e>=11&&e<=20&&s>=1&&s<e){const o=e-s;if(o>=0&&o<=20&&o===answerInt(n)){const r={from:e,to:o,label:`-${s}`},a={a:e,b:s,result:o,answer:o};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} - ${s}?`,computed:a,steps:[{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[],unknown:null},animation:"highlight_start",caption:`Start at ${e}.`,spoken:`${capitalised(numberWord(e))} minus ${numberWord(s)}. We start at ${numberWord(e)} on the number line.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[r],unknown:null},animation:"first_hop",caption:`Count back ${s}.`,spoken:`Jump back ${numberWord(s)} from ${numberWord(e)}.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[r],unknown:o},animation:"land",caption:`That lands on ${o}. ${e} - ${s} = ${o}.`,spoken:`That lands on ${numberWord(o)}. So ${numberWord(e)} minus ${numberWord(s)} is ${numberWord(o)}.`}]})}}return null}},FEWER_COMPARISON=/^([A-Za-z]+) has (\d+) [a-z]+\. ([A-Za-z]+) has (\d+)\. how many fewer has \1\??$/i,fewerComparisonY1={id:"AS.P3.M04.fewer-comparison.v1",microSkillId:"AS.P3.M04",forms:["misconception_contrast_probe"],generate(n){const t=FEWER_COMPARISON.exec(normaliseStem(n.stem));if(!t)return null;const e=t[1],s=Number(t[2]),o=t[3],r=Number(t[4]);if(!Number.isInteger(s)||!Number.isInteger(r)||r<=s||s<0||r>20)return null;const a=r-s;if(a<1||a!==answerInt(n))return null;const i={countA:s,countB:r,diff:a,answer:a},l={from:s,to:r,label:`+${a}`};return buildExample({generatorId:this.id,question:n,title:`How many fewer? Count up from ${s} to ${r}.`,computed:i,steps:[{visual_data:{type:"number_line",min:0,max:20,marked:[s,r],hops:[],unknown:null},animation:"highlight_start",caption:`${capitalised(o)} ${r}, ${capitalised(e)} ${s} — how many fewer?`,spoken:`How many fewer does ${e} have than ${o}? We can count up from ${e}'s number to ${o}'s.`},{visual_data:{type:"number_line",min:0,max:20,marked:[s,r],hops:[l],unknown:null},animation:"count_up",caption:`Count up from ${s} to ${r}.`,spoken:`Start at ${numberWord(s)}. Count up to ${numberWord(r)} — ${countingRun(s+1,r)}.`},{visual_data:{type:"number_line",min:0,max:20,marked:[s,r],hops:[l],unknown:null,conclude_label:`${a} fewer`},animation:"show_difference",caption:`${a} fewer. ${r} − ${s} = ${a}.`,spoken:`That took ${numberWord(a)} ${a===1?"hop":"hops"}, so ${e} has ${numberWord(a)} fewer. The difference is ${numberWord(a)}.`}]})}},asP3SupplementalY1Generators=[nearDoubleTenFrameMixedY1,storyTakeAwayY1,storyAdditionBridgingY1,storySubtractionNumberLineY1,fewerComparisonY1];function normaliseVerdict(n){return normaliseStem(n).toLowerCase()}function countingRunDown$1(n,t){const e=[];for(let s=n;s>=t;s--)e.push(numberWord(s));return e.join(", ")}function statedFactFromVisual(n){const t=n.visualData;if(!t||t.type!=="equation_display")return null;const e=t.terms;if(e.length!==5)return null;const[s,o,r,a,i]=e;if(typeof s!="number"||typeof r!="number"||typeof i!="number"||a!=="=")return null;const l=o==="-"?"−":o;return l!=="+"&&l!=="−"?null:{a:s,op:l,b:r,stated:i}}function buildFactCheckDemo(n){const{a:t,op:e,b:s,stated:o,actual:r}=n,a=r===o,i=e==="+"?"add":"take away",l={a:t,b:s,stated:o,actual:r},u=[t,e,s,"=",o],d=[t,e,s,"=",r],m=a?`${t} ${e} ${s} = ${r}. True.`:`${t} ${e} ${s} = ${r}, not ${o}. False.`,h=a?`So ${numberWord(t)} ${i} ${numberWord(s)} really is ${numberWord(r)}. This is true.`:`So ${numberWord(t)} ${i} ${numberWord(s)} is ${numberWord(r)}, not ${numberWord(o)}. This is false.`;return buildExample({generatorId:n.generatorId,question:n.question,title:n.title,computed:l,steps:[{visual_data:{type:"equation_display",terms:u},animation:"show_equation",caption:`Does ${t} ${e} ${s} really make ${o}?`,spoken:`Let's check. Does ${numberWord(t)} ${i} ${numberWord(s)} really make ${numberWord(o)}?`},{visual_data:n.workVisual??{type:"equation_display",terms:u},animation:"work_it_out",caption:n.workCaption,spoken:n.workSpoken},{visual_data:{type:"equation_display",terms:d},animation:"write_answer",caption:m,spoken:h}]})}const BOND_KNOWN_LEFT=/^(\d+) and __ make (\d+)$/,BOND_KNOWN_RIGHT=/^__ and (\d+) make (\d+)$/,bondEquationY1={id:"AS.P2.M03.bond-reasoning.v1",microSkillId:"AS.P2.M03",forms:["bond_equation"],generate(n){const t=statedFactFromVisual(n);if(t&&t.op==="+"){const h=t.a+t.b;return(h===t.stated?"true":"false")!==normaliseVerdict(n.correctAnswer)?null:buildFactCheckDemo({generatorId:this.id,question:n,a:t.a,op:"+",b:t.b,stated:t.stated,actual:h,title:"Is this bond correct?",workCaption:`Count on ${t.b} from ${t.a}: ${countingRun(t.a+1,h)}.`,workSpoken:`Start at ${numberWord(t.a)} and count on ${numberWord(t.b)} more: ${countingRun(t.a+1,h)}.`,workVisual:{type:"part_whole",whole:h,parts:[t.a,null]}})}let e=matchInts(n.stem,BOND_KNOWN_LEFT),s=!1;if(e||(e=matchInts(n.stem,BOND_KNOWN_RIGHT),s=!0),!e)return null;const[o,r]=e;if(r<1||r>20||o<0||o>r)return null;const a=r-o;if(a!==answerInt(n))return null;const i=s?["__","+",o,"=",r]:[o,"+","__","=",r],l=s?[a,"+",o,"=",r]:[o,"+",a,"=",r],u=a>=1,d=o===0,m={known:o,whole:r,missing:a,answer:a};return buildExample({generatorId:this.id,question:n,title:`How do we make ${r}?`,computed:m,steps:[{visual_data:{type:"equation_display",terms:i},animation:"pulse_unknown",caption:`A number bond joins two parts to make ${r}.`,spoken:`A number bond joins two parts to make ${numberWord(r)}. One part is ${numberWord(o)}. What's the other part?`},{visual_data:{type:"equation_display",terms:i},animation:"count_up",caption:d?`Zero is nothing, so the other part must be all ${r}.`:u?`Count on from ${o} to ${r}.`:`${o} is already ${r}.`,spoken:d?`Zero is nothing at all. So the other part has to be all of it — ${numberWord(r)}.`:u?`Count on from ${numberWord(o)} to ${numberWord(r)}: ${countingRun(o+1,r)}.`:`${capitalised(numberWord(o))} is already ${numberWord(r)}, so we add nothing more.`},{visual_data:{type:"equation_display",terms:l},animation:"write_answer",caption:`${o} and ${a} make ${r}.`,spoken:`The other part is ${numberWord(a)}. ${capitalised(numberWord(o))} and ${numberWord(a)} make ${numberWord(r)}.`}]})}},unknownPartCheckY1={id:"AS.P2.M04.subtraction-check.v1",microSkillId:"AS.P2.M04",forms:["unknown_part_within_whole"],generate(n){const t=statedFactFromVisual(n);if(!t||t.op!=="−"||t.b>t.a)return null;const e=t.a-t.b;return(e===t.stated?"true":"false")!==normaliseVerdict(n.correctAnswer)?null:buildFactCheckDemo({generatorId:this.id,question:n,a:t.a,op:"−",b:t.b,stated:t.stated,actual:e,title:"Is this fact correct?",workCaption:`Start at ${t.a} and count back ${t.b}: ${countingRunDown$1(t.a-1,e)}.`,workSpoken:`Taking away means counting back, so we start at ${numberWord(t.a)} and count back ${numberWord(t.b)}: ${countingRunDown$1(t.a-1,e)}.`,workVisual:{type:"number_line",min:0,max:Math.max(20,t.a),marked:[t.a
71],hops:[{from:t.a,to:e,label:`-${t.b}`}],unknown:null}})}},OTHER_PART_CLAIM=/^[a-z]+ says the other part is (\d+)\. is (?:he|she|that) right\??$/i,partWholeClaimCheckY1={id:"AS.P2.M04.part-whole-claim.v1",microSkillId:"AS.P2.M04",forms:["unknown_part_within_whole"],generate(n){const t=OTHER_PART_CLAIM.exec(normaliseStem(n.stem));if(!t)return null;const e=Number(t[1]),s=partWholeFromVisual(n);if(!s)return null;const{whole:o,knownPart:r}=s;if(o<=r)return null;const a=o-r;if(![o,r,a,e].every(h=>Number.isInteger(h)&&h>=0&&h<=30))return null;const i=a===e?"true":"false";if(i!==normaliseVerdict(n.correctAnswer))return null;const l={whole:o,knownPart:r,claimed:e,missing:a,answer:i==="true"?1:0},u={type:"part_whole",whole:o,parts:[r,null]},d={type:"part_whole",whole:o,parts:[r,a]},m=i==="true";return buildExample({generatorId:this.id,question:n,title:"Is the other part right?",computed:l,steps:[{visual_data:u,animation:"highlight_unknown",caption:`The claim: the other part is ${e}.`,spoken:`The whole is ${numberWord(o)}. One part is ${numberWord(r)}. Someone says the other part is ${numberWord(e)}. Let's check.`},{visual_data:u,animation:"count_up",caption:`Count up from ${r} to ${o}.`,spoken:`Count up from ${numberWord(r)} to ${numberWord(o)}: ${countingRun(r+1,o)}.`},{visual_data:d,animation:"fill_part",caption:m?`${r} and ${a} make ${o}. True.`:`The other part is ${a}, not ${e}. False.`,spoken:m?`The real missing part is ${numberWord(a)}. ${capitalised(numberWord(r))} and ${numberWord(a)} make ${numberWord(o)} — that claim is true.`:`The real missing part is ${numberWord(a)}, not ${numberWord(e)}. ${capitalised(numberWord(r))} and ${numberWord(a)} make ${numberWord(o)} — that claim is false.`}]})}},DOUBLE_OPTION=/^(\d+) \+ (\d+)$/,doublingReasoningY1={id:"AS.P3.M01.doubling-reasoning.v1",microSkillId:"AS.P3.M01",forms:["doubling"],generate(n){const t=statedFactFromVisual(n);if(t&&t.op==="+"){if(t.a!==t.b)return null;const l=t.a,u=t.a+t.b,d=u===t.stated?"true":"false";if(d!==normaliseVerdict(n.correctAnswer)||l<1||l>9)return null;const m="🔵",h={type:"mirror",each_side:l,emoji:m,right_hidden:!0},f={type:"mirror",each_side:l,emoji:m,right_hidden:!1},g=d==="true",b=g?`${l} and ${l} make ${u}. True.`:`${l} and ${l} make ${u}, not ${t.stated}. False.`,x=g?`Both sides together: ${numberWord(l)} and ${numberWord(l)} make ${numberWord(u)}. That matches what was said, so this is true.`:`Both sides together: ${numberWord(l)} and ${numberWord(l)} make ${numberWord(u)}, not ${numberWord(t.stated)}. That does not match, so this is false.`;return buildExample({generatorId:this.id,question:n,title:"Is this double correct?",computed:{n:l,stated:t.stated,actual:u,answer:g?1:0},steps:[{visual_data:h,animation:"highlight_one_side",caption:`Someone says ${l} + ${l} = ${t.stated}. Here is ${l} on one side.`,spoken:`Someone says ${numberWord(l)} plus ${numberWord(l)} equals ${numberWord(t.stated)}. Let's check. Here we have ${numberWord(l)} on one side.`},{visual_data:f,animation:"mirror_and_count",caption:`${l} and ${l} more: ${countingRun(l+1,u)}.`,spoken:`Add ${numberWord(l)} more on the other side: ${countingRun(l+1,u)}.`},{visual_data:f,animation:"write_total",caption:b,spoken:x}]})}const e=DOUBLE_OPTION.exec(normaliseStem(n.correctAnswer));if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(s!==o||s<1||s>10)return null;const r=s*2,a={n:s,total:r},i="🔵";return buildExample({generatorId:this.id,question:n,title:"What is a double?",computed:a,steps:[{visual_data:{type:"mirror",each_side:s,emoji:i,right_hidden:!0},animation:"highlight_one_side",caption:"A double adds the same number twice.",spoken:`A double means we add the same number again. Here we have ${numberWord(s)} on one side.`},{visual_data:{type:"mirror",each_side:s,emoji:i,right_hidden:!1},animation:"mirror_and_count",caption:`${s} and ${s} more — that's a double.`,spoken:`Add ${numberWord(s)} more on the other side. That's why ${numberWord(s)} plus ${numberWord(s)} is a double.`},{visual_data:{type:"mirror",each_side:s,emoji:i,right_hidden:!1},animation:"write_total",caption:`${s} + ${s} = ${r}
71. That's the double.`,spoken:`Now put both sides together: ${numberWord(s)} and ${numberWord(s)} make ${numberWord(r)}. We added the same number twice, so ${numberWord(r)} is the double.`}]})}},ADD_EQ$1=/^(\d+) \+ (\d+) = __$/,bridgingSmallFirstY1={id:"AS.P3.M03.bridging-small-first.v1",microSkillId:"AS.P3.M03",forms:["equation_completion"],generate(n){const t=matchInts(n.stem,ADD_EQ$1);if(!t)return null;const[e,s]=t,o=e+s;if(e>=s||s<5||s>9||e<2||e>9||o<11||o>18)return null;const r=10-s,a=e-r;if(a<1||o!==answerInt(n))return null;const i={a:e,b:s,gap:r,leftover:a,total:o,answer:o};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} + ${s}?`,computed:i,steps:[{visual_data:{type:"ten_frame_overflow",start:s,adding:e},animation:"highlight_gap",caption:`Start with the bigger number, ${s}. It needs ${r} more to make 10.`,spoken:`${capitalised(numberWord(e))} plus ${numberWord(s)}. Let's start with the bigger number, ${numberWord(s)}. It needs ${numberWord(r)} more to make ten.`},{visual_data:{type:"ten_frame_overflow",start:s,adding:e},animation:"dots_fly_in",caption:`${r} of the ${e} fly in to make 10.`,spoken:`We're adding ${numberWord(e)}. ${capitalised(numberWord(r))} of them fly in to make ten.`},{visual_data:{type:"ten_frame_overflow",start:s,adding:e},animation:"write_total",caption:`10 and ${a} more — that's ${o}.`,spoken:`Now we have ten, plus ${numberWord(a)} more. That's ${numberWord(o)}. ${capitalised(numberWord(e))} plus ${numberWord(s)} equals ${numberWord(o)}.`}]})}};function bridgingLineRead(n){const t=n.visualData;if(!t||t.type!=="number_line")return null;const e=t.hops??[];if(e.length!==2)return null;const[s,o]=e;return!o.unknown||!Number.isInteger(s.from)||!Number.isInteger(s.to)||!Number.isInteger(o.from)||!Number.isInteger(o.to)||s.to!==o.from?null:{start:s.from,mid:s.to,total:o.to}}const missingNumberLineY1={id:"AS.P3.M05.missing-number-line.v1",microSkillId:"AS.P3.M05",forms:["missing_number_bridging"],generate(n){const t=bridgingLineRead(n);if(!t)return null;const{start:e,mid:s,total:o}=t,r=s-e,a=o-s;if(a<1||r<1||a!==answerInt(n))return null;const i=0,l=Math.max(o,20)<=20?20:o,u={start:e,mid:s,total:o,firstHop:r,missing:a,answer:a};return buildExample({generatorId:this.id,question:n,title:`How many more to ${o}?`,computed:u,steps:[{visual_data:{type:"number_line",min:i,max:l,marked:[e],hops:[{from:e,to:s,label:`+${r}`}]},animation:"highlight_start",caption:`${e} and ${r} more makes ${s}.`,spoken:`We start at ${numberWord(e)}. ${capitalised(numberWord(e))} and ${numberWord(r)} more makes ${numberWord(s)}.`},{visual_data:{type:"number_line",min:i,max:l,marked:[s],hops:[{from:s,to:o,label:"+?"}]},animation:"count_on",caption:`Now count on from ${s} to ${o}.`,spoken:`Now count on from ${numberWord(s)} to ${numberWord(o)}: ${countingRun(s+1,o)}.`},{visual_data:{type:"number_line",min:i,max:l,marked:[s],hops:[{from:s,to:o,label:`+${a}`}],unknown:null,conclude_label:`${a} more`},animation:"write_answer",caption:`${a} more to reach ${o}.`,spoken:`That's ${numberWord(a)} more steps. So ${numberWord(a)} more takes us to ${numberWord(o)}.`}]})}},STORY_TWO_INTS=/^\D*(\d+)\D+(\d+)\D*$/,storyMissingAddendY1={id:"AS.P3.M05.story-missing-addend.v1",microSkillId:"AS.P3.M05",forms:["equation_completion","missing_addend"],generate(n){const t=matchInts(n.stem,STORY_TWO_INTS);if(!t||t.length!==2)return null;const[e,s]=t;if(s<=e||e<0||s>20)return null;const o=s-e;if(o<1||o!==answerInt(n))return null;const r={start:e,total:s,missing:o,answer:o},a=countOnPlan(e,s),{min:i,max:l}=a.hops.length>=2?onesWindow(e,s):{min:0,max:20};a.kind==="bridge"&&(r.toTen=a.toTen,r.rest=a.rest);const u=a.kind==="bridge"?[a.hops[0]]:[],d=a.kind==="bridge"?`To find how many were added, we count on from ${numberWord(e)}. ${capitalised(numberWord(e))} and ${numberWord(a.toTen)} more makes ten.`:`To find how many were added, we count on from ${numberWord(e)} to ${numberWord(s)}, one jump at a time.`,m=a.kind==="bridge"?`Then ${numberWord(a.rest)} more makes ${numberWord(s)}. ${capitalised(numberWord(a.toTen))} and ${numberWord(a.rest)} make ${numberWord(o)}, so ${numberWord(o)} more were added.`:a.kind==="ones"?`Count on: ${countingRun(e+1,s)}. That's ${numberWord(o)} ${o===1?"jump":"jumps"}, so ${numberWord(o)} more ${o===1?"was":"were"} added.`:`From ${numberWord(e)} to ${numberWord(s)} is ${numberWord(o)} more, so ${numberWord(o)} more were added.`;return buildExample({generatorId:this.id,question:n,title:`How many more from ${e} to ${s}?`,computed:r,steps:[{visual_data:{type:"number_line",min:i,max:l,marked:[e]},animation:"highlight_start",caption:`We start with ${e} and end with ${s}.`,spoken:`We start with ${numberWord(e)} and end with ${numberWord(s)}. How many more were added?`},{visual_data:{type:"number_line",min:i,max:l,marked:[e,s],hops:u},animation:"count_on",caption:`Count on from ${e} to ${s}.`,spoken:d},{visual_data:{type:"number_line",min:i,max:l,marked:[e,s],hops:a.hops,unknown:null,conclude_label:`${o} more`},animation:"write_answer",caption:`${e} and ${o} more make ${s}.`,spoken:m}]})}},asReasoningY1Generators=[bondEquationY1,unknownPartCheckY1,partWholeClaimCheckY1,doublingReasoningY1,bridgingSmallFirstY1,missingNumberLineY1,storyMissingAddendY1],SHARING_ID="FDP.P1.M03.equal-sharing.v1",SHARE_BETWEEN=/^share (\d+) .+? between (\d+)\b/i,SHARE_NO_GROUPS=/^share (\d+) \D+$/i,HALF_OF=/^half of (\d+) is\b/i;function parseSharing(n){const t=normaliseStem(n),e=t.match(SHARE_BETWEEN);if(e)return{total:Number(e[1]),groups:Number(e[2])};const s=t.match(HALF_OF);if(s)return{total:Number(s[1]),groups:2};const o=t.match(SHARE_NO_GROUPS);return o?{total:Number(o[1]),groups:2}:null}const SHARED_BETWEEN_ONE_GOT=/^shared between (\d+) friends?,\s*one got (\d+)\.\s*total\??$/i,SHARE_EACH_GETS_TOTAL=/^(\d+) friends share \S+\.\s*each (?:gets|got) (\d+)\.\s*how many (?:altogether|in all)\??$/i,SHARING_CLAIM=/^true or false:\s*sharing (\d+) \S+,\s*[a-z]+ gets (\d+)\.?$/i,SHARING_CLAIM_NAMED=/^[a-z]+ and [a-z]+ share (\d+) \S+ equally\.\s*[a-z]+ says (?:she|he) (?:gets|will get) (\d+)\.\s*true or false\??$/i,SHARING_CLAIM_ONE_NAME=/^[a-z]+ shares (\d+) \S+ equally and (?:gets|will get) (\d+)\.\s*true or false\??$/i;function sharesSum(n,t){if(t>5)return`${numberWord(t)} shares of ${n}`;const e=Array.from({length:t},()=>n);return`${e.slice(0,-1).join(", ")} and ${e[e.length-1]}`}function buildSharingDemo(n,t,e,s,o){const r=Math.max(1,Math.floor(o/2)),a=e-s*r,i=o===1,l={type:"objects_grid",emoji:t,count:e,group_size:o,dealt:0,hide_subtotals:!0},u={type:"objects_grid",emoji:t,count:e,group_size:o,dealt:r,hide_subtotals:!0},d={type:"objects_grid",emoji:t,count:e,group_size:o,hide_subtotals:!0,dealt:o,total_label:`${o} each`},m={total:e,groups:s,each:o,answer:o,dealt_so_far:r,pile:a},h=numberWord(e),f=numberWord(s),g=numberWord(o);return buildExample({generatorId:SHARING_ID,question:n,title:`How do we share ${e} equally?`,computed:m,steps:[{visual_data:l,animation:"show_whole",caption:`Share ${e} into equal groups.`,spoken:i?`We need to share ${h} equally between ${f} groups. We deal them out: one for each group, until the pile is empty.`:`We need to share ${h} equally between ${f} groups. We deal them out: one for each group, then one more for each, until the pile is empty.`},{visual_data:u,animation:"fill_groups",caption:i?"One each. None left.":`One each, then again: ${r} each so far, ${a} left.`,spoken:i?"One for each group. That's one each, and none left in the pile."
71:r===1?`One for each group. That's one each so far, and ${numberWord(a)} still in the pile. Keep dealing until none are left.`:`One for each group, then one more for each. Keep going: that's ${numberWord(r)} each so far, and ${numberWord(a)} still in the pile.`},{visual_data:d,animation:"conclude_share",caption:`Each group gets ${o}.`,spoken:`So when we share ${h} equally between ${f}, each group gets ${g}. ${capitalised(sharesSum(g,s))} make ${h}, so none are left over.`}]})}function buildShareReverseDemo(n,t,e,s,o){const r={type:"objects_grid",emoji:t,count:e,group_size:e,hide_subtotals:!0},a={type:"objects_grid",emoji:t,count:o,group_size:e,hide_subtotals:!0},i=`${Array.from({length:s},()=>e).join(" + ")} = ${o}`,l={...a,total_label:i},u={each:e,groups:s,total:o,answer:o},d=numberWord(e),m=numberWord(s),h=numberWord(o);return buildExample({generatorId:SHARING_ID,question:n,title:"How many altogether?",computed:u,steps:[{visual_data:r,animation:"show_whole",caption:`One share is ${e}.`,spoken:`We know one share is ${d}. Every share is the same size.`},{visual_data:a,animation:"fill_groups",caption:`${s} equal shares of ${e}.`,spoken:`Every share is the same, so there are ${m} shares of ${d}. To find how many altogether, we put the shares together.`},{visual_data:l,animation:"conclude_share",caption:`${i}.`,spoken:`${capitalised(sharesSum(d,s))} make ${h}. So there are ${h} altogether.`}]})}function buildShareClaimDemo(n,t,e,s,o,r){const a=o===r,i=Math.max(1,Math.floor(r/2)),l=e-s*i,u={total:e,groups:s,claimedEach:o,actualEach:r,dealt_so_far:i,pile:l,answer:a?1:0},d={type:"objects_grid",emoji:t,count:e,group_size:r,dealt:0,hide_subtotals:!0},m={type:"objects_grid",emoji:t,count:e,group_size:r,dealt:i,hide_subtotals:!0},h={type:"objects_grid",emoji:t,count:e,group_size:r,dealt:r,hide_subtotals:!0,total_label:`${r} each`},f=numberWord(e),g=numberWord(s),b=numberWord(o),x=numberWord(r);return buildExample({generatorId:SHARING_ID,question:n,title:"Check the sharing claim",computed:u,steps:[{visual_data:d,animation:"show_whole",caption:`The claim: sharing ${e} gives ${o} each.`,spoken:`We share ${f} between ${g}. The claim is ${b} each. Let's check by sharing it out ourselves.`},{visual_data:m,animation:"fill_groups",caption:r===1?"One each. None left.":`One each, then again: ${i} each so far, ${l} left.`,spoken:r===1?"To share fairly, we deal them out: one for each. That's one each, and none left in the pile.":`To share fairly, we deal them out: one for each, then one more for each. That's ${numberWord(i)} each so far, and ${numberWord(l)} still in the pile. Keep going until none are left.`},{visual_data:h,animation:"conclude_share",caption:a?`Each gets ${r}. True.`:`Each gets ${r}, not ${o}. False.`,spoken:a?`Each group really gets ${x}, and ${sharesSum(x,s)} make ${f}. That claim is true.`:`Each group really gets ${x}, not ${b}: ${sharesSum(x,s)} make ${f}. That claim is false.`}]})}const fdpP1M03Generator={id:SHARING_ID,microSkillId:"FDP.P1.M03",forms:["equal_sharing"],generate(n){const t=parseSharing(n.stem);if(t){const{total:r,groups:a}=t;if(a<2||r<a||r>30||r%a!==0)return null;const i=r/a;if(i<1||i>30||i!==answerInt(n))return null;const l=n.visualData,u=l&&l.type==="objects_grid"?l.emoji:"🔵";return buildSharingDemo(n,u,r,a,i)}const e=normaliseStem(n.stem),s=e.match(SHARED_BETWEEN_ONE_GOT)??e.match(SHARE_EACH_GETS_TOTAL);if(s){const r=Number(s[1]),a=Number(s[2]);if(r<2||a<1||a>30)return null;const i=a*r;if(i>30||i!==answerInt(n))return null;const l=n.visualData,u=l&&l.type==="objects_grid"?l.emoji:"🔵";return buildShareReverseDemo(n,u,a,r,i)}const o=e.match(SHARING_CLAIM)??e.match(SHARING_CLAIM_NAMED)??e.match(SHARING_CLAIM_ONE_NAME);if(o){const r=Number(o[1]),a=Number(o[2]),i=2;if(r<i||r>30||a<1||a>30||r%i!==0)return null;const l=r/i;if((l===a?"True":"False")!==normaliseStem(n.correctAnswer))return null;const d=n.visualData,m=d&&d.type==="objects_grid"?d.emoji:"🔵";return buildShareClaimDemo(n,m,r,i,a,l)}return null}},FRACTION_WORDS={"1/2":"one half","1/4":"one quarter","3/4":"three quarters","2/4":"two quarters"};function gcd$6(n,t){return t===0?n:gcd$6(t,n%t)}function reducedFraction(n,t){const e=gcd$6(n,t);return`${n/e}/${t/e}`}function shadedShapeFromVisual(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t;return e.equal===!1||e.total_parts!==2&&e.total_parts!==4||!Number.isInteger(e.shaded_parts)||e.shaded_parts<1||e.shaded_parts>=e.total_parts?null:{shape:e.shape,total:e.total_parts,shaded:e.shaded_parts}}function fractionFrames(n,t,e,s){const o={type:"fraction_shape",shape:t,total_parts:e,shaded_parts:0,color:n.visualData.color};return{unshaded:o,shadedPic:{...o,shaded_parts:s}}}const M01_ID="FDP.P2.M01.identify-fraction.v1";function claimedFraction(n){const t=normaliseStem(n).toLowerCase(),e=/\bone half\b/.test(t)||/\bhalf\b/.test(t),s=/\bone quarter\b/.test(t)||/\bquarter\b/.test(t);return e&&s?null:e?"1/2":s?"1/4":null}function buildClaimDemo(n,t,e,s,o,r){const a=reducedFraction(s,e),i=FRACTION_WORDS[`${s}/${e}`];if(!i)return null;const l=a===o?"True":"False";if(l!==normaliseStem(n.correctAnswer))return null;const u={total:e,shaded:s},{unshaded:d,shadedPic:m}=fractionFrames(n,t,e,s),h=numberWord(e),f=numberWord(s),g=s===1?"is":"are",b=l==="True",x=FRACTION_WORDS[o];return buildExample({generatorId:r,question:n,title:"Does this match the fraction?",computed:u,steps:[{visual_data:d,animation:"show_parts",caption:`This ${t} has ${e} equal parts.`,spoken:`To name a fraction, first we count the equal parts. This ${t} is cut into ${h} equal parts.`},{visual_data:m,animation:"shade_part",caption:`${s} of the ${e} parts ${g} shaded.`,spoken:`Next we count the parts that are shaded. ${capitalised(f)} of the ${h} equal parts ${g} shaded.`},{visual_data:m,animation:"write_fraction",caption:`${s} out of ${e} is ${i} — ${b?"true":"false"}.`,spoken:b?`${capitalised(f)} out of ${h} equal parts is ${i}. That matches the claim, so it's true.`:`${capitalised(f)} out of ${h} is ${i}, not ${x}. So that's false.`}]})}function buildNameFractionDemo(n,t,e,s,o){const r=FRACTION_WORDS[o];if(!r)return null;const a={total:e,shaded:s},{unshaded:i,shadedPic:l}=fractionFrames(n,t,e,s),u=numberWord(e),d=numberWord(s),m=s===1?"is":"are";return buildExample({generatorId:M01_ID,question:n,title:"What fraction is shaded?",computed:a,steps:[{visual_data:i,animation:"show_parts",caption:`This ${t} has ${e} equal parts.`,spoken:`To name a fraction, first we count the equal parts. This ${t} is cut into ${u} equal parts.`},{visual_data:l,animation:"shade_part",caption:`${s} of the ${e} parts ${m} shaded.`,spoken:`Next we count the parts that are shaded. ${capitalised(d)} of the ${u} equal parts ${m} shaded.`},{visual_data:l,animation:"write_fraction",caption:`${s} out of ${e} — that's ${r}.`,spoken:`${capitalised(d)} out of ${u} equal parts. We call that ${r}.`}]})}const CLAIM_SHOWS_FRACTION=/shows\s+(\d+)\/(\d+)\b/i,YES_RIGHT=/^yes,?\s*(?:he'?s|she'?s|they'?re)?\s*right$/i,NO_ITS_FRACTION=/^no,?\s*it'?s\s+(\d+)\/(\d+)$/i;function buildFractionClaimVerifyDemo(n,t,e,s,o=M01_ID){const r=CLAIM_SHOWS_FRACTION.exec(n.stem);if(!r)return null;const a=Number(r[1]),i=Number(r[2]);if(!Number.isInteger(a)||!i)return null;const l=reducedFraction(s,e),d=reducedFraction(a,i)===l,m=n.correctAnswer.trim();if(d){if(!YES_RIGHT.test(m))return null}else{const A=m.match(NO_ITS_FRACTION);if(!A||reducedFraction(Number(A[1]),Number(A[2]))!==l)return null}const h=FRACTION_WORDS[l];if(!h)return null;const[f,g]=l.split("/").map(Number),b={total:e,shaded:s,claimNumer:a,claimDenom:i,actualNumer:f,actualDenom:g},{unshaded:x,shadedPic:y}=fractionFrames(n,t,e,s),E=numberWord(e),k=numberWord(s),T=s===1?"is":"are";return buildExample({generatorId:o,question:n,title:"Does the claim match the picture?",computed:b,steps:[{visual_data:x,animation:"show_parts",caption:`This ${t} has ${e} equal parts.`,spoken:`To name a fraction, first we count the equal parts. This ${t} is cut into ${E} equal parts. Someone claims ${numberWord(a)} out of ${numberWord(i)} is shaded.`},{visual_data:y,animation:"shade_part",caption:`${s} of the ${e} parts ${T} shaded.`,spoken:`Next we count the parts that are shaded. ${capitalised(k)} of the ${E} equal parts ${T} shaded.`},{visual_data:y,animation:"write_fraction",caption:`${s} out of ${e} is ${h} — ${m}
71.`,spoken:d?`${capitalised(k)} out of ${E} is ${h}. The claim is right.`:`${capitalised(k)} out of ${E} is ${h}, not what was claimed. The claim is wrong.`}]})}const fdpP2M01Generator={id:M01_ID,microSkillId:"FDP.P2.M01",forms:["fraction_identification","visual_quantity_to_symbol","misconception_contrast_probe",""],generate(n){const t=shadedShapeFromVisual(n);if(!t)return null;const{shape:e,total:s,shaded:o}=t,r=claimedFraction(n.stem),a=normaliseStem(n.correctAnswer);if(r&&(a==="True"||a==="False"))return buildClaimDemo(n,e,s,o,r,M01_ID);const i=buildFractionClaimVerifyDemo(n,e,s,o);if(i)return i;const l=`${o}/${s}`,u=reducedFraction(o,s);return a===l?buildNameFractionDemo(n,e,s,o,l):a===u?buildNameFractionDemo(n,e,s,o,u):null}},M02_ID="FDP.P2.M02.recognise-parts.v1";function partitionFromVisual(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t;return{shape:e.shape,total:e.total_parts,equal:e.equal!==!1}}function claimedPartCount(n){const t=normaliseStem(n).toLowerCase();return/\bhalves\b/.test(t)||/\bhalf\b/.test(t)?{parts:2,word:"halves"}:/\bquarters\b/.test(t)||/\bquarter\b/.test(t)?{parts:4,word:"quarters"}:null}const EQUAL_PARTS_CLAIM=/\bparts .*\bequal\b/i;function buildPartitionDemo$1(n,t,e,s,o){const r=o.kind==="count"?s&&e===o.parts:s;if((r?"True":"False")!==normaliseStem(n.correctAnswer))return null;const i={total:e},l=n.visualData,u={type:"fraction_shape",shape:t,total_parts:l.total_parts,shaded_parts:l.shaded_parts,color:l.color,...l.equal!==void 0?{equal:l.equal}:{}},d={...u,show_part_numbers:!0},m=r?"true":"false",h=o.kind==="count"?`${capitalised(o.word)} — ${m}!`:`Equal — ${m}!`,f={...u,conclude_label:h},g=numberWord(e),b=s?"equal":"not equal",x=o.kind==="count"?`${e} ${s?"equal":"unequal"} parts — ${r?"true":"false"}.`:`The parts are ${b} — ${r?"true":"false"}.`,y=numberWord(o.kind==="count"?o.parts:0),E=[];o.kind==="count"&&e!==o.parts&&E.push(e<o.parts?`there are only ${g} parts`:`there are ${g} parts`),s||E.push(E.length?"they are not equal":"the parts are not equal");const k=o.kind==="count"?r?`${capitalised(g)} equal parts. So it is divided into ${o.word}. That's true.`:`${capitalised(o.word)} means ${y} equal parts. Here ${E.join(", and ")}. So it is not divided into ${o.word}. That's false.`:r?"The parts are the same size. So the parts are equal. That's true.":"The parts are different sizes. So the parts are not equal. That's false.";return buildExample({generatorId:M02_ID,question:n,title:"Are the parts equal?",computed:i,steps:[{visual_data:u,animation:"show_parts",caption:`Look at how the ${t} is cut.`,spoken:`Look carefully at how this ${t} is cut into parts.`},{visual_data:d,animation:"check_parts",caption:`Count the parts: ${e}.`,spoken:`Count the parts. There are ${g} parts. Check they are the same size.`},{visual_data:f,animation:"conclude_parts",caption:x,spoken:k}]})}const fdpP2M02Generator={id:M02_ID,microSkillId:"FDP.P2.M02",forms:["visual_quantity_to_symbol","fraction_identification"],generate(n){const t=shadedShapeFromVisual(n);if(t){const l=claimedFraction(n.stem),u=normaliseStem(n.correctAnswer);if(l&&(u==="True"||u==="False")){const m=buildClaimDemo(n,t.shape,t.total,t.shaded,l,M02_ID);if(m)return m}const d=buildFractionClaimVerifyDemo(n,t.shape,t.total,t.shaded,M02_ID);if(d)return d}const e=partitionFromVisual(n);if(!e)return null;const{shape:s,total:o,equal:r}=e,a=normaliseStem(n.correctAnswer);if(a!=="True"&&a!=="False"||o!==2&&o!==4)return null;const i=normaliseStem(n.stem);if(/\bdivided into\b/i.test(i)){const l=claimedPartCount(i);return l?buildPartitionDemo$1(n,s,o,r,{kind:"count",parts:l.parts,word:l.word}):null}return EQUAL_PARTS_CLAIM.test(i)?buildPartitionDemo$1(n,s,o,r,{kind:"equal"}):null}},fdpY1Generators=[fdpP1M03Generator,fdpP2M01Generator,fdpP2M02Generator],BLUE$1="#2563EB",SHAPE_COUNT$1={triangle:3,square:4,rectangle:4,pentagon:5,hexagon:6,circle:0},SHAPE_WORDS=Object.keys(SHAPE_COUNT$1);function asDrawableShape(n){if(!n)return null;const t=n.trim().toLowerCase();return SHAPE_WORDS.includes(t)?t:null}function soleStemShape(n){const t=normaliseStem(n).toLowerCase(),e=new Set;for(const s of SHAPE_WORDS)new RegExp(`\\b${s}s?\\b`).test(t)&&e.add(s);return e.size===1?[...e][0]:null}function visualShape(n){const t=n.visualData;
71return t&&t.type==="shape_svg"?asDrawableShape(t.shape):null}function soleStemInt(n){const t=normaliseStem(n).match(/\d+/g);return t&&t.length===1?Number(t[0]):null}const SIDES_VOCAB={plural:"sides",singular:"side",concept:"Sides are the straight edges.",countAnim:"trace_sides",totalAnim:"write_total_sides",nameAnim:"name_from_sides"},CORNERS_VOCAB={plural:"corners",singular:"corner",concept:"Corners are where two sides meet.",countAnim:"count_corners",totalAnim:"write_total_corners",nameAnim:"name_from_corners"};function noun(n,t){return t===1?n.singular:n.plural}function picture(n){return{type:"shape_svg",shape:n,color:BLUE$1}}const NEUTRAL_PALETTE=["#ec4899","#f59e0b","#10b981","#3b82f6","#8b5cf6","#ef4444"];function candidateShapes(n){const t=n.visualData,e=[];if(t&&t.type==="shape_options"){const s=t,o=s.colors??[];s.shapes.forEach((r,a)=>{const i=asDrawableShape(r);i&&e.push({shape:i,color:o[a]??NEUTRAL_PALETTE[a%NEUTRAL_PALETTE.length]})})}else(n.options??[]).forEach((s,o)=>{const r=asDrawableShape(s);r&&e.push({shape:r,color:NEUTRAL_PALETTE[o%NEUTRAL_PALETTE.length]})});return e.length>=2?e:[]}function buildCandidatesDemo(n,t,e,s,o,r){const a=s.map(d=>({shape:d.shape,color:d.color,label:capitalised(d.shape),highlight:d.shape===o})),i={type:"shape_svg",shape:s[0].shape,color:s[0].color,compare:a},l=picture(o),u=e.countAnim;return buildExample({generatorId:t,question:n,title:"Which shape is it?",computed:{[e.plural]:r,answer:r},steps:[{visual_data:i,animation:"show_options",caption:"Look at each shape.",spoken:`Look at each shape. We want the one with ${numberWord(r)} ${noun(e,r)}.`},{visual_data:l,animation:u,caption:`Count each ${e.singular}: ${countingRun(1,r)}.`,spoken:`Count the ${e.plural} on this one: ${countingRun(1,r)}.`},{visual_data:l,animation:e.nameAnim,caption:`${r} ${noun(e,r)} — that's a ${o}.`,spoken:`${/^[aeiou]/i.test(o)?"An":"A"} ${o.toLowerCase()} has ${numberWord(r)} ${noun(e,r)}. That is the shape.`}]})}function quadNaming(n,t){return n==="square"?{caption:`4 ${t.plural}, all 4 sides the same length — that's a square.`,spoken:`Four ${t.plural}. A rectangle has four too, so look at the sides: all four are the same length. That makes a square.`}:n==="rectangle"?{caption:`4 ${t.plural}: 2 long sides, 2 short — that's a rectangle.`,spoken:`Four ${t.plural}. A square has four too, so look at the sides: two are long and two are short. That makes a rectangle.`}:null}function roundCount(n){return n.singular==="side"?`straight ${n.plural}`:n.plural}function buildNameDemo(n,t,e,s,o){const r=picture(s);capitalised(s);const a=o<1,i={[e.plural]:o,answer:o,...quadNaming(s,e)?{sides:4,pairs:2}:{}},l=a?`It is round with 0 ${roundCount(e)}.`:`Count each ${e.singular}: ${countingRun(1,o)}.`,u=a?`A circle is perfectly round — it has no ${roundCount(e)} to count.`:`Count each ${e.singular} as we go around: ${countingRun(1,o)}.`,d=quadNaming(s,e),m=a?`Round with no ${e.plural} — that's a ${s}.`:d?.caption??`${o} ${noun(e,o)} — that's a ${s}.`,h=a?`A round shape with no ${roundCount(e)} is a ${s}.`:d?.spoken??`${capitalised(numberWord(o))} ${noun(e,o)} makes a ${s}. Counting the ${e.plural} tells us the name.`;return buildExample({generatorId:t,question:n,title:"What shape is this?",computed:i,steps:[{visual_data:r,animation:"show_shape",caption:`To name a shape, count its ${e.plural}.`,spoken:`To name a shape, count its ${e.plural} — ${e.concept.toLowerCase()}`},{visual_data:r,animation:e.countAnim,caption:l,spoken:u},{visual_data:r,animation:e.nameAnim,caption:m,spoken:h}]})}function buildNameWithOptionsDemo(n,t,e,s,o,r){const a=s.map(x=>({shape:x.shape,color:x.color,label:capitalised(x.shape),highlight:!1})),i={type:"shape_svg",shape:s[0].shape,color:s[0].color,compare:a},l=picture(o),u={[e.plural]:r,answer:r,...quadNaming(o,e)?{sides:4,pairs:2}:{}},d=r<1,m=d?`It is round with 0 ${roundCount(e)}.`:`Count each ${e.singular}: ${countingRun(1,r)}.`,h=d?`A circle is perfectly round — it has no ${roundCount(e)} to count.`:`Count each ${e.singular} as we go around: ${countingRun(1,r)}.`,f=quadNaming(o,e),g=d?`Round with no ${e.plural} — that's a ${o}.`:f?.caption??`${r} ${noun(e,r)} — that's a ${o}.`,b=d?`A round shape with no ${roundCount(e)} is a ${o}.`:f?.spoken??`${capitalised(numberWord(r))} ${noun(e,r)} makes a ${o}. Counting the ${e.plural} tells us the name.`;return buildExample({generatorId:t,question:n,title:"What shape is this?",computed:u,steps:[{visual_data:i,animation:"show_options",caption:"Look at each shape.",spoken:"Look at each shape. Let's work out which one this is."},{visual_data:l,animation:e.countAnim,caption:m,spoken:h},{visual_data:l,animation:e.nameAnim,caption:g,spoken:b}]})}function nameHandler(n,t,e){const s=asDrawableShape(n.correctAnswer);if(!s)return null;const o=SHAPE_COUNT$1[s],r=visualShape(n);if(r){if(r!==s)return null;const l=candidateShapes(n);return l.some(u=>u.shape===s)?buildNameWithOptionsDemo(n,t,e,l,s,o):buildNameDemo(n,t,e,s,o)}const a=soleStemInt(n.stem);if(a===null||a!==o||o<1)return null;const i=candidateShapes(n);return i.some(l=>l.shape===s)?buildCandidatesDemo(n,t,e,i,s,o):buildNameDemo(n,t,e,s,o)}function buildVerdictDemo(n,t,e,s,o,r,a){const i=picture(s),l={[e.plural]:o,claim:r};return buildExample({generatorId:t,question:n,title:`Counting ${e.plural}`,computed:l,steps:[{visual_data:i,animation:"show_shape",caption:`A ${s}. ${e.concept}`,spoken:`Here's a ${s}. ${e.concept} Let's count them.`},{visual_data:i,animation:e.countAnim,caption:`Count each ${e.singular}: ${countingRun(1,o)}.`,spoken:`Count each ${e.singular} as we go around: ${countingRun(1,o)}.`},{visual_data:i,animation:e.totalAnim,caption:`A ${s} has ${o} ${noun(e,o)}. So: ${a}.`,spoken:`A ${s} has ${numberWord(o)} ${noun(e,o)}, so the answer is ${a.toLowerCase()}.`}]})}function yesNoHandler(n,t,e,s){const r=normaliseStem(n.stem).match(new RegExp(`^does an? (\\w+) have (\\d+) ${s.source}\\??$`,"i"));if(!r)return null;const a=asDrawableShape(r[1]);if(!a)return null;const i=SHAPE_COUNT$1[a];if(i<1)return null;const l=Number(r[2]),u=i===l?"yes":"no";return u!==n.correctAnswer.trim().toLowerCase()?null:buildVerdictDemo(n,t,e,a,i,l,capitalised(u))}function trueFalseHandler(n,t,e,s){const r=normaliseStem(n.stem).match(new RegExp(`^true or false:? an? (\\w+) has (\\d+) ${s.source}\\.?$`,"i"));if(!r)return null;const a=asDrawableShape(r[1]);if(!a)return null;const i=SHAPE_COUNT$1[a];if(i<1)return null;const l=Number(r[2]),u=i===l?"true":"false";return u!==n.correctAnswer.trim().toLowerCase()?null:buildVerdictDemo(n,t,e,a,i,l,capitalised(u))}function circleHandler(n,t,e){if((visualShape(n)??soleStemShape(n.stem))!=="circle"||n.correctAnswer.trim()!=="0")return null;const o=picture("circle"),r={...o,trace_curve:!0},a={[e.plural]:0,answer:0};return buildExample({generatorId:t,question:n,title:`Counting ${e.plural}`,computed:a,steps:[{visual_data:o,animation:"show_shape",caption:`A circle. ${e.concept}`,spoken:`Here's a circle. ${e.concept} Let's look for them.`},{visual_data:r,animation:e.countAnim,caption:"Trace around: one smooth, curved edge.",spoken:`Trace all the way around the circle — it is one smooth, curved edge with no ${e.singular==="side"?"straight edges":"corners"}.`},{visual_data:o,animation:e.totalAnim,caption:`A circle has 0 ${e.singular==="side"?"straight sides":"corners"}.`,spoken:`We found nothing to count, so the answer is zero. A circle has zero ${e.singular==="side"?"straight sides":"corners"}.`}]})}function compareCornersHandler(n,t){const e=CORNERS_VOCAB,s=asDrawableShape(n.correctAnswer);if(!s)return null;const o=normaliseStem(n.stem).toLowerCase(),r=candidateShapes(n);if(!r.length||!r.some(m=>m.shape===s))return null;const a={};r.forEach((m,h)=>a[`c${h}`]=SHAPE_COUNT$1[m.shape]);let i,l;if(/\bmost\b/.test(o)){const m=Math.max(...r.map(f=>SHAPE_COUNT$1[f.shape])),h=r.filter(f=>SHAPE_COUNT$1[f.shape]===m);if(h.length!==1||h[0].shape!==s)return null;i=`A ${s} has ${SHAPE_COUNT$1[s]} ${e.plural} — the most ${e.plural}.`,l=`The ${s} has ${numberWord(SHAPE_COUNT$1[s])} ${e.plural} — the most of all.`}else if(/\bfewer\b|\bfewest\b|\bleast\b/.test(o)){const m=soleStemInt(n.stem);if(m===null)return null;const h=r.filter(f=>SHAPE_COUNT$1[f.shape]<m);if(h.length!==1||h[0].shape!==s)return null;a.claim=m,i=`A ${s} has ${SHAPE_COUNT$1[s]} ${e.plural} — fewer than ${m}.`,l=`The ${s} has ${numberWord(SHAPE_COUNT$1[s])} ${e.plural}. That is fewer than ${numberWord(m)}.`}else return null;const u=r.map(m=>({shape:m.shape,color:m.color,label:capitalised(m.shape),count:SHAPE_COUNT$1[m.shape],highlight:m.shape===s})),d=()=>({type:"shape_svg",shape:r[0].shape,color:r[0].color,compare:u});return buildExample({generatorId:t,question:n,title:`Counting ${e.plural}`,computed:a,steps:[{visual_data:d(),animation:"compare_corners",caption:`${e.concept}`,spoken:`${e.concept} Let's count the corners on each shape.`},{visual_data:d(),animation:"compare_corners",caption:`Count the ${e.plural} on each shape.`,spoken:`Count the ${e.plural} on every shape.`},{visual_data:d(),animation:"compare_corners",caption:i,spoken:l}]})}const SIDES_NOUN=/(?:sides?)/,CORNERS_NOUN=/(?:vert(?:ex|ices)|corners?)/,geoP3M01Y1Generator={id:"GEO.P3.M01.name-and-check-sides.v1",microSkillId:"GEO.P3.M01",forms:["shape_property","symbolic_choice","symbolic_numeric_entry"],generate(n){const t=geoP3M01Y1Generator.id,e=SIDES_VOCAB;return nameHandler(n,t,e)??yesNoHandler(n,t,e,SIDES_NOUN)??trueFalseHandler(n,t,e,SIDES_NOUN)??circleHandler(n,t,e)}},geoP3M02Y1Generator={id:"GEO.P3.M02.name-and-check-corners.v1",microSkillId:"GEO.P3.M02",forms:["shape_property","symbolic_choice","symbolic_numeric_entry"],generate(n){const t=geoP3M02Y1Generator.id,e=CORNERS_VOCAB;return nameHandler(n,t,e)??yesNoHandler(n,t,e,CORNERS_NOUN)??trueFalseHandler(n,t,e,CORNERS_NOUN)??compareCornersHandler(n,t)??circleHandler(n,t,e)}},geoY1Generators=[geoP3M01Y1Generator,geoP3M02Y1Generator];function isTeen(n){return Number.isInteger(n)&&n>=11&&n<=19}function symbolWord(n){return n==="<"?"less than":n===">"?"greater than":"equal to"}function symbolFor(n,t){return n<t?"<":n>t?">":"="}function pairedTeens(n){const t=n.visualData;if(!t||t.type!=="ten_frame_and_ones_paired")return null;const{left:e,right:s}=t;return!e||!s||!Number.isInteger(e.tens)||!Number.isInteger(e.ones)||!Number.isInteger(s.tens)||!Number.isInteger(s.ones)?null:{left:e.tens*10+e.ones,right:s.tens*10+s.ones}}const teenComparison={id:"NPV.P3.M03.teen-compare-symbol.v1",microSkillId:"NPV.P3.M03",forms:["teen_comparison"],generate(n){const t=pairedTeens(n);if(!t)return null;const{left:e,right:s}=t;if(!isTeen(e)||!isTeen(s))return null;const o=symbolFor(e,s);if(o!==n.correctAnswer.trim())return null;const r=e-10,a=s-10,i=Math.max(e,s),l=e===s,u={left:e,right:s,onesLeft:r,onesRight:a,bigger:i,answer:o==="<"?-1:o===">"?1:0},d=l?`Ones: ${r} and ${a} — the same.`:`Ones: ${r} and ${a}.`,m=l?`${capitalised(numberWord(r))} loose ones on each side — the same. So the numbers are equal.`:`${capitalised(numberWord(r))} loose ones and ${numberWord(a)} loose ones. ${capitalised(numberWord(Math.max(r,a)))} is more than ${numberWord(Math.min(r,a))}, so ${numberWord(i)} is greater.`,h=`${e} ${o} ${s}`,f=l?`${capitalised(numberWord(e))} is equal to ${numberWord(s)}.`:`${capitalised(numberWord(e))} is ${symbolWord(o)} ${numberWord(s)}. The open end of the symbol faces the greater number.`;return buildExample({generatorId:this.id,question:n,title:`Which symbol goes between ${e} and ${s}?`,computed:u,steps:[{visual_data:{type:"comparison",left:e,right:s,symbol:null},animation:"show_numbers",caption:`Compare ${e} and ${s}.`,spoken:`Let's compare ${numberWord(e)} and ${numberWord(s)}. Both have one full ten, so the tens are the same and the loose ones will decide.`},{visual_data:{type:"comparison",left:e,right:s,symbol:null},animation:"highlight_ones",caption:d,spoken:m},{visual_data:{type:"comparison",left:e,right:s,symbol:o},animation:"write_symbol",caption:h,spoken:f}]})}};function dragOrder(n){const t=n.visualData;if(!t||t.type!=="drag_order"||!Array.isArray(t.numbers)||t.numbers.length<2)return null;const e=t.numbers.filter(s=>Number.isInteger(s));return e.length!==t.numbers.length||t.direction!=="asc"&&t.direction!=="desc"?null:{numbers:e,direction:t.direction}}const teenOrdering={id:"NPV.P3.M03.teen-order.v1",microSkillId:"NPV.P3.M03",forms:["teen_ordering"],generate(n){const t=dragOrder(n);if(!t)return null;const{numbers:e,direction:s}=t;if(!e.every(isTeen))return null;const o=s==="asc",r=[...e].sort((f,g)=>o?f-g:g-f);if(r.join(",")!==n.correctAnswer.trim())return null;const a=r[0],i=o?"smallest":"largest",l=e.indexOf(a),u=[...e.slice(0,l),...e.slice(l+1)];let d=`Then compare the ones of the rest: ${u.map(f=>numberWord(f%10)).join(", ")}. ${o?"Fewest":"Most"} ones next, and so on.`;if(u.length===2){const[f,g]=u,b=o?Math.min(f,g):Math.max(f,g),x=b===f?g:f,y=o?"fewer":"more";d=`Of ${numberWord(f)} and ${numberWord(g)}, ${numberWord(b)} has ${y} ones — ${numberWord(b)} next, then ${numberWord(x)}.`}const m={count:e.length,answer:a};r.forEach((f,g)=>{m[`pos${g+1}`]=f,m[`ones${g+1}`]=f%10});const h=r.map((f,g)=>g===0?f:null);return buildExample({generatorId:this.id,question:n,title:o?"Order from smallest to largest":"Order from largest to smallest",computed:m,steps:[{visual_data:{type:"sequence",values:[...e],step:1,hide_hop:!0},animation:"highlight_pattern",caption:`Find the ${i} first.`,spoken:`All these numbers have one ten, so look at the ones. Find the ${i} number first.`},{visual_data:{type:"sequence",values:h,step:1,hide_hop:!0},animation:"place_in_order",caption:`Put ${a} first, then carry on.`,spoken:`${capitalised(numberWord(a))} has the ${o?"fewest":"most"} ones, so it goes first. ${d}`},{visual_data:{type:"sequence",values:[...r],step:1,hide_hop:!0},animation:"write_in",caption:`Ones ${r.map(f=>f%10).join(", ")}: ${r.join(", ")}`,spoken:`The ones in order: ${r.map(f=>numberWord(f%10)).join(", ")}. So the numbers in order are ${r.map(f=>numberWord(f)).join(", ")}.`}]})}},npvCompareY1Generators=[teenComparison,teenOrdering],WORD_TO_INT={zero:0,one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12,thirteen:13,fourteen:14,fifteen:15,sixteen:16,seventeen:17,eighteen:18,nineteen:19,twenty:20};function firstNumberWord(n){for(const t of n.toLowerCase().matchAll(/[a-z]+/g)){const e=WORD_TO_INT[t[0]];if(e!==void 0)return e}return null}const npvP2M01Generator={id:"NPV.P2.M01.count-to-20.v1",microSkillId:"NPV.P2.M01",forms:["count_to_numeral","number_line_identify","word_to_numeral","word_to_typed_numeral"],generate(n){const t=answerInt(n);if(t===null||t<0||t>20)return null;const e=n.visualData;if(e&&e.type==="number_line"){const s=e;if(s.unknown===null||s.unknown===void 0||s.unknown!==t)return null;const o=(s.marked??[]).filter(r=>Number.isInteger(r)).sort((r,a)=>r-a);return o.length===0?null:buildMarkerReadDemo(n,s.min,s.max,o,t)}if(n.mathematicalForm==="count_to_numeral")return e&&e.type==="tally"?e.count!==t||t<1?null:buildTallyCountDemo(n,t):!e||e.type!=="objects_grid"||e.count!==t?null:e.emoji&&t>=1?buildObjectsCountDemo(n,t,e.emoji):buildCountDemo(n,t,"count");if(n.mathematicalForm==="word_to_numeral"||n.mathematicalForm==="word_to_typed_numeral"){const s=firstNumberWord(n.stem);if(s===null||s!==t)return null;const o=n.mathematicalForm==="word_to_typed_numeral"?"typed":"word";return buildCountDemo(n,t,o)}return null}};function objectNounFromStem(n){const t=n.match(/how many ([a-z]+)\b/i),e=t?t[1].toLowerCase():null;return e&&!["in","are","is","altogether","there","do","does"].includes(e)?e:"objects"}function tensCountingRun(n){const t=[];for(let e=1;e<=n;e++)t.push(numberWord(e*10));return t.join(", ")}function buildCountDemo(n,t,e){const s=Math.floor(t/10),o=t%10,r=s>=1,a={type:"ten_frame_and_ones",tens:0,ones:1},i={type:"ten_frame_and_ones",tens:s,ones:o},l=s*10,u={count:t,tens:s,ones:o,tenTotal:l,answer:t},d=e==="count"?objectNounFromStem(n.stem):"counters",m=e==="count"?`Count the ${d} — each counter is one.`:`Start counting the ${d}, one at a time.`,h=e==="count"?`Here are the ${d} — each counter stands for one. Count them one by one, starting with the first.`:`Here are the ${d}. Count them one by one, starting with the first.`,f=r?`Count in tens: ${tensCountingRun(s)}.`:`Count on: ${countingRun(1
71,t)}.`,g=r?o>0?`A full ten-frame holds ten, so we count the full tens first: ${tensCountingRun(s)}. Then we count on the extra ones: ${countingRun(l+1,t)}.`:`A full ten-frame holds ten, so we count the full tens: ${tensCountingRun(s)}.`:`We touch each counter once, so none are missed or counted twice: ${countingRun(1,t)}.`,b=r&&o>0?`${l} + ${o} = ${t}.`:`That makes ${t}.`,x=r&&o>0?`${capitalised(numberWord(l))} and ${numberWord(o)} more makes ${numberWord(t)}. So there are ${numberWord(t)} altogether.`:`The last number we say tells us how many there are. Altogether that makes ${numberWord(t)}.`,y=e==="typed"?r?`${s} tens and ${o} extra ones — write ${s} then ${o}: ${t}.`:`${b} Write ${t}.`:b,E=e==="typed"?r?`When we write a number, the tens come first and then the ones. ${capitalised(numberWord(s))} tens and ${numberWord(o)} extra ones, so we write ${numberWord(s)}, then ${numberWord(o)}. That makes ${numberWord(t)}.`:`${x} Write ${numberWord(t)}.`:x;return buildExample({generatorId:npvP2M01Generator.id,question:n,title:"How do we count to find how many?",computed:u,steps:[{visual_data:a,animation:"show_objects",caption:m,spoken:h},{visual_data:i,animation:"highlight_ten",caption:f,spoken:g},{visual_data:i,animation:"reveal_total",caption:y,spoken:E}]})}function buildObjectsCountDemo(n,t,e){const s=objectNounFromStem(n.stem),o={type:"objects_grid",emoji:e,count:t,group_size:t};return buildExample({generatorId:npvP2M01Generator.id,question:n,title:`How many ${s}?`,computed:{count:t,answer:t},steps:[{visual_data:{...o,numbered:0},animation:"show_objects",caption:`Count the ${s}. Touch each one once.`,spoken:`Here are the ${s}. We touch each one once and say one number for it, so none are missed or counted twice.`},{visual_data:{...o,numbered:t},animation:"count_objects",caption:"Touch each one and say its number.",spoken:`Start at the first one and go along each row: ${countingRun(1,t)}.`},{visual_data:{...o,numbered:t,total_label:`${t} ${s}`},animation:"reveal_total",caption:`The last number we said is ${t}.`,spoken:`The last number we said was ${numberWord(t)}. That tells us how many: ${numberWord(t)} ${s}.`}]})}function buildTallyCountDemo(n,t){const e=Math.floor(t/5),s=t%5,o=Array.from({length:e},(l,u)=>(u+1)*5),r=Array.from({length:s},(l,u)=>e*5+u+1),a={count:t,answer:t,gates:e,five:5};o.forEach((l,u)=>{a[`five_${u+1}`]=l}),r.forEach((l,u)=>{a[`extra_${u+1}`]=l});const i={type:"tally",count:t};return buildExample({generatorId:npvP2M01Generator.id,question:n,title:"How many does the tally show?",computed:a,steps:[{visual_data:i,animation:"show_tally",caption:"A gate of tally marks is worth 5.",spoken:"Each mark in a tally stands for one thing. Four marks with one line across make a gate, and a gate is five."},{visual_data:i,animation:"count_groups",caption:e>0?`Count the gates in fives: ${o.join(", ")}.`:"There are no full gates.",spoken:e>0?`Gates are quick to count, so we start with them. Each gate is five, so we count in fives: ${o.map(numberWord).join(", ")}.`:"There are no full gates, so we count every mark."},{visual_data:i,animation:"count_extras",caption:s>0?`Then the loose marks: ${r.join(", ")}. That's ${t}.`:`That's ${t}.`,spoken:s>0?`Now the loose marks, one at a time, carrying on from ${numberWord(e*5)}: ${r.map(numberWord).join(", ")}. The last number tells us the tally shows ${numberWord(t)}.`:`Every mark is in a gate, so that's ${numberWord(t)}.`}]})}function buildMarkerReadDemo(n,t,e,s,o){const r=[...s].reverse().find(E=>E<=o)??s[0],a=s.find(E=>E>=o),i=a!==void 0&&r<=o&&a-o<o-r,l=i?a:r,u=Math.abs(o-l),d=i?-1:1,m={landmark:l,gap:u,one:1,answer:o},h={type:"number_line",min:t,max:e,marked:s,marker_at:o},f=Array.from({length:u},(E,k)=>({from:l+d*k,to:l+d*(k+1),label:i?"-1":"+1"})),g={type:"number_line",min:t,max:e,marked:s,marker_at:o,...u>1?{hops:[f[0]]}:{}},b={type:"number_line",min:t,max:e,marked:s,marker_at:o,hops:f,unknown:o},x=u===1?"step":"steps",y=i?"back":"on";return buildExample({generatorId:npvP2M01Generator.id,question:n,title:"How do we read the marker?",computed:m,steps:[{visual_data:h,animation:"show_landmarks",caption:"Find the nearest number label.",spoken:"There is the marker. It has no number on it, so we need a number we already know. Find the labelled number closest to it."},{visual_data:g,animation:"count_on",caption:`Count ${y} from ${l}, one step at a time.`,spoken:`The labelled number closest to the marker is ${numberWord(l)}. We start there and count ${y} to the marker, one step at a time.`},{visual_data:b,animation:"reveal_marker",caption:`${u} ${x} ${y} from ${l} is ${o}.`,spoken:`We counted ${numberWord(u)} ${x} ${y} from ${numberWord(l)} and landed on ${numberWord(o)}. So the marker shows ${numberWord(o)}.`}]})}function oneStepFromLine(n){const t=n.hops?.[0];if(!t||!Number.isInteger(t.from)||!Number.isInteger(t.to))return null;const e=t.to-t.from;return e!==1&&e!==-1?null:{start:t.from,delta:e}}function storyLinkFromStem(n,t){const s=normaliseStem(n).match(/\b([A-Z][a-z]+)\s+(ate|lost|dropped|gave away|spent|broke|popped)\s+1\b/);if(!s)return null;const[,o,r]=s;return`${o} ${r} one, so one step ${t===-1?"back":"on"}.`}function makeNumberLineOneStepGenerator(n,t){return{id:n,microSkillId:t,forms:["number_line_identify"],generate(e){const s=answerInt(e);if(s===null||s<0||s>20)return null;const o=e.visualData;if(!o||o.type!=="number_line")return null;const r=o,a=oneStepFromLine(r);if(!a||a.start+a.delta!==s)return null;const i=storyLinkFromStem(e.stem,a.delta);return buildOneStepDemo(n,t,e,r.min,r.max,a.start,a.delta,s,i?{step1Caption:i,step1Spoken:i}:void 0)}}}const npvP2M03Generator=makeNumberLineOneStepGenerator("NPV.P2.M03.count-back-one.v1","NPV.P2.M03");function oneStepFromStem(n){const t=normaliseStem(n).toLowerCase(),e=t.match(/^(\d+) ([+-]) 1 =/);if(e)return{base:Number(e[1]),delta:e[2]==="+"?1:-1};const s=t.match(/\b(?:one|1) (more|less) than (\d+)\b/);if(s)return{base:Number(s[2]),delta:s[1]==="more"?1:-1};const o=t.match(/^(\d+)\b.*?\b1 \w+ away\b/);if(o)return{base:Number(o[1]),delta:-1};const r=t.match(/^(\d+)\b.*?\b1 more\b/);return r?{base:Number(r[1]),delta:1}:null}function directionMistakeOverride(n,t){const s=normaliseStem(n).match(/^([A-Z][a-z]+) says (?:one|1) (more|less) than (\d+) is (\d+)\b/);if(!s)return null;const[,o,,r,a]=s,i=Number(a)-Number(r);
71if(i===t)return null;const l=i>0?"up":"down";return`${o} jumped ${l} — one ${t===1?"more":"less"} means jump ${t===1?"UP":"DOWN"}.`}const npvP2M04Generator={id:"NPV.P2.M04.one-more-one-less.v1",microSkillId:"NPV.P2.M04",forms:["number_line_identify","equation_completion","numeral_to_set_probe"],generate(n){const t=answerInt(n);if(t===null||t<0||t>20)return null;const e=n.visualData;if(n.mathematicalForm==="number_line_identify"){if(!e||e.type!=="number_line")return null;const i=e,l=oneStepFromLine(i);if(!l||l.start+l.delta!==t)return null;const u=storyLinkFromStem(n.stem,l.delta);return buildOneStepDemo(this.id,this.microSkillId,n,i.min,i.max,l.start,l.delta,t,u?{step1Caption:u,step1Spoken:`${u} First we find ${numberWord(l.start)} on the number line.`}:void 0)}const s=oneStepFromStem(n.stem);if(!s||s.base+s.delta!==t||s.base<0||s.base>20)return null;const{min:o,max:r}=windowAround(s.base,t),a=directionMistakeOverride(n.stem,s.delta);return buildOneStepDemo(this.id,this.microSkillId,n,o,r,s.base,s.delta,t,a?{step1Caption:a,step1Spoken:`${a.replace(/ — /,", but ").replace(/\b(UP|DOWN)\b/,i=>i.toLowerCase())} First we find ${numberWord(s.base)} on the number line.`}:void 0)}};function windowAround(n,t){const e=Math.min(n,t),s=Math.max(n,t);return{min:Math.max(0,e-3),max:Math.min(20,s+3)}}function buildOneStepDemo(n,t,e,s,o,r,a,i,l){const u=a===1,d=u?"more":"less",m=u?"count on":"count back",h={start:r,answer:i,one:1},f={type:"number_line",min:s,max:o,marked:[r],pulse_steps:[1,2]},g={type:"number_line",min:s,max:o,marked:[r],marker_at:i,pulse_steps:[1,2]},b={type:"number_line",min:s,max:o,marked:[r],marker_at:i,hops:[{from:r,to:i,label:u?"+1":"-1"}],unknown:i,pulse_steps:[1,2]};return buildExample({generatorId:n,question:e,title:u?"How do we find one more?":"How do we find one less?",computed:h,steps:[{visual_data:f,animation:"highlight_start",caption:l?.step1Caption??`One ${d} means a jump of 1.`,spoken:l?.step1Spoken??`One ${d} means one jump along the number line. First we find ${numberWord(r)}, where we start.`},{visual_data:g,animation:"show_hop_direction",caption:`Start at ${r} and ${m} 1.`,spoken:u?`Numbers get bigger as we go right, so one more is one step to the right. From ${numberWord(r)}, we count on just one step.`:`Numbers get smaller as we go left, so one less is one step to the left. From ${numberWord(r)}, we count back just one step.`},{visual_data:b,animation:"reveal_result",caption:`One ${d} than ${r} is ${i}.`,spoken:`The jump lands on ${numberWord(i)}. So one ${d} than ${numberWord(r)} is ${numberWord(i)}.`}]})}function realBars(n){return n.bars.filter(t=>!/ruler/i.test(t.label)&&Number.isInteger(t.length)&&t.label.trim().length>0).map(t=>({label:t.label,length:t.length,color:t.color,...Number.isInteger(t.start)&&(t.start??0)>0?{start:t.start}:{}}))}const meaP4M04Generator={id:"MEA.P4.M04.measure-length.v1",microSkillId:"MEA.P4.M04",forms:["comparative_length","symbolic_numeric_entry"],generate(n){const t=n.visualData;if(!t||t.type!=="measure_bars")return null;const e=realBars(t),s=t.unit??"cm";if(n.mathematicalForm==="comparative_length"){if(e.length<2||e.length>3)return null;const o=normaliseStem(n.stem).toLowerCase(),r=/\bshort/.test(o),a=/\blong/.test(o);if(r===a)return null;const i=o.match(/\b(longer|shorter) than (\d+)\s*cm\b/);if(i)return buildThresholdDemo(n,e,Number(i[2]),i[1]==="shorter",s);if(/\bthan \d+\s*(?:cm|centimetres?|m|metres?)\b/.test(o))return null;const l=n.correctAnswer.trim(),u=[...e].sort((h,f)=>r?h.length-f.length:f.length-h.length);if(e.every(h=>h.length===e[0].length))return/^they are the same\b/i.test(l)?buildCompareTieDemo(n,e,s,r):null;if(u[0].length===u[1].length)return null;const d=u[0];if(l!==d.label&&l!==`${d.label}'s`)return null;const m=l===`${d.label}'s`;return buildCompareDemo$1(n,e,d,s,r,m)}if(n.mathematicalForm==="symbolic_numeric_entry"){const o=answerInt(n);if(o===null)return null;const r=e.find(i=>i.length===o);if(!r)return null;const a=t.bars.find(i=>/ruler/i.test(i.label)&&Number.isInteger(i.length)&&i.length>0);return buildMeasureDemo(n,r,s,a?.length)}return null}};function buildMeasureDemo(n,t,e,s){const o=t.length,r={length:o,answer:o,zero:0},a=objectNoun(t.label),i=l=>({type:"measure_bars",bars:[...s&&s>o?[{label:"ruler",length:s,color:"#94a3b8"}]:[],{label:t.label,length:o,color:t.color}],unit:e,show_values:l});return buildExample({generatorId:meaP4M04Generator.id,question:n,title:"How do we measure in centimetres?",computed:r,steps:[{visual_data:i(!1),animation:"show_object",caption:`Line the ${a} up with 0.`,spoken:`We measure length in centimetres. Line the ${a} up with the start of the ruler.`},{visual_data:i(!1),animation:"count_cm",caption:"Count each centimetre from 0.",spoken:`A ruler starts at zero, not one. So we start at zero and count each centimetre along the ${a}.`},{visual_data:i(!0),animation:"write_length",caption:`The ${a} is ${o} ${e} long.`,spoken:`Read the number where the ${a} ends. It is ${numberWord(o)} centimetres long.`}]})}function buildCompareDemo$1(n,t,e,s,o,r=!1){const a=t.filter(T=>T!==e),i=t.some(T=>(T.start??0)>0),l={type:"measure_bars",bars:t.map(T=>
71({label:T.label,length:T.length,color:T.color,...T.start?{start:T.start}:{}})),unit:s,show_values:!1},u={type:"measure_bars",bars:t.map(T=>({label:T.label,length:T.length,color:T.color})),unit:s,show_values:!1},d={...u,compare:o?"shorter":"longer"},m={winnerLength:e.length,zero:0};a.forEach((T,A)=>{m[`loserLength${A}`]=T.length});const h=o?"shorter":"longer",f=o?"shortest":"longest",g=a.length>1?f:h,b=a.length>1?"all":"both",x=T=>r&&T===e?`${T.label}'s`:T.label,y=T=>r&&T===e?`${T.label}'s`:spokenLabel(T.label),E=o?"which one stops sooner":"which one reaches further";if(!i)return buildExample({generatorId:meaP4M04Generator.id,question:n,title:a.length>1?`Which is ${f}?`:`Which is ${h}?`,computed:m,steps:[{visual_data:u,animation:"show_object",caption:`Line ${b} up with 0.`,spoken:`To compare fairly, ${b==="all"?"all the objects":"both objects"} must start at zero on the ruler. Then we can see ${E}.`},{visual_data:u,animation:"count_cm",caption:`Measure each to see which is ${g}.`,spoken:`Count the centimetres for each one. The ${g} one covers ${o?"fewer":"more"} centimetres${a.length>1?" than the others":""}.`},{visual_data:d,animation:"write_length",caption:`The ${g} one is ${x(e)}.`,spoken:`It measures ${numberWord(e.length)} centimetres, ${o?"fewer":"more"} than ${a.length>1?"the others":"the other one"}. So the ${g} one is ${y(e)}.`}]});const k=t.map(T=>(T.start??0)>0?`${capitalised(y(T))} starts at ${numberWord(T.start)} and ends at ${numberWord(T.start+T.length)}: that's ${numberWord(T.length)} centimetres.`:`${capitalised(y(T))} starts at zero, so it covers ${numberWord(T.length)} centimetres.`).join(" ");return buildExample({generatorId:meaP4M04Generator.id,question:n,title:a.length>1?`Which is ${f}?`:`Which is ${h}?`,computed:m,steps:[{visual_data:l,animation:"show_object",caption:"They don't all start at 0.",spoken:"Look where each one starts. They don't all start at zero, so the number where one ends is not its length. We count the centimetres each one covers, from its own start to its own end."},{visual_data:{...l,show_values:!0},animation:"count_cm",caption:`Count each from its own start: ${t.map(T=>`${T.label} ${T.length} ${s}`).join(", ")}.`,spoken:k},{visual_data:d,animation:"write_length",caption:`Lined up at 0, the ${g} one is ${x(e)}.`,spoken:`Slide them back to zero to check. ${capitalised(y(e))} covers ${numberWord(e.length)} centimetres, ${o?"fewer":"more"} than ${a.length>1?"the others":"the other one"}. So the ${g} one is ${y(e)}.`}]})}function buildCompareTieDemo(n,t,e,s){const o=t.some(d=>(d.start??0)>0),r={type:"measure_bars",bars:t.map(d=>({label:d.label,length:d.length,color:d.color})),unit:e,show_values:!1},a={...r,bars:t.map(d=>({label:d.label,length:d.length,color:d.color,...d.start?{start:d.start}:{}}))},i={...r,compare:"same"},l={length:t[0].length,zero:0},u=s?"shorter":"longer";return buildExample({generatorId:meaP4M04Generator.id,question:n,title:`Is one ${u} — or are they the same?`,computed:l,steps:o?[{visual_data:a,animation:"show_object",caption:"They don't start at the same mark.",spoken:"Look where each one starts. They don't start at the same mark, so where they end doesn't tell us their lengths. We count the centimetres each one covers, from its own start."},{visual_data:{...a,show_values:!0},animation:"count_cm",caption:"Measure each one from its own start to its end.",spoken:t.map(d=>(d.start??0)>0?`${capitalised(spokenLabel(d.label))} starts at ${numberWord(d.start)} and ends at ${numberWord(d.start+d.length)}: that's ${numberWord(d.length)} centimetres.`:`${capitalised(spokenLabel(d.label))} starts at zero and covers ${numberWord(d.length)} centimetres.`).join(" ")},{visual_data:i,animation:"write_length",caption:"Lined up at 0, they measure the same.",spoken:`Slide them back to zero to check: both cover ${numberWord(t[0].length)} centimetres. Neither is ${u} — they are the same.`}]:[{visual_data:r,animation:"show_object",caption:"Line both up with 0.",spoken:"To compare fairly, line both objects up with the start of the ruler."},{visual_data:r,animation:"count_cm",caption:"Measure each one from its start to its end.",spoken:"Count the centimetres for each one, from where it starts to where it ends — not just where it ends."},{visual_data:i,animation:"write_length",caption:"They measure the same.",spoken:`Both cover ${numberWord(t[0].length)} centimetres. Neither is ${u} — they are the same.`}]})}function buildThresholdDemo(n,t,e,s,o){if(t.some(h=>(h.start??0)>
710)||!Number.isInteger(e)||e<2||e>30)return null;const r=t.filter(h=>s?h.length<e:h.length>e);if(r.length!==1)return null;const a=r[0];if(n.correctAnswer.trim()!==a.label)return null;const l={type:"measure_bars",bars:[{label:`${e} ${o}`,length:e,color:"#94a3b8"},...t.map(h=>({label:h.label,length:h.length,color:h.color}))],unit:o,show_values:!1},u=s?"shorter":"longer",d={limit:e,zero:0,answer:a.length};t.forEach((h,f)=>{d[`len${f}`]=h.length});const m=t.map(h=>h.length>e?`The ${spokenLabel(h.label).toLowerCase()} ends at ${numberWord(h.length)}, past ${numberWord(e)}.`:h.length<e?`The ${spokenLabel(h.label).toLowerCase()} ends at ${numberWord(h.length)}, before ${numberWord(e)}.`:`The ${spokenLabel(h.label).toLowerCase()} ends right at ${numberWord(e)}.`).join(" ");return buildExample({generatorId:meaP4M04Generator.id,question:n,title:`Which is ${u} than ${e} ${o}?`,computed:d,steps:[{visual_data:l,animation:"show_object",caption:`The grey bar is ${e} ${o}. Line them all up with 0.`,spoken:`We want the one that is ${u} than ${numberWord(e)} centimetres, so we compare each one with the grey bar, not with each other. They all start at zero.`},{visual_data:{...l,show_values:!0},animation:"count_cm",caption:`Find ${e} on the ruler.`,spoken:`Find ${numberWord(e)} on the ruler, where the grey bar ends. ${m}`},{visual_data:{...l,show_values:!0,verdict:`${a.label} is ${u} than ${e} ${o}`},animation:"write_length",caption:`${a.label} is ${u} than ${e} ${o}.`,spoken:`So the ${spokenLabel(a.label).toLowerCase()} is ${u} than ${numberWord(e)} centimetres.`}]})}function spokenLabel(n){return/^(red|orange|yellow|green|blue|purple|pink|brown|black|white|grey|gray)$/i.test(n)?n.toLowerCase():n}function objectNoun(n){return n.charAt(0).toLowerCase()+n.slice(1)}const npvCountingMeaY1Generators=[npvP2M01Generator,npvP2M03Generator,npvP2M04Generator,meaP4M04Generator];function optionInts$1(n){const e=(n.options??[]).map(s=>Number(String(s).trim()));return e.length<2||!e.every(s=>Number.isInteger(s))?null:e}const npvLargestSmallest={id:"NPV.P3.M03.pick-extreme.v1",microSkillId:"NPV.P3.M03",forms:["comparison"],generate(n){const t=optionInts$1(n);if(!t)return null;const e=normaliseStem(n.stem).toLowerCase(),s=/\b(largest|biggest|greatest|most|greater)\b/.test(e),o=/\b(smallest|least|fewest|lowest|less)\b/.test(e);if(s===o)return null;const r=s?Math.max(...t):Math.min(...t);if(t.filter(k=>k===r).length!==1||String(r)!==n.correctAnswer.trim())return null;const a=s?"largest":"smallest",i=t.filter(k=>k!==r),l={answer:r};t.forEach((k,T)=>l[`n${T+1}`]=k);const u=t.every(k=>k>=0&&k<=20),d=u?numberWord(r):String(r),m=[...t].sort((k,T)=>k-T),f=t.every((k,T)=>k===m[T])&&t.length>1?[...t.slice(1),t[0]]:[...t],g=t.every(k=>Number.isInteger(k)&&k>=11&&k<=19),b=m.map(k=>k-10);g&&(l.one=1,b.forEach((k,T)=>l[`ones${T+1}`]=k));const x=`Put them smallest to largest. Then the ${a} is at the ${s?"end":"start"}.`,y=g?`All have one ten, so compare the ones: ${b.map(k=>numberWord(k)).join(", ")}. ${x}`:x,E=m.indexOf(r);return buildExample({generatorId:this.id,question:n,title:`Which is the ${a}?`,computed:l,steps:[{visual_data:{type:"sequence",values:f,step:1,hide_hop:!0},animation:"highlight_pattern",caption:"Line up the numbers.",spoken:`Here are the numbers: ${u?f.map(k=>numberWord(k)).join(", "):f.join(", ")}. The ${a} is the one worth the ${s?"most":"least"}.`},{visual_data:{type:"sequence",values:[...m],step:1,hide_hop:!0},animation:"highlight_pattern",caption:g?`All have 1 ten. Ones: ${b.join(", ")}.`:"Put them in order.",spoken:y},{visual_data:{type:"sequence",values:[...m],step:1,hide_hop:!0,emphasis:E},animation:"write_in",caption:`${r} is the ${a}.`,spoken:`${capitalised(d)} is the ${a}.${u&&i.length?` It is ${s?"more":"less"} than ${i.map(k=>numberWord(k)).join(" and ")}.`:""}`}]})}},SHAPE_COUNT={triangle:3,square:4,rectangle:4,pentagon:5,hexagon:6,circle:0},SHAPE_NAMES=Object.keys(SHAPE_COUNT);function asShape(n){if(!n)return null;const t=n.trim().toLowerCase();return SHAPE_NAMES.includes(t)?t:null}function refShapeInStem(n){const t=new Set;for(const e of SHAPE_NAMES)new RegExp(`\\b${e}s?\\b`).test(n)&&t.add(e);return t.size===1?[...t][0]:null}const GREEN="#43A047",GREY="#7E8AA2";function buildShapeCompareDemo(n,t,e,s,o,r,a,i){const l={type:"shape_svg",shape:e,color:GREY,show_markers:!0},u={type:"shape_svg",shape:o,color:GREEN,show_markers:!0},d=a==="sides"?"side":"corner",m=E=>E===1?d:a,h=a==="sides"?"trace_sides":"trace_corners",f=i==="more"?"more":"fewer",g=[{shape:e,color:GREY,label:capitalised(e),count:s},{shape:o,color:GREEN,label:capitalised(o),count:r,highlight:!0}],b={type:"shape_svg",shape:o,color:GREEN,compare:g},x=i==="same"?`Both have ${r} ${m(r)} — the ${o} matches.`:`${capitalised(o)} has ${r}, ${e} has ${s} — ${o} has ${f}.`,y=i==="same"?`They both have ${numberWord(r)} ${m(r)}, so the ${o} is the match.`:`A ${o} has ${numberWord(r)} ${m(r)} and a ${e} has ${numberWord(s)}, so the ${o} has ${f}.`;return buildExample({generatorId:t,question:n,title:a==="sides"?"Compare the sides":"Compare the corners",computed:{otherCount:s,answerCount:r,answer:r},steps:[{visual_data:l,animation:h,caption:`A ${e} has ${s} ${m(s)}.`,spoken:`First, count the ${e}: ${numberWord(s)} ${m(s)}.`},{visual_data:u,animation:h,caption:`A ${o} has ${r} ${m(r)}.`,spoken:`Now count the ${o}: ${numberWord(r)} ${m(r)}.`},{visual_data:b,animation:a==="sides"?"compare_sides":"compare_corners",caption:x,spoken:y}]})}function shapeCompareGenerate(n,t,e){const s=asShape(n.correctAnswer);if(!s)return null;const o=(n.options??[]).map(asShape);if(!o.includes(s))return null;const r=normaliseStem(n.stem).toLowerCase(),a=SHAPE_COUNT[s],i=o.filter(d=>!!d&&d!==s);if(!i.length)return null;let l,u;if(/\bmore\b/.test(r)){if(!i.every(d=>a>SHAPE_COUNT[d]))return null;l="more",u=i.slice().sort((d,m)=>
71SHAPE_COUNT[m]-SHAPE_COUNT[d])[0]}else if(/\b(fewer|less|fewest)\b/.test(r)){if(!i.every(d=>a<SHAPE_COUNT[d]))return null;l="fewer",u=i.slice().sort((d,m)=>SHAPE_COUNT[d]-SHAPE_COUNT[m])[0]}else if(/\bsame\b/.test(r)){const d=refShapeInStem(r);if(!d||SHAPE_COUNT[d]!==a)return null;l="same",u=d}else return null;return a<1||SHAPE_COUNT[u]<1?null:buildShapeCompareDemo(n,t,u,SHAPE_COUNT[u],s,a,e,l)}const geoSidesCompare={id:"GEO.P3.M01.compare-sides.v1",microSkillId:"GEO.P3.M01",forms:["symbolic_choice"],generate(n){return shapeCompareGenerate(n,this.id,"sides")}},geoCornersCompare={id:"GEO.P3.M02.compare-corners.v1",microSkillId:"GEO.P3.M02",forms:["symbolic_choice"],generate(n){return shapeCompareGenerate(n,this.id,"corners")}},geoRectangleFromCorners={id:"GEO.P3.M02.rectangle-corners.v1",microSkillId:"GEO.P3.M02",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem).toLowerCase(),e=t.match(/(\d+)\s+square corners?/);if(!e||!/\bis it a (rectangle|square)\b/.test(t))return null;const s=Number(e[1]);if(!Number.isInteger(s)||(s===4?"Yes":"No")!==n.correctAnswer.trim()||s!==4)return null;const r={type:"shape_svg",shape:"rectangle",color:GREEN};return buildExample({generatorId:this.id,question:n,title:"Is it a rectangle?",computed:{corners:s,answer:s},steps:[{visual_data:r,animation:"show_shape",caption:"A rectangle has 4 square corners.",spoken:"A square corner is a right angle — like the corner of a book. A rectangle has four of them."},{visual_data:r,animation:"trace_corners",caption:`Count the square corners: ${numberWord(s)}.`,spoken:`Count each square corner: ${Array.from({length:s},(a,i)=>numberWord(i+1)).join(", ")}.`},{visual_data:r,animation:"write_total_corners",caption:"4 square corners — yes, a rectangle.",spoken:"Four square corners, so yes — it is a rectangle."}]})}};function gcd$5(n,t){return t===0?n:gcd$5(t,n%t)}function fractionForms(n,t){const e=gcd$5(n,t)||1;return new Set([`${n}/${t}`,`${n/e}/${t/e}`])}const fdpComplement={id:"FDP.P2.M01.not-shaded.v1",microSkillId:"FDP.P2.M01",forms:["fraction_identification","visual_quantity_to_symbol"],generate(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t.total_parts,s=t.shaded_parts;if(!Number.isInteger(e)||!Number.isInteger(s)||e<2||s<0)return null;const o=normaliseStem(n.stem).toLowerCase();if(!/\bnot shaded\b|\bunshaded\b|\bnot coloured\b/.test(o))return null;const r=e-s;if(r<1||!fractionForms(r,e).has(n.correctAnswer.trim()))return null;const a={type:"fraction_shape",shape:t.shape,total_parts:e,shaded_parts:s,color:t.color,conclude_label:n.correctAnswer.trim()},i={total:e,shaded:s,unshaded:r,answer:r},l="parts",u=r===1?"is":"are",d="#f59e0b",m=Array.from({length:r},(f,g)=>s+g),h={...a,part_marks:Object.fromEntries(m.map(f=>[f,d]))};return buildExample({generatorId:this.id,question:n,title:"What fraction is not shaded?",computed:i,steps:[{visual_data:a,animation:"show_parts",caption:`${e} equal ${l} in all.`,spoken:`The whole shape is split into ${numberWord(e)} equal ${l}.`},{visual_data:h,animation:"count_unshaded",caption:`Count the ${l} with no colour.`,spoken:`Now count only the ${l} that are not shaded.`},{visual_data:a,animation:"write_fraction",caption:`${r} of ${e} ${u} not shaded.`,spoken:`${capitalised(numberWord(r))} of the ${numberWord(e)} ${l} ${u} not shaded. That is ${numberWord(r)} out of ${numberWord(e)}.`}]})}},fdpComplementM02={...fdpComplement,id:"FDP.P2.M02.not-shaded.v1",microSkillId:"FDP.P2.M02"},residualsY1Generators=[npvLargestSmallest,geoSidesCompare,geoCornersCompare,geoRectangleFromCorners,fdpComplement,fdpComplementM02],Y1_SUPPLEMENTAL_GENERATORS=[...asP2M02Y1Generators,...asP2M03Y1Generators,...asReasoningY1Generators,...asP3SupplementalY1Generators,...fdpY1Generators,...geoY1Generators,...mdY1Generators,...npvCompareY1Generators,...npvCountingMeaY1Generators,...residualsY1Generators];function escapeRegExp$7(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const DAY_ABBR_TO_FULL$3={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"},WK_RE=/^wk(\d+)$/;function labelVariants$1(n){const t=n.trim().toLowerCase(),e=[t];DAY_ABBR_TO_FULL$3[t]&&e.push(DAY_ABBR_TO_FULL$3[t]);const s=t.match(WK_RE);return s&&e.push(`week ${s[1]}`),e}function findLabelIndex$2(n,t){for(const e of labelVariants$1(t)){const s=n.match(new RegExp(`\\b${escapeRegExp$7(e)}\\b`,"i"));if(s?.index!==void 0)return s.index}return-1}function speakableLabel$3(n){const t=n.trim().toLowerCase();if(DAY_ABBR_TO_FULL$3[t])return DAY_ABBR_TO_FULL$3[t];const e=t.match(WK_RE);return e?`week ${numberToWordsLong(Number(e[1]))}`:t.replace(/\d+/g,s=>` ${numberToWordsLong(Number(s))} `).replace(/\s+/g," ").trim()}function findNamedBars$2(n,t){const e=n.toLowerCase(),s=[];for(const o of t){const r=findLabelIndex$2(e,o.label);r>=0&&s.push({bar:o,index:r})}return s.sort((o,r)=>o.index-r.index),s}function decideOp$2(n,t){const e=normaliseStem(n).toLowerCase(),s=/\bmore\b/.test(e),o=/\bfewer\b/.test(e),r=/\baltogether\b/.test(e)||/\bin total\b/.test(e),a=e.match(/\bthan\b/),i=findNamedBars$2(n,t);if(s||o){if(!a)return null;const l=a.index,u=i.filter(m=>m.index<l),d=i.filter(m=>m.index>l);return u.length===1&&d.length===1?{kind:"cmp2",a:u[0].bar,b:d[0].bar}:null}return r&&i.length===0?{kind:"sumAll",bars:[...t]}:i.length===2?{kind:"sum",bars:i.map(l=>l.bar)}:i.length===1?{kind:"single",bar:i[0].bar}:null}function guardChart$2(n){const t=n.bars;if(t.length<2||t.length>5||t.some(a=>!Number.isInteger(a.value)||a.value<0)||n.axis_max!==void 0&&t.some(a=>a.value>n.axis_max))return null;const e=Math.max(...t.map(a=>a.value),1),s=Math.max(n.axis_max??0,e),o=n.axis_step??(s<=10?1:2);return Math.ceil(s/o)*o>40&&t.some(a=>a.value%o!==0)?null:{axisStep:o}}function stepVisual$2(n,t){return{type:"bar_chart",bars:n.bars,axis_max:n.axis_max,axis_step:n.axis_step,...t!==void 0?{read_index:t}:{}}}function buildFromOp$2(n,t,e,s){const o=m=>t.bars.indexOf(m),r={visual_data:stepVisual$2(t),animation:"grow",caption:`The side goes up in ${s}s.`,spoken:`Look at the numbers up the side. They go up in ${numberToWordsLong(s)}s. Each bar's height shows its number.`};let a,i,l;const u={axisStep:s};if(e.kind==="single"){a=e.bar.value;const m=t.bars.find(h=>h!==e.bar);i={visual_data:stepVisual$2(t,o(m)),animation:"read",caption:`First try ${m.label}: it lines up with ${m.value}.`,spoken:`First practise the move on the ${speakableLabel$3(m.label)} bar. Follow its top straight across to the side. It lines up with ${numberToWordsLong(m.value)}.`},l={visual_data:stepVisual$2(t,o(e.bar)),animation:"compare",caption:`${e.bar.label} lines up with ${e.bar.value} — the answer.`,spoken:`Now the ${speakableLabel$3(e.bar.label)} bar. Follow its top across to the side. It lines up with ${numberToWordsLong(e.bar.value)}. That is the answer.`},u.value=e.bar.value,u.practice=m.value}else if(e.kind==="sum"){a=e.bars.reduce((f,g)=>f+g.value,0);const[m,h]=e.bars;i={visual_data:stepVisual$2(t,o(m)),animation:"read",caption:`${m.label} lines up with ${m.value}.`,spoken:`Follow the top of the ${speakableLabel$3(m.label)} bar across to the side. It lines up with ${numberToWordsLong(m.value)}.`},l={visual_data:stepVisual$2(t,o(h)),animation:"compare",caption:`${m.value} + ${h.value} = ${a}.`,spoken:`The ${speakableLabel$3(h.label)} bar lines up with ${numberToWordsLong(h.value)}. To find the total, we add the two: ${numberToWordsLong(m.value)} add ${numberToWordsLong(h.value)} is ${numberToWordsLong(a)}.`}
71,u.a=m.value,u.b=h.value}else if(e.kind==="sumAll"){const m=e.bars.map(E=>E.value);a=m.reduce((E,k)=>E+k,0);const h=e.bars[0],f=e.bars.map(E=>E.label).join(", ");let g=m[0];const b=[];for(let E=1;E<m.length;E++){const k=g;g+=m[E],b.push(`${k} + ${m[E]} = ${g}`)}const x={type:"bar_chart",bars:t.bars,axis_max:t.axis_max,axis_step:t.axis_step,read_index:o(h),lit_indices:t.bars.map((E,k)=>k),hide_diff:!0};i={visual_data:stepVisual$2(t,o(h)),animation:"read",caption:`Read every bar: ${f}.`,spoken:`To find the whole total, read every bar's number, not just one. Follow the top of the ${speakableLabel$3(h.label)} bar across to the side. It lines up with ${numberToWordsLong(h.value)}.`},l={visual_data:x,animation:"compare",caption:`${b.join(", ")}.`,spoken:`Add each bar's number in turn: ${m.map(E=>numberToWordsLong(E)).join(", then ")}. Adding them one at a time makes ${numberToWordsLong(a)} altogether.`},e.bars.forEach((E,k)=>u[`bar${k}`]=E.value);let y=m[0];for(let E=1;E<m.length;E++)y+=m[E],u[`running${E}`]=y}else{a=Math.abs(e.a.value-e.b.value);const m=Math.max(e.a.value,e.b.value),h=Math.min(e.a.value,e.b.value);i={visual_data:stepVisual$2(t,o(e.a)),animation:"read",caption:`${e.a.label} lines up with ${e.a.value}.`,spoken:`Follow the top of the ${speakableLabel$3(e.a.label)} bar across to the side. It lines up with ${numberToWordsLong(e.a.value)}.`},l={visual_data:stepVisual$2(t,o(e.b)),animation:"compare",caption:`${m} − ${h} = ${a}.`,spoken:`The ${speakableLabel$3(e.b.label)} bar lines up with ${numberToWordsLong(e.b.value)}. The gap between the two bars is ${numberToWordsLong(m)} take away ${numberToWordsLong(h)}, which is ${numberToWordsLong(a)}.`},u.a=e.a.value,u.b=e.b.value}const d=Number(n.correctAnswer);return!Number.isFinite(d)||d!==a?null:(u.answer=a,buildExample({generatorId:"DS.P2.M03.bar_chart.v1",question:n,title:n.stem,steps:[r,i,l],computed:u}))}const CLAIM_RE$3=/^(\w+) says (\d+) children chose \w+ as their favourite pet\. Looking at the chart, is (?:he|she) right\?$/i;function claimCheck$4(n,t){const e=normaliseStem(n.stem).match(CLAIM_RE$3);if(!e||!guardChart$2(t))return null;const[,o,r]=e,a=Number(r),i=findNamedBars$2(n.stem,t.bars);if(i.length!==1)return null;const l=i[0].bar,u=l.value===a,d=n.correctAnswer.trim();if(u&&!isAffirmative(d)||!u&&!isNegative(d))return null;const m=t.bars.indexOf(l),h={type:"bar_chart",bars:[{label:l.label,value:l.value},{label:`${o}'s claim`,value:a}]};return buildExample({generatorId:"DS.P2.M03.claim-check.v1",question:n,title:`Is ${o}'s count of ${a} really right?`,computed:{claimed:a,real:l.value,answer:u?1:0},steps:[{visual_data:stepVisual$2(t),animation:"grow",caption:`${o} says ${a} chose ${l.label}.`,spoken:"Before trusting the claim, let's check the chart for ourselves."},{visual_data:stepVisual$2(t,m),animation:"read",caption:`${l.label} really lines up with ${l.value}.`,spoken:`Follow the top of the ${speakableLabel$3(l.label)} bar across to the side. It really lines up with ${numberToWordsLong(l.value)}.`},{visual_data:h,animation:"compare",caption:`${l.value} vs ${a} — ${o} is ${u?"right":"wrong"}.`,spoken:`The true number is ${numberToWordsLong(l.value)}, so ${o} is ${u?"right":"wrong"}. Always read the axis yourself before trusting a claim.`}]})}function compareSelect$1(n,t){if(!guardChart$2(t))return null;const s=normaliseStem(n.stem).toLowerCase(),o=/\bmore\b/.test(s),r=/\bfewer\b/.test(s);if(!o&&!r||/\bthan\b/.test(s))return null;const a=findNamedBars$2(n.stem,t.bars);if(a.length!==2)return null;const[i,l]=a.map(f=>f.bar);if(i.value===l.value)return null;const u=o?i.value>l.value?i:l:i.value<l.value?i:l,d=u===i?l:i;if(n.correctAnswer.trim()!==u.label)return null;const m=t.bars.indexOf(i),h=t.bars.indexOf(l);return buildExample({generatorId:"DS.P2.M03.compare-select.v1",question:n,title:`Which was chosen ${o?"more":"fewer"}, ${i.label} or ${l.label}?`,computed:{a:i.value,b:l.value,answer:u.value},steps:[{visual_data:stepVisual$2(t),animation:"grow",caption:`Read both ${i.label} and ${l.label} off the axis.`,spoken:"To compare two bars, read each one off the side axis before deciding."},{visual_data:stepVisual$2(t,m),animation:"read",caption:`${i.label} lines up with ${i.value}.`,spoken:`The ${speakableLabel$3(i.label)} bar lines up with ${numberToWordsLong(i.value)}.`},{visual_data:stepVisual$2(t,h),animation:"compare",caption:`${l.label} lines up with ${l.value} — ${u.label} wins.`,spoken:`The ${speakableLabel$3(l.label)} bar lines up with ${numberToWordsLong(l.value)}. ${u.label} is ${o?"more":"fewer"} than ${d.label}.`}]})}const DS_P2_M03_GENERATORS=[{id:"DS.P2.M03.bar_chart.v1",microSkillId:"DS.P2.M03",forms:["symbolic_numeric_entry","context_calculation"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart$2(t);if(!e)return null;const s=decideOp$2(n.stem,t.bars);return s?buildFromOp$2(n,t,s,e.axisStep):null}},{id:"DS.P2.M03.claim-check.v1",microSkillId:"DS.P2.M03",forms:["misconception_contrast_probe"],generate(n){const t=n.visualData;return!t||t.type!=="bar_chart"?null:claimCheck$4(n,t)}},{id:"DS.P2.M03.compare-select.v1",microSkillId:"DS.P2.M03",forms:["comparison_statement"],generate(n){const t=n.visualData;return!t||t.type!=="bar_chart"?null:compareSelect$1(n,t)}}];function escapeRegExp$6(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const DAY_ABBR_TO_FULL$2={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"}
71,MONTH_ABBR_TO_FULL={jan:"january",feb:"february",mar:"march",apr:"april",jun:"june",jul:"july",aug:"august",sep:"september",oct:"october",nov:"november",dec:"december"};function labelVariants(n){const t=n.trim().toLowerCase(),e=[t];return DAY_ABBR_TO_FULL$2[t]&&e.push(DAY_ABBR_TO_FULL$2[t]),MONTH_ABBR_TO_FULL[t]&&e.push(MONTH_ABBR_TO_FULL[t]),e}function labelInStem(n,t){return labelVariants(t).some(e=>new RegExp(`\\b${escapeRegExp$6(e)}\\b`,"i").test(n))}function speakableLabel$2(n){const t=n.trim().toLowerCase();return(DAY_ABBR_TO_FULL$2[t]??MONTH_ABBR_TO_FULL[t]??t).replace(/\d+/g,s=>` ${numberToWords(Number(s))} `).replace(/\s+/g," ").trim()}function guardPictogram(n){const{rows:t,per_symbol:e}=n;if(t.length<2||t.length>5||!Number.isInteger(e)||e<1)return!1;for(const s of t)if(typeof s.symbols!="number"||s.symbols<0||s.symbols>8||s.symbols*2%1!==0)return!1;return!0}function rowTotal(n,t){const e=Math.floor(n),s=n-e>=.5,o=e*t+(s?t/2:0);return Number.isInteger(o)?o:null}function reorderedPic(n,t){return{type:"pictogram",rows:[t,...n.rows.filter(s=>s.label!==t.label)].map(s=>({label:s.label,symbols:s.symbols})),emoji:n.emoji,per_symbol:n.per_symbol,key_label:n.key_label}}const CLAIM_RE$2=/^(\w+) counts the pictures and says .+ read (\d+) books this term\. Looking at the key, is (?:he|she) right\?$/i;function claimCheck$3(n,t){const e=normaliseStem(n.stem).match(CLAIM_RE$2);if(!e||!guardPictogram(t))return null;const[,s,o]=e,r=Number(o),a=n.stem.toLowerCase(),i=t.rows.filter(b=>labelInStem(a,b.label));if(i.length!==1)return null;const l=i[0],u=rowTotal(l.symbols,t.per_symbol);if(u===null)return null;const d=u===r,m=n.correctAnswer.trim();if(d&&!isAffirmative(m)||!d&&!isNegative(m))return null;const h=numberToWords(t.per_symbol),f=speakableLabel$2(l.label),g=reorderedPic(t,l);return buildExample({generatorId:"DS.P2.M04.claim-check.v1",question:n,title:`Is ${s}'s count of ${r} really right?`,computed:{perSymbol:t.per_symbol,claimed:r,real:u,answer:d?1:0},steps:[{visual_data:g,animation:"pic_key",caption:`Read the key: each picture = ${t.per_symbol}.`,spoken:`Before trusting the claim, read the key. Every picture stands for ${h}, not one.`},{visual_data:g,animation:"pic_find",caption:`${l.label} has ${Math.floor(l.symbols)} pictures.`,spoken:`Now find the ${f} row and count its pictures.`},{visual_data:g,animation:"pic_count",caption:`${l.label} is really ${u} — ${s} is ${d?"right":"wrong"}.`,spoken:`Counting in ${h}s, the ${f} row is really ${numberToWords(u)}. So ${s} is ${d?"right":"wrong"}.`}]})}function skipCountPartials(n,t){const e=Math.floor(n),s=[];for(let o=1;o<=e;o++)s.push(t*o);return n-e>=.5&&s.push((s[s.length-1]??0)+t/2),s}function compareSelect(n,t){if(!guardPictogram(t))return null;const e=normaliseStem(n.stem).toLowerCase(),s=/\bmore\b/.test(e),o=/\bfewer\b/.test(e);if(!s&&!o||/\bthan\b/.test(e))return null;const r=t.rows.filter(y=>labelInStem(e,y.label));if(r.length!==2)return null;const[a,i]=r,l=rowTotal(a.symbols,t.per_symbol),u=rowTotal(i.symbols,t.per_symbol);if(l===null||u===null||l===u)return null;const d=s?l>u?a:i:l<u?a:i,m=d===a?l:u,h=d===a?i:a;if(n.correctAnswer.trim()!==d.label)return null;const f=reorderedPic(t,a),g=reorderedPic(t,i),b=skipCountPartials(a.symbols,t.per_symbol),x=skipCountPartials(i.symbols,t.per_symbol);return buildExample({generatorId:"DS.P2.M04.compare-select.v1",question:n,title:`Which read more, ${a.label} or ${i.label}?`,computed:{perSymbol:t.per_symbol,aTotal:l,bTotal:u,answer:m,...Object.fromEntries(b.map((y,E)=>[`aPart${E}`,y])),...Object.fromEntries(x.map((y,E)=>[`bPart${E}`,y]))},steps:[{visual_data:f,animation:"pic_key",caption:`Read the key: each picture = ${t.per_symbol}.`,spoken:`Every picture stands for ${numberToWords(t.per_symbol)}, not one — read the key first.`},{visual_data:{...f,count_index:0},animation:"pic_find",caption:`${a.label}: ${b.join(", ")} — ${l}.`,spoken:`Count the ${speakableLabel$2(a.label)} row's pictures in ${numberToWords(t.per_symbol)}s: ${b.map(y=>numberToWords(y)).join(", ")}. That makes ${numberToWords(l)}.`},{visual_data:g,animation:"pic_count",caption:`${i.label}: ${x.join(", ")} — ${u}. ${d.label} wins.`,spoken:`Count the ${speakableLabel$2(i.label)} row's pictures the same way: ${x.map(y=>numberToWords(y)).join(", ")}, which makes ${numberToWords(u)}. ${d.label} read more than ${h.label}.`}]})}function totalRowPic(n,t,e){const s=n.per_symbol,o=e/s;return o*2%1!==0||o<0?null:{type:"pictogram",rows:[{label:t,symbols:o},...n.rows.map(r=>({label:r.label,symbols:r.symbols}))],emoji:n.emoji,per_symbol:s,key_label:n.key_label}}function sumTwoRows(n,t){if(!guardPictogram(t))return null;const e=n.stem.toLowerCase();if(/missing from the pictogram/i.test(n.stem))return null;const s=t.rows.filter(f=>labelInStem(e,f.label));if(s.length!==2)return null;const[o,r]=s,a=rowTotal(o.symbols,t.per_symbol),i=rowTotal(r.symbols,t.per_symbol);if(a===null||i===null)return null;const l=a+i;if(l!==Number(n.correctAnswer))return null;const u=reorderedPic(t,o),d=reorderedPic(t,r),m=totalRowPic(t,"Total",l)??d,h=skipCountPartials(o.symbols,t.per_symbol);return buildExample({generatorId:"DS.P2.M04.sum-two-rows.v1",question:n,title:n.stem,computed:{perSymbol:t.per_symbol,firstTotal:a,lastTotal:i,answer:l,...Object.fromEntries(h.map((f,g)=>[`firstPart${g}`,f]))},steps:[{visual_data:u,animation:"pic_key",caption:`Read the key: each picture = ${t.per_symbol}.`,spoken:`To add two rows, we first need to know what one picture is 
71worth. The key says every picture stands for ${numberToWords(t.per_symbol)}.`},{visual_data:{...u,count_index:0},animation:"pic_find",caption:`${o.label}: ${h.join(", ")} — ${a}.`,spoken:`Count the ${speakableLabel$2(o.label)} row's pictures in ${numberToWords(t.per_symbol)}s: ${h.map(f=>numberToWords(f)).join(", ")}. That makes ${numberToWords(a)}.`},{visual_data:m,animation:"pic_count",caption:`${a} + ${i} = ${l}.`,spoken:`Counted the same way, the ${speakableLabel$2(r.label)} row makes ${numberToWords(i)}. To find both rows together, we add: ${numberToWords(a)} add ${numberToWords(i)} makes ${numberToWords(l)}.`}]})}const TOTAL_RE=/(\d+)\s+books\s+between\s+them/i,MISSING_LABEL_RE=/but\s+(.+?)\s+is missing from the pictogram/i;function subtractMissingRow(n,t){if(!guardPictogram(t)||!/missing from the pictogram/i.test(n.stem))return null;const e=n.stem.match(TOTAL_RE);if(!e)return null;const s=Number(e[1]),o=n.stem.toLowerCase(),r=t.rows.filter(b=>labelInStem(o,b.label));if(r.length!==1)return null;const a=r[0],i=rowTotal(a.symbols,t.per_symbol);if(i===null)return null;const l=s-i;if(l<0||l!==Number(n.correctAnswer))return null;const u=reorderedPic(t,a),d=speakableLabel$2(a.label),m=skipCountPartials(a.symbols,t.per_symbol),f=n.stem.match(MISSING_LABEL_RE)?.[1]?.trim(),g=f?totalRowPic(t,f,l)??u:u;return buildExample({generatorId:"DS.P2.M04.subtract-missing-row.v1",question:n,title:n.stem,computed:{perSymbol:t.per_symbol,presentTotal:i,total:s,answer:l,...Object.fromEntries(m.map((b,x)=>[`part${x}`,b]))},steps:[{visual_data:u,animation:"pic_key",caption:`Read the key: each picture = ${t.per_symbol}.`,spoken:`One row is missing from the pictogram, so we start with the row we can see. First read the key: every picture stands for ${numberToWords(t.per_symbol)}.`},{visual_data:u,animation:"pic_find",caption:`${a.label}: ${m.join(", ")} — ${i}.`,spoken:`Count the ${d} row's pictures in ${numberToWords(t.per_symbol)}s: ${m.map(b=>numberToWords(b)).join(", ")}. That makes ${numberToWords(i)}.`},{visual_data:g,animation:"pic_count",caption:`${s} − ${i} = ${l}.`,spoken:`The whole total is ${numberToWords(s)}. Take away the ${d} row's ${numberToWords(i)}, leaving ${numberToWords(l)} for the missing row.`}]})}const DS_P2_M04_GENERATORS=[{id:"DS.P2.M04.pictogram.v1",microSkillId:"DS.P2.M04",forms:["context_calculation"],generate(n){const t=n.visualData;if(!t||t.type!=="pictogram"||!guardPictogram(t))return null;const e=n.stem.toLowerCase(),s=t.rows.filter(x=>labelInStem(e,x.label));if(s.length!==1)return null;const o=s[0],r=o.symbols;if(r<2)return null;const a=t.per_symbol,i=r*a;if(!Number.isInteger(i)||i!==Number(n.correctAnswer))return null;const l=Math.floor(r),u=r-l>=.5,d=l*a,m=u?a/2:0,h=[o,...t.rows.filter(x=>x!==o)],f=()=>({type:"pictogram",rows:h.map(x=>({label:x.label,symbols:x.symbols})),emoji:t.emoji,per_symbol:a,key_label:t.key_label}),g=speakableLabel$2(o.label),b=numberToWords(a);return buildExample({generatorId:"DS.P2.M04.pictogram.v1",question:n,title:n.stem,computed:{perSymbol:a,wholeTotal:d,half:m,answer:i},steps:[{visual_data:f(),animation:"pic_key",caption:`Read the key: each ${t.emoji} = ${a}.`,spoken:`First, read the key. Each picture is not worth one — every picture stands for ${b}.`},{visual_data:f(),animation:"pic_find",caption:`Find the ${o.label} row.`,spoken:`Now find the ${g} row. It has ${numberToWords(l)} whole ${l===1?"picture":"pictures"}${u?" and a half":""}.`},{visual_data:f(),animation:"pic_count",caption:`Count in ${a}s: ${i} in all.`,spoken:`${capitalised(b)} for each whole picture makes ${numberToWords(d)}${u?`, and the half picture adds ${numberToWords(m)}`:""}. Altogether, the ${g} row is ${numberToWords(i)}. Always read the key first, then count in that number.`}]})}},{id:"DS.P2.M04.claim-check.v1",microSkillId:"DS.P2.M04",forms:["misconception_contrast_probe"],generate(n){const t=n.visualData;return!t||t.type!=="pictogram"?null:claimCheck$3(n,t)}},{id:"DS.P2.M04.compare-select.v1",microSkillId:"DS.P2.M04",forms:["comparison_statement"],generate(n){const t=n.visualData;return!t||t.type!=="pictogram"?null:compareSelect(n,t)}},{id:"DS.P2.M04.sum-two-rows.v1",microSkillId:"DS.P2.M04",forms:["context_calculation"],generate(n){const t=n.visualData;return!t||t.type!=="pictogram"?null:sumTwoRows(n,t)}},{id:"DS.P2.M04.subtract-missing-row.v1",microSkillId:"DS.P2.M04",forms:["context_calculation"],generate(n){const t=n.visualData;return!t||t.type!=="pictogram"?null:subtractMissingRow(n,t)}}],BUILD_TITLE="How do we build this bar chart?";function expectedAnswer(n){return n.categories.map(t=>t.count).join(",")}function guardBuilder(n){const{categories:t,max:e}=n;if(t.length<2||t.length>5||!Number.isInteger(e)||e<1||e>20)return!1;const s=n.step??1;if(!Number.isInteger(s)||s<1)return!1;for(const o of t)if(!Number.isInteger(o.count)||o.count<0||o.count>e||/\d/.test(o.label))return!1;return!0}const DS_P2_M05_GENERATORS=[{id:"DS.P2.M05.bar_builder.v1",microSkillId:"DS.P2.M05",forms:["bar_chart_construction"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_builder"||!guardBuilder(t)||expectedAnswer(t)!==n.correctAnswer.trim())return null;const e=t.categories.map(m=>m.count),s=e.length,o=t.step??1,r=t.categories.map(m=>({label:m.label,count:m.count})),a=m=>({type:"bar_builder",categories:r,max:t.max,step:o,built:m}),i=n.stem.match(/^Shelf A holds (\d+) books\. Shelf B holds (\d+) more books than Shelf A\. Shelf C holds (\d+) fewer books than Shelf A\. Build the bar chart for Shelf A, Shelf B, and Shelf C\.$/);if(i){const[m,h,f]=i.slice(1).map(Number),g=m+h,b=m-f;return r.map(x=>x.label).join(",")!=="Shelf A,Shelf B,Shelf C"||e.join(",")!==[m,g,b].join(",")?null:buildExample({generatorId:this.id,question:n,title:BUILD_TITLE,computed:{base:m,more:h,fewer:f,b:g,c:b,answer:g},steps:[{visual_data:{type:"equation_display",terms:[m,"+",h,"=",g]},animation:"write_answer",caption:`Shelf B: ${m} + ${h} = ${g} books.`,spoken:`Shelf A has ${numberToWords(m)} books. Shelf B has ${numberToWords(h)} more, so add: ${numberToWords(m)} plus ${numberToWords(h)} makes ${numberToWords(g)} books.`},{visual_data:{type:"equation_display",terms:[m,"−",f,"=",b]},animation:"write_answer",caption:`Shelf C: ${m} − ${f} = ${b} books.`,spoken:`Shelf C has ${numberToWords(f)} fewer than Shelf A. Start with ${numberToWords(m)} and subtract ${numberToWords(f)}. That leaves ${numberToWords(b)} book${b===1?"":"s"}.`},{visual_data:a(e),animation:"grow_bars",caption:`Build A at ${m}, B at ${g}, C at ${b}.`,spoken:`Now build each bar to its calculated height: Shelf A at ${numberToWords(m)}, Shelf B at ${numberToWords(g)}, and Shelf C at ${numberToWords(b)}. Check each height against the side scale.`}]})}const l=e.map((m,h)=>h<s-1?m:0),u=t.categories[0],d={max:t.max};return e.forEach((m,h)=>d[`count${h}`]=m),buildExample({generatorId:"DS.P2.M05.bar_builder.v1",question:n,title:BUILD_TITLE,computed:d,steps:[{visual_data:a(e.map(()=>0)),animation:"show_data",caption:"Each bar must match its number.",spoken:`Here are the empty bars and the data list. Building a chart means making each bar as tall as its own number. Let's start with the first one, ${u.label}.`},{visual_data:a(l),animation:"grow_bars",caption:"Grow each bar up to its number.",spoken:`The ${u.label} bar grows up the scale and stops level with ${numberToWords(u.count)}. Each bar climbs to match its number. Check every bar against the side scale as it grows.`},{visual_data:a(e),animation:"check_match",caption:"Check every bar matches the data.",spoken:"The last bar grows to its number too. Now every bar lines up with the scale and matches the data. Building a chart is setting each bar to exactly its number, one at a time."}]})}}];function readMissing(n){const{col_headers:t,row_headers:e,cells:s}=n;if(e.length<2||e.length>4||t.length<2||t.length>5||s.length!==e.length)return null;let o=null;for(let h=0;h<s.length;h++){if(s[h].length!==t.length)return null;for(let f=0;f<s[h].length;f++){const g=s[h][f];if(g==null){if(o)return null;o={row:h,col:f}}else if(!Number.isInteger(g)||g<0)return null}}if(!o)return null;const r=t.map(h=>(h??"").trim().toLowerCase()),a=r.indexOf("total");if(a<0||r.filter(h=>h==="total").length>1||(e[o.row]??"").trim().toLowerCase()==="total")return null;const l=s[o.row];if(o.col===a){const h=l.filter(f=>typeof f=="number");return h.length<2||h.length>4?null:{...o,op:"add",data:h,answer:h.reduce((f,g)=>f+g,0)}}const u=l[a];if(typeof u!="number")return null;const d=l.filter((h,f)=>typeof h=="number"&&f!==a);if(d.length<1||d.length>3)return null;const m=u-d.reduce((h,f)=>h+f,0);return m<0?null:{...o,op:"subtract",data:d,total:u,answer:m}}function escapeRegExp$5(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function guardFullTable(n){const{col_headers:t,row_headers:e,cells:s}=n;if(e.length<2||e.length>4||t.length<2||t.length>5||s.length!==e.length)return!1;for(const o of s){if(o.length!==t.length)return!1;for(const r of o)if(typeof r!="number"||!Number.isInteger(r)||r<0)return!1}return!0}function findRowIndex(n,t){const e=n.toLowerCase();return t.findIndex(s=>new RegExp(`\\b${escapeRegExp$5(s.toLowerCase())}\\b`,"i").test(e))}function colVariants(n){const t=n.trim().toLowerCase();return t.endsWith("s")?[t]:[t,`${t}s`]}function findColIndex(n,t){const e=n.toLowerCase();return t.findIndex(s=>colVariants(s).some(o=>new RegExp(`\\b${escapeRegExp$5(o)}\\b`,"i").test(e)))}const CLAIM_RE$1=/^(\w+) says (\d+) children .*\. Looking at the table, is (?:he|she) right\?$/i;function claimCheck$2(n,t){if(!guardFullTable(t))return null;const e=normaliseStem(n.stem).match(CLAIM_RE$1);if(!e)return null;const[,s,o]=e,r=Number(o),a=findRowIndex(n.stem,t.row_headers),i=findColIndex(n.stem,t.col_headers);if(a<0||i<0)return null;const l=t.cells[a][i];if(typeof l!="number")return null;const u=l===r,d=n.correctAnswer.trim();if(u&&!isAffirmative(d)||!u&&!isNegative(d))return null;const m=t.row_headers[a],h=t.col_headers[i],f=()=>({type:"two_way_table",col_headers:t.col_headers,row_headers:t.row_headers,cells:t.cells,corner_label:t.corner_label}),g=()=>({...f(),highlight_col:i}),b=()=>({...f(),highlight_col:i,highlight_cell:{row:a,col:i}});return buildExample({generatorId:"DS.P3.M01.claim-check.v1",question:n,title:`Is ${s}'s count of ${r} really right?`,computed:{claimed:r,real:l,answer:u?1:0},steps:[{visual_data:f(),animation:"highlight_missing",caption:`${s} claims ${m}, ${h} is ${r}.`,spoken:"Before trusting a claim, check the table for yourself."},{visual_data:g(),animation:"sum_known",caption:`First find the ${h} column.`,spoken:`First find the ${h} column, then look down it for the ${digitsToWords(m)} row.`},{visual_data:b(),animation:"reveal_answer",caption:`${m}, ${h} really shows ${l} — ${s} is ${u?"right":"wrong"}.`,spoken:`Where the ${digitsToWords(m)} row crosses the ${h} column, the true number is ${numberToWords(l)}, so ${s} is ${u?"right":"wrong"}. Always read the exact cell yourself before trusting a claim.`}]})}function columnTotal(n,t){if(!guardFullTable(t))return null;const e=n.stem.toLowerCase();if(!/\btogether\b/.test(e))return null;const s=findColIndex(n.stem,t.col_headers);if(s<0||!t.row_headers.every(h=>new RegExp(`\\b${escapeRegExp$5(h.toLowerCase())}\\b`,"i").test(e)))return null;const r=t.cells.map(h=>h[s]);if(r.some(h=>typeof h!="number"))return null;const a=r,i=a.reduce((h,f)=>h+f,0);if(i!==Number(n.correctAnswer))return null;const l=t.col_headers[s],u=a.join(" + "),d=h=>({type:"two_way_table",col_headers:t.col_headers,row_headers:t.row_headers,cells:t.cells,corner_label:t.corner_label,highlight_col:s,chip_expression:h}),m={total:i,answer:i};return a.forEach((h,f)=>m[`val${f}`]=h),buildExample({generatorId:"DS.P3.M01.column-total.v1",question:n,title:n.stem,computed:m,steps:[{visual_data:{type:"two_way_table",col_headers:t.col_headers,row_headers:t.row_headers,cells:t.cells,corner_label:t.corner_label},animation:"highlight_missing",caption:`Read the ${l} column for every row.`,spoken:"To find a whole-column total, add that column's number from every row, not just one."},{visual_data:d(`${u} = ?`),animation:"sum_known",caption:`Add every row's ${l} number.`,spoken:`Add the ${l} numbers from all the rows together.`},{visual_data:d(`${u} = ${i}`),animation:"reveal_answer",caption:`${u} = ${i}.`,spoken:`Adding every row's number together makes ${numberToWords(i)}.`}]})}function compareColumn(n,t){if(!guardFullTable(t))return null;const e=normaliseStem(n.stem).toLowerCase(),s=/\bmore\b/.test(e),o=/\bfewer\b/.test(e);
71if(!s&&!o||/\bthan\b/.test(e))return null;const r=findColIndex(n.stem,t.col_headers);if(r<0)return null;const a=t.row_headers.map((x,y)=>({label:x,i:y})).filter(({label:x})=>new RegExp(`\\b${escapeRegExp$5(x.toLowerCase())}\\b`,"i").test(e));if(a.length!==2)return null;const[i,l]=a,u=t.cells[i.i][r],d=t.cells[l.i][r];if(typeof u!="number"||typeof d!="number"||u===d)return null;const m=s?u>d?i:l:u<d?i:l,h=m===i?l:i;if(n.correctAnswer.trim()!==m.label)return null;const f=t.col_headers[r],g=()=>({type:"two_way_table",col_headers:t.col_headers,row_headers:t.row_headers,cells:t.cells,corner_label:t.corner_label}),b=x=>({...g(),highlight_col:r,highlight_cell:{row:x,col:r}});return buildExample({generatorId:"DS.P3.M01.compare-column.v1",question:n,title:`Which has more in ${f}, ${i.label} or ${l.label}?`,computed:{a:u,b:d,answer:m===i?u:d},steps:[{visual_data:g(),animation:"highlight_missing",caption:`Read the ${f} column for both rows.`,spoken:"To compare, read the same column for each row before deciding."},{visual_data:b(i.i),animation:"sum_known",caption:`${i.label} shows ${u}.`,spoken:`The ${digitsToWords(i.label)} row's ${f} number is ${numberToWords(u)}.`},{visual_data:b(l.i),animation:"reveal_answer",caption:`${l.label} shows ${d} — ${m.label} wins.`,spoken:`The ${digitsToWords(l.label)} row's ${f} number is ${numberToWords(d)}. ${digitsToWords(m.label)} has more than ${digitsToWords(h.label)}.`}]})}const DS_P3_M01_GENERATORS=[{id:"DS.P3.M01.two_way_table.v1",microSkillId:"DS.P3.M01",forms:["equation_completion"],generate(n){const t=n.visualData;if(!t||t.type!=="two_way_table")return null;const e=readMissing(t);if(!e||e.answer!==Number(n.correctAnswer))return null;const s=(f,g)=>({type:"two_way_table",col_headers:t.col_headers,row_headers:t.row_headers,cells:f,corner_label:t.corner_label,...g!==void 0?{chip_expression:g}:{}}),o=t.cells.map(f=>[...f]),r=t.cells.map((f,g)=>f.map((b,x)=>g===e.row&&x===e.col?e.answer:b)),a=e.data.map(f=>numberToWords(f));(t.row_headers[e.row]??"").trim(),(t.col_headers[e.col]??"").trim();const i="its row",l="the empty cell",u=numberToWords(e.answer),d={answer:e.answer};if(e.data.forEach((f,g)=>d[`term${g}`]=f),e.op==="add")return buildExample({generatorId:"DS.P3.M01.two_way_table.v1",question:n,title:n.stem,computed:d,steps:[{visual_data:s(o),animation:"highlight_missing",caption:"One number is missing from the table.",spoken:`This table has one missing number. It is the total for ${i}, and a total is everything in that row added together.`},{visual_data:s(o,`${e.data.join(" + ")} = ?`),animation:"sum_known",caption:"Add the known numbers along the row.",spoken:`So to find it, we add the numbers we know along ${i}: ${a.join(" and ")}.`},{visual_data:s(r),animation:"reveal_answer",caption:`${e.data.join(" + ")} = ${e.answer}.`,spoken:`${capitalised(a.join(" and "))} make ${u}, so ${u} goes in the empty cell. We found the missing total from the other numbers in its row.`}]});const m=e.total;d.total=m;const h=`${m} − ${e.data.join(" − ")}`;return buildExample({generatorId:"DS.P3.M01.two_way_table.v1",question:n,title:n.stem,computed:d,steps:[{visual_data:s(o),animation:"highlight_missing",caption:"One number is missing from the table.",spoken:`This table has one missing number, in ${i}. The total for that row is already there: ${numberToWords(m)}. We can use it to find the missing part.`},{visual_data:s(o,`${h} = ?`),animation:"sum_known",caption:`The row makes ${m} — take away the known part.`,spoken:`Everything in the row adds up to the total, ${numberToWords(m)}. We already know ${a.join(" and ")}, so we take ${a.length===1?"it":"them"} away from the total to find the part we don't know.`},{visual_data:s(r,`${h} = ${e.answer}`),animation:"reveal_answer",caption:`${h} = ${e.answer}.`,spoken:`${capitalised(numberToWords(m))} take away ${a.join(" take away ")} leaves ${u}, so ${u} goes in ${l}. Check: ${u} and ${a.join(" and ")} make ${numberToWords(m)}, the total.`}]})}},{id:"DS.P3.M01.claim-check.v1",microSkillId:"DS.P3.M01",forms:["misconception_contrast_probe"],generate(n){const t=n.visualData;return!t||t.type!=="two_way_table"?null:claimCheck$2(n,t)}},{id:"DS.P3.M01.column-total.v1",microSkillId:"DS.P3.M01",forms:["context_calculation"],generate(n){const t=n.visualData;return!t||t.type!=="two_way_table"?null:columnTotal(n,t)}},{id:"DS.P3.M01.compare-column.v1",microSkillId:"DS.P3.M01",forms:["comparison_statement"],generate(n){const t=n.visualData;return!t||t.type!=="two_way_table"?null:compareColumn(n,t)}}],WHICH_CMP=/^Which fraction is (greater|smaller): (\d+)\/(\d+) or (\d+)\/(\d+)\?$/,CLAIM_CMP$1=/^[A-Z][a-z]+ says (\d+)\/(\d+) is (bigger|greater|larger|smaller) than (\d+)\/(\d+)\. Is (?:he|she|they) right\?$/,WHO_MORE=/^(?:.*?\. )?([A-Z][a-z]+) (ate|ran|drank|used|read|walked|swam|painted|coloured|covered) (\d+)\/(\d+) of (?:an?|the|his|her)? ?(?:identical )?[a-z ]+? and ([A-Z][a-z]+) (?:ate|ran|drank|used|read|walked|swam|painted|coloured|covered) (\d+)\/(\d+) of (?:an?|the|his|her)? ?(?:identical )?[a-z ]+?\. Who (.+?)\?$/,NUMBER_LINE_RIGHT=/^On a number line marked in [a-z]+, which point is further to the right: (\d+)\/(\d+) or (\d+)\/(\d+)\?$/,SUPPORTED$3=new Set([2,3,4,5,6,8,10]),UNIT$5={2:"half",3:"third",4:"quarter",5:"fifth",6:"sixth",8:"eighth",10:"tenth"};function fractionWords$5(n,t){const e=UNIT$5[t],s=t===2?"halves":`${e}s`;return`${numberToWords(n)} ${n===1?e:s}`}function normFrac$1(n){return n.trim().replace(/\s*\/\s*/,"/")}const FDP_P5_M01_GENERATORS=[{id:"FDP.P5.M01.like-denominator-bars.v1",microSkillId:"FDP.P5.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WHICH_CMP);if(!t)return null;const e=t[1]==="greater",s=Number(t[2]),o=Number(t[3]),r=Number(t[4]),a=Number(t[5]);if(o!==a||!SUPPORTED$3.has(o)||s<1||s>o||r<1||r>o||s===r)return null;const i=o,l=e?Math.max(s,r):Math.min(s,r),u=`${l}/${o}`;if(normFrac$1(u)!==normFrac$1(n.correctAnswer))return null;const d=l===s?0:1,m=s<r?"<":">",h=fractionWords$5(l,o),f={left:s,right:r,denominator:o,answer:l},g=(E,k)=>[{total_parts:i,shaded_parts:E,label:`${s}/${o}`},{total_parts:i,shaded_parts:k,label:`${r}/${o}`}],b=(E,k)=>({type:"fraction_shape",shape:"rectangle",total_parts:i,shaded_parts:E,color:"#2563eb",shapes:g(E,k)}),x=e?`${u} covers more — it is greater.`:`${u} covers less — it is smaller.`,y=e?`Side by side, ${h} covers more of the bar. So ${h} is the greater fraction.`:`Side by side, ${h} covers less of the bar. So ${h} is the smaller fraction.`;return buildExample({generatorId:this.id,question:n,title:`Compare ${s}/${o} and ${r}/${o}.`,computed:f,steps:[{visual_data:b(0,0),animation:"cut_bars",caption:`Two bars cut into ${o} equal parts.`,spoken:`Here are two bars the same size. Each is cut into ${numberToWords(o)} equal parts, so every piece is the same size.`},{visual_data:b(s,r),animation:"shade_pieces",caption:`Shade ${s}, then shade ${r}.`,spoken:`The first bar has ${fractionWords$5(s,o)} shaded. The second bar has ${fractionWords$5(r,o)} shaded.`},{visual_data:{...b(s,r),compare_symbol:m,emphasis:d},animation:"compare_side_by_side",caption:x,spoken:`${capitalised(y)}`}]})}},{id:"FDP.P5.M01.claim-check-same-denom.v1",microSkillId:"FDP.P5.M01",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_CMP$1);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=t[3]==="bigger"||t[3]==="greater"||t[3]==="larger",r=Number(t[4]),a=Number(t[5]);if(s!==a||!SUPPORTED$3.has(s)||e<1||e>s||r<1||r>s||e===r)return null;const i=o?e>r:e<r;if(i){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const l=s,u=o?Math.max(e,r):Math.min(e,r),d=u===e?0:1,m=e<r?"<":">",h=(y,E)=>[{total_parts:l,shaded_parts:y,label:`${e}/${s}`},{total_parts:l,shaded_parts:E,label:`${r}/${s}`}],f=(y,E)=>({type:"fraction_shape",shape:"rectangle",total_parts:l,shaded_parts:y,color:"#2563eb",shapes:h(y,E)}),g=o?"bigger":"smaller",b=i?"Correct":"Not correct",x={left:e,right:r,denominator:s,answer:i?1:0};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is ${e}/${s} ${g} than ${r}/${s}?`,computed:x,steps:[{visual_data:f(0,0),animation:"cut_bars",caption:`Two bars cut into ${s} equal parts.`,spoken:`Here are two bars the same size, both cut into ${numberToWords(s)} equal parts, so every piece is the same size.`},{visual_data:f(e,r),animation:"shade_pieces",caption:`Shade ${e}, then shade ${r}.`,spoken:`The first bar has ${fractionWords$5(e,s)} shaded. The second bar has ${fractionWords$5(r,s)} shaded.`},{visual_data:{...f(e,r),compare_symbol:m,emphasis:d},animation:"compare_side_by_side",caption:`${b} — ${u}/${s} covers more.`,spoken:`Since the pieces are the same size, we just count how many are shaded. The claim is ${i?"correct":"not correct"}.`}]})}},{id:"FDP.P5.M01.word-problem-compare.v1",microSkillId:"FDP.P5.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WHO_MORE);if(!t)return null;const e=t[1],s=Number(t[3]),o=Number(t[4]),r=t[5],a=Number(t[6]),i=Number(t[7]),l=t[8];
71if(o!==i||!SUPPORTED$3.has(o)||s<1||s>o||a<1||a>o||s===a||!/\b(more|further|farther)\b/i.test(l))return null;const u=s>a?e:r;if(u!==n.correctAnswer)return null;const d=o,m=s>a?s:a,h=s>a?0:1,f=s<a?"<":">",g=(y,E)=>[{total_parts:d,shaded_parts:y,label:`${e}: ${s}/${o}`},{total_parts:d,shaded_parts:E,label:`${r}: ${a}/${o}`}],b=(y,E)=>({type:"fraction_shape",shape:"rectangle",total_parts:d,shaded_parts:y,color:"#2563eb",shapes:g(y,E)}),x={left:s,right:a,denominator:o};return buildExample({generatorId:this.id,question:n,title:`Compare ${e}'s ${s}/${o} with ${r}'s ${a}/${o}.`,computed:x,steps:[{visual_data:b(0,0),animation:"cut_bars",caption:`Both wholes are the same size, cut into ${o} equal parts.`,spoken:`Both amounts are the same size overall, cut into ${numberToWords(o)} equal parts, so every piece is the same size.`},{visual_data:b(s,a),animation:"shade_pieces",caption:`${e} has ${s}/${o} shaded, ${r} has ${a}/${o} shaded.`,spoken:`The pieces are all the same size, so we just count the shaded pieces. ${e} has ${fractionWords$5(s,o)}, and ${r} has ${fractionWords$5(a,o)}.`},{visual_data:{...b(s,a),compare_symbol:f,emphasis:h},animation:"compare_side_by_side",caption:`${u} has more — ${m}/${o} covers more of the bar.`,spoken:`Side by side, ${u}'s bar covers more: ${fractionWords$5(m,o)} is the bigger fraction. So ${u} has more.`}]})}},{id:"FDP.P5.M01.number-line-right.v1",microSkillId:"FDP.P5.M01",forms:["number_line_location"],generate(n){const t=normaliseStem(n.stem).match(NUMBER_LINE_RIGHT);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(s!==r||!SUPPORTED$3.has(s)||e<1||e>s||o<1||o>s||e===o)return null;const a=Math.max(e,o),i=`${a}/${s}`;if(normFrac$1(i)!==normFrac$1(n.correctAnswer))return null;const l=e/s,u=o/s,d={left:e,right:o,denominator:s,answer:a,min:0,max:1},m=(h,f)=>({from:h,to:h,label:f});return buildExample({generatorId:this.id,question:n,title:`Which point is further right: ${e}/${s} or ${o}/${s}?`,computed:d,steps:[{visual_data:{type:"number_line",min:0,max:1,hops:[]},animation:"show_line",caption:"A number line from 0 to 1.",spoken:"Here is a number line from nought to one. Numbers get bigger as we go right, so we place both fractions on it to see which is further along."},{visual_data:{type:"number_line",min:0,max:1,hops:[m(l,`${e}/${s}`),m(u,`${o}/${s}`)]},animation:"plot_points",caption:`Plot ${e}/${s} and ${o}/${s}.`,spoken:`We plot both points: ${fractionWords$5(e,s)}, and ${fractionWords$5(o,s)}.`},{visual_data:{type:"number_line",min:0,max:1,hops:a===e?[m(u,`${o}/${s}`),m(l,`${e}/${s}`)]:[m(l,`${e}/${s}`),m(u,`${o}/${s}`)],conclude_label:`${i} is further right.`},animation:"highlight_further_right",caption:`${i} is further to the right.`,spoken:`The point further along the line is the bigger fraction. ${capitalised(fractionWords$5(a,s))} sits further to the right.`}]})}}],ORDER_SMALLEST=/^Order these from smallest: (\d+)\/(\d+), (\d+)\/(\d+), (\d+)\/(\d+)$/,CLAIM_ORDER=/^[A-Z][a-z]+ puts these fractions in order like this: (\d+)\/(\d+), (\d+)\/(\d+), (\d+)\/(\d+)\. Is that the correct order from smallest to largest\?$/,WORD_ORDER=/^.+ measure (\d+)\/(\d+) ([a-z]+), (\d+)\/(\d+) \3 and (\d+)\/(\d+) \3\. Put the .+ order from (?:shortest to longest|smallest to largest)$/i,ORDER_LARGEST=/^Which list shows these fractions in order from largest to smallest: (\d+)\/(\d+), (\d+)\/(\d+), (\d+)\/(\d+)\?$/,BETWEEN=/^These fractions are in order from smallest to largest: (\d+)\/(\d+), __, (\d+)\/(\d+)\. Which fraction could be the missing one\?$/,SUPPORTED$2=new Set([2,3,4,5,6,8,10]),UNIT$4={2:"half",3:"third",4:"quarter",5:"fifth",6:"sixth",8:"eighth",10:"tenth"};function fractionWords$4(n,t){const e=t===2?"halves":`${UNIT$4[t]}s`;return`${numberToWords(n)} ${n===1?UNIT$4[t]:e}`}function fracList(n){return n.split(",").map(t=>t.trim().replace(/\s*\/\s*/,"/")).filter(Boolean)}const FDP_P5_M02_GENERATORS=[{id:"FDP.P5.M02.like-denominator-order.v1",microSkillId:"FDP.P5.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(ORDER_SMALLEST);if(!t)return null;const e=[Number(t[1]),Number(t[3]),Number(t[5])],s=[Number(t[2]),Number(t[4]),Number(t[6])],o=s[0];if(s[1]!==o||s[2]!==o||!SUPPORTED$2.has(o)||e.some(g=>g<1||g>o)||new Set(e).size!==3)return null;const r=o,a=[...e].sort((g,b)=>g-b),i=a.map(g=>`${g}/${o}`),l=fracList(n.correctAnswer);if(l.length!==3||l.join("|")!==i.join("|"))return null;const u={n1:e[0],n2:e[1],n3:e[2],denominator:o,answer:a[0]},d=g=>g.map(b=>({total_parts:r,shaded_parts:b,label:`${b}/${o}`})),m=g=>({type:"fraction_shape",shape:"rectangle",total_parts:r,shaded_parts:g[0],color:"#2563eb",shapes:d(g)}),h=e.every((g,b)=>g===a[b]),f=i.map((g,b)=>fractionWords$4(a[b],o)).join(", ");return buildExample({generatorId:this.id,question:n,title:`Order ${e[0]}/${o}, ${e[1]}/${o}, ${e[2]}/${o} smallest first.`,computed:u,steps:[{visual_data:m(e),animation:"show_bars",caption:`Three bars, all cut into ${o} parts.`,spoken:`Here are the three fractions, each cut into ${numberToWords(o)} equal parts. Let's check their order.`},{visual_data:{...m(a),emphasis:0},animation:"reorder",caption:h?"Already smallest first — check the shaded parts.":"Now stand them smallest first.",spoken:h?"The pieces are all the same size. Count the shaded parts: they already increase from left to right, so keep this order.":"The pieces are all the same size, so we count how many are shaded and stand the bars smallest first."},{visual_data:{...m(a),order_labels:i},animation:"list_order",caption:`Smallest first: ${i.join(", ")}.`,spoken:`In order from smallest it goes ${f}.`}]})}},{id:"FDP.P5.M02.claim-check-order.v1",microSkillId:"FDP.P5.M02",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_ORDER);if(!t)return null;const e=[Number(t[1]),Number(t[3]),Number(t[5])],s=[Number(t[2]),Number(t[4]),Number(t[6])],o=s[0];if(s[1]!==o||s[2]!==o||!SUPPORTED$2.has(o)||e.some(m=>m<1||m>o)||new Set(e).size!==3)return null;const r=e[0]<e[1]&&e[1]<e[2];if(r){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const a=o,i=[...e].sort((m,h)=>m-h),l=m=>m.map(h=>({total_parts:a,shaded_parts:h,label:`${h}/${o}`})),u=m=>({type:"fraction_shape",shape:"rectangle",total_parts:a,shaded_parts:m[0],color:"#2563eb",shapes:l(m)}
71),d={n1:e[0],n2:e[1],n3:e[2],denominator:o,answer:r?1:0};return buildExample({generatorId:this.id,question:n,title:`Check the order: ${e[0]}/${o}, ${e[1]}/${o}, ${e[2]}/${o}.`,computed:d,steps:[{visual_data:u(e),animation:"show_bars",caption:`Three bars, all cut into ${o} parts.`,spoken:`Here are the three fractions, each cut into ${numberToWords(o)} equal parts, shown in the claimed order.`},{visual_data:u(e),animation:"count_shaded",caption:`Count the shaded parts: ${e.join(", ")}.`,spoken:"Since the pieces are the same size, we just count how many are shaded in each bar, in order."},{visual_data:{...u(i),order_labels:i.map(m=>`${m}/${o}`)},animation:"check_order",caption:`${r?"Correct":"Not correct"} — smallest first is ${i.map(m=>`${m}/${o}`).join(", ")}.`,spoken:`The claim is ${r?"correct":"not correct"}.`}]})}},{id:"FDP.P5.M02.word-problem-order.v1",microSkillId:"FDP.P5.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WORD_ORDER);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=t[3],r=Number(t[4]),a=Number(t[5]),i=Number(t[6]),l=Number(t[7]);if(a!==s||l!==s||!SUPPORTED$2.has(s))return null;const u=[e,r,i];if(u.some(x=>x<1||x>s)||new Set(u).size!==3)return null;const d=[...u].sort((x,y)=>x-y),m=d.map(x=>`${x}/${s} ${o}`).join(", ");if(m.trim().replace(/\s+/g," ")!==n.correctAnswer.trim().replace(/\s+/g," "))return null;const h=s,f=x=>x.map(y=>({total_parts:h,shaded_parts:y,label:`${y}/${s} ${o}`})),g=x=>({type:"fraction_shape",shape:"rectangle",total_parts:h,shaded_parts:x[0],color:"#2563eb",shapes:f(x)}),b={n1:e,n2:r,n3:i,denominator:s};return buildExample({generatorId:this.id,question:n,title:`Order ${e}/${s} ${o}, ${r}/${s} ${o}, ${i}/${s} ${o} shortest first.`,computed:b,steps:[{visual_data:g(u),animation:"show_bars",caption:`Three lengths, all measured in ${s}ths.`,spoken:`Here are the three lengths, each cut into ${numberToWords(s)} equal parts, all jumbled up.`},{visual_data:g(d),animation:"reorder",caption:"Now stand them shortest first.",spoken:"The pieces are all the same size, so we just count how many are shaded and stand the bars shortest first."},{visual_data:{...g(d),order_labels:d.map(x=>`${x}/${s} ${o}`)},animation:"list_order",caption:`Shortest first: ${m}.`,spoken:`In order from shortest it goes ${d.map(x=>fractionWords$4(x,s)).join(", ")}.`}]})}},{id:"FDP.P5.M02.like-denominator-order-descending.v1",microSkillId:"FDP.P5.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(ORDER_LARGEST);if(!t)return null;const e=[Number(t[1]),Number(t[3]),Number(t[5])],s=[Number(t[2]),Number(t[4]),Number(t[6])],o=s[0];if(s[1]!==o||s[2]!==o||!SUPPORTED$2.has(o)||e.some(h=>h<1||h>o)||new Set(e).size!==3)return null;const r=[...e].sort((h,f)=>f-h),a=r.map(h=>`${h}/${o}`),i=fracList(n.correctAnswer);if(i.length!==3||i.join("|")!==a.join("|"))return null;const l=o,u=h=>h.map(f=>({total_parts:l,shaded_parts:f,label:`${f}/${o}`})),d=h=>({type:"fraction_shape",shape:"rectangle",total_parts:l,shaded_parts:h[0],color:"#2563eb",shapes:u(h)}),m={n1:e[0],n2:e[1],n3:e[2],denominator:o,answer:r[0]};return buildExample({generatorId:this.id,question:n,title:`Order ${e[0]}/${o}, ${e[1]}/${o}, ${e[2]}/${o} largest first.`,computed:m,steps:[{visual_data:d(e),animation:"show_bars",caption:`Three bars, all cut into ${o} parts.`,spoken:`Here are the three fractions, each cut into ${numberToWords(o)} equal parts, all jumbled up.`},{visual_data:d(r),animation:"reorder",caption:"Now stand them largest first.",spoken:"The pieces are all the same size, so we just count how many are shaded and stand the bars largest first."},{visual_data:{...d(r),order_labels:a,order_symbol:">"},animation:"list_order",caption:`Largest first: ${a.join(", ")}.`,spoken:`In order from largest it goes ${r.map(h=>fractionWords$4(h,o)).join(", ")}.`}]})}},{id:"FDP.P5.M02.between.v1",microSkillId:"FDP.P5.M02",forms:["alternate_reasoning_prompt"],generate(n){const t=normaliseStem(n.stem).match(BETWEEN);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(s!==r||!SUPPORTED$2.has(s)||e<1||e>s||o<1||o>s||e>=o)return null;const a=fracList(n.correctAnswer);if(a.length!==1)return null;const i=a[0].match(/^(\d+)\/(\d+)$/);if(!i)return null;const l=Number(i[1]);if(Number(i[2])!==s||!(l>e&&l<o))return null;const d=s,m=g=>g.map(b=>({total_parts:d,shaded_parts:b??0,label:b===null?"?":`${b}/${s}`})),h=g=>({type:"fraction_shape",shape:"rectangle",total_parts:d,shaded_parts:g[0]??0,color:"#2563eb",shapes:m(g)}),f={low:e,high:o,denominator:s,answer:l};return buildExample({generatorId:this.id,question:n,title:`Which fraction fits between ${e}/${s} and ${o}/${s}?`,computed:f,steps:[{visual_data:h([e,null,o]),animation:"show_gap",caption:`${e}/${s}, then a gap, then ${o}/${s}.`,spoken:"Here are the two known fractions in order, with a gap between them. The pieces are all the same size, so we can compare by counting shaded pieces."},{visual_data:h([e,l,o]),animation:"try_middle",caption:`Try ${l}/${s} in the gap.`,spoken:`We need a fraction with more shaded than ${fractionWords$4(e,s)}, but less shaded than ${fractionWords$4(o,s)}. Let's try ${fractionWords$4(l,s)}.`},{visual_data:{...h([e,l,o]),order_labels:[`${e}/${s}`,`${l}/${s}`,`${o}/${s}`]},animation:"confirm_between",caption:`${l}/${s} sits between ${e}/${s} and ${o}/${s}.`,spoken:`${capitalised(fractionWords$4(l,s))} has more shaded than ${fractionWords$4(e,s)} and less than ${fractionWords$4(o,s)}, so it fits in the gap.`}]})}}],WHICH_GREATER$1=/^Which fraction is greater: (\d+)\/(\d+) or (\d+)\/(\d+)\?$/,WHICH_IS_LARGER=/^Which is larger: (\d+)\/(\d+) or (\d+)\/(\d+)\?$/,CLAIM_CMP=/^[A-Z][a-z]+ says (\d+)\/(\d+) is bigger than (\d+)\/(\d+)\. Is (?:he|she|they) right\?$/,EQUIVALENT_READ=/^(.+?)'s .+? and (.+?)'s .+? have the same .+?\. \1 has (?:read|used|coloured|painted|drunk) (\d+)\/(\d+) of (?:her|his|their) .+?\. \2 has (?:read|used|coloured|painted|drunk) (\d+)\/(\d+) of (?:her|his|their) .+?\. Who has (?:read|used|coloured|painted|drunk) more\??$/,NUMBER_LINE_UNLIKE=/^Two number lines .+ from 0 to 1\. One is marked in [a-z]+ and the other in [a-z]+\. Which point is further along the line: (\d+)\/(\d+) or (\d+)\/(\d+)\?$/,SUPPORTED$1=new Set([2,3,4,5,6,8,10]),UNIT$3={2:"half",3:"third",4:"quarter",5:"fifth",6:"sixth",8:"eighth",10:"tenth"};function fractionWords$3(n,t){const e=t===2?"halves":`${UNIT$3[t]}s`;return`${numberToWords(n)} ${n===1?UNIT$3[t]:e}`}function normFrac(n){return n.trim().replace(/\s*\/\s*/,"/")}const FDP_P5_M03_GENERATORS=[{id:"FDP.P5.M03.unlike-denominator-bars.v1",microSkillId:"FDP.P5.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WHICH_GREATER$1);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(!SUPPORTED$1.has(s)||!SUPPORTED$1.has(r)||e<1||e>s||o<1||o>r)return null;const a=e*r-o*s;if(a===0)return null;const i=e===1&&o===1;if(!i&&12*Math.abs(a)<s*r)return null;const l=a>0,u=l?`${e}/${s}`:`${o}/${r}`;if(normFrac(u)!==normFrac(n.correctAnswer))return null;const d=s,m=r,h=l?">":"<",f=l?0:1,g=l?e:o,x=fractionWords$3(g,l?s:r),y={left:e,left_denominator:s,right:o,right_denominator:r,answer:g}
71,E=(A,N)=>[{total_parts:d,shaded_parts:A,label:`${e}/${s}`},{total_parts:m,shaded_parts:N,label:`${o}/${r}`}],k=(A,N)=>({type:"fraction_shape",shape:"rectangle",total_parts:d,shaded_parts:A,color:"#2563eb",shapes:E(A,N)}),T=i?"We shade one piece on each bar. The more pieces a bar is cut into, the smaller each piece.":`The first bar has ${fractionWords$3(e,s)} shaded. The second has ${fractionWords$3(o,r)} shaded.`;return buildExample({generatorId:this.id,question:n,title:`Compare ${e}/${s} and ${o}/${r}.`,computed:y,steps:[{visual_data:k(0,0),animation:"cut_differently",caption:`Two bars, cut into ${s} and into ${r}.`,spoken:`Here are two bars the same size. One is cut into ${numberToWords(s)} parts, the other into ${numberToWords(r)}.`},{visual_data:k(e,o),animation:"shade_pieces",caption:`Shade ${e}, then shade ${o}.`,spoken:T},{visual_data:{...k(e,o),compare_symbol:h,emphasis:f},animation:"compare_pieces",caption:`${u} covers more — it is greater.`,spoken:`Side by side, ${x} covers more of the bar. So ${x} is the greater fraction.`}]})}},{id:"FDP.P5.M03.claim-check-unlike-denom.v1",microSkillId:"FDP.P5.M03",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_CMP);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(!SUPPORTED$1.has(s)||!SUPPORTED$1.has(r)||e<1||e>s||o<1||o>r)return null;const a=e*r-o*s;if(a===0)return null;const i=a>0;if(i){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const l=s,u=r,d=i?0:1,m=a>0?">":"<",h=(b,x)=>[{total_parts:l,shaded_parts:b,label:`${e}/${s}`},{total_parts:u,shaded_parts:x,label:`${o}/${r}`}],f=(b,x)=>({type:"fraction_shape",shape:"rectangle",total_parts:l,shaded_parts:b,color:"#2563eb",shapes:h(b,x)}),g={left:e,left_denominator:s,right:o,right_denominator:r,answer:i?1:0};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is ${e}/${s} bigger than ${o}/${r}?`,computed:g,steps:[{visual_data:f(0,0),animation:"cut_differently",caption:`Two bars, cut into ${s} and into ${r}.`,spoken:`Here are two bars the same size. One is cut into ${numberToWords(s)} parts, the other into ${numberToWords(r)}.`},{visual_data:f(e,o),animation:"shade_pieces",caption:`Shade ${e}, then shade ${o}.`,spoken:`The first bar has ${fractionWords$3(e,s)} shaded. The second has ${fractionWords$3(o,r)} shaded.`},{visual_data:{...f(e,o),compare_symbol:m,emphasis:d},animation:"compare_pieces",caption:`${i?"Correct":"Not correct"} — the shaded lengths show which is bigger.`,spoken:`Side by side, we can see which bar covers more. The claim is ${i?"correct":"not correct"}.`}]})}},{id:"FDP.P5.M03.equivalent-fractions.v1",microSkillId:"FDP.P5.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(EQUIVALENT_READ);if(!t)return null;const e=t[1],s=t[2],o=Number(t[3]),r=Number(t[4]),a=Number(t[5]),i=Number(t[6]);if(!SUPPORTED$1.has(r)||!SUPPORTED$1.has(i)||o<1||o>r||a<1||a>i||o*i-a*r!==0||n.correctAnswer===e||n.correctAnswer===s)return null;const u=r,d=i,m=(g,b)=>[{total_parts:u,shaded_parts:g,label:`${e}: ${o}/${r}`},{total_parts:d,shaded_parts:b,label:`${s}: ${a}/${i}`}],h=(g,b)=>({type:"fraction_shape",shape:"rectangle",total_parts:u,shaded_parts:g,color:"#2563eb",shapes:m(g,b)}),f={left:o,left_denominator:r,right:a,right_denominator:i};return buildExample({generatorId:this.id,question:n,title:`Compare ${e}'s ${o}/${r} with ${s}'s ${a}/${i}.`,computed:f,steps:[{visual_data:h(0,0),animation:"cut_differently",caption:`Two equal wholes, cut into ${r} and into ${i}.`,spoken:`Both wholes are the same size overall. One is cut into ${numberToWords(r)} parts, the other into ${numberToWords(i)}.`},{visual_data:h(o,a),animation:"shade_pieces",caption:`${e} shades ${o}/${r}, ${s} shades ${a}/${i}.`,spoken:`${e} has ${fractionWords$3(o,r)} shaded. ${s} has ${fractionWords$3(a,i)} shaded.`},{visual_data:{...h(o,a),compare_symbol:"="},animation:"compare_pieces",caption:`Same amount shaded — ${o}/${r} and ${a}/${i} are equal.`,spoken:`Even though the bars are cut differently, exactly the same amount is shaded on each. So ${e} and ${s} have read the same amount.`}]})}},{id:"FDP.P5.M03.number-line-unlike.v1",microSkillId:"FDP.P5.M03",forms:["number_line_location"],generate(n){const t=normaliseStem(n.stem).match(NUMBER_LINE_UNLIKE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(!SUPPORTED$1.has(s)||!SUPPORTED$1.has(r)||e<1||e>s||o<1||o>r)return null;const a=e*r-o*s;if(a===0||12*Math.abs(a)<s*r)return null;const i=a>0,l=i?`${e}/${s}`:`${o}/${r}`;if(normFrac(l)!==normFrac(n.correctAnswer))return null;const u=e/s,d=o/r,m={left:e,left_denominator:s,right:o,right_denominator:r},h=(f,g)=>({from:f,to:f,label:g});return buildExample({generatorId:this.id,question:n,title:`Which point is further along: ${e}/${s} or ${o}/${r}?`,computed:m,steps:[{visual_data:{type:"number_line",min:0,max:1,hops:[]},animation:"show_line",caption:"Both lines go from 0 to 1.",spoken:"Both number lines are the same length, going from nought to one."},{visual_data:{type:"number_line",min:0,max:1,hops:[h(u,`${e}/${s}`),h(d,`${o}/${r}`)]},animation:"plot_points",caption:`Plot ${e}/${s} and ${o}/${r}.`,spoken:`We plot both points: ${fractionWords$3(e,s)}, and ${fractionWords$3(o,r)}.`},{visual_data:{type:"number_line",min:0,max:1,hops:i?[h(d,`${o}/${r}`),h(u,`${e}/${s}`)]:[h(u,`${e}/${s}`),h(d,`${o}/${r}`)],conclude_label:`${l} is further along.`},animation:"highlight_further",caption:`${l} is further along the line.`,spoken:`The point further along the line is the bigger fraction — ${fractionWords$3(i?e:o,i?s:r)} sits further along.`}]})}},{id:"FDP.P5.M03.compare-larger.v1",microSkillId:"FDP.P5.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WHICH_IS_LARGER);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(!SUPPORTED$1.has(s)||!SUPPORTED$1.has(r)||e<1||e>s||o<1||o>r)return null;const a=e*r-o*s;if(a===0)return null;const i=a>0,l=i?`${e}/${s}`:`${o}/${r}`;if(normFrac(l)!==normFrac(n.correctAnswer))return null;const u=s,d=r,m=i?">":"<",h=i?0:1,f=i?e:o,g=i?s:r,b=(E,k)=>[{total_parts:u,shaded_parts:E,label:`${e}/${s}`},{total_parts:d,shaded_parts:k,label:`${o}/${r}`}],x=(E,k)=>({type:"fraction_shape",shape:"rectangle",total_parts:u,shaded_parts:E,color:"#2563eb",shapes:b(E,k)}),y={left:e,left_denominator:s,right:o,right_denominator:r,answer:f};return buildExample({generatorId:this.id,question:n,title:`Which is larger: ${e}/${s} or ${o}/${r}?`,computed:y,steps:[{visual_data:x(0,0),animation:"cut_differently",caption:`Two bars, cut into ${s} and into ${r}.`,spoken:`Here are two bars the same size. One is cut into ${numberToWords(s)} parts, the other into ${numberToWords(r)}.`},{visual_data:x(e,o),animation:"shade_pieces",caption:`Shade ${e}, then shade ${o}.`,spoken:`The first bar has ${fractionWords$3(e,s)} shaded. The second has ${fractionWords$3(o,r)} shaded.`},{visual_data:{...x(e,o),compare_symbol:m,emphasis:h},animation:"compare_pieces",caption:`${l} is larger.`,spoken:`Side by side, ${fractionWords$3(f,g)} covers more of the bar. So ${fractionWords$3(f,g)} is the larger fraction.`}]})}}],FDP_P6_M07_GENERATORS=FDP_P5_M03_GENERATORS.map(n=>({...n,id:n.id.replace("FDP.P5.M03","FDP.P6.M07"),microSkillId:"FDP.P6.M07"})),SUPPORTED=new Set([2,3,4,5,6,8,10]),UNIT$2={2:"half",3:"third",4:"quarter",5:"fifth",6:"sixth",8:"eighth",10:"tenth"};function fractionWords$2(n,t){const e=t===2?"halves":`${UNIT$2[t]}s`;return`${numberToWords(n)} ${n===1?UNIT$2[t]:e}`}function normLabel$3(n){return n.trim().toLowerCase().replace(/\s+/g," ")}const CLAIM_HALF=/^[A-Z][a-z]+ says (\d+)\/(\d+) is (more|less) than a half\. Is (?:he|she|they) right\?$/,NEARER_ZERO_OR_HALF=/^A .+? is (\d+)\/(\d+) full\. Is the .+? closer to empty \(0\) or half full \(1\/2\)\?$/,CLOSEST_TO_HALF=/^Which fraction is closest to a half: (\d+)\/(\d+), (\d+)\/(\d+), or (\d+)\/(\d+)\?$/,CLOSER_TO_HALF_PAIR=/^Which fraction is close(?:r|st) to a half: (\d+)\/(\d+) or (\d+)\/(\d+)\?$/,CLOSEST_TO_ONE=/^A fraction is marked on a number line between 0 and 1\. It sits closer to 1 than it does to 0 or to 1\/2\. Which of these could it be: (\d+)\/(\d+), (\d+)\/(\d+), or (\d+)\/(\d+)\?$/,FDP_P5_M04_GENERATORS=[{id:"FDP.P5.M04.half-benchmark.v1",microSkillId:"FDP.P5.M04",forms:["comparison_statement"],generate(n){const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t.total_parts,s=t.shaded_parts;if(!SUPPORTED.has(e)||!Number.isInteger(s)||s<0||s>e)return null;const o=n.stem.toLowerCase(),r=o.includes("nearer 0 or 1"),a=o.includes("more than, less than, or exactly a half");if(!r&&!a)return null;const i=s*2;let l;if(r){if(i===e)return null;l=i<e?"Nearer 0":"Nearer 1"}else l=i===e?"Exactly a half":i<e?"Less than a half":"More than a half";if(normLabel$3(l)!==normLabel$3(n.correctAnswer))return null;const u=e,d=numberToWords(e),m=numberToWords(s),h=(x={})=>({type:"fraction_shape",shape:"rectangle",total_parts:u,shaded_parts:s,color:"#2563eb",split_columns:!0,...x}),f={total_parts:e,shaded_parts:s,answer:i<e?-1:i>e?1:0},g=r?l==="Nearer 0"?"Under half, so nearer 0.":"Over half, so nearer 1.":`${l}.`,b=r?l==="Nearer 0"?"The shading stops short of the line, so it is nearer nought.":"The shading runs past the line, so it is nearer one.":l==="Exactly a half"?"The shading reaches exactly the line, so it is exactly a half.":l==="Less than a half"?"The shading stops short of the line, so it is less than a half.":"The shading runs past the line, so it is more than a half.";return buildExample({generatorId:this.id,question:n,title:"Compare the shaded amount with a half.",computed:f,steps:[{visual_data:h(),animation:"shade",caption:`${s} out of ${e} is shaded.`,spoken:`Here is a bar cut into ${d} equal parts, with ${m} shaded.`},{visual_data:h({half_line:!0}
71),animation:"drop_half_line",caption:"Drop a line at the halfway mark.",spoken:"A line drops down at the halfway mark. That line shows exactly one half."},{visual_data:h({half_line:!0,benchmark_verdict:l}),animation:"benchmark_verdict",caption:g,spoken:b}]})}},{id:"FDP.P5.M04.claim-check-half.v1",microSkillId:"FDP.P5.M04",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_HALF);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=t[3]==="more";if(!SUPPORTED.has(s)||e<0||e>s)return null;const r=e*2;if(r===s)return null;const a=r>s,i=o?a:!a;if(i){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const l=s,u=(m={})=>({type:"fraction_shape",shape:"rectangle",total_parts:l,shaded_parts:e,color:"#2563eb",...m}),d={total_parts:s,shaded_parts:e,answer:i?1:0};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is ${e}/${s} ${o?"more":"less"} than a half?`,computed:d,steps:[{visual_data:u(),animation:"shade",caption:`${e} out of ${s} is shaded.`,spoken:`Here is a bar cut into ${numberToWords(s)} equal parts, with ${fractionWords$2(e,s)} shaded.`},{visual_data:u({half_line:!0}
71),animation:"drop_half_line",caption:"Drop a line at the halfway mark.",spoken:"A line drops down at the halfway mark. That line shows exactly one half."},{visual_data:u({half_line:!0,benchmark_verdict:i?"Correct":"Not correct"}),animation:"benchmark_verdict",caption:`${i?"Correct":"Not correct"} — compare the shading with the line.`,spoken:`The claim is ${i?"correct":"not correct"}.`}]})}},{id:"FDP.P5.M04.nearer-zero-or-half.v1",microSkillId:"FDP.P5.M04",forms:["alternate_reasoning_prompt"],generate(n){const t=normaliseStem(n.stem).match(NEARER_ZERO_OR_HALF);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(!SUPPORTED.has(s)||e<0||e>s)return null;const o=e*4-s;if(o===0)return null;const r=o<0,a=r?"Closer to empty (0)":"Closer to half full (1/2)";if(normLabel$3(a)!==normLabel$3(n.correctAnswer))return null;const i=s,l=(d={})=>({type:"fraction_shape",shape:"rectangle",total_parts:i,shaded_parts:e,color:"#2563eb",...d}),u={total_parts:s,shaded_parts:e,answer:r?-1:1};return buildExample({generatorId:this.id,question:n,title:`Is ${e}/${s} closer to empty or half full?`,computed:u,steps:[{visual_data:l(),animation:"shade",caption:`${e} out of ${s} is filled.`,spoken:`Here is the bottle, cut into ${numberToWords(s)} equal parts, with ${fractionWords$2(e,s)} filled.`},{visual_data:l({half_line:!0}),animation:"drop_half_line",caption:"Mark empty (0) and half full (1/2).",spoken:"We mark empty at nought, and half full at the half
71way line."},{visual_data:l({half_line:!0,benchmark_verdict:a}),animation:"benchmark_verdict",caption:`${a}.`,spoken:r?"The filled amount is much closer to empty than to half full.":"The filled amount is much closer to half full than to empty."}]})}},{id:"FDP.P5.M04.closest-to-half.v1",microSkillId:"FDP.P5.M04",forms:["alternate_reasoning_prompt"],generate(n){const t=normaliseStem(n.stem),e=t.match(CLOSEST_TO_HALF),s=e?null:t.match(CLOSER_TO_HALF_PAIR),o=e??s;if(!o)return null;const r=e?[{n:Number(o[1]),d:Number(o[2])},{n:Number(o[3]),d:Number(o[4])},{n:Number(o[5]),d:Number(o[6])}]:[{n:Number(o[1]),d:Number(o[2])},{n:Number(o[3]),d:Number(o[4])}];if(r.some(T=>!SUPPORTED.has(T.d)||T.n<0||T.n>T.d))return null;const a=r.map(T=>Math.abs(2*T.n-T.d)/T.d),i=Math.min(...a);if(a.filter(T=>T===i).length!==1)return null;const u=a.indexOf(i),d=r[u],m=`${d.n}/${d.d}`;if(m.trim()!==n.correctAnswer.trim())return null;const h=r.map(T=>T.d),f=r.map((T,A)=>({total_parts:h[A],shaded_parts:T.n,label:`${T.n}/${T.d}`})),g=(T={})=>({type:"fraction_shape",shape:"rectangle",total_parts:h[0],shaded_parts:r[0].n,color:"#2563eb",shapes:f,...T}),b={};r.forEach((T,A)=>{b[`n${A+1}`]=T.n,b[`d${A+1}`]=T.d});const x=r.length===2,y=x?"two":"three",E=x?"Two":"Three",k=x?"both":"all three";return buildExample({generatorId:this.id,question:n,title:`Which fraction is closer to a half: ${r.map(T=>`${T.n}/${T.d}`).join(", ")}?`,computed:b,steps:[{visual_data:g(),animation:"show_bars",caption:`${E} bars, shaded to each fraction.`,spoken:`Here are ${y} bars, each shaded to show one of the fractions. To find the one closest to a half, we compare each bar with its halfway mark.`},{visual_data:g({half_line:!0}),animation:"drop_half_line",caption:`Drop a halfway line on ${k} bars.`,spoken:"A line drops down the halfway mark on each bar. That line shows exactly one half."},{visual_data:g({half_line:!0,emphasis:u}),animation:"highlight_closest",caption:`${m} sits closest to the halfway line.`,spoken:`${capitalised(fractionWords$2(d.n,d.d))} is shaded closest to the halfway line, so it is closest to a half.`}]})}},{id:"FDP.P5.M04.closest-to-one.v1",microSkillId:"FDP.P5.M04",forms:["number_line_location"],generate(n){const t=normaliseStem(n.stem).match(CLOSEST_TO_ONE);if(!t)return null;const e=[{n:Number(t[1]),d:Number(t[2])},{n:Number(t[3]),d:Number(t[4])},{n:Number(t[5]),d:Number(t[6])}];if(e.some(h=>!SUPPORTED.has(h.d)||h.n<0||h.n>h.d))return null;const s=h=>{const f=2*h.n,g=Math.abs(2*h.n-h.d),b=2*(h.d-h.n);return b<f&&b<g},o=e.filter(s);if(o.length!==1)return null;const r=o[0],a=e.indexOf(r),i=`${r.n}/${r.d}`;if(i.trim()!==n.correctAnswer.trim())return null;const l={n1:e[0].n,d1:e[0].d,n2:e[1].n,d2:e[1].d,n3:e[2].n,d3:e[2].d},u=(h,f)=>({from:h,to:h,label:f}),d=e.map(h=>u(h.n/h.d,`${h.n}/${h.d}`)),m=[...d.filter((h,f)=>f!==a),d[a]];return buildExample({generatorId:this.id,question:n,title:`Which fraction sits closest to 1: ${e.map(h=>`${h.n}/${h.d}`).join(", ")}?`,computed:l,steps:[{visual_data:{type:"number_line",min:0,max:1,hops:d},animation:"plot_points",caption:`Plot ${e.map(h=>`${h.n}/${h.d}`).join(", ")} between 0 and 1.`,spoken:"To see which fraction is closest to one, we plot each fraction on the number line, between nought and one."},{visual_data:{type:"number_line",min:0,max:1,hops:[...d,u(.5,"1/2")],conclude_label:"Compare with 0, 1/2 and 1."},animation:"show_benchmarks",caption:"Compare each point with 0, 1/2 and 1.",spoken:"For each point, we check whether it sits nearest to nought, a half, or one."},{visual_data:{type:"number_line",min:0,max:1,hops:m,conclude_label:`${i} is closest to 1.`},animation:"highlight_winner",caption:`${i} sits closest to 1.`,spoken:`${capitalised(fractionWords$2(r.n,r.d))} sits nearest to one, closer than it is to nought or a half.`}]})}}];function normCoords(n){return n.replace(/[()\s]/g,"")}const CLAIM_POINT=/^[A-Z][a-z]+ says point ([A-Za-z]) is at \((\d+), ?(\d+)\)\. Is (?:he|she|they) right\?$/,CLOSER_TO_BOTTOM=/^Point ([A-Za-z]) is at \((\d+), ?(\d+)\) and point ([A-Za-z]) is at \((\d+), ?(\d+)\)\. Which point sits closer to the bottom of the grid — \1 or \4\?$/,WHICH_POINT_SHOWS=/coordinates \((\d+), ?(\d+)\)/,readCoordinate$1={id:"GEO.P4.M02.read-coordinate.v1",microSkillId:"GEO.P4.M02",forms:["symbolic_choice","visual_quantity_to_symbol"],generate(n){const t=n.visualData;if(!t||t.type!=="coordinate_grid"||!Array.isArray(t.points)||t.points.length<1)return null;const e=normaliseStem(n.stem).match(/coordinates of ([A-Za-z])\b/);if(!e)return null;const s=e[1],o=t.points.find(T=>T.label===s);if(!o)return null;const{x:r,y:a}=o;if(!Number.isInteger(r)||!Number.isInteger(a))return null;const i=t.max_x,l=t.max_y;if(!Number.isInteger(i)||!Number.isInteger(l)||r<0||r>i||a<0||a>l||normCoords(`${r},${a}`)!==normCoords(n.correctAnswer))return null;const u=r!==a,m={max_x:r>=i?i+1:i,max_y:l},h={x:0,y:0,label:"O"},f={x:r,y:a,label:s},g={type:"coordinate_grid",...m,points:[h]},b={type:"coordinate_grid",...m,points:[h,f]},x={type:"coordinate_grid",...m,points:[h,f],...u?{contrast_point:{x:a,y:r,label:`(${a}, ${r})`}}:{}},y=(T,A)=>T===0?`Not along at all, just up ${numberToWordsLong(A)}`:A===0?`Along ${numberToWordsLong(T)}, and not up at all`:`Along ${numberToWordsLong(T)}, then up ${numberToWordsLong(A)}`,E=u?`(${r}, ${a}) is not (${a}, ${r}).`:`${s} is at (${r}, ${a}).`,k=u?`${y(r,a)} lands us on the point. If we swapped the numbers and went ${y(a,r).toLowerCase()}, we would land on a different point. The first number is always along, and the second is always up.`:"Along and then up lands us on the point. We always read along first, then up.";return buildExample({generatorId:this.id,question:n,title:`What are the coordinates of ${s}?`,computed:{x:r,y:a,max_x:i,max_y:l},steps:[{visual_data:g,animation:"show_start",caption:"Every point starts from the corner.",spoken:"Every coordinate journey starts in the same place, at the corner where the two number lines meet."},{visual_data:b,animation:"hop_and_climb",caption:"Along the corridor first, then up.",spoken:"We travel along the bottom first, counting the steps to the right, then we climb straight up. Along before up."},{visual_data:x,animation:"land_and_reveal",caption:E,spoken:`${k} So the point sits at ${numberToWordsLong(r)} across and ${numberToWordsLong(a)} up.`}]})}},readSolePoint={id:"GEO.P4.M02.read-sole-point.v1",microSkillId:"GEO.P4.M02",forms:["visual_quantity_to_symbol"],generate(n){const t=n.visualData;if(!t||t.type!=="coordinate_grid"||!Array.isArray(t.points)||t.points.length!==1||/coordinates of [A-Za-z]\b/.test(normaliseStem(n.stem)))return null;const e=t.points[0],{x:s,y:o}=e;if(!Number.isInteger(s)||!Number.isInteger(o))return null;const r=t.max_x,a=t.max_y;if(!Number.isInteger(r)||!Number.isInteger(a)||s<0||s>r||o<0||o>a||normCoords(`${s},${o}`)!==normCoords(n.correctAnswer))return null;const i=e.label??"X",l=s!==o,u={max_x:r,max_y:a},d={x:0,y:0,label:"O"},m={x:s,y:o,label:i},h={type:"coordinate_grid",...u,points:[d]},f={type:"coordinate_grid",...u,points:[d,m]},g={type:"coordinate_grid",...u,points:[d,m],...l?{contrast_point:{x:o,y:s,label:`(${o}, ${s})`}}:{}};return buildExample({generatorId:this.id,question:n,title:`What are the coordinates of the ${i} mark?`,computed:{x:s,y:o,max_x:r,max_y:a},steps:[{visual_data:h,animation:"show_start",caption:"Every point starts from the corner.",spoken:"Every coordinate journey starts in the same place, at the corner where the two number lines meet."},{visual_data:f,animation:"hop_and_climb",caption:"Along the corridor first, then up.",spoken:"We travel along the bottom first, counting the steps to the right, then we climb straight up. Along before up."},{visual_data:g,animation:"land_and_reveal",caption:`The spot is at (${s}, ${o}
71).`,spoken:`So the spot sits at ${numberToWordsLong(s)} across and ${numberToWordsLong(o)} up.`}]})}},claimPointCheck={id:"GEO.P4.M02.claim-check.v1",microSkillId:"GEO.P4.M02",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_POINT);if(!t)return null;const e=t[1],s=Number(t[2]),o=Number(t[3]),r=n.visualData;if(!r||r.type!=="coordinate_grid")return null;const a=r.points.find(k=>k.label===e);if(!a)return null;const{x:i,y:l}=a;if(!Number.isInteger(i)||!Number.isInteger(l))return null;const u=r.max_x,d=r.max_y;if(!Number.isInteger(u)||!Number.isInteger(d))return null;const m=i===s&&l===o;if(m){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const h={max_x:u,max_y:d},f={x:0,y:0,label:"O"},g={x:i,y:l,label:e},b={x:s,y:o,label:`(${s}, ${o})`},x={type:"coordinate_grid",...h,points:[f]},y={type:"coordinate_grid",...h,points:[f,g]},E={type:"coordinate_grid",...h,points:[f,g],...m?{}:{contrast_point:b}};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is ${e} at (${s}, ${o})?`,computed:{x:i,y:l,claimX:s,claimY:o,max_x:u,max_y:d,answer:m?1:0},steps:[{visual_data:x,animation:"show_start",caption:"Every point starts from the corner.",spoken:"Every coordinate journey starts at the corner where the two number lines meet."},{visual_data:y,animation:"hop_and_climb",caption:"Along the corridor first, then up.",spoken:"We travel along the bottom first, then climb straight up. Along before up."},{visual_data:E,animation:"land_and_reveal",caption:`${e} is at (${i}, ${l}) — the claim is ${m?"correct":"not correct"}.`,spoken:`${e} sits at ${numberToWordsLong(i)} across and ${numberToWordsLong(l)} up. The claim is ${m?"correct":"not correct"}.`}]})}},closerToBottom={id:"GEO.P4.M02.closer-to-bottom.v1",microSkillId:"GEO.P4.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(CLOSER_TO_BOTTOM);if(!t)return null;const e=t[1],s=Number(t[2]),o=Number(t[3]),r=t[4],a=Number(t[5]),i=Number(t[6]);if(o===i)return null;const l=o<i?e:r;if(l!==n.correctAnswer)return null;const u=n.visualData,d=u&&u.type==="coordinate_grid"?u.max_x:Math.max(s,a,6),m=u&&u.type==="coordinate_grid"?u.max_y:Math.max(o,i,6),h={max_x:d,max_y:m},f={x:s,y:o,label:e},g={x:a,y:i,label:r}
71,b=l===e?f:g,x=l===e?g:f;return buildExample({generatorId:this.id,question:n,title:`Which point is closer to the bottom: ${e} or ${r}?`,computed:{x1:s,y1:o,x2:a,y2:i,max_x:d,max_y:m},steps:[{visual_data:{type:"coordinate_grid",...h,points:[f]},animation:"show_points",caption:`${e} is at (${s}, ${o}).`,spoken:`${e} sits at ${numberToWordsLong(s)} across and ${numberToWordsLong(o)} up.`},{visual_data:{type:"coordinate_grid",...h,points:[g]},animation:"show_points",caption:`${r} is at (${a}, ${i}). Bottom = smallest up-number.`,spoken:`${r} sits at ${numberToWordsLong(a)} across and ${numberToWordsLong(i)} up. The point closer to the bottom of the grid is the one whose second number, the up-number, is smaller.`},{visual_data:{type:"coordinate_grid",...h,points:[b],contrast_point:x},animation:"highlight_winner",caption:`${o} vs ${i}: ${l} has the smaller up-number, so ${l} is closer to the bottom.`,spoken:`Comparing ${numberToWordsLong(o)} and ${numberToWordsLong(i)}: ${l} has the smaller up-number, so ${l} sits closer to the bottom of the grid.`}]})}},whichPointShows={id:"GEO.P4.M02.which-point-shows.v1",microSkillId:"GEO.P4.M02",forms:["visual_quantity_to_symbol"],generate(n){const e=normaliseStem(n.stem).match(WHICH_POINT_SHOWS);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=n.visualData;if(!r||r.type!=="coordinate_grid"||!Array.isArray(r.points)||r.points.length<2)return null;const a=r.points.filter(b=>b.x===s&&b.y===o);if(a.length!==1)return null;const i=a[0];if(!i.label||i.label!==n.correctAnswer)return null;const l=r.points.find(b=>b!==i&&b.label);if(!l)return null;const u=r.max_x,d=r.max_y;if(!Number.isInteger(u)||!Number.isInteger(d))return null;const h={max_x:s>=u?u+1:u,max_y:d},f={x:0,y:0,label:"O"},g={x:s,y:o,label:i.label};return buildExample({generatorId:this.id,question:n,title:`Which point shows (${s}, ${o})?`,computed:{targetX:s,targetY:o,max_x:u,max_y:d},steps:[{visual_data:{type:"coordinate_grid",...h,points:[f]},animation:"show_start",caption:`We start at the corner and trace to (${s}, ${o}).`,spoken:"Every coordinate journey starts at the corner where the two number lines meet."},{visual_data:{type:"coordinate_grid",...h,points:[f,g]},animation:"trace_target",caption:`Trace ${s} across, then ${o} up.`,spoken:`We travel ${numberToWordsLong(s)} across, then ${numberToWordsLong(o)} up, to see which point we land on.`},{visual_data:{type:"coordinate_grid",...h,points:[f,g],contrast_point:{x:l.x,y:l.y,label:l.label}},animation:"reveal_match",caption:`${i.label} shows (${s}, ${o}) — ${l.label} does not match.`,spoken:`That journey lands on ${i.label}. So ${i.label} shows the coordinates, not ${l.label}.`}]})}},GEO_P4_M02_GENERATORS=[readCoordinate$1,readSolePoint,claimPointCheck,closerToBottom,whichPointShows],CLAIM_RIGHT_ANGLE=/^(?:.*?\. )?[A-Z][a-z]+ says the angle is (bigger|smaller) than a right angle\. Is (?:he|she|they) right\?$/,WHICH_IS_RIGHT_ANGLE=/Which one is a right angle — ([A-Za-z]) or ([A-Za-z])\?$/,TABLE_BOOK_ANGLE_COMPARE=/^A table corner makes exactly a right angle\. A book opened flat on a desk makes a much wider angle\. Which is bigger than a right angle — the table corner or the open book\?$/;function singleAngleDegrees(n){const t=n.visualData;if(!t||t.type!=="angle_diagram"||!Array.isArray(t.angles)||t.angles.length!==1)return null;const e=t.angles[0];return e.unknown||!Number.isInteger(e.degrees)?null:e.degrees}function normLabel$2(n){return n.trim().replace(/\s+/g," ").toLowerCase()}const rightAngleCompare={id:"GEO.P5.M01.right-angle-compare.v1",microSkillId:"GEO.P5.M01",forms:["comparison_statement"],generate(n){const t=singleAngleDegrees(n);if(t===null||t<1||t>179||t>=88&&t<=92&&t!==90)return null;const e=t<90?"Smaller than a right angle":t>90?"Bigger than a right angle":"The same as a right angle";if(normLabel$2(e)!==normLabel$2(n.correctAnswer))return null;const s={type:"angle_diagram",angles:[{degrees:t}],baseline:"none",right_angle_verdict:!0,gap_guide:!0},o={type:"angle_diagram",angles:[{degrees:90}],baseline:"none",right_angle_verdict:!0,gap_guide:!0},r=t<90,i=t===90?"Held against the square corner, the opening matches it exactly — so this angle is the same as a right angle.":r?"Held against the square corner, this opening does not reach it — so the angle is smaller than a right angle. The length of the arms never matters, only how wide it opens.":"Held against the square corner, this opening goes past it — so the angle is bigger than a right angle. The length of the arms never matters, only how wide it opens.";return buildExample({generatorId:this.id,question:n,title:"Is this angle bigger or smaller than a right angle?",computed:{degrees:t,right_angle:90},steps:[{visual_data:s,animation:"show_angle",caption:"Look at how wide this angle opens.",spoken:"Here is the angle. Before we decide anything, let's look carefully at how wide it opens at the corner."},{visual_data:o,animation:"compare_square_corner",caption:"A square corner is a right angle.",spoken:"A square corner, like the corner of a book, is a right angle. That is our measuring stick — we hold it against the angle to compare."},{visual_data:s,animation:"compare_verdict",caption:e+".",spoken:i}]})}},claimRightAngle={id:"GEO.P5.M01.claim-check.v1",microSkillId:"GEO.P5.M01",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_RIGHT_ANGLE);if(!t)return null;const e=t[1]==="bigger",s=singleAngleDegrees(n);if(s===null||s<1||s>179||s>=88&&s<=92&&s!==90)return null;const o=s>90,r=s===90,a=r?!1:e?o:!o;if(r)return null;if(a){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const i={type:"angle_diagram",angles:[{degrees:s}],baseline:"none",right_angle_verdict:!0,gap_guide:!0},l={type:"angle_diagram",angles:[{degrees:90}],baseline:"none",right_angle_verdict:!0,gap_guide:!0};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is this angle ${e?"bigger":"smaller"} than a right angle?`,computed:{degrees:s,right_angle:90,answer:a?1:0},steps:[{visual_data:i,animation:"show_angle",caption:"Look at how wide this angle opens.",spoken:"Here is the angle. Let's look carefully at how wide it opens at the corner."},{visual_data:l,animation:"compare_square_corner",caption:"A square corner is a right angle.",spoken:"A square corner, like the corner of a book, is a right angle. That is our measuring stick."},{visual_data:i,animation:"compare_verdict",caption:`${a?"Correct":"Not correct"} — held against the square corner.`,spoken:`Held against the square corner, we can see whether the claim is correct. It is ${a?"correct":"not correct"}.`}]})}},clockRightAngleCompare={id:"GEO.P5.M01.clock-right-angle-compare.v1",microSkillId:"GEO.P5.M01",forms:["comparison_statement"],generate(n){const t=n.visualData;if(!t||t.type!=="clock"||t.minute!==0)return null;const e=t.hour;if(!Number.isInteger(e)||e<1||e>12)return null;const s=e%12*30,o=Math.min(s,360-s);if(o<1||o>179)return null;const r=o<90?"Smaller than a right angle":o>90?"Bigger than a right angle":"The same as a right angle";if(normLabel$2(r)!==normLabel$2(n.correctAnswer))return null;const a=(i,l)=>({type:"clock",hour:i,minute:0,...l?{conclude_label:l}:{}});return buildExample({generatorId:this.id,question:n,title:`Is the angle between the hands at ${e} o'clock bigger, smaller, or the same as a right angle?`,computed:{hour:e,degrees:o,right_angle:90,reference_hour:3},steps:[{visual_data:a(e),animation:"show_hour",caption:`The clock shows ${e} o'clock.`,spoken:`Here is the clock at ${numberToWords(e)} o'clock. Look at the angle between the two hands.`},{visual_data:a(3),animation:"show_reference_hour",caption:"At 3 o'clock, the hands make a right angle — a quarter turn.",spoken:"At three o'clock, the hands make a right angle — a quarter turn. That is our measuring stick."},{visual_data:a(e,r),animation:"compare_verdict",caption:`${r}.`,spoken:`Comparing the gap between the hands with a quarter turn, the angle is ${r.toLowerCase()}.`}]})}};function sideOf$1(n){const t=(n%360+360)%360;return Math.cos(t*Math.PI/180)>=0?"right":"left"}const whichIsRightAngle={id:"GEO.P5.M01.which-is-right-angle.v1",microSkillId:"GEO.P5.M01",forms:["shape_property"],generate(n){const t=normaliseStem(n.stem).match(WHICH_IS_RIGHT_ANGLE);if(!t)return null;const e=t[1],s=t[2],o=n.visualData;if(!o||o.type!=="angle_diagram"||!Array.isArray(o.angles)||o.angles.length!==2)return null;const[r,a]=o.angles;if(r.unknown||a.unknown||!Number.isInteger(r.degrees)||!Number.isInteger(a.degrees))return null;const i=r.degrees===90,l=a.degrees===90;if(i===l)return null;const u=i?e:s;
71if(u!==n.correctAnswer)return null;const d=r.degrees/2,m=r.degrees+a.degrees/2,h=sideOf$1(d),f=sideOf$1(m);if(h===f)return null;const g=h==="left"?e:s,b=h==="right"?e:s,x=(E,k)=>({type:"angle_diagram",angles:[{degrees:E,label:k}],baseline:"none",right_angle_verdict:!0,gap_guide:!0}),y={type:"angle_diagram",baseline:"none",angles:[{degrees:r.degrees,label:e},{degrees:a.degrees,label:s}]};return buildExample({generatorId:this.id,question:n,title:`Which angle, ${e} or ${s}, is a right angle?`,computed:{degreesA:r.degrees,degreesB:a.degrees,right_angle:90},steps:[{visual_data:y,animation:"show_angles",caption:`Two angles: ${g} on the left, ${b} on the right.`,spoken:`Here are two angles. The one on the left is ${g}, the one on the right is ${b}. We test each one, one at a time.`},{visual_data:x(r.degrees,e),animation:"compare_square_corner",caption:`Test angle ${e} against a square corner.`,spoken:`First, let's test angle ${e} on its own. We hold a square corner up against it to compare.`},{visual_data:x(a.degrees,s),animation:"reveal_winner",caption:`Test ${s} too — ${u} is the right angle.`,spoken:`Now we test angle ${s} the very same way. Only one of the two matched the square corner exactly, so ${u} is the right angle.`}]})}},tableBookAngleCompare={id:"GEO.P5.M01.table-book-compare.v1",microSkillId:"GEO.P5.M01",forms:["comparison_statement"],generate(n){if(!TABLE_BOOK_ANGLE_COMPARE.test(normaliseStem(n.stem))||!/book/i.test(n.correctAnswer))return null;const t={type:"angle_diagram",angles:[{degrees:90}],baseline:"none",right_angle_verdict:!0,gap_guide:!0},e={type:"angle_diagram",angles:[{degrees:150}],baseline:"none"},s={type:"angle_diagram",angles:[{degrees:150}],baseline:"none",right_angle_verdict:!0,gap_guide:!0};return buildExample({generatorId:this.id,question:n,title:"Which is bigger than a right angle — the table corner or the open book?",computed:{right_angle:90,book_angle:150},steps:[{visual_data:t,animation:"show_table_corner",caption:"A table corner is a right angle.",spoken:"Here is a table corner. It makes exactly a right angle — a square corner, no more and no less."},{visual_data:e,animation:"show_book_angle",caption:"The open book makes a much wider angle.",spoken:"Now here is the book, opened flat on a desk. Look how much wider it opens than the table corner did."},{visual_data:s,animation:"compare_verdict",caption:"The open book is bigger than a right angle.",spoken:"The table corner was exactly a right angle, but the open book opens much wider than that. So the open book is the one that is bigger than a right angle."}]})}},GEO_P5_M01_GENERATORS=[rightAngleCompare,claimRightAngle,clockRightAngleCompare,whichIsRightAngle,tableBookAngleCompare];function cap$j(n){return n.charAt(0).toUpperCase()+n.slice(1)}const CLAIM_MISSING_LINE=/^Two angles meet on a straight line\. One is (\d+) degrees\. [A-Z][a-z]+ says the other one must be (\d+) degrees\. Is (?:he|she|they) right\?$/,TWO_LINES_RIGHT_ANGLES=/^On one straight line, the two angles are (\d+) degrees and (\d+) degrees\. On another straight line, the two angles are (\d+) degrees and (\d+) degrees\. Which straight line has two right angles — the first or the second\?$/,missingAngleLine={id:"GEO.P5.M02.missing-line.v1",microSkillId:"GEO.P5.M02",forms:["symbolic_numeric_entry"],generate(n){const t=n.visualData;if(!t||t.type!=="angle_diagram"||t.baseline!=="straight")return null;const e=t.angles;if(!Array.isArray(e)||e.length<2)return null;const s=e.filter(d=>d.unknown),o=e.filter(d=>!d.unknown);if(s.length!==1||o.length<1||!o.every(d=>Number.isInteger(d.degrees)))return null;const r=180,a=o.reduce((d,m)=>d+m.degrees,0),i=r-a;if(i<1||i>179||i!==answerInt(n))return null;const u={type:"angle_diagram",baseline:"straight",angles:[...o.map(d=>({degrees:d.degrees,label:`${d.degrees}°`})),{degrees:i,unknown:!0}]};return buildExample({generatorId:this.id,question:n,title:"What is the missing angle on this straight line?",computed:{knownSum:a,total:r,missing:i,answer:i},steps:[{visual_data:u,animation:"show_angles",caption:"One angle is missing on the line.",spoken:"This straight line is split into two angles by the middle arm. However it is split, the two angles together always make the whole straight line."},{visual_data:u,animation:"highlight_rule",caption:"Angles on a line add to 180°.",spoken:"Angles on a straight line always add up to one hundred and eighty degrees. So to find the missing one, we take the known angle away from one hundred and eighty."},{visual_data:u,animation:"reveal_missing",caption:`${r}° − ${a}° = ${i}°.`,spoken:`${cap$j(numberToWordsLong(r))} take away ${numberToWordsLong(a)} leaves ${numberToWordsLong(i)}. So the missing angle is ${numberToWordsLong(i)} degrees — the two angles add back to the whole straight line.`}]})}}
71,claimMissingLine={id:"GEO.P5.M02.claim-check.v1",microSkillId:"GEO.P5.M02",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_MISSING_LINE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>179)return null;const o=180-e;if(o<1||o>179)return null;const r=o===s;if(r){if(!isAffirmative(n.correctAnswer))return null}else if(!isNegative(n.correctAnswer))return null;const a={type:"angle_diagram",baseline:"straight",angles:[{degrees:e,label:`${e}°`},{degrees:o,unknown:!0}]};return buildExample({generatorId:this.id,question:n,title:`Check the claim: is the missing angle ${s}°?`,computed:{known:e,total:180,missing:o,claimed:s,answer:r?1:0},steps:[{visual_data:a,animation:"show_angles",caption:"One angle is missing on the line.",spoken:"This straight line is split into two angles by the middle arm."},{visual_data:a,animation:"highlight_rule",caption:"Angles on a line add to 180°.",spoken:"Angles on a straight line always add up to one hundred and eighty degrees."},{visual_data:a,animation:"reveal_missing",caption:`180° − ${e}° = ${o}° — the claim is ${r?"correct":"not correct"}.`,spoken:`So the missing angle is what is left after the known angle. ${cap$j(numberToWordsLong(180))} take away ${numberToWordsLong(e)} leaves ${numberToWordsLong(o)} degrees, so the claim is ${r?"correct":"not correct"}.`}]})}},twoLinesRightAngles={id:"GEO.P5.M02.two-lines-right-angles.v1",microSkillId:"GEO.P5.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(TWO_LINES_RIGHT_ANGLES);if(!t)return null;const[e,s,o,r]=t.slice(1).map(Number);if(e+s!==180||o+r!==180)return null;const a=e===90&&s===90;if(a===(o===90&&r===90))return null;const l=a?"The first":"The second";if(l!==n.correctAnswer)return null;const u={type:"angle_diagram",baseline:"straight",angles:[{degrees:e,label:`${e}°`},{degrees:s,label:`${s}°`}]},d={type:"angle_diagram",baseline:"straight",angles:[{degrees:o,label:`${o}°`},{degrees:r,label:`${r}°`}]};return buildExample({generatorId:this.id,question:n,title:"Which straight line has two right angles?",computed:{a1:e,b1:s,a2:o,b2:r,right_angle:90},steps:[{visual_data:u,animation:"show_first_line",caption:`The first line: ${e}° and ${s}°.`,spoken:`The first straight line splits into ${numberToWordsLong(e)} degrees and ${numberToWordsLong(s)} degrees.`},{visual_data:d,animation:"show_second_line",caption:`The second line: ${o}° and ${r}°.`,spoken:`The second straight line splits into ${numberToWordsLong(o)} degrees and ${numberToWordsLong(r)} degrees.`},{visual_data:l==="The first"?u:d,animation:"reveal_winner",caption:`${l} line has two right angles (90° and 90°).`,spoken:`A right angle is exactly ninety degrees. ${l} line is split into two right angles.`}]})}},GEO_P5_M02_GENERATORS=[missingAngleLine,claimMissingLine,twoLinesRightAngles],MUL_STEM=/^(\d+) boxes hold (\d+) pens each\. How many pens(?: are there altogether)?\?$/,SHARE_STEM=/^(\d+) sweets (?:are )?shared between (\d+) children\. How many (?:each|sweets does each child get)\?$/,boxesMultiply={id:"MD.P3.M05.boxes-multiply.v1",microSkillId:"MD.P3.M05",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,MUL_STEM);if(!t)return null;const[e,s]=t;if(e<2||e>12||s<1||s>12)return null;const o=e*s;if(o>100)return null;const r=Number(n.correctAnswer);if(!Number.isFinite(r)||r!==o)return null;const a="🖊️",i={type:"objects_grid",emoji:a,count:s,group_size:s,keyword:"each"},l={type:"objects_grid",emoji:a,count:o,group_size:s,keyword:"each",hide_subtotals:!0,hide_total:!0},u={type:"objects_grid",emoji:a,count:o,group_size:s,keyword:"each"};return buildExample({generatorId:this.id,question:n,title:`${e} boxes, ${s} pens each — how many pens?`,computed:{boxes:e,pensEach:s,total:o,answer:o},steps:[{visual_data:i,animation:"keyword_multiply",caption:`${s} pens in one box.`,spoken:`${capitalised(numberToWordsLong(s))} pens in one box. The word "each" tells us every box gets its own equal group.`},{visual_data:l,animation:"write_fact",caption:"Copy it box by box.",spoken:"More boxes arrive, each one holding the very same amount of pens. We build copy after copy until every box is placed."},{visual_data:u,animation:"write_fact",caption:`${e} boxes: ${o} pens.`,spoken:`${capitalised(numberToWordsLong(e))} boxes of ${numberToWordsLong(s)} pens makes ${numberToWordsLong(o)} pens altogether.`}]})}},sweetsShare={id:"MD.P3.M05.sweets-share.v1",microSkillId:"MD.P3.M05",forms:["context_reasoning"],generate(n){const t=matchInts(n.stem,SHARE_STEM);if(!t)return null;const[e,s]=t;if(s<2||s>10||e<s||e>100||e%s!==0)return null;const o=e/s;if(o<1||o>12)return null;const r=Number(n.correctAnswer);return!Number.isFinite(r)||r!==o?null:buildExample({generatorId:this.id,question:n,title:`${e} shared between ${s} — how many each?`,computed:{total:e,children:s,per:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[s,"×","__","=",e]},animation:"pulse_unknown",caption:`${s} × ? = ${e}.`,spoken:`We need ${numberToWords(s)} equal shares. Use the times table: ${numberToWords(s)} times what number makes ${numberToWordsLong(e)}?`},{visual_data:{type:"objects_grid",emoji:"🍬",count:e,group_size:o,mode:"share",hide_total:!0},animation:"share_contrast",caption:`Find the equal share that makes ${e}.`,spoken:`The picture builds ${numberToWords(s)} equal shares. Each round adds ${numberToWords(s)} sweets altogether. Count those rounds until all ${numberToWordsLong(e)} sweets are used. The number of rounds is the missing times-table number.`},{visual_data:{type:"objects_grid",emoji:"🍬",count:e,group_size:o,mode:"share",total_label:`${s} × ${o} = ${e}; ${o} each`},animation:"share_contrast",caption:`${s} × ${o} = ${e}, so ${e} ÷ ${s} = ${o}.`,spoken:`${capitalised(numberToWords(s))} times ${numberToWords(o)} is ${numberToWordsLong(e)}. That tells us ${numberToWordsLong(e)} shared between ${numberToWords(s)} gives ${numberToWords(o)} sweets each.`}]})}},PACKS_CLAIM=/^(\w+) says (\d+) packs of (\d+) stickers make (\d+) stickers altogether\. Is (?:he|she) right\?$/,packsClaimCheck={id:"MD.P3.M05.packs-claim-check.v1",microSkillId:"MD.P3.M05",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(PACKS_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r);
71if(a<2||a>12||i<1||i>12)return null;const u=a*i;if(u>100)return null;const d=u===l,m=n.correctAnswer.trim();if(d&&!isAffirmative(m)||!d&&!isNegative(m))return null;const h="⭐",f={type:"objects_grid",emoji:h,count:i,group_size:i},g={type:"objects_grid",emoji:h,count:u,group_size:i,hide_subtotals:!0,hide_total:!0},b={type:"objects_grid",emoji:h,count:u,group_size:i};return buildExample({generatorId:this.id,question:n,title:`Is ${a} packs of ${i} really ${l}?`,computed:{packs:a,perPack:i,real:u,claimed:l,answer:u},steps:[{visual_data:f,animation:"show_unit",caption:`${i} stickers in one pack.`,spoken:`Before trusting the claim, let's check for ourselves. ${capitalised(numberToWordsLong(i))} stickers make one pack.`},{visual_data:g,animation:"organise_copies",caption:"Copy the pack, again and again.",spoken:"More packs arrive, each one the very same size as the first."},{visual_data:b,animation:"count_copies",caption:`${a} packs of ${i} = ${u} — ${e} is ${d?"right":"wrong"}.`,spoken:`Counting every sticker, ${numberToWords(a)} packs of ${numberToWords(i)} really makes ${numberToWordsLong(u)}. So ${e} is ${d?"right":"wrong"}.`}]})}},TEAMS_COMPARE=/^Which is more: (\d+) teams of (\d+) players, or (\d+) teams of (\d+) players\?$/,teamsCompare={id:"MD.P3.M05.teams-compare.v1",microSkillId:"MD.P3.M05",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(TEAMS_COMPARE);if(!t)return null;const[e,s,o,r]=t.slice(1).map(Number);if([e,s,o,r].some(m=>m<2||m>12))return null;const a=e*s,i=o*r;if(a===i)return null;const l=`${e} teams of ${s} players`,u=`${o} teams of ${r} players`,d=a>i?l:u;return d!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:`Which is more: ${l} or ${u}?`,computed:{A:e,B:s,C:o,D:r,p1:a,p2:i,answer:Math.max(a,i)},steps:[{visual_data:{type:"objects_grid",emoji:"🔵",count:a,group_size:s},animation:"write_fact",caption:`${l} = ${a}.`,spoken:`To compare, we find how many players there are each way. ${capitalised(numberToWords(e))} teams of ${numberToWords(s)} is ${numberToWords(e)} lots of ${numberToWords(s)}, which makes ${numberToWordsLong(a)} players.`},{visual_data:{type:"objects_grid",emoji:"🟠",count:i,group_size:r},animation:"write_fact",caption:`${u} = ${i}.`,spoken:`${capitalised(numberToWords(o))} teams of ${numberToWords(r)} is ${numberToWords(o)} lots of ${numberToWords(r)}, which makes ${numberToWordsLong(i)} players.`},{visual_data:{type:"sequence",values:[a,i],step:1,hide_hop:!0},animation:"highlight_pattern",caption:`${a} vs ${i} — ${d} is more.`,spoken:`${capitalised(numberToWordsLong(Math.max(a,i)))} is more than ${numberToWordsLong(Math.min(a,i))}, so ${digitsToWords(d)} is the greater number of players.`}]})}},OP_CHOICE=/^A shop has (\d+) pencils to share equally into boxes of (\d+)\. Should you multiply or divide to find how many boxes are needed\?$/,opChoiceReasoning={id:"MD.P3.M05.op-choice.v1",microSkillId:"MD.P3.M05",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(OP_CHOICE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(s<2||s>12||e<s||e>100||e%s!==0)return null;const o=e/s;if(o<1||o>12||n.correctAnswer.trim().toLowerCase()!=="divide")return null;const r="✏️",a={type:"objects_grid",emoji:r,count:e,group_size:s,scatter:!0},i={type:"objects_grid",emoji:r,count:e,group_size:s,scatter:!0,groups_formed:o};return buildExample({generatorId:this.id,question:n,title:`Multiply or divide: ${e} pencils, boxes of ${s}?`,computed:{total:e,boxSize:s,boxes:o,answer:o},steps:[{visual_data:a,animation:"scatter_show",caption:`We know the total (${e}) and the box size (${s}).`,spoken:"We already know the whole amount and the size of each box. What we don't know is how many boxes that makes."},{visual_data:i,animation:"ring_groups",caption:`Ring groups of ${s} — how many boxes?`,spoken:`Multiplying joins equal groups together. Here we are doing the opposite — ringing groups of ${numberToWords(s)} out of the total — and that is dividing.`},{visual_data:i,animation:"remainder_reveal",caption:`${e} in ${s}s = ${o} boxes.`,spoken:`${capitalised(numberToWordsLong(e))} divided by ${numberToWords(s)} gives ${numberToWords(o)}
71 boxes. Whenever the total and group size are known but the number of groups is not, divide.`}]})}},EGGS_TWO_STEP=/^Each box holds (\d+) eggs\. (.+?) buys (\d+) boxes and uses (\d+) of the eggs for baking\. How many eggs does (?:he|she) have left\?$/,eggsTwoStep={id:"MD.P3.M05.eggs-two-step.v1",microSkillId:"MD.P3.M05",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(EGGS_TWO_STEP);if(!t)return null;const e=Number(t[1]),s=t[2],o=Number(t[3]),r=Number(t[4]);if(e<1||e>12||o<2||o>12)return null;const a=e*o;if(a>100||r<1||r>=a)return null;const i=a-r;if(i!==answerInt(n))return null;const l="🥚",u={type:"objects_grid",emoji:l,count:a,group_size:e,hide_subtotals:!0,hide_total:!0},d={type:"objects_grid",emoji:l,count:a,group_size:e},m={type:"take_away",total:a,take_away:r,result_visible:!0};return buildExample({generatorId:this.id,question:n,title:`${o} boxes of ${e} eggs, then ${r} used — how many left?`,computed:{perBox:e,boxes:o,total:a,used:r,left:i,answer:i},steps:[{visual_data:u,animation:"write_fact",caption:`First, ${o} boxes of ${e} eggs each.`,spoken:`${s} buys ${numberToWords(o)} boxes, each holding ${numberToWords(e)} eggs. We find the total first.`},{visual_data:d,animation:"write_fact",caption:`${o} × ${e} = ${a} eggs altogether.`,spoken:`${capitalised(numberToWords(o))} boxes of ${numberToWords(e)} makes ${numberToWordsLong(a)} eggs in all.`},{visual_data:m,animation:"remainder_reveal",caption:`${a} − ${r} = ${i} eggs left.`,spoken:`Taking away the ${numberToWords(r)} eggs used for baking leaves ${numberToWordsLong(i)} eggs.`}]})}},MD_P3_M05_GENERATORS=[boxesMultiply,sweetsShare,packsClaimCheck,teamsCompare,opChoiceReasoning,eggsTwoStep],COMPLEMENT_GENERATOR_ID="MEA.P5.M03.complement-1000.v1",COMPLEMENT=/^An? .+? holds (\d+) ml\. How much more to fill 1 litre\?$/i,complementTo1000={id:COMPLEMENT_GENERATOR_ID,microSkillId:"MEA.P5.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(COMPLEMENT);if(!t)return null;const e=Number(t[1]);if(e<1||e>999)return null;const s=1e3-e;if(s!==Number(n.correctAnswer))return null;const o=(m,h,f)=>({type:"measure_bars",unit:"ml",vessel:!0,capacity:1e3,show_values:!0,bars:[{label:`${e} ml`,length:e,color:"#2563eb"},...m>0?[{label:`${m} ml`,start:h,length:m,color:f}]:[]]}),r=e>900?Math.min(1e3,(Math.floor(e/10)+1)*10):Math.ceil(e/100)*100,a=r-e,i=1e3-r,l=[...a>0?[{from:e,to:r,label:`+${a}`}]:[],...i>0?[{from:r,to:1e3,label:`+${i}`}]:[]],u=`Count on ${a}, then ${i}, to reach 1000 ml.`,d=`Count up from ${numberToWordsLong(e)}. First add ${numberToWordsLong(a)} to reach ${numberToWordsLong(r)}. Then add ${numberToWordsLong(i)} to reach one thousand. Add the jumps to find how much more is needed.`;return buildExample({generatorId:COMPLEMENT_GENERATOR_ID,question:n,title:"How much more to fill this to 1 litre?",computed:{n:e,gap:s,nextHundred:r,firstStep:a,secondStep:i,thousand:1e3,answer:s},steps:[{visual_data:o(0,e,""),animation:"show_start",caption:`This jug holds ${e} ml.`,spoken:`This jug holds ${numberToWordsLong(e)} millilitres. A litre is one thousand millilitres, so the jug isn't full yet.`},{visual_data:{type:"number_line",min:e,max:1e3,marked:[e,r,1e3],hops:l,unknown:null},animation:"show_gap",caption:u,spoken:d},{visual_data:o(s,e,"#fbbf24"),animation:"complete_fill",caption:`${a} + ${i} = ${s} ml more.`,spoken:`The jumps are ${numberToWordsLong(a)} and ${numberToWordsLong(i)}. Together they make ${numberToWordsLong(s)} millilitres. So that is how much more fills the container to one litre.`}]})}},CROSS_COMPARE_GENERATOR_ID="MEA.P5.M03.cross-compare.v1",PATTERN_ARTICLE=/^An? (.+?) holds (.+?)\. An? (.+?) holds (.+?)\. Which holds more\?$/,PATTERN_PLAIN=/^(.+?) holds (.+?)\. (.+?) holds (.+?)\. Which holds more\?$/;function normLabel$1(n){return n.trim().replace(/\s+/g," ").toLowerCase()}function parseVolumeMl(n){const t=n.trim();let e=t.match(/^(\d+)\s*ml$/i);return e?Number(e[1]):(e=t.match(/^(\d+)\s*litres?$/i),e?Number(e[1])*1e3:(e=t.match(/^(\d+)\s*litres?\s+(\d+)\s*ml$/i),e?Number(e[1])*1e3+Number(e[2]):null))}function conversionNote(n,t){const e=n.trim();if(/^\d+\s*ml$/i.test(e))return null;const s=e.match(/^(\d+)\s*litres?\s+(\d+)\s*ml$/i);if(s){const r=Number(s[1]),a=Number(s[2]);return{caption:`A litre is 1000 ml, so ${r} litre ${a} ml is ${r*1e3} + ${a} = ${t} ml.`,spoken:`One litre is one thousand millilitres, so ${numberToWords(r)} litre ${numberToWordsLong(a)} millilitres is ${numberToWordsLong(r*1e3)} plus ${numberToWordsLong(a)}, which is ${numberToWordsLong(t)} millilitres.`}}const o=e.match(/^(\d+)\s*litres?$/i);if(o){const r=Number(o[1]);return{caption:`A litre is 1000 ml, so ${r} ${r===1?"litre":"litres"} is ${t} ml.`,spoken:`A litre is one thousand millilitres, so ${numberToWords(r)} ${r===1?"litre":"litres"} is ${numberToWordsLong(t)} millilitres.`}}return null}const crossCompare={id:CROSS_COMPARE_GENERATOR_ID,microSkillId:"MEA.P5.M03",forms:["comparison_statement","set_comparison_judgement"],generate(n){const t=normaliseStem(n.stem);let e,s,o,r,a;
71if(n.mathematicalForm==="comparison_statement"){const S=t.match(PATTERN_ARTICLE);if(!S)return null;[,e,s,o,r]=S,a="the"}else if(n.mathematicalForm==="set_comparison_judgement"){const S=t.match(PATTERN_PLAIN);if(!S)return null;[,e,s,o,r]=S,a="verbatim"}else return null;const i=parseVolumeMl(s),l=parseVolumeMl(r);if(i===null||l===null||i<1||i>5e3||l<1||l>5e3||i===l)return null;const u=a==="the"?`The ${e}`:e,d=a==="the"?`The ${o}`:o,m=i>l?u:d;if(normLabel$1(m)!==normLabel$1(n.correctAnswer))return null;const h=a==="the"?capitalised(e):e,f=a==="the"?capitalised(o):o,g=Math.max(i,l),b=(S,M,L)=>({label:S,length:M,color:L,start:0}),x=conversionNote(s,i),y=conversionNote(r,l),E=!x&&!y,k=x?x.caption:`${u} holds ${i} ml.`,T=x?`${x.spoken} So ${u} holds ${numberToWordsLong(i)} millilitres.`:`${u} holds ${numberToWordsLong(i)} millilitres.`,A=y?y.caption:`${d} holds ${l} ml.`,N=E?"Both are already in millilitres, so we can compare them directly.":y?`${y.spoken} So ${d} holds ${numberToWordsLong(l)} millilitres.`:`${d} holds ${numberToWordsLong(l)} millilitres. We change both amounts to the same unit before comparing.`;return buildExample({generatorId:CROSS_COMPARE_GENERATOR_ID,question:n,title:`Which holds more: ${u} or ${d}?`,computed:{amt1:i,amt2:l,thousand:1e3},steps:[{visual_data:{type:"measure_bars",unit:"ml",vessel:!0,capacity:g,bars:[b(h,i,"#2563eb")]},animation:"show_start",caption:k,spoken:T},{visual_data:{type:"measure_bars",unit:"ml",vessel:!0,capacity:g,bars:[b(h,i,"#2563eb"),b(f,l,"#0d9488")]},animation:"show_second",caption:A,spoken:N},{visual_data:{type:"measure_bars",unit:"ml",vessel:!0,capacity:g,bars:[b(h,i,"#2563eb"),b(f,l,"#0d9488")]},animation:"compare_units",caption:`${i} ml vs ${l} ml — ${m} holds more.`,spoken:E?`Comparing directly, ${m} holds more.`:`Now that both are in millilitres, we can compare them directly. ${m} holds more.`}]})}},visualCompare={id:"MEA.P5.M03.visual-compare.v1",microSkillId:"MEA.P5.M03",forms:["comparison_statement","set_comparison_judgement"],generate(n){const t=n.visualData;if(!t||t.type!=="measure_bars")return null;const e=t.bars;if(e.length!==2)return null;const[s,o]=e;if((s.start??0)!==0||(o.start??0)!==0||s.length<=0||o.length<=0||s.length===o.length)return null;const r=t.unit??"ml",a=t.capacity??Math.max(s.length,o.length),i=s.length>o.length?s:o;if(normLabel$1(i.label)!==normLabel$1(n.correctAnswer))return null;const u=/^m?l$/i.test(r.trim())?"holds more":"is more",d=f=>({label:f.label,length:f.length,color:f.color,start:0}),m={type:"measure_bars",unit:r,vessel:!0,capacity:a,bars:[d(s)]},h={type:"measure_bars",unit:r,vessel:!0,capacity:a,bars:[d(s),d(o)]};return buildExample({generatorId:"MEA.P5.M03.visual-compare.v1",question:n,title:`Which holds more: ${s.label} or ${o.label}?`,computed:{aLength:s.length,bLength:o.length,capacity:a},steps:[{visual_data:m,animation:"show_start",caption:`Look at how high the water reaches in ${s.label}.`,spoken:"Here is the first container. Both containers are the same size, so look at how high the water reaches up the side."},{visual_data:h,animation:"show_second",caption:`Now look at ${o.label} too.`,spoken:"Now here is the second container next to it. We compare the two water levels side by side."},{visual_data:h,animation:"compare_units",caption:`${i.label} ${u}.`,spoken:`The water climbs higher in ${digitsToWords(i.label)}, so ${digitsToWords(i.label)} ${u}. These containers are the same size, so the higher water level means more water.`}]})}},FRACTION_ML={"a quarter":250,half:500,"three quarters":750},POUR_CLAIM=/^(\w+) pours (\d+) ml of juice into a jug that already has (\d+) ml in it\. (?:He|She) says the jug now holds (a quarter|half|three quarters) of a litre of juice\. Is (?:he|she) right\?$/,pourClaimCheck={id:"MEA.P5.M03.pour-claim-check.v1",microSkillId:"MEA.P5.M03",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(POUR_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o);if(a<1||a>999||i<1||i>999)return null;const l=a+i;if(l>1200)return null;const u=FRACTION_ML[r],d=l===u,m=n.correctAnswer.trim();if(d&&!isAffirmative(m)||!d&&!isNegative(m))return null;const h=(x,y,E,k)=>({label:x,length:y,start:E,color:k}),f=Math.max(l,u,1e3),g={type:"measure_bars",unit:"ml",vessel:!0,
71capacity:f,bars:[h("Already in jug",i,0,"#2563eb")]},b={type:"measure_bars",unit:"ml",vessel:!0,capacity:f,bars:[h("Already in jug",i,0,"#2563eb"),h("Poured in",a,i,"#0d9488")]};return buildExample({generatorId:this.id,question:n,title:`Do ${i} ml and ${a} ml really make ${u} ml?`,computed:{poured:a,already:i,total:l,claimedMl:u,litreMl:1e3,answer:l},steps:[{visual_data:g,animation:"show_start",caption:`${i} ml in the jug. ${capitalised(r)} of a litre = ${u} ml.`,spoken:`Before trusting ${e}'s claim, let's check for ourselves. There are already ${numberToWordsLong(i)} millilitres in the jug. A litre is one thousand millilitres, so ${r} of a litre is ${numberToWordsLong(u)} millilitres — that's the target we're checking against.`},{visual_data:b,animation:"show_second",caption:`${i} + ${a} = ${l} ml in the jug.`,spoken:`${e} pours in another ${numberToWordsLong(a)} millilitres. That brings the jug to ${numberToWordsLong(l)} millilitres in total.`},{visual_data:b,animation:"compare_units",caption:`${l} ml vs ${u} ml (${r} of a litre) — ${e} is ${d?"right":"wrong"}.`,spoken:`${capitalised(numberToWordsLong(l))} millilitres compared with the target of ${numberToWordsLong(u)} millilitres: so ${e} is ${d?"right":"wrong"}.`}]})}},LITRE_SUBTRACT=/^An? .+? holds (\d+) litres?(?: of \w+)?\. .+? (?:empties|pours? out|tips? out) (\d+) ml(?: .+?)?\. How many ml are left(?: in the .+?)?\?$/i,litreSubtract={id:"MEA.P5.M03.litre-subtract.v1",microSkillId:"MEA.P5.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(LITRE_SUBTRACT);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>20||s<1||s>19999)return null;const o=e*1e3,r=o-s;if(r<0||r!==answerInt(n))return null;const a=d=>({type:"equation_display",terms:d}),i=10**Math.floor(Math.log10(s)),l=Math.floor(s/i)*i,u=s-l>0?{head:l,rest:s-l,mid:o-l}:null;return buildExample({generatorId:"MEA.P5.M03.litre-subtract.v1",question:n,title:`${e} litres, ${s} ml taken out — how many ml are left?`,computed:{litres:e,removed:s,totalMl:o,thousand:1e3,left:r,answer:r,...u?{head:u.head,rest:u.rest,mid:u.mid}:{}},steps:[{visual_data:a([String(e),"litres","=",String(o),"ml"]),animation:"show_base_fact",caption:`${e} litres is ${o} ml.`,spoken:`The amount taken out is in millilitres, so first we change the ${numberToWords(e)} litres into millilitres too.`},{visual_data:a([String(o),"ml","-",String(s),"ml","=","?"]),animation:"show_gap",caption:`${o} ml − ${s} ml = ?`,spoken:`Now we take away the ${numberToWordsLong(s)} millilitres that were emptied out from the ${numberToWordsLong(o)} millilitres we started with.`},{visual_data:a(u?[String(o),"-",String(u.head),"-",String(u.rest),"=",String(r)]:[String(o),"-",String(s),"=",String(r)]),animation:"write_answer",caption:u?`${o} − ${u.head} = ${u.mid}, then ${u.mid} − ${u.rest} = ${r} ml.`:`${o} − ${s} = ${r} ml.`,spoken:u?`Take it away in two parts. ${capitalised(numberToWordsLong(o))} take away ${numberToWordsLong(u.head)} is ${numberToWordsLong(u.mid)}. Then take away ${numberToWordsLong(u.rest)} more: that leaves ${numberToWordsLong(r)} millilitres.`:`${capitalised(numberToWordsLong(o))} take away ${numberToWordsLong(s)} leaves ${numberToWordsLong(r)} millilitres.`}]})}},TANK_FILL=/^A fish tank holds (\d+) litres of water\. (\w+) fills it using a jug that holds (\d+) ml each time\. How many full jugs does (?:he|she) need to fill the tank\?$/,tankFillCount={id:"MEA.P5.M03.tank-fill.v1",microSkillId:"MEA.P5.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(TANK_FILL);if(!t)return null;const e=Number(t[1]),s=Number(t[3]);if(e<1||e>20||s<50||s>1e3)return null;const o=e*1e3;if(o%s!==0)return null;const r=o/s;if(r<2||r>20||r!==answerInt(n))return null;const a=1e3%s===0?1e3/s:null;let i,l;a!==null?(i=`${a} × ${s} ml = 1 litre, so ${e} × ${a} = ${r} jugs.`,l=`${capitalised(numberToWords(a))} jugs of ${numberToWordsLong(s)} millilitres make one litre. So for ${numberToWords(e)} litres, that's ${numberToWords(e)} times ${numberToWords(a)}, which is ${numberToWords(r)} jugs.`):(i=`Count up in ${s}s to ${o}: that's ${r} jugs.`,l=`Counting up in jumps of ${numberToWordsLong(s)} millilitres until we reach ${numberToWordsLong(o)} gives ${numberToWords(r)} jugs.`);const u=d=>({type:"equation_display",terms:d});return buildExample({generatorId:this.id,question:n,title:`How many ${s} ml jugs fill a ${e} litre tank?`,computed:{litres:e,jugMl:s,tankMl:o,one:1,thousand:1e3,jugs:r,groupsPerLitre:a??0,answer:r},steps:[{visual_data:u([String(e),"litres","=",String(o),"ml"]),animation:"show_base_fact",caption:`${e} litres is ${o} ml.`,spoken:"The jug is measured in millilitres, so first we change the tank's capacity into millilitres too."},{visual_data:u([String(o),"ml","÷",String(s),"ml","=","?"]),animation:"write_multiplication",caption:`How many ${s} ml jugs fit into ${o} ml?`,spoken:`Now we find how many jugfuls of ${numberToWordsLong(s)}
71 millilitres fit into the tank's ${numberToWordsLong(o)} millilitres.`},{visual_data:u([String(o),"÷",String(s),"=",String(r)]),animation:"write_answer",caption:i,spoken:l}]})}},COUNT_WORDS$1={one:1,two:2,three:3,four:4,five:5,six:6},CARTON_VS_BOTTLE=/^Which holds more: (\w+) cartons of (\d+) ml each, or (\w+) (\d+) litre bottles?\?$/,cartonsVsBottle={id:"MEA.P5.M03.cartons-vs-bottle.v1",microSkillId:"MEA.P5.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(CARTON_VS_BOTTLE);if(!t)return null;const[,e,s,o,r]=t,a=COUNT_WORDS$1[e.toLowerCase()],i=COUNT_WORDS$1[o.toLowerCase()];if(!a||!i)return null;const l=Number(s),u=Number(r);if(l<50||l>1e3||u<1||u>5)return null;const d=a*l,m=i*u*1e3;if(d===m)return null;const h=`${capitalised(e)} cartons`,f=o.toLowerCase()==="one"?`One ${u} litre bottle`:`${capitalised(o)} ${u} litre bottles`,g=d>m?h:f;if(g.trim().toLowerCase()!==n.correctAnswer.trim().toLowerCase())return null;const b=x=>({type:"equation_display",terms:x});return buildExample({generatorId:this.id,question:n,title:`${h} or ${f} — which holds more?`,computed:{cartons:a,mlEach:l,total1:d,bottles:i,litreEach:u,thousand:1e3,total2:m,answer:Math.max(d,m)},steps:[{visual_data:b([String(a),"×",String(l),"=",String(d)]),animation:"show_start",caption:`${h}: ${a} × ${l} = ${d} ml.`,spoken:`${capitalised(e)} cartons of ${numberToWordsLong(l)} millilitres each makes ${numberToWordsLong(d)} millilitres.`},{visual_data:b([String(i),"×",String(u*1e3),"=",String(m)]),animation:"show_second",caption:`${f} = ${m} ml.`,spoken:`${digitsToWords(f)} changes to ${numberToWordsLong(m)} millilitres — we always change both to the same unit before comparing.`},{visual_data:b([String(d),"ml","vs",String(m),"ml"]),animation:"compare_units",caption:`${d} ml vs ${m} ml — ${g} holds more.`,spoken:`Now that both are in millilitres, we can compare them directly. ${digitsToWords(g)} holds more.`}]})}},TWO_MUGS_FILL=/^A jug already has (\w+) mugs of juice poured in, each holding (\d+) ml\. It still needs (\d+) ml more to be completely full\. How many ml does the jug hold when full\?$/,twoMugsFill={id:"MEA.P5.M03.two-mugs-fill.v1",microSkillId:"MEA.P5.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(TWO_MUGS_FILL);if(!t)return null;const e=COUNT_WORDS$1[t[1].toLowerCase()];if(!e)return null;const s=Number(t[2]),o=Number(t[3]);if(s<50||s>999||o<1||o>999)return null;const r=e*s,a=r+o;if(a>3e3||a!==answerInt(n))return null;const i=(d,m,h,f)=>({label:d,length:m,start:h,color:f}),l={type:"measure_bars",unit:"ml",vessel:!0,capacity:a,bars:[i("Poured so far",r,0,"#2563eb")],show_values:!0},u={type:"measure_bars",unit:"ml",vessel:!0,capacity:a,bars:[i("Poured so far",r,0,"#2563eb"),i("Still needed",o,r,"#fbbf24")],show_values:!0};return buildExample({generatorId:this.id,question:n,title:`${e} mugs of ${s} ml, plus ${o} ml more — how much when full?`,computed:{mugsCount:e,mlEach:s,poured:r,topUp:o,full:a,answer:a},steps:[{visual_data:l,animation:"show_start",caption:`${e} mugs × ${s} ml = ${r} ml poured so far.`,spoken:`${capitalised(numberToWords(e))} mugs, each holding ${numberToWordsLong(s)} millilitres, means ${numberToWords(e)} times ${numberToWordsLong(s)} — that's ${numberToWordsLong(r)} millilitres already in the jug.`},{visual_data:u,animation:"show_gap",caption:`${o} ml more needed to fill it.`,spoken:`The jug still needs ${numberToWordsLong(o)} more millilitres to be completely full.`},{visual_data:u,animation:"complete_fill",caption:`${r} + ${o} = ${a} ml when full.`,spoken:`Adding what's already poured to what's still needed gives ${numberToWordsLong(a)} millilitres — the jug's full capacity.`}]})}},MULTIPLY_DIGIT=/^There are (\d+) (?:small |large |)?(\w+?)s\. Each \2 holds (\d+) ml of (\w+)\. How much \4 is there in total\? Answer in ml$/i,MULTIPLY_WORD=/^(\w+) (\w+?)s each hold (\d+) ml of (\w+)\. How much \4 is there altogether, in ml\?$/i,multiplyContainers={id:"MEA.P5.M03.multiply-containers.v1",microSkillId:"MEA.P5.M03",forms:["context_calculation","symbolic_numeric_entry"],generate(n){const t=normaliseStem(n.stem);let e,s,o,r;const a=t.match(MULTIPLY_DIGIT);if(a)e=Number(a[1]),s=a[2],o=Number(a[3]),r=a[4];else{const h=t.match(MULTIPLY_WORD);if(!h)return null;const f=COUNT_WORDS$1[h[1].toLowerCase()];if(!f)return null;e=f,s=h[2],o=Number(h[3]),r=h[4]}if(e<2||e>12||o<1||o>999)return null;const i=e*o;if(i>12e3||i!==answerInt(n))return null;const l=`${s}s`,d=Array.from({length:e},(h,f)=>(f+1)*o).map(h=>numberToWordsLong(h)).join(", "),m={type:"measure_bars",unit:"ml",vessel:!0,bars:[{label:r,length:e,color:"#2563eb"}],mark_count:e,mark_value:o,show_values:!1};return buildExample({generatorId:"MEA.P5.M03.multiply-containers.v1",question:n,title:`${e} ${l} of ${o} ml each — how much ${r} altogether?`,computed:{count:e,mlEach:o,total:i,answer:i},steps:[{visual_data:m,animation:"show_scale",caption:`There are ${e} ${l}, each holding ${o} ml.`,spoken:`There are ${numberToWords(e)} ${l} of ${r}, each holding ${numberToWordsLong(o)} millilitres. Watch what happens as each one is poured in.`},{visual_data:m,animation:"count_marks",caption:`Count in ${o}s, one ${s} at a time: ${o} … ${i}.`,spoken:`Every ${s} adds the same amount, so we count on in steps of ${numberToWordsLong(o)}, one ${s} at a time: ${d}.`},{visual_data:m,animation:"times_value",caption:`${e} × ${o} ml = ${i} ml.`,spoken:`${capitalised(numberToWords(e))} ${l}, each worth ${numberToWordsLong(o)} millilitres: ${numberToWords(e)} times ${numberToWordsLong(o)} is ${numberToWordsLong(i)}. Altogether there are ${numberToWordsLong(i)} millilitres of ${r}.`}]})}},ADD_WATER_ALREADY=/^An? (.+?) already has (\d+) ml of (\w+) in it\. (\w+) tips in an? (.+?) holding (\d+) ml\. How many ml are in the (.+?) now\?$/i,addWaterAlready={id:"MEA.P5.M03.add-water-already.v1",microSkillId:"MEA.P5.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(ADD_WATER_ALREADY);
71if(!t)return null;const[,e,s,o,r,a,i]=t,l=Number(s),u=Number(i);if(l<1||l>9999||u<1||u>9999)return null;const d=l+u;if(d>2e4||d!==answerInt(n))return null;const m=(b,x,y,E)=>({label:b,length:x,start:y,color:E}),h={type:"measure_bars",unit:"ml",vessel:!0,capacity:d,bars:[m("Already in",l,0,"#2563eb")],show_values:!0},f={type:"measure_bars",unit:"ml",vessel:!0,capacity:d,bars:[m("Already in",l,0,"#2563eb"),m("Tipped in",u,l,"#0d9488")],show_values:!0},g={type:"measure_bars",unit:"ml",vessel:!0,capacity:d,bars:[m("Total",d,0,"#2563eb")],show_values:!0};return buildExample({generatorId:"MEA.P5.M03.add-water-already.v1",question:n,title:`${l} ml already there, plus ${u} ml poured in — how much now?`,computed:{already:l,added:u,total:d,answer:d},steps:[{visual_data:h,animation:"show_start",caption:`${l} ml already in the ${e}.`,spoken:`There are already ${numberToWordsLong(l)} millilitres of ${o} in the ${e}.`},{visual_data:f,animation:"show_second",caption:`${r} tips in ${u} ml from the ${a}.`,spoken:`${r} tips in another ${numberToWordsLong(u)} millilitres from the ${a}. Tipping more in makes the ${e} fuller, so we add.`},{visual_data:g,animation:"complete_fill",caption:`${l} + ${u} = ${d} ml.`,spoken:`${capitalised(numberToWordsLong(l))} plus ${numberToWordsLong(u)} makes ${numberToWordsLong(d)} millilitres now in the ${e}.`}]})}},TWO_STEP_DRINK=/^An? (.+?) holds 1 litres? of (\w+)\. (\w+) drinks (\d+) ml, then drinks another (\d+) ml\. How many ml of \2 are left\?$/i,twoStepDrink={id:"MEA.P5.M03.two-step-drink.v1",microSkillId:"MEA.P5.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(TWO_STEP_DRINK);if(!t)return null;const[,e,s,o,r,a]=t,i=Number(r),l=Number(a);if(i<1||i>999||l<1||l>999)return null;const u=1e3-i;if(u<=0)return null;const d=u-l;if(d<0)return null;const m=n.correctAnswer.match(/(\d+)/);if(!m||Number(m[1])!==d)return null;const h=f=>({type:"equation_display",terms:f});return buildExample({generatorId:"MEA.P5.M03.two-step-drink.v1",question:n,title:`1 litre of ${s}, ${i} ml then ${l} ml drunk — how much is left?`,computed:{thousand:1e3,firstDrink:i,afterFirst:u,secondDrink:l,afterSecond:d,answer:d},steps:[{visual_data:h(["1","litre","=","1000","ml"]),animation:"show_base_fact",caption:`The ${e} holds 1 litre, which is 1000 ml.`,spoken:`The ${e} holds one litre of ${s}. A litre is one thousand millilitres, so we start from one thousand.`},{visual_data:h(["1000","ml","-",String(i),"ml","=",String(u),"ml"]),animation:"show_first_drink",caption:`First drink: 1000 − ${i} = ${u} ml.`,spoken:`${o} drinks ${numberToWordsLong(i)} millilitres first. Take that away from one thousand: ${numberToWordsLong(u)} millilitres are left.`},{visual_data:h([String(u),"ml","-",String(l),"ml","=",String(d),"ml"]),animation:"show_second_drink",caption:`Second drink: ${u} − ${l} = ${d} ml.`,spoken:`Then ${o} drinks another ${numberToWordsLong(l)} millilitres. Take that away from the ${numberToWordsLong(u)} millilitres that were left: ${numberToWordsLong(d)} millilitres of ${s} remain.`}]})}},MARKS_SCALE=/^An? .+? has marks every (\d+) ml\. The water reaches the (\d+)(?:st|nd|rd|th) mark above zero\. How much water is in the .+?\? Answer in ml$/i,marksScaleReasoning={id:"MEA.P5.M03.marks-scale-reasoning.v1",microSkillId:"MEA.P5.M03",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(MARKS_SCALE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>999||s<2||s>20)return null;const o=s*e;if(o!==answerInt(n))return null;const r=n.visualData;let a="Water";if(r&&r.type==="measure_bars"&&r.mark_count!=null){if(r.mark_count!==s||(r.mark_value??1)!==e)return null;r.bars[0]?.label&&(a=r.bars[0].label)}const i="ml",u=Array.from({length:s},(f,g)=>(g+1)*e).map(f=>numberToWordsLong(f)).join(", "),d=e*2,m=s===2?`Count the marks: ${e}, ${o}.`:`Count the marks: ${e}, ${d} … ${o}.`,h={type:"measure_bars",unit:i,vessel:!0,bars:[{label:a,length:s,color:"#2563eb"}],mark_count:s,mark_value:e,show_values:!1};return buildExample({generatorId:"MEA.P5.M03.marks-scale-reasoning.v1",question:n,title:"How much water is in this jug?",computed:{markCount:s,markValue:e,total:o,secondMark:d,answer:o},steps:[{visual_data:h,animation:"show_scale",caption:`Each mark on this jug is worth ${e} ${i}.`,spoken:`Here is a measuring jug. The lines up its side are the marks, and each mark is worth ${numberToWordsLong(e)} millilitres. To read it, we work out how many marks the water reaches.`},{visual_data:h,animation:"count_marks",caption:m,spoken:`Now we count the marks one at a time, adding ${numberToWordsLong(e)} each time: ${u}.`},{visual_data:h,animation:"times_value",caption:`${s} marks × ${e} ${i} = ${o} ${i}.`,spoken:`${capitalised(numberToWords(s))} marks, and each mark is worth ${numberToWordsLong(e)} millilitres. So we multiply: ${numberToWords(s)} times ${numberToWordsLong(e)} is ${numberToWordsLong(o)}. The jug holds ${numberToWordsLong(o)} millilitres. To read any scale, count the marks, then times by what one mark is worth.`}]})}},MEA_P5_M03_GENERATORS=[complementTo1000,litreSubtract,crossCompare,visualCompare,pourClaimCheck,tankFillCount,cartonsVsBottle,twoMugsFill,multiplyContainers,addWaterAlready,twoStepDrink,marksScaleReasoning],massLabelWords=n=>digitsToWords(n).replace(/\bone kg\b/g,"one kilogram").replace(/\bkg\b/g,"kilograms").replace(/\bone g\b/g,"one gram").replace(/\bg\b/g,"grams"),readingWords=(n,t)=>`${Number.isInteger(n)?numberToWordsLong(n):decimalToWords(String(n))} ${t==="kg"?n===1?"kilogram":"kilograms":n===1?"gram":"grams"}`,GENERATOR_ID$1="MEA.P5.M04.mass-compare.v1";function normLabel(n){return n.trim().replace(/\s+/g," ").toLowerCase()}function toGrams(n,t){return t==="g"?n:t==="kg"?n*1e3:null}function kgFact(n,t){return n===1?"One kilogram is one thousand grams.":`One kilogram is one thousand grams, so ${numberToWords(n)} kilograms is ${numberToWordsLong(t)} grams.`}function kgRowsFact(n){const t=n.filter(e=>e.grams!==1e3);return t.length===0?"One kilogram is one thousand grams.":`One kilogram is one thousand grams, so ${t.map(e=>
71`${e.words} is ${numberToWordsLong(e.grams)} grams`).join(" and ")}.`}const massCompare={id:GENERATOR_ID$1,microSkillId:"MEA.P5.M04",forms:["comparison_statement"],generate(n){const t=n.visualData;if(!t||t.type!=="mass_compare")return null;const{left:e,right:s,left_label:o,right_label:r}=t;if(!Number.isFinite(e.reading)||!Number.isFinite(s.reading)||e.reading<0||s.reading<0)return null;const a=toGrams(e.reading,e.unit),i=toGrams(s.reading,s.unit);if(a===null||i===null||a>2e4||i>2e4)return null;const l=a>i?o:a<i?r:"They weigh the same";if(normLabel(l)!==normLabel(n.correctAnswer))return null;const u=a>i?">":a<i?"<":"=",d=a===i?"they weigh the same":`${a>i?o:r} is heavier`,m=e.unit===s.unit;let h,f;if(m)h=`${e.reading} ${e.unit} ${u} ${s.reading} ${s.unit} — ${d}.`,f=a===i?`Both dials are already in the same unit, so we compare directly. ${capitalised(readingWords(e.reading,e.unit))} and ${readingWords(s.reading,s.unit)} are the same, so they weigh the same.`:`Both dials are already in the same unit, so we compare the readings directly. ${capitalised(readingWords(Math.max(e.reading,s.reading),e.unit))} is more than ${readingWords(Math.min(e.reading,s.reading),e.unit)}, so ${d}.`;else{const x=e.unit==="kg",y=x?`${a} g`:`${e.reading} g`,E=x?`${s.reading} g`:`${i} g`;h=`${x?`${e.reading} kg = ${a} g`:`${s.reading} kg = ${i} g`}. ${y} ${u} ${E} — ${d}.`;const T=x?e.reading:s.reading,A=x?a:i;f=a===i?`${kgFact(T,A)} Once both are in grams, they come to exactly the same amount — they weigh the same.`:`${kgFact(T,A)} Now that both are in grams, we can compare them directly: ${numberToWordsLong(Math.max(a,i))} grams is more than ${numberToWordsLong(Math.min(a,i))} grams, so ${d}.`}const g=x=>({type:"mass_compare",left:e,right:s,left_label:o,right_label:r,show_readings:x.showReadings,emphasis:x.emphasis}),b=a===i?null:a>i?"left":"right";return buildExample({generatorId:GENERATOR_ID$1,question:n,title:`Which is heavier: ${o} or ${r}?`,computed:{leftReading:e.reading,rightReading:s.reading,leftGrams:a,rightGrams:i},steps:[{visual_data:g({showReadings:!1,emphasis:null}),animation:"sweep_left",caption:`${o}'s scale reads ${e.reading} ${e.unit}.`,spoken:`To compare the weights, we read each scale. Let's read ${o.replace(/^The /,"the ")}'s scale first. The needle sweeps round, and we count the marks up to where it stops: ${readingWords(e.reading,e.unit)}.`},{visual_data:g({showReadings:!0,emphasis:null}),animation:"sweep_right",caption:`${r}'s scale reads ${s.reading} ${s.unit}.`,spoken:`Now ${r.replace(/^The /,"the ")}. Its needle sweeps too, and we count its marks the same way: ${readingWords(s.reading,s.unit)}.`},{visual_data:g({showReadings:!0,emphasis:b}),animation:"highlight_heavier",caption:h,spoken:f}]})}},eq$b=n=>({type:"equation_display",terms:n}),MASS_CLAIM=/^(\w+) says a (\d+) kg bag of (\w+) is heavier than a (\d+) g bag of \3\. Is (?:he|she) right\?$/,massClaimCheck={id:"MEA.P5.M04.mass-claim-check.v1",microSkillId:"MEA.P5.M04",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(MASS_CLAIM);if(!t)return null;const[,e,s,,o]=t,r=Number(s),a=Number(o);if(r<1||r>20||a<1||a>2e4)return null;const i=r*1e3,l=i>a,u=n.correctAnswer.trim();return l&&!isAffirmative(u)||!l&&!isNegative(u)?null:buildExample({generatorId:this.id,question:n,title:`Is ${r} kg really heavier than ${a} g?`,computed:{kg:r,g:a,kgInG:i,thousand:1e3,answer:i},steps:[{visual_data:eq$b(["1","kg","=","1000","g"]),animation:"show_base_fact",caption:"Before trusting the claim, let's check the base fact.",spoken:"Kilo means a thousand, so one kilogram is exactly one thousand grams — that is what every check like this uses."},{visual_data:eq$b([String(r),"kg","=",String(i),"g"]),animation:"write_multiplication",caption:`${r} kg = ${i} g.`,spoken:`${capitalised(numberToWords(r))} kilograms changes to ${numberToWordsLong(i)} grams.`},{visual_data:eq$b([String(i),"g","vs",String(a),"g"]),animation:"write_answer",caption:`${i} g vs ${a} g — ${e} is ${l?"right":"wrong"}.`,spoken:`Now both are in grams, so we can compare directly. That means ${e} is ${l?"right":"wrong"}.`}]})}},MASS_TOTAL_GRAMS=/^A delivery van carries two parcels: one weighs (\d+) kg and the other weighs (\d+) g\. What is their total weight in grams\?$/,massTotalGrams={id:"MEA.P5.M04.total-grams.v1",microSkillId:"MEA.P5.M04",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MASS_TOTAL_GRAMS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>20||s<1||s>2e4)return null;const o=e*1e3,r=o+s;return r!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${e} kg parcel + ${s} g parcel — total in grams?`,computed:{kg:e,g:s,kgInG:o,thousand:1e3,total:r,answer:r},steps:[{visual_data:eq$b([String(e),"kg","=",String(o),"g"]),animation:"show_base_fact",caption:`First, change ${e} kg into grams: ${o} g.`,spoken:`We change both parcels into the same unit before adding. ${capitalised(numberToWords(e))} kilograms is ${numberToWordsLong(o)} grams.`},{visual_data:eq$b([String(o),"g","+",String(s),"g","=","?"]),animation:"write_multiplication",caption:`${o} g + ${s} g.`,spoken:"Now both weights are in grams, we can add them together."},{visual_data:eq$b([String(o),"+",String(s),"=",String(r)]),animation:"write_answer",caption:`${o} + ${s} = ${r} g altogether.`,spoken:`Adding both parcels together gives ${numberToWordsLong(r)} grams in total.`}]})}},HEAVIEST_3WAY=/^Which is the heaviest: (.+?), (.+?), or (.+?)\?$/,MASS_AMOUNT=/^(\d+) (g|kg)$/,heaviest3Way={id:"MEA.P5.M04.heaviest-3way.v1",microSkillId:"MEA.P5.M04",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(HEAVIEST_3WAY);if(!t)return null;const s=[t[1],t[2],t[3]].map(u=>{const d=u.match(MASS_AMOUNT);if(!d)return null;const m=Number(d[1]),h=d[2],f=h==="kg"?m*1e3:m;return{label:u,grams:f,unit:h,raw:m}});if(s.some(u=>u===null))return null;const o=s;if(o.some(u=>u.grams<1||u.grams>2e4))return null;const r=o.filter(u=>u.unit==="kg"),a=Math.max(...o.map(u=>u.grams)),i=o.filter(u=>u.grams===a);if(i.length!==1)return null;const l=i[0];return l.label!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:`Which is the heaviest: ${o.map(u=>u.label).join(", ")}?`,computed:Object.fromEntries([...o.map((u,d)=>[`amount${d}
71`,u.grams]),["thousand",1e3],["answer",a]]),steps:[{visual_data:eq$b(o.map(u=>u.label)),animation:"show_base_fact",caption:"Change every amount to grams first.",spoken:"We always change every amount to the same unit before comparing them."},{visual_data:eq$b(o.map(u=>`${u.grams} g`)),animation:"write_multiplication",caption:`${o.map(u=>`${u.grams} g`).join(", ")}.`,spoken:r.length===0?"Every amount is already in grams, so we can line them up and compare.":`${kgRowsFact(r.map(u=>({words:massLabelWords(u.label),grams:u.grams})))} Now every amount is in grams, so we can line them up and compare.`},{visual_data:eq$b([l.label]),animation:"write_answer",caption:`${l.label} is the heaviest.`,spoken:`Comparing all three in grams, ${massLabelWords(l.label)} is the heaviest.`}]})}},MASS_REVERSE=/^Two bags of flour together weigh (\d+) kg\. One bag weighs (\d+) g\. How many grams does the other bag weigh\?$/,massReverse={id:"MEA.P5.M04.reverse.v1",microSkillId:"MEA.P5.M04",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MASS_REVERSE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>20)return null;const o=e*1e3;if(s<1||s>=o)return null;const r=o-s;return r!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`Two bags weigh ${e} kg together, one is ${s} g — what's the other?`,computed:{kg:e,knownG:s,totalG:o,thousand:1e3,otherG:r,answer:r},steps:[{visual_data:eq$b([String(e),"kg","=",String(o),"g"]),animation:"show_base_fact",caption:`Both bags together: ${e} kg = ${o} g.`,spoken:`The known bag is weighed in grams, so we change the total into grams too. ${capitalised(numberToWords(e))} ${e===1?"kilogram":"kilograms"} is ${numberToWordsLong(o)} grams.`},{visual_data:eq$b([String(o),"g","-",String(s),"g","=","?"]),animation:"write_multiplication",caption:`${o} − ${s} g (the known bag).`,spoken:`One bag already weighs ${numberToWordsLong(s)} grams. We take that away from the total.`},{visual_data:eq$b([String(o),"-",String(s),"=",String(r)]),animation:"write_answer",caption:`${o} − ${s} = ${r} g.`,spoken:`Subtracting the known bag from the total leaves ${numberToWordsLong(r)} grams for the other bag.`}]})}},MEA_P5_M04_GENERATORS=[massCompare,massClaimCheck,massTotalGrams,heaviest3Way,massReverse];function expandSides(n,t){return n==="square"?t.length===1?{fullSides:[t[0],t[0],t[0],t[0]],given:[0]}:t.length===4?{fullSides:t,given:[0,1,2,3]}:null:n==="rectangle"?t.length===2?{fullSides:[t[0],t[1],t[0],t[1]],given:[0,1]}:t.length===4?{fullSides:t,given:[0,1,2,3]}:null:n==="triangle"?t.length!==3?null:{fullSides:t,given:[0,1,2]}:t.length!==6?null:{fullSides:t,given:[0,1,2,3,4,5]}}const perimeterGenerator={id:"MEA.P5.M05.perimeter.v1",microSkillId:"MEA.P5.M05",forms:["context_calculation"],generate(n){const t=n.visualData;if(!t||t.type!=="perimeter_shape")return null;const{shape:e,sides:s}=t;if(e!=="rectangle"&&e!=="square"&&e!=="triangle"&&e!=="l_shape"||s.length<1||s.length>6||s.some(k=>!Number.isInteger(k)||k<1||k>100))return null;const o=t.unit??"cm",r=expandSides(e,s);if(!r)return null;const{fullSides:a}=r;if(a.length>6)return null;const i=a.map((k,T)=>T),l=a.reduce((k,T)=>k+T,0),u=Number(n.correctAnswer);if(!Number.isFinite(u)||l!==u||e==="rectangle"&&(a[0]!==a[2]||a[1]!==a[3])||e==="square"&&a.some(k=>k!==a[0]))return null;const d=Math.max(1,Math.min(a.length-1,Math.ceil(a.length/2))),m=a.slice(0,d).reduce((k,T)=>k+T,0),h=e==="l_shape"?"shape":e,f=k=>({type:"perimeter_shape",shape:e,sides:a,unit:o,given:i,walked:k}),g=e==="square"?`A square: all 4 sides are ${a[0]} ${o}.`:e==="rectangle"?`Opposite sides match: ${a[0]}, ${a[1]}, ${a[2]}, ${a[3]} ${o}.`:`This ${h}'s ${a.length} sides: ${a.join(", ")} ${o}.`,b={cm:"centimetres",m:"metres",mm:"millimetres",km:"kilometres"}[o]??o,x="Perimeter means the distance all the way round the edge, so we need every side.",y=e==="square"?`${x} A square has four sides all the same length, so every side is ${numberToWordsLong(a[0])} ${b}.`:e==="rectangle"?`${x} A rectangle has two long sides and two short sides, and the sides opposite each other match: ${numberToWordsLong(a[0])} and ${numberToWordsLong(a[2])}, ${numberToWordsLong(a[1])} and ${numberToWordsLong(a[3])}.`:`${x} Every side of this ${h} is labelled, so we add them all up.`,E=a.slice(0,d);return buildExample({generatorId:this.id,question:n,title:`What is the perimeter of this ${h}?`,computed:Object.fromEntries([...a.map((k,T)=>[`side${T}`,k]),["walkedTotal",m],["perimeter",l],["answer",l],["givenCount",r.given.length],["sideCount",a.length]]),steps:[{visual_data:f(0),animation:"show_shape",caption:g,spoken:y},{visual_data:f(d),animation:"walk_sides",caption:`Count on round the edge: ${E.join(" + ")} = ${m} so far.`,spoken:`Watch the dot go round the edge. We add each side as the dot goes past it, so none gets missed. After ${numberToWords(d)} sides, ${E.map(k=>numberToWordsLong(k)).join(" and ")} make ${numberToWordsLong(m)} so far.`},{visual_data:f(a.length),animation:"total",caption:`${a.join(" + ")} = ${l} ${o}.`,spoken:`Keep going all the way round: ${a.map(k=>numberToWordsLong(k)).join(" and ")} make ${numberToWordsLong(l)}. So the perimeter is ${numberToWordsLong(l)} ${b} — every side counted once.`}]})}},PERIMETER_CLAIM_HALF=/^(\w+) says the perimeter of this rectangle is (\d+) cm because (?:he|she) added the length and the width\. Is (?:he|she) right\?$/,perimeterClaimHalf={id:"MEA.P5.M05.claim-half.v1",microSkillId:"MEA.P5.M05",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(PERIMETER_CLAIM_HALF);if(!t)return null;const[,e,s]=t,o=Number(s),r=n.visualData;if(!r||r.type!=="perimeter_shape"||r.shape!=="rectangle")return null;const a=expandSides("rectangle",r.sides);if(!a)return null;const{fullSides:i,given:l}=a;if(i.some(g=>!Number.isInteger(g)||g<1||g>100))return null;const u=i[0]+i[1],d=i.reduce((g,b)=>g+b,0);if(u!==o)return null;const m=n.correctAnswer.trim();if(!isNegative(m))return null;const h=r.unit??"cm",f=g=>({type:"perimeter_shape",shape:"rectangle",sides:i,unit:h,given:l,walked:g});return buildExample({generatorId:this.id,question:n,title:`Is the perimeter really ${o} ${h}
71?`,computed:{side0:i[0],side1:i[1],halfSum:u,perimeter:d,answer:d},steps:[{visual_data:f(0),animation:"show_shape",caption:"Before trusting the claim, let's walk every side.",spoken:"Before trusting the claim, let's check for ourselves — a rectangle has four sides, not two."},{visual_data:f(4),animation:"walk_sides",caption:`${i.join(" + ")} = ${d} ${h}.`,spoken:`Walking all the way round, every side gets added — not just one length and one width, but both of each. ${capitalised(i.map(g=>numberToWordsLong(g)).join(" and "))} make ${numberToWordsLong(d)} ${h==="cm"?"centimetres":h==="m"?"metres":h}.`},{visual_data:f(4),animation:"total",caption:`Perimeter = ${d} ${h} — ${e} is wrong.`,spoken:`The true perimeter is ${numberToWordsLong(d)} ${h==="cm"?"centimetres":h}, not ${numberToWordsLong(o)}. ${e} only added the length and width once, missing the other two sides.`}]})}},FENCING=/^A (.+?) is a rectangle\. Two of its sides are (\d+) m long and the other two sides are (\d+) m long\. How many metres of fencing are needed to go all the way around it\?$/,fencingRectangle={id:"MEA.P5.M05.fencing.v1",microSkillId:"MEA.P5.M05",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(FENCING);if(!t)return null;const e=t[1],s=Number(t[2]),o=Number(t[3]);if(s<1||s>100||o<1||o>100)return null;const r=2*(s+o);if(r!==answerInt(n))return null;const a=[s,o,s,o],i=l=>({type:"perimeter_shape",shape:"rectangle",sides:a,unit:"m",given:[0,1,2,3],walked:l});return buildExample({generatorId:this.id,question:n,title:`Fencing all the way around a ${s} m by ${o} m rectangle?`,computed:{a:s,b:o,walkedTotal:s+o,perimeter:r,answer:r},steps:[{visual_data:i(0),animation:"show_shape",caption:`This ${e}'s sides: ${s} m, ${o} m, ${s} m, ${o} m.`,spoken:`A rectangle has two matching pairs of sides — here, ${numberToWords(s)} metres and ${numberToWords(o)} metres, each appearing twice.`},{visual_data:i(2),animation:"walk_sides",caption:`Walking the edge: ${s+o} m so far.`,spoken:`We walk round the edge and add each side as we pass it. After two sides, we have ${numberToWordsLong(s+o)} metres so far.`},{visual_data:i(4),animation:"total",caption:`${s} + ${o} + ${s} + ${o} = ${r} m.`,spoken:`All four sides together make ${numberToWordsLong(r)} metres of fencing — every side counted once.`}]})}},SQUARE_VS_RECT=/^Shape A is a square with sides of (\d+) cm\. Shape B is a rectangle with sides of (\d+) cm and (\d+) cm\. Which shape has the greater perimeter\?$/,squareVsRectPerimeter={id:"MEA.P5.M05.square-vs-rect.v1",microSkillId:"MEA.P5.M05",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(SQUARE_VS_RECT);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if([e,s,o].some(d=>d<1||d>100))return null;const r=4*e,a=2*(s+o);if(r===a)return null;const i=r>a?"Shape A":"Shape B";if(i!==n.correctAnswer.trim())return null;const l={type:"perimeter_shape",shape:"square",sides:[e,e,e,e],unit:"cm",given:[0,1,2,3],walked:4},u={type:"perimeter_shape",shape:"rectangle",sides:[s,o,s,o],unit:"cm",given:[0,1,2,3],walked:4};return buildExample({generatorId:this.id,question:n,title:"Which has the greater perimeter: Shape A or Shape B?",computed:{squareSide:e,squarePerimeter:r,rectA:s,rectB:o,rectPerimeter:a,four:4,answer:Math.max(r,a)},steps:[{visual_data:l,animation:"total",caption:`Shape A: ${e} × 4 = ${r} cm.`,spoken:`Shape A is a square, so every side is ${numberToWords(e)}. Adding it four times makes ${numberToWordsLong(r)} centimetres.`},{visual_data:u,animation:"total",caption:`Shape B: ${s} + ${o} + ${s} + ${o} = ${a} cm.`,spoken:`Shape B is a rectangle, so we add its two pairs of sides: ${numberToWords(s)} and ${numberToWords(o)}, twice each, making ${numberToWordsLong(a)} centimetres.`},{visual_data:i==="Shape A"?l:u,animation:"total",caption:`${r} vs ${a} — ${i} is greater.`,spoken:`Comparing both perimeters, ${i==="Shape A"?"shape A":"shape B"} has the greater one.`}]})}},PERIMETER_REVERSE=/^A rectangular field has a perimeter of (\d+) m\. One side measures (\d+) m\. What is the length of the other side\?$/,perimeterReverse={id:"MEA.P5.M05.reverse.v1",microSkillId:"MEA.P5.M05",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(PERIMETER_REVERSE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<4||e>400||s<1||s>100)return null;const o=e-2*s;if(o<2||o%2!==0)return null;const r=o/2;if(r!==answerInt(n))return null;const a=[s,r,s,r],i=(l,u,d=[])=>({type:"perimeter_shape",shape:"rectangle",sides:a,unit:"m",given:l,walked:u,hidden:d});return buildExample({generatorId:this.id,question:n,title:`Perimeter ${e} m, one side ${s} m — what's the other side?`,computed:{perimeter:e,side:s,twoSides:2*s,remaining:o,two:2,other:r,answer:r},steps:[{visual_data:i([0,2],0,[1,3]),animation:"show_shape",caption:`Two known sides: ${s} m each.`,spoken:`This rectangle has two sides of ${numberToWords(s)} metres already known — one at each end.`},{visual_data:i([0,2],0,[1,3]),animation:"total",caption:`${e} − ${2*s} = ${o} m left for the other two sides.`,spoken:`Taking those two known sides away from the whole perimeter leaves ${numberToWordsLong(o)} metres shared between the other two matching sides.`},{visual_data:i([0,1,2,3],4),animation:"total",caption:`${o} ÷ 2 = ${r} m.`,spoken:`Splitting what's left equally between the two matching sides gives ${numberToWordsLong(r)} metres each.`}]})}},MEA_P5_M05_GENERATORS=[perimeterGenerator,perimeterClaimHalf,fencingRectangle,squareVsRectPerimeter,perimeterReverse],TIME_CONVERSION=/^(\d+) (hours?|minutes?) = __ (minutes|seconds)$/,N_MIN=2,N_MAX=60,GENERATOR_ID="MEA.P6.M04.time-convert.v1";function timeConversionGenerate(n){const t=normaliseStem(n.stem).match(TIME_CONVERSION);if(!t)return null;const e=Number(t[1]),s=t[2],o=t[3],r=s.startsWith("hour"),a=s.startsWith("minute");
71if(!(r&&o==="minutes"||a&&o==="seconds")||e<N_MIN||e>N_MAX||s!==(r?"hours":"minutes"))return null;const u=n.visualData;if(u&&u.type==="equation_display"){const A=u.terms;if(Number(A[0])!==e)return null;const N=String(A[1]);if(r&&!N.startsWith("hour")||a&&!N.startsWith("minute"))return null}const d=e*60;if(d!==Number(n.correctAnswer))return null;const m=r?"hour":"minute",h=r?"hours":"minutes",f=Math.min(e,10),g=Array.from({length:f},(A,N)=>({from:N*60,to:(N+1)*60,label:"+60"})),b=e>10?600:0,x=Array.from({length:e>10?e-10:e},(A,N)=>({from:b+N*60,to:b+(N+1)*60,label:"+60"})),y=d-60,E=x.length===1,k=E?x:x.slice(0,-1),T=A=>({type:"number_line",min:b,max:d,step:60,marked:[b,...(A?x:k).map(N=>N.to)],hops:A?x:k,unknown:A?d:null});return buildExample({generatorId:GENERATOR_ID,question:n,title:`How do we convert ${e} ${h} into ${o}?`,computed:{one:1,n:e,base:60,ten:10,sixHundred:600,beforeLast:y,answer:d},steps:[{visual_data:e>10?{type:"number_line",min:0,max:600,step:60,marked:[0,...g.map(A=>A.to)],hops:g,unknown:null}:{type:"equation_display",terms:[1,m,"=",60,o]},animation:"show_base_fact",caption:e>10?`Each ${m} is 60 ${o}; ten jumps reach 600.`:`1 ${m} = 60 ${o}.`,spoken:`Every ${m} is sixty ${o}. Each group we add is another sixty.${e>10?` First count ten jumps: ${g.map(A=>numberToWordsLong(A.to)).join(", ")}.`:""}`},{visual_data:T(!1),animation:"count_on",caption:E?`One group remains: 600 + 60 = ${d}.`:e>10?"From 600, keep adding 60; one group still to add.":"Count on in 60s; one group still to add.",spoken:E?`We have counted ten groups, reaching six hundred. One ${m} remains, so add one more sixty: ${numberToWordsLong(d)}.`:`${e>10?"We have already counted ten groups. Continue from six hundred: ":`Count one sixty for each ${m}: `}${k.map(A=>numberToWordsLong(A.to)).join(", ")}. We have one more sixty to add.`},{visual_data:T(!0),animation:"land",caption:`${e} ${h} = ${d} ${o}.`,spoken:`The last jump takes ${numberToWordsLong(y)} to ${numberToWordsLong(d)}. We have counted all ${numberToWordsLong(e)} groups of sixty. So ${numberToWordsLong(e)} ${h} is ${numberToWordsLong(d)} ${o}.`}]})}const timeConversion={id:GENERATOR_ID,microSkillId:"MEA.P6.M04",forms:["context_calculation","fact_symbolic_retrieval"],generate:timeConversionGenerate},HOUR_FACT_GENERATOR_ID="MEA.P6.M04.hour-in-minutes-fact.v1",HOUR_FACT_STEM=/^1 hour = __ minutes$/i,hourInMinutesFact={id:HOUR_FACT_GENERATOR_ID,microSkillId:"MEA.P6.M04",forms:["fact_symbolic_retrieval"],generate(n){const t=normaliseStem(n.stem);if(!HOUR_FACT_STEM.test(t)||answerInt(n)!==60)return null;const e=s=>({type:"clock",hour:12,minute:0,sweep_start:0,...s?{conclude_label:s}:{}});return buildExample({generatorId:HOUR_FACT_GENERATOR_ID,question:n,title:"How many minutes are in 1 hour?",computed:{one:1,answer:60},steps:[{visual_data:e(),animation:"show_start",caption:"The long hand starts at the top.",spoken:"Watch the long hand as it travels all the way around the clock, back to the top. That whole journey is one hour."},{visual_data:e(),animation:"sweep_minute_hand",caption:"Count the minutes as the hand goes all the way round.",spoken:"Count the minutes as the long hand sweeps round the clock face, all the way up to sixty."},{visual_data:e("1 hour = 60 minutes"),animation:"write_answer",caption:"1 hour = 60 minutes.",spoken:"One whole turn of the long hand is sixty minutes. So one hour is sixty minutes."}]})}},FRACTION_HOUR=/^(Half|Quarter|Three[- ]quarters) (?:of )?an hour = __ minutes$/i,FRACTION_HOUR_PARTS={half:{num:1,den:2},quarter:{num:1,den:4},"three quarters":{num:3,den:4},"three-quarters":{num:3,den:4}},fractionalHour={id:"MEA.P6.M04.fraction-hour.v1",microSkillId:"MEA.P6.M04",forms:["context_calculation","fact_symbolic_retrieval"],generate(n){const t=normaliseStem(n.stem).match(FRACTION_HOUR);if(!t)return null;const e=FRACTION_HOUR_PARTS[t[1].toLowerCase()];if(!e)return null;const{num:s,den:o}=e;if(60%o!==0)return null;const r=60/o,a=r*s;if(a!==Number(n.correctAnswer))return null;const i=t[0].replace(/ = __ minutes$/i,"").toLowerCase(),l=m=>({type:"equation_display",terms:m}),u=o===2?"half":o===4?"quarter":`${o}th`,d=s===1?{visual_data:l([capitalised(i),"=",String(a),"minutes"]),animation:"write_answer",caption:`${capitalised(i)} = ${a} minutes.`,spoken:`We need just one of those parts, and one part is ${numberToWordsLong(a)} minutes. So ${i} is ${numberToWordsLong(a)} minutes.`}:{visual_data:l([String(r),"×",String(s),"=",String(a)]),animation:"write_answer",caption:`${s} parts of ${r}: ${r} × ${s} = ${a} minutes.`,spoken:`We take ${numberToWordsLong(s)} of those parts — ${numberToWordsLong(r)} times ${numberToWordsLong(s)} is ${numberToWordsLong(a)}. So ${i} is ${numberToWordsLong(a)} minutes.`};return buildExample({generatorId:this.id,question:n,title:`How many minutes is ${i}?`,computed:{one:1,num:s,den:o,base:60,onePart:r,answer:a},steps:[{visual_data:l(["1","hour","=","60","minutes"]),animation:"show_base_fact",caption:"1 hour = 60 minutes.",spoken:"Every hour is sixty minutes. That is the fact every hour-fraction is built from."},{visual_data:{type:"number_line",min:0,max:60,step:r,marked:Array.from({length:o+1},(m,h)=>h*r),hops:Array.from({length:o},(m,h)=>({from:h*r,to:(h+1)*r,label:`${r} min`})),unknown:null},animation:"write_multiplication",caption:`Split the hour into ${o}: 60 ÷ ${o} = ${r}.`,spoken:`We split the sixty minutes into ${numberToWordsLong(o)} equal parts. Each ${u} of an hour is ${numberToWordsLong(r)} minutes.`},d]})}},eq$a=n=>({type:"equation_display",terms:n}
71),SECONDS_CLAIM=/^(\w+) says there are (\d+) seconds in a minute\. Is (?:he|she) right\?$/,secondsInMinuteClaim={id:"MEA.P6.M04.seconds-claim.v1",microSkillId:"MEA.P6.M04",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(SECONDS_CLAIM);if(!t)return null;const[,e,s]=t,o=Number(s),r=o===60,a=n.correctAnswer.trim();return r&&!isAffirmative(a)||!r&&!isNegative(a)?null:buildExample({generatorId:this.id,question:n,title:`Is it really ${o} seconds in a minute?`,computed:{claimed:o,one:1,sixty:60,answer:60},steps:[{visual_data:eq$a(["1","minute","=","__","seconds"]),animation:"show_base_fact",caption:"Before trusting the claim, let's check.",spoken:"Before trusting the claim, let's think about how a clock counts seconds within one minute."},{visual_data:eq$a(["1","minute","=","60","seconds"]),animation:"write_multiplication",caption:"1 minute = 60 seconds.",spoken:"Every minute is sixty seconds — that fact never changes, however the claim is worded."},{visual_data:eq$a([String(o),"vs","60"]),animation:"write_answer",caption:`${o} vs 60 — ${e} is ${r?"right":"wrong"}.`,spoken:`Comparing the claim to the true fact, ${e} is ${r?"right":"wrong"}.`}]})}},WHOLE_HOURS=/^A (.+?) lasts (\d+) minutes\. How many whole hours is that\?$/,wholeHours={id:"MEA.P6.M04.whole-hours.v1",microSkillId:"MEA.P6.M04",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(WHOLE_HOURS);if(!t)return null;const e=t[1],s=Number(t[2]);if(s<60||s>600)return null;const o=Math.floor(s/60);if(o<1||o>10||o!==answerInt(n))return null;const r=Array.from({length:o},(d,m)=>(m+1)*60),a=o===1?"sixty":"sixties",i=o===1?"hour":"hours",l=o<=4?`${r.join(", ")} — ${o} ${a} fit, so ${o} whole ${i}.`:`Counting in 60s up to ${s}: ${o} sixties fit, so ${o} hours.`,u=`Counting up in sixties — ${r.slice(0,Math.min(3,o)).map(d=>numberToWordsLong(d)).join(", ")}${o>3?", and so on":""} — ${numberToWords(o)} ${a} fit inside, so that's ${numberToWords(o)} whole ${i}.`;return buildExample({generatorId:this.id,question:n,title:`${s} minutes — how many whole hours?`,computed:Object.fromEntries([["minutes",s],["sixty",60],["hours",o],["answer",o],...r.map((d,m)=>[`multiple${m}`,d])]),steps:[{visual_data:eq$a(["1","hour","=","60","minutes"]),animation:"show_base_fact",caption:"Every hour is 60 minutes.",spoken:`To find whole hours in the ${e}'s length, we think about how many groups of sixty minutes fit inside it.`},{visual_data:eq$a([String(s),"minutes","÷","60","=","?"]),animation:"write_multiplication",caption:`${s} minutes ÷ 60.`,spoken:"We divide the total minutes by sixty to find how many whole hours fit in."},{visual_data:eq$a([String(s),"÷","60","=",String(o)]),animation:"write_answer",caption:l,spoken:u}]})}},SECONDS_VS_MINUTES=/^Which is longer: (\d+) seconds or (\d+) minutes\?$/,secondsVsMinutes={id:"MEA.P6.M04.seconds-vs-minutes.v1",microSkillId:"MEA.P6.M04",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(SECONDS_VS_MINUTES);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>600||s<1||s>10)return null;const o=s*60;if(e===o)return null;const r=`${e} seconds`,a=`${s} minutes`,i=e>o?r:a;return i!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:`Which is longer: ${r} or ${a}?`,computed:{seconds:e,minutes:s,minutesInS:o,one:1,sixty:60,answer:Math.max(e,o)},steps:[{visual_data:eq$a([r]),animation:"show_base_fact",caption:`${r} stays as it is.`,spoken:`To compare two times, we put them in the same unit. ${capitalised(numberToWordsLong(e))} seconds is already in seconds, so it stays as it is.`},{visual_data:eq$a([String(s),"×","60","=",String(o)]),animation:"write_multiplication",caption:`1 min = 60 s, so ${s} × 60 = ${o} s.`,spoken:`One minute is sixty seconds, so ${numberToWords(s)} minutes is ${numberToWords(s)} times sixty, which is ${numberToWordsLong(o)} seconds.`},{visual_data:eq$a([String(e),"seconds","vs",String(o),"seconds"]),animation:"write_answer",caption:`${e} vs ${o} — ${i} is longer.`,spoken:`Now both are in seconds, so we compare directly. ${capitalised(numberToWordsLong(Math.max(e,o)))}
71 seconds is more than ${numberToWordsLong(Math.min(e,o))} seconds, so ${digitsToWords(i)} is the longer time.`}]})}},MINUTES_PLUS_SECONDS=/^A (.+?) is (\d+) minutes? long, plus another (\d+) seconds\. How many seconds does the \1 last altogether\?$/,minutesPlusSeconds={id:"MEA.P6.M04.minutes-plus-seconds.v1",microSkillId:"MEA.P6.M04",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(MINUTES_PLUS_SECONDS);if(!t)return null;const e=t[1],s=Number(t[2]),o=Number(t[3]);if(s<1||s>10||o<1||o>59)return null;const r=s*60,a=r+o;return a!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${s} min + ${o} s — how many seconds altogether?`,computed:{minutes:s,extraSeconds:o,minutesInS:r,one:1,sixty:60,total:a,answer:a},steps:[{visual_data:eq$a([String(s),"minutes","=",String(r),"seconds"]),animation:"show_base_fact",caption:`1 min = 60 s, so ${s} × 60 = ${r} s.`,spoken:`One minute is sixty seconds, so to find the ${e}'s length we change ${numberToWords(s)} minutes into ${numberToWordsLong(r)} seconds first.`},{visual_data:eq$a([String(r),"seconds","+",String(o),"seconds","=","?"]),animation:"write_multiplication",caption:`${r} + ${o} more seconds.`,spoken:`Then we add on the extra ${numberToWordsLong(o)} seconds.`},{visual_data:eq$a([String(r),"+",String(o),"=",String(a)]),animation:"write_answer",caption:`${r} + ${o} = ${a} seconds.`,spoken:`Adding them together gives ${numberToWordsLong(a)} seconds altogether.`}]})}},MEA_P6_M04_GENERATORS=[timeConversion,hourInMinutesFact,fractionalHour,secondsInMinuteClaim,wholeHours,secondsVsMinutes,minutesPlusSeconds];function placeWord$2(n){return n===100?"hundred":n===10?"ten":"one"}function stepNoun(n){return n===1?"step":"steps"}function placePhrase(n,t){const e=placeWord$2(t);return`${n} ${n===1?e:`${e}s`}`}function tickHops(n,t,e){return Array.from({length:e},(s,o)=>({from:n+o*t,to:n+(o+1)*t,label:`+${t}`}))}const npvP5M03={id:"NPV.P5.M03.number-line-read.v1",microSkillId:"NPV.P5.M03",forms:["number_line_location","number_line_identify"],generate(n){const t=n.visualData;if(!t||t.type!=="number_line")return null;const{min:e,max:s,unknown:o,marked:r}=t;if(!Number.isInteger(e)||!Number.isInteger(s)||s<=e||o==null||!Number.isInteger(o)||o<=e||o>s)return null;const a=s-e;let i;if(a===100)i=10;else if(a===1e3)i=100;else return null;if((o-e)%i!==0)return null;const l=a/i;if(l<2||l>20||(o-e)/i<=0)return null;const d=r??[];for(const x of d)if(!Number.isInteger(x)||x<e||x>s||(x-e)%i!==0)return null;if(n.mathematicalForm==="number_line_identify"){if(n.correctAnswer.trim()!==String(o))return null}else if(n.mathematicalForm==="number_line_location"){if(answerInt(n)!==o)return null}else return null;if(a===1e3&&!d.includes(o)){const x=Math.max(e,...d.filter(E=>E<o)),y=Math.min(s,...d.filter(E=>E>o));if(o*2===x+y){const E=(y-x)/2,k=[{from:x,to:o,label:`+${E}`},{from:o,to:y,label:`+${E}`}];return buildExample({generatorId:this.id,question:n,title:`Find the point halfway between ${x} and ${y}.`,computed:{min:e,max:s,lower:x,upper:y,halfGap:E,unknown:o,answer:o},steps:[{visual_data:{type:"number_line",min:x,max:y,step:E,marked:[x,y],hops:[],unknown:null},animation:"show_start",caption:`The arrow is halfway between ${x} and ${y}.`,spoken:`Find the labelled numbers on either side: ${numberToWordsLong(x)} and ${numberToWordsLong(y)}. The arrow is exactly halfway between them.`},{visual_data:{type:"number_line",min:x,max:y,step:E,marked:[x,y],hops:k,unknown:null},animation:"count_on",caption:`Two equal jumps of ${E}.`,spoken:`Split the gap into two equal jumps. Each jump is ${numberToWordsLong(E)}. Add one jump to ${numberToWordsLong(x)} to reach halfway.`},{visual_data:{type:"number_line",min:x,max:y,step:E,marked:[x,y],hops:k,unknown:o},animation:"land",caption:`${x} + ${E} = ${o}.`,spoken:`${capitalised(numberToWordsLong(x))} plus ${numberToWordsLong(E)} makes ${numberToWordsLong(o)}. So the arrow points to ${numberToWordsLong(o)}.`}]})}}const m=normaliseStem(n.stem).match(/^This number line runs from (\d+) to (\d+)\. The landmark (\d+) is marked\. The arrow points to a number that is (\d+) more than (\d+)\. What number does the arrow point to\?$/),h=m?Number(m[3]):e;if(m&&(Number(m[1])!==e||Number(m[2])!==s||Number(m[5])!==h||h+Number(m[4])!==o||h<e||h>=o))return null;const f=(o-h)/i;if(!Number.isInteger(f))return null;const g=tickHops(h,i,f),b=placeWord$2(i);return buildExample({generatorId:this.id,question:n,title:`Where does the arrow point between ${e} and ${s}?`,computed:{min:e,max:s,step:i,unknown:o,start:h,n:f,answer:o},steps:[{visual_data:{type:"number_line",min:e,max:s,step:i,marked:d,hops:[],unknown:null},animation:"show_start",caption:`${e} to ${s}. Each step is ${i}.`,spoken:`This number line runs from ${numberToWordsLong(e)} to ${numberToWordsLong(s)}
71. Each step along it is worth ${numberToWordsLong(i)}.`},{visual_data:{type:"number_line",min:e,max:s,step:i,marked:d,hops:g,unknown:null},animation:"count_on",caption:`Count on: ${f} ${stepNoun(f)} of ${i} — ${placePhrase(f,i)}.`,spoken:`To find the arrow, we count on in ${b}s from ${numberToWordsLong(h)}, one ${b} at a time. That is ${numberToWordsLong(f)} ${stepNoun(f)}.`},{visual_data:{type:"number_line",min:e,max:s,step:i,marked:d,hops:g,unknown:o},animation:"land",caption:`${o} is ${placePhrase(f,i)} after ${h}.`,spoken:`${capitalised(numberToWordsLong(f))} ${stepNoun(f)} of ${numberToWordsLong(i)} lands us at ${numberToWordsLong(o)}. So the arrow points to ${numberToWordsLong(o)}.`}]})}},CLAIM$7=/^(\w+) says the arrow on this number line is pointing to (\d+)\. Is (?:he|she) right\?$/,npvP5M03Claim={id:"NPV.P5.M03.claim-check.v1",microSkillId:"NPV.P5.M03",forms:["context_reasoning_judgement"],generate(n){const t=normaliseStem(n.stem).match(CLAIM$7);if(!t)return null;const[,e,s]=t,o=Number(s),r=n.visualData;if(!r||r.type!=="number_line")return null;const{min:a,max:i,unknown:l}=r;if(!Number.isInteger(a)||!Number.isInteger(i)||i<=a||l==null||!Number.isInteger(l)||l<=a||l>i||i-a!==100)return null;const d=10;if((l-a)%d!==0)return null;const m=(l-a)/d;if(m<=0)return null;const h=o===l,f=n.correctAnswer.trim();if(h&&!isAffirmative(f)||!h&&!isNegative(f))return null;const g=tickHops(a,d,m);return buildExample({generatorId:this.id,question:n,title:`Does the arrow really point to ${o}?`,computed:{min:a,max:i,step:d,unknown:l,n:m,answer:l},steps:[{visual_data:{type:"number_line",min:a,max:i,step:d,marked:[],hops:[],unknown:null},animation:"show_start",caption:`This line runs from ${a} to ${i}.`,spoken:`Before trusting the claim, let's count it out ourselves. This line runs from ${numberToWordsLong(a)} to ${numberToWordsLong(i)}.`},{visual_data:{type:"number_line",min:a,max:i,step:d,marked:[],hops:g,unknown:l},animation:"count_on",caption:`Count on ${placePhrase(m,d)} — the arrow really points to ${l}.`,spoken:`Counting on ${numberToWordsLong(m)} ${stepNoun(m)} of ${numberToWordsLong(d)} from ${numberToWordsLong(a)}, one ${placeWord$2(d)} at a time, lands the arrow at ${numberToWordsLong(l)}.`},{visual_data:{type:"number_line",min:a,max:i,step:d,marked:[],hops:g,unknown:l},animation:"land",caption:`${o} vs ${l} — ${e} is ${h?"right":"wrong"}.`,spoken:`${e} said ${numberToWordsLong(o)}, and the arrow is really at ${numberToWordsLong(l)}. So ${e} is ${h?"right":"wrong"}.`}]})}},OUT_OF_RANGE=/^A number line runs from (\d+) to (\d+)\. Which (?:of these )?numbers? would NOT (?:be marked on it|fit on this line): (\d+), (\d+), or (\d+)\?$/,npvP5M03OutOfRange={id:"NPV.P5.M03.out-of-range.v1",microSkillId:"NPV.P5.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(OUT_OF_RANGE);if(!t)return null;const[,e,s,o,r,a]=t,i=Number(e),l=Number(s),u=[Number(o),Number(r),Number(a)],d=n.visualData;if(!d||d.type!=="number_line"||d.min!==i||d.max!==l||l<=i)return null;const m=u.filter(x=>x<i||x>l);if(m.length!==1)return null;const h=m[0];if(String(h)!==n.correctAnswer.trim())return null;const f=u.filter(x=>x>=i&&x<=l),g=l-i,b=[2,5,10,25,50,100].find(x=>g%x===0&&g/x<=20&&f.every(y=>(y-i)%x===0));return b?buildExample({generatorId:this.id,question:n,title:`Which does NOT fit on a line from ${i} to ${l}?`,computed:{min:i,max:l,winner:h,answer:h},steps:[{visual_data:{type:"number_line",min:i,max:l,step:b,marked:[],hops:[],unknown:null},animation:"show_start",caption:`This line covers ${i} to ${l}.`,spoken:`This number line only covers values from ${numberToWordsLong(i)} up to ${numberToWordsLong(l)}.`},{visual_data:{type:"number_line",min:i,max:l,step:b,marked:f,hops:[],unknown:null},animation:"compare_distance",caption:`${f.join(" and ")} both fit on this line.`,spoken:"Two of the three numbers sit inside that range, so they belong on the line."},{visual_data:{type:"number_line",min:i,max:l,step:b,marked:f,hops:[{from:h>l?l:i,to:h,label:`${h}`}],unknown:null},animation:"land",caption:`${h} is outside ${i}–${l} — it wouldn't fit.`,spoken:`${capitalised(numberToWordsLong(h))} falls outside the line's range, so it is the one that would not be marked on it.`}]}):null}},NPV_P5_M03_GENERATORS=[npvP5M03,npvP5M03Claim,npvP5M03OutOfRange];function tensPhrase(n){return`${numberToWords(n)} ${n===1?"ten":"tens"}`}const CLAIM$6=/^(\w+) says (\d+) is greater than (\d+)\. Is (?:he|she) right\?$/,CLASS_COMPARE=/^(\w+ \d+) raised (\d+) pence for charity and (\w+ \d+) raised (\d+) pence\. Which class raised more money\?$/;function hundredsTieTensDecide(n,t){if(!Number.isInteger(n)||!Number.isInteger(t)||n<100||n>999||t<100||t>999||n===t)return null;const e=Math.floor(n/100),s=Math.floor(t/100);if(e!==s)return null;const o=Math.floor(n/10)%10,r=Math.floor(t/10)%10;if(o===r)return null;const a=Math.max(n,t),i=Math.min(n,t);
71return{hL:e,symbol:n<t?"<":">",greater:a,smaller:i,gTens:Math.floor(a/10)%10,sTens:Math.floor(i/10)%10}}const npvP5M07={id:"NPV.P5.M07.hundreds-tie-tens-decide.v1",microSkillId:"NPV.P5.M07",forms:["comparison","comparison_statement","context_reasoning_judgement","context_calculation"],generate(n){const t=normaliseStem(n.stem),e=n.correctAnswer.trim(),s=t.match(CLAIM$6);if(s){const[,g,b,x]=s,y=Number(b),E=Number(x),k=hundredsTieTensDecide(y,E);if(!k)return null;const{hL:T,symbol:A,greater:N,gTens:S,sTens:M}=k,L=y>E;return L&&!isAffirmative(e)||!L&&!isNegative(e)?null:buildExample({generatorId:this.id,question:n,title:`Is it true that ${y} is greater than ${E}?`,computed:{left:y,right:E,greater:N,hundreds:T,greaterTens:S,smallerTens:M,answer:N},steps:[{visual_data:{type:"comparison",left:y,right:E,symbol:null},animation:"highlight_hundreds",caption:"Start on the left: hundreds first.",spoken:`Before trusting the claim, let's compare place by place. The hundreds light up first — both are ${numberToWords(T)}, equal, so no decision yet.`},{visual_data:{type:"comparison",left:y,right:E,symbol:null},animation:"highlight_tens",caption:"Hundreds match — the tens decide.",spoken:`Now the tens light up: ${tensPhrase(S)} against ${tensPhrase(M)}. ${capitalised(tensPhrase(S))} beats ${tensPhrase(M)}.`},{visual_data:{type:"comparison",left:y,right:E,symbol:A},animation:"highlight_tens",caption:`${y} ${A} ${E} — ${g} is ${L?"right":"wrong"}.`,spoken:`The tens decide it, so ${numberToWordsLong(N)} is really the bigger one. That means ${g} is ${L?"right":"wrong"}.`}]})}const o=t.match(CLASS_COMPARE);if(o){const[,g,b,x,y]=o,E=Number(b),k=Number(y),T=hundredsTieTensDecide(E,k);if(!T)return null;const{hL:A,symbol:N,greater:S,gTens:M,sTens:L}=T,P=E>k?g:x;return e!==P?null:buildExample({generatorId:this.id,question:n,title:`Who raised more: ${g} or ${x}?`,computed:{left:E,right:k,greater:S,hundreds:A,greaterTens:M,smallerTens:L,answer:S},steps:[{visual_data:{type:"comparison",left:E,right:k,symbol:null},animation:"highlight_hundreds",caption:"Start on the left: hundreds first.",spoken:`We compare place by place, starting on the left. The hundreds light up first — both are ${numberToWords(A)}, equal, so no decision yet.`},{visual_data:{type:"comparison",left:E,right:k,symbol:null},animation:"highlight_tens",caption:"Hundreds match — the tens decide.",spoken:`Now the tens light up: ${tensPhrase(M)} against ${tensPhrase(L)}. ${capitalised(tensPhrase(M))} beats ${tensPhrase(L)}.`},{visual_data:{type:"comparison",left:E,right:k,symbol:E>k?">":"<"},animation:"highlight_tens",caption:`${P} raised more.`,spoken:`${digitsToWords(P)} raised the bigger amount — the tens made the difference.`}]})}const r=n.visualData;if(!r||r.type!=="comparison")return null;const{left:a,right:i}=r,l=hundredsTieTensDecide(a,i);if(!l)return null;const{hL:u,symbol:d,greater:m,gTens:h,sTens:f}=l;if(/symbol/i.test(t)){if(e!==d)return null}else if(/greater/i.test(t)){if(e!==String(m))return null}else return null;return buildExample({generatorId:this.id,question:n,title:`Which is bigger, ${a} or ${i}?`,computed:{left:a,right:i,greater:m,hundreds:u,greaterTens:h,smallerTens:f,answer:m},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"highlight_hundreds",caption:"Start on the left: hundreds first.",spoken:`${capitalised(numberToWordsLong(a))}, and ${numberToWordsLong(i)}. We compare place by place, starting on the left. The hundreds light up first — both are ${numberToWords(u)}, equal, so no decision yet.`},{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"highlight_tens",caption:"Hundreds match — the tens decide.",spoken:`Now the tens light up: ${tensPhrase(h)} against ${tensPhrase(f)}. ${capitalised(tensPhrase(h))} beats ${tensPhrase(f)}, so the decision is made right here.`},{visual_data:{type:"comparison",left:a,right:i,symbol:d},animation:"highlight_tens",caption:`${a} ${d} ${i} — the tens decided it.`,spoken:`${capitalised(tensPhrase(h))} beats ${tensPhrase(f)}, so ${numberToWordsLong(m)} is bigger. We never even needed to look at the ones.`}]})}};function optionInts(n){const e=(n.options??[]).map(s=>Number(String(s).trim()));return e.length!==3||!e.every(s=>Number.isInteger(s)&&s>=100&&s<=999)?null:e}const npvP5M07PickExtreme={id:"NPV.P5.M07.pick-extreme.v1",microSkillId:"NPV.P5.M07",forms:["comparison"],generate(n){const t=optionInts(n);if(!t)return null;const e=normaliseStem(n.stem).toLowerCase(),s=/\b(largest|biggest|greatest|most)\b/.test(e),o=/\b(smallest|least|fewest|lowest)\b/.test(e);
71if(s===o)return null;const r=s?Math.max(...t):Math.min(...t);if(t.filter(h=>h===r).length!==1||String(r)!==n.correctAnswer.trim())return null;const a=t.map(h=>Math.floor(h/100)),i=Math.floor(r/100);if(!(s?a.every(h=>h<=i)&&a.filter(h=>h===i).length===1:a.every(h=>h>=i)&&a.filter(h=>h===i).length===1))return null;const u=s?"greatest":"smallest",d=t.indexOf(r),m=t.map(()=>0);return buildExample({generatorId:this.id,question:n,title:`Which is the ${u}: ${t.join(", ")}?`,computed:{n1:t[0],n2:t[1],n3:t[2],answer:r},steps:[{visual_data:{type:"sequence",values:t,step:1,hide_hop:!0},animation:"highlight_pattern",caption:"Compare the hundreds digit of each.",spoken:`Here are three numbers. When they are three digits long, the hundreds digit decides the ${u} first.`},{visual_data:{type:"sequence",values:t,highlight_digits:m,step:1,hide_hop:!0},animation:"highlight_pattern",caption:"One hundreds digit stands out.",spoken:`One of the hundreds digits is ${s?"bigger":"smaller"} than the other two.`},{visual_data:{type:"sequence",values:t,highlight_digits:m,step:1,hide_hop:!0,emphasis:d},animation:"write_in",caption:`${r} is the ${u}.`,spoken:`${capitalised(numberToWordsLong(r))} is the ${u} — its hundreds digit settled it.`}]})}};function integersFromDragOrder(n){if(!n||n.type!=="drag_order")return null;const t=n.numbers;if(!Array.isArray(t)||t.length<2||t.length>4)return null;const e=t.filter(s=>typeof s=="number");return e.length!==t.length||!e.every(s=>Number.isInteger(s)&&s>=100&&s<=999)?null:e}const npvP5M07OrderIntegers={id:"NPV.P5.M07.order-sequence.v1",microSkillId:"NPV.P5.M07",forms:["comparison_statement"],generate(n){const t=n.visualData,e=integersFromDragOrder(t);if(!e||!t)return null;const s=t.direction;if(s!=="asc"&&s!=="desc"||new Set(e).size!==e.length)return null;const o=[...e].sort((y,E)=>s==="asc"?y-E:E-y);if(o.map(String).join(",")!==n.correctAnswer.trim())return null;const r=s==="asc"?"smallest":"largest",a=o.map(y=>numberToWordsLong(y)).join(", "),i={answer:o[0]};o.forEach((y,E)=>i[`v${E}`]=y);const l=e.map(y=>Math.floor(y/100)),u=new Map;l.forEach((y,E)=>u.set(y,[...u.get(y)??[],E]));const d=[...u.values()].filter(y=>y.length>1);if(d.some(y=>y.length>2))return null;const m=d.length===1?d[0]:d.length===0?[]:null;if(m===null)return null;const h=m.map(y=>e[y]).sort((y,E)=>s==="asc"?y-E:E-y),f=h.map(y=>Math.floor(y/10)%10);if(f.length===2&&f[0]===f[1])return null;f.forEach((y,E)=>i[`tens${E}`]=y);const g=e.map(()=>0),b=s==="asc"?"<":">",x=m.length===2?{visual_data:{type:"sequence",values:[...e],highlight_digits:e.map((y,E)=>m.includes(E)?1:null),step:1,hide_hop:!0},animation:"highlight_pattern",caption:`${f[0]} ${f[0]===1?"ten":"tens"} ${b} ${f[1]} ${f[1]===1?"ten":"tens"}, so ${h[0]} ${b} ${h[1]}.`,spoken:`Two numbers share the same hundreds digit, so we compare their tens. ${capitalised(tensPhrase(f[0]))} is ${s==="asc"?"less":"greater"} than ${tensPhrase(f[1])}. So ${numberToWordsLong(h[0])} comes before ${numberToWordsLong(h[1])} in ${s==="asc"?"smallest":"largest"}-first order.`}:{visual_data:{type:"sequence",values:[...e],highlight_digits:g,step:1,hide_hop:!0},animation:"highlight_pattern",caption:"The hundreds digit alone puts them in order.",spoken:"Every hundreds digit is different, so that digit alone tells us the order."};return buildExample({generatorId:this.id,question:n,title:`Order these numbers: ${r} first`,computed:i,steps:[{visual_data:{type:"sequence",values:[...e],highlight_digits:g,step:1,hide_hop:!0},animation:"highlight_pattern",caption:"Compare the hundreds digit of each.",spoken:"Here are the numbers, not yet in order. We compare place by place, starting with the hundreds."},x,{visual_data:{type:"sequence",values:[...o],highlight_digits:o.map(y=>h.includes(y)?1:0),step:1,hide_hop:!0},animation:"write_in",caption:`Order: ${o.join(", ")}`,spoken:`Reading them off in order gives: ${a}.`}]})}},NPV_P5_M07_GENERATORS=[npvP5M07,npvP5M07PickExtreme,npvP5M07OrderIntegers],STEM$2=/^There are (\d+) .+\. How many times as many .+ are here\?$/,timesAsMany={id:"RP.P1.M01.times-as-many.v1",microSkillId:"RP.P1.M01",forms:["comparison_statement"],generate(n){const t=matchInts(n.stem,STEM$2);if(!t)return null;const[e]=t,s=n.visualData;if(!s||s.type!=="objects_grid"||s.group_size!==e||e<2||e>10)return null;const o=s.count;if(!Number.isInteger(o)||o<=e||o%e!==0)return null;const r=o/e;if(r<2||r>10)return null;const a=`${r} times as many`;if(n.correctAnswer.trim().toLowerCase()!==a.toLowerCase())return null;const i=s.emoji,l={type:"objects_grid",emoji:i,count:e,group_size:e},u={type:"objects_grid",emoji:i,count:o,group_size:e,hide_subtotals:!0,hide_total:!0},d={type:"objects_grid",emoji:i,count:o,group_size:e};return buildExample({generatorId:this.id,question:n,title:"How many times as many are there?",computed:{base:e,multiplier:r,total:o,answer:r},steps:[{visual_data:l,animation:"show_unit",caption:`${e} makes one group.`,spoken:`${capitalised(numberToWordsLong(e))} counters make one group — a unit we can compare against.`},{visual_data:u,animation:"organise_copies",caption:"Copy the group, again and again.",spoken:"Now more counters arrive. They arrange into groups the very same size as our first group — we build copy after copy until every one is placed."},{visual_data:d,animation:"count_copies",caption:`${o} is ${r} times as many as ${e}.`,spoken:`We counted ${numberToWordsLong(r)} copies of ${numberToWordsLong(e)}, which makes ${numberToWordsLong(o)}. So ${numberToWordsLong(o)} is ${numberToWordsLong(r)} times as many as ${numberToWordsLong(e)} — we counted copies of the group, not single counters one by one.`}]})}},CLAIM$5=/^(\w+) says (\d+) \w+ is (\d+) times as many as (\d+) \w+\. Is (?:he|she) right\?$/,timesAsManyClaimCheck={id:"RP.P1.M01.claim-check.v1",microSkillId:"RP.P1.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(CLAIM$5);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r);
71if(l<2||l>10||a<=l||a%l!==0)return null;const u=a/l;if(u<2||u>10)return null;const d=u===i,m=n.correctAnswer.trim();if(d&&!isAffirmative(m)||!d&&!isNegative(m))return null;const h="🔵",f={type:"objects_grid",emoji:h,count:l,group_size:l},g={type:"objects_grid",emoji:h,count:a,group_size:l,hide_subtotals:!0,hide_total:!0},b={type:"objects_grid",emoji:h,count:a,group_size:l};return buildExample({generatorId:this.id,question:n,title:`Is ${a} really ${i} times as many as ${l}?`,computed:{base:l,total:a,real:u,claimed:i,answer:u},steps:[{visual_data:f,animation:"show_unit",caption:`${l} makes one group.`,spoken:`Before trusting the claim, let's check for ourselves. ${capitalised(numberToWordsLong(l))} counters make one group.`},{visual_data:g,animation:"organise_copies",caption:"Copy the group, again and again.",spoken:"More counters arrive, arranged into groups the very same size as our first group."},{visual_data:b,animation:"count_copies",caption:`${a} is really ${u} times ${l} — ${e} is ${d?"right":"wrong"}.`,spoken:`Counting the copies, ${numberToWordsLong(a)} is really ${numberToWordsLong(u)} times ${numberToWordsLong(l)}. So ${e} is ${d?"right":"wrong"}.`}]})}},STEM_NO_VISUAL=/^There are (\d+) \w+ in one jar and (\d+) \w+ in another jar\. How many times as many \w+ are in the second jar\?$/,timesAsManyNoVisual={id:"RP.P1.M01.times-as-many-stem.v1",microSkillId:"RP.P1.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(STEM_NO_VISUAL);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<2||e>10||s<=e||s%e!==0)return null;const o=s/e;if(o<2||o>10)return null;const r=`${o} times as many`;if(n.correctAnswer.trim().toLowerCase()!==r.toLowerCase())return null;const a="🔵",i={type:"objects_grid",emoji:a,count:e,group_size:e},l={type:"objects_grid",emoji:a,count:s,group_size:e,hide_subtotals:!0,hide_total:!0},u={type:"objects_grid",emoji:a,count:s,group_size:e};return buildExample({generatorId:this.id,question:n,title:"How many times as many are there?",computed:{base:e,multiplier:o,total:s,answer:o},steps:[{visual_data:i,animation:"show_unit",caption:`${e} makes one group.`,spoken:`${capitalised(numberToWordsLong(e))} counters make one group — a unit we can compare against.`},{visual_data:l,animation:"organise_copies",caption:"Copy the group, again and again.",spoken:"Now more counters arrive. They arrange into groups the very same size as our first group."},{visual_data:u,animation:"count_copies",caption:`${s} is ${o} times as many as ${e}.`,spoken:`We counted ${numberToWordsLong(o)} copies of ${numberToWordsLong(e)}, which makes ${numberToWordsLong(s)}. So ${numberToWordsLong(s)} is ${numberToWordsLong(o)} times as many as ${numberToWordsLong(e)}.`}]})}},WHICH_SHOWS=/^Which shows (\d+) times as many: (.+?), (.+?), or (.+?)\?$/,GROWS_TO=/^(\d+) (?:grows to|becomes) (\d+)$/,timesAsManyPickExtreme={id:"RP.P1.M01.pick-extreme.v1",microSkillId:"RP.P1.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WHICH_SHOWS);if(!t)return null;const e=Number(t[1]),o=[t[2],t[3],t[4]].map(m=>{const h=m.match(GROWS_TO);return h?{label:m,base:Number(h[1]),target:Number(h[2])}:null});if(o.some(m=>m===null))return null;const r=o,a=r[0].base;if(!r.every(m=>m.base===a)||a<2||a>10||e<2||e>10)return null;const i=a*e,l=r.filter(m=>m.target===i);if(l.length!==1)return null;const u=l[0];if(u.label!==n.correctAnswer.trim()||r.some(m=>m.target%a!==0))return null;const d=r.map(m=>m.target/a);return buildExample({generatorId:this.id,question:n,title:`Which shows ${e} times as many as ${a}?`,computed:{base:a,mult:e,expected:i,answer:i,n1:r[0].target,n2:r[1].target,n3:r[2].target,f1:d[0],f2:d[1],f3:d[2]},steps:r.map((m,h)=>({visual_data:{type:"objects_grid",emoji:"🔵",count:m.target,group_size:a,total_label:`${m.target} ÷ ${a} = ${d[h]}`},animation:"build_rows",caption:`${m.target} ÷ ${a} = ${d[h]} times as many.${h===2?` Choose ${u.label}.`:""}`,spoken:`Count groups of ${numberToWordsLong(a)} in ${numberToWordsLong(m.target)}: there are ${numberToWordsLong(d[h])}, which ${d[h]===e?"matches":"does not match"} ${numberToWordsLong(e)} times as many.${h===2?` Choose ${digitsToWords(u.label)}.`:""}`}))})}},RIBBON=/^A ribbon is (\d+) times as long as another ribbon\. The shorter ribbon is (\d+) cm\. What is the length of both ribbons added together\?$/,ribbonSum={id:"RP.P1.M01.ribbon-sum.v1",microSkillId:"RP.P1.M01",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(RIBBON);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<2||e>10||s<1||s>20)return null;const o=e*s,r=o+s;if(r!==answerInt(n))return null;const a=i=>({type:"ratio_bar",left_label:`${s} cm`,right_label:i.rightLabel,left_parts:1,right_parts:e,...i.withTotal?{total:r}:{}});return buildExample({generatorId:this.id,question:n,title:`Both ribbons together, if one is ${e} times ${s} cm?`,computed:{shorter:s,mult:e,longer:o,total:r,answer:r,unit_parts:1},steps:[{visual_data:a({rightLabel:"?"}),animation:"rb_build",caption:`The shorter ribbon is ${s} cm — that's 1 part.`,spoken:`The shorter ribbon is our starting length: ${numberToWordsLong(s)} centimetres. That is one part.`},{visual_data:a({rightLabel:`${o} cm`}
71),animation:"rb_shade:right",caption:`The longer ribbon is ${e} times that: ${o} cm.`,spoken:`The longer ribbon is ${numberToWordsLong(e)} copies of the shorter length, which makes ${numberToWordsLong(o)} centimetres.`},{visual_data:a({rightLabel:`${o} cm`,withTotal:!0}),animation:"rb_conclude",caption:`${o} + ${s} = ${r} cm altogether.`,spoken:`Joining both ribbons end to end gives ${numberToWordsLong(r)} centimetres in all.`}]})}},RP_P1_M01_GENERATORS=[timesAsMany,timesAsManyClaimCheck,timesAsManyNoVisual,timesAsManyPickExtreme,ribbonSum],STEM$1=/^One (\w+) (?:has|holds) (\d+) (\w+)\. How many \3 (?:are|is) (?:in|on) (\d+) \w+\?$/i,NOUN_EMOJI={sweets:"🍬",eggs:"🥚",biscuits:"🍪",flowers:"🌸",apples:"🍎",shells:"🐚",strawberries:"🍓",pencils:"✏️",carrots:"🥕",marbles:"🔵",fish:"🐟",stickers:"⭐",croissants:"🥐",doughnuts:"🍩",tomatoes:"🍅",crayons:"🖍️",batteries:"🔋",bottles:"🍾",tulips:"🌷",sunflowers:"🌻",bees:"🐝"};function emojiFromVisual(n){return n&&(n.type==="objects_grid"||n.type==="story")?n.emoji:null}const perGroupTimesGroups={id:"RP.P1.M02.per-group-times-groups.v1",microSkillId:"RP.P1.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(STEM$1);if(!t)return null;const e=Number(t[2]),s=t[3].toLowerCase(),o=Number(t[4]);if(!Number.isInteger(e)||e<1||e>12||!Number.isInteger(o)||o<2||o>10)return null;const r=e*o;if(r>100)return null;const a=Number(n.correctAnswer);if(!Number.isFinite(a)||a!==r)return null;const i=emojiFromVisual(n.visualData)??NOUN_EMOJI[s]??"🔵",l={type:"objects_grid",emoji:i,count:e,group_size:e},u={type:"objects_grid",emoji:i,count:r,group_size:e,hide_subtotals:!0,hide_total:!0},d={type:"objects_grid",emoji:i,count:r,group_size:e};return buildExample({generatorId:this.id,question:n,title:`${o} groups of ${e} — how many altogether?`,computed:{per:e,groups:o,total:r,answer:r},steps:[{visual_data:l,animation:"show_one_bag",caption:`One group holds ${e}.`,spoken:`One group holds ${numberToWordsLong(e)}. That is our starting group.`},{visual_data:u,animation:"duplicate_bags",caption:"Copy the group, again and again.",spoken:"More groups arrive, each one holding the very same amount. We build copy after copy until every group is placed."},{visual_data:d,animation:"duplicate_bags",caption:`${o} groups of ${e} = ${r}.`,spoken:`${capitalised(numberToWordsLong(o))} copies of ${numberToWordsLong(e)} makes ${numberToWordsLong(r)}. Scaling up means copies of the group, not adding groups to items.`}]})}},CLAIM$4=/^(\w+) says (\d+) \w+ of (\d+) \w+ is (\d+) \w+ in total\. Is (?:he|she) right\?$/,perGroupClaimCheck={id:"RP.P1.M02.claim-check.v1",microSkillId:"RP.P1.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(CLAIM$4);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r);if(i<1||i>12||a<2||a>10)return null;const u=a*i;if(u>100)return null;const d=u===l,m=n.correctAnswer.trim();if(d&&!isAffirmative(m)||!d&&!isNegative(m))return null;const h="🔵",f={type:"objects_grid",emoji:h,count:i,group_size:i},g={type:"objects_grid",emoji:h,count:u,group_size:i,hide_subtotals:!0,hide_total:!0},b={type:"objects_grid",emoji:h,count:u,group_size:i};return buildExample({generatorId:this.id,question:n,title:`Is ${a} groups of ${i} really ${l}?`,computed:{groups:a,per:i,real:u,claimed:l,answer:u},steps:[{visual_data:f,animation:"show_one_bag",caption:`One group holds ${i}.`,spoken:`Before trusting the claim, let's check for ourselves. One group holds ${numberToWordsLong(i)}.`},{visual_data:g,animation:"duplicate_bags",caption:"Copy the group, again and again.",spoken:"More groups arrive, each one holding the very same amount."},{visual_data:b,animation:"duplicate_bags",caption:`${a} × ${i} = ${u} — ${e} is ${d?"right":"wrong"}.`,spoken:`${capitalised(numberToWordsLong(a))} copies of ${numberToWordsLong(i)} really makes ${numberToWordsLong(u)}. So ${e} is ${d?"right":"wrong"}.`}]})}},UNIT_RATE=/^Tickets to the .+ cost £(\d+) each\. (\w+) spends £(\d+) on tickets\. How many tickets does (?:he|she|they) buy\?$/,unitRateDivide={id:"RP.P1.M02.unit-rate-divide.v1",microSkillId:"RP.P1.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(UNIT_RATE);if(!t)return null;const e=Number(t[1]),s=Number(t[3]);if(e<1||e>12||s>100||s%e!==0)return null;const o=s/e;if(o<2||o>20||o!==answerInt(n))return null;const r="🪙";return buildExample({generatorId:this.id,question:n,title:`£${s} at £${e} each — how many tickets?`,computed:{per:e,total:s,groups:o,answer:o},steps:[{visual_data:{type:"objects_grid",emoji:r,count:s,group_size:s,hide_subtotals:!0},animation:"show_whole",caption:`£${s} to spend in total.`,spoken:`The total spent is ${numberToWordsLong(s)} pounds. We need to share it into groups of the ticket price.`},{visual_data:{type:"objects_grid",emoji:r,count:s,group_size:e},animation:"share_into_groups",caption:`Share into groups of £${e} — each group buys one ticket.`,spoken:`Each ticket costs ${numberToWordsLong(e)} pounds, so we share the total into groups of that size. Each group of coins buys one ticket.`},{visual_data:{type:"equation_display",terms:[s,"÷",e,"=",o]},animation:"write_answer",caption:`${s} ÷ ${e} = ${o} tickets.`,spoken:`That makes ${numberToWordsLong(o)} tickets in total.`}]})}},WHICH_GREATER=/^Which is (?:greater|the greatest): (.+?), (.+?), or (.+?)\?$/,BAGS_OF=/^(\d+) \w+ of (\d+) \w+$/,pickGreatestProduct={id:"RP.P1.M02.pick-greatest.v1",microSkillId:"RP.P1.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(WHICH_GREATER);if(!t)return null;const e=[t[1],t[2],t[3]],s=e.map(g=>{const b=g.match(BAGS_OF);return b?{label:g,groups:Number(b[1]),per:Number(b[2])}:null});if(s.some(g=>g===null))return null;const o=s;if(!o.every(g=>g.groups>=1&&g.groups<=12&&g.per>=1&&g.per<=12))return null;const r=o.map(g=>g.groups*g.per),a=Math.max(...r);if(r.filter(g=>g===a).length!==1)return null;const i=r.indexOf(a);if(o[i].label!==n.correctAnswer.trim())return null;const l=o[0],u=o[i],d=new Set([0,i]),m=o.map((g,b)=>b).filter(g=>!d.has(g)),h=m.map(g=>`${o[g].groups} × ${o[g].per} = ${r[g]}`).join(", "),f=m.map(g=>`${numberToWordsLong(o[g].groups)} groups of ${numberToWordsLong(o[g].per)} makes ${numberToWordsLong(r[g])}`).join(", and ");return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${e.join(", ")}?`,computed:{t1:r[0],t2:r[1],t3:r[2],answer:a},steps:[{visual_data:{type:"objects_grid",emoji:"🔵",count:r[0],group_size:l.per},animation:"duplicate_bags",caption:`First option: ${l.groups} × ${l.per} = ${r[0]}.`,spoken:`Let's work out the first option: ${numberToWordsLong(l.groups)} groups of ${numberToWordsLong(l.per)} makes ${numberToWordsLong(r[0])}.`},{visual_data:{type:"objects_grid",emoji:"🔵",count:a,group_size:u.per},animation:"duplicate_bags",caption:`${u.groups} × ${u.per} = ${a}.`,spoken:`Now let's work out another option: ${numberToWordsLong(u.groups)} groups of ${numberToWordsLong(u.per)} makes ${numberToWordsLong(a)}.`},{visual_data:{type:"sequence",values:r,step:1,hide_hop:!0,emphasis:i},animation:"write_in",caption:h?`${h} — ${u.label} is the greatest.`:`${u.label} is the greatest.`,spoken:f?`The last option works out as ${f}. Comparing every total, ${numberToWordsLong(a)} is the biggest — that came from ${digitsToWords(u.label)}.`:`Comparing every total, ${numberToWordsLong(a)} is the biggest — that came from ${digitsToWords(u.label)}.`}]})}},RATE_EQUATION=/^\w+ cost (\d+)p each\. \w+ pays (\d+)p in total for some \w+\. How many \w+ did (?:he|she|they) buy\?$/,rateEquationDiv
71ide={id:"RP.P1.M02.rate-equation-divide.v1",microSkillId:"RP.P1.M02",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem).match(RATE_EQUATION);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>12||s>100||s%e!==0)return null;const o=s/e;return o<2||o>20||o!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${s}p at ${e}p each — how many?`,computed:{rate:e,total:s,groups:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[`${e}p`,"×","__","=",`${s}p`]},animation:"pulse_unknown",caption:`Find the missing number: ${e}p × ? = ${s}p.`,spoken:`We know the price of one, ${numberToWordsLong(e)} ${e===1?"penny":"pence"}, and the total paid, ${numberToWordsLong(s)} pence. We need the missing number: how many were bought.`},{visual_data:{type:"objects_grid",emoji:"🪙",count:s,group_size:e},animation:"share_into_groups",caption:`Share ${s}p into groups of ${e}p — each group buys one.`,spoken:"To find how many were bought, we share the total into groups the size of one price. Each group of coins buys one item."},{visual_data:{type:"equation_display",terms:[`${e}p`,"×",o,"=",`${s}p`]},animation:"write_answer",caption:`${e}p × ${o} = ${s}p.`,spoken:`The total makes ${numberToWordsLong(o)} equal groups: ${numberToWordsLong(e)} ${e===1?"penny":"pence"} times ${numberToWordsLong(o)} is ${numberToWordsLong(s)} pence. So ${numberToWordsLong(o)} were bought, and the missing number is ${numberToWordsLong(o)}.`}]})}},RP_P1_M02_GENERATORS=[perGroupTimesGroups,perGroupClaimCheck,unitRateDivide,pickGreatestProduct,rateEquationDivide],Y3_GAP_GENERATORS=[...DS_P2_M03_GENERATORS,...DS_P2_M04_GENERATORS,...DS_P2_M05_GENERATORS,...DS_P3_M01_GENERATORS,...FDP_P5_M01_GENERATORS,...FDP_P5_M02_GENERATORS,...FDP_P5_M03_GENERATORS,...FDP_P6_M07_GENERATORS,...FDP_P5_M04_GENERATORS,...GEO_P4_M02_GENERATORS,...GEO_P5_M01_GENERATORS,...GEO_P5_M02_GENERATORS,...MD_P3_M05_GENERATORS,...MEA_P5_M03_GENERATORS,...MEA_P5_M04_GENERATORS,...MEA_P5_M05_GENERATORS,...MEA_P6_M04_GENERATORS,...NPV_P5_M03_GENERATORS,...NPV_P5_M07_GENERATORS,...RP_P1_M01_GENERATORS,...RP_P1_M02_GENERATORS],PLACES$2=["one","ten","hundred","thousand","ten thousand"],plural$1=n=>`${PLACES$2[n]}s`;function describeColumnWorking(n,t,e){if(![t,e].every(b=>Number.isInteger(b)&&b>=0&&b<=9999)||n==="subtract"&&e>t)throw new Error("Column working needs non-negative whole-number operands up to 9999.");const s=n==="add"?t+e:t-e,o=Math.max(String(t).length,String(e).length,String(s).length),r=Array.from({length:o},(b,x)=>Math.floor(t/10**x)%10),a=Array.from({length:o},(b,x)=>Math.floor(e/10**x)%10),i={top:t,bottom:e,result:s,exchangeSize:10,exchangeCount:1},l=[];let u=0;for(let b=0;b<o;b++){const x=plural$1(b),y=r[b],E=a[b];if(i[`top${b}`]=y,i[`bottom${b}`]=E,n==="add"){const k=u,T=y+E+k,A=T%10;u=Math.floor(T/10),i[`incoming${b}`]=k,i[`sum${b}`]=T,i[`digit${b}`]=A,i[`carry${b}`]=u;const N=`${y} + ${E}${k?` + ${k}`:""} = ${T}`,S=`${numberToWordsLong(y)} add ${numberToWordsLong(E)}${k?` and the carried ${numberToWordsLong(k)}`:""} makes ${numberToWordsLong(T)}`;l.push({caption:`${capitalised(x)}: ${N}.`,spoken:`${capitalised(x)}: ${S}.${u?` Write ${numberToWordsLong(A)} in the ${x} column and carry one ${PLACES$2[b+1]} to the next column.`:""}`,regroup:u>0,throughZero:!1})}else{let k="",T="",A=!1;if(r[b]<E){let M=b+1;for(;M<o&&r[M]===0;)M++;if(M===o)throw new Error("No higher place to exchange.");A=M>b+1,r[M]--;for(let F=M-1;F>b;F--)r[F]=9;r[b]+=10;const L=[],P=[];for(let F=M;F>b;F--)L.push(`1 ${PLACES$2[F]} → 10 ${plural$1(F-1)}`),P.push(`exchange one ${PLACES$2[F]} for ten ${plural$1(F-1)}`);k=`Exchange ${L.join("; ")}. `,T=`${capitalised(x)}: ${numberToWordsLong(y)} is less than ${numberToWordsLong(E)}. ${capitalised(P.join(", then "))}. `}const N=r[b],S=N-E;i[`effective${b}`]=N,i[`digit${b}`]=S,l.push({caption:`${capitalised(x)}: ${N} − ${E} = ${S}.`,spoken:`${T}${T?"Now":capitalised(x)+":"} ${numberToWordsLong(N)} take away ${numberToWordsLong(E)} leaves ${numberToWordsLong(S)} ${S===1?PLACES$2[b]:x}.`,regroup:k!=="",throughZero:A})}}const d=Math.max(0,l.findIndex(b=>b.regroup)),m=Math.max(d,...l.map((b,x)=>b.regroup?x:0)),h=l.slice(0,m+1),f=l.slice(m+1);return{columns:l,computed:i,visual:{type:"column_method",operation:n,top:t,bottom:e,keep_exchange:n==="subtract",focus_column:d},result:s,focusColumn:d,animation:`${n==="add"?"carry_slide":l[d].throughZero?"borrow_through_zero":l[d].regroup?"exchange":"work_column"}
71 show_all_regrouping`,keyCaption:h.map(b=>b.caption).join(" "),keySpoken:h.map(b=>b.spoken).join(" "),restCaption:f.map(b=>b.caption).join(" "),restSpoken:f.map(b=>b.spoken).join(" "),caption:l.map(b=>b.caption).join(" "),spoken:l.map(b=>b.spoken).join(" ")}}const SUB_CLAIM$1=/^.*?(\d+) - (\d+) (?:and gets |= )(\d+)\. Check by working out (\d+) \+ (\d+)\. Is it right\?$/,ADD_CLAIM_SUBTRACT=/^.*?(\d+) \+ (\d+) (?:and gets |books and records )(\d+)\. Check it by subtracting (\d+) from the (?:answer|total)\. Is it right\?$/,ADD_CLAIM_WORKOUT=/^.*?(\d+) \+ (\d+) = (\d+)\. Check by working out (\d+) - (\d+)\. Is it right\?$/;function parseClaim(n){const t=normaliseStem(n),e=t.match(SUB_CLAIM$1);if(e){const[,r,a,i,l,u]=e.map(Number);return l!==i||u!==a?null:{claimOp:"subtract",a:r,b:a,c:i}}const s=t.match(ADD_CLAIM_SUBTRACT);if(s){const[,r,a,i,l]=s.map(Number);return l!==a?null:{claimOp:"add",a:r,b:a,c:i}}const o=t.match(ADD_CLAIM_WORKOUT);if(o){const[,r,a,i,l,u]=o.map(Number);return l!==i||u!==a?null:{claimOp:"add",a:r,b:a,c:i}}return null}const WHICH_CHECK=/^.+ has worked out (\d+) ([+\-]) (\d+) = (\d+)\. Which (?:calculation|subtraction) would check (?:his|her|their) answer\?$/i,whichCheck={id:"AS.P5.M03.which-check.v1",microSkillId:"AS.P5.M03",forms:["check_reasoning_probe"],generate(n){const t=normaliseStem(n.stem).match(WHICH_CHECK);if(!t)return null;const e=Number(t[1]),s=t[2]==="+"?"add":"subtract",o=Number(t[3]),r=Number(t[4]);if([e,o,r].some(g=>g<100||g>999)||(s==="subtract"?e-o:e+o)!==r)return null;const i=s==="subtract"?"add":"subtract",u=`${r} ${i==="add"?"+":"−"} ${o}`;if(i==="subtract"&&r<o)return null;const d=g=>g.replace(/−/g,"-").replace(/\s+/g," ").trim();if(d(n.correctAnswer)!==d(u))return null;const m={type:"column_method",operation:s,top:e,bottom:o},h={type:"column_method",operation:i,top:r,bottom:o},f=`${numberToWordsLong(r)} ${i==="add"?"add":"take away"} ${numberToWordsLong(o)}`;return buildExample({generatorId:this.id,question:n,title:"Which calculation checks the answer?",computed:{a:e,b:o,c:r,answer:e},steps:[{visual_data:m,animation:"show_sum",caption:s==="subtract"?`They worked out ${e} − ${o} = ${r}.`:`They worked out ${e} + ${o} = ${r}.`,spoken:`Someone worked this out and got ${numberToWordsLong(r)}. Which calculation would check it?`},{visual_data:h,animation:"undo_with_inverse",caption:i==="add"?`Undo it — add back: ${u}.`:`Undo it — take away: ${u}.`,spoken:i==="add"?"Every take-away has a partner that undoes it: adding. Add the part they took away back on to their answer.":"Every addition has a partner that undoes it: taking away. Take one part back off their answer."},{visual_data:h,animation:"confirm_result",caption:`${u} = ${e} — back to the start.`,spoken:`${capitalised(f)} lands back on ${numberToWordsLong(e)}, the number they started with. So that is the calculation that checks it.`}]})}},CHECK_EXPECTED_RESULT=/^.*?(\d+)[^.]*\. In the afternoon (\d+) more arrive, making (\d+)[^.]*\. .*?checks this by working out (\d+) [-−] (\d+)\. What should this check give if the total is correct\?$/i;function parseExpectedCheck(n){const e=normaliseStem(n).match(CHECK_EXPECTED_RESULT);if(!e)return null;const[s,o,r,a,i]=e.slice(1).map(Number);return r!==s+o||a!==r||i!==o?null:{start:s,added:o,total:r}}const expectedCheckResult={id:"AS.P5.M03.expected-check.v1",microSkillId:"AS.P5.M03",forms:["check_reasoning_probe"],generate(n){const t=parseExpectedCheck(n.stem);if(!t)return null;const{start:e,added:s,total:o}=t;if([e,s,o].some(i=>i<1||i>999)||e!==answerInt(n))return null;const r={type:"column_method",operation:"add",top:e,bottom:s},a={type:"column_method",operation:"subtract",top:o,bottom:s};return buildExample({generatorId:this.id,question:n,title:`What should ${o} − ${s} give, if the total is right?`,computed:{start:e,added:s,total:o,answer:e},steps:[{visual_data:r,animation:"show_sum",caption:`They added ${e} + ${s} = ${o}.`,spoken:`They added ${numberToWordsLong(e)} and ${numberToWordsLong(s)} to get a total of ${numberToWordsLong(o)}.`},{visual_data:a,animation:"undo_with_inverse",caption:`Undo it — take away: ${o} − ${s}.`,spoken:`Every addition has a partner that undoes it: taking away. Take the part that was added, ${numberToWordsLong(s)}, back off the total.`},{visual_data:a,animation:"confirm_result",caption:`${o} − ${s} = ${e} — back to the start.`,spoken:`${capitalised(numberToWordsLong(o))} take away ${numberToWordsLong(s)} lands back on ${numberToWordsLong(e)}
71. If the total was right, that is exactly what this check should give.`}]})}},WHOSE_CHECK=/^.+ has worked out (\d+) [-−] (\d+) = (\d+)\. To check this, (\w+) suggests working out (\d+) \+ (\d+)\. (\w+) suggests working out (\d+) \+ (\d+)\. Whose check is correctly set up to confirm the answer\?$/i;function parseWhoseCheck(n){const e=normaliseStem(n).match(WHOSE_CHECK);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]),a=e[4],i=Number(e[5]),l=Number(e[6]),u=e[7],d=Number(e[8]),m=Number(e[9]);if(s-o!==r)return null;const h=(b,x)=>b===r&&x===o||b===o&&x===r,f=h(i,l),g=h(d,m);return f===g?null:f?{a:s,b:o,c:r,winner:a,loser:u,loserX:d,loserY:m}:{a:s,b:o,c:r,winner:u,loser:a,loserX:i,loserY:l}}const whoseCheck={id:"AS.P5.M03.whose-check.v1",microSkillId:"AS.P5.M03",forms:["misconception_contrast_probe"],generate(n){const t=parseWhoseCheck(n.stem);if(!t)return null;const{a:e,b:s,c:o,winner:r,loser:a,loserX:i,loserY:l}=t;if([e,s,o].some(h=>h<100||h>999)||n.correctAnswer.trim()!==r)return null;const u=i+l,d={type:"column_method",operation:"subtract",top:e,bottom:s},m={type:"column_method",operation:"add",top:o,bottom:s};return buildExample({generatorId:this.id,question:n,title:`Whose check correctly confirms ${e} − ${s} = ${o}?`,computed:{a:e,b:s,c:o,loserTotal:u},steps:[{visual_data:d,animation:"show_sum",caption:`They worked out ${e} − ${s} = ${o}.`,spoken:`Someone worked out ${numberToWordsLong(e)} take away ${numberToWordsLong(s)}, and got ${numberToWordsLong(o)}.`},{visual_data:m,animation:"undo_with_inverse",caption:`${r}'s check: ${o} + ${s}.`,spoken:`${r} suggests adding the answer back to the part that was subtracted: ${numberToWordsLong(o)} add ${numberToWordsLong(s)}.`},{visual_data:m,animation:"confirm_result",caption:`${o} + ${s} = ${e} — back to the start. ${r} is right.`,spoken:`${capitalised(numberToWordsLong(o))} add ${numberToWordsLong(s)} lands back on ${numberToWordsLong(e)}, the number we started with. ${r}'s check is correctly set up. ${a} suggests adding ${numberToWordsLong(i)} and ${numberToWordsLong(l)} instead, which makes ${numberToWordsLong(u)} — that is not a return to the starting number, so it does not confirm anything.`}]})}},AS_P5_M03_GENERATORS=[{id:"AS.P5.M03.inverse-check.v1",microSkillId:"AS.P5.M03",forms:["check_reasoning_probe"],generate(n){const t=parseClaim(n.stem);if(!t)return null;const{claimOp:e,a:s,b:o,c:r}=t;if(s<100||s>999||o<100||o>999||r<100||r>999)return null;const a=n.correctAnswer.trim();if(!/^(yes|no)$/i.test(a))return null;const i=/^yes$/i.test(a),u=(e==="subtract"?s-o:s+o)===r,d=e==="subtract"?r+o:r-o;if(u!==i||d<0||d>999)return null;const m={type:"column_method",operation:e,top:s,bottom:o},h=e==="subtract"?"add":"subtract";if(h==="subtract"&&r<o)return null;const f=describeColumnWorking(h,r,o),g=e==="subtract"?"take away":"add",b=h==="add"?"add":"take away",x=h==="add"?`Adding back undoes the taking away. If their answer is right, adding the ${numberToWordsLong(o)} back on should take us back to ${numberToWordsLong(s)}.`:`Taking away undoes the adding. If their answer is right, taking the ${numberToWordsLong(o)} back off should take us back to ${numberToWordsLong(s)}.`,y=e==="subtract"?`They worked out ${s} − ${o} and got ${r}.`:`They added ${s} + ${o} and got ${r}.`,E=`${capitalised(numberToWordsLong(r))} ${b} ${numberToWordsLong(o)} is ${numberToWordsLong(d)}`,k=u?`${E}, the number they started with. The check takes us back to the start, so their answer is right. The answer is yes.`:`${E}, not ${numberToWordsLong(s)}. The check does not take us back to the start, so their answer is wrong. The answer is no.`;return buildExample({generatorId:this.id,question:n,title:"How do we check if an answer is right?",computed:{...f.computed,a:s,b:o,c:r,checkResult:d},steps:[{visual_data:m,animation:"show_sum",caption:y,spoken:`Someone worked out ${numberToWordsLong(s)} ${g} ${numberToWordsLong(o)}, and got ${numberToWordsLong(r)}. To be sure it is right, we can check it by undoing the calculation.`},{visual_data:f.visual,animation:f.animation,caption:f.keyCaption,spoken:`${x} ${f.keySpoken}`},{visual_data:f.visual,animation:"confirm_result",caption:`${f.restCaption} ${d} ${u?`matches ${s}: yes.`:`is not ${s}: no.`}`.trim(),spoken:`${f.restSpoken} ${k}`.trim()}]})}},whichCheck,expectedCheckResult,whoseCheck];function solve(n){let t;return n.unknown==="first"?t=n.op==="+"?n.z-n.known:n.z+n.known:t=n.op==="+"?n.z-n.known:n.known-n.z,t>0?t:null}function inverseExpression(n){let t,e,s;return n.unknown==="first"?(t=n.z,n.op==="+"?(e="−",s=n.known):(e="+",s=n.known)):n.op==="+"?(t=n.z,e="−",s=n.known):(t=n.known,e="−",s=n.z),{text:`${t} ${e} ${s}`,terms:["__","=",t,e,s],p:t,sign:e,qv:s}}function equationTerms(n){const t=n.op==="+"?"+":"−";return n.unknown==="first"?["__",t,n.known,"=",n.z]:[n.known,t,"__","=",n.z]}const EQUATION_STEM=/^(\d+|__) ([+\-]) (\d+|__) = (\d+)$/;function fromStemEquation(n){const t=normaliseStem(n).match(EQUATION_STEM);return t?buildEquation(t[1],t[2],t[3],Number(t[4])):null}const CHOICE_A=/^(\d+|__) ([+\-]) (\d+|__) = (\d+)\. Which calculation finds the missing number\?$/,CHOICE_B=/^.* the missing number in (\d+|__) ([+\-]) (\d+|__) = (\d+)\. Which calculation should she use\?$/;function fromChoiceStem(n){const t=normaliseStem(n),e=t.match(CHOICE_A)??t.match(CHOICE_B);return e?buildEquation(e[1],e[2],e[3],Number(e[4])):null}function fromVisualTerms(n){const t=n.visualData;if(!t||t.type!=="equation_display")return null;const e=t.terms;if(!Array.isArray(e)||e.length!==5||e[3]!=="=")return null;const[s,o,r,,a]=e;return typeof a!="number"||o!=="+"&&o!=="-"&&o!=="−"?null:buildEquation(String(s),o==="−"?"-":String(o),String(r),a)}function buildEquation(n,t,e,s){const o=t==="+"?"+":t==="-"||t==="−"?"-":null;if(!o)return null;const r=n==="__"||n==="?";if(r===(e==="__"||e==="?"))return null;if(r){const l=Number(e);return Number.isInteger(l)?{op:o,unknown:"first",known:l,z:s}:null}const i=Number(n);return Number.isInteger(i)?{op:o,unknown:"second",known:i,z:s}:null}const CONTEXT_MORE=/had some .*\. After (\d+) more arrived it held (\d+)\. How many were there before\?$/;function fromContext(n){const t=normaliseStem(n).match(CONTEXT_MORE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);return{op:"+",unknown:"first",known:e,z:s}}const CONTEXT_DONATION=/had some .*\. it then received a donation of £(\d+), bringing the total to £(\d+)\. how much had .+ saved before the donation\?$/i;function fromContextDonation(n){const t=normaliseStem(n).match(CONTEXT_DONATION);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);return{op:"+",unknown:"first",known:e,z:s}}const CONTEXT_REMOVED=/^.+\. After [a-z ]*?(\d+) [a-z ]+?, (\d+) (?:were left|remained|were left loose)\. How many .+\?$/i;function fromContextRemoval(n){const t=normaliseStem(n).match(CONTEXT_REMOVED);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);return{op:"-",unknown:"first",known:e,z:s}}function buildMissingNumberDemo(n,t,e,s){const o=inverseExpression(e),r=describeColumnWorking(o.sign==="−"?"subtract":"add",o.p,o.qv),a=t==="AS.P5.M04.which-inverse.v1",i=e.op==="+"?"Two parts make the whole. Take the known part away from the whole to find the missing part.":e.unknown==="first"?"The starting amount is missing. Add the amount taken away back to what is left.":"The part taken away is missing. Take what is left away from the starting amount.";return buildExample({generatorId:t,question:n,title:"How do we find the missing number?",computed:{...r.computed,known:e.known,whole:e.z,missing:s,answer:s},steps:[{visual_data:{type:"equation_display",terms:equationTerms(e)},animation:"show_equation",caption:`Find the gap using ${o.text}.`,spoken:`${i} We work out ${numberToWordsLong(o.p)} ${o.sign==="−"?"take away":"add"} ${numberToWordsLong(o.qv)}.`},{visual_data:r.visual,animation:r.animation,caption:r.keyCaption,spoken:r.keySpoken},{visual_data:r.visual,animation:"solve_in_columns",caption:`${r.restCaption} Gap = ${s}.`.trim(),spoken:`${r.restSpoken} The missing number is ${numberToWordsLong(s)}.${a?` So choose ${numberToWordsLong(o.p)} ${o.sign==="−"?"take away":"add"} ${numberToWordsLong(o.qv)} to find the gap.`:" Put it in the original gap to make the number sentence true."}`.trim()}]})}function inDomain$1(n,t){return n.known<1||n.known>1e3||n.z<1||n.z>1e3?!1:t>=1&&t<=1e3}const missingNumberNumeric={id:"AS.P5.M04.missing-number.v1",microSkillId:"AS.P5.M04",forms:["missing_addend","missing_number_bridging","equation_completion","context_calculation"],generate(n){const t=fromStemEquation(n.stem)??fromVisualTerms(n)??fromContext(n.stem)??fromContextDonation(n.stem)??fromContextRemoval(n.stem);if(!t)return null;const e=solve(t);return e===null||!inDomain$1(t,e)||e!==answerInt(n)?null:buildMissingNumberDemo(n,this.id,t,e)}
71},missingNumberChoice={id:"AS.P5.M04.which-inverse.v1",microSkillId:"AS.P5.M04",forms:["symbolic_choice"],generate(n){const t=fromChoiceStem(n.stem);if(!t)return null;const e=solve(t);if(e===null||!inDomain$1(t,e))return null;const s=inverseExpression(t),o=r=>r.replace(/−/g,"-").replace(/\s+/g," ").trim();return o(n.correctAnswer)!==o(s.text)?null:buildMissingNumberDemo(n,this.id,t,e)}},GAP_CLAIM=/^.+ says the number that goes in the gap, so that __ ([+\-]) (\d+) = (\d+) is true, is (\d+)\. Is .+ correct\?$/i,gapClaimVerdict={id:"AS.P5.M04.gap-claim.v1",microSkillId:"AS.P5.M04",forms:["check_reasoning_probe"],generate(n){const e=normaliseStem(n.stem).match(GAP_CLAIM);if(!e)return null;const s=e[1]==="+"?"+":"-",o=Number(e[2]),r=Number(e[3]),a=Number(e[4]),i={op:s,unknown:"first",known:o,z:r},l=solve(i);if(l===null||[o,r,l,a].some(x=>x<1||x>1e3))return null;const u=n.correctAnswer.trim();if(!/^(yes|no)$/i.test(u))return null;const d=/^yes$/i.test(u),m=l===a;if(m!==d)return null;const h=s==="+"?"+":"−",f=equationTerms(i),g=inverseExpression(i),b=describeColumnWorking(g.sign==="−"?"subtract":"add",g.p,g.qv);return buildExample({generatorId:this.id,question:n,title:`Does __ ${h} ${o} = ${r} give ${a}?`,computed:{...b.computed,known:o,z:r,claimed:a,missing:l},steps:[{visual_data:{type:"equation_display",terms:f},animation:"show_equation",caption:`Claim: the missing number is ${a}.`,spoken:`Someone says the missing number is ${numberToWordsLong(a)}. Let's check it properly.`},{visual_data:b.visual,animation:b.animation,caption:b.keyCaption,spoken:`${s==="+"?"Take the known part away from the whole.":"Add back the part that was taken away."} ${b.keySpoken}`},{visual_data:b.visual,animation:"reveal_missing",caption:`${b.restCaption} ${m?`Gap = ${l}. The claim is right.`:`Gap = ${l}, not ${a}. The claim is wrong.`}`.trim(),spoken:`${b.restSpoken} ${m?`So the missing number really is ${numberToWordsLong(l)}. That matches the claim, so it is correct.`:`So the missing number is really ${numberToWordsLong(l)}, not ${numberToWordsLong(a)}. That does not match, so the claim is wrong.`}`.trim()}]})}};function decidingPlaceAnim$2(n,t){for(const[e,s]of[["hundreds",100],["tens",10],["ones",1]])if(Math.floor(n/s)%10!==Math.floor(t/s)%10)return`highlight_${e}`;return null}const TWO_EQ_COMPARE=/^Which equation has the (greater|smaller) missing number: __ ([+\-]) (\d+) = (\d+), or __ ([+\-]) (\d+) = (\d+)\?$/i,twoEquationCompare={id:"AS.P5.M04.two-equation-compare.v1",microSkillId:"AS.P5.M04",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(TWO_EQ_COMPARE);if(!e)return null;const s=e[1].toLowerCase(),o=e[2]==="+"?"+":"-",r=Number(e[3]),a=Number(e[4]),i=e[5]==="+"?"+":"-",l=Number(e[6]),u=Number(e[7]),d={op:o,unknown:"first",known:r,z:a},m={op:i,unknown:"first",known:l,z:u},h=solve(d),f=solve(m);if(h===null||f===null||h===f||[r,a,l,u,h,f].some(S=>S<1||S>1e3))return null;const g=s==="greater"?h>f:h<f,b=`__ ${o} ${r} = ${a}`,x=`__ ${i} ${l} = ${u}`,y=g?b:x;if(n.correctAnswer.trim()!==y)return null;const E=inverseExpression(d),k=inverseExpression(m),T=describeColumnWorking(E.sign==="−"?"subtract":"add",E.p,E.qv),A=describeColumnWorking(k.sign==="−"?"subtract":"add",k.p,k.qv),N=Object.fromEntries([...Object.entries(T.computed).map(([S,M])=>[`first_${S}`,M]),...Object.entries(A.computed).map(([S,M])=>[`second_${S}`,M])]);return buildExample({generatorId:this.id,question:n,title:`Which has the ${s} missing number: ${b} or ${x}?`,computed:{...N,k1:r,z1:a,k2:l,z2:u,miss1:h,miss2:f},steps:[{visual_data:{...T.visual,show_answer:!0},animation:"reveal_missing",caption:`${T.keyCaption} ${E.text} = ${h}.`,spoken:`To compare the missing numbers, we find each one first by undoing the calculation. ${T.spoken} ${capitalised(numberToWordsLong(E.p))} ${E.sign==="−"?"take away":"add"} ${numberToWordsLong(E.qv)} is ${numberToWordsLong(h)}.`},{visual_data:{...A.visual,show_answer:!0},animation:"reveal_missing",caption:`${A.keyCaption} ${k.text} = ${f}.`,spoken:`Now the second one. ${A.spoken} ${capitalised(numberToWordsLong(k.p))} ${k.sign==="−"?"take away":"add"} ${numberToWordsLong(k.qv)} is ${numberToWordsLong(f)}.`},{visual_data:{type:"comparison",left:h,right:f,symbol:h>f?">":"<"},animation:`confirm_result${decidingPlaceAnim$2(h,f)?` ${decidingPlaceAnim$2(h,f)}`:""}`,caption:`${s==="greater"?"Greater":"Smaller"}: ${y}.`,spoken:`${capitalised(numberToWordsLong(g?h:f))} is ${s==="greater"?"bigger":"smaller"} than ${numberToWordsLong(g?f:h)}, so that equation has the ${s} missing number.`}]})}},partWholeMissingPart={id:"AS.P5.M04.part-whole.v1",microSkillId:"AS.P5.M04",forms:["part_whole_structure"],generate(n){const t=partWholeFromVisual(n);if(!t)return null;const{whole:e,knownPart:s}=t;if(e<1||e>1e3||s<0||s>1e3)return null;const o=e-s;if(o<0||o>1e3||o!==answerInt(n))return null;const r={type:"part_whole",whole:e,parts:[s,null]},a=describeColumnWorking("subtract",e,s);return buildExample({generatorId:this.id,question:n,title:`A bar model splits ${e} into two parts. Find the missing part.`,computed:{...a.computed,whole:e,knownPart:s,missing:o,answer:o},steps:[{visual_data:r,animation:"highlight_unknown",caption:`${e} splits into ${s} and a missing part.`,spoken:`The whole is ${numberToWordsLong(e)}. One part is ${numberToWordsLong(s)}. What is the other part?`},{visual_data:a.visual,animation:a.animation,caption:a.keyCaption,spoken:`Take the known part away from the whole. ${a.keySpoken}`},{visual_data:a.visual,animation:"fill_part",caption:`${a.restCaption} ${s} and ${o} make ${e}.`.trim(),spoken:`${a.restSpoken} The missing part is ${numberToWordsLong(o)}. ${capitalised(numberToWordsLong(s))} and ${numberToWordsLong(o)} make ${numberToWordsLong(e)}.`.trim()}]})}},AS_P5_M04_GENERATORS=[missingNumberNumeric,missingNumberChoice,gapClaimVerdict,twoEquationCompare,partWholeMissingPart];function buildWorkedExample(n){const t={...n.computed},e=new Map,s=n.steps.map((o,r)=>
71{const{workingLead:a,...i}=o,l=i.visual_data;if(l.type!=="column_method")return i;const u=describeColumnWorking(l.operation,l.top,l.bottom);for(const[b,x]of Object.entries(u.computed))t[`stage${r}_${b}`]=x;const d=b=>{const x=b?.visual_data;return x?.type==="column_method"&&x.operation===l.operation&&x.top===l.top&&x.bottom===l.bottom},m=d(n.steps[r-1]),h=d(n.steps[r+1]),f=m?u.restSpoken:h?u.keySpoken:u.spoken,g=[a,f,h?void 0:i.spoken].filter(Boolean).join(" ").split(new RegExp("(?<=[.!?])\\s+")).map(b=>{const x=b.toLowerCase(),y=e.get(x);return e.set(x,r),y!==void 0&&y!==r?`For this ${r===1?"second":"last"} calculation, ${b[0].toLowerCase()}${b.slice(1)}`:b}).join(" ");return{...i,visual_data:{...l,...u.visual,show_answer:!h},animation:`${i.animation} ${u.animation}`,caption:h?u.keyCaption:i.caption,spoken:g}});return buildExample({...n,computed:t,steps:s})}const GATE=/^.+, then .+\. How (?:many|much) .+\?$/;function parseTwoStep(n){const t=normaliseStem(n.stem);if(!GATE.test(t))return null;const e=(t.match(/\d+/g)??[]).map(Number);if(e.length!==3)return null;const[s,o,r]=e;if([s,o,r].some(g=>g<1||g>999))return null;const a=answerInt(n);if(a===null)return null;const l=[{op1:"subtract",op2:"add"},{op1:"add",op2:"subtract"},{op1:"add",op2:"add"},{op1:"subtract",op2:"subtract"}].filter(g=>{const b=g.op1==="add"?s+o:s-o;if(b<0)return!1;const x=g.op2==="add"?b+r:b-r;return x>=0&&x===a});if(l.length!==1)return null;const{op1:u,op2:d}=l[0],m=u==="add"?s+o:s-o,h=d==="add"?m+r:m-r,f=/\bpounds?\b|£/.test(t);return{S:s,A:o,B:r,op1:u,op2:d,stage1:m,final:h,money:f}}function decidingPlaceAnim$1(n,t){for(const[e,s]of[["hundreds",100],["tens",10],["ones",1]])if(Math.floor(n/s)%10!==Math.floor(t/s)%10)return`highlight_${e}`;return null}const opChar=n=>n==="add"?"+":"−",opWord=n=>n==="add"?"add":"take away";function buildTwoStage(n,t,e){const{S:s,A:o,B:r,op1:a,op2:i,money:l,stage1:u,final:d}=e,m=b=>l?`£${b}`:`${b}`,h={type:"column_method",operation:a,top:s,bottom:o,show_answer:!0},f={type:"column_method",operation:i,top:u,bottom:r},g=l?" pounds":"";return buildWorkedExample({generatorId:n,question:t,title:normaliseStem(t.stem),computed:{S:s,A:o,B:r,stage1:u,final:d,answer:d},steps:[{visual_data:h,workingLead:"First, work out the amount after the first change.",animation:"stage_one_add",caption:`First: ${m(s)} ${opChar(a)} ${m(o)} = ${m(u)}.`,spoken:`This problem has two stages. First, we ${opWord(a)}: ${numberToWordsLong(s)}${g} ${a==="add"?"and":"take away"} ${numberToWordsLong(o)}${g} ${a==="add"?"makes":"leaves"} ${numberToWordsLong(u)}${g}.`},{visual_data:f,workingLead:"Use that new amount for the second change.",animation:"stage_two_subtract",caption:`Then: ${m(u)} ${opChar(i)} ${m(r)}.`,spoken:`That ${numberToWordsLong(u)}${g} is where the second stage begins. Now we ${opWord(i)} ${numberToWordsLong(r)}${g}.`},{visual_data:f,animation:"reveal_total",caption:`Answer: ${m(d)}.`,spoken:`${capitalised(numberToWordsLong(u))}${g} ${i==="add"?"and":"take away"} ${numberToWordsLong(r)}${g} ${i==="add"?"makes":"leaves"} ${numberToWordsLong(d)}${g}. Two stages, worked one after the other.`}]})}const SUM_MINUS_A=/sells (\d+) tickets for an afternoon film and (\d+) tickets for an evening film\. If (\d+) of those tickets were children's tickets, how many were adult tickets\?$/i,SUM_MINUS_B=/sold (\d+) tickets on Friday and (\d+) tickets on Saturday\. On Sunday it sold (\d+) fewer tickets than Friday and Saturday combined\. How many tickets were sold on Sunday\?$/i,START_MINUS_TWO=/has (\d+) books\. Year \d+ borrows (\d+) and Year \d+ borrows (\d+)\. How many books are left/i,sumMinusFamily={id:"AS.P5.M05.sum-minus.v1",microSkillId:"AS.P5.M05",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem),e=t.match(SUM_MINUS_A)??t.match(SUM_MINUS_B);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]);if([s,o,r].some(l=>l<1||l>999))return null;const a=s+o,i=a-r;return a>9999||i<0||i!==answerInt(n)?null:buildTwoStage(this.id,n,{S:s,A:o,B:r,op1:"add",o
71p2:"subtract",money:!1,stage1:a,final:i})}},startMinusTwoFamily={id:"AS.P5.M05.start-minus-two.v1",microSkillId:"AS.P5.M05",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(START_MINUS_TWO);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if([e,s,o].some(i=>i<1||i>999))return null;const r=e-s,a=r-o;return r<0||a<0||a!==answerInt(n)?null:buildTwoStage(this.id,n,{S:e,A:s,B:o,op1:"subtract",op2:"subtract",money:!1,stage1:r,final:a})}},TWO_STEP_SUBTRACT_CLAIM=/^.+ made (\d+) [a-z]+\. .+ sold (\d+) in the morning and (\d+) more in the afternoon\. .+ says there are (\d+) [a-z]+ left\. Is .+ correct\?$/i,twoStepSubtractClaim={id:"AS.P5.M05.two-step-claim.v1",microSkillId:"AS.P5.M05",forms:["check_reasoning_probe"],generate(n){const e=normaliseStem(n.stem).match(TWO_STEP_SUBTRACT_CLAIM);if(!e)return null;const[s,o,r,a]=e.slice(1).map(Number);if([s,o,r,a].some(g=>g<1||g>999))return null;const i=s-o;if(i<0)return null;const l=i-r;if(l<0)return null;const u=n.correctAnswer.trim();if(!/^(yes|no)$/i.test(u))return null;const d=/^yes$/i.test(u),m=l===a;if(m!==d)return null;const h={type:"column_method",operation:"subtract",top:s,bottom:o,show_answer:!0},f={type:"column_method",operation:"subtract",top:i,bottom:r};return buildWorkedExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{S:s,A:o,B:r,stage1:i,actual:l,claimed:a},steps:[{visual_data:h,workingLead:"First, work out the amount after the first change.",animation:"stage_one_subtract",caption:`First: ${s} − ${o} = ${i}.`,spoken:`This is a two-step problem. First, take away the morning amount: ${numberToWordsLong(s)} take away ${numberToWordsLong(o)} is ${numberToWordsLong(i)}.`},{visual_data:f,workingLead:"Use that new amount for the second change.",animation:"stage_two_subtract",caption:`Then: ${i} − ${r} = ${l}.`,spoken:`Now take away the afternoon amount too: ${numberToWordsLong(i)} take away ${numberToWordsLong(r)} is ${numberToWordsLong(l)}.`},{visual_data:f,animation:"confirm_result",caption:m?`${l} left — the claim is right.`:`${l} left, not ${a}
71 — claim is wrong.`,spoken:m?`Working through both steps gives ${numberToWordsLong(l)}, which matches the claim — so it is correct.`:`Working through both steps gives ${numberToWordsLong(l)}, not ${numberToWordsLong(a)} — so the claim is wrong.`}]})}},TWO_PERSON_COMPARE=/^(\w+) starts with (\d+) ([a-z]+)\. .+? gives away (\d+), then buys (\d+) more\. (\w+) starts with (\d+) [a-z]+\. .+? gives away (\d+), then buys (\d+) more\. Who has more [a-z]+ now\?$/i,twoPersonCompare={id:"AS.P5.M05.two-person-compare.v1",microSkillId:"AS.P5.M05",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(TWO_PERSON_COMPARE);if(!e)return null;const s=e[1],o=Number(e[2]),r=e[3],a=Number(e[4]),i=Number(e[5]),l=e[6],u=Number(e[7]),d=Number(e[8]),m=Number(e[9]);if([o,a,i,u,d,m].some(A=>A<1||A>999))return null;const h=o-a;if(h<0)return null;const f=h+i,g=u-d;if(g<0)return null;const b=g+m;if(f===b)return null;const x=f>b?s:l;if(n.correctAnswer.trim()!==x)return null;const y=describeColumnWorking("add",h,i),E=describeColumnWorking("add",g,m),k={type:"column_method",operation:"subtract",top:o,bottom:a,show_answer:!0},T={type:"column_method",operation:"subtract",top:u,bottom:d,show_answer:!0};return buildWorkedExample({generatorId:this.id,question:n,title:`${s} vs ${l}: who has more ${r} now?`,computed:{S1:o,A1:a,B1:i,final1:f,S2:u,A2:d,B2:m,final2:b,stage1a:h,stage1b:g},steps:[{visual_data:k,workingLead:"Work out the first person’s amount after giving some away.",animation:"person_one",caption:`${s}: ${o} − ${a} = ${h}, then + ${i} = ${f}.`,spoken:`${s} starts with ${numberToWordsLong(o)}, gives away ${numberToWordsLong(a)}, then buys ${numberToWordsLong(i)} more. ${y.spoken} That gives ${numberToWordsLong(f)} in total.`},{visual_data:T,workingLead:"Now work out the second person’s amount after giving some away.",animation:"person_two",caption:`${l}: ${u} − ${d} = ${g}, then + ${m} = ${b}.`,spoken:`${l} starts with ${numberToWordsLong(u)}, gives away ${numberToWordsLong(d)}, then buys ${numberToWordsLong(m)} more. ${E.spoken} That gives ${numberToWordsLong(b)} in total.`},{visual_data:{type:"comparison",left:f,right:b,symbol:f>b?">":"<"},animation:`compare_totals${decidingPlaceAnim$1(f,b)?` ${decidingPlaceAnim$1(f,b)}`:""}`,caption:`${x} has more: ${Math.max(f,b)}.`,spoken:`Now we compare the two totals. ${capitalised(numberToWordsLong(Math.max(f,b)))} is more than ${numberToWordsLong(Math.min(f,b))}, so ${x} has more ${r} now.`}]})}},TANK_WORK_BACKWARDS=/^.+ holds some [a-z]+ of [a-z]+\. After (\d+) [a-z]+ are added and then (\d+) [a-z]+ are removed, the [a-z]+ holds (\d+) [a-z]+\. How many [a-z]+ were in the [a-z]+ at the start\?$/i,tankWorkBackwards={id:"AS.P5.M05.work-backwards.v1",microSkillId:"AS.P5.M05",forms:["unknown_part_within_whole"],generate(n){const e=normaliseStem(n.stem).match(TANK_WORK_BACKWARDS);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]);if([s,o,r].some(d=>d<1||d>999))return null;const a=r+o,i=a-s;if(i<0||i>999||i!==answerInt(n))return null;const l={type:"column_method",operation:"add",top:r,bottom:o},u={type:"column_method",operation:"subtract",top:a,bottom:s};return buildWorkedExample({generatorId:this.id,question:n,title:"Work backwards to find the starting amount.",computed:{added:s,removed:o,total:r,intermediate:a,start:i,answer:i},steps:[{visual_data:l,workingLead:"Undo the last change first: add back what was removed.",animation:"undo_last_step",caption:`Undo the removal: ${r} + ${o} = ${a}.`,spoken:`Work backwards, undoing the last thing first. Add the removed amount back: ${numberToWordsLong(r)} add ${numberToWordsLong(o)} is ${numberToWordsLong(a)}.`},{visual_data:u,workingLead:"Undo the earlier addition by subtracting the amount added.",animation:"undo_first_step",caption:`Undo the addition: ${a} − ${s} = ${i}.`,spoken:`Now undo the first thing that happened: taking away undoes the adding. ${capitalised(numberToWordsLong(a))} take away ${numberToWordsLong(s)} is ${numberToWordsLong(i)}.`},{visual_data:u,animation:"reveal_start",caption:`The tank started with ${i}.`,spoken:`Working backwards through both steps gives ${numberToWordsLong(i)} — that was there at the start.`}]})}}
71,DISCOUNT_MISSING=/^.+ costing £(\d+) and some [a-z]+\. The shop takes £(\d+) off the total, and .+ pays £(\d+) in the end\. How much did the [a-z]+ cost\?$/i,discountMissingAddend={id:"AS.P5.M05.discount-missing.v1",microSkillId:"AS.P5.M05",forms:["missing_addend"],generate(n){const e=normaliseStem(n.stem).match(DISCOUNT_MISSING);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]);if([s,o,r].some(d=>d<1||d>999))return null;const a=r+o,i=a-s;if(i<0||i>999||i!==answerInt(n))return null;const l={type:"column_method",operation:"add",top:r,bottom:o},u={type:"column_method",operation:"subtract",top:a,bottom:s};return buildWorkedExample({generatorId:this.id,question:n,title:"Work backwards to find the pens' cost.",computed:{booksCost:s,discount:o,paid:r,fullTotal:a,pens:i,answer:i},steps:[{visual_data:l,workingLead:"Add the discount back to find the full cost in pounds.",animation:"undo_discount",caption:`Undo the discount: £${r} + £${o} = £${a}.`,spoken:`Undo the ${numberToWordsLong(o)} pound discount first: ${numberToWordsLong(r)} pounds add ${numberToWordsLong(o)} pounds is ${numberToWordsLong(a)} pounds.`},{visual_data:u,workingLead:"Subtract the books’ cost in pounds to find the pens’ cost.",animation:"subtract_books",caption:`Take away the books: £${a} − £${s} = £${i}.`,spoken:`That ${numberToWordsLong(a)} pounds was books and pens together. Take away the books, ${numberToWordsLong(s)} pounds, to find the pens.`},{visual_data:u,animation:"reveal_pens",caption:`The pens cost £${i}.`,spoken:`Working backwards gives ${numberToWordsLong(i)} pounds — that is what the pens cost.`}]})}},TRANSFER_COMPARE=/^(\w+) has (\d+) [a-z]+(?: [a-z]+)* and (\w+) has (\d+)\. After \1 gives \3 (\d+) [a-z]+(?: [a-z]+)*, how many more [a-z]+(?: [a-z]+)* does one of them have than the other\?$/i,transferCompareFamily={id:"AS.P5.M05.transfer-compare.v1",microSkillId:"AS.P5.M05",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(TRANSFER_COMPARE);if(!e)return null;const s=e[1],o=Number(e[2]),r=e[3],a=Number(e[4]),i=Number(e[5]);if([o,a,i].some(b=>b<1||b>999))return null;const l=o-i;if(l<0)return null;const u=a+i,d=Math.abs(l-u);if(d===0||d!==answerInt(n))return null;const m=l>u?s:r,h={type:"column_method",operation:"subtract",top:o,bottom:i,show_answer:!0},f={type:"column_method",operation:"add",top:a,bottom:i,show_answer:!0},g={type:"column_method",operation:"subtract",top:Math.max(l,u),bottom:Math.min(l,u)};return buildWorkedExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{giverStart:o,receiverStart:a,transfer:i,giverNow:l,receiverNow:u,diff:d,answer:d},steps:[{visual_data:h,workingLead:"The giver has fewer, so subtract the amount given away.",animation:"giver_now",caption:`${s}: ${o} − ${i} = ${l}.`,spoken:`${s} gives away ${numberToWordsLong(i)}: ${numberToWordsLong(o)} take away ${numberToWordsLong(i)} is ${numberToWordsLong(l)}.`},{visual_data:f,workingLead:"The receiver has more, so add the amount received.",animation:"receiver_now",caption:`${r}: ${a} + ${i} = ${u}.`,spoken:`${r} receives ${numberToWordsLong(i)}: ${numberToWordsLong(a)} add ${numberToWordsLong(i)} is ${numberToWordsLong(u)}.`},{visual_data:g,animation:"find_difference",workingLead:"Subtract the smaller new amount from the larger to find how many more.",caption:`${m} has ${d} more.`,spoken:`To find how many more, we find the difference between their new amounts. ${capitalised(numberToWordsLong(Math.max(l,u)))} take away ${numberToWordsLong(Math.min(l,u))} is ${numberToWordsLong(d)}, so ${m} has ${numberToWordsLong(d)} more.`}]})}},GAP_TO_TARGET=/^.+ has (\d+) [a-z]+ of [a-z]+\. (\d+) [a-z]+ leak out, then (\d+) [a-z]+ are pumped in\. How many [a-z]+ more would the [a-z]+ need to reach (\d+) [a-z]+\?$/i,GAP_REACH_SOLD_BORN=/^.+ has (\d+) [a-z]+\. (\d+) [a-z]+ are sold, then (\d+) [a-z]+ (?:are|is) born\. How many more [a-z]+ would the [a-z]+ need to reach (\d+)\?$/i,GAP_HAVE_LEFT_TWO_USES=/^.+ has (\d+) grams of [a-z]+\. .+ uses (\d+) grams for [a-z]+, then (\d+) grams for [a-z]+\. How many more grams of [a-z]+ would .+ need to have (\d+) grams left\?$/i,CAPACITY_DISTRACTOR_SUM=/^.+ holds (\d+) [a-z]+\. (\d+) are set out, then (\d+) more(?: [a-z]+)? are added\. How many [a-z]+ are set out altogether\?$/i,gapToTargetFamily={id:"AS.P5.M05.gap-to-target.v1",microSkillId:"AS.P5.M05",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem),e=t.match(GAP_TO_TARGET);
71if(e){const a=Number(e[1]),i=Number(e[2]),l=Number(e[3]),u=Number(e[4]);if([a,i,l].some(x=>x<1||x>999)||u<1||u>1e3)return null;const d=a-i;if(d<0)return null;const m=d+l,h=u-m;if(h<0||h!==answerInt(n))return null;const f={type:"column_method",operation:"subtract",top:a,bottom:i,show_answer:!0},g={type:"column_method",operation:"add",top:d,bottom:l,show_answer:!0},b={type:"column_method",operation:"subtract",top:u,bottom:m,show_answer:!0};return buildWorkedExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{start:a,leak:i,pumped:l,target:u,afterLeak:d,current:m,gap:h,answer:h},steps:[{visual_data:f,workingLead:"Subtract the water that leaked out.",animation:"stage_one_subtract",caption:`First: ${a} − ${i} = ${d}.`,spoken:`This has two stages before we find the gap. First, take away what leaked out: ${numberToWordsLong(a)} take away ${numberToWordsLong(i)} is ${numberToWordsLong(d)}.`},{visual_data:g,workingLead:"Add the water pumped in to the amount left.",animation:"stage_two_add",caption:`Then: ${d} + ${l} = ${m}.`,spoken:`Now add what was pumped in: ${numberToWordsLong(d)} add ${numberToWordsLong(l)} is ${numberToWordsLong(m)}. That is how much there is now.`},{visual_data:b,workingLead:"Subtract the current amount from the target to find how much more is needed.",animation:"find_gap",caption:`Gap: ${u} − ${m} = ${h}.`,spoken:`To reach ${numberToWordsLong(u)}, find the gap: ${numberToWordsLong(u)} take away ${numberToWordsLong(m)} is ${numberToWordsLong(h)} more needed.`}]})}const s=t.match(GAP_REACH_SOLD_BORN);if(s){const a=Number(s[1]),i=Number(s[2]),l=Number(s[3]),u=Number(s[4]);if([a,i,l].some(x=>x<1||x>999)||u<1||u>1e3)return null;const d=a-i;if(d<0)return null;const m=d+l,h=u-m;if(h<0||h!==answerInt(n))return null;const f={type:"column_method",operation:"subtract",top:a,bottom:i,show_answer:!0},g={type:"column_method",operation:"add",top:d,bottom:l,show_answer:!0},b={type:"column_method",operation:"subtract",top:u,bottom:m,show_answer:!0};return buildWorkedExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{start:a,sold:i,born:l,target:u,afterSold:d,current:m,gap:h,answer:h},steps:[{visual_data:f,workingLead:"Subtract the animals sold.",animation:"stage_one_subtract",caption:`First: ${a} − ${i} = ${d}.`,spoken:`This has two stages before we find the gap. First, take away the number sold: ${numberToWordsLong(a)} take away ${numberToWordsLong(i)} is ${numberToWordsLong(d)}.`},{visual_data:g,workingLead:"Add the new animals to the number left.",animation:"stage_two_add",caption:`Then: ${d} + ${l} = ${m}.`,spoken:`Now add the new ones born: ${numberToWordsLong(d)} add ${numberToWordsLong(l)} is ${numberToWordsLong(m)}. That is how many there are now.`},{visual_data:b,workingLead:"Subtract the current amount from the target to find how much more is needed.",animation:"find_gap",caption:`Gap: ${u} − ${m} = ${h}.`,spoken:`To reach ${numberToWordsLong(u)}, find the gap: ${numberToWordsLong(u)} take away ${numberToWordsLong(m)} is ${numberToWordsLong(h)} more needed.`}]})}const o=t.match(GAP_HAVE_LEFT_TWO_USES);if(o){const a=Number(o[1]),i=Number(o[2]),l=Number(o[3]),u=Number(o[4]);if([a,i,l].some(x=>x<1||x>999)||u<1||u>1e3)return null;const d=a-i;if(d<0)return null;const m=d-l;if(m<0)return null;const h=u-m;if(h<0||h!==answerInt(n))return null;const f={type:"column_method",operation:"subtract",top:a,bottom:i,show_answer:!0},g={type:"column_method",operation:"subtract",top:d,bottom:l,show_answer:!0},b={type:"column_method",operation:"subtract",top:u,bottom:m,show_answer:!0};return buildWorkedExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{start:a,use1:i,use2:l,target:u,afterUse1:d,current:m,gap:h,answer:h},steps:[{visual_data:f,workingLead:"Subtract the first amount used.",animation:"stage_one_subtract",caption:`First: ${a} − ${i} = ${d}.`,spoken:`This has two stages before we find the gap. First, take away the first amount used: ${numberToWordsLong(a)} take away ${numberToWordsLong(i)} is ${numberToWordsLong(d)}.`},{visual_data:g,workingLead:"Subtract the second amount from what is left.",animation:"stage_two_subtract",caption:`Then: ${d} − ${l} = ${m}.`,spoken:`Now take away the second amount used: ${numberToWordsLong(d)} take away ${numberToWordsLong(l)} is ${numberToWordsLong(m)}. That is how much is left now.`},{visual_data:b,workingLead:"Subtract the current amount from the target to find how much more is needed.",animation:"find_gap",caption:`Gap: ${u} − ${m} = ${h}.`,spoken:`To have ${numberToWordsLong(u)} left, find the gap: ${numberToWordsLong(u)} take away ${numberToWordsLong(m)} is ${numberToWordsLong(h)} more needed.`}]})}const r=t.match(CAPACITY_DISTRACTOR_SUM);if(r){const a=Number(r[1]),i=Number(r[2]),l=Number(r[3]);if([a,i,l].some(f=>f<1||f>999))return null;const u=i+l;if(u!==answerInt(n))return null;const d={type:"equation_display",terms:[String(a),String(i),String(l)],highlight:[1,2]},m={type:"column_method",operation:"add",top:i,bottom:l,show_answer:!1},h={type:"column_method",operation:"add",top:i,bottom:l,show_answer:!0};return buildWorkedExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{capacity:a,setOut:i,added:l,total:u,answer:u},steps:[{visual_data:d,animation:"highlight_needed",caption:`The capacity, ${a}, is not needed — only the set-out numbers matter.`,spoken:`The total capacity is not needed for this. We only need the numbers actually set out: ${numberToWordsLong(i)} and ${numberToWordsLong(l)}.`},{visual_data:m,workingLead:"Altogether means add the two amounts set out.",animation:"stage_add",caption:`Add ${i} + ${l}.`,spoken:`Altogether means add, so we add the two set-out amounts: ${numberToWordsLong(i)} add ${numberToWordsLong(l)}.`},{visual_data:h,animation:"land",caption:`${i} + ${l} = ${u}
71.`,spoken:`${capitalised(numberToWordsLong(i))} add ${numberToWordsLong(l)} is ${numberToWordsLong(u)}, so there are ${numberToWordsLong(u)} altogether.`}]})}return null}},AS_P5_M05_GENERATORS=[{id:"AS.P5.M05.two-step.v1",microSkillId:"AS.P5.M05",forms:["context_calculation"],generate(n){const t=parseTwoStep(n);return t?buildTwoStage(this.id,n,t):null}},sumMinusFamily,startMinusTwoFamily,twoStepSubtractClaim,twoPersonCompare,tankWorkBackwards,discountMissingAddend,transferCompareFamily,gapToTargetFamily];function escapeRegExp$4(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const DAY_ABBR_TO_FULL$1={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"},LABEL_PREFIX_SYNONYMS={wk:"week",week:"week"};function findNumberedLabelIndex(n,t){const e=t.trim().toLowerCase().match(/^([a-z]+)\s*0*(\d+)$/);if(!e)return-1;const s=LABEL_PREFIX_SYNONYMS[e[1]];return!s||!new RegExp(`\\b${s}s?\\b`).test(n)?-1:n.match(new RegExp(`\\b${e[2]}\\b`))?.index??-1}function findRangeLabelIndex(n,t){const e=t.trim().match(/^(\d+)-(\d+)$/);if(!e)return-1;const s=new RegExp(`\\b${e[1]}\\b\\s*(?:and|to)\\s*\\b${e[2]}\\b`);return n.match(s)?.index??-1}function findLabelIndex$1(n,t){const e=t.trim().toLowerCase(),s=n.match(new RegExp(`\\b${escapeRegExp$4(e)}\\b`,"i"));if(s?.index!==void 0)return s.index;const o=DAY_ABBR_TO_FULL$1[e];if(o){const a=n.match(new RegExp(`\\b${o}\\b`,"i"));if(a?.index!==void 0)return a.index}const r=findRangeLabelIndex(n,e);return r>=0?r:findNumberedLabelIndex(n,e)}const FIRST_N_WORD={two:2,three:3,four:4,five:5};function parseFirstN(n){const t=n.match(/\bfirst (two|three|four|five)\b/);return t?FIRST_N_WORD[t[1]]:null}function speakableLabel$1(n){const t=n.trim().toLowerCase(),e=t.match(/^(\d+)-(\d+)$/);return e?`${numberToWordsLong(Number(e[1]))} to ${numberToWordsLong(Number(e[2]))}`:(DAY_ABBR_TO_FULL$1[t]??t).replace(/\d+/g,o=>` ${numberToWordsLong(Number(o))} `).replace(/\s+/g," ").trim()}function capitalise$3(n){return n.charAt(0).toUpperCase()+n.slice(1)}function findNamedBars$1(n,t){const e=n.toLowerCase(),s=[];for(const o of t){const r=findLabelIndex$1(e,o.label);r>=0&&s.push({bar:o,index:r})}return s.sort((o,r)=>o.index-r.index),s}function decideOp$1(n,t){const e=normaliseStem(n).toLowerCase(),s=/\bmore\b/.test(e),o=/\bfewer\b/.test(e),r=/\btogether\b/.test(e)||/\bcombined\b/.test(e),a=/\baltogether\b/.test(e)||/\bin total\b/.test(e)||/\bacross\b/.test(e),i=/\ball\b/.test(e),l=/\bor\b/.test(e),u=e.match(/\bthan\b/),d=findNamedBars$1(n,t);if(s||o){if(u){const f=u.index,g=d.filter(x=>x.index<f),b=d.filter(x=>x.index>f);return g.length===1&&b.length===1?{kind:"cmp2",a:g[0].bar,b:b[0].bar}:g.length===1&&b.length===2&&r?{kind:"cmp3",subject:g[0].bar,group:[b[0].bar,b[1].bar]}:null}return l&&d.length===2?{kind:"pick",bars:d.map(f=>f.bar),pickType:s?"max":"min",pairwise:!0}:null}const m=/\b(fewest|least|lowest|smallest)\b/.test(e),h=/\b(most|greatest|highest|largest)\b/.test(e);if(m||h)return{kind:"pick",bars:[...t],pickType:m?"min":"max"};if(a){if(d.length>=2)return{kind:"sum",bars:d.map(g=>g.bar)};const f=parseFirstN(e);return d.length===0&&f!==null&&f>=2&&f<=t.length?{kind:"sum",bars:t.slice(0,f),positional:!0}:d.length===0&&i?{kind:"sum",bars:[...t]}:null}return l&&d.length===2?{kind:"sum",bars:d.map(f=>f.bar)}:d.length===1?{kind:"single",bar:d[0].bar}:null}function guardChart$1(n){const t=n.bars;if(t.length<2||t.length>5||t.some(a=>!Number.isInteger(a.value)||a.value<0)||n.axis_max!==void 0&&t.some(a=>a.value>n.axis_max))return null;const e=Math.max(...t.map(a=>a.value),1),s=Math.max(n.axis_max??0,e),o=n.axis_step??(s<=10?1:2);return Math.ceil(s/o)*o>40&&t.some(a=>a.value%o!==0)?null:{axisStep:o}}function stepVisual$1(n,t,e){return{type:"bar_chart",bars:n.bars,axis_max:n.axis_max,axis_step:n.axis_step,...t!==void 0?{read_index:t}:{},...e!==void 0?{lit_indices:e,hide_diff:!0}:{}}}function gridlineFacts$1(n,t){if(n%t===0)return{kind:"exact"};const e=Math.floor(n/t)*t,s=e+t;if(t%2===0&&n===e+t/2)return{kind:"halfway",low:e,high:s};const o=n-e,r=s-n;return o<=r?{kind:"count",anchor:e,direction:"up",ticks:o}:{kind:"count",anchor:s,direction:"down",ticks:r}}function readCaption$1(n,t){const e=gridlineFacts$1(n.value,t);return e.kind==="exact"?`${n.label}: ${n.value}.`:e.kind==="halfway"?`${n.label}: halfway ${e.low}–${e.high} → ${n.value}.`:e.direction==="up"?`${n.label}: above ${e.anchor} → ${n.value}.`:`${n.label}: below ${e.anchor} → ${n.value}.`}function readGridNumbers$1(n,t){const e=gridlineFacts$1(n.value,t);return e.kind==="halfway"?[e.low,e.high]:e.kind==="count"?[e.anchor]:[]}function readSpoken$1(n,t){const e=gridlineFacts$1(n.value,t),s=speakableLabel$1(n.label);if(e.kind==="exact")return`Follow the top of the ${s} bar across to the side. It lines up with ${numberToWordsLong(n.value)}.`;if(e.kind==="halfway")return`Follow the top of the ${s} bar. It sits halfway between ${numberToWordsLong(e.low)} and ${numberToWordsLong(e.high)}. It shows ${numberToWordsLong(n.value)}.`;const o=e.ticks===1?"tick":"ticks";if(e.direction==="up"){const r=[];for(let a=e.anchor+1;a<=n.value;a++)r.push(numberToWordsLong(a));return`Follow the top of the ${s} bar. It sits just above the ${numberToWordsLong(e.anchor)} line. Count the small ticks: ${r.join(", ")}. It shows ${numberToWordsLong(n.value)}.`}return`Follow the top of the ${s} bar. It sits just below the ${numberToWordsLong(e.anchor)} line — ${numberToWordsLong(e.ticks)} small ${o} below. It shows ${numberToWordsLong(n.value)}.`}function buildFromOp$1(n,t,e,s){const o=m=>t.bars.indexOf(m);let r={visual_data:stepVisual$1(t),animation:"grow",caption:`The side goes up in ${s}s.`,spoken:`Look at the numbers up the side. They go up in ${numberToWordsLong(s)}s.`},a,i,l;const u={axisStep:s};if(e.kind==="single"){a=e.bar.value;const m=t.bars.find(h=>h!==e.bar);i={visual_data:stepVisual$1(t,o(m)),animation:"read",caption:`First try: ${readCaption$1(m,s)}`,spoken:`First practise the move. ${readSpoken$1(m,s)}`},l={visual_data:stepVisual$1(t,o(e.bar)),animation:"compare",caption:`${readCaption$1(e.bar,s).replace(/\.$/,"")} — the answer.`,spoken:`Now the ${speakableLabel$1(e.bar.label)} bar. ${readSpoken$1(e.bar,s)} That is the answer.`},u.value=e.bar.value,u.practice=m.value;for(const h of readGridNumbers$1(m,s))u[`practiceGrid${h}`]=h;for(const h of readGridNumbers$1(e.bar,s))u[`targetGrid${h}`]=h}else if(e.kind==="sum"){a=e.bars.reduce((y,E)=>y+E.value,0);const m=e.bars[0];i={visual_data:stepVisual$1(t,o(m)),animation:"read",caption:readCaption$1(e.positional?{...m,label:"First bar"}:m,s),spoken:readSpoken$1(e.positional?{...m,label:"first"}:m,s)};const h=e.bars[e.bars.length-1],f=e.bars.map(y=>y.value).join(" + "),g=e.bars.slice(1).map((y,E)=>readSpoken$1(e.positional?{...y,label:["second","third","fourth","fifth"][E]}:y,s)).join(" ");let b,x;if(e.bars.length>2){let y=e.bars[0].value;const E=[],k=[];for(let T=1;T<e.bars.length;T++){const A=e.bars[T].value,N=y+A;E.push(`${y}+${A}=${N}`),k.push(`${numberToWordsLong(y)} add ${numberToWordsLong(A)} is ${numberToWordsLong(N)}`),y=N,u[`partial${T}`]=y}b=E.join(", "),x=k.join(", then ")}else b=`${f}=${a}`,x=`${e.bars.map(y=>numberToWordsLong(y.value)).join(" add ")} is ${numberToWordsLong(a)}`;l={visual_data:stepVisual$1(t,void 0,e.bars.map(y=>o(y))),animation:"compare",caption:e.positional?`Add the selected bars: ${b}.`:`${readCaption$1(h,s).replace(/\.$/,"")}. ${b}.`,spoken:`${g} Altogether means add: ${x}.`}
71,e.bars.forEach((y,E)=>u[`term${E}`]=y.value);for(const y of readGridNumbers$1(m,s))u[`firstGrid${y}`]=y;for(const y of e.bars)for(const E of readGridNumbers$1(y,s))u[`readGrid${E}`]=E}else if(e.kind==="cmp2"){a=Math.abs(e.a.value-e.b.value),i={visual_data:stepVisual$1(t,o(e.a)),animation:"read",caption:readCaption$1(e.a,s),spoken:readSpoken$1(e.a,s)};const m=Math.max(e.a.value,e.b.value),h=Math.min(e.a.value,e.b.value);l={visual_data:stepVisual$1(t,o(e.b)),animation:"compare",caption:`${readCaption$1(e.b,s).replace(/\.$/,"")}. Gap: ${m}−${h}=${a}.`,spoken:`${readSpoken$1(e.b,s)} The gap between the two lines is ${numberToWordsLong(m)} take away ${numberToWordsLong(h)}, which is ${numberToWordsLong(a)}.`},u.a=e.a.value,u.b=e.b.value;for(const f of readGridNumbers$1(e.a,s))u[`aGrid${f}`]=f;for(const f of readGridNumbers$1(e.b,s))u[`bGrid${f}`]=f}else{const[m,h]=e.group,f=m.value+h.value;a=e.subject.value-f,r={visual_data:stepVisual$1(t),animation:"grow",caption:readCaption$1(e.subject,s),spoken:readSpoken$1(e.subject,s)},i={visual_data:stepVisual$1(t,void 0,[o(m),o(h)]),animation:"read",caption:`Together: ${m.value} + ${h.value} = ${f}.`,spoken:`Now the pair. ${readSpoken$1(m,s)} ${readSpoken$1(h,s)} Add ${numberToWordsLong(m.value)} and ${numberToWordsLong(h.value)} to make ${numberToWordsLong(f)}.`},l={visual_data:{type:"bar_chart",bars:[e.subject,{label:"Together",value:f}],axis_max:Math.max(t.axis_max??0,f),axis_step:s},animation:"compare",caption:`${e.subject.value} − ${f} = ${a}.`,spoken:`To find the difference, we take the pair's total away from the ${speakableLabel$1(e.subject.label)} bar: ${numberToWordsLong(e.subject.value)} take away ${numberToWordsLong(f)} is ${numberToWordsLong(a)}.`};for(const g of[e.subject,m,h])for(const b of readGridNumbers$1(g,s))u[`readGrid${b}`]=b;u.subject=e.subject.value,u.g1=m.value,u.g2=h.value,u.groupSum=f}const d=Number(n.correctAnswer);return!Number.isFinite(d)||d!==a?null:(u.answer=a,buildExample({generatorId:"DS.P3.M02.bar_chart.v1",question:n,title:n.stem,steps:[r,i,l],computed:u}))}function buildPickDemo(n,t,e,s){const o=h=>t.bars.indexOf(h),r=e.bars.map(h=>h.value),a=e.pickType==="min"?Math.min(...r):Math.max(...r),i=e.bars.filter(h=>h.value===a);if(i.length!==1)return null;const l=i[0];if(n.correctAnswer.trim()!==l.label)return null;const u=e.bars.find(h=>h!==l)??t.bars.find(h=>h!==l);if(!u)return null;const d=e.pairwise?{visual_data:stepVisual$1(t,o(l)),animation:"compare",caption:`${l.value} is ${e.pickType==="max"?"more":"fewer"} than ${u.value}, so ${l.label} had ${e.pickType==="max"?"more":"fewer"} than ${u.label}.`,spoken:`Now compare the two reads. ${capitalise$3(numberToWordsLong(l.value))} is ${e.pickType==="max"?"more":"fewer"} than ${numberToWordsLong(u.value)}, so ${speakableLabel$1(l.label)} had ${e.pickType==="max"?"more":"fewer"} than ${speakableLabel$1(u.label)}.`}:{visual_data:stepVisual$1(t,o(l)),animation:"compare",caption:`${l.label} is the ${e.pickType==="min"?"fewest":"most"} — ${l.value}.`,spoken:`Comparing the bars, ${speakableLabel$1(l.label)} is the ${e.pickType==="min"?"fewest":"most"}, at ${numberToWordsLong(l.value)}.`},m=e.pairwise?{visual_data:stepVisual$1(t,o(u)),animation:"read",caption:`${u.label} lines up with ${u.value}.`,spoken:`Follow the top of the ${speakableLabel$1(u.label)} bar across to the side. It lines up with ${numberToWordsLong(u.value)}.`}:{visual_data:stepVisual$1(t,o(u)),animation:"read",caption:`${u.label}: ${u.value} — compare every bar for the ${e.pickType==="min"?"fewest":"most"}.`,spoken:`First look at the ${speakableLabel$1(u.label)} bar. It lines up with ${numberToWordsLong(u.value)}. We must compare every bar to find the ${e.pickType==="min"?"fewest":"most"}.`};return buildExample({generatorId:"DS.P3.M02.bar_chart.v1",question:n,title:n.stem,computed:{axisStep:s,practice:u.value,winner:l.value},steps:[{visual_data:stepVisual$1(t),animation:"grow",caption:`The side goes up in ${s}s.`,spoken:`Look at the numbers up the side. They go up in ${numberToWordsLong(s)}s.`},m,d]})}const BAR_CLAIM=/^.+ says the ([a-z]+) bar shows (\d+)\. Is (?:he|she|they) right\?$/i,barClaimVerdict={id:"DS.P3.M02.bar-claim.v1",microSkillId:"DS.P3.M02",forms:["misconception_contrast_probe"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart$1(t);if(!e)return null;const o=normaliseStem(n.stem).match(BAR_CLAIM);if(!o)return null;const r=o[1],a=Number(o[2]),i=t.bars.find(g=>g.label.toLowerCase()===r.toLowerCase());if(!i)return null;const l=n.correctAnswer.trim().toLowerCase(),u=l.startsWith("yes"),d=l.startsWith("no");if(!u&&!d)return null;const m=i.value===a;if(m!==u)return null;const h=t.bars.find(g=>g!==i);if(!h)return null;const f=g=>t.bars.indexOf(g);return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{claimed:a,actual:i.value,practice:h.value,axisStep:e.axisStep},steps:[{visual_data:stepVisual$1(t),animation:"grow",caption:`The side goes up in ${e.axisStep}s.`,spoken:`Look at the numbers up the side. They go up in ${numberToWordsLong(e.axisStep)}s.`},{visual_data:stepVisual$1(t,f(h)),animation:"read",caption:`${h.label} lines up with ${h.value}.`,spoken:`First practise the move on the ${speakableLabel$1(h.label)} bar. It lines up with ${numberToWordsLong(h.value)}.`},{visual_data:stepVisual$1(t,f(i)),animation:"compare",caption:m?`${i.label} shows ${i.value} — the claim is correct.`:`${i.label} shows ${i.value}, not ${a} — the claim is wrong.`,spoken:m?`Now the ${speakableLabel$1(i.label)} bar: it really does show ${numberToWordsLong(i.value)}, so the claim is right.`:`Now the ${speakableLabel$1(i.label)} bar: it shows ${numberToWordsLong(i.value)}, not ${numberToWordsLong(a)} — so the claim is wrong.`}]})}},AXIS_HALFWAY=/^A bar chart's vertical axis is numbered [\d, ]+, going up in steps of (\d+)\. A bar's top sits exactly halfway between the (\d+) and (\d+) lines\. What value does the bar show\?$/i,AXIS_HALFWAY_TRIMMED=/^On a bar chart, a bar's top sits exactly halfway between the (\d+) and (\d+) (?:gridlines|lines)\. What value does the bar show\?$/i,axisHalfway={id:"DS.P3.M02.axis-halfway.v1",microSkillId:"DS.P3.M02",forms:["symbolic_numeric_entry"],generate(n){const t=normaliseStem(n.stem);let e,s,o,r=t.match(AXIS_HALFWAY);if(r)e=Number(r[1]),s=Number(r[2]),o=Number(r[3]);
71else if(r=t.match(AXIS_HALFWAY_TRIMMED))s=Number(r[1]),o=Number(r[2]),e=o-s;else return null;if(o-s!==e||e!==1&&e!==2&&e!==5&&e!==10||(s+o)%2!==0)return null;const a=(s+o)/2;if(a!==answerInt(n)||o>200)return null;const i={type:"bar_chart",bars:[{label:`${s}`,value:s},{label:"?",value:a},{label:`${o}`,value:o}],axis_max:o,axis_step:e},l={...i,read_index:0},u={...i,read_index:1};return buildExample({generatorId:this.id,question:n,title:`Halfway between ${s} and ${o} on the axis`,computed:{step:e,low:s,high:o,halfway:a,answer:a},steps:[{visual_data:i,animation:"grow",caption:`The axis goes up in steps of ${e}.`,spoken:`Look at the numbers up the side. They go up in steps of ${numberToWordsLong(e)}.`},{visual_data:l,animation:"read",caption:`The ${s} line and the ${o} line are ${e} apart.`,spoken:`The bar stops between two lines, so first we find the gap between them. The ${numberToWordsLong(s)} line and the ${numberToWordsLong(o)} line are ${numberToWordsLong(e)} apart.`},{visual_data:u,animation:"compare",caption:`Halfway is ${a} — right between the two lines.`,spoken:`Halfway means half of that gap. Half of ${numberToWordsLong(e)} is ${numberToWordsLong(e/2)}. ${capitalise$3(numberToWordsLong(s))} and ${numberToWordsLong(e/2)} more is ${numberToWordsLong(a)}.`}]})}},DS_P3_M02_GENERATORS=[{id:"DS.P3.M02.bar_chart.v1",microSkillId:"DS.P3.M02",forms:["context_calculation","symbolic_choice","comparison_statement","symbolic_numeric_entry"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart$1(t);if(!e)return null;const s=decideOp$1(n.stem,t.bars);return s?s.kind==="pick"?buildPickDemo(n,t,s,e.axisStep):buildFromOp$1(n,t,s,e.axisStep):null}},barClaimVerdict,axisHalfway];function escapeRegExp$3(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}const DAY_ABBR_TO_FULL={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"},LABEL_VERB_SYNONYM={scored:"scored?"};function findLabelIndex(n,t){const e=t.trim().toLowerCase(),s=n.match(new RegExp(`\\b${escapeRegExp$3(e)}\\b`,"i"));if(s?.index!==void 0)return s.index;const o=DAY_ABBR_TO_FULL[e];if(o){const i=n.match(new RegExp(`\\b${o}\\b`,"i"));if(i?.index!==void 0)return i.index}const r=LABEL_VERB_SYNONYM[e];if(r){const i=n.match(new RegExp(`\\b${r}\\b`));if(i?.index!==void 0)return i.index}const a=e.match(/^([a-z]+) ([a-z0-9]+)$/);if(a){const[,i,l]=a;if(new RegExp(`\\b${escapeRegExp$3(i)}s\\b`).test(n)){const u=n.match(new RegExp(`\\b${escapeRegExp$3(l)}\\b`));if(u?.index!==void 0)return u.index}}return-1}function speakableLabel(n){const t=n.trim().toLowerCase();return(DAY_ABBR_TO_FULL[t]??t).replace(/\d+/g,s=>` ${numberToWordsLong(Number(s))} `).replace(/\s+/g," ").trim()}function findNamedBars(n,t){const e=n.toLowerCase(),s=[];for(const o of t){const r=findLabelIndex(e,o.label);r>=0&&s.push({bar:o,index:r})}return s.sort((o,r)=>o.index-r.index),s}function decideOp(n,t){const e=normaliseStem(n).toLowerCase(),s=/\bmore\b/.test(e),o=/\bfewer\b/.test(e),r=/\btogether\b/.test(e)||/\bcombined\b/.test(e),a=e.match(/\bthan\b/);if(!(s||o)||!a)return null;const i=findNamedBars(n,t),l=a.index,u=i.filter(m=>m.index<l),d=i.filter(m=>m.index>l);return u.length===1&&d.length===1?{kind:"cmp2",a:u[0].bar,b:d[0].bar}:u.length===1&&d.length===2&&r?{kind:"cmp3",subject:u[0].bar,group:[d[0].bar,d[1].bar]}:null}function guardChart(n){const t=n.bars;if(t.length<2||t.length>5||t.some(a=>!Number.isInteger(a.value)||a.value<0)||n.axis_max!==void 0&&t.some(a=>a.value>n.axis_max))return null;const e=Math.max(...t.map(a=>a.value),1),s=Math.max(n.axis_max??0,e),o=n.axis_step??(s<=10?1:2);return Math.ceil(s/o)*o>40&&t.some(a=>a.value%o!==0)?null:{axisStep:o}}function stepVisual(n,t,e){return{type:"bar_chart",bars:n.bars,axis_max:n.axis_max,axis_step:n.axis_step,...t!==void 0?{read_index:t}:{},...e!==void 0?{lit_indices:e,hide_diff:!0}:{}}}function gridlineFacts(n,t){if(n%t===0)return{kind:"exact"};const e=Math.floor(n/t)*t,s=e+t;if(t%2===0&&n===e+t/2)return{kind:"halfway",low:e,high:s};const o=n-e,r=s-n;return o<=r?{kind:"count",anchor:e,direction:"up",ticks:o}:{kind:"count",anchor:s,direction:"down",ticks:r}}function readCaption(n,t){const e=gridlineFacts(n.value,t);return e.kind==="exact"?`${n.label}: ${n.value}.`:e.kind==="halfway"?`${n.label}: halfway ${e.low}–${e.high} → ${n.value}.`:e.direction==="up"?`${n.label}: above ${e.anchor} → ${n.value}.`:`${n.label}: below ${e.anchor} → ${n.value}.`}function readGridNumbers(n,t){const e=gridlineFacts(n.value,t);return e.kind==="halfway"?[e.low,e.high]:e.kind==="count"?[e.anchor]:[]}function readSpoken(n,t){const e=gridlineFacts(n.value,t),s=speakableLabel(n.label);if(e.kind==="exact")return`Follow the top of the ${s} bar across to the side. It lines up with ${numberToWordsLong(n.value)}.`;if(e.kind==="halfway")return`The top of the ${s} bar sits halfway between ${numberToWordsLong(e.low)} and ${numberToWordsLong(e.high)}. So it shows ${numberToWordsLong(n.value)}.`;const o=e.ticks===1?"tick":"ticks";if(e.direction==="up"){const r=[];for(let a=e.anchor+1;a<=n.value;a++)r.push(numberToWordsLong(a));return`The top of the ${s} bar sits just above the ${numberToWordsLong(e.anchor)} line. Each small tick is one more, so count the ticks: ${r.join(", ")}. It shows ${numberToWordsLong(n.value)}.`}return`The top of the ${s} bar sits just below the ${numberToWordsLong(e.anchor)} line — ${numberToWordsLong(e.ticks)} small ${o} below. Each small tick is one, so it shows ${numberToWordsLong(n.value)}.`}function buildFromOp(n,t,e,s){const o=m=>t.bars.indexOf(m);let r={visual_data:stepVisual(t),animation:"grow",caption:`The side goes up in ${s}s.`,spoken:`To find how many more or fewer, we read both bars and find the difference between them. First, look at the numbers up the side. They go up in ${numberToWordsLong(s)}s.`},a,i,l;const u={axisStep:s};if(e.kind==="cmp2"){a=Math.abs(e.a.value-e.b.value),i={visual_data:stepVisual(t,o(e.a)),animation:"read",caption:readCaption(e.a,s),spoken:readSpoken(e.a,s)};const m=Math.max(e.a.value,e.b.value),h=Math.min(e.a.value,e.b.value);l={visual_data:stepVisual(t,o(e.b)),animation:"compare",caption:`${readCaption(e.b,s).replace(/\.$/,"")}. Gap: ${m}−${h}=${a}.`,spoken:`${readSpoken(e.b,s)} The difference is the gap between the two bars: ${numberToWordsLong(m)} take away ${numberToWordsLong(h)} is ${numberToWordsLong(a)}. So the difference is ${numberToWordsLong(a)}.`}
71,u.a=e.a.value,u.b=e.b.value;for(const f of readGridNumbers(e.a,s))u[`aGrid${f}`]=f;for(const f of readGridNumbers(e.b,s))u[`bGrid${f}`]=f}else{const[m,h]=e.group,f=m.value+h.value;a=e.subject.value-f,r={visual_data:stepVisual(t),animation:"grow",caption:`${e.subject.label} lines up with ${e.subject.value}.`,spoken:`We compare one bar with two others put together, so we need all three values. First read the ${speakableLabel(e.subject.label)} bar. Follow its top across to the side. It lines up with ${numberToWordsLong(e.subject.value)}.`},i={visual_data:stepVisual(t,o(m)),animation:"read",caption:readCaption(m,s),spoken:readSpoken(m,s)},l={visual_data:stepVisual(t,o(h)),animation:"compare",caption:`${readCaption(h,s).replace(/\.$/,"")}. ${m.value}+${h.value}=${f}. ${e.subject.value}−${f}=${a}.`,spoken:`${readSpoken(h,s)} Together, ${speakableLabel(m.label)} and ${speakableLabel(h.label)} make ${numberToWordsLong(f)}. The difference is ${numberToWordsLong(e.subject.value)} take away ${numberToWordsLong(f)}, which is ${numberToWordsLong(a)}.`},u.subject=e.subject.value,u.g1=m.value,u.g2=h.value,u.groupSum=f;for(const g of readGridNumbers(m,s))u[`g1Grid${g}`]=g;for(const g of readGridNumbers(h,s))u[`g2Grid${g}`]=g}const d=Number(n.correctAnswer);return!Number.isFinite(d)||d!==a?null:(u.answer=a,buildExample({generatorId:"DS.P3.M03.bar_chart.v1",question:n,title:n.stem,steps:[r,i,l],computed:u}))}const CLAIM_CMP3=/^.+ says more [a-z]+ (?:chose|picked|prefer(?:red)?) ([A-Za-z]+) than ([A-Za-z]+) and ([A-Za-z]+) put together\. Is (?:he|she|they) right\?$/i,claimCmp3={id:"DS.P3.M03.claim-cmp3.v1",microSkillId:"DS.P3.M03",forms:["misconception_contrast_probe"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart(t);if(!e)return null;const o=normaliseStem(n.stem).match(CLAIM_CMP3);if(!o)return null;const r=b=>t.bars.find(x=>x.label.toLowerCase()===b.toLowerCase()),a=r(o[1]),i=r(o[2]),l=r(o[3]);if(!a||!i||!l)return null;const u=i.value+l.value,d=a.value>u,m=n.correctAnswer.trim().toLowerCase(),h=m.startsWith("yes"),f=m.startsWith("no");if(!h&&!f||d!==h)return null;const g=b=>t.bars.indexOf(b);return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{subject:a.value,g1:i.value,g2:l.value,groupSum:u,axisStep:e.axisStep},steps:[{visual_data:stepVisual(t),animation:"grow",caption:`${a.label} lines up with ${a.value}.`,spoken:`To test the claim, we need all three bars. First read the ${speakableLabel(a.label)} bar. It lines up with ${numberToWordsLong(a.value)}.`},{visual_data:stepVisual(t,g(i)),animation:"read",caption:`${i.label} is ${i.value}.`,spoken:`Now ${speakableLabel(i.label)}: it is ${numberToWordsLong(i.value)}.`},{visual_data:stepVisual(t,void 0,[g(a),g(i),g(l)]),animation:"compare",caption:d?`${i.value}+${l.value}=${u}. ${a.value} is more — the claim is right.`:`${i.value}+${l.value}=${u}. ${a.value} is not more — the claim is wrong.`,spoken:d?`Now ${speakableLabel(l.label)}: it is ${numberToWordsLong(l.value)}. Together, ${speakableLabel(i.label)} and ${speakableLabel(l.label)} make ${numberToWordsLong(u)}. ${capitalised(numberToWordsLong(a.value))} is more than ${numberToWordsLong(u)}, so the claim is right.`:`Now ${speakableLabel(l.label)}: it is ${numberToWordsLong(l.value)}. Together, ${speakableLabel(i.label)} and ${speakableLabel(l.label)} make ${numberToWordsLong(u)}. ${capitalised(numberToWordsLong(a.value))} is not more than ${numberToWordsLong(u)}, so the claim is wrong.`}]})}},CLASS_FOOTBALL_COMPARE=/Which class had more [a-z]+ choose ([A-Za-z]+): (\w+) or (\w+)\?$/i,classActivityCompare={id:"DS.P3.M03.class-activity-compare.v1",microSkillId:"DS.P3.M03",forms:["comparison_statement"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart(t);if(!e)return null;const o=normaliseStem(n.stem).match(CLASS_FOOTBALL_COMPARE);if(!o)return null;const r=o[1],a=o[2],i=o[3],l=f=>t.bars.find(g=>g.label.toLowerCase()===`${f} ${r}`.toLowerCase()),u=l(a),d=l(i);if(!u||!d||u.value===d.value)return null;const m=u.value>d.value?a:i;if(n.correctAnswer.trim()!==m)return null;const h=f=>t.bars.indexOf(f);return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{bar1:u.value,bar2:d.value,axisStep:e.axisStep},steps:[{visual_data:stepVisual(t),animation:"grow",caption:`The side goes up in ${e.axisStep}s.`,spoken:`Look at the numbers up the side. They go up in ${numberToWordsLong(e.axisStep)}s.`},{visual_data:stepVisual(t,h(u)),animation:"read",caption:`Only the two ${r} bars matter. ${u.label}: ${u.value}.`,spoken:`We only need the two ${r} bars, so we can ignore the others. Follow the top of the ${speakableLabel(u.label)} bar across to the side. It lines up with ${numberToWordsLong(u.value)}.`},{visual_data:stepVisual(t,h(d)),animation:"compare",caption:`${d.label} lines up with ${d.value}. ${m} had more.`,spoken:`Now the ${speakableLabel(d.label)} bar. It lines up with ${numberToWordsLong(d.value)}. ${capitalised(numberToWordsLong(Math.max(u.value,d.value)))} is more than ${numberToWordsLong(Math.min(u.value,d.value))}, so class ${speakableLabel(m)} had more.`}]})}},SUM_THEN_COMPARE=/altogether prefer ([A-Za-z]+) or ([A-Za-z]+)\?$/i,sumThenCompare={id:"DS.P3.M03.sum-then-compare.v1",microSkillId:"DS.P3.M03",forms:["varied_symbolic_calculation"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart(t);if(!e)return null;const o=normaliseStem(n.stem).match(SUM_THEN_COMPARE);if(!o)return null;const r=o[1],a=o[2],i=k=>t.bars.filter(T=>T.label.toLowerCase().endsWith(k.toLowerCase())),l=i(r),u=i(a);if(l.length<2||u.length<2)return null;const d=l.reduce((k,T)=>k+T.value,0),m=u.reduce((k,T)=>k+T.value,0);if(d===m)return null;const h=d>m?r:a;if(n.correctAnswer.trim()!==h)return null;const f=k=>t.bars.indexOf(k),g={sum1:d,sum2:m,axisStep:e.axisStep};l.forEach((k,T)=>g[`g1_${T}`]=k.value),u.forEach((k,T)=>g[`g2_${T}`]=k.value);const b=k=>k.map(T=>`${T.label}:${T.value}`).join(", "),x=k=>capitalised(k.map(T=>`${speakableLabel(T.label)} is ${numberToWordsLong(T.value)}`).join(", and ")),y=(k,T)=>`${k.map(A=>A.value).join("+")}=${T}`,E=[...l,...u].map(k=>f(k));return buildExample({generatorId:this.id,question:n,title:n.stem,computed:g,steps:[{visual_data:stepVisual(t,void 0,l.map(k=>f(k))),animation:"read",caption:`${b(l)}. ${r}: ${y(l,d)}.`,spoken:`To compare the two activities, we total each one across both classes. ${x(l)}. Add both classes' ${r} bars together: ${l.map(k=>numberToWordsLong(k.value)).join(" add ")} is ${numberToWordsLong(d)}.`},{visual_data:stepVisual(t,void 0,u.map(k=>f(k))),animation:"read",caption:`${b(u)}. ${a}: ${y(u,m)}.`,spoken:`${x(u)}. Add both classes' ${a} bars together: ${u.map(k=>numberToWordsLong(k.value)).join(" add ")} is ${numberToWordsLong(m)}.`},{visual_data:stepVisual(t,void 0,E),animation:"compare",caption:`${h} wins: ${Math.max(d,m)} vs ${Math.min(d,m)}.`,spoken:`Now we compare the two totals. ${capitalised(numberToWordsLong(Math.max(d,m)))} is more than ${numberToWordsLong(Math.min(d,m))}, so altogether more children prefer ${h}.`}]})}},HOUSE_TOTAL_COMPARE=/^House (\w+) raised £(\d+) in Week 1 and £(\d+) in Week 2 for the school fund\. House (\w+) raised £(\d+) in Week 1 and £(\d+) in Week 2\. How many more pounds did House \1 raise in total than House \4\?$/i,houseTotalCompare={id:"DS.P3.M03.house-total-compare.v1",microSkillId:"DS.P3.M03",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(HOUSE_TOTAL_COMPARE);if(!e)return null;const s=e[1],o=Number(e[2]),r=Number(e[3]),a=e[4],i=Number(e[5]),l=Number(e[6]);if([o,r,i,l].some(b=>b<1||b>999))return null;const u=o+r,d=i+l,m=u-d;if(m<0||m!==answerInt(n))return null;const h={type:"column_method",operation:"add",top:o,bottom:r,show_answer:!0},f={type:"column_method",operation:"add",top:i,bottom:l,show_answer:!0},g={type:"column_method",operation:"subtract",top:u,bottom:d,keep_exchange:!0};return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{w1a:o,w1b:r,total1:u,w2a:i,w2b:l,total2:d,diff:m,answer:m},steps:[{visual_data:h,animation:"house_one",caption:`House ${s}: £${o} + £${r} = £${u}.`,spoken:`To compare the houses, we first find each house's total. House ${s} raised ${numberToWordsLong(o)} pounds and ${numberToWordsLong(r)} pounds: ${numberToWordsLong(u)} pounds in total.`},{visual_data:f,animation:"house_two",caption:`House ${a}: £${i} + £${l} = £${d}.`,spoken:`House ${a} raised ${numberToWordsLong(i)} pounds and ${numberToWordsLong(l)} pounds: ${numberToWordsLong(d)} pounds in total.`},{visual_data:g,animation:"compare_totals",caption:`£${u} − £${d} = £${m} more.`,spoken:`To find how many more, we take the smaller total away from the bigger one. ${capitalised(numberToWordsLong(u))} pounds take away ${numberToWordsLong(d)} pounds is ${numberToWordsLong(m)} pounds, so the first house raised ${numberToWordsLong(m)} pounds more.`}]})}},DS_P3_M03_GENERATORS=[{id:"DS.P3.M03.bar_chart.v1",microSkillId:"DS.P3.M03",forms:["context_calculation","comparison_statement"],generate(n){const t=n.visualData;if(!t||t.type!=="bar_chart")return null;const e=guardChart(t);if(!e)return null;const s=decideOp(n.stem,t.bars);return s?buildFromOp(n,t,s,e.axisStep):null}},claimCmp3,classActivityCompare,sumThenCompare,houseTotalCompare],COLOR$3="#2563eb",EMPTY_MARK="#f59e0b",nUnit$2=(n,t)=>`${n} ${t}${n===1?"":"s"}`,wUnit$2=(n,t)=>`${numberToWords(n)} ${t}${n===1?"":"s"}`;function tenthsFromAnswer$1(n){const t=n.trim();let e=t.match(/^(\d+)\/10$/);return e||(e=t.match(/^0\.(\d)$/),e)||(e=t.match(/(\d+)\/10\b/),e)||(e=t.match(/^(\d+) tenths?$/i),e)?Number(e[1]):/^\d+$/.test(t)?Number(t):null}function acceptedTenths(n){const t=n.trim().replace(/\s+(m|metres?|litres?|l|kg|g|km|cm)$/i,"").trim();return tenthsFromAnswer$1(t)}function sourceTenths(n){const t=normaliseStem(n.stem),e=t.match(/one tenth more than 0\.(\d)/);if(e){const i=Number(e[1]);return{n:i+1,mode:"add",base:i}}if(/1 whole written in tenths is __\/10/i.test(t))return{n:10,mode:"whole"};let s=t.match(/(\d+)\/10\b/);if(s)return{n:Number(s[1]),mode:"plain",given:"fraction"};if(s=t.match(/0\.(\d)\b/),s)return{n:Number(s[1]),mode:"plain",given:"decimal"};const o=t.match(/(\d+) (?:parts|pieces|slices)\b/);if(o&&/(?:split into 10|10 equal|ten equal|marked in tenths)/i.test(t))return{n:Number(o[1]),mode:"plain",given:"fraction"};const r=t.match(/(\d+) of the tenths (?:are|is) (?:lit up|shaded|coloured)/i);if(r&&/(?:split into 10|10 equal|ten equal)/i.test(t))return{n:Number(r[1]),mode:"plain",given:"fraction"};const a=n.visualData;return a&&a.type==="fraction_shape"&&a.total_parts===10&&Number.isInteger(a.shaded_parts)?/not\s+shaded/i.test(t)?{n:10-a.shaded_parts,mode:"complement",complementOf:a.shaded_parts}:{n:a.shaded_parts,mode:"plain",given:"fraction"}:null}function compareTenths$1(n,t){const e=normaliseStem(n.stem);if(!/larger|greater|bigger/i.test(e))return null;const s=e.match(/0\.(\d)\b/),o=e.match(/(\d+)\/10\b/)??e.match(/(\d+) tenths?\b/);if(!s||!o)return null;const r=Number(s[1]),a=Number(o[1]);if(r<1||r>9||a<1||a>9||r===a)return null;const i=Math.max(r,a);if(acceptedTenths(n.correctAnswer)!==i)return null;const l=r/10,u=a/10,d=i/10,m=/(\d+)\/10\b/.test(e)?`${a}/10`:nUnit$2(a,"tenth"),h=/bigger/i.test(e)?"bigger":/greater/i.test(e)?"greater":"larger",f=(g={})=>({type:"number_line",min:0,max:1,step:.1,marked:[u,l],...g});return buildExample({generatorId:t,question:n,title:`Which is ${h}: ${m} or 0.${r}?`,computed:{decimal:l,fraction:u,larger:d,answer:d},steps:[{visual_data:f(),animation:"show_start",caption:`Put ${m} and 0.${r} on the tenths line.`,spoken:`We place both ${wUnit$2(a,"tenth")} and nought point ${numberToWords(r)} on a number line split into tenths, so we can see which is ${h}.`},{visual_data:f({conclude_label:`${a}/10 = 0.${a}`}),animation:"translate",caption:`${a}/10 is the same as 0.${a}.`,spoken:`${capitalised(numberToWords(a))} tenths written as a decimal is nought point ${numberToWords(a)}. Now both are decimals.`},{visual_data:f({unknown:d}),animation:"land",caption:`0.${i} sits further right than 0.${r===i?a:r} — it is ${h}.`,spoken:`Nought point ${numberToWords(i)} sits further to the right than nought point ${numberToWords(r===i?a:r)}, so it is the ${h} amount.`}]})}const bar$3=(n,t)=>({type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:n,color:COLOR$3,...t?{conclude_label:t}:{}}),complementBar=(n,t)=>{const e={};for(let s=n;s<10;s++)e[s]=EMPTY_MARK;return{type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:n,color:COLOR$3,part_marks:e,...t?{conclude_label:t}:{}}};function claimCheck$1(n,t){const s=normaliseStem(n.stem).match(/^(\w+) says 0\.(\d) is the same as (\d+) tenths\. Is (?:he|she|they) right\?$/i);if(!s)return null;const o=s[1],r=Number(s[2]),a=Number(s[3]);if(a===r)return null;const i=a!==r;return/wrong/i.test(n.correctAnswer)!==i?null:buildExample({generatorId:t,question:n,title:`${o} says 0.${r} is the same as ${a} tenths — is that right?`,computed:{trueTenths:r,claimed:a,answer:r},steps:[{visual_data:bar$3(r),animation:"shade_parts",caption:`0.${r} means ${nUnit$2(r,"tenth")} shaded.`,spoken:`Nought point ${numberToWords(r)} means ${wUnit$2(r,"tenth")} out of the ten are shaded.`},{visual_data:{...bar$3(r,`${r} tenths`),show_part_numbers:!0},animation:"count_parts",caption:`Count the shaded tenths: ${r}, not ${a}.`,spoken:`Counting the shaded parts gives ${numberToWords(r)} tenths — the digit after the point already tells us the tenths count, with no extra zeros needed.`},{visual_data:bar$3(r,`${o} is wrong — it's ${r} tenths, not ${a}.`),animation:"land",caption:`${o} is wrong: 0.${r} is ${r} tenths, not ${a}
71 tenths.`,spoken:`So ${o} is wrong. Nought point ${numberToWords(r)} is ${wUnit$2(r,"tenth")}, not ${numberToWords(a)} tenths.`}]})}function misplacedDigitMistake(n,t){const s=normaliseStem(n.stem).match(/^(\w+) writes (\d+)\/10 = 0\.0(\d)\. What is (?:his|her|their) mistake\?$/i);if(!s)return null;const o=s[1],r=Number(s[2]),a=Number(s[3]);if(r<1||r>9||r!==a||!/hundredths/i.test(n.correctAnswer))return null;const i=`0.${r}`,l=`0.0${r}`,u=r/10,d=r/100;return buildExample({generatorId:t,question:n,title:`${o} writes ${r}/10 = ${l} — what is the mistake?`,computed:{n:r,correctVal:u,wrongVal:d,answer:r},steps:[{visual_data:bar$3(r),animation:"shade_parts",caption:`${r}/10 means ${nUnit$2(r,"tenth")} shaded.`,spoken:`${capitalised(numberToWords(r))} over ten means ${wUnit$2(r,"tenth")} shaded — ${numberToWords(r)} of the first ten equal parts.`},{visual_data:{type:"equation_display",terms:[i,"=",r,"tenths"],highlight:[2],fit:!0},animation:"write_decimal",caption:`Tenths go straight after the point: ${r}/10 = ${i}.`,spoken:`Tenths are written straight after the point, so ${wUnit$2(r,"tenth")} is nought point ${numberToWords(r)}.`},{visual_data:{type:"equation_display",terms:[l,"=",r,"hundredths"],highlight:[3],fit:!0},animation:"land",caption:`${l} has ${r} hundredths; ${r}/10 needs ${i}.`,spoken:`${o} wrote it one place too far right, in the hundredths place. The correct decimal is nought point ${numberToWords(r)}.`}]})}function twoCountContext(n,t){const s=normaliseStem(n.stem).match(/cut into 10 equal slices\. (\w+) eats (\d+) slices? and (\w+) eats (\d+)\. Which decimal of the pizza did they eat altogether\?/i);if(!s)return null;const[,o,r,a,i]=s,l=Number(r),u=Number(i),d=l+u;if(d<1||d>9)return null;const m=`0.${d}`;return n.correctAnswer.trim()!==m?null:buildExample({generatorId:t,question:n,title:`${o} eats ${l} slices, ${a} eats ${u} more — what decimal did they eat altogether?`,computed:{c1:l,c2:u,total:d,answer:d/10},steps:[{visual_data:bar$3(l),animation:"shade_parts",caption:`${o} eats ${nUnit$2(l,"slice")}: shade ${l}.`,spoken:`${o} eats ${wUnit$2(l,"slice")}. Each slice is one tenth, so we shade ${numberToWords(l)}.`},{visual_data:bar$3(d),animation:"shade_parts",caption:`${a} eats ${nUnit$2(u,"slice")} more: now ${d} shaded.`,spoken:`${a} eats ${wUnit$2(u,"slice")} more. ${capitalised(numberToWords(l))} plus ${numberToWords(u)} makes ${numberToWords(d)} tenths shaded altogether.`},{visual_data:bar$3(d,m),animation:"write_decimal",caption:`${d}/10 = ${m}`,spoken:`${capitalised(wUnit$2(d,"tenth"))} is written as a decimal by putting the ${numberToWords(d)} just after the point — nought point ${numberToWords(d)}.`}]})}function numberLineDemo(n,t){const e=n.visualData;if(!e||e.type!=="number_line"||typeof e.unknown!="number")return null;const s=Math.round(e.unknown*10)/10,o=Math.round(s*10);if(o<1||o>9)return null;const r=`0.${o}`,a=`${o}/10`,i=n.correctAnswer.trim();let l;if(i===a||i===String(o))l=!0;else if(i===r)l=!1;else return null;const u=e.min??0,d=e.max??1,m=e.step??.1,h=(g={})=>({type:"number_line",min:u,max:d,step:m,marked:[],...g}),f=l?a:r;return buildExample({generatorId:t,question:n,title:l?"Which fraction does the marked point show?":"Which decimal does the marked point show?",computed:{n:o,val:s,answer:l?o:s},steps:[{visual_data:h({marker_at:s}),animation:"show_start",caption:"The line is split into tenths. Find the marked point.",spoken:"The line from nought to one is split into ten equal tenths. A point is marked somewhere on it."},{visual_data:h({marker_at:s,hops:[{from:0,to:s,label:nUnit$2(o,"tenth")}]}),animation:"count_ticks",caption:`Count the tenths from 0: ${nUnit$2(o,"tenth")}.`,spoken:`We count the tenth ticks from nought up to the mark: ${wUnit$2(o,"tenth")}.`},{visual_data:h({unknown:s,conclude_label:f}),animation:"land",caption:l?`That is ${a}.`:`${nUnit$2(o,"tenth")} written as a decimal is ${r}.`,spoken:l?`${capitalised(numberToWords(o))} tenths is written ${numberToWords(o)} over ten.`:`Tenths go straight after the point. So ${wUnit$2(o,"tenth")} written as a decimal is nought point ${numberToWords(o)}.`}]})}const round1=n=>Math.round(n*10)/10;function sequenceFill(n,t){const s=normaliseStem(n.stem).match(/Counting up in tenths: ([\d.,\s]+), __, ([\d.]+)\. What decimal is missing\?/i);if(!s)return null;const o=s[1].split(",").map(b=>Number(b.trim())).filter(b=>Number.isFinite(b)),r=Number(s[2]);if(o.length<2)return null;const a=o[o.length-1],i=round1(a+.1);if(Math.abs(round1(i+.1)-r)>1e-9||Number(n.correctAnswer)!==i)return null;const l=Math.floor(a),u=Math.round((a-l)*10),d=u+1,m=d===10,h=round1(o[0]-.1),f=rou
71nd1(r+.1),g=(b={})=>({type:"number_line",min:h,max:f,step:.1,marked:[...o,r],...b});return buildExample({generatorId:t,question:n,title:`Counting up in tenths, what comes after ${a}?`,computed:{last:a,missing:i,after:r,whole:l,lastTenths:u,nextTenths:d,answer:i},steps:[{visual_data:g(),animation:"show_start",caption:`Counting up in tenths: ${o.join(", ")}, __, ${r}.`,spoken:`We are counting up in tenths: ${o.map(b=>decimalWord(b)).join(", ")}, then a gap, then ${decimalWord(r)}.`},{visual_data:m?{...bar$3(d),show_part_numbers:!0}:g({hops:[{from:a,to:i,label:"one tenth more"}]}),animation:m?"shade_parts":"count_on",caption:m?`${u} tenths + 1 tenth = ${d} tenths = 1 whole.`:`One tenth more than ${a} is ${i}.`,spoken:m?`Keep the ${numberToWords(l)} whole ones. The fractional part has ${numberToWords(u)} tenths. Add one more tenth to fill the strip: ten tenths make one new whole.`:`Each step counts on one more tenth. One tenth more than ${decimalWord(a)} is ${decimalWord(i)}.`},{visual_data:g({unknown:i,conclude_label:String(i)}),animation:"land",caption:m?`${l} wholes + 1 whole = ${i}.`:`The missing decimal is ${i}.`,spoken:m?`Add the new whole to the ${numberToWords(l)} whole ones: ${numberToWords(i)}. So the missing decimal is ${decimalWord(i)}.`:`Counting on from ${decimalWord(a)} lands on ${decimalWord(i)}, the missing decimal.`}]})}function decimalWord(n){if(Number.isInteger(n))return numberToWords(n);const[t,e]=String(n).split(".");return`${Number(t)===0?"nought":numberToWords(Number(t))} point ${e.split("").map(o=>numberToWords(Number(o))).join(" ")}`}const FDP_P4_M01_GENERATORS=[{id:"FDP.P4.M01.tenths-bar.v1",microSkillId:"FDP.P4.M01",forms:["equation_completion","visual_quantity_to_symbol","symbolic_calculation","number_line_location","comparison_statement","context_calculation","misconception_contrast_probe","symbolic_numeric_entry"],generate(n){if(n.mathematicalForm==="number_line_location")return numberLineDemo(n,this.id);if(n.mathematicalForm==="comparison_statement"){const a=misplacedDigitMistake(n,this.id);return a||compareTenths$1(n,this.id)}if(n.mathematicalForm==="misconception_contrast_probe")return claimCheck$1(n,this.id);if(n.mathematicalForm==="symbolic_numeric_entry")return sequenceFill(n,this.id);if(n.mathematicalForm==="context_calculation"){const a=twoCountContext(n,this.id);if(a)return a}const t=sourceTenths(n);if(!t)return null;if(t.mode==="whole")return Number(n.correctAnswer)!==10?null:buildExample({generatorId:this.id,question:n,title:"1 whole written in tenths is __/10.",computed:{answer:10},steps:[{visual_data:bar$3(0),animation:"show_parts",caption:"One whole, split into 10 equal parts.",spoken:"One whole strip, split into ten equal parts. Each part is one tenth."},{visual_data:bar$3(10),animation:"shade_parts",caption:"Shade every part: all 10 tenths.",spoken:"We shade every single part — all ten tenths are shaded, because the whole strip is completely full."},{visual_data:bar$3(10,"10/10 = 1"),animation:"write_decimal",caption:"10/10 = 1 whole.",spoken:"Ten tenths shaded is the whole strip. So one whole written in tenths is ten over ten."}]});const{n:e}=t;if(e<1||e>9)return null;if(t.mode==="complement"){const a=t.complementOf;if(acceptedTenths(n.correctAnswer)!==e)return null;const i=Number(n.correctAnswer),l=Number.isFinite(i)?i:e;return buildExample({generatorId:this.id,question:n,title:`The bar shows ${a}/10 shaded. What is the part that is NOT shaded?`,computed:{orig:a,n:e,answer:l},steps:[{visual_data:bar$3(a),animation:"show_parts",caption:`${nUnit$2(a,"part")} of the 10 are already shaded.`,spoken:`Each part of the strip is one tenth. ${capitalised(wUnit$2(a,"tenth"))} out of ten ${a===1?"is":"are"} shaded already.`},{visual_data:complementBar(a),animation:"count_parts",caption:`The other ${nUnit$2(e,"part")} are NOT shaded: 10 − ${a} = ${e}.`,spoken:`We want the parts that are NOT shaded, so we take the shaded ones away from all ten. Ten parts take away ${numberToWords(a)} leaves ${numberToWords(e)} ${e===1?"part":"parts"} not shaded.`},{visual_data:complementBar(a,`0.${e}`),animation:"write_decimal",caption:`${e}/10 = 0.${e}`,spoken:`${capitalised(wUnit$2(e,"tenth"))} not shaded is nought point ${numberToWords(e)}.`}]})}if(acceptedTenths(n.correctAnswer)!==e)return null;const s=Number(n.correctAnswer),o=Number.isFinite(s)?s:e,r=e/10;if(t.mode==="add"){const a=t.base;return buildExample({generatorId:this.id,question:n,title:`What is one tenth more than 0.${a}
71?`,computed:{n:e,base:a,decimal:r,answer:o},steps:[{visual_data:bar$3(a),animation:"shade_parts",caption:`Start with ${nUnit$2(a,"tenth")} shaded.`,spoken:`We start from ${wUnit$2(a,"tenth")} — ${numberToWords(a)} of the ten parts ${a===1?"is":"are"} shaded.`},{visual_data:bar$3(e),animation:"shade_parts",caption:`One tenth more makes ${e} tenths.`,spoken:`One tenth more means shading one more part. That gives ${numberToWords(e)} tenths altogether.`},{visual_data:bar$3(e,`0.${e}`),animation:"write_decimal",caption:`${e} tenths = 0.${e}`,spoken:`${capitalised(numberToWords(e))} tenths is written as a decimal by putting the ${numberToWords(e)} just after the point. So one tenth more than the start is nought point ${numberToWords(e)}.`}]})}return t.given==="decimal"?buildExample({generatorId:this.id,question:n,title:`What tenths does 0.${e} show?`,computed:{n:e,decimal:r,answer:o},steps:[{visual_data:bar$3(0),animation:"show_parts",caption:`0.${e} — one whole strip split into 10 equal parts.`,spoken:`To see what nought point ${numberToWords(e)} means, we use one whole strip split into ten equal parts. Each part is one tenth.`},{visual_data:bar$3(e),animation:"shade_parts",caption:`The digit after the point is ${e}, so shade ${nUnit$2(e,"part")}.`,spoken:`The digit right after the point counts the tenths. Here that digit is ${numberToWords(e)}, so we shade ${wUnit$2(e,"part")} to check.`},{visual_data:bar$3(e,`${e}/10`),animation:"write_decimal",caption:`0.${e} = ${e}/10`,spoken:`So nought point ${numberToWords(e)} is the same as ${wUnit$2(e,"tenth")} — ${numberToWords(e)} over ten.`}]}):buildExample({generatorId:this.id,question:n,title:`How do we write ${e}/10 as a decimal?`,computed:{n:e,decimal:r,answer:o},steps:[{visual_data:bar$3(0),animation:"show_parts",caption:"One whole, split into 10 equal parts.",spoken:"A fraction out of ten is made of tenths, so we use one whole strip split into ten equal parts. Each part is one tenth."},{visual_data:bar$3(e),animation:"shade_parts",caption:`Shade ${nUnit$2(e,"part")}: that is ${e}/10.`,spoken:`Now we shade ${numberToWords(e)} of the ten parts. That gives ${wUnit$2(e,"tenth")}, written ${numberToWords(e)} over ten.`},{visual_data:bar$3(e,`0.${e}`),animation:"write_decimal",caption:`${e}/10 = 0.${e}`,spoken:`${capitalised(wUnit$2(e,"tenth"))} can be written as a decimal. We put the ${numberToWords(e)} just after the point, giving nought point ${numberToWords(e)}. So ${numberToWords(e)} over ten and nought point ${numberToWords(e)} are two names for the same amount.`}]})}}],nw$5=n=>decimalToWords(String(n)).replace(/^zero point/,"nought point"),COLOR$2="#2563eb",nUnit$1=(n,t)=>`${n} ${t}${n===1?"":"s"}`,wUnit$1=(n,t)=>`${numberToWords(n)} ${t}${n===1?"":"s"}`;function tenthsFromAnswer(n){const t=n.trim();let e=t.match(/^0\.(\d)$/);return e||(e=t.match(/^(\d+)\/10$/),e)?Number(e[1]):/^\d+$/.test(t)?Number(t):null}function parseM02(n){const t=normaliseStem(n.stem);let e=null,s=null,o="fraction";const r=t.match(/(\d+)\/(\d+)/);if(r)e=Number(r[1]),s=Number(r[2]),o="fraction";else{const i=t.match(/0\.(\d)\b/);if(i)e=Number(i[1]),s=10,o="decimal";else{const l=n.visualData;if(l&&l.type==="fraction_shape"&&Number.isInteger(l.shaded_parts))e=l.shaded_parts,s=l.total_parts,o="fraction";else if(l&&l.type==="equation_display"&&Array.isArray(l.terms)){const u=String(l.terms[0]).match(/^(\d+)\/(\d+)$/);u&&(e=Number(u[1]),s=Number(u[2]),o="fraction")}}}if(e===null||s===null){const i=tenthsFromAnswer(n.correctAnswer.trim());if(i!==null&&i>=1&&i<=9){const l=(t.match(/\d+/g)??[]).map(Number);for(const u of[10,5,2]){if(!l.includes(u))continue;const d=i*u/10;if(Number.isInteger(d)&&d>=1&&d<u&&l.includes(d)){e=d,s=u,o="fraction";break}}}}if(e===null||s===null||s!==2&&s!==5&&s!==10)return null;const a=e*10/s;return!Number.isInteger(a)||a<1||a>9?null:{n:a,N:e,D:s,mode:s===10?"base":"varied",given:o}}const bar$2=(n,t,e)=>({type:"fraction_shape",shape:"rectangle",total_parts:n,shaded_parts:t,color:COLOR$2,...e?{conclude_label:e}:{}}),round2$2=n=>Math.round(n*100)/100;function claimCheckM02(n,t){const s=normaliseStem(n.stem).match(/^(\w+) says (\d+)\/(\d+) = 0\.(\d+)\. Is (?:he|she|they) right\?$/i);if(!s)return null;const o=s[1],r=Number(s[2]),a=Number(s[3]);if(![2,4,5,10].includes(a)||r<1||r>=a)return null;const i=+`0.${s[4]}`,l=round2$2(r/a);if(l===i||/wrong/i.test(n.correctAnswer)!==(l!==i))return null;const d=String(l);if(a===4){const m=100/a,h=r*m;return buildExample({generatorId:t,question:n,title:`${o} says ${r}/${a} = 0.${s[4]} — is that right?`,computed:{N:r,D:a,trueVal:l,claimed:i,hundredthsPerPart:m,hu
71ndredths:h,answer:l},steps:[{visual_data:bar$2(a,r),animation:"show_parts",caption:`${r}/${a} shaded: share 100 hundredths into ${a} equal groups.`,spoken:"A whole is one hundred hundredths. To find a quarter, share those hundredths equally among the four quarters."},{visual_data:{type:"equation_display",terms:[100,"÷",a,"=",m],fit:!0},animation:"write_answer",caption:`100 ÷ ${a} = ${m} hundredths in each quarter.`,spoken:`One hundred divided by four is ${numberToWords(m)}. Each quarter contains ${numberToWords(m)} hundredths. We need ${numberToWords(r)} ${r===1?"quarter":"quarters"}, giving ${numberToWords(h)} hundredths.`},{visual_data:{type:"equation_display",terms:[`${r}/${a}`,"=",`${h}/100`,"=",d],fit:!0},animation:"land",caption:`${o} is wrong: ${r}/${a} = ${d}, not 0.${s[4]}.`,spoken:`${capitalised(numberToWords(h))} hundredths is ${nw$5(d)}. So ${o} is wrong: ${numberToWords(r)} over four is ${nw$5(d)}, not ${nw$5(i)}.`}]})}return buildExample({generatorId:t,question:n,title:`${o} says ${r}/${a} = 0.${s[4]} — is that right?`,computed:{N:r,D:a,trueVal:l,claimed:i,answer:l},steps:[{visual_data:bar$2(a,r),animation:"show_parts",caption:`${r}/${a} shaded — let's check ${o}'s claim.`,spoken:`${o} says ${numberToWords(r)} over ${numberToWords(a)} equals ${nw$5(i)}. To check a claim, we work it out ourselves.`},{visual_data:{type:"equation_display",terms:[`${r}/${a}`,"=",d]},animation:"write_decimal",caption:`${r}/${a} is really ${d}.`,spoken:`Let's write it as a decimal. ${capitalised(numberToWords(r))} over ${numberToWords(a)} is really ${nw$5(d)}.`},{visual_data:{type:"equation_display",terms:[`${r}/${a}`,"=",d,"≠",`0.${s[4]}`]},animation:"land",caption:`${o} is wrong: ${r}/${a} = ${d}, not 0.${s[4]}.`,spoken:`So ${o} is wrong. ${capitalised(numberToWords(r))} over ${numberToWords(a)} is ${nw$5(d)}, not ${nw$5(i)}.`}]})}function compareFracDec(n,t){const e=normaliseStem(n.stem);if(!/bigger|larger|greater/i.test(e))return null;const s=e.match(/(\d+)\/(\d+) or 0\.(\d)\b/i);if(!s)return null;const o=Number(s[1]),r=Number(s[2]),a=Number(s[3]);if(r!==2&&r!==5&&r!==10)return null;const i=10/r,l=o*i;if(!Number.isInteger(l)||l<1||l>9)return null;const u=l/10,d=a/10;if(u===d)return null;const m=u>d,h=m?`${o}/${r}`:`0.${a}`;if(n.correctAnswer.trim()!==h)return null;const f=(g={})=>({type:"number_line",min:0,max:1,step:.1,marked:[u,d],...g});return buildExample({generatorId:t,question:n,title:`Which is bigger: ${o}/${r} or 0.${a}?`,computed:{N:o,D:r,fracVal:u,decVal:d,answer:m?u:d},steps:[{visual_data:bar$2(r,o),animation:"show_parts",caption:`${o}/${r} shaded on a strip of ${r}.`,spoken:`We start with ${numberToWords(o)} out of ${numberToWords(r)} parts shaded.`},{visual_data:bar$2(10,l,`${l}/10 = 0.${l}`),animation:"subdivide_parts",caption:`${o}/${r} = ${l}/10 = 0.${l}.`,spoken:`Splitting into tenths, ${numberToWords(o)} out of ${numberToWords(r)} is the same as ${wUnit$1(l,"tenth")} — nought point ${numberToWords(l)}.`},{visual_data:f({unknown:m?u:d}),animation:"land",caption:m?`${o}/${r} (0.${l}) is bigger than 0.${a}.`:`0.${a} is bigger than ${o}/${r} (0.${l}).`,spoken:m?`Nought point ${numberToWords(l)} is more than nought point ${numberToWords(a)}, so ${numberToWords(o)} over ${numberToWords(r)} is bigger.`:`Nought point ${numberToWords(a)} is more than nought point ${numberToWords(l)}, so nought point ${numberToWords(a)} is bigger.`}]})}function numberLineFifths(n,t){const s=normaliseStem(n.stem).match(/split into (\d+) equal parts\. Which decimal does the marked point show\?/i);if(!s)return null;const o=Number(s[1]);if(o!==2&&o!==5&&o!==10)return null;const r=n.visualData;if(!r||r.type!=="number_line"||typeof r.unknown!="number")return null;const a=r.unknown,i=Math.round(a*o);if(!Number.isInteger(i)||i<1||i>o-1)return null;const l=10/o,u=i*l;if(!Number.isInteger(u)||Number(n.correctAnswer)!==a)return null;const d=(h={})=>({type:"number_line",min:0,max:1,step:1/o,marked:[],...h}),m=i===1?"st":i===2?"nd":i===3?"rd":"th";return buildExample({generatorId:t,question:n,title:"Which decimal does the marked point show?",computed:{D:o,N:i,decTenths:u,val:a,answer:a},steps:[{visual_data:d({marker_at:a}),animation:"show_start",caption:`The line is split into ${o} equal parts. Find the marked point.`,spoken:`The line from nought to one is split into ${numberToWords(o)} equal parts. A point is marked somewhere on it.`},{visual_data:d({marker_at:a,marked:Array.from({length:o-1},(h,f)=>round2$2((f+1)/o)).filter(h=>h!==a)}),animation:"count_parts",caption:`The point sits on the ${numberToWords(i)}${m} mark: ${i} of the ${o} equal parts.`,spoken:`Counting ${numberToWords(i)} of the ${numberToWords(o)} equal parts gives us the fraction ${numberToWords(i)} over ${numberToWords(o)}.`},{visual_data:d({unknown:a,conclude_label:`0.${u}`}),animation:"land",caption:`${i}/${o} = ${u}/10 = 0.${u}.`,spoken:`Splitting into tenths, ${numberToWords(i)} over ${numberToWords(o)} is the same as ${wUnit$1(u,"tenth")} — nought point ${numberToWords(u)}.`}]})}const FDP_P4_M02_GENERATORS=[{id:"FDP.P4.M02.fraction-to-decimal.v1",microSkillId:"FDP.P4.M02",forms:["symbolic_calculation","equation_completion","fraction_identification","visual_quantity_to_symbol","varied_symbolic_calculation","comparison_statement","context_calculation","misconception_contrast_probe","number_line_location"],generate(n){if(n.mathematicalForm==="misconception_contrast_probe")return claimCheckM02(n,this.id);if(n.mathematicalForm==="number_line_location")return numberLineFifths(n,this.id);if(n.mathematicalForm==="comparison_statement"){const d=compareFracDec(n,this.id);if(d)return d}const t=parseM02(n);if(!t)return null;const{n:e,N:s,D:o,mode:r,given:a}=t;if(tenthsFromAnswer(n.correctAnswer)!==e)return null;const i=Number(n.correctAnswer),l=Number.isFinite(i)?i:e,u=e/10;if(r==="varied"){const d=o===2?"half":"fifth",m=o===2?"halves":"fifths",h=10/o;return buildExample({generatorId:this.id,question:n,title:`How do we write ${s}/${o}
71 as a decimal?`,computed:{n:e,N:s,D:o,decimal:u,answer:l},steps:[{visual_data:bar$2(o,s),animation:"show_parts",caption:`${s}/${o} — but tenths need 10 parts.`,spoken:`To write ${numberToWords(s)} ${s===1?d:m} as a decimal, we need tenths. The bottom number is ${numberToWords(o)}, so the strip is in ${numberToWords(o)} parts, not ten, and we cannot count it in tenths yet.`},{visual_data:bar$2(10,e),animation:"subdivide_parts",caption:`Split each part: now ${e}/10.`,spoken:`We split every ${d} into ${numberToWords(h)} equal pieces. Now the strip has ten parts, and the shaded part has become ${numberToWords(e)} of them.`},{visual_data:bar$2(10,e,`0.${e}`),animation:"write_decimal",caption:`${s}/${o} = ${e}/10 = 0.${e}`,spoken:`${capitalised(wUnit$1(e,"tenth"))} is the same as ${numberToWords(s)} ${s===1?d:m}. In decimals, ${wUnit$1(e,"tenth")} is nought point ${numberToWords(e)}.`}]})}return buildExample(a==="decimal"?{generatorId:this.id,question:n,title:`What tenths does 0.${e} show?`,computed:{n:e,decimal:u,answer:l},steps:[{visual_data:bar$2(10,0),animation:"show_parts",caption:`0.${e} — split a strip into 10 equal parts.`,spoken:`Let's see what nought point ${numberToWords(e)} shows as a fraction. We split a strip into ten equal parts, one for each tenth.`},{visual_data:bar$2(10,e),animation:"shade_parts",caption:`The digit after the point is ${e}, so shade ${nUnit$1(e,"part")}.`,spoken:`The digit right after the point counts the tenths. Here that digit is ${numberToWords(e)}, so we shade ${wUnit$1(e,"part")} to check.`},{visual_data:bar$2(10,e,`${e}/10`),animation:"write_decimal",caption:`0.${e} = ${e}/10`,spoken:`So nought point ${numberToWords(e)} is the same as ${wUnit$1(e,"tenth")} — ${numberToWords(e)} over ten.`}]}:{generatorId:this.id,question:n,title:`How do we convert ${e}/10 to a decimal?`,computed:{n:e,decimal:u,answer:l},steps:[{visual_data:bar$2(10,0),animation:"show_parts",caption:"Split the strip into 10 equal parts.",spoken:"The bottom of the fraction is ten, so we split the strip into ten equal parts, ready to fill."},{visual_data:bar$2(10,e),animation:"shade_parts",caption:`Shade ${nUnit$1(e,"part")}: that is ${e}/10.`,spoken:`The top number is ${numberToWords(e)}, so we shade ${numberToWords(e)} of the ten parts.`},{visual_data:bar$2(10,e,`0.${e}`),animation:"write_decimal",caption:`${e}/10 = 0.${e}`,spoken:`Tenths go in the first place after the point. So ${wUnit$1(e,"tenth")} becomes nought point ${numberToWords(e)}.`}]})}}],round4$4=n=>Math.round(n*1e4)/1e4,dwN=n=>decimalToWords(n).replace(/^zero point/,"nought point"),nUnit=(n,t)=>`${n} ${t}${n===1?"":"s"}`,wUnit=(n,t)=>`${numberToWords(n)} ${t}${n===1?"":"s"}`,WORD_TO_NUM$1={one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9};function parseM03(n){const t=normaliseStem(n.stem);let e=t.match(/A pound is split into 100 equal pence\..*?(\d+) pence\..*decimal/is);if(e){const r=Number(e[1]);return r<1||r>99?null:{v:r/100,mode:"decimal",N:r}}if(e=t.match(/A hundred-square is split into 100 equal parts\..*?(\d+) of the parts are shaded in\..*decimal/is),e){const r=Number(e[1]);return r<1||r>99?null:{v:r/100,mode:"decimal",N:r}}let s=t.match(/(\d+)\/100 = 0\.0__/);if(s){const r=Number(s[1]);return r<1||r>9?null:{v:r/100,mode:"digit"}}if(s=t.match(/(\d+)\/100/),s&&/(as a decimal|which decimal is the same as|which decimal is correct)/i.test(t)){const r=Number(s[1]);return r<1||r>99?null:{v:r/100,mode:"decimal",N:r}}if(s=t.match(/which digit is in the hundredths place of 0\.(\d+)/i)??t.match(/In 0\.(\d+), which digit is in the hundredths place/i)??t.match(/In 0\.(\d+), how many hundredths does the digit .* stand for/i)??t.match(/0\.(\d+) has \d tenths\. (?:How many hundredths does it have|What is its hundredths digit)/i),s)return{v:+`0.${s[1]}`,mode:"digit"};if(s=t.match(/0\.(\d+) is the same as __ hundredths/i)??t.match(/How many hundredths are there in 0\.(\d+)/i)??t.match(/0\.(\d+) is the same as how many hundredths/i),s)return{v:+`0.${s[1]}`,mode:"count"};const o=t.match(/^Which decimal means ([a-z]+) hundredths?\??$/i);if(o){const r=WORD_TO_NUM$1[o[1].toLowerCase()];return r===void 0||r<1||r>9?null:{v:r/100,mode:"decimal",N:r}}return null}const line$3=(n,t,e={})=>({type:"number_line",min:n,max:t,step:.01,marked:[],...e}),DIGIT_VALUE_OF$1=/^In 0\.(\d)(\d), what is the value of the digit (\d)\??$/i,digitValue$4={id:"FDP.P4.M03.digit-value.v1",microSkillId:"FDP.P4.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(DIGIT_VALUE_OF$1);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);let r;if(o===e&&o!==s)r="tenths";else if(o===s&&o!==e)r="hundredths";else return null;if(n.correctAnswer.trim()!==`${o} ${r}`)return null;const a=`0.${e}${s}`;if((r==="tenths"?s/100:e/10)===0){const m=r==="tenths"?"hundredths":"tenths";return buildExample({generatorId:this.id,question:n,title:`What is the value of the digit ${o} in ${a}?`,computed:{decimal:Number(a),digit:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[a]},animation:"show_start",caption:`Look at the number ${a}.`,spoken:`Let's look at ${dwN(a)}. Each place after the point has a name — tenths, then hundredths — and the place a digit sits in tells us what it is 
71worth.`},{visual_data:{type:"equation_display",terms:[a],highlight:[0]},animation:"pulse_unknown",caption:`The ${m} place is nought — the ${o} sits in the ${r} place.`,spoken:`The ${m} place is nought here, so the digit ${numberToWords(o)} sits on its own, in the ${r} place.`},{visual_data:{type:"equation_display",terms:[a,"=",String(o),r],highlight:[0]},animation:"write_answer",caption:`${o} is worth ${o} ${r}.`,spoken:`So the ${numberToWords(o)} is worth ${numberToWords(o)} ${r}.`}]})}const l=[{value:e/10,isTarget:r==="tenths"},{value:s/100,isTarget:r==="hundredths"}].filter(m=>m.value>0||m.isTarget),u=[a,"="],d=[a,"="];return l.forEach((m,h)=>{h>0&&(u.push("+"),d.push("+")),u.push(m.isTarget?"__":m.value),d.push(m.value)}),buildExample({generatorId:this.id,question:n,title:`What is the value of the digit ${o} in ${a}?`,computed:{decimal:Number(a),digit:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[a]},animation:"show_start",caption:`Look at the number ${a}.`,spoken:`Let's look at ${dwN(a)}. Each place after the point has a name — tenths, then hundredths — and the place a digit sits in tells us what it is worth.`},{visual_data:{type:"equation_display",terms:u},animation:"pulse_unknown",caption:`The ${o} sits in the ${r} place.`,spoken:`The digit ${numberToWords(o)} sits in the ${r} place, so it counts ${r}.`},{visual_data:{type:"equation_display",terms:d},animation:"write_answer",caption:`${o} is worth ${o} ${r}.`,spoken:`So the ${numberToWords(o)} is worth ${numberToWords(o)} ${r}.`}]})}},hbar=(n,t)=>({type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:n,color:"#2563eb",...t?{conclude_label:t}:{}}),claimCheckM03={id:"FDP.P4.M03.claim-check.v1",microSkillId:"FDP.P4.M03",forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(/^(\w+) says 0\.(\d) is the same as (\d+) hundredths\. Is (?:he|she|they) right\?$/i);if(!e)return null;const s=e[1],o=Number(e[2]),r=Number(e[3]),a=o*10;if(a===r||/wrong/i.test(n.correctAnswer)!==(a!==r))return null;const l=o/10,u=(d={})=>({type:"number_line",min:0,max:1,step:.1,marked:[],...d});return buildExample({generatorId:this.id,question:n,title:`${s} says 0.${o} is the same as ${r} hundredths — is that right?`,computed:{tenthDigit:o,trueHundredths:a,claimed:r,answer:a},steps:[{visual_data:hbar(o),animation:"shade_parts",caption:`0.${o} means ${nUnit(o,"tenth")} shaded.`,spoken:`Nought point ${numberToWords(o)} means ${wUnit(o,"tenth")} shaded.`},{visual_data:u({marker_at:l,under_labels:Object.fromEntries(Array.from({length:o+1},(d,m)=>[m/10,String(m*10)]))}),animation:"switch_to_line",caption:"Each tenth is worth 10 hundredths.",spoken:`The line is marked in tenths. The labels below count in tens of hundredths: ${Array.from({length:o},(d,m)=>numberToWords((m+1)*10)).join(", ")}. So ${numberToWords(o)} tenths contains ${numberToWords(a)} hundredths.`},{visual_data:u({hops:[{from:0,to:l,label:nUnit(a,"hundredth")}],unknown:l,conclude_label:`${s} is wrong — it's ${a} hundredths, not ${r}.`}),animation:"land",caption:`${s} is wrong: 0.${o} is ${a} hundredths, not ${r}.`,spoken:`So ${s} is wrong. Nought point ${numberToWords(o)} is ${numberToWords(a)} hundredths, not ${numberToWords(r)}.`}]})}},compareHundredthsDec={id:"FDP.P4.M03.compare-hundredths.v1",microSkillId:"FDP.P4.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem);if(!/greater|bigger|larger/i.test(t))return null;const e=t.match(/(\d+) hundredths or 0\.(\d)\b/i);if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(s<1||s>99)return null;const r=round4$4(s/100),a=o/10;if(r===a)return null;const i=r>a,l=i?`${s} hundredths`:`0.${o}`;if(n.correctAnswer.trim()!==l)return null;const u=`0.${String(s).padStart(2,"0")}`,d=(m={})=>({type:"number_line",min:0,max:1,step:.1,marked:[r,a],...m});return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${s} hundredths or 0.${o}?`,computed:{hund:s,hundVal:r,decVal:a,answer:i?r:a},steps:[{visual_data:{type:"equation_display",terms:[`${s}`,"hundredths","=",u]},animation:"write_decimal",caption:`${s}
71 hundredths written as a decimal is ${u}.`,spoken:`To compare them, we write both as decimals. ${capitalised(numberToWords(s))} hundredths is ${dwN(u)}.`},{visual_data:d(),animation:"show_start",caption:`Place ${u} and 0.${o} on the line.`,spoken:"We place both decimals on a number line so we can see which is further right."},{visual_data:d({unknown:i?r:a}),animation:"land",caption:i?`${u} sits further right — it is greater than 0.${o}.`:`0.${o} sits further right — it is greater than ${s} hundredths.`,spoken:i?`${capitalised(dwN(u))} sits further to the right, so ${numberToWords(s)} hundredths is greater.`:`${capitalised(dwN(`0.${o}`))} sits further to the right, so it is greater than ${numberToWords(s)} hundredths.`}]})}};function wholeValueHundredths(n,t){const s=normaliseStem(n.stem).match(/^(\d+)\.(\d\d) is the same as how many hundredths\?$/i);if(!s)return null;const o=Number(s[1]),r=s[2],a=Number(r);if(o<1||o>8)return null;const i=o*100+a;if(Number(n.correctAnswer)!==i)return null;const l=`${o}.${r}`,u=o*100;return buildExample({generatorId:t,question:n,title:`How many hundredths are in ${l}?`,computed:{whole:o,fracVal:a,wholeHundreds:u,cents:i,answer:i},steps:[{visual_data:{type:"equation_display",terms:[l]},animation:"show_start",caption:`Look at ${l}.`,spoken:`Let's find how many hundredths are in ${dwN(l)}. One whole is a hundred hundredths, so we start with the wholes.`},{visual_data:{type:"equation_display",terms:[String(o),"×","100","=",String(u)]},animation:"pulse_unknown",caption:`${o} whole${o===1?"":"s"} = ${u} hundredths.`,spoken:`Each whole is a hundred hundredths, so ${numberToWords(o)} whole${o===1?"":"s"} ${o===1?"is":"are"} ${numberToWords(u)} hundredths.`},{visual_data:{type:"equation_display",terms:[String(u),"+",String(a),"=",String(i)]},animation:"land",caption:`${u} + ${a} = ${i} hundredths.`,spoken:`Add the extra ${wUnit(a,"hundredth")}: ${numberToWords(u)} plus ${numberToWords(a)} makes ${numberToWords(i)} hundredths.`}]})}function zeroHundredthsDigit(n,t){const s=normaliseStem(n.stem).match(/^In 0\.(\d)0, what digit is in the hundredths place\?$/i);if(!s)return null;const o=Number(s[1]);if(Number(n.correctAnswer)!==0)return null;const r=`0.${o}0`,a=o/10,i=(l={})=>({type:"number_line",min:0,max:1,step:.1,marked:[],...l});return buildExample({generatorId:t,question:n,title:`What digit is in the hundredths place of ${r}?`,computed:{tenthDigit:o,val:a,digit:0,answer:0},steps:[{visual_data:i({marker_at:a}),animation:"show_start",caption:"Find the marked point.",spoken:"To find the hundredths digit, we look at where the point sits on a line of tenths."},{visual_data:i({marked:[a]}),animation:"read_tenths",caption:`The point lands exactly on ${r}.`,spoken:"The point lands exactly on a tenths mark — there is no little step past it yet."},{visual_data:i({marked:[a],unknown:a,conclude_label:"0"}),animation:"land",caption:"No extra hundredth steps past the tenth, so the hundredths digit is 0.",spoken:"There are no extra little hundredth steps past the tenth mark, so the hundredths digit is nought."}]})}const WHICH_DIGIT_TENTHS_PLACE=/^Which digit is in the tenths place of 0\.(\d)(\d)\??$/i;function tenthsPlaceDigit(n,t){const s=normaliseStem(n.stem).match(WHICH_DIGIT_TENTHS_PLACE);if(!s)return null;const o=Number(s[1]),r=Number(s[2]);if(Number(n.correctAnswer)!==o)return null;const a=`0.${o}${r}`;return buildExample({generatorId:t,question:n,title:`Which digit is in the tenths place of ${a}?`,computed:{tenthDigit:o,hundredthDigit:r,answer:o},steps:[{visual_data:{type:"equation_display",terms:[a]},animation:"show_start",caption:`Look at ${a}.`,spoken:`Let's look at ${dwN(a)}. Each place after the point has a name: the place right next to the point is tenths, and the next place along is hundredths.`},{visual_data:{type:"equation_display",terms:[a,"=",String(o),"tenths","+",String(r),"hundredths"],highlight:[2]},animation:"pulse_unknown",caption:`The tenths digit is right next to the point: ${o}.`,spoken:`The digit right next to the point is the tenths digit. In ${dwN(a)}, that digit is ${numberToWords(o)} — the ${numberToWords(r)} sits one place further along, in hundredths.`},{visual_data:{type:"equation_display",terms:["Tenths digit","=",o],highlight:[2]},animation:"land",caption:`The tenths digit of ${a} is ${o}
71.`,spoken:`So the tenths digit of ${dwN(a)} is ${numberToWords(o)}.`}]})}const FRACTION_X0_OVER_100_AS_DECIMAL=/^Write (\d)0\/100 as a decimal\??$/i;function zeroHundredthsFractionToDecimal(n,t){const s=normaliseStem(n.stem).match(FRACTION_X0_OVER_100_AS_DECIMAL);if(!s)return null;const o=Number(s[1]);if(o<1||o>9)return null;const r=o*10,a=`0.${o}0`;if(n.correctAnswer.trim()!==a)return null;const i=o/10,l=(u={})=>({type:"number_line",min:0,max:1,step:.1,marked:[],...u});return buildExample({generatorId:t,question:n,title:`Write ${r}/100 as a decimal.`,computed:{N:r,tenthDigit:o,val:i,answer:Number(a)},steps:[{visual_data:{type:"equation_display",terms:[`${r}/100`]},animation:"show_start",caption:`Look at ${r}/100.`,spoken:`Let's write ${numberToWords(r)} hundredths as a decimal. Every ten hundredths make one tenth, so we look for whole tenths first.`},{visual_data:l({marker_at:i}),animation:"pulse_unknown",caption:`${r} hundredths is exactly ${nUnit(o,"tenth")} — no extra hundredths past it.`,spoken:`Ten hundredths make each tenth, so ${numberToWords(r)} hundredths is exactly ${wUnit(o,"tenth")}. There are no extra hundredths left over.`},{visual_data:l({marked:[i],unknown:i,conclude_label:a}),animation:"land",caption:`${r}/100 = ${a}.`,spoken:`So ${numberToWords(r)} hundredths is written ${dwN(a)}.`}]})}function contrastHundredthsDigit(n,t){const e=normaliseStem(n.stem);if(!/^Which decimal has the digit (\d) in the hundredths place\?$/i.test(e))return null;const s=Number(e.match(/^Which decimal has the digit (\d) in the hundredths place\?$/i)[1]),o=n.correctAnswer.trim(),r=o.match(/^0\.(\d)(\d)$/);if(!r)return null;const a=Number(r[1]),i=Number(r[2]);if(i!==s||a===i)return null;const l=`0.${i}${a}`;return buildExample({generatorId:t,question:n,title:`Which decimal has the digit ${s} in the hundredths place?`,computed:{tenthDigit:a,hundredthDigit:i,digit:s,swapped:Number(l),answer:Number(o)},steps:[{visual_data:{type:"equation_display",terms:[o]},animation:"show_start",caption:`Look at ${o}.`,spoken:`Let's look at ${dwN(o)}. Each place after the point has a name, so we can check where every digit sits.`},{visual_data:{type:"equation_display",terms:[o,"=",String(a),"tenths","+",String(i),"hundredths"],highlight:[6]},animation:"pulse_unknown",caption:`${o}: ${a} tenths, ${i} hundredths.`,spoken:`Splitting it into places, ${dwN(o)} has ${numberToWords(a)} in the tenths place and ${numberToWords(i)} in the hundredths place — the second place after the point.`},{visual_data:{type:"equation_display",terms:[o,"not",l],highlight:[0]},animation:"land",caption:`${o} has ${s} in the hundredths place — not ${l}, which swaps the digits.`,spoken:`So ${dwN(o)} is correct. ${capitalised(dwN(l))} swaps the digits, putting the ${numberToWords(s)} in the tenths place instead.`}]})}function tenthsAndHundredthsBuild(n,t){const s=normaliseStem(n.stem).match(/^Which decimal shows (\d+) tenths? and (\d+) hundredths?\?$/i);if(!s)return null;const o=Number(s[1]),r=Number(s[2]);if(o<0||o>9||r<0||r>9)return null;const a=`0.${o}${r}`;if(n.correctAnswer.trim()!==a)return null;const i=`0.${r}${o}`;return i===a?null:buildExample({generatorId:t,question:n,title:`Which decimal shows ${nUnit(o,"tenth")} and ${nUnit(r,"hundredth")}?`,computed:{tenths:o,hund:r,trap:Number(i),answer:Number(a)},steps:[{visual_data:hbar(o),animation:"shade_parts",caption:`Shade ${nUnit(o,"tenth")}: that is the tenths place.`,spoken:`We shade ${wUnit(o,"tenth")} first — that fills the tenths place.`},{visual_data:hbar(o,`${a} so far`),animation:"count_hundredths",caption:`Add ${nUnit(r,"hundredth")} more: still ${a}.`,spoken:`Now we add ${wUnit(r,"hundredth")} more. That gives us ${dwN(a)} altogether.`},{visual_data:hbar(o,`${a}, not ${i}`),animation:"land",caption:`${a} is correct — not ${i}, which swaps the digits.`,spoken:`So ${dwN(a)} is correct. ${capitalised(dwN(i))} swaps the tenths and hundredths digits.`}]})}const HOW_MANY_HUNDREDTHS_IN_A_TENTH=/^How many hundredths make one tenth\?$/i;function hundredthsInATenth(n,t){const e=normaliseStem(n.stem);if(!HOW_MANY_HUNDREDTHS_IN_A_TENTH.test(e)||Number(n.correctAnswer)!==10)return null;const s=(o={})=>({type:"number_line",min:0,max:.1,step:.01,marked:[],...o});return buildExample({generatorId:t,question:n,title:"How many hundredths make one tenth?",computed:{zero:0,tenth:.1,answer:10},steps:[{visual_data:s(),animation:"show_start",caption:"A close-up of one tenth: from 0 to 0.1.",spoken:"To see the hundredths, we need a close-up of the number line. Here is just one tenth, from nought to nought point one."},{visual_data:s({hops:[{from:0,to:.1,label:"10 hundredths"}]}),animation:"count_parts",caption:"Count every little hundredth step across it.",spoken:"Counting every little hundredth step across the tenth gives ten steps."},{visual_data:s({unknown:.1,conclude_label:"10"}),animation:"land",caption:"10 hundredths make one tenth.",spoken:"We counted ten little steps, and each one is a hundredth. So ten hundredths make one tenth."}]})}const DECIMAL_EQUAL_TO_TENTH=/^Which decimal is equal to 0\.(\d)\?$/i;function tenthsHundredthsEquivalence(n,t){const s=normaliseStem(n.stem).match(DECIMAL_EQUAL_TO_TENTH);if(!s)return null;const o=Number(s[1]);if(o<1||o>9)return null;const r=`0.${o}`,a=o*10,i=`0.${o}0`;return n.correctAnswer.trim()!==i?null:buildExample({generatorId:t,question:n,title:`Which decimal is equal to ${r}
71?`,computed:{tenthDigit:o,hundredthsCount:a,answer:Number(i)},steps:[{visual_data:{type:"equation_display",terms:[r,"=",String(o),"tenths"]},animation:"show_start",caption:`${r} is ${nUnit(o,"tenth")}.`,spoken:`To write ${dwN(r)} another way, we start from its tenths. ${capitalised(dwN(r))} is ${wUnit(o,"tenth")}.`},{visual_data:{type:"equation_display",terms:[String(o),"tenths","=",String(a),"hundredths"]},animation:"pulse_unknown",caption:`${nUnit(o,"tenth")} = ${nUnit(a,"hundredth")}.`,spoken:`Each tenth is ten hundredths, so ${wUnit(o,"tenth")} is ${numberToWords(a)} hundredths.`},{visual_data:{type:"equation_display",terms:[r,"=",i]},animation:"land",caption:`${r} = ${i} — the trailing zero doesn't change the value.`,spoken:`So ${dwN(r)} is the same as ${dwN(i)}. Writing a nought in the hundredths place doesn't change the value.`}]})}const SMALLEST_OF_THREE=/^Which of these is the smallest\? (0\.\d{1,2}), (0\.\d{1,2}), (0\.\d{1,2})$/i;function smallestOfThreeHundredths(n,t){const s=normaliseStem(n.stem).match(SMALLEST_OF_THREE);if(!s)return null;const o=[s[1],s[2],s[3]],r=o.map(Number),a=Math.min(...r),i=r.indexOf(a),l=o[i];if(n.correctAnswer.trim()!==l)return null;const u=m=>Number(m.split(".")[1][0]),d=o.map(u);return buildExample({generatorId:t,question:n,title:`Which of these is the smallest? ${o.join(", ")}`,computed:{a:r[0],b:r[1],c:r[2],answer:a},steps:[{visual_data:{type:"equation_display",terms:o},animation:"show_start",caption:"Compare the tenths digit of each number first.",spoken:`Let's compare ${o.map(m=>dwN(m)).join(", ")}. Tenths are worth more than hundredths, so the tenths digit — the first digit after the point — tells us the size first.`},{visual_data:{type:"equation_display",terms:[String(d[0]),"vs",String(d[1]),"vs",String(d[2])]},animation:"pulse_unknown",caption:`Tenths digits: ${d.join(", ")} — ${l} has the fewest tenths.`,spoken:`The tenths digits are ${d.map(m=>numberToWords(m)).join(", ")}. ${capitalised(dwN(l))} has the fewest tenths, so it is the smallest so far.`},{visual_data:{type:"equation_display",terms:o,highlight:[i]},animation:"land",caption:`${l} is the smallest.`,spoken:`So ${dwN(l)} is the smallest of the three.`}]})}function hundredthsDenominator(n,t){const e=normaliseStem(n.stem).match(/^Fill the box: 0\.0(\d) = (\d+)\/__$/i);if(!e)return null;const s=Number(e[1]),o=100;if(s<1||Number(e[2])!==s||Number(n.correctAnswer)!==o)return null;const r=s/o;return buildExample({generatorId:t,question:n,title:`Complete ${r} = ${s}/__.`,computed:{numerator:s,denominator:o,decimal:r,answer:o},steps:[{visual_data:{type:"equation_display",terms:[r,"=",`${s}/__`]},animation:"show_start",caption:`${r}: the ${s} is in the hundredths place.`,spoken:`The zero just after the point holds the tenths place. The ${numberToWords(s)} is in the next place, which counts hundredths.`},{visual_data:line$3(0,.1,{hops:[{from:0,to:r,label:`${s} hundredths`}]}),animation:"count_ticks",caption:`${s} hundredths means ${s} parts out of 100.`,spoken:`Each little step is one hundredth of a whole. ${capitalised(numberToWords(s))} steps give ${numberToWords(s)} hundredths, so the fraction is out of one hundred.`},{visual_data:{type:"equation_display",terms:[r,"=",`${s}/${o}`],highlight:[2]},animation:"write_answer",caption:`${r} = ${s}/${o}; the missing denominator is ${o}.`,spoken:`So ${dwN(r)} equals ${numberToWords(s)} over one hundred. The missing bottom number is one hundred.`}]})}const FDP_P4_M03_GENERATORS=[{id:"FDP.P4.M03.hundredths-line.v1",microSkillId:"FDP.P4.M03",forms:["equation_completion","misconception_contrast_probe","context_calculation","fraction_identification"],generate(n){if(n.mathematicalForm==="equation_completion"){const N=hundredthsDenominator(n,this.id);if(N)return N;const S=wholeValueHundredths(n,this.id);if(S)return S;const M=zeroHundredthsDigit(n,this.id);if(M)return M;const L=hundredthsInATenth(n,this.id);if(L)return L;const P=tenthsHundredthsEquivalence(n,this.id);if(P)return P;const F=tenthsPlaceDigit(n,this.id);if(F)return F;const D=zeroHundredthsFractionToDecimal(n,this.id);if(D)return D}if(n.mathematicalForm==="misconception_contrast_probe"){const N=tenthsAndHundredthsBuild(n,this.id);if(N)return N;const S=smallestOfThreeHundredths(n,this.id);if(S)return S}const t=parseM03(n);if(!t)return null;const{v:e,mode:s}=t;if(!(e>0&&e<1))return null;const o=Math.round(e*100);if(!Number.isInteger(o)||o<1)return null;if(s==="count"&&e>=.1&&o%10!==0){if(Number(n.correctAnswer)!==o)return null;const N=String(e),S=Math.floor(o/10),M=o%10,L=S*10;return buildExample({generatorId:this.id,question:n,title:`How many hundredths are in ${N}?`,computed:{v:e,tenthsPart:S,hundredthsDigit:M,tenthsAsHundredths:L,cents:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[N]},animation:"show_start",caption:`Look at ${N}.`,spoken:`Let's find how many hundredths are in ${dwN(N)}. First we split it into tenths and hundredths.`},{visual_data:{type:"equation_display",terms:[N,"=",String(S),"tenths","+",String(M),"hundredths"]},animation:"pulse_unknown",caption:`${N} = ${nUnit(S,"tenth")} + ${nUnit(M,"hundredth")}.`,spoken:`The first digit after the point is the tenths, and the next is the hundredths. So ${dwN(N)} is ${wUnit(S,"tenth")} and ${wUnit(M,"hundredth")}.`},{visual_data:{type:"equation_display",terms:[String(L),"+",String(M),"=",String(o)]},animation:"land",caption:`${L} + ${M} = ${o} hundredths.`,spoken:`Each tenth is ten hundredths, so ${wUnit(S,"tenth")} is ${numberToWords(L)}
71 hundredths. Add the extra ${wUnit(M,"hundredth")}: ${numberToWords(o)} hundredths altogether.`}]})}if(s==="count"&&e>=.1){if(Number(n.correctAnswer)!==o)return null;const N=o/10,S=String(e),M=P=>({type:"number_line",min:0,max:1,step:.1,marked:[e],...P}),L={from:0,to:e,label:nUnit(N,"tenth")};return buildExample({generatorId:this.id,question:n,title:`How many hundredths are in ${S}?`,computed:{v:e,tenths:N,count:o,answer:o},steps:[{visual_data:M({hops:[]}),animation:"show_start",caption:"The line is marked in tenths.",spoken:`Let's find how many hundredths are in ${dwN(S)}. The line is marked in tenths — and every tenth holds ten hundredths.`},{visual_data:M({hops:[L]}),animation:"read_places",caption:`${S} is ${nUnit(N,"tenth")}.`,spoken:`Count the tenth ticks up to the mark: ${dwN(S)} is ${wUnit(N,"tenth")}.`},{visual_data:M({hops:[L],unknown:e}),animation:"land",caption:`${N} tenths = ${o} hundredths.`,spoken:`Each tenth is ten hundredths, so ${wUnit(N,"tenth")} is ${numberToWords(o)} hundredths.`}]})}const r=e<.1?0:round4$4(Math.floor(e*10)/10),a=round4$4(r+.1),i=Math.round((e-r)*100),l=Math.floor(round4$4(e*10)),u=o%10,d=`0.${String(o).padStart(2,"0")}`;let m=!1,h;if(s==="count"){if(e>=.1)return null;m=Number(n.correctAnswer)===o,h=o}else if(s==="digit"){if(i<1)return null;m=Number(n.correctAnswer)===u,h=u}else{if(i<1)return null;m=n.correctAnswer.trim()===d,h=e}if(!m)return null;if(s==="decimal"&&l>0){const N=l*10,S=/pound.*pence/i.test(normaliseStem(n.stem)),M=S?`£${d}`:d;return buildExample({generatorId:this.id,question:n,title:S?`Write ${o}p in pounds.`:`Write ${o}/100 as a decimal.`,computed:{v:e,lo:r,hi:a,tenths:l,cents:o,steps:i,groupedHundredths:N,answer:h},steps:[{visual_data:{type:"equation_display",terms:[o,"=",N,"+",i],fit:!0},animation:"show_start",caption:`Split ${o} ${S?"pence":"hundredths"}: ${N} + ${i}.`,spoken:S?`A pound is one hundred pence, so each penny is one hundredth of a pound. Split ${numberToWords(o)} pence into ${numberToWords(N)} pence and ${numberToWords(i)} pence.`:`To write hundredths as a decimal, group them in tens. Split ${numberToWords(o)} hundredths into ${numberToWords(N)} and ${numberToWords(i)}.`},{visual_data:{type:"equation_display",terms:[N,"÷",10,"=",l],fit:!0},animation:"write_answer",caption:`${N} hundredths = ${nUnit(l,"tenth")}; ${nUnit(i,"hundredth")} left.`,spoken:`Ten hundredths make one tenth. ${capitalised(numberToWords(N))} divided by ten gives ${wUnit(l,"group")}: ${wUnit(l,"tenth")}, with ${wUnit(i,"hundredth")} left.`},{visual_data:line$3(r,a,{marked:[e],hops:[{from:r,to:e,label:nUnit(i,"hundredth")}],unknown:e,conclude_label:M}),animation:"land",caption:`${nUnit(l,"tenth")} + ${nUnit(i,"hundredth")} = ${M}.`,spoken:`Start at ${dwN(r)} and move ${wUnit(i,"hundredth")} along. The tenths digit is ${numberToWords(l)} and the hundredths digit is ${numberToWords(i)}, giving ${dwN(d)}${S?" pounds":""}.`}]})}const f=i>=1?[{from:r,to:e,label:nUnit(i,"hundredth")}]:[],g=String(r),b=String(a),x=r===0?"This whole stretch is one tenth.":`Close-up: ${g} to ${b}.`,y=r===0?"To see hundredths, we need a close-up of the number line. This one goes from nought to one tenth, so every little step is one hundredth.":`To see hundredths, we need a close-up of the number line. Here is just one tenth, from ${dwN(r)} to ${dwN(a)}. Every little step along it is one hundredth.`;let E,k;s==="decimal"?(E=`${nUnit(l,"tenth")} and ${nUnit(i,"hundredth")}.`,k=`We place it: ${wUnit(l,"tenth")} along, then ${wUnit(i,"more little hundredth step")}. Each little step is one hundredth.`):r===0?(E=`${nUnit(i,"step")} from 0: ${nUnit(i,"hundredth")}.`,k=`We take ${wUnit(i,"little step")} from nought. Each step is one hundredth, so we have ${wUnit(i,"hundredth")}.`):(E=`${nUnit(i,"step")} past ${g}: ${nUnit(i,"hundredth")}.`,k=`From ${dwN(r)} we take ${wUnit(i,"little step")}. Each step is one hundredth, so we are ${wUnit(i,"hundredth")} past ${dwN(r)}.`);let T,A;return s==="count"?(T=`${nUnit(i,"hundredth")} = ${d}`,A=`${capitalised(wUnit(i,"hundredth"))} is written ${dwN(d)}. The zero straight after the point holds the tenths place, because there are no tenths, and the ${numberToWords(i)} sits in the hundredths place.`):s==="digit"?(T=`The hundredths digit of ${d} is ${u}.`,A=`The digit in the hundredths place is the number of little steps past the tenth. So in ${dwN(d)}, the hundredths digit is ${numberToWords(u)}
71.`):(T=`${t.N}/100 = ${d}`,A=`${capitalised(wUnit(l,"tenth"))} and ${wUnit(i,"hundredth")} is written ${dwN(d)}. So ${wUnit(t.N,"hundredth")} is ${dwN(d)}.`),buildExample({generatorId:this.id,question:n,title:`What does the hundredths place mean in ${d}?`,computed:{v:e,lo:r,hi:a,steps:i,tenths:l,cents:o,answer:h},steps:[{visual_data:line$3(r,a),animation:"show_start",caption:x,spoken:y},{visual_data:line$3(r,a,{marked:[e],hops:f}),animation:"show_place",caption:E,spoken:k},{visual_data:line$3(r,a,{marked:[e],hops:i>=1?[{from:r,to:e,label:""}]:[],unknown:e}),animation:"land",caption:T,spoken:A}]})}},digitValue$4,claimCheckM03,compareHundredthsDec,{id:"FDP.P4.M03.contrast-hundredths-digit.v1",microSkillId:"FDP.P4.M03",forms:["comparison_statement"],generate(n){return contrastHundredthsDigit(n,this.id)}},{id:"FDP.P4.M03.tenths-hundredths-equivalence.v1",microSkillId:"FDP.P4.M03",forms:["comparison_statement"],generate(n){return tenthsHundredthsEquivalence(n,this.id)}}],round4$3=n=>Math.round(n*1e4)/1e4,decimalWords$1=n=>decimalToWords(n).replace(/^zero point/,"nought point"),closeUp=(n,t)=>`a close-up of the number line, from ${decimalWords$1(String(n))} to ${decimalWords$1(String(t))}`,UNIT_WORDS$1={kg:"kilograms",g:"grams",km:"kilometres",m:"metres",cm:"centimetres",mm:"millimetres",l:"litres",ml:"millilitres",s:"seconds"},unitWords=n=>UNIT_WORDS$1[n.toLowerCase()]??n,dp$1=n=>{const t=n.indexOf(".");return t<0?0:n.length-t-1};function parseM04$1(n){const t=normaliseStem(n.stem),e="(\\d+(?:\\.\\d+)?)";let s=t.match(new RegExp(`Which symbol goes in the box: ${e} __ ${e}`,"i"));if(s)return{aStr:s[1],bStr:s[2],a:Number(s[1]),b:Number(s[2]),ask:"symbol"};if(s=t.match(new RegExp(`Which(?: decimal)? is (greater|larger|smaller): ${e} or ${e}`,"i")),s)return{aStr:s[2],bStr:s[3],a:Number(s[2]),b:Number(s[3]),ask:s[1].toLowerCase()==="smaller"?"smaller":"greater"};const o=n.visualData;return/Which symbol/i.test(t)&&o&&o.type==="comparison"?{aStr:String(o.left),bStr:String(o.right),a:o.left,b:o.right,ask:"symbol"}:null}function windowFor$1(n,t){const e=round4$3(Math.floor(10*Math.min(n,t))/10);let s=round4$3(Math.ceil(10*Math.max(n,t))/10);s-e<.2&&(s=round4$3(e+.2));const r=round4$3(s-e)>.2?.1:.01;return{lo:e,hi:s,step:r,minSep:r===.1?.05:.01}}function sprintCompare(n,t){const s=normaliseStem(n.stem).match(/^(?:In [a-z ]+?, )?(\w+)'s ([a-z ]+) is (\d+(?:\.\d+)?) (\w+)\. (\w+)'s \2 is (\d+(?:\.\d+)?) \4\. Who (?:ran faster|is faster|was faster)/i);if(!s)return null;const[,o,,r,,a,i]=s,l=Number(r),u=Number(i);if(dp$1(r)>2||dp$1(i)>2||!(l>0&&l<10&&u>0&&u<10)||l===u)return null;const{lo:d,hi:m,step:h,minSep:f}=windowFor$1(l,u);if(Math.abs(l-u)<f)return null;const g=l<u,b=g?o:a;if(n.correctAnswer.trim()!==b)return null;const x=Math.min(l,u),y=Math.max(l,u),E=g?r:i,k=g?i:r,T=Math.floor(round4$3(x*10))%10,A=Math.floor(round4$3(y*10))%10,N=Math.round(x*100)%10,S=Math.round(y*100)%10,M=T===A,L=(P={})=>({type:"number_line",min:d,max:m,step:h,marked:[],...P});return buildExample({generatorId:t,question:n,title:`Who ran faster: ${o} (${r}s) or ${a} (${i}s)?`,computed:{val1:l,val2:u,winner:x,lo:d,hi:m,answer:x},steps:[{visual_data:L({marked:[l,u]}),animation:"show_start",caption:`Place both times on the line: ${o} ${r}s, ${a} ${i}s.`,spoken:`A shorter time is a faster time, so we put both times on ${closeUp(d,m)}, and look for the one further to the left.`},{visual_data:L({marked:[l,u],hops:[{from:y,to:x,label:"further left"}]}),animation:"compare_place",caption:"Further left means a shorter, faster time.",spoken:`${cap$i(decimalWords$1(E))} sits further to the left than ${decimalWords$1(k)}, and further left means a shorter time — faster.`},{visual_data:L({marked:[l,u],hops:[{from:y,to:x,label:""}],unknown:x}),animation:"land",caption:M?`Tenths tie — hundredths decide: ${N} is less than ${S}, so ${b} is faster.`:`Tenths decide: ${T} is less than ${A}, so ${b} is faster.`,spoken:M?`The tenths are equal, so the hundredths decide. ${cap$i(numberToWords(N))} is less than ${numberToWords(S)}, so ${b}'s time of ${decimalWords$1(E)} seconds is faster.`:`The tenths decide it. ${cap$i(numberToWords(T))} is less than ${numberToWords(A)}, so ${b}'s time of ${decimalWords$1(E)}
71 seconds is faster.`}]})}function lengthCompare(n,t){const s=normaliseStem(n.stem).match(/^A (\w+) (\w+) is (\d+(?:\.\d+)?) (\w+) long\. A (\w+) \2 is (\d+(?:\.\d+)?) \4 long\. Which \2 is longer\?$/i);if(!s)return null;const[,o,r,a,i,l,u]=s,d=Number(a),m=Number(u);if(dp$1(a)>2||dp$1(u)>2||!(d>0&&d<10&&m>0&&m<10)||d===m)return null;const{lo:h,hi:f,step:g,minSep:b}=windowFor$1(d,m);if(Math.abs(d-m)<b)return null;const x=d>m,y=x?o:l,E=x?a:u,k=`The ${y} ${r} (${E} ${i})`;if(n.correctAnswer.trim()!==k)return null;const T=Math.max(d,m),A=Math.min(d,m),N=x?u:a,S=Math.floor(round4$3(T*10))%10,M=Math.floor(round4$3(A*10))%10,L=Math.round(T*100)%10,P=Math.round(A*100)%10,F=S===M,D=(U={})=>({type:"number_line",min:h,max:f,step:g,marked:[],...U});return buildExample({generatorId:t,question:n,title:`Which ${r} is longer: ${o} (${a} ${i}) or ${l} (${u} ${i})?`,computed:{val1:d,val2:m,winner:T,lo:h,hi:f,answer:T},steps:[{visual_data:D({marked:[d,m]}),animation:"show_start",caption:`Place both ${r}s on the line: ${o} ${a}${i}, ${l} ${u}${i}.`,spoken:`The further right a number sits, the bigger it is, so we put both ${r} lengths on ${closeUp(h,f)}.`},{visual_data:D({marked:[d,m],hops:[{from:A,to:T,label:"further right"}]}),animation:"compare_place",caption:"Further right means longer.",spoken:`${cap$i(decimalWords$1(E))} sits further to the right than ${decimalWords$1(N)}, and further right always means longer.`},{visual_data:D({marked:[d,m],hops:[{from:A,to:T,label:""}],unknown:T}),animation:"land",caption:F?`Tenths tie — hundredths decide: ${L} beats ${P}, so the ${y} ${r} is longer.`:`Tenths decide: ${S} beats ${M}, so the ${y} ${r} is longer.`,spoken:F?`The tenths are equal, so the hundredths decide. ${cap$i(numberToWords(L))} is more than ${numberToWords(P)}, so the ${y} ${r} is longer.`:`The tenths decide it. ${cap$i(numberToWords(S))} is more than ${numberToWords(M)}, so the ${y} ${r} is longer.`}]})}function weighsCompare(n,t){const s=normaliseStem(n.stem).match(/^One (\w+) weighs (\d+(?:\.\d+)?) (\w+)\. Another weighs (\d+(?:\.\d+)?) \3\. Which \1 is heavier\?$/i);if(!s)return null;const[,o,r,a,i]=s,l=Number(r),u=Number(i);if(dp$1(r)>2||dp$1(i)>2||!(l>0&&l<10&&u>0&&u<10)||l===u)return null;const{lo:d,hi:m,step:h,minSep:f}=windowFor$1(l,u);if(Math.abs(l-u)<f)return null;const g=l>u,b=g?r:i,x=`The ${b} ${a} ${o}`;if(n.correctAnswer.trim()!==x)return null;const y=Math.max(l,u),E=Math.min(l,u),k=g?i:r,T=Math.floor(round4$3(y*10))%10,A=Math.floor(round4$3(E*10))%10,N=Math.round(y*100)%10,S=Math.round(E*100)%10,M=T===A,L=(P={})=>({type:"number_line",min:d,max:m,step:h,marked:[],...P});return buildExample({generatorId:t,question:n,title:`One ${o} weighs ${r} ${a}. Another weighs ${i} ${a}. Which ${o} is heavier?`,computed:{val1:l,val2:u,winner:y,lo:d,hi:m,answer:y},steps:[{visual_data:L({marked:[l,u]}),animation:"show_start",caption:`Place both weights on the line: ${r} ${a} and ${i} ${a}.`,spoken:`The further right a number sits, the bigger it is, so we put both weights on ${closeUp(d,m)}.`},{visual_data:L({marked:[l,u],hops:[{from:E,to:y,label:"further right"}]}),animation:"compare_place",caption:"Further right means heavier.",spoken:`${cap$i(decimalWords$1(b))} sits further to the right than ${decimalWords$1(k)}, and further right always means heavier.`},{visual_data:L({marked:[l,u],hops:[{from:E,to:y,label:""}],unknown:y}),animation:"land",caption:M?`Tenths tie — hundredths decide: ${N} beats ${S}, so the ${b} ${a} ${o} is heavier.`:`Tenths decide: ${T} beats ${A}, so the ${b} ${a} ${o} is heavier.`,spoken:M?`The tenths are equal, so the hundredths decide. ${cap$i(numberToWords(N))} is more than ${numberToWords(S)}, so the ${o} weighing ${decimalWords$1(b)} ${unitWords(a)} is heavier.`:`The tenths decide it. ${cap$i(numberToWords(T))} is more than ${numberToWords(A)}, so the ${o} weighing ${decimalWords$1(b)} ${unitWords(a)} is heavier.`}]})}function holdsCompare(n,t){const s=normaliseStem(n.stem).match(/^A (\w+) holds (\d+(?:\.\d+)?) (\w+)\. A (\w+) holds (\d+(?:\.\d+)?) \3\. Which holds more\?$/i);if(!s)return null;const[,o,r,a,i,l]=s,u=Number(r),d=Number(l);if(dp$1(r)>2||dp$1(l)>2||!(u>0&&u<10&&d>0&&d<10)||u===d)return null;const{lo:m,hi:h,step:f,minSep:g}=windowFor$1(u,d);
71if(Math.abs(u-d)<g)return null;const b=u>d,x=b?o:i,y=b?r:l,E=`The ${x} (${y} ${a})`;if(n.correctAnswer.trim()!==E)return null;const k=Math.max(u,d),T=Math.min(u,d),A=b?l:r,N=Math.floor(round4$3(k*10))%10,S=Math.floor(round4$3(T*10))%10,M=Math.round(k*100)%10,L=Math.round(T*100)%10,P=N===S,F=(D={})=>({type:"number_line",min:m,max:h,step:f,marked:[],...D});return buildExample({generatorId:t,question:n,title:`A ${o} holds ${r} ${a}. A ${i} holds ${l} ${a}. Which holds more?`,computed:{val1:u,val2:d,winner:k,lo:m,hi:h,answer:k},steps:[{visual_data:F({marked:[u,d]}),animation:"show_start",caption:`Place both on the line: ${o} ${r} ${a}, ${i} ${l} ${a}.`,spoken:`The further right a number sits, the bigger it is, so we put both amounts on ${closeUp(m,h)}.`},{visual_data:F({marked:[u,d],hops:[{from:T,to:k,label:"further right"}]}),animation:"compare_place",caption:"Further right means holds more.",spoken:`${cap$i(decimalWords$1(y))} sits further to the right than ${decimalWords$1(A)}, and further right always means holds more.`},{visual_data:F({marked:[u,d],hops:[{from:T,to:k,label:""}],unknown:k}),animation:"land",caption:P?`Tenths tie — hundredths decide: ${M} beats ${L}, so the ${x} holds more.`:`Tenths decide: ${N} beats ${S}, so the ${x} holds more.`,spoken:P?`The tenths are equal, so the hundredths decide. ${cap$i(numberToWords(M))} is more than ${numberToWords(L)}, so the ${x} holds more.`:`The tenths decide it. ${cap$i(numberToWords(N))} is more than ${numberToWords(S)}, so the ${x} holds more.`}]})}function equalityDemo(n,t){const s=normaliseStem(n.stem).match(/^Which symbol makes this true: (\d+(?:\.\d+)?) __ (\d+(?:\.\d+)?)\?$/i);if(!s)return null;const[,o,r]=s,a=Number(o),i=Number(r);if(dp$1(o)>2||dp$1(r)>2||a!==i||n.correctAnswer.trim()!=="=")return null;const l=a,u=round4$3(Math.floor(10*l)/10),d=round4$3(u+.2),m=(h={})=>({type:"number_line",min:u,max:d,step:.01,marked:[],...h});return buildExample({generatorId:t,question:n,title:`Which symbol makes this true: ${o} __ ${r}?`,computed:{val:l,lo:u,hi:d,answer:l},steps:[{visual_data:m({marker_at:l}),animation:"show_start",caption:`Place ${o} and ${r} on the line.`,spoken:`To compare them, we place both decimals on ${closeUp(u,d)}.`},{visual_data:m({marked:[l]}),animation:"compare_place",caption:`${o} has the same value as ${r} — a trailing zero doesn't change it.`,spoken:"A zero written at the end of the decimal places doesn't change its value."},{visual_data:m({unknown:l,conclude_label:`${o} = ${r}`}),animation:"land",caption:`Both land on the same tick: ${o} = ${r}.`,spoken:`${cap$i(decimalWords$1(o))} and ${decimalWords$1(r)} land on exactly the same point, so they are equal. The symbol is the equals sign.`}]})}const TRAILING_ZERO_DIGIT_CLAIM=/^(\w+) says (\d+(?:\.\d+)?) is bigger than (\d+(?:\.\d+)?) because it has fewer digits\. Is (?:he|she|they|\1) right\?$/i;function trailingZeroDigitClaim(n,t){const s=normaliseStem(n.stem).match(TRAILING_ZERO_DIGIT_CLAIM);if(!s)return null;const[,o,r,a]=s,i=Number(r),l=Number(a);if(dp$1(r)>2||dp$1(a)>2||i!==l||n.correctAnswer.trim()!=="No, they are equal")return null;const u=i,d=round4$3(Math.floor(10*u)/10),m=round4$3(d+.2),h=(f={})=>({type:"number_line",min:d,max:m,step:.01,marked:[],...f});return buildExample({generatorId:t,question:n,title:`${o} says ${r} is bigger than ${a} because it has fewer digits — is that right?`,computed:{a:i,b:l,val:u,lo:d,hi:m,answer:u},steps:[{visual_data:h({marker_at:u}),animation:"show_start",caption:`Place ${r} and ${a} on the line.`,spoken:`${o} says ${decimalWords$1(r)} is bigger than ${decimalWords$1(a)} because it has fewer digits. Let's check by placing both on ${closeUp(d,m)}.`},{visual_data:h({marked:[u]}),animation:"compare_place",caption:`${r} has the same value as ${a} — digit count doesn't decide size.`,spoken:"A zero written at the end of the decimal places doesn't change its value, no matter how few digits the other number has."},{visual_data:h({unknown:u,conclude_label:`${r} = ${a}`}),animation:"land",caption:`${o} is wrong: both land on the same tick, ${r} = ${a}
71.`,spoken:`So ${o} is wrong. ${cap$i(decimalWords$1(r))} and ${decimalWords$1(a)} land on exactly the same point — they are equal, not one bigger than the other.`}]})}const THREE_WAY_FASTEST=/^In a computer reaction game, three players get times of (\d+(?:\.\d+)?) s, (\d+(?:\.\d+)?) s and (\d+(?:\.\d+)?) s\. Which time is the fastest\?$/i;function threeWayFastest(n,t){const s=normaliseStem(n.stem).match(THREE_WAY_FASTEST);if(!s)return null;const o=[s[1],s[2],s[3]];if(o.some(m=>dp$1(m)>2))return null;const r=o.map(Number),a=Math.min(...r),i=r.indexOf(a),l=`${o[i]} s`;if(n.correctAnswer.trim()!==l)return null;const u=m=>Number((m.split(".")[1]??"0")[0]??"0"),d=o.map(u);return buildExample({generatorId:t,question:n,title:`Which time is the fastest: ${o.map(m=>`${m} s`).join(", ")}?`,computed:{a:r[0],b:r[1],c:r[2],answer:a},steps:[{visual_data:{type:"equation_display",terms:o.map(m=>`${m} s`)},animation:"show_start",caption:"Fastest = smallest number — compare the tenths digit first.",spoken:`Three times: ${o.map(m=>decimalWords$1(m)).join(", ")} seconds. The fastest time is the smallest number, so let's compare the tenths digit of each.`},{visual_data:{type:"equation_display",terms:[String(d[0]),"vs",String(d[1]),"vs",String(d[2])]},animation:"pulse_unknown",caption:`Tenths digits: ${d.join(", ")} — ${o[i]} has the fewest tenths.`,spoken:`The tenths digits are ${d.map(m=>numberToWords(m)).join(", ")}. ${cap$i(decimalWords$1(o[i]))} has the fewest tenths, so it is the smallest.`},{visual_data:{type:"equation_display",terms:o.map(m=>`${m} s`),highlight:[i]},animation:"land",caption:`${l} is the fastest — the smallest wins, not the longest.`,spoken:`So ${decimalWords$1(o[i])} seconds is the fastest. The smallest number wins, not the longest-looking decimal.`}]})}function claimCheckM04(n,t){const s=normaliseStem(n.stem).match(/^(\w+) says (\d+(?:\.\d+)?) is bigger than (\d+(?:\.\d+)?) because (\d+) is bigger than (\d+)\. Is (?:he|she|they) right\?$/i);if(!s)return null;const[,o,r,a,i,l]=s,u=Number(r),d=Number(a);if(dp$1(r)>2||dp$1(a)>2||!(u>0&&u<10&&d>0&&d<10)||u===d)return null;const m=u<=d;if(/wrong/i.test(n.correctAnswer)!==m)return null;const{lo:f,hi:g,step:b,minSep:x}=windowFor$1(u,d);if(Math.abs(u-d)<x)return null;const y=Math.max(u,d),E=Math.min(u,d),k=u>d?r:a,T=u>d?a:r,A=Math.floor(round4$3(y*10))%10,N=Math.floor(round4$3(E*10))%10,S=(M={})=>({type:"number_line",min:f,max:g,step:b,marked:[],...M});return buildExample({generatorId:t,question:n,title:`${o} says ${r} is bigger than ${a} because ${i} is bigger than ${l} — is that right?`,computed:{a:u,b:d,bigger:y,lo:f,hi:g,answer:y},steps:[{visual_data:S({marked:[u,d]}),animation:"show_start",caption:`Place ${r} and ${a} on the line.`,spoken:`The further right a number sits, the bigger it is, so we put both decimals on ${closeUp(f,g)}.`},{visual_data:S({marked:[u,d],hops:[{from:E,to:y,label:"further right"}]}),animation:"compare_place",caption:`Compare the tenths digit: ${A} vs ${N}.`,spoken:`The digits after the point don't join into one whole number — each place has its own value. Comparing tenths first: ${numberToWords(A)} against ${numberToWords(N)}.`},{visual_data:S({marked:[u,d],unknown:y}),animation:"land",caption:`${o} is wrong: ${k} is bigger, not ${T}.`,spoken:`So ${o} is wrong. ${cap$i(decimalWords$1(k))} is bigger than ${decimalWords$1(T)} — reading the digits after the point as one whole number, ${numberToWords(Number(i))} against ${numberToWords(Number(l))}, ignores place value.`}]})}function pointCompare(n,t){const s=normaliseStem(n.stem).match(/^A number line goes from 0 to 1\. Point (\w+) sits at (\d+(?:\.\d+)?)\. Point (\w+) sits at (\d+(?:\.\d+)?)\. Which point is further along the line, closer to 1\?$/i);if(!s)return null;const[,o,r,a,i]=s,l=`Point ${o}`,u=`Point ${a}`,d=Number(r),m=Number(i);if(dp$1(r)>2||dp$1(i)>2||!(d>0&&d<1&&m>0&&m<1)||d===m)return null;const{lo:h,hi:f,step:g,minSep:b}=windowFor$1(d,m);if(Math.abs(d-m)<b)return null;const x=d>m,y=x?l:u;if(n.correctAnswer.trim()!==y)return null;const E=Math.max(d,m),k=Math.min(d,m),T=x?r:i,A=x?i:r,N=Math.floor(round4$3(E*10)),S=Math.floor(round4$3(k*10)),M=N===S,L=M?Math.round(E*100)%10:N,P=M?Math.round(k*100)%10:S,F=M?"hundredths":"tenths",D=(U={})=>({type:"number_line",min:h,max:f,step:g,marked:[],...U});return buildExample({generatorId:t,question:n,title:`Which point is further along, closer to 1: ${l} (${r}) or ${u} (${i})?`,computed:{val1:d,val2:m,winner:E,lo:h,hi:f,wDigit:L,lDigit:P,answer:E}
71,steps:[{visual_data:{type:"comparison",left:d,right:m,symbol:"none"},animation:M?"highlight_hundredths":"highlight_tenths",caption:`${M?"Tenths tie; compare hundredths":"Compare tenths first"}: ${L} > ${P}.`,spoken:`${M?"The tenths are equal, so compare the hundredths.":"Compare the tenths before the hundredths."} ${cap$i(decimalWords$1(T))} has ${numberToWords(L)} ${F}, while ${decimalWords$1(A)} has ${numberToWords(P)}. So ${y} is further along.`},{visual_data:D({marked:[d,m],hops:[{from:k,to:E,label:"further right"}]}),animation:"compare_place",caption:"Further right means closer to 1.",spoken:`${cap$i(decimalWords$1(T))} sits further to the right than ${decimalWords$1(A)}, so it is closer to one.`},{visual_data:D({marked:[d,m],hops:[{from:k,to:E,label:""}],unknown:E}),animation:"land",caption:`${y} (${T}) is further along than the other point (${A}).`,spoken:`So ${y} is further along the line, closer to one.`}]})}function distanceCompare(n,t){const e=normaliseStem(n.stem).match(/^(\w+) ran (\d+(?:\.\d+)?) km\. (\w+) ran (\d+(?:\.\d+)?) km\. Who ran further\?$/i);if(!e)return null;const[,s,o,r,a]=e,i=Number(o),l=Number(a);if(!(i>0&&i<1&&l>0&&l<1)||dp$1(o)>2||dp$1(a)>2||i===l)return null;const u=Math.floor(round4$3(i*10)),d=Math.floor(round4$3(l*10));if(u===d)return null;const m=Math.max(i,l),h=i>l?s:r,f=i>l?o:a;return n.correctAnswer.trim()!==`${h} (${f} km)`?null:buildExample({generatorId:t,question:n,title:`Who ran further: ${s} or ${r}?`,computed:{a:i,b:l,ta:u,tb:d,answer:m},steps:[{visual_data:{type:"comparison",left:i,right:l,symbol:"none"},animation:"highlight_tenths",caption:`Compare tenths: ${s} has ${u}, ${r} has ${d}.`,spoken:`Both distances are less than one kilometre. Compare the tenths first: ${s} has ${numberToWords(u)} tenths of a kilometre and ${r} has ${numberToWords(d)}.`},{visual_data:{type:"comparison",left:i,right:l,symbol:i>l?">":"<"},animation:"highlight_tenths show_statement",caption:`${o} km ${i>l?">":"<"} ${a} km: more tenths means further.`,spoken:`${cap$i(numberToWords(Math.max(u,d)))} tenths is more than ${numberToWords(Math.min(u,d))} tenths. So ${h} ran further, even if the other decimal has more digits.`},{visual_data:{type:"number_line",min:0,max:1,step:.1,marked:[i,l],unknown:m,conclude_label:`${h}: ${f} km`},animation:"land",caption:`${h} ran further: ${f} km.`,spoken:`${h}'s distance is further right on the line. So ${h} ran further, with ${decimalWords$1(f)} kilometres.`}]})}const FDP_P4_M04_GENERATORS=[{id:"FDP.P4.M04.compare-line.v1",microSkillId:"FDP.P4.M04",forms:["comparison_statement","misconception_contrast_probe","number_line_location"],generate(n){if(n.mathematicalForm==="misconception_contrast_probe")return claimCheckM04(n,this.id);if(n.mathematicalForm==="number_line_location")return pointCompare(n,this.id);if(n.mathematicalForm==="comparison_statement"){const K=distanceCompare(n,this.id);if(K)return K;const Z=sprintCompare(n,this.id);if(Z)return Z;const Q=lengthCompare(n,this.id);if(Q)return Q;const B=weighsCompare(n,this.id);if(B)return B;const G=holdsCompare(n,this.id);if(G)return G;const X=equalityDemo(n,this.id);if(X)return X;const ne=trailingZeroDigitClaim(n,this.id);if(ne)return ne;const se=threeWayFastest(n,this.id);if(se)return se}const t=parseM04$1(n);if(!t)return null;const{aStr:e,bStr:s,a:o,b:r,ask:a}=t;if(!(o>0&&o<10&&r>0&&r<10)||dp$1(e)>2||dp$1(s)>2||o===r||Math.floor(o)!==Math.floor(r))return null;const i=round4$3(Math.floor(10*Math.min(o,r))/10);let l=round4$3(Math.ceil(10*Math.max(o,r))/10);l-i<.2&&(l=round4$3(i+.2));const d=round4$3(l-i)>.2?.1:.01,m=d===.1?.05:.01;if(Math.abs(o-r)<m)return null;const h=(K={})=>({type:"number_line",min:i,max:l,step:d,marked:[],...K}),f=o>r?o:r,g=o>r?r:o,b=o>r?e:s,x=o>r?s:e,y=a==="smaller";let E;if(a==="symbol"){if(n.correctAnswer.trim()!==(o<r?"<":">"))return null;E=f}else{const K=y?x:b;if(n.correctAnswer.trim()!==K)return null;E=y?g:f}const k=y?g:f,T=Math.floor(round4$3(f*10))%10,A=Math.floor(round4$3(g*10))%10,N=Math.round(f*100)%10,S=Math.round(g*100)%10,M=T===A,L=K=>K===1?"tenth":"tenths",P=K=>K===1?"hundredth":"hundredths",F=o<r?"<":">",D=a==="symbol"?`, so ${e} ${F} ${s}`:"",U=a==="symbol"?` That means ${decimalWords$1(e)} is ${F===">"?"greater than":"less than"} ${decimalWords$1(s)}, and the symbol is "${F===">"?"greater than":"less than"}". The open side of the sign faces the bigger number.`:"",q=y?M?`Tenths tie — hundredths decide: ${S} is less than ${N}.`:`Tenths decide: ${A} is less than ${T}.`:M?`Tenths tie — hundredths decide: ${N} beats ${S}.`:`Tenths decide: ${T} beats ${A}.`,V=D?`${q.replace(/\.$/,"")}${D}.`:q,z=`${y?M?`The tenths are equal, so the hundredths decide. ${cap$i(numberToWords(S))} ${P(S)} is less than ${numberToWords(N)} ${P(N)}, so ${decimalWords$1(x)} is smaller.`:`The tenths decide it. ${cap$i(numberToWords(A))} ${L(A)} is less than ${numberToWords(T)} ${L(T)}, so ${decimalWords$1(x)} is smaller — a longer decimal is not always bigger.`:M?`The tenths are equal, so the hundredths decide. ${cap$i(numberToWords(N))} ${P(N)} beats ${numberToWords(S)} ${P(S)}, so ${decimalWords$1(b)} is greater.`:`The tenths decide it. ${cap$i(numberToWords(T))} ${L(T)} is more than ${numberToWords(A)} ${L(A)}, so ${decimalWords$1(b)} is greater — a longer decimal is not always bigger.`}${U}`;return buildExample({generatorId:this.id,question:n,title:y?`Which is smaller: ${e} or ${s}?`:`Which is greater: ${e} or ${s}?`,computed:{left:o,right:r,winner:f,wTenth:T,lTenth:A,wHund:N,lHund:S,answer:E},steps:[{visual_data:h({marked:[o,r]}),animation:"show_start",caption:`Place ${e} and ${s} on the line.`,spoken:y?`The further left a number sits, the smaller it is. So we put both decimals on ${closeUp(i,l)}, and look for the one further to the left.`:`The further right a number sits, the bigger it is. So we put both decimals on ${closeUp(i,l)}, and look for the one further to the right.`},{visual_data:h({marked:[o,r],hops:[y?{from:f,to:g,label:"further left"}:{from:g,to:f,label:"further right"}]}),animation:"compare_place",caption:y?"Further left means smaller.":"Further right means greater.",spoken:y?`${cap$i(decimalWords$1(x))} sits further to the left than ${decimalWords$1(b)}, and further left always means smaller. Let's check with the digits.`:`${cap$i(decimalWords$1(b))} sits further to the right than ${decimalWords$1(x)}, and further right always means greater. Let's check with the digits.`},{visual_data:h({marked:[o,r],hops:[y?{from:f,to:g,label:""}:{from:g,to:f,label:""}],unknown:k}),animation:"land",caption:V,spoken:z}]})}}];function cap$i(n){return n.charAt(0).toUpperCase()+n.slice(1)}const MAX_GRID=12;function parseTranslate(n){const t=normaliseStem(n.stem);if(/\bfirst\b.*\bthen\b/i.test(t)||/starting coordinates|were its start/i.test(t))return null;const e=t.match(/\((\d+),\s*(\d+)\)/);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=t.match(/(\d+) squares?(?: to the)? (right|left)/i),a=t.match(/(\d+) squares?(?: to the)? (up|down)/i);if(!r&&!a)return null;let i=0,l=0;r&&(i=Number(r[1])*(/right/i.test(r[2])?1:-1)),a&&(l=Number(a[1])*(/up/i.test(a[2])?1:-1));const u=n.visualData;let d="The point";return u&&u.type==="coordinate_grid"&&u.points[0]?.label&&/^[A-Z]$/.test(u.points[0].label)&&(d=u.points[0].label),{x0:s,y0:o,dx:i,dy:l,label:d}}function gridBounds(n,t,e){const s=n.visualData;let o=t,r=e;return s&&s.type==="coordinate_grid"&&(o=Math.max(s.max_x,t),r=Math.max(s.max_y,e)),o>MAX_GRID||r>
71MAX_GRID?null:{max_x:o,max_y:r}}const NUM$1=numberWord;function moveWords(n,t){const e=[];return n!==0&&e.push(`${NUM$1(Math.abs(n))} ${n>0?"right":"left"}`),t!==0&&e.push(`${NUM$1(Math.abs(t))} ${t>0?"up":"down"}`),e.join(" and ")}const coordinateTranslate={id:"GEO.P4.M03.translate.v1",microSkillId:"GEO.P4.M03",forms:["symbolic_choice","symbolic_numeric_entry"],generate(n){const t=parseTranslate(n);if(!t)return null;const{x0:e,y0:s,dx:o,dy:r,label:a}=t;if(e<0||s<0)return null;const i=e+o,l=s+r;if(i<0||l<0)return null;const u=gridBounds(n,Math.max(e,i),Math.max(s,l));if(!u)return null;const d=n.correctAnswer.trim(),m=d.match(/^\(\s*(\d+)\s*,\s*(\d+)\s*\)$/);let h,f=null;if(m){if(Number(m[1])!==i||Number(m[2])!==l)return null;h=l}else if(/^\d+$/.test(d)){const L=normaliseStem(n.stem).match(/new (x|y)-coordinate/i);if(!L||(f=L[1].toLowerCase(),h=f==="x"?i:l,Number(d)!==h))return null}else return null;const g=/^[A-Z]$/.test(a)?a:"A",b=`${g}′`,x=o!==0?e+o:e,y=o!==0?s:s+r,E=L=>({type:"coordinate_grid",max_x:u.max_x,max_y:u.max_y,points:L==="start"?[{x:e,y:s,label:g}]:L==="mid"?[{x:e,y:s,label:g},{x,y,label:""}]:[{x:e,y:s,label:g},{x:i,y:l,label:b}]}),k=`${NUM$1(e)} across and ${NUM$1(s)} up`,T=o!==0?`${NUM$1(Math.abs(o))} ${o>0?"right":"left"}`:`${NUM$1(Math.abs(r))} ${r>0?"up":"down"}`,A=o!==0&&r!==0?`${NUM$1(Math.abs(r))} ${r>0?"up":"down"}`:null,N=o!==0?`Moving right or left changes the across number. First we move ${T}.`:`Moving up or down changes the up number. The move is ${T}.`;let S=`${b} = (${i}, ${l}).`,M=`It lands here. Counting the across move takes us to ${NUM$1(i)} across, and the up or down move takes us to ${NUM$1(l)} up. So point ${g} moves to ${NUM$1(i)} across and ${NUM$1(l)} up.`;return f==="x"?(S=o!==0?`New x-coordinate: ${i}, so type ${i}.`:`Across stays ${i}, so type ${i}.`,M=o!==0?`Moving right or left changes the across number: it is now ${NUM$1(i)}. That is the answer — type ${NUM$1(i)}.`:`Moving up or down does not change the across number — it stays ${NUM$1(i)}. That is the answer — type ${NUM$1(i)}.`):f==="y"&&(S=r!==0?`New y-coordinate: ${l}, so type ${l}.`:`Up stays ${l}, so type ${l}.`,M=r!==0?`Moving up or down changes the up number: it is now ${NUM$1(l)}. That is the answer — type ${NUM$1(l)}.`:`Moving right or left does not change the up number — it stays ${NUM$1(l)}. That is the answer — type ${NUM$1(l)}.`),buildExample({generatorId:this.id,question:n,title:`How do we translate point ${g}?`,computed:{x0:e,y0:s,dx:Math.abs(o),dy:Math.abs(r),xe:i,ye:l,answer:h},steps:[{visual_data:E("start"),animation:"show_start",caption:`${g} starts at (${e}, ${s}).`,spoken:`Here is point ${g}. We read it off the lines: ${k}.`},{visual_data:E("mid"),animation:"translate",caption:`Move ${T}${A?", then "+A:""}.`,spoken:`${N} We count the squares of the move before we look at where it lands.`},{visual_data:E("end"),animation:"land",caption:S,spoken:M}]})}};function parseMoveLabel(n){const t=/(\d+)\s*(?:squares?\s*)?(?:to the\s*)?(right|left|up|down)/gi;let e=0,s=0,o=!1,r;for(;(r=t.exec(n))!==null;){const a=Number(r[1]),i=r[2].toLowerCase();i==="right"?e+=a:i==="left"?e-=a:i==="up"?s+=a:s-=a,o=!0}return o?{dx:e,dy:s}:null}const pairOf=n=>{const t=n.trim().match(/^\(\s*(\d+)\s*,\s*(\d+)\s*\)$/);return t?{x:Number(t[1]),y:Number(t[2])}:null},plot=(n,t)=>n&&n.trim().length>0?n:t,readCoordinate={id:"GEO.P4.M03.read-coordinate.v1",microSkillId:"GEO.P4.M03",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem);if(!/coordinates of point [A-Za-z]/i.test(t))return null;const e=n.visualData;if(!e||e.type!=="coordinate_grid"||e.points.length!==1)return null;const s=e.points[0];if(!Number.isInteger(s.x)||!Number.isInteger(s.y)||s.x<1||s.y<1)return null;const o=pairOf(n.correctAnswer);if(!o||o.x!==s.x||o.y!==s.y)return null;const r=gridBounds(n,s.x,s.y);if(!r)return null;const a=plot(s.label,"P"),i=l=>({type:"coordinate_grid",max_x:r.max_x,max_y:r.max_y,points:l?[{x:0,y:0,label:"O"},{x:s.x,y:s.y,label:a}]:[{x:0,y:0,label:"O"}]});return buildExample({generatorId:this.id,question:n,title:`How do we read point ${a}'s coordinates?`,computed:{x:s.x,y:s.y,origin:0},steps:[{visual_data:i(!1),animation:"show_start",caption:"Start at the corner (0, 0).",spoken:"We always start at the corner, where the two number lines meet."},{visual_data:i(!0),animation:"translate",caption:`Along ${NUM$1(s.x)}, then up ${NUM$1(s.y)}.`,spoken:`First we go along the corridor — ${NUM$1(s.x)} across. Then up the stairs — ${NUM$1(s.y)} up.`},{visual_data:i(!0),animation:"land",caption:`Point ${a} is at (${s.x}, ${s.y}
71).`,spoken:`So point ${a} is ${NUM$1(s.x)} across and ${NUM$1(s.y)} up. We always read the across number first.`}]})}},describeMove={id:"GEO.P4.M03.describe-move.v1",microSkillId:"GEO.P4.M03",forms:["symbolic_choice","symbolic_numeric_entry"],generate(n){const t=normaliseStem(n.stem);if(!/how do you move|which move takes you|which move describes|how many squares|move from/i.test(t))return null;const e=n.visualData;if(!e||e.type!=="coordinate_grid"||e.points.length<2)return null;const s=e.points[0],o=e.points[1];if(![s.x,s.y,o.x,o.y].every(Number.isInteger))return null;const r=o.x-s.x,a=o.y-s.y,i=gridBounds(n,Math.max(s.x,o.x),Math.max(s.y,o.y));if(!i)return null;const u=a!==0&&o.x>=i.max_x-2?{max_x:Math.min(i.max_x+2,MAX_GRID),max_y:i.max_y}:i,d=n.correctAnswer.trim();let m=null,h="";if(/^\d+$/.test(d)){const k=t.match(/how many squares?(?: to the)? (right|left|up|down)/i);if(!k)return null;h=k[1].toLowerCase();const T=h==="right"?r:h==="left"?-r:h==="up"?a:-a;if(T<0||Number(d)!==T)return null;m=T}else{const k=parseMoveLabel(d);if(!k||k.dx!==r||k.dy!==a)return null}const f=plot(s.label,"A"),g=plot(o.label,"B"),b=()=>({type:"coordinate_grid",max_x:u.max_x,max_y:u.max_y,points:[{x:s.x,y:s.y,label:f},{x:o.x,y:o.y,label:g}],show_target_ghost:!0}),x=h==="right"||h==="left"?a!==0?`${NUM$1(Math.abs(a))} ${a>0?"up":"down"}`:null:r!==0?`${NUM$1(Math.abs(r))} ${r>0?"right":"left"}`:null,y=m!==null?x?`${NUM$1(m)} ${h} and ${x} — asked for ${h}, type ${m}.`:`It moves ${NUM$1(m)} ${h}.`:`The move is ${moveWords(r,a)}.`,E={sx:s.x,sy:s.y,ex:o.x,ey:o.y,dx:Math.abs(r),dy:Math.abs(a)};return m!==null&&(E.answer=m),buildExample({generatorId:this.id,question:n,title:`How do we describe the move from ${f} to ${g}?`,computed:E,steps:[{visual_data:b(),animation:"show_start",caption:`Start at point ${f}.`,spoken:`We begin at point ${f} and look at where we need to get to.`},{visual_data:b(),animation:"translate",caption:"Count across, then count up or down.",spoken:"We count how many squares across the point moves, and then how many squares up or down."},{visual_data:b(),animation:"land",caption:y,spoken:m!==null?x?`The move from ${f} to ${g} is ${NUM$1(m)} ${h} and ${x}. The question only asks about ${h}, so the answer is ${NUM$1(m)}.`:`So the move from ${f} to ${g} is ${NUM$1(m)} ${h}.`:`So the move from ${f} to ${g} is ${moveWords(r,a)}.`}]})}},inverseFindStart={id:"GEO.P4.M03.find-start.v1",microSkillId:"GEO.P4.M03",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem);if(!/starting coordinates|were its start/i.test(t))return null;const e=t.match(/\((\d+),\s*(\d+)\)/);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=t.match(/(\d+) squares?(?: to the)? (right|left)/i),a=t.match(/(\d+) squares?(?: to the)? (up|down)/i);if(!r&&!a)return null;const i=r?Number(r[1])*(/right/i.test(r[2])?1:-1):0,l=a?Number(a[1])*(/up/i.test(a[2])?1:-1):0,u=s-i,d=o-l;if(u<0||d<0)return null;const m=pairOf(n.correctAnswer);if(!m||m.x!==u||m.y!==d)return null;const h=gridBounds(n,Math.max(u,s),Math.max(d,o));if(!h)return null;const f=g=>({type:"coordinate_grid",max_x:h.max_x,max_y:h.max_y,points:g?[{x:s,y:o,label:"End"},{x:u,y:d,label:"Start"}]:[{x:s,y:o,label:"End"}]});return buildExample({generatorId:this.id,question:n,title:"Where did the point start?",computed:{landX:s,landY:o,startX:u,startY:d,moveX:Math.abs(i),moveY:Math.abs(l)},steps:[{visual_data:f(!1),animation:"show_start",caption:`It ends on (${s}, ${o}).`,spoken:`We know where the point finished — ${NUM$1(s)} across and ${NUM$1(o)} up.`},{visual_data:f(!0),animation:"translate",caption:"Undo the move — go back the other way.",spoken:"To find where it started, we do the move backwards: the opposite way, the same number of squares."},{visual_data:f(!0),animation:"land",caption:`It started at (${u}, ${d}).`,spoken:`Going back, the point started at ${NUM$1(u)} across and ${NUM$1(d)} up.`}]})}},twoStepChain={id:"GEO.P4.M03.two-step.v1",microSkillId:"GEO.P4.M03",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem);if(!/\bfirst\b.*\bthen\b/i.test(t))return null;const e=t.split(/,?\s*then\s+/i);if(e.length<2)return null;const s=parseMoveLabel(e[0]),o=parseMoveLabel(e[1]);if(!s||!o)return null;const r=e[0].match(/\((\d+),\s*(\d+)\)/),a=n.visualData;let i,l;if(r)i=Number(r[1]),l=Number(r[2]);else if(a&&a.type==="coordinate_grid"&&a.points[0])i=a.points[0].x,l=a.points[0].y;else return null;const u=s.dx+o.dx,d=s.dy+o.dy,m=i+u,h=l+d;if(m<0||h<0)return null;const f=pairOf(n.correctAnswer);if(!f||f.x!==m||f.y!==h)return null;const g=gridBounds(n,Math.max(i,m),Math.max(l,h));if(!g)return null;
71const b=plot(a&&a.type==="coordinate_grid"?a.points[0]?.label:void 0,"Z"),x=i+s.dx,y=l+s.dy,E=k=>({type:"coordinate_grid",max_x:g.max_x,max_y:g.max_y,points:k==="start"?[{x:i,y:l,label:b}]:k==="mid"?[{x:i,y:l,label:b},{x,y,label:""}]:[{x,y,label:`(${x}, ${y})`},{x:m,y:h,label:`${b}′`}]});return buildExample({generatorId:this.id,question:n,title:`Where does point ${b} end up?`,computed:{sx:i,sy:l,move1x:Math.abs(s.dx),move1y:Math.abs(s.dy),move2x:Math.abs(o.dx),move2y:Math.abs(o.dy),netX:Math.abs(u),netY:Math.abs(d),ex:m,ey:h},steps:[{visual_data:E("start"),animation:"show_start",caption:`${b} starts at (${i}, ${l}).`,spoken:`Point ${b} starts at ${NUM$1(i)} across and ${NUM$1(l)} up.`},{visual_data:E("mid"),animation:"add_moves",caption:`First move: ${moveWords(s.dx,s.dy)}.`,spoken:`Watch the first move — ${moveWords(s.dx,s.dy)}. There is still another move to do, so we add the across moves together, and the up moves together.`},{visual_data:E("end"),animation:"land",caption:`From (${x}, ${y}): ${moveWords(o.dx,o.dy)} more. Total: ${moveWords(u,d)}.`,spoken:`The first move landed us at ${NUM$1(x)} across and ${NUM$1(y)} up. From there, the second move is ${moveWords(o.dx,o.dy)}. Adding both moves together, that is ${moveWords(u,d)} altogether, so point ${b} ends at ${NUM$1(m)} across and ${NUM$1(h)} up.`}]})}},GEO_P4_M03_GENERATORS=[coordinateTranslate,readCoordinate,describeMove,inverseFindStart,twoStepChain],DEG={quarter:90,half:180,three_quarter:270,full:360},TURN_FIELD={90:"quarter",180:"half",270:"three_quarter",360:"full"},NAME$1={90:"quarter",180:"half",270:"three-quarter",360:"full"},START_BEARING={up:0,right:90,down:180,left:270},BEARING_WORD={0:"up",90:"right",180:"down",270:"left"},SMALL_WORDS$1={one:1,two:2,three:3,four:4};function turnWordToDegrees(n){const t=n.toLowerCase();return/three[- ]quarters?/.test(t)?270:/\bquarter\b/.test(t)?90:/\bhalf\b/.test(t)?180:/\bfull\b/.test(t)?360:null}function answerDegrees(n){const t=n.trim().toLowerCase();if(/^\d+$/.test(t))return Number(t);const e=t.match(/^(\d+)\s*degrees?$/);if(e)return Number(e[1]);const s=t.match(/(\w+) right angles?/);if(s){if(t.startsWith("a "))return 90;const o=SMALL_WORDS$1[s[1]];if(o)return o*90}return/three[- ]quarters?/.test(t)?270:/\bquarter\b/.test(t)?90:/\bhalf\b/.test(t)?180:/\bfull\b/.test(t)?360:null}function rightAngleBridge(n,t){if(!/right angles?/i.test(n))return null;const e=t/90;return e===1?{cap:"a right angle",spoken:" Ninety degrees is exactly what we call a right angle."}:{cap:`${e} right angles`,spoken:` Every ninety degrees is one right angle, so that makes ${numberToWordsLong(e)} right angles.`}}function turnDiagram(n,t){const e=n.visualData,s=e&&e.type==="turn"?e.direction:"clockwise",o=e&&e.type==="turn"?e.start??"up":"up";return{type:"turn",turn:TURN_FIELD[t],direction:s,start:o}}function cap$h(n){return n.charAt(0).toUpperCase()+n.slice(1)}const turnDegrees={id:"GEO.P4.M05.turn-degrees.v1",microSkillId:"GEO.P4.M05",forms:["fact_symbolic_retrieval","symbolic_numeric_entry","symbolic_choice"],generate(n){const t=normaliseStem(n.stem);if(/and then|two .* turns/i.test(t))return null;let e=null;const s=n.visualData;if(s&&s.type==="turn")e=DEG[s.turn];else{const u=t.match(/(\d+) degrees?/);if(u)e=Number(u[1]);else{const d=t.match(/three[- ]quarters?|quarter|half|full/i);d&&(e=turnWordToDegrees(d[0]))}}if(e===null||!(e in TURN_FIELD)||answerDegrees(n.correctAnswer)!==e)return null;const o=turnDiagram(n,e),r=NAME$1[e],a=rightAngleBridge(n.correctAnswer,e);if(e===360){const u=turnDiagram(n,270);return buildExample({generatorId:this.id,question:n,title:"How many degrees is a full turn?",computed:{quarter:90,quarters:4,threeQuarterCount:3,threeQuarters:270,full:360,result:e,rightAngles:e/90,answer:e},steps:[{visual_data:turnDiagram(n,90),animation:"compare_first_quarter",caption:"One quarter turn is 90°.",spoken:"A whole turn is three hundred and sixty degrees, and a quarter turn is one of four equal parts of it. So one quarter turn is ninety degrees."},{visual_data:u,animation:"compare_build",caption:"Three quarters: 90° × 3 = 270°.",spoken:"Three quarters of the turn is ninety degrees, three times over — two hundred and seventy degrees."},{visual_data:o,animation:"land",caption:a?`One more quarter: 270° + 90° = 360° = ${a.cap}.`:"One more quarter: 270° + 90° = 360° — all the way round.",spoken:`Keep going all the way back round to the start. Two hundred and seventy degrees plus ninety more makes ${numberToWordsLong(e)} degrees.${a?.spoken??""}`}]})}if(e===90)return buildExample({generatorId:this.id,question:n,title:`How many degrees is a ${r} turn?`,computed:{quarter:90,quarters:4,full:360,result:e,rightAngles:e/90,answer:e},steps:[{visual_data:o,animation:"show_start",caption:"One full turn splits into 4 quarters.",spoken:"A full turn all the way round splits into four equal quarters."},{visual_data:o,animation:"rotate",caption:"A full turn = 360°, so each quarter = 90°.",spoken:"A whole turn is three hundred and sixty degrees, shared equally between the four quarters, so one quarter is ninety degrees."},{visual_data:o,animation:"land",caption:a?`A ${r} turn is ${e}° = ${a.cap}.`:`A ${r} turn is ${e}
71°.`,spoken:`So a ${r} turn is ${numberToWordsLong(e)} degrees.${a?.spoken??""}`}]});const i=turnDiagram(n,90),l=turnDiagram(n,180);return buildExample({generatorId:this.id,question:n,title:`How many degrees is a ${r} turn?`,computed:{quarter:90,quarters:4,half:180,full:360,result:e,rightAngles:e/90,answer:e},steps:[{visual_data:i,animation:"compare_first_quarter",caption:"One quarter turn is 90°.",spoken:"A whole turn is three hundred and sixty degrees, and a quarter turn is one of four equal parts of it. So one quarter turn is ninety degrees."},{visual_data:l,animation:"compare_build",caption:"Two quarters: 90° + 90° = 180°.",spoken:"We build the turn one quarter at a time. Two quarters together make ninety plus ninety, which is one hundred and eighty degrees."},{visual_data:e===180?{...o,showDegreeTotal:!0}:o,animation:"land",caption:a?`A ${r} turn is ${e}° = ${a.cap}.`:e===270?`One more quarter: 180° + 90° = ${e}°.`:`A ${r} turn is ${e}°.`,spoken:e===270?`One more quarter adds another ninety: one hundred and eighty plus ninety is ${numberToWordsLong(e)}. So a ${r} turn is ${numberToWordsLong(e)} degrees.${a?.spoken??""}`:`Two quarter turns make a half turn. So a ${r} turn is ${numberToWordsLong(e)} degrees.${a?.spoken??""}`}]})}},turnSum={id:"GEO.P4.M05.turn-sum.v1",microSkillId:"GEO.P4.M05",forms:["symbolic_numeric_entry","symbolic_choice"],generate(n){const t=normaliseStem(n.stem);let e=null,s=null;const o=t.match(/a (three[- ]quarter|quarter|half|full) turn and (?:then )?a (three[- ]quarter|quarter|half|full) turn/i),r=t.match(/two (three[- ]quarter|quarter|half|full) turns/i);if(o)e=turnWordToDegrees(o[1]),s=turnWordToDegrees(o[2]);else if(r)e=turnWordToDegrees(r[1]),s=e;else return null;if(e===null||s===null)return null;const a=e+s;if(!(e in TURN_FIELD)||!(a in TURN_FIELD)||answerDegrees(n.correctAnswer)!==a)return null;const i=turnDiagram(n,e),l=turnDiagram(n,a),u=rightAngleBridge(n.correctAnswer,a);return buildExample({generatorId:this.id,question:n,title:"How many degrees altogether?",computed:{first:e,second:s,quarter:90,full:360,result:a,rightAngles:a/90,answer:a},steps:[{visual_data:i,animation:"compare_first_turn",caption:`First: a ${NAME$1[e]} turn = ${e}°.`,spoken:`Watch the first turn — a ${NAME$1[e]} turn. A ${NAME$1[e]} turn is ${numberToWordsLong(e)} degrees.`},{visual_data:l,animation:"compare_add_second_turn",caption:`Then a ${NAME$1[s]} turn (${s}°) more — see the whole sweep.`,spoken:`Then it turns a ${NAME$1[s]} turn more — that is another ${numberToWordsLong(s)} degrees. Look how far round the pointer has swept altogether now.`},{visual_data:l,animation:"land",caption:u?`${e}° + ${s}° = ${a}° = ${u.cap}.`:`${e}° + ${s}° = ${a}°.`,spoken:`${cap$h(numberToWordsLong(e))} degrees and ${numberToWordsLong(s)} degrees together make ${numberToWordsLong(a)} degrees.${u?.spoken??""}`}]})}},heading={id:"GEO.P4.M05.heading.v1",microSkillId:"GEO.P4.M05",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem);if(!/which way does it (face|point)/i.test(t))return null;const e=n.visualData;if(!e||e.type!=="turn")return null;const s=e.start??"up",o=(e.direction==="clockwise"?1:-1)*DEG[e.turn],r=((START_BEARING[s]+o)%360+360)%360,a=BEARING_WORD[r];if(!a)return null;const i=n.correctAnswer.trim().toLowerCase().match(/\b(up|down|left|right)\b/);if(!i||i[1]!==a)return null;const l=NAME$1[DEG[e.turn]],u=e.direction==="clockwise"?"clockwise":"anticlockwise",d={type:"turn",turn:e.turn,direction:e.direction,start:s};return buildExample({generatorId:this.id,question:n,title:"Which way does it face after the turn?",computed:{},steps:[{visual_data:d,animation:"show_start",caption:`It starts facing ${s}.`,spoken:`The pointer starts by facing ${s}.`},{visual_data:d,animation:"rotate",caption:`Make a ${l} turn ${u}.`,spoken:`Now it makes a ${l} turn, going ${u}.`},{visual_data:d,animation:`${u}_direction`,caption:`Now it faces ${a}.`,spoken:`Follow the pointer round the dial. It ends up facing ${a}.`}]})}},directionRetrieve={id:"GEO.P4.M05.turn-direction.v1",microSkillId:"GEO.P4.M05",forms:["fact_symbolic_retrieval"],generate(n){const t=normaliseStem(n.stem);if(!/which way (does (it|she|he) turn|do they turn)/i.test(t))return null;const e=n.visualData;if(!e||e.type!=="turn")return null;const s=e.direction==="clockwise"?"Clockwise":"Anticlockwise";if(n.correctAnswer.trim()!==s)return null;const o=e.start??"up",r=NAME$1[DEG[e.turn]],a={type:"turn",turn:e.turn,direction:e.direction,start:o},i=e.direction==="clockwise"?"it turns the way clock hands go — clockwise":"it turns the opposite way to clock hands — anticlockwise";return buildExample({generatorId:this.id,question:n,title:"Which way does it turn?",computed:{},steps:[{visual_data:a,animation:"show_start",caption:`It starts facing ${o}.`,spoken:`The pointer starts by facing ${o}.`},{visual_data:a,animation:"rotate",caption:`Watch it make a ${r} turn.`,spoken:`Watch closely as it makes a ${r} turn.`},{visual_data:a,animation:`${e.direction}_direction`,caption:`${cap$h(i)}.`,spoken:`Follow the arrow round: ${i}.`}
71]})}},complement={id:"GEO.P4.M05.complement.v1",microSkillId:"GEO.P4.M05",forms:["symbolic_numeric_entry","symbolic_choice"],generate(n){const t=normaliseStem(n.stem);if(!/how many more degrees/i.test(t)||!/complete a full turn/i.test(t))return null;const e=t.match(/a (three[- ]quarter|quarter|half) turn/i);if(!e)return null;const s=turnWordToDegrees(e[1]);if(s===null||!(s in TURN_FIELD))return null;const o=360-s;if(!(o in TURN_FIELD)||o===360||answerDegrees(n.correctAnswer)!==o)return null;const r=NAME$1[s],a=NAME$1[o],i=turnDiagram(n,s),l=turnDiagram(n,360),u=(((START_BEARING[i.start??"up"]??0)+(i.direction==="clockwise"?1:-1)*s)%360+360)%360,d=BEARING_WORD[u],m={type:"turn",turn:TURN_FIELD[o],direction:i.direction,start:d??"right"};return buildExample({generatorId:this.id,question:n,title:"How many more degrees to a full turn?",computed:{made:s,full:360,remaining:o,answer:o},steps:[{visual_data:i,animation:"compare_made",caption:`Already turned ${s}°.`,spoken:`It has already made a ${r} turn — that is ${numberToWordsLong(s)} degrees.`},{visual_data:l,animation:"compare_full",caption:"A full turn is 360°.",spoken:"A full turn, all the way round, is three hundred and sixty degrees."},{visual_data:m,animation:"compare_left",caption:`360° − ${s}° = ${o}°.`,spoken:`Take away the ${r} turn already made. Three hundred and sixty degrees take away ${numberToWordsLong(s)} degrees leaves ${numberToWordsLong(o)} degrees — a ${a} turn.`}]})}},turnSumClaim={id:"GEO.P4.M05.turn-sum-claim.v1",microSkillId:"GEO.P4.M05",forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(/^(\w+) says (two|three|four) (three[- ]quarter|quarter|half|full) turns make (\d+) degrees\. Is (?:he|she|they) right\?$/i);if(!e)return null;const s=e[1],o=e[2].toLowerCase(),r=o==="two"?2:o==="three"?3:4,a=turnWordToDegrees(e[3]),i=Number(e[4]);if(a===null)return null;const l=a*r;if(l===i||/^no$/i.test(n.correctAnswer.trim())!==(l!==i)||!(a in TURN_FIELD))return null;const d=turnDiagram(n,a),m=NAME$1[a];if(l in TURN_FIELD){const h=turnDiagram(n,l);return buildExample({generatorId:this.id,question:n,title:`${s} says ${o} ${m} turns make ${i}° — is that right?`,computed:{each:a,count:r,trueTotal:l,claimed:i,answer:l},steps:[{visual_data:d,animation:"compare_first_turn",caption:`Each turn is a ${m} turn = ${a}°.`,spoken:`To check the claim, we first change one turn into degrees. A ${m} turn is ${numberToWordsLong(a)} degrees.`},{visual_data:h,animation:"compare_add_second_turn",caption:`${r} of them: ${a}° × ${r} = ${l}°.`,spoken:`${cap$h(numberToWordsLong(a))} degrees, ${numberToWordsLong(r)} times over, makes ${numberToWordsLong(l)} degrees altogether.`},{visual_data:h,animation:"land",caption:`${s} is wrong: it's ${l}°, not ${i}°.`,spoken:`So ${s} is wrong. ${cap$h(numberToWordsLong(r))} ${m} turns make ${numberToWordsLong(l)} degrees, not ${numberToWordsLong(i)}.`}]})}return buildExample({generatorId:this.id,question:n,title:`${s} says ${o} ${m} turns make ${i}° — is that right?`,computed:{each:a,count:r,trueTotal:l,claimed:i,answer:l},steps:[{visual_data:d,animation:"compare_first_turn",caption:`Each turn is a ${m} turn = ${a}°.`,spoken:`To check the claim, we first change one turn into degrees. A ${m} turn is ${numberToWordsLong(a)} degrees.`},{visual_data:{type:"equation_display",terms:Array(r).fill(a).flatMap((h,f)=>f===0?[h]:["+",h])},animation:"rotate",caption:`${r} of them: ${Array(r).fill(a).join(" + ")}.`,spoken:`We add the ${m} turn ${numberToWordsLong(r)} times.`},{visual_data:{type:"equation_display",terms:[...Array(r).fill(a).flatMap((h,f)=>f===0?[h]:["+",h]),"=",l]},animation:"land",caption:`${s} is wrong: it's ${l}°, not ${i}°.`,spoken:`So ${s} is wrong. ${cap$h(numberToWordsLong(r))} ${m} turns make ${numberToWordsLong(l)} degrees, not ${numberToWordsLong(i)}.`}]})}},turnSumLarge={id:"GEO.P4.M05.turn-sum-large.v1",microSkillId:"GEO.P4.M05",forms:["fact_symbolic_retrieval","symbolic_numeric_entry"],generate(n){const e=normaliseStem(n.stem).match(/(?:a|one) (three[- ]quarter|quarter|half|full) turn and (?:then )?a (three[- ]quarter|quarter|half|full) turn/i);if(!e)return null;
71const s=turnWordToDegrees(e[1]),o=turnWordToDegrees(e[2]);if(s===null||o===null||!(s in TURN_FIELD)||!(o in TURN_FIELD))return null;const r=s+o;if(r<=360||answerDegrees(n.correctAnswer)!==r)return null;const a=turnDiagram(n,s),i=turnDiagram(n,o);return buildExample({generatorId:this.id,question:n,title:"How many degrees altogether?",computed:{first:s,second:o,result:r,answer:r},steps:[{visual_data:a,animation:"show_start",caption:`First: a ${NAME$1[s]} turn = ${s}°.`,spoken:`The first turn is a ${NAME$1[s]} turn — ${numberToWordsLong(s)} degrees.`},{visual_data:i,animation:"rotate",caption:`Then a ${NAME$1[o]} turn = ${o}°.`,spoken:`Then it makes another turn — a ${NAME$1[o]} turn, which is ${numberToWordsLong(o)} degrees.`},{visual_data:{type:"equation_display",terms:[s,"+",o,"=",r]},animation:"write_answer",caption:`${s}° + ${o}° = ${r}°.`,spoken:`${cap$h(numberToWordsLong(s))} degrees plus ${numberToWordsLong(o)} degrees makes ${numberToWordsLong(r)} degrees altogether — more than one full turn.`}]})}};function extractTurnClaims(n){const t=[],e=/(?:A|The) ([a-z]+(?: [a-z]+)?) (?:rotates|turns|spins) through (?:an? )?(three[- ]quarters? of a full turn|three[- ]quarter turn|quarter turn|half turn|full turn)/gi;let s;for(;s=e.exec(n);){const o=turnWordToDegrees(s[2]);o!==null&&t.push({label:s[1],turn:o})}return t}const turnCompare={id:"GEO.P4.M05.turn-compare.v1",microSkillId:"GEO.P4.M05",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem);if(!/more degrees/i.test(t))return null;const e=extractTurnClaims(t);if(e.length!==2)return null;const[s,o]=e;if(s.turn===o.turn||!(s.turn in TURN_FIELD)||!(o.turn in TURN_FIELD))return null;const r=s.turn>o.turn?s:o,a=s.turn>o.turn?o:s,i=cap$h(r.label);if(n.correctAnswer.trim()!==i)return null;const l=turnDiagram(n,s.turn),u=turnDiagram(n,o.turn);return buildExample({generatorId:this.id,question:n,title:`Which turns through more degrees: the ${s.label} or the ${o.label}?`,computed:{first:s.turn,second:o.turn,winner:r.turn,answer:r.turn},steps:[{visual_data:l,animation:"compare_first_turn",caption:`The ${s.label}: a ${NAME$1[s.turn]} turn = ${s.turn}°.`,spoken:`To compare the turns fairly, we change each one into degrees. The ${s.label} makes a ${NAME$1[s.turn]} turn, which is ${numberToWordsLong(s.turn)} degrees.`},{visual_data:u,animation:"compare_second_turn",caption:`The ${o.label}: a ${NAME$1[o.turn]} turn = ${o.turn}°.`,spoken:`Now the ${o.label}. It makes a ${NAME$1[o.turn]} turn, which is ${numberToWordsLong(o.turn)} degrees.`},{visual_data:{type:"equation_display",terms:[s.turn,"vs",o.turn]},animation:"land",caption:`${s.turn}° vs ${o.turn}° — ${i} turns through more.`,spoken:`${cap$h(numberToWordsLong(r.turn))} degrees is more than ${numberToWordsLong(a.turn)} degrees, so the ${r.label} turns through more.`}]})}},turnRepeatQuarters={id:"GEO.P4.M05.turn-repeat-quarters.v1",microSkillId:"GEO.P4.M05",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(/spins through (two|three|four) quarter turns/i);if(!e)return null;const s=e[1].toLowerCase(),o=s==="two"?2:s==="three"?3:4;if(o<3)return null;const r=o*90;if(!(r in TURN_FIELD)||answerDegrees(n.correctAnswer)!==r)return null;const a=90*(o-1);return buildExample({generatorId:this.id,question:n,title:`How many degrees is ${s} quarter turns altogether?`,computed:{quarter:90,count:o,midDeg:a,result:r,answer:r},steps:[{visual_data:turnDiagram(n,90),animation:"compare_first_quarter",caption:"First quarter turn: 90°.",spoken:"The first quarter turn is ninety degrees."},{visual_data:turnDiagram(n,a),animation:"compare_more_quarters",caption:`After ${numberToWords(o-1)} quarters: ${a}°.`,spoken:`Turning another quarter takes us to ${numberToWordsLong(a)} degrees.`},{visual_data:turnDiagram(n,r),animation:"land",caption:`${o} quarter turns = ${o} × 90° = ${r}°.`,spoken:`${cap$h(numberToWordsLong(o))} quarter turns make ${numberToWordsLong(r)} degrees altogether.`}]})}},GEO_P4_M05_GENERATORS=[turnSum,heading,directionRetrieve,complement,turnDegrees,turnSumClaim,turnSumLarge,turn
71RepeatQuarters,turnCompare];function narrativeDegrees(n){return/almost a straight line|opens so wide/i.test(n)?165:/small amount|is narrow/i.test(n)?25:/half of a right angle/i.test(n)?45:/quarter turn/i.test(n)||/one right angle on its own|a straight angle is split into two equal angles/i.test(n)?90:/wider than a square corner but (?:is )?not yet a straight line/i.test(n)?135:null}function angleDegrees(n){const t=n.visualData;if(t&&t.type==="angle_diagram"&&t.angles.length===1){const o=t.angles[0].degrees;return Number.isFinite(o)?o:null}const e=normaliseStem(n.stem),s=e.match(/[Aa]n? angle measures (\d+) degrees/);return s?Number(s[1]):narrativeDegrees(e)}const classifyWord=n=>n<90?"Acute":n===90?"Right":"Obtuse";function parseAsk$1(n){const t=n.toLowerCase();if(/smaller or larger than a right angle/.test(t))return{kind:"compare"};const e=t.match(/true or false: this angle is an? (right|obtuse|acute) angle/);if(e)return{kind:"true_false",prop:e[1]};const s=n.match(/(\w+) says it is an? (right|obtuse|acute) angle\. is \w+ correct\?/i);return s?{kind:"claim",prop:s[2].toLowerCase(),name:s[1]}:/acute, right,? or obtuse/.test(t)?{kind:"classify"}:/which kind of angle|what kind of angle/.test(t)?{kind:"classify"}:null}function expectedVerdict(n,t){return n.kind==="compare"?t<90?"Smaller":"Larger":n.kind==="true_false"?(n.prop==="right"?t===90:n.prop==="obtuse"?t>90:t<90)?"True":"False":n.kind==="claim"?(n.prop==="right"?t===90:n.prop==="obtuse"?t>90:t<90)?"Yes":"No":classifyWord(t)}const relation=n=>n<90?"narrower":n>90?"wider":"matches",classifyAngle={id:"GEO.P5.M03.classify.v1",microSkillId:"GEO.P5.M03",forms:["shape_property","comparison_statement","misconception_contrast_probe","symbolic_choice"],generate(n){const t=angleDegrees(n);if(t===null||t<1||t>179||t!==90&&t>=88&&t<=92)return null;const e=normaliseStem(n.stem),s=parseAsk$1(e);if(s===null||s.kind==="compare"&&t===90)return null;const o=expectedVerdict(s,t);if(o.toLowerCase()!==n.correctAnswer.trim().toLowerCase())return null;const r=n.visualData&&n.visualData.type==="angle_diagram"?n.visualData.baseline??"none":"none",a=s.kind!=="classify",i={type:"angle_diagram",angles:[{degrees:t}],baseline:r},l=i,u=i,d=a?{type:"angle_diagram",angles:[{degrees:t}],baseline:r,right_angle_verdict:!0}:i,m=relation(t),h=normaliseStem(n.stem).match(/(\d+) degrees/),f=!!h&&Number(h[1])===t,g=/\bdoor\b/i.test(e);let b,x,y;if(s.kind==="classify"){const S=classifyWord(t);if(b="Is this angle acute, right, or obtuse?",x=S==="Acute"?"Narrower than a square corner = acute.":S==="Right"?"It matches the square corner = right angle.":"Between a square corner and a straight line = obtuse.",y=S==="Acute"?"It opens less than a square corner, so it is acute. An angle smaller than a right angle is acute.":S==="Right"?"It fits the square corner exactly, so it is a right angle.":"It opens wider than a square corner but less than a straight line. An obtuse angle is greater than ninety degrees and less than one hundred and eighty degrees.",f){const M=t<90?"less than":t>90?"more than":"equal to";x=t>90?`90° < ${t}° < 180° — obtuse.`:`${t}° is ${M} 90° — ${x.toLowerCase()}`,y=`${capitalised(numberToWordsLong(t))} degrees is ${M} ninety degrees. ${y}`}}else if(s.kind==="compare")b="Is this angle smaller or larger than a right angle?",x=m==="narrower"?"It is smaller than a right angle.":"It is larger than a right angle.",y=m==="narrower"?"It opens less than the square corner, so this angle is smaller than a right angle.":"It opens wider than the square corner, so this angle is larger than a right angle.";else if(s.kind==="true_false"){b=`Is this angle really ${s.prop==="right"?"a right angle":s.prop==="obtuse"?"an obtuse angle":"an acute angle"}?`;const M=o==="True";x=`${m==="matches"?"It matches the square corner exactly":m==="narrower"?"It is narrower than the square corner":"It is wider than the square corner"} — the statement is ${M?"true":"false"}.`,y=`Look closely: ${m==="matches"?"it fits the square corner exactly, so it is a right angle":m==="narrower"?"it opens less than the square corner, so it is smaller than a right angle":"it opens wider than the square corner, so it is larger than a right angle"}. So the statement is ${M?"true":"false"}.`}else{const S=s.prop,M=o==="Yes";if(b=`${s.name} says this is ${S==="right"?"a":"an"} ${S} angle — is that correct?`,x=`${m==="matches"?"It matches the square corner exactly":m==="narrower"?"It is narrower than the square corner":"It is wider than the square corner"} — ${s.name} is ${M?"correct":"wrong"}.`,y=`Look closely: ${m==="matches"?"it fits the square corner exactly, so it is a right angle":m==="narrower"?"it opens less than the square corner, so it is acute — smaller than a right angle":"it opens wider than the square corner, but less than a straight line, so it is obtuse"}. So ${s.name} is ${M?"correct":"wrong"}.`,f){const F=t<90?"less than":t>90?"more than":"equal to";x=`${t}° is ${F} 90° — ${s.name} is ${M?"correct":"wrong"}.`,y=`${capitalised(numberToWordsLong(t))} degrees is ${F} ninety degrees. ${y}`}}const E=/half of a right angle/i.test(e),k=/quarter turn/i.test(e),T=E?"Half a right angle: 90° ÷ 2 = 45°.":k?"A quarter turn: 360° ÷ 4 = 90°.":f?`This angle measures ${t}°.`:g?"Look at how wide the door's gap opens.":"Look at how wide the angle opens.",A=E?"A right angle is ninety degrees. Half means divide by two, so half a right angle is forty-five degrees.":k?"A full turn is three hundred and sixty degrees. Divide by four for a quarter turn: ninety degrees, which is a right angle.":f?`This angle measures ${numberToWordsLong(t)} degrees. Let's see how wide that really opens.`:g?"Here is the door's gap. We look at how wide it opens at the hinge.":"Here is the angle. We look at how wide it opens at the corner.",N=s.kind==="classify"?{type:"angle_diagram",angles:[{degrees:t,label:`${t}°`}],baseline:r}:d;return buildExample({generatorId:this.id,question:n,title:b,computed:{degrees:t,...t>90?{straight_angle_reference:180}:{},...E?{right_angle_reference:90,divisor:2}:{},...k?{full_turn:360,divisor:4,right_angle_reference:90}:{},...f&&(s.kind==="classify"||s.kind==="claim")?{right_angle_reference:90}:{}},steps:[{visual_data:u,animation:"show_angle",caption:T,spoken:A},{visual_data:l,animation:"compare_right_angle",caption:g?"Compare the door's gap to a 
71square corner.":"Compare it to a square corner.",spoken:g?"We put a square corner against the door's gap. A square corner is a right angle, so we can see whether the gap is narrower or wider.":"We put a square corner against it. A square corner is a right angle, so we can see whether this angle is narrower or wider."},{visual_data:N,animation:"reveal_type",caption:x,spoken:y}]})}},doorCompare={id:"GEO.P5.M03.door-compare.v1",microSkillId:"GEO.P5.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem),e=t.match(/Which door's opening angle is (obtuse|acute), Door A or Door B\?/i);if(!e)return null;const s=e[1].toLowerCase(),o=t.match(/Door A ([^.]*)\./i),r=t.match(/Door B ([^.]*)\./i);if(!o||!r)return null;const a=narrativeDegrees(o[1]),i=narrativeDegrees(r[1]);if(a===null||i===null||a===i)return null;const l=s==="obtuse"?a>90:a<90,u=s==="obtuse"?i>90:i<90;if(l===u)return null;const d=l?"Door A":"Door B";if(n.correctAnswer.trim()!==d)return null;const m={type:"angle_diagram",angles:[{degrees:a}]},h={type:"angle_diagram",angles:[{degrees:i}]},f=l?m:h,g=classifyWord(l?a:i),b=classifyWord(l?i:a);return buildExample({generatorId:this.id,question:n,title:`Which door's opening angle is ${s}: Door A or Door B?`,computed:{degA:a,degB:i,answer:l?a:i},steps:[{visual_data:m,animation:"show_angle",caption:"Door A's gap: wide, almost a straight line.",spoken:"To sort each gap, we compare it with a square corner, which is a right angle. Door A's gap opens almost all the way to a straight line, much wider than a square corner."},{visual_data:h,animation:"show_angle",caption:"Door B's gap: narrow, just a small amount.",spoken:"Door B's gap opens just a little, much narrower than a square corner."},{visual_data:f,animation:"reveal_type",caption:`Door A is ${l?g.toLowerCase():b.toLowerCase()}, Door B is ${l?b.toLowerCase():g.toLowerCase()} — ${d} is ${s}.`,spoken:`Door A's gap is ${l?g.toLowerCase():b.toLowerCase()} and Door B's gap is ${l?b.toLowerCase():g.toLowerCase()}, so ${d} is the ${s} one.`}]})}},GEO_P5_M03_GENERATORS=[classifyAngle,doorCompare];function knownAngles(n){const t=n.visualData;if(t&&t.type==="angle_diagram"){const o=t.angles.filter(r=>!r.unknown).map(r=>r.degrees);return o.length>=1&&o.every(r=>Number.isInteger(r))?o:null}const e=normaliseStem(n.stem),s=[...e.matchAll(/(\d+) degrees?/g)].map(o=>Number(o[1]));return/and a right angle|a right angle,|, and a right angle/.test(e)&&s.push(90),s.length>=1?s:null}const accepted=n=>{const t=n.trim();if(/^\d+$/.test(t))return Number(t);const e=t.match(/^(\d+)\s*degrees?$/i);return e?Number(e[1]):null},missingAngle={id:"GEO.P5.M04.missing.v1",microSkillId:"GEO.P5.M04",forms:["symbolic_numeric_entry","symbolic_choice","context_calculation"],generate(n){const t=knownAngles(n);if(!t)return null;const e=t.reduce((x,y)=>x+y,0),s=accepted(n.correctAnswer);if(s===null)return null;const r=[90,180,360].filter(x=>x-e===s&&s>=1&&s<=359);if(r.length!==1)return null;const a=r[0],i=s,l=a===180?"straight":"none",u=a===90,m={type:"angle_diagram",angles:[...t.map(x=>({degrees:x,label:`${x}°`})),{degrees:i,unknown:!0}],baseline:l,mark_right_angle:u},f={type:"angle_diagram",angles:[...t.map(x=>({degrees:x,label:`${x}°`})),{degrees:i,label:`${i}°`,unknown:!0}],baseline:l,mark_right_angle:u},g=a===180?"Angles on a line add to 180°.":a===360?"Angles round a point add to 360°."
71:"A right angle is 90°.",b=a===180?"Angles on a straight line always add up to one hundred and eighty degrees.":a===360?"Angles that meet all the way round a point always add up to three hundred and sixty degrees.":"The two parts of a right angle always add up to ninety degrees.";return buildExample({generatorId:this.id,question:n,title:"What is the missing angle?",computed:{knownSum:e,total:a,missing:i,answer:i,...Object.fromEntries(t.map((x,y)=>[`known${y}`,x]))},steps:[{visual_data:m,animation:"show_angles",caption:t.length>1?`Known angles: ${t.join("° + ")}° = ${e}°; one is missing.`:`The known angle is ${e}°; one is missing.`,spoken:t.length>1?`The angles we know add up to ${numberToWordsLong(e)} degrees. One angle is still missing.`:`The angle we know is ${numberToWordsLong(e)} degrees. One angle is still missing.`},{visual_data:m,animation:"highlight_rule",caption:g,spoken:`${b} So we take the known part away from the whole.`},{visual_data:f,animation:"reveal_missing",caption:`${a}° − ${e}° = ${i}°.`,spoken:`${cap$g(numberToWordsLong(a))} take away ${numberToWordsLong(e)} leaves ${numberToWordsLong(i)}. So the missing angle is ${numberToWordsLong(i)} degrees.`}]})}};function cap$g(n){return n.charAt(0).toUpperCase()+n.slice(1)}const missingAngleClaim={id:"GEO.P5.M04.missing-claim.v1",microSkillId:"GEO.P5.M04",forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(/^Two angles lie on a straight line\. One is (\d+) degrees\. (\w+) says the missing angle is (\d+) degrees\. Is (?:he|she|they) right\?$/i);if(!e)return null;const s=Number(e[1]),o=e[2],r=Number(e[3]),a=180-s;if(a<1||a>179||a===r||/^no$/i.test(n.correctAnswer.trim())!==(a!==r))return null;const l={type:"angle_diagram",angles:[{degrees:s,label:`${s}°`},{degrees:a,unknown:!0}],baseline:"straight"},u={type:"angle_diagram",angles:[{degrees:s,label:`${s}°`},{degrees:a,label:`${a}°`,unknown:!0}],baseline:"straight"};return buildExample({generatorId:this.id,question:n,title:`${o} says the missing angle is ${r}° — is that right?`,computed:{known:s,trueMissing:a,claimed:r,total:180,answer:a},steps:[{visual_data:l,animation:"show_angles",caption:`One angle is ${s}°; the other is missing.`,spoken:`One angle is ${numberToWordsLong(s)} degrees. The other angle on the line is still missing.`},{visual_data:l,animation:"highlight_rule",caption:"Angles on a line add to 180°.",spoken:"Angles on a straight line always add up to one hundred and eighty degrees."},{visual_data:u,animation:"reveal_missing",caption:`180° − ${s}° = ${a}°, not ${r}°. ${o} is wrong.`,spoken:`One hundred and eighty take away ${numberToWordsLong(s)} leaves ${numberToWordsLong(a)}. So ${o} is wrong — the missing angle is ${numberToWordsLong(a)} degrees, not ${numberToWordsLong(r)}.`}]})}},missingAngleCompare={id:"GEO.P5.M04.missing-compare.v1",microSkillId:"GEO.P5.M04",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(/On one straight line, one angle is (\d+) degrees and the other is missing\. On another straight line, one angle is (\d+) degrees and the other is missing\. Which missing angle is bigger\?/i);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=180-s,a=180-o;if(r<1||r>179||a<1||a>179||r===a)return null;const i=r>a,l=i?"The angle on the first line":"The angle on the second line";if(n.correctAnswer.trim()!==l)return null;const u={type:"angle_diagram",angles:[{degrees:s,label:`${s}°`},{degrees:r,unknown:!0}],baseline:"straight"},d={type:"angle_diagram",angles:[{degrees:o,label:`${o}°`},{degrees:a,unknown:!0}],baseline:"straight"},m=i?r:a,h=i?a:r;return buildExample({generatorId:this.id,question:n,title:"Which missing angle is bigger?",computed:{first:s,second:o,missing1:r,missing2:a,total:180,answer:m},steps:[{visual_data:u,animation:"show_angles",caption:`First line: 180° − ${s}° = ${r}°.`,spoken:`On the first line, one hundred and eighty take away ${numberToWordsLong(s)} leaves ${numberToWordsLong(r)}.`},{visual_data:d,animation:"show_angles",caption:`Second line: 180° − ${o}° = ${a}°.`,spoken:`On the second line, one hundred and eighty take away ${numberToWordsLong(o)} leaves ${numberToWordsLong(a)}.`},{visual_data:{type:"equation_display",terms:[r,"vs",a]},animation:"land",caption:`${m}° > ${h}° — ${l.toLowerCase()} is bigger.`,spoken:`${cap$g(numberToWordsLong(m))} degrees is more than ${numberToWordsLong(h)} degrees, so ${l.toLowerCase()} is bigger.`}]})}},GEO_P5_M04_GENERATORS=[missingAngle,missingAngleClaim,missingAngleCompare];function pluralNumberWord(n){const t=numberToWords(n);return t.endsWith("x")?`${t}es`:`${t}s`}function buildSkipCount(n,t,e,s,o,r){if(t<2||t>12||e<1||e>12)return null;const a=t*e;if(a!==(r??answerInt(n)))return null;const i=Array.from({length:t-1},(h,f)=>(f+1)*e),l=i.join(", "),u=numberToWords(e),d=pluralNumberWord(e),m={rows:t,cols:e,product:a,answer:a};
71return i.forEach((h,f)=>{m[`run${f+1}`]=h}),buildExample({generatorId:o,question:n,title:s,computed:m,steps:[{visual_data:{type:"array",rows:t,cols:e},animation:"array_build",caption:`${t} equal rows of ${e}.`,spoken:`${capitalised(numberToWords(t))} equal rows of dots. Each row has ${u}.`},{visual_data:{type:"array",rows:t,cols:e},animation:"array_skip_count",caption:`Count in ${d}: ${l}…`,spoken:`We count the rows in ${d}. One row is ${u}, two rows is ${numberToWords(e*2)}.`},{visual_data:{type:"array",rows:t,cols:e},animation:"array_product",caption:`${t} × ${e} = ${a}.`,spoken:`${capitalised(numberToWords(t))} rows of ${u} make ${numberToWords(a)} altogether.`}]})}function buildSplitDemo(n,t,e,s){let o,r;if(t===11||t===12)o=t,r=e;else if(e===11||e===12)o=e,r=t;else return null;if(r<1||r>12)return null;const a=t*e;if(a>200||a!==answerInt(n))return null;const i=o-10,l=10*r,u=i*r,d={type:"equation_display",terms:[`10 × ${r}`,"+",`${i} × ${r}`,"=","__"]},m={type:"equation_display",terms:[`${l}`,"+",`${u}`,"=","__"]},h={type:"equation_display",terms:[`${l}`,"+",`${u}`,"=",`${a}`]};return buildExample({generatorId:s,question:n,title:`How do we work out ${o} × ${r}?`,computed:{splitFactor:o,mult:r,rest:i,tensPart:l,restPart:u,product:a,answer:a},steps:[{visual_data:d,animation:"partition",caption:`Split ${o} into 10 and ${i}.`,spoken:`${capitalised(numberToWords(o))} times ${numberToWords(r)}. We split the ${numberToWords(o)} into ten and ${numberToWords(i)}, then multiply each part.`},{visual_data:m,animation:"multiply_parts",caption:`10 × ${r} = ${l}, ${i} × ${r} = ${u}.`,spoken:`Ten times ${numberToWords(r)} is ${numberToWords(l)}. ${capitalised(numberToWords(i))} times ${numberToWords(r)} is ${numberToWords(u)}.`},{visual_data:h,animation:"recombine",caption:`${l} + ${u} = ${a}.`,spoken:`We add the parts back together. ${capitalised(numberToWords(l))} plus ${numberToWords(u)} is ${numberToWords(a)}.`}]})}const PRODUCT$2=/^(\d+) × (\d+) = __$/,symbolic$6={id:"MD.P3.M02.split.v1",microSkillId:"MD.P3.M02",forms:["fact_symbolic_retrieval","symbolic_choice"],generate(n){const t=matchInts(n.stem,PRODUCT$2);return t?buildSplitDemo(n,t[0],t[1],this.id):null}};function buildContextSplitDemo(n,t,e,s){let o,r;if(t===11||t===12)o=t,r=e;else if(e===11||e===12)o=e,r=t;else return null;if(r<1||r>12)return null;const a=t*e;if(a>200||a!==answerInt(n))return null;const i=o-10,l=10*r,u=i*r,d={type:"equation_display",terms:[`${o} × ${r}`,"=","__"]},m={type:"equation_display",terms:[`${l}`,"+",`${u}`,"=","__"]},h={type:"equation_display",terms:[`${l}`,"+",`${u}`,"=",`${a}`]};return buildExample({generatorId:s,question:n,title:`${r} groups of ${o} — how many altogether?`,computed:{splitFactor:o,mult:r,rest:i,tensPart:l,restPart:u,product:a,answer:a},steps:[{visual_data:d,animation:"show_equation",caption:`${r} equal groups of ${o}. Work out ${o} × ${r}.`,spoken:`Each group has ${numberToWords(o)}, and there are ${numberToWords(r)} groups. So we need to work out ${numberToWords(o)} times ${numberToWords(r)}.`},{visual_data:m,animation:"multiply_parts",caption:`Split ${o} into 10 and ${i}: 10 × ${r} = ${l}, ${i} × ${r} = ${u}.`,spoken:`We split the ${numberToWords(o)} into ten and ${numberToWords(i)}. Ten times ${numberToWords(r)} is ${numberToWords(l)}. ${capitalised(numberToWords(i))} times ${numberToWords(r)} is ${numberToWords(u)}.`},{visual_data:h,animation:"recombine",caption:`${l} + ${u} = ${a}.`,spoken:`We add the parts back together. ${capitalised(numberToWords(l))} plus ${numberToWords(u)} is ${numberToWords(a)} altogether.`}]})}const CTX_MUL$2=/(?:holds|has|carries) (\d+)[^.]*\. How many \w+ (?:are|fit) (?:in|on) (\d+)/,context$6={id:"MD.P3.M02.split-context.v1",microSkillId:"MD.P3.M02",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,CTX_MUL$2);return t?buildContextSplitDemo(n,t[0],t[1],this.id):null}},arrayFact={id:"MD.P3.M02.array-skip-count.v1",microSkillId:"MD.P3.M02",forms:["fact_symbolic_retrieval","symbolic_choice"],generate(n){const t=matchInts(n.stem,PRODUCT$2);if(!t)return null;const[e,s]=t;return e<2||e>9||s<2||s>9?null:buildSkipCount(n,e,s,`How do we work out ${e} × ${s}?`,this.id)}},MISSING_LEFT=/^(\d+) × __ = (\d+)$/,MISSING_RIGHT=/^__ × (\d+) = (\d+)$/,missingFactor$1={id:"MD.P3.M02.missing-factor.v1",microSkillId:"MD.P3.M02",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem),e=t.match(MISSING_LEFT),s=t.match(MISSING_RIGHT);if(!e&&!s)return null;
71const o=Number((e??s)[1]),r=Number((e??s)[2]);if(o<2||o>12||r%o!==0)return null;const a=r/o;if(a<1||a>12||r>144||a!==answerInt(n))return null;const i=a<=o,l=i?a:o,u=i?o:a;if(l>9)return null;const d={type:"array",rows:l,cols:u};if(i){const m=Array.from({length:l-1},(b,x)=>(x+1)*u),h=m.join(", "),f=pluralNumberWord(u),g={known:o,missing:a,product:r,answer:a};return m.forEach((b,x)=>{g[`run${x+1}`]=b}),buildExample({generatorId:this.id,question:n,title:`Find the missing factor of ${r}.`,computed:g,steps:[{visual_data:d,animation:"array_build",caption:`Start with ${r} dots, in groups of ${o}.`,spoken:`We have ${numberToWords(r)} dots, arranged in groups of ${numberToWords(o)}.`},{visual_data:d,animation:"array_skip_count",caption:`Count the groups: ${h}…`,spoken:`We count the groups in ${f}. One group is ${numberToWords(o)}, two groups is ${numberToWords(o*2)}.`},{visual_data:d,animation:"array_product",caption:`${r} ÷ ${o} = ${a}.`,spoken:`${capitalised(numberToWords(a))} groups of ${numberToWords(o)} fit exactly, so the missing factor is ${numberToWords(a)}.`}]})}return buildExample({generatorId:this.id,question:n,title:`Find the missing factor of ${r}.`,computed:{known:o,missing:a,product:r,answer:a},steps:[{visual_data:d,animation:"divide_build",caption:`Start with ${r} dots.`,spoken:`We have ${numberToWords(r)} dots to share out equally.`},{visual_data:d,animation:"divide_group",caption:`Share them into ${o} equal groups.`,spoken:`We share them into ${numberToWords(o)} equal groups, because the known factor is ${numberToWords(o)}.`},{visual_data:d,animation:"divide_reveal",caption:`${r} ÷ ${o} = ${a}.`,spoken:`${capitalised(numberToWords(r))} shared into ${numberToWords(o)} groups is ${numberToWords(a)} in each group, so the missing factor is ${numberToWords(a)}.`}]})}},countGroups={id:"MD.P3.M02.count-groups.v1",microSkillId:"MD.P3.M02",forms:["count_equal_groups"],generate(n){const t=n.visualData;if(!t||t.type!=="array")return null;const{rows:e,cols:s}=t;return!Number.isInteger(e)||!Number.isInteger(s)?null:buildSkipCount(n,e,s,`How many in ${e} rows of ${s}?`,this.id)}},CLAIM$3=/^(\w+) says (\d+) × (\d+) = (\d+)\. Is (?:he|she|they) right\?$/,claimSplit={id:"MD.P3.M02.claim-split.v1",microSkillId:"MD.P3.M02",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM$3);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r);let u,d;if(a===11||a===12)u=a,d=i;else if(i===11||i===12)u=i,d=a;else return null;if(d<1||d>12)return null;const m=a*i;if(m>200)return null;const h=m===l,f=n.correctAnswer.trim().toLowerCase();if(h?!f.startsWith("yes"):!f.startsWith("no"))return null;const g=u-10,b=10*d,x=g*d;return buildExample({generatorId:this.id,question:n,title:`${e} says ${a} × ${i} = ${l}. Is that right?`,computed:{a,b:i,claim:l,split:u,mult:d,rest:g,tensPart:b,restPart:x,product:m},steps:[{visual_data:{type:"equation_display",terms:[`${a} × ${i}`,"=",`${l}`]},animation:"show_start",caption:`${e} says ${a} × ${i} = ${l}. Let's check.`,spoken:`${e} says ${numberToWords(a)} times ${numberToWords(i)} is ${numberToWordsLong(l)}. To check, we work it out for ourselves by splitting ${numberToWords(u)} into ten and ${numberToWords(g)}.`},{visual_data:{type:"equation_display",terms:[`10 × ${d}`,"+",`${g} × ${d}`,"=",`${m}`]},animation:"multiply_parts",caption:`10 × ${d} = ${b}, ${g} × ${d} = ${x}. Total ${m}.`,spoken:`Ten times ${numberToWords(d)} is ${numberToWords(b)}. ${capitalised(numberToWords(g))} times ${numberToWords(d)} is ${numberToWords(x)}. Together that is ${numberToWords(m)}.`},{visual_data:{type:"equation_display",terms:[`${a} × ${i}`,"=",`${m}`]},animation:"write_answer",caption:h?`${e} is right: ${a} × ${i} = ${m}.`:`${e} is wrong: ${a} × ${i} = ${m}, not ${l}.`,spoken:h?`${e} is right. ${capitalised(numberToWords(a))} times ${numberToWords(i)} really is ${numberToWords(m)}.`:`Our answer is ${numberToWords(m)}, and that does not match ${numberToWordsLong(l)}. So ${e} is wrong.`}]})}},CONTEXT_ROWS=/^A gardener plants \w+ plants in equal rows of (\d+) plants each\. She has planted (\d+) \w+ plants in total\. How many rows has she planted\?$/,contextRows={id:"MD.P3.M02.context-rows.v1",microSkillId:"MD.P3.M02",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(CONTEXT_ROWS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<2||e>12||s%e!==0)return null;const o=s/e;return o<1||o>12||o!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`How many rows of ${e} make ${s}?`,computed:{known:e,total:s,rows:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[`${e}`,"×","__","=",`${s}`]},animation:"show_equation",caption:`${e} plants a row, ${s} plants in total.`,spoken:`Each row has ${numberToWords(e)} plants, and there are ${numberToWords(s)} plants altogether. We need to find how many rows.`},{visual_data:{type:"equation_display",terms:[`${s}`,"÷",`${e}`,"=","__"]},animation:"inverse",caption:`So work out ${s} ÷ ${e}
71.`,spoken:`Multiplying and dividing are opposites, so we find the number of rows by dividing ${numberToWords(s)} by ${numberToWords(e)}.`},{visual_data:{type:"equation_display",terms:[`${e}`,"×",`${o}`,"=",`${s}`]},animation:"recombine",caption:`${e} × ${o} = ${s}. There are ${o} rows.`,spoken:`${capitalised(numberToWords(s))} divided by ${numberToWords(e)} is ${numberToWords(o)}, so she planted ${numberToWords(o)} rows.`}]})}},CONTEXT_MINIBUS=/^A minibus seats (\d+) passengers\. (\d+) minibuses are completely full, and (\d+) more child travels separately by car\. How many children travel altogether\?$/,twoStepMinibus={id:"MD.P3.M02.two-step-minibus.v1",microSkillId:"MD.P3.M02",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(CONTEXT_MINIBUS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(e<2||e>12||s<1||s>12)return null;const r=e*s,a=r+o;return a!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${s} minibuses of ${e}, plus ${o} more — how many?`,computed:{seats:e,buses:s,extra:o,busTotal:r,total:a,answer:a},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"+",`${o}`,"=","__"]},animation:"show_equation",caption:`First work out ${e} × ${s}, then add ${o}.`,spoken:`${capitalised(numberToWords(s))} full minibuses of ${numberToWords(e)}, plus one more child. First we multiply, then we add.`},{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${r}`]},animation:"multiply",caption:`${e} × ${s} = ${r}.`,spoken:`${capitalised(numberToWords(e))} times ${numberToWords(s)} is ${numberToWords(r)}.`},{visual_data:{type:"equation_display",terms:[`${r}`,"+",`${o}`,"=",`${a}`]},animation:"add",caption:`${r} + ${o} = ${a} children altogether.`,spoken:`${capitalised(numberToWords(r))} plus ${numberToWords(o)} more is ${numberToWords(a)} children altogether.`}]})}},COMPARE$4=/^Which is greater: (\d+) × (\d+) or (\d+) × (\d+)\?$/,compareProducts$2={id:"MD.P3.M02.compare-products.v1",microSkillId:"MD.P3.M02",forms:["comparison_statement"],generate(n){const t=matchInts(n.stem,COMPARE$4);if(!t)return null;const[e,s,o,r]=t;if(e<2||e>12||s<2||s>12||o<2||o>12||r<2||r>12)return null;const a=e*s,i=o*r,l=a===i?"They are equal":a>i?`${e} × ${s}`:`${o} × ${r}`;if(n.correctAnswer.trim()!==l)return null;const u=a===i?"=":a>i?">":"<";return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${e} × ${s} or ${o} × ${r}?`,computed:{a:e,b:s,c:o,d:r,left:a,right:i},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${a}`]},animation:"show_start",caption:`First: ${e} × ${s} = ${a}.`,spoken:`${capitalised(numberToWords(e))} times ${numberToWords(s)} is ${numberToWords(a)}.`},{visual_data:{type:"equation_display",terms:[`${o} × ${r}`,"=",`${i}`]},animation:"show_second",caption:`Second: ${o} × ${r} = ${i}.`,spoken:`${capitalised(numberToWords(o))} times ${numberToWords(r)} is ${numberToWords(i)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:u},animation:"write_answer",caption:a===i?`${e} × ${s} and ${o} × ${r} are equal.`:`${l} is greater (${Math.max(a,i)}).`,spoken:a===i?`Both are ${numberToWords(a)}, so they are equal.`:`${capitalised(numberToWords(Math.max(a,i)))} is more than ${numberToWords(Math.min(a,i))}, so the ${a>i?"first":"second"} one is greater.`}]})}},ANSWER_FACT=/^Which multiplication fact has an answer of (\d+)\?$/,OPTION_FACT=/^(\d+) × (\d+)$/,answerFact={id:"MD.P3.M02.answer-fact.v1",microSkillId:"MD.P3.M02",forms:["symbolic_choice"],generate(n){const t=matchInts(n.stem,ANSWER_FACT);if(!t)return null;const e=t[0],s=matchInts(n.correctAnswer,OPTION_FACT);if(!s)return null;const[o,r]=s;if(o*r!==e)return null;const a=Math.min(o,r),i=Math.max(o,r);return a<2||a>12||i<1||i>12?null:buildSkipCount(n,a,i,`Which fact makes ${e}?`,this.id,e)}},MD_P3_M02_GENERATORS=[symbolic$6,context$6,arrayFact,missingFactor$1,countGroups,claimSplit,contextRows,twoStepMinibus,compareProducts$2,answerFact];function buildPartition2(n,t,e,s){if(t<10||t>99||e<1||e>9)return null;const o=Math.floor(t/10)*10,r=t%10;if(r===0)return null;const a=t*e;if(a>999||a!==answerInt(n))return null;const i=o*e,l=r*e,u={type:"equation_display",terms:[`${o} × ${e}`,"+",`${r} × ${e}`,"=","__"]},d={type:"equation_display",terms:[`${i}`,"+",`${l}`,"=","__"]},m={type:"equation_display",terms:[`${i}`,"+",`${l}`,"=",`${a}`]};return buildExample({generatorId:s,question:n,title:`How do we work out ${t} × ${e}?`,computed:{twoDigit:t,oneDigit:e,tens:o,ones:r,tensProduct:i,onesProduct:l,product:a,answer:a},steps:[{visual_data:u,animation:"partition",caption:`Split ${t} into ${o} and ${r}.`,spoken:`We need ${numberToWords(t)} times ${numberToWords(e)}. Splitting ${numberToWords(t)} into ${numberToWords(o)} and ${numberToWords(r)} lets us use times tables we know, so we multiply each part.`},{visual_data:d,animation:"multiply_parts",caption:`${o} × ${e} = ${i}, ${r} × ${e} = ${l}.`,spoken:`${capitalised(numberToWords(o/10))} times ${numberToWords(e)} is ${numberToWords(o/10*e)}, and ${numberToWords(o)} is ten times bigger than ${numberToWords(o/10)}, so ${numberToWords(o)} times ${numberToWords(e)} is ${numberToWords(i)}. ${capitalised(numberToWords(r))} times ${numberToWords(e)} is ${numberToWords(l)}.`},{visual_data:m,animation:"recombine",caption:`${i} + ${l} = ${a}.`,spoken:`Now add the parts back together. ${capitalised(numberToWords(i))} plus ${numberToWords(l)} is ${numberToWords(a)}. So ${numberToWords(t)} times ${numberToWords(e)} is ${numberToWords(a)}.`}]})}function classifyFactors(n,t){return n>=10&&n<=99&&t>=1&&t<=9?[n,t]:t>=10&&t<=99&&n>=1&&n<=9?[t,n]:null}const PRODUCT$1=/^(\d+) × (\d+) = __$/,symbolic$5={id:"MD.P4.M01.partition.v1",microSkillId:"MD.P4.M01",forms:["symbolic_calculation","symbolic_choice"],generate(n){const t=matchInts(n.stem,PRODUCT$1);if(!t)return null;const e=classifyFactors(t[0],t[1]);return e?buildPartition2(n,e[0],e[1],this.id):null}};function buildContextPartition2(n,t,e,s){if(t<10||t>99||e<1||e>9)return null;const o=Math.floor(t/10)*10,r=t%10;if(r===0)return null;const a=t*e;if(a>999||a!==answerInt(n))return null;const i=o*e,l=r*e,u={type:"equation_display",terms:[`${t} × ${e}`,"=","__"]},d={type:"equation_display",terms:[`${i}`,"+",`${l}`,"=","__"]},m={type:"equation_display",terms:[`${i}`,"+",`${l}`,"=",`${a}`]};return buildExample({generatorId:s,question:n,title:`${e} groups of ${t} — how many altogether?`,computed:{twoDigit:t,oneDigit:e,tens:o,ones:r,tensProduct:i,onesProduct:l,product:a,answer:a},steps:[{visual_data:u,animation:"show_equation",caption:`${e} equal groups of ${t}. Work out ${t} × ${e}.`,spoken:`Each group has ${numberToWords(t)}, and there are ${numberToWords(e)} groups. So we need to work out ${numberToWords(t)} times ${numberToWords(e)}.`},{visual_data:d,animation:"multiply_parts",caption:`Split ${t} into ${o} and ${r}: ${o} × ${e} = ${i}, ${r} × ${e} = ${l}.`,spoken:`We split the ${numberToWords(t)} into ${numberToWords(o)} and ${numberToWords(r)}. ${capitalised(numberToWords(o))} times ${numberToWords(e)} is ${numberToWords(i)}. ${capitalised(numberToWords(r))} times ${numberToWords(e)} is ${numberToWords(l)}.`},{visual_data:m,animation:"recombine",caption:`${i} + ${l} = ${a}.`,spoken:`We add the parts back together. ${capitalised(numberToWords(i))} plus ${numberToWords(l)} is ${numberToWords(a)} altogether.`}]})}const CTX_MUL$1=/(?:holds|has|contains|seats|carries) (\d+)[^.]*\. How (?:many|much) .*?(\d+)/,context$5={id:"MD.P4.M01.partition-context.v1",microSkillId:"MD.P4.M01",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,CTX_MUL$1);if(!t)return null;const e=classifyFactors(t[0],t[1]);return e?buildContextPartition2(n,e[0],e[1],this.id):null}},MISSING=/^(\d+) × __ = (\d+)$/,missingFactor={id:"MD.P4.M01.missing-factor.v1",microSkillId:"MD.P4.M01",forms:["equation_completion"],generate(n){const t=matchInts(normaliseStem(n.stem),MISSING);
71if(!t)return null;const[e,s]=t;if(e<10||e>99||s<1||s>999||s%e!==0)return null;const o=s/e;if(o<1||o>12||o!==answerInt(n))return null;const r={type:"equation_display",terms:[`${e}`,"×","__","=",`${s}`]},a={type:"equation_display",terms:[`${s}`,"÷",`${e}`,"=","__"]},i={type:"equation_display",terms:[`${e}`,"×",`${o}`,"=",`${s}`]},l=o%2===0?2:1,u=[];for(let f=l;f<=o;f+=2)u.push({step:f,value:e*f});const d=u.map(f=>`${e} × ${f.step} = ${f.value}`).join(", "),m=u.map(f=>`${numberToWords(e)} times ${numberToWords(f.step)} is ${numberToWordsLong(f.value)}`).join(", "),h={known:e,missing:o,product:s,answer:o};return u.forEach(f=>{h[`step${f.step}`]=f.step,h[`value${f.step}`]=f.value}),buildExample({generatorId:this.id,question:n,title:`What times ${e} makes ${s}?`,computed:h,steps:[{visual_data:r,animation:"show_equation",caption:`We need ${e} × __ = ${s}.`,spoken:`We are looking for the number that multiplies ${numberToWordsLong(e)} to make ${numberToWordsLong(s)}.`},{visual_data:a,animation:"inverse",caption:`So work out ${s} ÷ ${e}.`,spoken:`Multiplying and dividing are opposites, so we can find the missing factor by working out ${numberToWordsLong(s)} divided by ${numberToWordsLong(e)}.`},{visual_data:i,animation:"recombine",caption:`${d}. So ${e} × ${o} = ${s}.`,spoken:`We step through the ${numberToWords(e)} times table: ${m}. So the missing factor is ${numberToWords(o)}.`}]})}},CLAIM$2=/^(\w+) says (\d+) × (\d+) = (\d+)\. Is (?:he|she|they) right\?$/,claimPartition$2={id:"MD.P4.M01.claim-partition.v1",microSkillId:"MD.P4.M01",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM$2);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r),u=classifyFactors(a,i);if(!u)return null;const[d,m]=u,h=Math.floor(d/10)*10,f=d%10;if(f===0)return null;const g=d*m;if(g>999)return null;const b=g===l,x=n.correctAnswer.trim().toLowerCase();if(b?!x.startsWith("yes"):!x.startsWith("no"))return null;const y=h*m,E=f*m;return buildExample({generatorId:this.id,question:n,title:`${e} says ${a} × ${i} = ${l}. Is that right?`,computed:{a,b:i,claim:l,tens:h,ones:f,tensProduct:y,onesProduct:E,product:g},steps:[{visual_data:{type:"equation_display",terms:[`${a} × ${i}`,"=",`${l}`]},animation:"show_start",caption:`${e} says ${a} × ${i} = ${l}. Let's check.`,spoken:`${e} says ${numberToWords(a)} times ${numberToWords(i)} is ${numberToWordsLong(l)}. To check, we work it out for ourselves by splitting ${numberToWords(d)} into tens and ones.`},{visual_data:{type:"equation_display",terms:[`${h} × ${m}`,"+",`${f} × ${m}`,"=",`${g}`]},animation:"multiply_parts",caption:`${h} × ${m} = ${y}, ${f} × ${m} = ${E}. Total ${g}.`,spoken:`${capitalised(numberToWords(h))} times ${numberToWords(m)} is ${numberToWords(y)}. ${capitalised(numberToWords(f))} times ${numberToWords(m)} is ${numberToWords(E)}. Together that is ${numberToWords(g)}.`},{visual_data:{type:"equation_display",terms:[`${a} × ${i}`,"=",`${g}`]},animation:"write_answer",caption:b?`${e} is right: ${a} × ${i} = ${g}.`:`${e} is wrong: ${a} × ${i} = ${g}, not ${l}.`,spoken:b?`${e} is right. ${capitalised(numberToWords(a))} times ${numberToWords(i)} really is ${numberToWords(g)}.`:`Our answer is ${numberToWords(g)}, and that does not match ${numberToWordsLong(l)}. So ${e} is wrong.`}]})}};function decisivePlaceFromRight$1(n,t){const e=Math.max(String(n).length,String(t).length),s=String(n).padStart(e,"0"),o=String(t).padStart(e,"0");for(let r=0;r<e;r++)if(s[r]!==o[r])return e-1-r;return 0}const HIGHLIGHT_BY_PLACE$1={0:"highlight_ones",1:"highlight_tens",2:"highlight_hundreds"},COMPARE$3=/^Which is greater: (\d+) × (\d+) or (\d+) × (\d+)\?$/,compareProducts$1={id:"MD.P4.M01.compare-products.v1",microSkillId:"MD.P4.M01",forms:["comparison_statement"],generate(n){const t=matchInts(n.stem,COMPARE$3);if(!t)return null;const[e,s,o,r]=t,a=classifyFactors(e,s),i=classifyFactors(o,r);if(!a||!i)return null;const[l,u]=a,[d,m]=i,h=Math.floor(l/10)*10,f=l%10,g=Math.floor(d/10)*10,b=d%10;if(f===0||b===0)return null;const x=e*s,y=o*r,E=x===y?"They are equal":x>y?`${e} × ${s}`:`${o} × ${r}`;if(n.correctAnswer.trim()!==E)return null;
71const k=h*u,T=f*u,A=g*m,N=b*m,S=x===y?"=":x>y?">":"<",M=x===y?"write_answer":`write_answer_${HIGHLIGHT_BY_PLACE$1[decisivePlaceFromRight$1(x,y)]}`;return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${e} × ${s} or ${o} × ${r}?`,computed:{a:e,b:s,c:o,d:r,tensL:h,onesL:f,tensR:g,onesR:b,left:x,right:y},steps:[{visual_data:{type:"equation_display",terms:[`${h} × ${u}`,"+",`${f} × ${u}`,"=",`${x}`]},animation:"show_start",caption:`First: ${e} × ${s} = ${h} × ${u} + ${f} × ${u} = ${x}.`,spoken:`${capitalised(numberToWords(e))} times ${numberToWords(s)}. ${capitalised(numberToWords(h))} times ${numberToWords(u)} is ${numberToWords(k)}. ${capitalised(numberToWords(f))} times ${numberToWords(u)} is ${numberToWords(T)}. Together that is ${numberToWords(x)}.`},{visual_data:{type:"equation_display",terms:[`${g} × ${m}`,"+",`${b} × ${m}`,"=",`${y}`]},animation:"show_second",caption:`Second: ${o} × ${r} = ${g} × ${m} + ${b} × ${m} = ${y}.`,spoken:`${capitalised(numberToWords(o))} times ${numberToWords(r)}. ${capitalised(numberToWords(g))} times ${numberToWords(m)} is ${numberToWords(A)}. ${capitalised(numberToWords(b))} times ${numberToWords(m)} is ${numberToWords(N)}. Together that is ${numberToWords(y)}.`},{visual_data:{type:"comparison",left:x,right:y,symbol:S},animation:M,caption:x===y?`${e} × ${s} and ${o} × ${r} are equal.`:`${E} is greater (${Math.max(x,y)}).`,spoken:x===y?`Both are ${numberToWords(x)}, so they are equal.`:`${capitalised(numberToWords(Math.max(x,y)))} is more than ${numberToWords(Math.min(x,y))}, so the ${x>y?"first":"second"} one is greater.`}]})}},MISSING_DIGIT$1=/^(\d+) × (\d+) = (\d+)_\. What is the missing digit\?$/,missingDigit$1={id:"MD.P4.M01.missing-digit.v1",microSkillId:"MD.P4.M01",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem).match(MISSING_DIGIT$1);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=classifyFactors(e,s);if(!r)return null;const[a,i]=r,l=Math.floor(a/10)*10,u=a%10;if(u===0)return null;const d=a*i;if(Math.floor(d/10)!==o)return null;const m=d%10;if(m!==answerInt(n))return null;const h=l*i,f=u*i;return buildExample({generatorId:this.id,question:n,title:`${e} × ${s} = ${o}_. What's the missing digit?`,computed:{a:e,b:s,prefix:o,tens:l,ones:u,tensProduct:h,onesProduct:f,product:d,missing:m,answer:m},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${o}_`]},animation:"show_start",caption:`${e} × ${s} = ${o}_. Find the missing digit.`,spoken:`The answer to ${numberToWords(e)} times ${numberToWords(s)} has its last digit missing. To find it, we work out the whole answer.`},{visual_data:{type:"equation_display",terms:[`${l} × ${i}`,"+",`${u} × ${i}`,"=",`${d}`]},animation:"multiply_parts",caption:`${l} × ${i} = ${h}, ${u} × ${i} = ${f}. Total ${d}.`,spoken:`${capitalised(numberToWords(l))} times ${numberToWords(i)} is ${numberToWords(h)}. ${capitalised(numberToWords(u))} times ${numberToWords(i)} is ${numberToWords(f)}. Together that is ${numberToWords(d)}.`},{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${d}`]},animation:"recombine",caption:`${e} × ${s} = ${d}. Missing digit: ${m}.`,spoken:`The full answer is ${numberToWords(d)}, so the missing digit is ${numberToWords(m)}.`}]})}},TWO_STEP_SUB=/^A pack has (\d+) stickers\. A shop orders (\d+) packs, but (\d+) stickers arrive damaged and cannot be sold\. How many stickers can the shop sell\?$/,twoStepSub={id:"MD.P4.M01.two-step-sub.v1",microSkillId:"MD.P4.M01",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(TWO_STEP_SUB);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=classifyFactors(e,s);if(!r)return null;const[a,i]=r,l=Math.floor(a/10)*10,u=a%10;if(u===0)return null;const d=e*s,m=d-o;if(m!==answerInt(n))return null;const h=l*i,f=u*i;return buildExample({generatorId:this.id,question:n,title:`${s} packs of ${e}, minus ${o} damaged — how many can sell?`,computed:{perPack:e,packs:s,damaged:o,tens:l,ones:u,tensProduct:h,onesProduct:f,ordered:d,sellable:m,answer:m},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"-",`${o}`,"=","__"]},animation:"show_equation",caption:`First work out ${e} × ${s}, then take away ${o}.`,spoken:`${capitalised(numberToWords(s))} packs of ${numberToWords(e)} stickers, but ${numberToWords(o)} are damaged. First we multiply, then we subtract.`},{visual_data:{type:"equation_display",terms:[`${l} × ${i}`,"+",`${u} × ${i}`,"=",`${d}`]},animation:"multiply",caption:`${e} × ${s}: ${l} × ${i} = ${h}, ${u} × ${i} = ${f}, total ${d}.`,spoken:`${capitalised(numberToWords(l))} times ${numberToWords(i)} is ${numberToWords(h)}. ${capitalised(numberToWords(u))} times ${numberToWords(i)} is ${numberToWords(f)}. Together that is ${numberToWords(d)}.`},{visual_data:{type:"equation_display",terms:[`${d}`,"-",`${o}`,"=",`${m}`]},animation:"subtract",caption:`${d} - ${o} = ${m}
71 stickers can be sold.`,spoken:`${capitalised(numberToWords(d))} take away ${numberToWords(o)} is ${numberToWords(m)} stickers that can be sold.`}]})}},MD_P4_M01_GENERATORS=[symbolic$5,context$5,missingFactor,claimPartition$2,compareProducts$1,missingDigit$1,twoStepSub];function buildPartition3(n,t,e,s,o){if(t<100||t>999||e<1||e>9)return null;const r=Math.floor(t/100)*100,a=Math.floor(t%100/10)*10,i=t%10;if(i===0)return null;const l=t*e;if(l>9999||l!==answerInt(n))return null;const u=r*e,d=a*e,m=i*e,h="",f="",g=a===0,b=g?[`${h}${r} × ${e}`,"+",`${h}${i} × ${e}`,"=","__"]:[`${h}${r} × ${e}`,"+",`${h}${a} × ${e}`,"+",`${h}${i} × ${e}`,"=","__"],x=g?[`${h}${u}`,"+",`${h}${m}`,"=","__"]:[`${h}${u}`,"+",`${h}${d}`,"+",`${h}${m}`,"=","__"],y=g?[`${h}${u}`,"+",`${h}${m}`,"=",`${h}${l}`]:[`${h}${u}`,"+",`${h}${d}`,"+",`${h}${m}`,"=",`${h}${l}`],E={type:"equation_display",terms:b},k={type:"equation_display",terms:x},T={type:"equation_display",terms:y},A=g?`Split ${h}${t} into ${h}${r} and ${h}${i} (no tens to split).`:`Split ${h}${t} into ${h}${r}, ${h}${a} and ${h}${i}.`,N=g?`${capitalised(numberToWordsLong(t))}${f} times ${numberToWords(e)}. We split it by place value into ${numberToWordsLong(r)} and ${numberToWords(i)} — there are no tens — then multiply each part.`:`${capitalised(numberToWordsLong(t))}${f} times ${numberToWords(e)}. We split it by place value into ${numberToWordsLong(r)}, ${numberToWords(a)} and ${numberToWords(i)}, then multiply each part.`,S=g?`${h}${u} and ${h}${m}.`:`${h}${u}, ${h}${d} and ${h}${m}.`,M=g?`${capitalised(numberToWordsLong(r))} times ${numberToWords(e)} is ${numberToWordsLong(u)}. ${capitalised(numberToWords(i))} times ${numberToWords(e)} is ${numberToWordsLong(m)}.`:`${capitalised(numberToWordsLong(r))} times ${numberToWords(e)} is ${numberToWordsLong(u)}. ${capitalised(numberToWords(a))} times ${numberToWords(e)} is ${numberToWordsLong(d)}. ${capitalised(numberToWords(i))} times ${numberToWords(e)} is ${numberToWordsLong(m)}.`,L=g?`${h}${u} + ${h}${m} = ${h}${l}.`:`${h}${u} + ${h}${d} + ${h}${m} = ${h}${l}.`;return buildExample({generatorId:o,question:n,title:`How do we work out ${t} × ${e}?`,computed:{threeDigit:t,oneDigit:e,hundreds:r,tens:a,ones:i,hProduct:u,tProduct:d,oProduct:m,product:l,answer:l},steps:[{visual_data:E,animation:"partition",caption:A,spoken:N},{visual_data:k,animation:"multiply_parts",caption:S,spoken:M},{visual_data:T,animation:"recombine",caption:L,spoken:`We add the parts back together. The total is ${numberToWordsLong(l)}${f}.`}]})}function eqFactors(n){const t=n.visualData;if(!t||t.type!=="equation_display")return null;const e=t.terms.filter(s=>typeof s=="number"||typeof s=="string"&&/^\d+$/.test(s)).map(Number);return e.length!==2?null:[e[0],e[1]]}function classify3(n,t){return n>=100&&n<=999&&t>=1&&t<=9?[n,t]:t>=100&&t<=999&&n>=1&&n<=9?[t,n]:null}const PRODUCT=/^(\d+) × (\d+) = __$/,symbolic$4={id:"MD.P4.M02.partition.v1",microSkillId:"MD.P4.M02",forms:["symbolic_calculation","varied_symbolic_calculation","misconception_contrast_probe","symbolic_choice"],generate(n){const t=matchInts(n.stem,PRODUCT),e=t?classify3(t[0],t[1]):(()=>{const s=eqFactors(n);return s?classify3(s[0],s[1]):null})();return e?buildPartition3(n,e[0],e[1],!1,this.id):null}};function buildContextPartition3(n,t,e,s,o){if(t<100||t>999||e<1||e>9)return null;const r=Math.floor(t/100)*100,a=Math.floor(t%100/10)*10,i=t%10;if(i===0)return null;const l=t*e;if(l>9999||l!==answerInt(n))return null;const u=r*e,d=a*e,m=i*e,h=s?"£":"",f=s?" pounds":"",g=a===0,b={type:"equation_display",terms:[`${h}${t} × ${e}`,"=","__"]},x=g?[`${h}${u}`,"+",`${h}${m}`,"=","__"]:[`${h}${u}`,"+",`${h}${d}`,"+",`${h}${m}`,"=","__"],y=g?[`${h}${u}`,"+",`${h}${m}`,"=",`${h}${l}`]:[`${h}${u}`,"+",`${h}${d}`,"+",`${h}${m}`,"=",`${h}${l}`],E={type:"equation_display",terms:x},k={type:"equation_display",terms:y},T=g?`Split ${h}${t} into ${h}${r} and ${h}${i} (no tens to split): ${h}${u} and ${h}${m}.`:`Split ${h}${t} into ${h}${r}, ${h}${a} and ${h}${i}: ${h}${u}, ${h}${d} and ${h}${m}.`,A=g?`We split the ${numberToWordsLong(t)}${f} into ${numberToWordsLong(r)} and ${numberToWords(i)} — there are no tens. ${capitalised(numberToWordsLong(r))} times ${numberToWords(e)} is ${numberToWordsLong(u)}. ${capitalised(numberToWords(i))} times ${numberToWords(e)} is ${numberToWordsLong(m)}.`:`We split the ${numberToWordsLong(t)}${f} into ${numberToWordsLong(r)}, ${numberToWords(a)} and ${numberToWords(i)}. ${capitalised(numberToWordsLong(r))} times ${numberToWords(e)} is ${numberToWordsLong(u)}. ${capitalised(numberToWords(a))} times ${numberToWords(e)} is ${numberToWordsLong(d)}. ${capitalised(numberToWords(i))} times ${numberToWords(e)} is ${numberToWordsLong(m)}.`,N=g?`${h}${u} + ${h}${m} = ${h}${l}.`:`${h}${u} + ${h}${d} + ${h}${m} = ${h}${l}.`;return buildExample({generatorId:o,question:n,title:`How do we work out ${t} × ${e}
71?`,computed:{threeDigit:t,oneDigit:e,hundreds:r,tens:a,ones:i,hProduct:u,tProduct:d,oProduct:m,product:l,answer:l},steps:[{visual_data:b,animation:"show_equation",caption:`Work out ${t} × ${e}.`,spoken:`We need to work out ${numberToWordsLong(t)}${f} times ${numberToWords(e)}.`},{visual_data:E,animation:"multiply_parts",caption:T,spoken:A},{visual_data:k,animation:"recombine",caption:N,spoken:`We add the parts back together. The total is ${numberToWordsLong(l)}${f}.`}]})}const CTX_MONEY=/costs £(\d+)\. What do (\d+) \w+ cost/,CTX_MUL=/(?:holds|has|contains|seats|carries|sells|makes|reads|weighs) (\d+)[^.]*\. How (?:many|heavy|long|much) .*?(\d+)/,context$4={id:"MD.P4.M02.partition-context.v1",microSkillId:"MD.P4.M02",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,CTX_MONEY);if(t){const o=classify3(t[0],t[1]);return o?buildContextPartition3(n,o[0],o[1],!0,this.id):null}const e=matchInts(n.stem,CTX_MUL);if(!e)return null;const s=classify3(e[0],e[1]);return s?buildContextPartition3(n,s[0],s[1],!1,this.id):null}},CTX_VERIFY=/worked out (\d+) × (\d+) and got \d+[^.]*\. Type the correct answer/,verify={id:"MD.P4.M02.verify.v1",microSkillId:"MD.P4.M02",forms:["context_reasoning"],generate(n){const t=matchInts(n.stem,CTX_VERIFY);if(!t)return null;const e=classify3(t[0],t[1]);return e?buildPartition3(n,e[0],e[1],!1,this.id):null}},CLAIM$1=/^(\w+) says (\d+) × (\d+) = (\d+)\. Is (?:he|she|they) (?:right|correct)\?$/,claimPartition$1={id:"MD.P4.M02.claim-partition.v1",microSkillId:"MD.P4.M02",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM$1);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r),u=classify3(a,i);if(!u)return null;const[d,m]=u,h=Math.floor(d/100)*100,f=Math.floor(d%100/10)*10,g=d%10;if(f===0||g===0)return null;const b=d*m;if(b>9999)return null;const x=b===l,y=n.correctAnswer.trim().toLowerCase();if(x?!y.startsWith("yes"):!y.startsWith("no"))return null;const E=h*m,k=f*m,T=g*m;return buildExample({generatorId:this.id,question:n,title:`${e} says ${a} × ${i} = ${l}. Is that right?`,computed:{a,b:i,claim:l,hundreds:h,tens:f,ones:g,hProduct:E,tProduct:k,oProduct:T,product:b},steps:[{visual_data:{type:"equation_display",terms:[`${a} × ${i}`,"=",`${l}`]},animation:"show_start",caption:`${e} says ${a} × ${i} = ${l}. Let's check.`,spoken:`${e} says ${numberToWordsLong(a)} times ${numberToWords(i)} is ${numberToWordsLong(l)}. To check, we work it out for ourselves by splitting ${numberToWordsLong(d)} into hundreds, tens and ones.`},{visual_data:{type:"equation_display",terms:[`${E}`,"+",`${k}`,"+",`${T}`,"=",`${b}`]},animation:"multiply_parts",caption:`${h} × ${m} + ${f} × ${m} + ${g} × ${m} = ${b}.`,spoken:`${capitalised(numberToWordsLong(h))} times ${numberToWords(m)} is ${numberToWordsLong(E)}. ${capitalised(numberToWords(f))} times ${numberToWords(m)} is ${numberToWordsLong(k)}. ${capitalised(numberToWords(g))} times ${numberToWords(m)} is ${numberToWordsLong(T)}. Together that is ${numberToWordsLong(b)}.`},{visual_data:{type:"equation_display",terms:[`${a} × ${i}`,"=",`${b}`]},animation:"write_answer",caption:x?`${e} is right: ${a} × ${i} = ${b}.`:`${e} is wrong: ${a} × ${i} = ${b}, not ${l}.`,spoken:x?`${e} is right. ${capitalised(numberToWordsLong(a))} times ${numberToWords(i)} really is ${numberToWordsLong(b)}.`:`Our answer is ${numberToWordsLong(b)}, and that does not match ${numberToWordsLong(l)}. So ${e} is wrong.`}]})}};function decisivePlaceFromRight3(n,t){const e=Math.max(String(n).length,String(t).length),s=String(n).padStart(e,"0"),o=String(t).padStart(e,"0");for(let r=0;r<e;r++)if(s[r]!==o[r])return e-1-r;return 0}const HIGHLIGHT_BY_PLACE3={0:"highlight_ones",1:"highlight_tens",2:"highlight_hundreds"},COMPARE$2=/^Which is greater: (\d+) × (\d+) or (\d+) × (\d+)\?$/,compareProducts={id:"MD.P4.M02.compare-products.v1",microSkillId:"MD.P4.M02",forms:["comparison_statement"],generate(n){const t=matchInts(n.stem,COMPARE$2);if(!t)return null;const[e,s,o,r]=t,a=classify3(e,s),i=classify3(o,r);if(!a||!i)return null;const[l,u]=a,[d,m]=i,h=Math.floor(l/100)*100,f=Math.floor(l%100/10)*10,g=l%10,b=Math.floor(d/100)*100,x=Math.floor(d%100/10)*10,y=d%10;if(g===0||y===0||f===0||x===0)return null;const E=e*s,k=o*r,T=E===k?"They are equal":E>k?`${e} × ${s}`:`${o} × ${r}`;if(n.correctAnswer.trim()!==T)return null;const A=h*u,N=f*u,S=g*u,M=b*m,L=x*m,P=y*m,F=E===k?"=":E>k?">":"<",D=E===k?null:decisivePlaceFromRight3(E,k);if(D!=null&&D>2)return null;const U=D==null?"write_answer":`write_answer_${HIGHLIGHT_BY_PLACE3[D]}`;return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${e} × ${s} or ${o} × ${r}?`,computed:{a:e,b:s,c:o,d:r,hL:h,tL:f,oL:g,hR:b,tR:x,oR:y,left:E,right:k},steps:[{visual_data:{type:"equation_display",terms:[`${h} × ${u}`,"+",`${f} × ${u}`,"+",`${g} × ${u}`,"=",`${E}`]},animation:"show_start",caption:`First: ${e} × ${s} = ${h} × ${u} + ${f} × ${u} + ${g} × ${u} = ${E}.`,spoken:`${capitalised(numberToWordsLong(e))} times ${numberToWords(s)}. ${capitalised(numberToWordsLong(h))} times ${numberToWords(u)} is ${numberToWordsLong(A)}. ${capitalised(numberToWords(f))} times ${numberToWords(u)} is ${numberToWordsLong(N)}. ${capitalised(numberToWords(g))} times ${numberToWords(u)} is ${numberToWordsLong(S)}. Together that is ${numberToWordsLong(E)}.`},{visual_data:{type:"equation_display",terms:[`${b} × ${m}`,"+",`${x} × ${m}`,"+",`${y} × ${m}`,"=",`${k}`]},animation:"show_second",caption:`Second: ${o} × ${r} = ${b} × ${m} + ${x} × ${m} + ${y} × ${m} = ${k}.`,spoken:`${capitalised(numberToWordsLong(o))} times ${numberToWords(r)}. ${capitalised(numberToWordsLong(b))} times ${numberToWords(m)} is ${numberToWordsLong(M)}. ${capitalised(numberToWords(x))} times ${numberToWords(m)} is ${numberToWordsLong(L)}. ${capitalised(numberToWords(y))} times ${numberToWords(m)} is ${numberToWordsLong(P)}. Together that is ${numberToWordsLong(k)}.`},{visual_data:{type:"comparison",left:E,right:k,symbol:F},animation:U,caption:E===k?`${e} × ${s} and ${o} × ${r} are equal.`:`${T} is greater (${Math.max(E,k)}).`,spoken:E===k?`Both are ${numberToWordsLong(E)}, so they are equal.`:`${capitalised(numberToWordsLong(Math.max(E,k)))} is more than ${numberToWordsLong(Math.min(E,k))}, so the ${E>k?"first":"second"} one is greater.`}]})}},MISSING_DIGIT=/^(\d+) × (\d+) = (\d+)_\. What is the missing digit\?$/,missingDigit={id:"MD.P4.M02.missing-digit.v1",microSkillId:"MD.P4.M02",forms:["equation_completion"],generate(n){const t=normaliseStem(n.stem).match(MISSING_DIGIT);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=classify3(e,s);if(!r)return null;const[a,i]=r,l=Math.floor(a/100)*100,u=Math.floor(a%100/10)*10,d=a%10;if(u===0||d===0)return null;const m=a*i;if(Math.floor(m/10)!==o)return null;const h=m%10;if(h!==answerInt(n))return null;const f=l*i,g=u*i,b=d*i;return buildExample({generatorId:this.id,question:n,title:`${e} × ${s} = ${o}_. What's the missing digit?`,computed:{a:e,b:s,prefix:o,hundreds:l,tens:u,ones:d,hProduct:f,tProduct:g,oProduct:b,product:m,missing:h,answer:h},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${o}_`]},animation:"show_start",caption:`${e} × ${s} = ${o}_. Find the missing digit.`,spoken:`The answer to ${numberToWordsLong(e)} times ${numberToWords(s)} has its last digit missing. To find it, we work out the whole answer.`},{visual_data:{type:"equation_display",terms:[`${f}`,"+",`${g}`,"+",`${b}`,"=",`${m}`]},animation:"multiply_parts",caption:`${l} × ${i} + ${u} × ${i} + ${d} × ${i} = ${m}.`,spoken:`${capitalised(numberToWordsLong(l))} times ${numberToWords(i)} is ${numberToWordsLong(f)}. ${capitalised(numberToWords(u))} times ${numberToWords(i)} is ${numberToWordsLong(g)}. ${capitalised(numberToWords(d))} times ${numberToWords(i)} is ${numberToWordsLong(b)}. Together that is ${numberToWordsLong(m)}.`},{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${m}`]},animation:"recombine",caption:`${e} × ${s} = ${m}. Missing digit: ${h}.`,spoken:`The full answer is ${numberToWordsLong(m)}, so the missing digit is ${numberToWords(h)}.`}]})}},TWO_STEP_ADD=/^A factory makes (\d+) bottles each hour\. It runs for (\d+) hours, then makes (\d+) more bottles as overtime\. How many bottles does it make in total\?$/,twoStepAdd={id:"MD.P4.M02.two-step-add.v1",microSkillId:"MD.P4.M02",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(TWO_STEP_ADD);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=classify3(e,s);if(!r)return null;const[a,i]=r,l=Math.floor(a/100)*100,u=Math.floor(a%100/10)*10,d=a%10;if(d===0)return null;const m=e*s,h=m+o;if(h!==answerInt(n))return null;const f=l*i,g=u*i,b=d*i,x=u===0;return buildExample({generatorId:this.id,question:n,title:`${s} hours of ${e}, plus ${o}
71 overtime — how many?`,computed:{perHour:e,hours:s,overtime:o,hundreds:l,tens:u,ones:d,hProduct:f,tProduct:g,oProduct:b,shift:m,total:h,answer:h},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"+",`${o}`,"=","__"]},animation:"show_equation",caption:`First work out ${e} × ${s}, then add ${o}.`,spoken:`${capitalised(numberToWords(s))} hours making ${numberToWordsLong(e)} bottles each, plus overtime. First we multiply, then we add.`},{visual_data:x?{type:"equation_display",terms:[`${l} × ${i}`,"+",`${d} × ${i}`,"=",`${m}`]}:{type:"equation_display",terms:[`${l} × ${i}`,"+",`${u} × ${i}`,"+",`${d} × ${i}`,"=",`${m}`]},animation:"multiply",caption:x?`${e} × ${s}: ${l} × ${i} = ${f}, ${d} × ${i} = ${b}, total ${m}.`:`${e} × ${s}: ${l} × ${i} = ${f}, ${u} × ${i} = ${g}, ${d} × ${i} = ${b}, total ${m}.`,spoken:x?`${capitalised(numberToWordsLong(l))} times ${numberToWords(i)} is ${numberToWordsLong(f)}. ${capitalised(numberToWords(d))} times ${numberToWords(i)} is ${numberToWordsLong(b)}. Together that is ${numberToWordsLong(m)}.`:`${capitalised(numberToWordsLong(l))} times ${numberToWords(i)} is ${numberToWordsLong(f)}. ${capitalised(numberToWords(u))} times ${numberToWords(i)} is ${numberToWordsLong(g)}. ${capitalised(numberToWords(d))} times ${numberToWords(i)} is ${numberToWordsLong(b)}. Together that is ${numberToWordsLong(m)}.`},{visual_data:{type:"equation_display",terms:[`${m}`,"+",`${o}`,"=",`${h}`]},animation:"add",caption:`${m} + ${o} = ${h} bottles in total.`,spoken:`${capitalised(numberToWordsLong(m))} plus ${numberToWords(o)} more is ${numberToWordsLong(h)} bottles altogether.`}]})}},ESTIMATE_ROUND=/^Round (\d+) to the nearest 100, then multiply the rounded number by (\d+)\. What is your estimate\?$/,estimateRound={id:"MD.P4.M02.estimate-round.v1",microSkillId:"MD.P4.M02",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem).match(ESTIMATE_ROUND);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<100||e>999||s<1||s>9||e%100===0)return null;const o=Math.floor(e/100)*100,r=o+100,a=o+50,i=e-o,l=r-e,u=i<=l?o:r,d=u*s;if(d!==answerInt(n))return null;const m=i===l,h={from:e,to:u,label:m?"rounds up":i<l?`${i} away`:`${l} away`};return buildExample({generatorId:this.id,question:n,title:`Round ${e} to the nearest 100, then multiply by ${s}.`,computed:{N:e,lower:o,upper:r,mid:a,rounded:u,mult:s,product:d,answer:d},steps:[{visual_data:{type:"number_line",min:o,max:r,marked:[e],hops:[],unknown:null},animation:"show_start",caption:`${e} sits between ${o} and ${r}.`,spoken:`To estimate, we first round ${numberToWordsLong(e)} to the nearest hundred. It sits between ${numberToWordsLong(o)} and ${numberToWordsLong(r)} — the two nearest hundreds.`},{visual_data:{type:"number_line",min:o,max:r,marked:[e,a],hops:[h],unknown:u},animation:"hop_to_nearer",caption:m?`${e} rounds to ${u} — halfway rounds up.`:`${e} rounds to ${u}.`,spoken:m?`${capitalised(numberToWordsLong(e))} is exactly halfway, so by the rounding rule we round up to ${numberToWordsLong(u)}.`:`${capitalised(numberToWordsLong(e))} is closer to ${numberToWordsLong(u)}, so it rounds to ${numberToWordsLong(u)}.`},{visual_data:{type:"equation_display",terms:[`${u} × ${s}`,"=",`${d}`]},animation:"multiply",caption:`${u} × ${s} = ${d}. That's the estimate.`,spoken:`Now we multiply the rounded number: ${numberToWordsLong(u)} times ${numberToWords(s)} is ${numberToWordsLong(d)}. That is our estimate.`}]})}},WHICH_CALC=/^A .*? costs £(\d+)\. Which calculation gives the cost of (\d+) \w+\?$/,whichCalculation$1={id:"MD.P4.M02.which-calculation.v1",microSkillId:"MD.P4.M02",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem).match(WHICH_CALC);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>999||s<2||s>6||n.correctAnswer.trim()!==`${e} × ${s}`)return null;const o=e*s,r=[];for(let a=0;a<s;a++)a>0&&r.push("+"),r.push(`£${e}`);return r.push("=","__"),buildExample({generatorId:this.id,question:n,title:`Which calculation gives the cost of ${s} packs of £${e}?`,computed:{price:e,packs:s,total:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:r},animation:"show_addition",caption:`${s} packs of £${e}, added ${s} times.`,spoken:`${capitalised(numberToWords(s))} packs that each cost ${numberToWordsLong(e)} pounds. We add ${numberToWords(e)} pounds ${numberToWords(s)} times.`},{visual_data:{type:"equation_display",terms:[`${s} × £${e}`,"=","__"]},animation:"group_as_multiply",caption:`Adding the same amount ${s} times is the same as ${e} × ${s}.`,spoken:`Adding the same amount over and over is multiplying, so this is ${numberToWords(e)} times ${numberToWords(s)}.`},{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`£${o}`]},animation:"write_answer",caption:`${e} × ${s} = £${o}. That's the calculation.`,spoken:`${capitalised(numberToWords(e))} times ${numberToWords(s)} is ${numberToWordsLong(o)} pounds, so that is the calculation that gives the cost.`}]})}},MD_P4_M02_GENERATORS=[symbolic$4,context$4,verify,claimPartition$1,compareProducts,missingDigit,twoStepAdd,estimateRound,whichCalculation$1];function divisionColumns$1(n,t){const e=String(n).split("").map(Number),s=[];let o=0;for(const r of e){const a=o*10+r,i=Math.floor(a/t),l=a-i*t;s.push({digit:r,carryIn:o,working:a,q:i,carryOut:l}),o=l}return s}function isLeadingZeroCol$1(n,t){return t<n.length-1&&n.slice(0,t+1).every(e=>e.q===0)}function columnsComputed$1(n,t=""){const e={};
71return n.forEach((s,o)=>{e[`${t}col${o}Working`]=s.working,e[`${t}col${o}CarryIn`]=s.carryIn,e[`${t}col${o}Q`]=s.q}),e}function narrateColumns$1(n,t){const e=[],s=[];for(let o=0;o<n.length;o++){if(isLeadingZeroCol$1(n,o))continue;const r=n[o],a=r.carryIn>0?`${capitalised(numberToWordsLong(r.carryIn))} remainder makes ${numberToWordsLong(r.working)}. `:"";r.q>0?(e.push(`${t}s in ${r.working}? ${r.q}.`),s.push(`${a}${capitalised(numberToWords(t))} goes into ${numberToWordsLong(r.working)} ${numberToWords(r.q)} times.`)):(e.push(`${t} doesn't go into ${r.working}.`),s.push(`${a}${capitalised(numberToWords(t))} doesn't go into ${numberToWordsLong(r.working)}, so we carry it on.`))}return{caption:e.join(" "),spoken:s.join(" ")}}function buildBusStop(n,t,e,s){if(t<10||t>999||e<2||e>9||t%e!==0)return null;const o=t/e;if(o!==answerInt(n))return null;const r=divisionColumns$1(t,e),i=String(t).split("").map(Number)[0];if(!(i>=e)&&t<100)return null;const{caption:u,spoken:d}=narrateColumns$1(r,e),m={type:"short_division",dividend:t,divisor:e},h={type:"short_division",dividend:t,divisor:e,reveal_columns:r.length},f={type:"short_division",dividend:t,divisor:e};return buildExample({generatorId:s,question:n,title:`How do we work out ${t} ÷ ${e}?`,computed:{dividend:t,divisor:e,quotient:o,answer:o,leadDigit:i,...columnsComputed$1(r)},steps:[{visual_data:m,animation:"set_up",caption:"Set up the bus stop.",spoken:"First we set up the bus stop. The number we are sharing goes inside, and the number we divide by sits just outside on the left."},{visual_data:h,animation:"divide_digits",caption:u,spoken:`We work from the left. ${d}`},{visual_data:f,animation:"reveal_quotient",caption:`${t} ÷ ${e} = ${o}.`,spoken:`Reading above the bar, ${numberToWordsLong(t)} divided by ${numberToWords(e)} is ${numberToWordsLong(o)}.`}]})}const DIVISION=/^(\d+) ÷ (\d+) = __$/,symbolic$3={id:"MD.P4.M03.busstop.v1",microSkillId:"MD.P4.M03",forms:["symbolic_calculation","symbolic_choice"],generate(n){const t=matchInts(n.stem,DIVISION);return t?buildBusStop(n,t[0],t[1],this.id):null}},CTX_SHARE$1=/(\d+) \w+ are (?:shared|split|arranged) equally (?:among|between|on) (\d+)/,CTX_PACK=/(?:packs|puts) (\d+) \w+ equally into (\d+)/,context$3={id:"MD.P4.M03.busstop-context.v1",microSkillId:"MD.P4.M03",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,CTX_SHARE$1)??matchInts(n.stem,CTX_PACK);return t?buildBusStop(n,t[0],t[1],this.id):null}},CLAIM=/^(\w+) says (\d+) ÷ (\d+) = (\d+)\. Is (?:he|she|they) right\?$/,claimDivide={id:"MD.P4.M03.claim-divide.v1",microSkillId:"MD.P4.M03",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM);if(!t)return null;const e=Number(t[2]),s=Number(t[3]),o=Number(t[4]),r=t[1];if(e<10||e>999||s<2||s>9||e%s!==0)return null;const a=e/s,i=a===o,l=n.correctAnswer.trim().toLowerCase();if(i?!l.startsWith("yes"):!l.startsWith("no"))return null;const d=String(e).split("").map(Number)[0];if(!(d>=s)&&e<100)return null;const h=divisionColumns$1(e,s),{caption:f,spoken:g}=narrateColumns$1(h,s),b={type:"short_division",dividend:e,divisor:s},x={type:"short_division",dividend:e,divisor:s,reveal_columns:h.length},y={type:"short_division",dividend:e,divisor:s};return buildExample({generatorId:this.id,question:n,title:`${r} says ${e} ÷ ${s} = ${o}. Is that right?`,computed:{dividend:e,divisor:s,claim:o,quotient:a,leadDigit:d,...columnsComputed$1(h)},steps:[{visual_data:b,animation:"set_up",caption:`${r} says ${e} ÷ ${s} = ${o}. Let's check.`,spoken:`${r} says ${numberToWordsLong(e)} divided by ${numberToWords(s)} is ${numberToWordsLong(o)}. To check, we work it out ourselves with the bus stop method.`},{visual_data:x,animation:"divide_digits",caption:f,spoken:`We work from the left. ${g}`},{visual_data:y,animation:"reveal_quotient",caption:i?`${r} is right: ${e} ÷ ${s} = ${a}.`:`${r} is wrong: ${e} ÷ ${s} = ${a}, not ${o}.`,spoken:i?`${r} is right. ${capitalised(numberToWordsLong(e))} divided by ${numberToWords(s)} really is ${numberToWordsLong(a)}.`:`Reading above the bar, our answer is ${numberToWordsLong(a)}, and that does not match ${numberToWordsLong(o)}. So ${r} is wrong.`}]})}}
71,CTX_PRIZE=/has (\d+) \w+ to give out as prizes\. Each \w+ receives (\d+) \w+\. How many \w+ get \w+\?/,contextPrize={id:"MD.P4.M03.busstop-prize.v1",microSkillId:"MD.P4.M03",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,CTX_PRIZE);return t?buildBusStop(n,t[0],t[1],this.id):null}},CTX_VERIFY_MUL=/divides a bag of stickers equally among (\d+) friends\. Each friend receives (\d+) stickers\. Use multiplication to check how many stickers were in the bag altogether$/,contextVerifyMultiply={id:"MD.P4.M03.verify-multiply.v1",microSkillId:"MD.P4.M03",forms:["context_reasoning"],generate(n){const t=matchInts(n.stem,CTX_VERIFY_MUL);if(!t)return null;const e=t[0],s=t[1];if(e<2||e>9||s<10||s>99)return null;const o=Math.floor(s/10)*10,r=s%10;if(r===0)return null;const a=e*s;if(a>999||a!==answerInt(n))return null;const i=o*e,l=r*e;return buildExample({generatorId:this.id,question:n,title:`Check ${e} × ${s} to find the total.`,computed:{friends:e,each:s,tens:o,ones:r,tensProduct:i,onesProduct:l,dividend:a,answer:a},steps:[{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=","__"]},animation:"show_equation",caption:`To check a division, multiply back: ${e} × ${s}.`,spoken:`Each of ${numberToWords(e)} friends got ${numberToWords(s)} stickers. Multiplying and dividing are opposites, so we multiply to check the total.`},{visual_data:{type:"equation_display",terms:[`${o} × ${e}`,"+",`${r} × ${e}`,"=","__"]},animation:"multiply_parts",caption:`${o} × ${e} = ${i}, ${r} × ${e} = ${l}.`,spoken:`${capitalised(numberToWords(o))} times ${numberToWords(e)} is ${numberToWords(i)}. ${capitalised(numberToWords(r))} times ${numberToWords(e)} is ${numberToWords(l)}.`},{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${a}`]},animation:"recombine",caption:`${e} × ${s} = ${a} stickers in total.`,spoken:`${capitalised(numberToWords(i))} plus ${numberToWords(l)} is ${numberToWords(a)}, so there were ${numberToWords(a)} stickers in the bag.`}]})}},COMPARE$1=/^Which is greater: (\d+) ÷ (\d+) or (\d+) ÷ (\d+)\?$/,compareQuotients={id:"MD.P4.M03.compare-quotients.v1",microSkillId:"MD.P4.M03",forms:["comparison_statement"],generate(n){const t=matchInts(n.stem,COMPARE$1);if(!t)return null;const[e,s,o,r]=t;if(s<2||s>9||r<2||r>9||e%s!==0||o%r!==0)return null;const a=e/s,i=o/r,l=a===i?"They are equal":a>i?`${e} ÷ ${s}`:`${o} ÷ ${r}`;if(n.correctAnswer.trim()!==l)return null;const u=a===i?"=":a>i?">":"<",d=divisionColumns$1(e,s),m=divisionColumns$1(o,r),h=narrateColumns$1(d,s),f=narrateColumns$1(m,r),g=a===i?"highlight_ones":Math.floor(a/100)%10!==Math.floor(i/100)%10?"highlight_hundreds":Math.floor(a/10)%10!==Math.floor(i/10)%10?"highlight_tens":"highlight_ones";return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${e} ÷ ${s} or ${o} ÷ ${r}?`,computed:{a:e,b:s,c:o,d:r,left:a,right:i,...columnsComputed$1(d,"left"),...columnsComputed$1(m,"right")},steps:[{visual_data:{type:"short_division",dividend:e,divisor:s,full_reveal:!0,show_carries_on_reveal:!0,reveal_columns:d.length},animation:"show_start",caption:`First: ${h.caption}`,spoken:`We work out ${numberToWordsLong(e)} divided by ${numberToWords(s)} first. ${h.spoken} That gives ${numberToWordsLong(a)}.`},{visual_data:{type:"short_division",dividend:o,divisor:r,full_reveal:!0,show_carries_on_reveal:!0,reveal_columns:m.length},animation:"show_second",caption:`Second: ${f.caption}`,spoken:`Now ${numberToWordsLong(o)}
71 divided by ${numberToWords(r)}. ${f.spoken} That gives ${numberToWordsLong(i)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:u},animation:`write_answer ${g}`,caption:a===i?`${e} ÷ ${s} and ${o} ÷ ${r} are equal.`:`${Math.max(a,i)} > ${Math.min(a,i)}: ${l} is greater.`,spoken:a===i?`Both are ${numberToWords(a)}, so they are equal.`:`${capitalised(numberToWords(Math.max(a,i)))} is more than ${numberToWords(Math.min(a,i))}, so the ${a>i?"first":"second"} one is greater.`}]})}},MD_P4_M03_GENERATORS=[symbolic$3,context$3,claimDivide,contextPrize,contextVerifyMultiply,compareQuotients],litreWord=n=>`litre${n===1?"":"s"}`;function buildLitresToMl(n,t,e,s){const o=e*1e3+s,r=`${e} ${litreWord(e)}`,a=s>0,i={type:"equation_display",terms:a?[r,`${s} ml`,"=","__","ml"]:[r,"=","__","ml"]},l=e*1e3,u={type:"equation_display",terms:[r,"=",String(l),"ml"]},d={type:"equation_display",terms:a?[`${l} ml`,"+",`${s} ml`,"=",`${o} ml`]:[String(e),"×","1000 ml","=",`${o} ml`]};return buildExample({generatorId:n,question:t,title:a?`How do we change ${r} ${s} ml into millilitres?`:`How do we change ${r} into millilitres?`,computed:{one:1,thousand:1e3,litres:e,ml:s,litresMl:l,total:o,answer:o},steps:[{visual_data:i,animation:"show_start",caption:a?`${r} ${s} ml = ? ml`:`${r} = ? ml`,spoken:a?`${capitalised(numberToWordsLong(e))} ${litreWord(e)} and ${numberToWordsLong(s)} millilitres. We want to know how many millilitres that is altogether.`:`${capitalised(numberToWordsLong(e))} ${litreWord(e)}. We want to know how many millilitres that is.`},a?{visual_data:u,animation:"write_multiplication",caption:`${r} = ${l} ml.`,spoken:e===1?"One litre is one thousand millilitres.":`${capitalised(numberToWordsLong(e))} lots of one thousand millilitres is ${numberToWordsLong(l)} millilitres.`}:{visual_data:d,animation:"write_multiplication",caption:`${e} × 1000 ml = ${o} ml.`,spoken:e===1?"One litre is one thousand millilitres.":`${capitalised(numberToWordsLong(e))} times one thousand millilitres is ${numberToWordsLong(o)} millilitres.`},a?{visual_data:d,animation:"add",caption:`${l} + ${s} = ${o} ml.`,spoken:`Add on the extra ${numberToWordsLong(s)} millilitres: ${numberToWordsLong(l)} plus ${numberToWordsLong(s)} is ${numberToWordsLong(o)} millilitres.`}:{visual_data:u,animation:"write_answer",caption:`${r} = ${l} ml.`,spoken:`So ${capitalised(numberToWordsLong(e))} ${litreWord(e)} is ${numberToWordsLong(o)} millilitres.`}]})}function buildMlToLitres(n,t,e){const s=e/1e3,o={type:"equation_display",terms:[`${e} ml`,"=","__","litres"]},r={type:"equation_display",terms:[String(e),"ml","÷","1000","=",String(s)]},a={type:"equation_display",terms:[`${e} ml`,"=",s,"litres"]};return buildExample({generatorId:n,question:t,title:`How do we change ${e} ml into litres?`,computed:{one:1,thousand:1e3,ml:e,litres:s,answer:s},steps:[{visual_data:o,animation:"show_start",caption:`${e} ml = ? litres`,spoken:`${capitalised(numberToWordsLong(e))} millilitres. We want to know how many litres that is.`},{visual_data:r,animation:"write_division",caption:`${e} ml ÷ 1000 = ${s}.`,spoken:`${capitalised(numberToWordsLong(e))} millilitres divided by one thousand is ${numberToWordsLong(s)}, because one thousand millilitres makes one litre.`},{visual_data:a,animation:"land",caption:`${e} ml = ${s} litres.`,spoken:`So that is ${numberToWordsLong(s)} litres.`}]})}function acceptedNumber$1(n){const t=Number(n.correctAnswer);if(Number.isFinite(t))return t;const e=n.correctAnswer.trim().match(/^(\d+)\s*ml$/i);return e?Number(e[1]):null}const LITRES_MIN=1,LITRES_MAX=9,ML_MIN=0,ML_MAX=999,compoundToMl=/^.+ holds (\d+) litres? (\d+) ?ml(?: of [a-z]+)?\. how many millilitres(?: of [a-z]+)? is that(?: altogether)?\??$/i,compoundSameAmount=/^.+ holds (\d+) litres? (\d+) ?ml\. which is the same amount in millilitres\??$/i,compoundEquationFill=/^fill in the missing number: (\d+) litres? (\d+) ?ml = _+ ?ml\??$/i,toppedUp=/^a (\d+) litre bottle of [a-z]+ is topped up with another (\d+) ?ml\. how many millilitres of [a-z]+ are in it now\??$/i,compoundGenerator={id:"MEA.P7.M02.compound-to-ml.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation","symbolic_choice"],generate(n){const t=normaliseStem(n.stem),e=t.match(compoundToMl)??t.match(compoundSameAmount)??t.match(compoundEquationFill)??t.match(toppedUp);if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(s<LITRES_MIN||s>LITRES_MAX||o<ML_MIN||o>ML_MAX)return null;const r=s*1e3+o,a=acceptedNumber$1(n);return a===null||a!==r?null:buildLitresToMl(this.id,n,s,o)}},plainLitresToMl=/^.+ holds (\d+) litres?\. how many millilitres(?: of [a-z]+)? is that\??$/i,plainGenerator={id:"MEA.P7.M02.plain-to-ml.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(plainLitresToMl);if(!e)return null;const s=Number(e[1]);if(s<LITRES_MIN||s>LITRES_MAX)return null;const o=s*1e3,r=acceptedNumber$1(n);return r===null||r!==o?null:buildLitresToMl(this.id,n,s,0)}},mlToLitresExact=/^.+ (?:is filled with|holds) (\d+) ?ml(?: of [a-z]+)?\. how many litres is that\??$/i,reverseGenerator={id:"MEA.P7.M02.ml-to-litres.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(mlToLitresExact);if(!e)return null;const s=Number(e[1]);if(s%1e3!==0)return null;const o=s/1e3;if(o<LITRES_MIN||o>LITRES_MAX)return null;const r=acceptedNumber$1(n);return r===null||r!==o?null:buildMlToLitres(this.id,n,s)}},LITRES_ML_LABEL=/^(\d+) litres? (\d+) ?ml$/i;function buildMlToLitresMl(n,t,e,s,o){const r=`${s} ${litreWord(s)} ${o} ml`,a=s*1e3,i={type:"equation_display",terms:[`${e} ml`,"=","__"]},l={type:"equation_display",terms:[String(e),"ml","=",String(a),"ml","+",String(o),"ml"]},u={type:"equation_display",terms:[`${e} ml`,"=",r]};return buildExample({generatorId:n,question:t,title:`How do we write ${e} ml in litres and millilitres?`,computed:{one:1,thousand:1e3,ml:e,litres:s,litresMl:a,rem:o,answer:e},steps:[{visual_data:i,animation:"show_start",caption:`${e} ml = ? litres and ml`,spoken:`${capitalised(numberToWordsLong(e))} millilitres. We want to write that as whole litres and the millilitres left over.`},{visual_data:l,animation:"write_split",caption:`${e} ml = ${a} ml + ${o} ml.`,spoken:`${capitalised(numberToWordsLong(e))} millilitres splits into ${numberToWordsLong(s)} whole thousand${s===1?"":"s"}, with ${numberToWordsLong(o)} millilitres left over.`},{visual_data:u,animation:"land",caption:`${e} ml = ${r}.`,spoken:`${capitalised(numberToWordsLong(a))} millilitres is ${numberToWordsLong(s)} whole ${litreWord(s)}, so that is ${numberToWordsLong(s)} ${litreWord(s)} and ${numberToWordsLong(o)} millilitres.`}]})}const reversePartitionGenerator={id:"MEA.P7.M02.ml-to-litres-ml.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation","symbolic_choice"],generate(n){const t=n.correctAnswer.trim().match(LITRES_ML_LABEL);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=normaliseStem(n.stem).match(/(\d+) ?ml\b/i);if(!o)return null;const r=Number(o[1]);return Math.floor(r/1e3)!==e||r%1e3!==s||e<LITRES_MIN||e>LITRES_MAX||s<1||s>ML_MAX?null:buildMlToLitresMl(this.id,n,r,e,s)}},CLAIM_CONVERT=/^(\w+) says that (\d+) litres? (\d+) ?ml is the same as (\d+) ?ml\. Is (?:he|she|they) right\?$/i,claimConvert={id:"MEA.P7.M02.claim-convert.v1",microSkillId:"MEA.P7.M02",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_CONVERT);
71if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o),l=Number(r);if(a<LITRES_MIN||a>LITRES_MAX||i<ML_MIN||i>ML_MAX)return null;const u=a*1e3,d=u+i,m=d===l,h=n.correctAnswer.trim().toLowerCase();return(m?!h.startsWith("yes"):!h.startsWith("no"))?null:buildExample({generatorId:this.id,question:n,title:`${e} says ${a} litres ${i} ml = ${l} ml. Right?`,computed:{one:1,thousand:1e3,litres:a,ml:i,claim:l,total:d,litresMl:u},steps:[{visual_data:{type:"equation_display",terms:[`${a} litres ${i} ml`,"?",`${l} ml`]},animation:"show_start",caption:`${e} says ${a} litres ${i} ml = ${l} ml. True?`,spoken:`${e} says ${numberToWordsLong(a)} ${a===1?"litre":"litres"} and ${numberToWordsLong(i)} millilitres is ${numberToWordsLong(l)} millilitres. To check, we convert it ourselves, starting with the litres.`},{visual_data:{type:"equation_display",terms:["1 litre","=",1e3,"ml"]},animation:"key_fact",caption:`1 litre = 1000 ml, so ${a} litres = ${u} ml.`,spoken:`One litre is one thousand millilitres, so ${numberToWordsLong(a)} ${a===1?"litre":"litres"} is ${numberToWordsLong(a*1e3)} millilitres.`},{visual_data:{type:"equation_display",terms:[`${u} ml`,"+",`${i} ml`,"=",`${d} ml`]},animation:"write_answer",caption:m?`${u} + ${i} = ${d} ml — ${e} is right.`:`${u} + ${i} = ${d} ml, not ${l} — ${e} is wrong.`,spoken:m?`Now add on the extra ${numberToWordsLong(i)} millilitres: that makes ${numberToWordsLong(d)}, which matches the claim. So ${e} is right.`:`Now add on the extra ${numberToWordsLong(i)} millilitres: that makes ${numberToWordsLong(d)}, which does not match ${numberToWordsLong(l)}. So ${e} is wrong.`}]})}},COMPARE_VOLUME=/^Which holds more \w+: a bottle with (\d+(?:\.\d+)?) litres?, or a bottle with ([\d,]+) ?ml\?$/i;function decisivePlaceIndex(n,t){const e=Math.max(String(n).length,String(t).length),s=String(n).padStart(e,"0"),o=String(t).padStart(e,"0");for(let r=0;r<e;r++)if(s[r]!==o[r]){const a=e-1-r;return a===0||a===1||a===2?a:null}return null}const HIGHLIGHT_ANIMATION={0:"write_answer_highlight_ones",1:"write_answer_highlight_tens",2:"write_answer_highlight_hundreds"},compareVolume={id:"MEA.P7.M02.compare-volume.v1",microSkillId:"MEA.P7.M02",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(COMPARE_VOLUME);if(!t)return null;const[,e,s]=t,o=Number(e),r=Number(s.replace(/,/g,""));if(o<=0||o>LITRES_MAX||r<=0||r>9999)return null;const a=o*1e3,i=a===r?"They hold the same amount":a>r?`The ${e} litre bottle`:`The ${s} ml bottle`;if(n.correctAnswer.trim()!==i)return null;const l=a===r?"=":a>r?">":"<",u=a===r?0:decisivePlaceIndex(a,r);return u===null&&a!==r?null:buildExample({generatorId:this.id,question:n,title:`Which holds more: ${e} litres or ${s} ml?`,computed:{one:1,thousand:1e3,litresVal:o,mlVal:r,mlFromLitres:a},steps:[{visual_data:{type:"equation_display",terms:[`${e} litres`,"?",`${s} ml`]},animation:"show_start",caption:`Compare ${e} litres with ${s} ml.`,spoken:"To compare them fairly, both amounts need to be in the same unit."},{visual_data:{type:"equation_display",terms:[`${e} litres`,"=",`${a} ml`]},animation:"key_fact",caption:`1 litre = 1000 ml, so ${e} litres = ${a} ml.`,spoken:`Let's change the litres into millilitres. One litre is one thousand millilitres, so ${decimalToWords(e)} litres is ${numberToWordsLong(a)} millilitres.`},{visual_data:{type:"comparison",left:a,right:r,symbol:l},animation:HIGHLIGHT_ANIMATION[u??0],caption:a===r?`${e} litres and ${s} ml are the same.`:`${i} holds more.`,spoken:a===r?"Both amounts come to the same number of millilitres, so they are equal.":`Now both are in millilitres. ${capitalised(numberToWordsLong(Math.max(a,r)))} millilitres is more than ${numberToWordsLong(Math.min(a,r))} millilitres, so that amount holds more.`}]})}},CTX_TOP_UP=/^A punch bowl needs (\d+) litres? (\d+) ?ml of \w+ in total\. A jug holds (\d+) litre (\d+) ?ml\. If the whole jug is poured in, how many more millilitres of \w+ are needed to fill the punch bowl\?$/i,contextTopUp={id:"MEA.P7.M02.context-top-up.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(CTX_TOP_UP);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(e<LITRES_MIN||e>LITRES_MAX||o<LITRES_MIN||o>LITRES_MAX)return null;const a=e*1e3+s,i=o*1e3+r;if(a<=i)return null;const l=a-i,u=acceptedNumber$1(n);return u===null||u!==l?null:buildExample({generatorId:this.id,question:n,title:`${e} L ${s} ml needed, ${o} L ${r} ml poured — how much more?`,computed:{one:1,thousand:1e3,needL:e,needMlPart:s,jugL:o,jugMlPart:r,needTotal:a,jugTotal:i,diff:l,answer:l},steps:[{visual_data:{type:"equation_display",terms:[`${e} litres ${s} ml`,"-",`${o} litre ${r} ml`,"=","__"]},animation:"show_start",caption:"Change both amounts to ml first, then subtract.",spoken:"We need both amounts in the same unit before we can subtract, so we convert litres and millilitres into millilitres."},{visual_data:{type:"equation_display",terms:[`${a} ml`,"-",`${i} ml`,"=","__"]},animation:"convert",caption:`${a} ml - ${i} ml = __.`,spoken:`The bowl needs ${numberToWordsLong(a)} millilitres, and the jug gives ${numberToWordsLong(i)} millilitres.`},{visual_data:{type:"equation_display",terms:[`${a}`,"-",`${i}`,"=",`${l}`]},animation:"subtract",caption:`${a} - ${i} = ${l} ml still needed.`,spoken:`${capitalised(numberToWordsLong(a))} take away ${numberToWordsLong(i)} is ${numberToWordsLong(l)} millilitres still needed.`}]})}},CTX_START=/^A jug held some \w+\. After (\d+) ?ml was poured out, (\d+) litre (\d+) ?ml was left\. How many millilitres of \w+ were in the jug at the start\?$/i,contextStart={id:"MEA.P7.M02.context-start.v1",microSkillId:"MEA.P7.M02",forms:["varied_symbolic_calculation"],generate(n){const t=normaliseStem(n.stem).match(CTX_START);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(s<LITRES_MIN||s>LITRES_MAX)return null;const r=s*1e3+o,a=r+e,i=acceptedNumber$1(n);return i===null||i!==a?null:buildExample({generatorId:this.id,question:n,title:`${s} L ${o} ml left, ${e} ml poured out — how much at the start?`,computed:{one:1,thousand:1e3,pouredOut:e,leftL:s,leftMlPart:o,leftTotal:r,start:a,answer:a},steps:[{visual_data:{type:"equation_display",terms:[`${s} litre ${o} ml`,"=","__ ml"]},animation:"show_start",caption:`Change ${s} litre ${o} ml into ml first.`,spoken:"Before we can add anything back, we change the amount left over into millilitres."},{visual_data:{type:"equation_display",terms:[`${s} litre ${o} ml`,"=",`${r} ml`]},animation:"key_fact",caption:`${s} litre ${o} ml = ${r} ml.`,spoken:`One litre is one thousand millilitres, so that comes to ${numberToWordsLong(r)} millilitres.`},{visual_data:{type:"equation_display",terms:[`${r}`,"+",`${e}`,"=",`${a}`]},animation:"add",caption:`${r} + ${e} = ${a} ml at the start.`,spoken:`We add back the ${numberToWordsLong(e)} millilitres that were poured out: ${numberToWordsLong(r)} plus ${numberToWordsLong(e)} is ${numberToWordsLong(a)}.`}]})}},CUPS_WORD_TO_NUM={one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9},CUPS_TO_LITRES=/^A jug is filled (?:using|with) (\w+) (\d+) ?ml cups(?: of [a-z]+)?\. How many litres does it hold\?$/i,cupsToLitres={id:"MEA.P7.M02.cups-to-litres.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(CUPS_TO_LITRES);if(!t)return null;const e=CUPS_WORD_TO_NUM[t[1].toLowerCase()],s=Number(t[2]);if(e===void 0||s<1||s>999)return null;const o=e*s;if(o%1e3!==0)return null;const r=o/1e3;if(r<LITRES_MIN||r>LITRES_MAX)return null;const a=acceptedNumber$1(n);return a===null||a!==r?null:buildExample({generatorId:this.id,question:n,title:`${e} cups of ${s} ml — how many litres altogether?`,computed:{cups:e,mlPerCup:s,totalMl:o,thousand:1e3,litres:r,answer:r},steps:[{visual_data:{type:"equation_display",terms:[String(e),"×",`${s} ml`,"=","__","ml"]},animation:"show_start",caption:`${e} cups × ${s} ml = ? ml`,spoken:`${capitalised(numberToWordsLong(e))} cups, each holding ${numberToWordsLong(s)} millilitres. First we find the total in millilitres.`},{visual_data:{type:"equation_display",terms:[String(e),"×",`${s} ml`,"=",`${o} ml`]},animation:"write_multiplication",caption:`${e} × ${s} = ${o} ml.`,spoken:`${capitalised(numberToWordsLong(e))} times ${numberToWordsLong(s)} is ${numberToWordsLong(o)} millilitres altogether.`},{visual_data:{type:"equation_display",terms:[`${o} ml`,"=",`${r} litres`]},animation:"write_answer",caption:`${o} ml = ${r} litres.`,spoken:`${capitalised(numberToWordsLong(o))} millilitres divided by one thousand is ${numberToWordsLong(r)}. So that is ${numberToWordsLong(r)} litres.`}]})}},SHARE_INTO_BOWLS=/^A large pot holds (\d+) ?ml of \w+, shared equally into (\d+) bowls\. How many millilitres of \w+ goes in each bowl\?$/i,shareIntoBowls={id:"MEA.P7.M02.share-into-bowls.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(SHARE_INTO_BOWLS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(s<2||s>12||e<1||e>99999||e%s!==0)return null;const o=e/s,r=acceptedNumber$1(n);if(r===null||r!==o)return null;const a=10**Math.floor(Math.log10(o)),i=Math.floor(o/a)*a,l=o-i,u=i*s,d=l*s,m=u/a,h=i/a,f=l>0;return buildExample({generatorId:this.id,question:n,title:`${e} ml shared into ${s} bowls — how much in each?`,computed:{totalMl:e,bowls:s,each:o,place:a,leadingShare:i,remainingShare:l,leadingMl:u,remainingMl:d,factTotal:m,factShare:h,answer:o},steps:f?[{visual_data:{type:"equation_display",terms:[e,"=",u,"+",d],fit:!0},animation:"write_split",caption:`Split ${e} ml into ${u} ml and ${d} ml.`,spoken:`Sharing equally means dividing by ${numberToWords(s)}. We split ${numberToWordsLong(e)} millilitres into ${numberToWordsLong(u)} and ${numberToWordsLong(d)} millilitres, so we can share each part separately.`},{visual_data:{type:"equation_display",terms:[`${u} ÷ ${s} = ${i}`,"and",`${d} ÷ ${s} = ${l}`]},animation:"write_division",caption:"Divide both parts by the number of bowls.",spoken:`${capitalised(numberToWords(s))} lots of ${numberToWordsLong(i)} make ${numberToWordsLong(u)}, so the first part gives ${numberToWordsLong(i)} millilitres per bowl. ${capitalised(numberToWords(s))} lots of ${numberToWordsLong(l)} make ${numberToWordsLong(d)}, so the second part gives another ${numberToWordsLong(l)} millilitres per bowl.`},{visual_data:{type:"equation_display",terms:[i,"+",l,"=",`${o} ml`],fit:!0},animation:"add",caption:`${i} + ${l} = ${o} ml in each bowl.`,spoken:`Each bowl gets a share of both parts, so we add them. ${capitalised(numberToWordsLong(i))} plus ${numberToWordsLong(l)} is ${numberToWordsLong(o)}. So each bowl gets ${numberToWordsLong(o)} millilitres.`}]:[{visual_data:{type:"equation_display",terms:[m,"÷",s,"=","__"]},animation:"show_start",caption:`Start with the division fact: ${m} ÷ ${s}.`,spoken:`Sharing equally means dividing by ${numberToWords(s)}. We have ${numberToWordsLong(m)} groups of ${numberToWordsLong(a)} millilitres, so first we share those groups between the bowls.`},{visual_data:{type:"equation_display",terms:[m,"÷",s,"=",h]},animation:"write_division",caption:`${m} ÷ ${s} = ${h}.`,spoken:`${capitalised(numberToWords(s))} times ${numberToWordsLong(h)} is ${numberToWordsLong(m)}, so ${numberToWordsLong(m)} divided by ${numberToWords(s)} is ${numberToWordsLong(h)}.`},{visual_data:{type:"equation_display",terms:[h,"×",`${a} ml`,"=",`${o} ml`],fit:!0},animation:"land",caption:`${h} × ${a} = ${o} ml in each bowl.`,spoken:`Each group is ${numberToWordsLong(a)} millilitres, so ${numberToWordsLong(h)} times ${numberToWordsLong(a)} is ${numberToWordsLong(o)}. So each bowl gets ${numberToWordsLong(o)} millilitres.`}]})}},USED_FROM_BARREL=/^A barrel held (\d+) litres? of \w+\. After watering the plants, (\d+) litres? (\d+) ?ml is left\. How many millilitres were used\?$/i,usedFromBarrel={id:"MEA.P7.M02.used-from-barrel.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(USED_FROM_BARREL);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(e<LITRES_MIN||e>LITRES_MAX||s<0||s>e||o<0||o>999)return null;const r=e*1e3,a=s*1e3+o;if(a>=r)return null;const i=r-a,l=acceptedNumber$1(n);return l===null||l!==i?null:buildExample({generatorId:this.id,question:n,title:`${e} L to start, ${s} L ${o} ml left — how much was used?`,computed:{startL:e,startMl:r,leftL:s,leftMlPart:o,leftMl:a,used:i,answer:i},steps:[{visual_data:{type:"equation_display",terms:[`${e} litres`,"-",`${s} litre${s===1?"":"s"} ${o} ml`,"=","__"]},animation:"show_start",caption:"Change both amounts to ml first, then subtract.",spoken:"We need both amounts in the same unit before we can subtract, so we convert litres and millilitres into millilitres."},{visual_data:{type:"equation_display",terms:[`${r} ml`,"-",`${a} ml`,"=","__"]},animation:"convert",caption:`${r} ml - ${a} ml = __.`,spoken:`The barrel started with ${numberToWordsLong(r)} millilitres, and ${numberToWordsLong(a)} millilitres is left.`},{visual_data:{type:"equation_display",terms:[`${r}`,"-",`${a}`,"=",`${i}`]},animation:"subtract",caption:`${r} - ${a} = ${i} ml used.`,spoken:`${capitalised(numberToWordsLong(r))} take away ${numberToWordsLong(a)} is ${numberToWordsLong(i)} millilitres used.`}]})}},COUNT_WORDS={two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10};function parseAmount(n){const t=n.trim().match(/^(\d+) litres?(?: (\d+) ?ml)?$/i);if(t){const s=Number(t[1]),o=t[2]?Number(t[2]):0;return s<LITRES_MIN||s>LITRES_MAX||o>ML_MAX?null:{ml:s*1e3+o,litres:s,mlPart:o,label:o?`${s} ${litreWord(s)} ${o} ml`:`${s} ${litreWord(s)}`}}const e=n.trim().match(/^(\d+) ?ml$/i);if(e){const s=Number(e[1]);return s<1||s>9999?null:{ml:s,litres:0,mlPart:s,label:`${s} ml`}}return null}function convertStep(n,t){const e=n.filter(r=>r.litres>0);if(e.length===0)return null;const s=e[0],o=s.mlPart?`${capitalised(numberToWords(s.litres))} ${litreWord(s.litres)} is ${numberToWordsLong(s.litres*1e3)} millilitres, and ${numberToWordsLong(s.mlPart)} more makes ${numberToWordsLong(s.ml)}.`:s.litres===1?"One litre is one thousand millilitres.":`One litre is one thousand millilitres, so ${numberToWords(s.litres)} ${litreWord(s.litres)} is ${numberToWordsLong(s.ml)} millilitres.`;return{visual_data:{type:"equation_display",terms:[s.label,"=",`${s.ml} ml`]},animation:"key_fact",caption:s.litres===1&&!s.mlPart?"Change to ml first: 1 litre = 1000 ml.":`1 litre = 1000 ml, so ${s.label} = ${s.ml} ml.`,spoken:`${t} ${o}`}}function lineStep(n){for(const t of[1e3,500,250,200,100,50])if(n.every(e=>e%t===0))return t;return null}const CTX_DRINK_LEFT=/^A (\w+) holds (\d+ litres?(?: \d+ ?ml)?)\. A \w+ (?:drinks|pours out|uses) (\d+ ?ml) (?:from|of) it\. How many millilitres(?: of \w+)? (?:are|is) left\?$/i,CTX_NEED_MORE=/^A \w+ needs (\d+ litres?(?: \d+ ?ml)?) of \w+, but only (\d+ ?ml) has been made\. How many more millilitres are needed\?$/i,CTX_TWO_TOTAL=/^Two \w+ hold (\d+ (?:litres?(?: \d+ ?ml)?|ml)) and (\d+ (?:litres?(?: \d+ ?ml)?|ml)) of \w+\. How many millilitres of \w+ is that in total\?$/i,CTX_SHARE=[/^A (\d+ litres?(?: \d+ ?ml)?) \w+ of \w+ is shared equally into (\d+ ?ml) glasses\. How many glasses can be filled\?$/i,/^A (\d+ litres?(?: \d+ ?ml)?) \w+ of \w+ is poured into glasses holding (\d+ ?ml) each\. How many glasses does it fill\?$/i,/^A \w+ holds (\d+ litres?(?: \d+ ?ml)?)\. A glass holds (\d+ ?ml)\. How many full glasses can be poured from the \
71w+\?$/i],CTX_LOTS_OF=/^A \w+ (?:uses|empties|pours) (two|three|four|five|six|seven|eight|nine|ten) (\d+ ?(?:ml|litre)) ([a-z]+(?: [a-z]+)?) (?:of \w+|onto [a-z ]+)\. How many millilitres(?: of \w+)? is that\?$/i,convertThenCalculate={id:"MEA.P7.M02.convert-then-calculate.v1",microSkillId:"MEA.P7.M02",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem),e=acceptedNumber$1(n);return e===null?null:takeAwayStory(this.id,n,t,e)??totalStory(this.id,n,t,e)??shareStory(this.id,n,t,e)??lotsOfStory(this.id,n,t,e)}};function takeAwayStory(n,t,e,s){let o=null,r=null,a;const i=e.match(CTX_DRINK_LEFT),l=e.match(CTX_NEED_MORE);if(i)o=parseAmount(i[2]),r=parseAmount(i[3]),a="left";else if(l)o=parseAmount(l[1]),r=parseAmount(l[2]),a="more";else return null;if(!o||!r||r.ml>=o.ml)return null;const u=o.ml-r.ml;if(u!==s)return null;const d=convertStep([o],"The question wants millilitres, so we change the litres into millilitres first.");if(!d)return null;const m=a==="left"?{cap:`${r.ml} ml is drunk. The rest is left.`,words:`${capitalised(numberToWordsLong(r.ml))} millilitres is drunk, so that part is gone. The part that is left is the missing part of the bar.`}:{cap:`${r.ml} ml is made. The rest is still needed.`,words:`${capitalised(numberToWordsLong(r.ml))} millilitres has been made. The part still needed is the missing part of the bar.`},h=a==="left"?`${o.ml} − ${r.ml} = ${u}. So ${u} ml is left.`:`${o.ml} − ${r.ml} = ${u}. So ${u} ml more is needed.`,f=a==="left"?`The whole take away the part that was drunk: ${numberToWordsLong(o.ml)} take away ${numberToWordsLong(r.ml)} is ${numberToWordsLong(u)}. So ${numberToWordsLong(u)} millilitres are left.`:`The whole take away the part already made: ${numberToWordsLong(o.ml)} take away ${numberToWordsLong(r.ml)} is ${numberToWordsLong(u)}. So ${numberToWordsLong(u)} more millilitres are needed.`;return buildExample({generatorId:n,question:t,title:a==="left"?`${o.label} take away ${r.label} — how much is left?`:`${o.label} needed, ${r.label} made — how much more?`,computed:{one:1,thousand:1e3,litres:o.litres,mlPart:o.mlPart,whole:o.ml,part:r.ml,answer:u},steps:[d,{visual_data:{type:"part_whole",whole:o.ml,parts:[r.ml,null]},animation:"show_parts",caption:m.cap,spoken:`The long bar is the whole: ${numberToWordsLong(o.ml)} millilitres. ${m.words}`},{visual_data:{type:"part_whole",whole:o.ml,parts:[r.ml,u]},animation:"fill_missing",caption:h,spoken:f}]})}function totalStory(n,t,e,s){const o=e.match(CTX_TWO_TOTAL);if(!o)return null;const r=parseAmount(o[1]),a=parseAmount(o[2]);if(!r||!a)return null;const i=r.ml+a.ml;if(i!==s)return null;const l=lineStep([r.ml,a.ml]);if(!l||i/l>12)return null;const u=convertStep([r,a],"We can only add the two amounts when they are in the same unit, so we change the litres into millilitres.");if(!u)return null;const d={type:"number_line",min:0,max:i+l,step:l,marked:[r.ml],tick_labels:{[i+l]:""}};return buildExample({generatorId:n,question:t,title:`${r.label} and ${a.label} — how many ml in total?`,computed:{one:1,thousand:1e3,a:r.ml,b:a.ml,answer:i},steps:[u,{visual_data:{...d,hops:[],unknown:null,pulse_steps:[2]},animation:"show_start",caption:`Both bottles together, so add: start at ${r.ml} ml.`,spoken:`We want both bottles together, so we add. We start the line at the first bottle, ${numberToWordsLong(r.ml)} millilitres, and add the second bottle on top.`},{visual_data:{...d,hops:[{from:r.ml,to:i,label:`+${a.ml}`}],unknown:i},animation:"hop",caption:`${r.ml} + ${a.ml} = ${i}. So ${i} ml in total.`,spoken:`We jump on ${numberToWordsLong(a.ml)} and land on ${numberToWordsLong(i)}. So the two bottles hold ${numberToWordsLong(i)} millilitres.`}]})}function equalJumps(n,t){return Array.from({length:t},(e,s)=>({from:s*n,to:(s+1)*n,label:String(s+1)}))}function shareStory(n,t,e,s){const o=CTX_SHARE.map(d=>e.match(d)).find(Boolean);if(!o)return null;const r=parseAmount(o[1]),a=parseAmount(o[2]);if(!r||!a||a.litres||r.ml%a.ml!==0)return null;const i=r.ml/a.ml;if(i!==s||i<2||i>10)return null;const l=convertStep([r],"The glass is measured in millilitres, so we change the bottle into millilitres too.");if(!l)return null;const u={type:"number_line",min:0,max:r.ml,step:a.ml};return buildExample({generatorId:n,question:t,title:`How many ${a.ml} ml glasses from ${r.label}?`,computed:{one:1,thousand:1e3,litres:r.litres,mlPart:r.mlPart,bottle:r.ml,glass:a.ml,answer:i},steps:[l,{visual_data:{...u,hops:equalJumps(a.ml,1)},animation:"first_jump",caption:`One glass is ${a.ml} ml: one jump of ${a.ml}.`,spoken:`Each glass takes ${numberToWordsLong(a.ml)} millilitres. On the line, one jump of ${numberToWordsLong(a.ml)} is one glass.`},{visual_data:{...u,hops:equalJumps(a.ml,i),conclude_label:`${r.ml} ÷ ${a.ml} = ${i}`},animation:"count_jumps",caption:`${i} jumps reach ${r.ml}. So ${i} glasses.`,spoken:`We keep jumping until the bottle is empty at ${numberToWordsLong(r.ml)}. Count the jumps: there are ${numberToWords(i)}. So ${numberToWords(i)} glasses.`}]})}
71function lotsOfStory(n,t,e,s){const o=e.match(CTX_LOTS_OF);if(!o)return null;const r=COUNT_WORDS[o[1].toLowerCase()],a=parseAmount(o[2]),i=o[3];if(!r||!a)return null;const l=r*a.ml;if(l!==s)return null;const u=i.replace(/s$/,"").replace(/ cans$/," can"),d=convertStep([a],"The question wants millilitres, so we change each one into millilitres first.")??{visual_data:{type:"number_line",min:0,max:l,step:a.ml,hops:[]},animation:"show_line",caption:`Each ${u} is ${a.ml} ml. We jump ${a.ml} for each ${u}.`,spoken:`Each ${u} holds ${numberToWordsLong(a.ml)} millilitres. That is already in millilitres, so we can count up in jumps of ${numberToWordsLong(a.ml)}, one jump for each ${u}.`},m={type:"number_line",min:0,max:l,step:a.ml};return buildExample({generatorId:n,question:t,title:`${capitalised(o[1].toLowerCase())} ${i} of ${a.label} — how many ml?`,computed:{one:1,thousand:1e3,count:r,litres:a.litres,each:a.ml,answer:l},steps:[d,{visual_data:{...m,hops:equalJumps(a.ml,1)},animation:"first_jump",caption:`One ${u} is one jump of ${a.ml}.`,spoken:`Here is the first jump, ${numberToWordsLong(a.ml)} millilitres long. That is the amount in one ${u}, so every jump on this line stands for one ${u}.`},{visual_data:{...m,hops:equalJumps(a.ml,r),conclude_label:`${r} × ${a.ml} = ${l}`},animation:"count_jumps",caption:`${r} jumps of ${a.ml} = ${l}. So ${l} ml.`,spoken:`${capitalised(numberToWords(r))} jumps of ${numberToWordsLong(a.ml)} land on ${numberToWordsLong(l)}. So ${numberToWords(r)} ${i} hold ${numberToWordsLong(l)} millilitres altogether.`}]})}const MEA_P7_M02_GENERATORS=[compoundGenerator,plainGenerator,reverseGenerator,reversePartitionGenerator,claimConvert,compareVolume,contextTopUp,contextStart,cupsToLitres,shareIntoBowls,usedFromBarrel,convertThenCalculate],MAX_WIDTH=25,MAX_HEIGHT=12,STEM=/^.+ (?:is|measures) (\d+) ?cm (?:long|tall) and (\d+) ?cm wide(?: on a plan)?\. what is (?:its area|the area of [a-z ]+)\??$/i;function acceptedNumber(n){const t=Number(n.correctAnswer);if(Number.isFinite(t))return t;const e=n.correctAnswer.trim().match(/^(\d+)\s*cm²$/i);return e?Number(e[1]):null}const areaOfRectangle={id:"MEA.P7.M03.area-grid.v1",microSkillId:"MEA.P7.M03",forms:["context_calculation","symbolic_choice"],generate(n){const e=normaliseStem(n.stem).match(STEM);if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(s<1||s>MAX_WIDTH||o<1||o>MAX_HEIGHT||s>=10&&o>=10)return null;const r=s*o,a=acceptedNumber(n);if(a===null||a!==r)return null;const i={type:"area_grid",width:s,height:o,unit_label:"cm"};return buildExample({generatorId:this.id,question:n,title:"How do we find the area of this rectangle?",computed:{width:s,height:o,area:r,answer:r},steps:[{visual_data:i,animation:"show_start",caption:`${s} across, ${o} down. Count the squares.`,spoken:`This rectangle is ${numberToWordsLong(s)} centimetres along and ${numberToWordsLong(o)} centimetres down. Area is how many unit squares fit inside.`},{visual_data:i,animation:"structure",caption:`${s} in a row, ${o} rows.`,spoken:`There are ${numberToWordsLong(s)} squares in each row, and there are ${numberToWordsLong(o)} rows. So we have ${numberToWordsLong(o)} rows of ${numberToWordsLong(s)}.`},{visual_data:i,animation:"reveal",caption:`${s} × ${o} = ${r} squares.`,spoken:`${capitalised(numberToWordsLong(s))} times ${numberToWordsLong(o)} is ${numberToWordsLong(r)} squares. So the area is ${numberToWordsLong(r)} square centimetres.`}]})}},bigAreaPartition={id:"MEA.P7.M03.partition.v1",microSkillId:"MEA.P7.M03",forms:["context_calculation","symbolic_choice"],generate(n){const e=normaliseStem(n.stem).match(STEM);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=s>MAX_WIDTH||o>MAX_HEIGHT,a=s>=10&&o>=10;if(!r&&!a||s<1||s>99||o<1||o>99)return null;const i=s*o,l=acceptedNumber(n);if(l===null||l!==i)return null;const u=o>=10,d=u?o:s,m=u?s:o;if(d<10||d>99)return null;const h=Math.floor(d/10)*10,f=d%10;if(f===0||m<1)return null;const g=m*h,b=m*f,x={type:"equation_display",terms:[`${s}`,"×",`${o}`,"=","__"]},y={type:"equation_display",terms:[`${g}`,"+",`${b}`,"=","__"]},E={type:"equation_display",terms:[`${g}`,"+",`${b}`,"=",`${i}`]};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${s} × ${o}?`,computed:{width:s,height:o,split:d,keep:m,tens:h,ones:f,tensProduct:g,onesProduct:b,area:i,answer:i},steps:[{visual_data:x,animation:"setup",caption:`Area = ${s} × ${o}.`,spoken:`${capitalised(numberToWordsLong(s))} times ${numberToWordsLong(o)}
71 is too big to count square by square, so we split it into two easier parts.`},{visual_data:y,animation:"partition",caption:`Split ${d}: ${g} and ${b}.`,spoken:`We split ${numberToWordsLong(d)} into ${numberToWordsLong(h)} and ${numberToWordsLong(f)}. ${capitalised(numberToWordsLong(m))} times ${numberToWordsLong(h)} is ${numberToWordsLong(g)}, and ${numberToWordsLong(m)} times ${numberToWordsLong(f)} is ${numberToWordsLong(b)}.`},{visual_data:E,animation:"recombine",caption:`${g} + ${b} = ${i}.`,spoken:`We add the parts back together. ${capitalised(numberToWordsLong(g))} and ${numberToWordsLong(b)} makes ${numberToWordsLong(i)}. So the area is ${numberToWordsLong(i)} square centimetres.`}]})}},CLAIM_AREA=/^A (\w+) is (\d+) m long and (\d+) m wide\. (\w+) says the area is (\d+) m² because (?:he|she|they) added all the sides together\. Is (?:he|she|they) right\?$/i,claimArea={id:"MEA.P7.M03.claim-area.v1",microSkillId:"MEA.P7.M03",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_AREA);if(!t)return null;const e=Number(t[2]),s=Number(t[3]),o=t[4],r=Number(t[5]);if(e<1||e>MAX_WIDTH||s<1||s>MAX_HEIGHT)return null;const a=e*s,i=a===r,l=n.correctAnswer.trim().toLowerCase();if(i?!l.startsWith("yes"):!l.startsWith("no"))return null;const u=2*(e+s);if(r!==u)return null;const d={type:"area_grid",width:e,height:s,unit_label:"m"};return buildExample({generatorId:this.id,question:n,title:`${o} says the area is ${r} m². Is that right?`,computed:{length:e,width:s,claim:r,area:a},steps:[{visual_data:{type:"equation_display",terms:[`${e}`,"+",`${s}`,"+",`${e}`,"+",`${s}`,"=",`${r}`]},animation:"show_start",caption:`${o}'s ${r} m² is the distance around, not the area.`,spoken:`${o} walked all the way around the outside and added the four sides — that gives the distance around, not the area.`},{visual_data:d,animation:"structure",caption:`${e} across, ${s} down: count the squares.`,spoken:`This shape is ${numberToWordsLong(e)} metres along and ${numberToWordsLong(s)} metres down. Area is how many unit squares fit inside.`},{visual_data:d,animation:"reveal",caption:i?`${o} is right: it's ${a} m².`:`${o} is wrong: it's ${a} m², not ${r} m².`,spoken:i?`${o} is right. ${capitalised(numberToWordsLong(e))} times ${numberToWordsLong(s)} really is ${numberToWordsLong(a)} square metres.`:`${o} is wrong. ${capitalised(numberToWordsLong(e))} times ${numberToWordsLong(s)} is ${numberToWordsLong(a)} square metres, not what was claimed.`}]})}},COMPARE_RUGS=/^Which rug covers more floor: Rug A measuring (\d+) m by (\d+) m, or Rug B measuring (\d+) m by (\d+) m\?$/i,compareRugs={id:"MEA.P7.M03.compare-rugs.v1",microSkillId:"MEA.P7.M03",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(COMPARE_RUGS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(e<1||e>MAX_WIDTH||s<1||s>MAX_HEIGHT||o<1||o>MAX_WIDTH||r<1||r>MAX_HEIGHT)return null;const a=e*s,i=o*r;if(a===i)return null;const l=a>i?"Rug A":"Rug B";if(n.correctAnswer.trim()!==l)return null;const u=a>i?">":"<";return buildExample({generatorId:this.id,question:n,title:`Which rug is bigger: ${e}×${s} or ${o}×${r}?`,computed:{widthA:e,heightA:s,widthB:o,heightB:r,areaA:a,areaB:i},steps:[{visual_data:{type:"area_grid",width:e,height:s,unit_label:"m"},animation:"show_start",caption:`Rug A: ${e} × ${s} = ${a} m².`,spoken:`Rug A is ${numberToWordsLong(e)} metres by ${numberToWordsLong(s)} metres, so its area is ${numberToWordsLong(a)} square metres.`},{visual_data:{type:"area_grid",width:o,height:r,unit_label:"m"},animation:"show_second",caption:`Rug B: ${o} × ${r} = ${i} m².`,spoken:`Rug B is ${numberToWordsLong(o)} metres by ${numberToWordsLong(r)} metres, so its area is ${numberToWordsLong(i)} square metres.`},{visual_data:{type:"comparison",left:a,right:i,symbol:u},animation:"write_answer_highlight_none",caption:`${l} covers more (${a} vs ${i} m²).`,spoken:`${capitalised(numberToWordsLong(Math.max(a,i)))} square metres is more than ${numberToWordsLong(Math.min(a,i))}, so ${l} covers more floor.`}]})}},CTX_INVERSE_SIDE=/^A rectangular (\w+) has an area of (\d+) m²\. It is (\d+) m long\. How wide is it, in metres\?$/i,contextInverseSide={id:"MEA.P7.M03.inverse-side.v1",microSkillId:"MEA.P7.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(CTX_INVERSE_SIDE);if(!t)return null;const e=Number(t[2]),s=Number(t[3]);if(s<1||s>MAX_WIDTH||e%s!==0)return null;const o=e/s;if(o<1||o>MAX_HEIGHT||o!==Number(n.correctAnswer))return null;const r={type:"area_grid",width:s,height:o,unit_label:"m"};return buildExample({generatorId:this.id,question:n,title:`Area ${e} m², length ${s} m — how wide?`,computed:{area:e,length:s,width:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[`${e} m²`,"=",`${s} m`,"×","__"]},animation:"show_start",caption:`Area = length × width: ${e} = ${s} × __.`,spoken:`Area is length times width. We know the area, ${numberToWordsLong(e)}, and the length, ${numberToWordsLong(s)}, so we can work backwards to find the width.`},{visual_data:{type:"equation_display",terms:[`${e}`,"÷",`${s}`,"=","__"]},animation:"inverse",caption:`So divide: ${e} ÷ ${s} = __.`,spoken:`Dividing undoes multiplying, so we find the width by dividing the area by the length: ${numberToWordsLong(e)} divided by ${numberToWordsLong(s)}.`},{visual_data:r,animation:"reveal",caption:`${e} ÷ ${s} = ${o}. The width is ${o} m.`,spoken:`${capitalised(numberToWordsLong(e))} divided by ${numberToWordsLong(s)} is ${numberToWordsLong(o)}, so the garden is ${numberToWordsLong(o)} metres wide.`}]})}},CTX_TILING=/^A (\w+) is (\d+) m long and (\d+) m wide\. It will be covered with square tiles that are each 1 m²\. How many tiles are needed to cover the \
71w+\?$/i,tilingArea={id:"MEA.P7.M03.tiling.v1",microSkillId:"MEA.P7.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(CTX_TILING);if(!t)return null;const e=Number(t[2]),s=Number(t[3]);if(e<1||e>MAX_WIDTH||s<1||s>MAX_HEIGHT)return null;const o=e*s;if(o!==Number(n.correctAnswer))return null;const r={type:"area_grid",width:e,height:s,unit_label:"m"};return buildExample({generatorId:this.id,question:n,title:`How many 1 m² tiles cover ${e} m by ${s} m?`,computed:{width:e,height:s,tiles:o,answer:o},steps:[{visual_data:r,animation:"show_start",caption:`${e} across, ${s} down. Each tile is 1 m².`,spoken:`This patio is ${numberToWordsLong(e)} metres along and ${numberToWordsLong(s)} metres down. Each tile covers one square metre, so the number of tiles equals the area.`},{visual_data:r,animation:"structure",caption:`${e} in a row, ${s} rows.`,spoken:`There are ${numberToWordsLong(e)} tiles in each row, and ${numberToWordsLong(s)} rows.`},{visual_data:{type:"equation_display",terms:[`${e} × ${s}`,"=",`${o}`]},animation:"reveal",caption:`${e} × ${s} = ${o} tiles needed.`,spoken:`${capitalised(numberToWordsLong(e))} times ${numberToWordsLong(s)} is ${numberToWordsLong(o)}, so that many tiles are needed.`}]})}},MEA_P7_M03_GENERATORS=[areaOfRectangle,bigAreaPartition,claimArea,compareRugs,contextInverseSide,tilingArea],DIGIT_VALUE$1=/value of the digit (\d) in (\d{2,4})\?$/,DIGIT_VALUE_INLINE$1=/value of the digit (\d) in this (?:reading|number|total|amount)\?$/,CLAIM_CHECK=/^In the number ([\d,]+), (\w+) says the digit (\d) is worth (\d+)\. (\w+) says it is worth (\d+)\. Who is correct\?$/,COMPARE_DIGIT=/^Which number has a (ones|tens|hundreds|thousands) digit worth more: ([\d,]+) or ([\d,]+)\?$/,PARTITION_BUILD=/^A four-digit number has (\d) in the thousands place, (\d) in the hundreds place, (\d) in the tens place, and (\d) in the ones place\. What is the value of the (ones|tens|hundreds|thousands) digit in this number\?$/,PLACE_POW={ones:0,tens:1,hundreds:2,thousands:3},PLACE_NAMES$4=["ones","tens","hundreds","thousands"];function acceptedValue(n){const t=n.trim();return/^\d+$/.test(t)?Number(t):null}function digitInfo$1(n,t){if(n<100||n>9999)return null;const e=String(n);if(e.split("").filter(a=>a===String(t)).length!==1)return null;const o=e.indexOf(String(t)),r=e.length-1-o;return r>3?null:{placeFromRight:r,placeName:PLACE_NAMES$4[r],value:t*10**r,idxFromLeft:o,numStr:e}}function expansionParts$1(n,t){const e=n.split("").map(Number),s=e.length,o=e.map((i,l)=>({value:i*10**(s-1-l),isTarget:l===t})).filter(i=>i.value>0||i.isTarget),r=[],a=[];return o.forEach((i,l)=>{l>0&&(r.push("+"),a.push("+")),r.push(i.value),a.push(i.isTarget?"__":i.value)}),{fullTerms:r,step2Terms:a}}function findTargetNumber$1(n,t){const e=n.match(/\d{1,3}(?:,\d{3})+|\d{3,4}/g);if(!e)return null;for(const s of e){const o=Number(s.replace(/,/g,""));if(o<100||o>9999)continue;if(String(o).split("").filter(a=>a===String(t)).length===1)return{raw:s,clean:o}}return null}const digitValue$3={id:"NPV.P5.M05.digit-value.v1",microSkillId:"NPV.P5.M05",forms:["digit_value_entry","place_value_model_choice","context_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem);let e,s;const o=t.match(DIGIT_VALUE$1);if(o)e=Number(o[1]),s=Number(o[2]);else{const g=t.match(DIGIT_VALUE_INLINE$1);if(!g||(e=Number(g[1]),e===0))return null;const b=findTargetNumber$1(t,e);if(!b)return null;s=b.clean}if(e===0)return null;const r=digitInfo$1(s,e);if(!r)return null;const{placeName:a,value:i,idxFromLeft:l,numStr:u}=r,d=acceptedValue(n.correctAnswer);if(d===null||d!==i)return null;const m=numberToWordsLong(e),h=u.split("").map(Number),f=(g={})=>({type:"place_value_chart",digits:h,...g});return buildExample({generatorId:this.id,question:n,title:`What is the ${e} worth in ${s}?`,computed:{num:s,digit:e,value:i,answer:i},steps:[{visual_data:f(),animation:"show_number",caption:"One number, its place-value columns.",spoken:"What a digit is 
71worth depends on the column it sits in — thousands, hundreds, tens or ones. Each column shows its digit as that many counters."},{visual_data:f({highlight:l,values:{[l]:"?"}}),animation:"pulse_unknown",caption:`The ${e} sits in the ${a} column.`,spoken:`Find the ${m} on the chart. It sits in the ${a} column, so it tells us how many ${a} there are. So what is that ${m} really worth?`},{visual_data:f({highlight:l,values:{[l]:String(i)}}),animation:"write_answer",caption:`The ${e} is worth ${i}.`,spoken:`The ${m} sits in the ${a} place, so it is worth ${numberToWordsLong(i)}. Position tells worth.`}]})}},claimCheck={id:"NPV.P5.M05.claim-check.v1",microSkillId:"NPV.P5.M05",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CLAIM_CHECK);if(!t)return null;const[,e,s,o,r,a,i]=t,l=Number(e.replace(/,/g,"")),u=Number(o);if(u===0)return null;const d=digitInfo$1(l,u);if(!d)return null;const{placeName:m,value:h,idxFromLeft:f,numStr:g}=d,b=Number(r),x=Number(i);let y;if(b===h)y=s;else if(x===h)y=a;else return null;if(n.correctAnswer.trim()!==y)return null;const E=numberToWordsLong(u),{fullTerms:k,step2Terms:T}=expansionParts$1(g,f),A=b===h?x:b;return buildExample({generatorId:this.id,question:n,title:`Is the digit ${u} in ${e} worth ${b} or ${x}?`,computed:{num:l,digit:u,value:h,claim1:b,claim2:x},steps:[{visual_data:{type:"equation_display",terms:[l]},animation:"show_number",caption:`${e}: two claims about the digit ${u}.`,spoken:`Two people disagree about what the ${E} in this number is worth. Let's split it by place to check.`},{visual_data:{type:"equation_display",terms:T},animation:"pulse_unknown",caption:`Split by place: the ${u} sits in ${m}.`,spoken:`We split the number by place. The ${E} sits in the ${m} column, so it is worth that many ${m}.`},{visual_data:{type:"equation_display",terms:k},animation:"write_answer",caption:`${y} is right: the ${u} is worth ${h}, not ${A}.`,spoken:`${y} is right. The ${E} sits in the ${m} place, so it is worth ${numberToWordsLong(h)}. Position tells worth.`}]})}},compareDigit={id:"NPV.P5.M05.compare-digit.v1",microSkillId:"NPV.P5.M05",forms:["comparison"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(COMPARE_DIGIT);if(!t)return null;const[,e,s,o]=t,r=PLACE_POW[e],a=Number(s.replace(/,/g,"")),i=Number(o.replace(/,/g,""));if(a<100||a>9999||i<100||i>9999)return null;const l=Math.floor(a/10**r)%10,u=Math.floor(i/10**r)%10,d=l*10**r,m=u*10**r;if(d===m)return null;const h=d>m?s:o,f=Math.max(d,m);return n.correctAnswer.trim()!==h?null:buildExample({generatorId:this.id,question:n,title:`Which has the bigger ${e} digit: ${s} or ${o}?`,computed:{numA:a,numB:i,valueA:d,valueB:m,digitA:l,digitB:u},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`Compare the ${e} digit in ${s} and ${o}.`,spoken:`The question asks about one digit, so we compare the ${e} column in each number, not the whole number's size.`},{visual_data:{type:"comparison",left:d,right:m,symbol:null},animation:"pulse_unknown",caption:`${e} worth: ${d} vs ${m}.`,spoken:`In the first number, the ${e} digit is worth ${numberToWordsLong(d)}
71. In the second, it is worth ${numberToWordsLong(m)}.`},{visual_data:{type:"comparison",left:d,right:m,symbol:d>m?">":"<"},animation:"write_answer",caption:`${h} has the bigger ${e} digit (${f}).`,spoken:`${capitalised(numberToWordsLong(f))} is more than ${numberToWordsLong(Math.min(d,m))}, so that number has the bigger ${e} digit.`}]})}},partitionBuild={id:"NPV.P5.M05.partition-build.v1",microSkillId:"NPV.P5.M05",forms:["partition_equation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(PARTITION_BUILD);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]),a=t[5],i=e*1e3+s*100+o*10+r;if(i<1e3||i>9999)return null;const l=PLACE_POW[a],d=[r,o,s,e][l],m=d*10**l,h=acceptedValue(n.correctAnswer);if(h===null||h!==m)return null;const f=numberToWordsLong(d),g=[e*1e3,s*100,o*10,r],b=3-l,x=k=>{const T=[];return g.forEach((A,N)=>{N>0&&T.push("+"),T.push(k(A,N))}),T},y=x(k=>k),E=x((k,T)=>T===b?"__":k);return buildExample({generatorId:this.id,question:n,title:`Build the number from its clues, then find the ${a} digit's value.`,computed:{th:e,h:s,t:o,o:r,num:i,digit:d,value:m,answer:m},steps:[{visual_data:{type:"equation_display",terms:y},animation:"show_expansion",caption:`Build the number from the clues: ${i}.`,spoken:"Each clue gives one column. Put the thousands, hundreds, tens and ones together to build the whole number."},{visual_data:{type:"equation_display",terms:E},animation:"pulse_unknown",caption:`Now find the ${a} digit.`,spoken:`Once the number is built, look at the ${a} column on its own. That digit is ${f}.`},{visual_data:{type:"equation_display",terms:y},animation:"write_answer",caption:`The ${a} digit is worth ${m}.`,spoken:`The ${a} digit is ${f}, so it is worth ${numberToWordsLong(m)}.`}]})}},NPV_P5_M05_GENERATORS=[digitValue$3,claimCheck,compareDigit,partitionBuild],ADJUST_EQ=/^(1000|100) (more|less) than (\d{3,4}) = __$/,ADJUST_WHICH=/^Which number is (1000|100) (more|less) than (\d{3,4})\?$/,CLAIM_ADJUST=/^(\w+) says (100|1000) (more|less) than ([\d,]+) is ([\d,]+)\. Is (?:he|she|they) right\?$/,COMPARE_ADJUST_WRAP=/^Which is bigger: (.+), or (.+)\?$/,COMPARE_ADJUST_PART=/^(100|1000) (more|less) than ([\d,]+)$/,TWO_STEP_ADJUST=/^A number is (100|1000) (more|less) than ([\d,]+)\. What is (100|1000) (more|less) than that new number\?$/;function extractInts(n){return[...n.matchAll(/£?\d{1,3}(?:,\d{3})*(?:\.\d+)?/g)].map(t=>Number(t[0].replace(/[£,]/g,"")))}function expansion$1(n){const t=Math.floor(n/1e3),e=Math.floor(n/100)%10,s=Math.floor(n/10)%10,o=n%10;return[t*1e3,e*100,s*10,o]}function placeParts(n,t){return expansion$1(n).map((e,s)=>({value:e,isTarget:3-s===t})).filter(e=>e.value>0||e.isTarget)}function partTerms(n,t){const e=[];return n.forEach((s,o)=>{o>0&&e.push("+"),e.push(t&&s.isTarget?"__":s.value)}),e}function digitDiffCount(n,t){const e=String(n).padStart(4,"0"),s=String(t).padStart(4,"0");let o=0;for(let r=0;r<4;r++)e[r]!==s[r]&&o++;return o}function digitAt(n,t){return Number(String(n).padStart(4,"0")[t])}function decisivePlace(n,t){const e=Math.max(String(n).length,String(t).length),s=String(n).padStart(e,"0"),o=String(t).padStart(e,"0");for(let r=0;r<e;r++){if(s[r]===o[r])continue;const a=e-1-r,i=Number(s[r]),l=Number(o[r]);return a===2?{name:"hundreds",anim:"highlight_hundreds",digitA:i,digitB:l}:a===1?{name:"tens",anim:"highlight_tens",digitA:i,digitB:l}:a===0?{name:"ones",anim:"highlight_ones",digitA:i,digitB:l}:null}return null}function planFromSymbolic(n){const t=normaliseStem(n.stem),e=t.match(ADJUST_EQ)??t.match(ADJUST_WHICH);if(!e)return null;const s=Number(e[1]),o=e[2],r=Number(e[3]);if(r<1e3||r>9999)return null;const a=o==="more"?r+s:r-s,i=/^\d+$/.test(n.correctAnswer.trim())?Number(n.correctAnswer.trim()):null;return i===null||i!==a||a<1e3||a>9999||digitDiffCount(r,a)!==1?null:{adj:s,dir:o,N:r,result:a}}function planCarrySymbolic(n){const t=normaliseStem(n.stem),e=t.match(ADJUST_EQ)??t.match(ADJUST_WHICH);if(!e)return null;const s=Number(e[1]),o=e[2],r=Number(e[3]);if(r<1e3||r>9999)return null;const a=o==="more"?r+s:r-s;if(a<1e3||a>10999)return null;const i=/^\d+$/.test(n.correctAnswer.trim())?Number(n.correctAnswer.trim()):null;return i===null||i!==a||r%s!==a%s||r===a?null:{adj:s,dir:o,N:r,result:a}}function planCarryFromContext(n){const t=n.correctAnswer.trim();if(!/^\d+$/.test(t))return null;const e=Number(t);if(e<1e3||e>10999)return null;const s=[...normaliseStem(n.stem).matchAll(/\d+/g)].map(o=>Number(o[0]));for(const o of[1e3,100])if(s.includes(o)){for(const[r,a]of[["more",e-o],["less",e+o]])if(!(a<1e3||a>9999)&&s.includes(a)&&a%o===e%o&&a!==e)return{adj:o,dir:r,N:a,result:e}}return null}function planFromContext(n){const t=n.correctAnswer.trim();if(!/^\d+$/.test(t))return null;const e=Number(t);if(e<1e3||e>9999)return null;const s=[...normaliseStem(n.stem).matchAll(/\d+/g)].map(o=>Number(o[0]));for(const o of[1e3,100])if(s.includes(o)){for(const[r,a]of[["more",e-o],["less",e+o]])if(!(a<1e3||a>9999)&&s.includes(a)&&digitDiffCount(a,e)===1)return{adj:o,dir:r,N:a,result:e}}return null}function buildFullTerms(n){const t=[];
71return n.forEach((e,s)=>{s>0&&t.push("+"),t.push(e)}),t}function buildCarryExample(n,t,e){const{adj:s,dir:o,N:r,result:a}=e,i=s,l=r%i,u=r-l,d=o==="more"?u+i:u-i,m=o==="more"?"+":"−",h=numberToWordsLong(s),f=s===100?"hundreds":"thousands",g=Math.floor(r/i)%10,b=o==="more"?r-g*i:r-g*i+i*9,x=buildFullTerms(expansion$1(r)),y=[...buildFullTerms(expansion$1(a)),"=",a],E=o==="more"?[u,"+",s,"=",d]:[u,"-",s,"=",d],k=o==="more"?`${u} + ${s} = ${d} — the ${f} column rolls over.`:`Borrow from above: ${u} − ${s} = ${d}.`,T=`${capitalised(numberToWordsLong(u))} ${o==="more"?"add":"take away"} ${h} is ${numberToWordsLong(d)}.`,A=o==="more"?`The ${f} column is already at nine, so one more ${s===100?"hundred":"thousand"} makes ten ${f}. That is too many for one column, so it goes back to zero and carries one into the column above. ${T}`:`There are no ${f} to take away from, so we exchange one from the column above. ${T}`;return buildExample({generatorId:n,question:t,title:`What is ${s} ${o} than ${r}?`,computed:{N:r,adj:s,result:a,wrong:b,answer:a,above:u,unit:i,afterAbove:d},steps:[{visual_data:{type:"equation_display",terms:x},animation:"show_expansion",caption:`${r} split into place columns.`,spoken:`We want ${h} ${o}, so first we split ${numberToWordsLong(r)} into thousands, hundreds, tens and ones. Then we can see each column on its own.`},{visual_data:{type:"equation_display",terms:E},animation:"pulse_unknown",caption:k,spoken:A},{visual_data:{type:"equation_display",terms:y},animation:"write_answer",caption:`${r} ${m} ${s} = ${a}, not ${b}.`,spoken:`The columns below the ${f} never moved. So ${h} ${o} than ${numberToWordsLong(r)} is ${numberToWordsLong(a)}. It is not ${numberToWordsLong(b)} — that would forget the carry.`}]})}const adjustment$4={id:"NPV.P5.M06.adjust.v1",microSkillId:"NPV.P5.M06",forms:["place_value_adjustment"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=planFromSymbolic(n)??planFromContext(n);if(!t){const O=planCarrySymbolic(n)??planCarryFromContext(n);return O?buildCarryExample(this.id,n,O):null}const{adj:e,dir:s,N:o,result:r}=t,a=e===100?s==="more"?o+10:o-10:s==="more"?o+1:o-1,i=e===100?"tens":"ones",l=e===100?"hundreds":"thousands",u=s==="more"?"+":"−",d=s==="more"?"Adding":"Taking away",m=numberToWordsLong(e),h=e===100?"hundred":"thousand",f=O=>O===1?`one ${h}`:`${numberToWords(O)} ${l}`,g=Math.floor(o/e)%10,b=Math.floor(r/e)%10,y=Math.floor(a/e)%10!==g?`that is ${numberToWordsLong(Math.abs(a-o))} ${s}, not ${m} ${s}`:`that changes the ${i}, not the ${l}`,E=s==="more"?`${capitalised(f(g))} add one ${h} makes ${f(b)}.`:`${capitalised(f(g))} take away one ${h} leaves ${f(b)}.`,k=e===100?2:3,T=placeParts(o,k),A=placeParts(r,k),N=partTerms(T,!1),S=partTerms(T,!0),M=A.length>1?[...partTerms(A,!1),"=",r]:partTerms(A,!1),L=Math.max(String(o).length,String(r).length),P=O=>String(O).padStart(L,"0").split("").map(Number),F=L-1-k,D=P(o),U=P(r),q=L>=2&&L<=5&&F>=0,V=(O,z={})=>({type:"place_value_chart",digits:O,...z});return buildExample({generatorId:this.id,question:n,title:`What is ${e} ${s} than ${o}?`,computed:{N:o,adj:e,result:r,wrong:a,dBefore:g,dAfter:b,answer:r},steps:[{visual_data:q?V(D):{type:"equation_display",terms:N},animation:"show_expansion",caption:`${o} split into place columns.`,spoken:`We want ${m} ${s}, so first we split ${numberToWordsLong(o)} into thousands, hundreds, tens and ones. Then we can see each column on its own.`},{visual_data:q?V(U.map((O,z)=>
71z===F?O:D[z]),{highlight:F,digit_text:{[F]:"?"},change:s==="more"?1:-1}):{type:"equation_display",terms:S},animation:"pulse_unknown",caption:`${e} ${s} changes only the ${l}.`,spoken:`${d} ${m} changes the number of ${l}, so only the ${l} column changes. ${E} The other columns stay just as they were.`},{visual_data:q?V(U,{highlight:F}):{type:"equation_display",terms:M},animation:"write_answer",caption:`${o} ${u} ${e} = ${r}, not ${a} — the ${i} do not move.`,spoken:`Now put the columns back together. ${capitalised(m)} ${s} than ${numberToWordsLong(o)} is ${numberToWordsLong(r)}. It is not ${numberToWordsLong(a)} — ${y}.`}]})}},claimAdjust={id:"NPV.P5.M06.claim-adjust.v1",microSkillId:"NPV.P5.M06",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CLAIM_ADJUST);if(!t)return null;const[,e,s,o,r,a]=t,i=Number(s),l=Number(r.replace(/,/g,"")),u=Number(a.replace(/,/g,""));if(l<1e3||l>9999)return null;const d=o==="more"?l+i:l-i;if(d<0||d>99999)return null;const m=d===u,h=m?"Yes":"No";if(n.correctAnswer.trim()!==h)return null;const f=o==="more"?"+":"−",g=o==="more"?"add":"take away",b=numberToWordsLong(i),x=i===100&&digitDiffCount(l,d)>1,y=digitAt(l,1),E=x?{visual_data:{type:"equation_display",terms:o==="more"?[y*100,"+",i,"=",1e3]:[1e3,"-",i,"=",900]},animation:"pulse_unknown",caption:o==="more"?`${y*100} + ${i} = 1000 — a whole extra thousand.`:`Borrow 1000 from the thousands: 1000 − ${i} = 900.`,spoken:o==="more"?`There are already ${numberToWordsLong(y)} hundreds. Adding one more hundred makes ten hundreds, which is one thousand — a whole extra thousand. Watch the hundreds roll back to zero as the thousands go up by one.`:"There are no hundreds to take away from, so we borrow one thousand from the thousands column, giving ten hundreds. Now we can take away one hundred, leaving nine hundred."}:{visual_data:{type:"equation_display",terms:[l,f,i,"=",d]},animation:"pulse_unknown",caption:`Work it out: ${l} ${f} ${i} = ${d}.`,spoken:`We ${g} ${b} ourselves and watch every column that changes, including any carry.`};return buildExample({generatorId:this.id,question:n,title:`${e} says ${i} ${o} than ${l} is ${u}. Right?`,computed:x?{N:l,adj:i,claim:u,actual:d,hundredsValue:y*100,thousandUnit:1e3,tenHundreds:900}:{N:l,adj:i,claim:u,actual:d},steps:[{visual_data:{type:"equation_display",terms:[l]},animation:"show_number",caption:`${e} claims ${i} ${o} than ${l} is ${u}.`,spoken:`${e} says ${b} ${o} than ${numberToWordsLong(l)} is ${numberToWordsLong(u)}. To check a claim, we work it out ourselves.`},E,{visual_data:{type:"equation_display",terms:[l,f,i,"=",d]},animation:"write_answer",caption:m?`${e} is right: ${l} ${f} ${i} = ${d}.`:`${e} is wrong: it's ${d}, not ${u}.`,spoken:m?`So ${b} ${o} than ${numberToWordsLong(l)} really is ${numberToWordsLong(d)}. That matches the claim, so ${e} is right.`:`So ${b} ${o} than ${numberToWordsLong(l)} is really ${numberToWordsLong(d)}. That does not match ${numberToWordsLong(u)}, so ${e} is wrong.`}]})}},contextCalcAdjust={id:"NPV.P5.M06.context-calc.v1",microSkillId:"NPV.P5.M06",forms:["context_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.correctAnswer.trim();if(!/^\d+$/.test(t))return null;const e=Number(t),s=normaliseStem(n.stem),o=/\b(adds|another|gains|more)\b/i.test(s)?"more":/\b(spends|loses|less|leaves|fewer)\b/i.test(s)?"less":null;if(!o)return null;const r=extractInts(s);for(const a of[1e3,100])if(r.includes(a))for(const i of r){if(i===a||i<1e3||i>9999)continue;const l=o==="more"?i+a:i-a;if(l!==e)continue;const u=o==="more"?"+":"−",d=o==="more"?"adds":"takes away",m=o==="more"?"add":"take away",h=numberToWordsLong(a),f=a===100&&digitDiffCount(i,l)>1,g=digitAt(i,1),b=f?{visual_data:{type:"equation_display",terms:o==="more"?[g*100,"+",a,"=",1e3]:[1e3,"-",a,"=",900]},animation:"pulse_unknown",caption:o==="more"?`${g*100} + ${a} = 1000 — a whole extra thousand.`:`Borrow 1000 from the thousands: 1000 − ${a} = 900.`,spoken:o==="more"?`There are already ${numberToWordsLong(g)} hundreds. Adding one more hundred makes ten hundreds, which is one thousand — a whole extra thousand. The hundreds roll back to zero as the thousands go up by one.`:"There are no hundreds to take away from, so we borrow one thousand from the thousands column, giving ten hundreds. Now we can take away one hundred, leaving nine hundred."}:{visual_data:{type:"equation_display",terms:[i,u,a]},animation:"pulse_unknown",caption:`${d==="adds"?"Add":"Take away"} ${a}: ${i} ${u} ${a}.`,spoken:`We ${m} ${h}, watching every column that changes, including any carry.`};return buildExample({generatorId:this.id,question:n,title:`What is ${i} ${o==="more"?"plus":"minus"} ${a}?`,computed:f?{N:i,adj:a,result:l,answer:l,hundredsValue:g*100,thousandUnit:1e3,tenHundreds:900}:{N:i,adj:a,result:l,answer:l},steps:[{visual_data:{type:"equation_display",terms:[i]},animation:"show_number",caption:`Start with ${i}.`,spoken:`We start with ${numberToWordsLong(i)}, and someone ${d} ${h} more. To find the new amount, we ${m} ${h}.`},b,{visual_data:{type:"equation_display",terms:[i,u,a,"=",l]},animation:"write_answer",caption:`${i} ${u} ${a} = ${l}.`,spoken:`Putting the columns back together, ${numberToWordsLong(i)} ${o==="more"?"plus":"minus"} ${h} gives ${numberToWordsLong(l)}. So the new amount is ${numberToWordsLong(l)}.`}]})}return null}},compareAdjust={id:"NPV.P5.M06.compare-adjust.v1",microSkillId:"NPV.P5.M06",forms:["comparison"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(COMPARE_ADJUST_WRAP);if(!t)return null;const[,e,s]=t,o=e.match(COMPARE_ADJUST_PART),r=s.match(COMPARE_ADJUST_PART);if(!o||!r)return null;const a=Number(o[1]),i=o[2],l=Number(o[3].replace(/,/g,"")),u=Number(r[1]),d=r[2],m=Number(r[3].replace(/,/g,""));if(l<1e3||l>9999||m<1e3||m>9999)return null;const h=i==="more"?l+a:l-a,f=d==="more"?m+u:m-u;if(h===f)return null;const g=h>f?e:s;if(n.correctAnswer.trim()!==g)return null;const b=Math.max(h,f),x=decisivePlace(h,f),y=x?` ${x.anim}`:"";return buildExample({generatorId:this.id,question:n,title:`Which is bigger: ${e}, or ${s}?`,computed:x?{NA:l,adjA:a,resultA:h,NB:m,adjB:u,resultB:f,decisiveDigitA:x.digitA,decisiveDigitB:x.digitB}:{NA:l,adjA:a,resultA:h,NB:m,adjB:u,resultB:f},steps:[{visual_data:{type:"comparison",left:h,right:f,symbol:null},animation:"show_both",caption:`${e} = ${h}. ${s} = ${f}.`,spoken:`First we work out each new number: ${numberToWordsLong(h)}, and ${numberToWordsLong(f)}.`},{visual_data:{type:"comparison",left:h,right:f,symbol:null},animation:`pulse_unknown${y}`,caption:x?`Compare the ${x.name}: ${x.digitA} and ${x.digitB}.`:"Now compare the two results.",spoken:x?`Both numbers start the same, until the ${x.name}. That is the column that decides which is bigger.`:"The first calculation gives one result, and the second gives another. Now we compare the two."},{visual_data:{type:"comparison",left:h,right:f,symbol:h>f?">":"<"},animation:`write_answer${y}`,caption:`${g} is bigger (${b}
71).`,spoken:`So ${numberToWordsLong(b)} is the bigger result, and that calculation gives the bigger number.`}]})}},twoStepAdjust={id:"NPV.P5.M06.two-step.v1",microSkillId:"NPV.P5.M06",forms:["equation_completion"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(TWO_STEP_ADJUST);if(!t)return null;const[,e,s,o,r,a]=t,i=Number(e),l=Number(r),u=s,d=a,m=Number(o.replace(/,/g,""));if(m<1e3||m>9999)return null;const h=u==="more"?m+i:m-i,f=d==="more"?h+l:h-l,g=/^\d+$/.test(n.correctAnswer.trim())?Number(n.correctAnswer.trim()):null;if(g===null||g!==f)return null;const b=u==="more"?"+":"−",x=d==="more"?"+":"−",y=numberToWordsLong(i),E=numberToWordsLong(l);return buildExample({generatorId:this.id,question:n,title:`${i} ${s} than ${m}, then ${l} ${a} than that.`,computed:{N:m,adj1:i,mid:h,adj2:l,final:f,answer:f},steps:[{visual_data:{type:"equation_display",terms:[m,b,i,"=",h]},animation:"show_number",caption:`First: ${m} ${b} ${i} = ${h}.`,spoken:`This has two changes, so we do them one at a time. First, ${y} ${s} than ${numberToWordsLong(m)} is ${numberToWordsLong(h)}.`},{visual_data:{type:"equation_display",terms:[h,x,l]},animation:"pulse_unknown",caption:`Then: find ${l} ${a} than ${h}.`,spoken:`Now we start from that new number, ${numberToWordsLong(h)}, and find ${E} ${a} than it.`},{visual_data:{type:"equation_display",terms:[h,x,l,"=",f]},animation:"write_answer",caption:`${h} ${x} ${l} = ${f}.`,spoken:`Working it out, ${numberToWordsLong(h)} ${d==="more"?"plus":"minus"} ${E} gives ${numberToWordsLong(f)}. So the final number is ${numberToWordsLong(f)}.`}]})}},NPV_P5_M06_GENERATORS=[adjustment$4,claimAdjust,contextCalcAdjust,compareAdjust,twoStepAdjust],DEMO_COLOR="#2563eb",ORDINALS=["first","second","third","fourth","fifth"],CLAIM_TENTHS=/^A number line runs from 0 to 1, split into tenths\. The marked point is (\d) steps from 0\. (\w+) says this point is (0\.\d)\. (\w+) says it is (\d)\. Who is right\?$/,COMPARE_TENTHS=/^Point A shows (\d) tenths on a number line from 0 to 1\. Point B shows (\d) tenths on a different number line from 0 to 1\. Which point is further from 0\?$/;function answerTenths(n){const t=n.trim();let e;return(e=t.match(/^0\.(\d)$/))||(e=t.match(/^(\d+)\/10$/))||(e=t.match(/^(\d+)\s+tenths?$/))?Number(e[1]):null}const WORD_DIGIT={1:"one",2:"two",3:"three",4:"four",5:"five",6:"six",7:"seven",8:"eight",9:"nine"};function stemTenths(n){let t;return(t=n.match(/0\.(\d)\b/))||(t=n.match(/(\d+)\/10\b/))||(t=n.match(/(\d+)\s+tenths?\b/))?Number(t[1]):null}function buildTenthsDemo(n,t,e=t){const s=t/10,o=Number(n.correctAnswer),r=Number.isFinite(o)?o:s,a=e!==t,i=10-e,l=g=>({type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:g,color:DEMO_COLOR}),u=t===1?"tenth":"tenths",d=t===1?"That same":"Those same",m=t===1?"lives":"live",h=a?{visual_data:l(e),animation:"show_start",caption:`The bar: ${e} of the 10 parts are coloured.`,spoken:`Here is the bar from the question. ${cap$f(numberToWords(e))} of the ten equal parts are already coloured in.`}:{visual_data:l(0),animation:"show_start",caption:"One whole, split into 10 equal parts.",spoken:"Here is one whole bar. To make tenths, we split it into ten equal parts."},f=a?{visual_data:l(e),animation:"count_complement",caption:`Count the WHITE parts: ${i} of the 10.`,spoken:`The coloured parts already have their answer. We count the parts that are still white instead — ${numberToWords(i)} of them.`}:{visual_data:l(t),animation:"shade_parts",caption:`Colour ${t} of the 10 equal parts.`,spoken:`Each part is one tenth. We colour ${numberToWords(t)} of those equal parts — that is ${numberToWords(t)} ${u}.`};return buildExample({generatorId:"NPV.P6.M01.tenths.v1",question:n,title:`How do we show ${t} ${u} of one whole?`,computed:a?{n:t,value:s,answer:r,pictureShaded:e,notShadedCount:i}:{n:t,value:s,answer:r},steps:[h,f,{visual_data:{type:"number_line",min:0,max:1,step:.1,marked:[s],unknown:s},animation:"mark_position",caption:`${t} ${u} = ${s} on the line.`,spoken:`${d} ${numberToWords(t)} ${u} ${m} at one spot on the number line. ${cap$f(numberToWords(t))} ${u} is ${decimalWords(s)}.`}]})}function buildLocationDemo(n,t,e,s){const o=s/10,r=ORDINALS[e],a=s===1?"tenth":"tenths",i=l=>({type:"number_line",min:0,max:1,step:.1,marked:t,...l});return buildExample({generatorId:"NPV.P6.M01.tenths.v1",question:n,title:`Where is ${o} (${s} ${a}) on the line?`,computed:{n:s,target:o,idx:e,markCount:t.length},steps:[{visual_data:i({}),animation:"show_scale",caption:`The line has ${t.length}
71 dots marked.`,spoken:"Here is the question's own number line, with several dots already marked."},{visual_data:i({unknown:o}),animation:"count_ticks",caption:`Count ${s} ${a} from 0.`,spoken:`We count from zero, one tenth at a time, up to ${numberToWords(s)} ${a}.`},{visual_data:i({marker_at:o}),animation:"write_answer",caption:`That's the ${r} dot.`,spoken:`Counting lands on the dot at ${decimalWords(o)} — the ${r} dot on the line.`}]})}function buildIdentifyDemo(n,t,e,s,o){const r=s===1?"tenth":"tenths",a=s/10,i=d=>({type:"number_line",min:0,max:1,step:.1,marked:t,...d}),l=o==="fraction"?`The ? shows ${s}/10 (${s} ${r}).`:`The ? shows ${a}.`,u=o==="fraction"?`The mark is ${numberToWords(s)} out of ten equal parts — ${numberToWords(s)} tenths.`:`The mark is ${numberToWords(s)} tenths, which is ${decimalWords(a)}.`;return buildExample({generatorId:"NPV.P6.M01.tenths.v1",question:n,title:`Which ${o==="fraction"?"fraction":"decimal"} does the ? mark show?`,computed:{n:s,value:a,target:e,answer:a},steps:[{visual_data:i({marker_at:e}),animation:"show_scale",caption:"Count the tenths from 0 to the ? mark.",spoken:"We start at zero and count the tenths, one tick at a time, until we reach the marked point."},{visual_data:i({marker_at:e,hops:[{from:0,to:e,label:`${s} ${r}`}]}),animation:"count_ticks",caption:`That's ${s} ${r} from 0.`,spoken:`Counting up one tick at a time, the mark is ${numberToWords(s)} ${r} from zero.`},{visual_data:i({marker_at:e,unknown:e}),animation:"write_answer",caption:l,spoken:u}]})}function cap$f(n){return n.charAt(0).toUpperCase()+n.slice(1)}function decimalWords(n){const t=Math.round(n*10);return`nought point ${numberToWords(t)}`}const tenths={id:"NPV.P6.M01.tenths.v1",microSkillId:"NPV.P6.M01",forms:["place_value_model_probe","place_value_model_choice","symbolic_numeric_entry","number_line_identify","number_line_location","comparison_statement","symbolic_choice","equation_completion","context_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=n.correctAnswer.trim(),s=n.visualData;if(n.mathematicalForm==="context_calculation"){const a=answerTenths(e);if(a===null||a<1||a>9||!/(?:ten equal|10 equal|tenths)/i.test(t))return null;const i=new RegExp(`\\b${a}\\b`).test(t),l=new RegExp(`\\b${WORD_DIGIT[a]}\\b`,"i").test(t);return!i&&!l?null:buildTenthsDemo(n,a)}if(s&&s.type==="fraction_shape"){if(s.total_parts!==10)return null;const a=s.shaded_parts;if(!Number.isInteger(a)||a<0||a>10)return null;const l=/not\s+shaded/i.test(t)?10-a:a;return l<1||l>9||answerTenths(e)!==l?null:buildTenthsDemo(n,l,a)}if(s&&s.type==="number_line"){if(s.unknown!==null&&s.unknown!==void 0){const m=Math.round(s.unknown*10);if(m<1||m>9||answerTenths(e)!==m)return null;const h=/^(\d+)\/10$/.test(e)?"fraction":"decimal",f=Array.isArray(s.marked)?s.marked:[];return buildIdentifyDemo(n,f,s.unknown,m,h)}const a=stemTenths(t);if(a===null||a<1||a>9)return null;const i=a/10,l=Array.isArray(s.marked)?s.marked:[],u=l.findIndex(m=>Math.abs(m-i)<1e-9);if(u<0||u>=ORDINALS.length)return null;const d=`the ${ORDINALS[u]} dot`;return e.toLowerCase()!==d?null:buildLocationDemo(n,l,u,a)}let o=stemTenths(t);if(o===null&&/(?:ten|10)\s+equal\s+parts/i.test(t)){const a=t.match(/(\d)\s+parts?\s+(?:are\s+|is\s+)?(?:left\s+)?(?:not\s+shaded|unshaded)/i);a&&(o=10-Number(a[1]))}return o===null||o<1||o>9||(/^\d+$/.test(e)?Number(e):answerTenths(e))!==o?null:buildTenthsDemo(n,o)}},claimTenths={id:"NPV.P6.M01.claim-tenths.v1",microSkillId:"NPV.P6.M01",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CLAIM_TENTHS);if(!t)return null;const e=Number(t[1]),s=t[2],o=Number(t[3]),r=t[4],a=Number(t[5]);if(e<1||e>9)return null;const i=e/10;let l;if(o===i)l=s;else if(a===i)l=r;else return null;if(n.correctAnswer.trim()!==l)return null;const u=l===s?a:o,d=h=>({type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:h,color:DEMO_COLOR}),m=e===1?"tenth":"tenths";return buildExample({generatorId:this.id,question:n,title:`${s} says ${o}, ${r} says ${a}. Who's right?`,computed:{n:e,value:i,claim1:o,claim2:a},steps:[{visual_data:d(0),animation:"show_start",caption:`${s} and ${r} disagree. One whole, split into 10 parts.`,spoken:`${s} and ${r} disagree about the marked point. Here is one whole bar, split into ten equal parts to make tenths.`},{visual_data:d(e),animation:"shade_parts",caption:`Colour ${e} of the 10 parts — that's ${e} ${m}.`,spoken:`We colour ${numberToWords(e)} of the equal parts. That is ${numberToWords(e)} ${m}, not a whole ${numberToWords(e)}.`},{visual_data:{type:"number_line",min:0,max:1,step:.1,marked:[i],unknown:i},animation:"write_answer",caption:`${l} is right: ${e} ${m} is ${i}, not ${u}.`,spoken:`${l} is right. ${cap$f(numberToWords(e))} ${m} out of ten is ${decimalWords(i)} of the whole.`}]})}},compareTenths={id:"NPV.P6.M01.compare-tenths.v1",microSkillId:"NPV.P6.M01",forms:["comparison"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(COMPARE_TENTHS);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>9||s<1||s>9||e===s)return null;const o=e/10,r=s/10,a=o>r?"Point A":"Point B";if(n.correctAnswer.trim()!==a)return null;const i=l=>({type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:l,color:DEMO_COLOR});return buildExample({generatorId:this.id,question:n,title:`Point A: ${e} tenths. Point B: ${s} tenths. Which is further from 0?`,computed:{nA:e,nB:s,valueA:o,valueB:r},steps:[{visual_data:i(e),animation:"show_start",caption:`Point A: colour ${e} of 10 parts (${e} tenths).`,spoken:`Point A is ${numberToWords(e)} tenths of the whole.`},{visual_data:i(s),animation:"shade_parts",caption:`Point B: colour ${s} of 10 parts (${s} tenths).`,spoken:`Point B is ${numberToWords(s)} tenths of the whole — a different number line, same idea.`},{visual_data:{type:"comparison",left:o,right:r,symbol:o>r?">":"<"},animation:"write_answer",caption:`${a} is further from 0 (${Math.max(o,r)} > ${Math.min(o,r)}).`,spoken:`As decimals, ${decimalWords(Math.max(o,r))} is more than ${decimalWords(Math.min(o,r))}, so that point sits further from the start.`}]})}},equalTenthsProbe={id:"NPV.P6.M01.equal-tenths-probe.v1",microSkillId:"NPV.P6.M01",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId||!
71/^Is the bar split into equal tenths\?$/i.test(normaliseStem(n.stem)))return null;const t=n.visualData;if(!t||t.type!=="fraction_shape")return null;const e=t.total_parts;if(!Number.isInteger(e)||e===10||e<2||e>12)return null;const s=`No, it has ${numberToWords(e)} parts`;if(n.correctAnswer.trim()!==s)return null;const o=r=>({type:"fraction_shape",shape:t.shape,total_parts:e,shaded_parts:r,color:t.color});return buildExample({generatorId:this.id,question:n,title:"Is this bar split into equal tenths?",computed:{totalParts:e,shaded:t.shaded_parts},steps:[{visual_data:o(t.shaded_parts),animation:"show_start",caption:"Here is the bar from the question.",spoken:"Let's check whether this bar is really split into tenths, or something else."},{visual_data:o(t.shaded_parts),animation:"count_parts",caption:`Count the equal parts: ${e}.`,spoken:"We count every equal part in the bar, one at a time."},{visual_data:o(t.shaded_parts),animation:"write_answer",caption:`No — it has ${e} parts, not ten.`,spoken:`Tenths need exactly ten equal parts. This bar has ${numberToWords(e)}, so these are not tenths.`}]})}},wholeTenths={id:"NPV.P6.M01.whole-tenths.v1",microSkillId:"NPV.P6.M01",forms:["comparison_statement"],generate(n){if(n.microSkillId!==this.microSkillId||!/^How many tenths make one whole\?$/i.test(normaliseStem(n.stem))||n.correctAnswer.trim()!=="10 tenths")return null;const t=e=>({type:"fraction_shape",shape:"rectangle",total_parts:10,shaded_parts:e,color:DEMO_COLOR});return buildExample({generatorId:this.id,question:n,title:"How many tenths make one whole?",computed:{n:10},steps:[{visual_data:t(0),animation:"show_start",caption:"One whole, split into 10 equal parts.",spoken:"Here is one whole bar, split into ten equal parts to make tenths."},{visual_data:t(10),animation:"shade_parts",caption:"Colour every part: all 10 of them.",spoken:"We colour every single part — the whole bar is coloured in."},{visual_data:t(10),animation:"write_answer",caption:"10 tenths make one whole.",spoken:"Ten tenths, all coloured together, make exactly one whole."}]})}},NPV_P6_M01_GENERATORS=[tenths,claimTenths,compareTenths,equalTenthsProbe,wholeTenths],MICRO$c="NPV.P6.M02",cents=n=>Math.round(n*100),isTwoDp=n=>/^\d+(\.\d{1,2})?$/.test(n),WORD_TO_NUM={zero:0,one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12,thirteen:13,fourteen:14,fifteen:15,sixteen:16,seventeen:17,eighteen:18,nineteen:19,twenty:20},wordToNum=n=>{const t=WORD_TO_NUM[n.toLowerCase()];return t===void 0?null:t};function tokenToValue(n){if(isTwoDp(n))return Number(n);let t=n.match(/^(\d+) ones?$/);return t?Number(t[1]):(t=n.match(/^(\d+) tenths?$/),t?Number(t[1])/10:(t=n.match(/^(\d+) hundredths?$/),t?Number(t[1])/100:null))}const unitWord$2=(n,t)=>n===1?t.slice(0,-1):t,PART_FIND=/^(\d+(?:\.\d+)?) = (.+)$/,PART_WHOLE=/^Which decimal is the same as (.+)\?$/;function plusJoin(n){const t=[];return n.forEach((e,s)=>{s>0&&t.push("+"),t.push(e)}),t}const dwP=n=>decimalToWords(n).replace(/^zero point/,"nought point"),partition$2={id:"NPV.P6.M02.partition.v1",microSkillId:MICRO$c,forms:["partition_equation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=n.correctAnswer.trim();if(!isTwoDp(e))return null;const s=cents(Number(e)),o=t.match(PART_FIND);if(o){const a=o[1];if(!isTwoDp(a))return null;const i=Number(a);if(i<=0||i>=10)return null;const l=o[2].split(" + ").map(y=>y.trim());if(l.filter(y=>y==="__").length!==1)return null;const d=l.filter(y=>y!=="__").map(tokenToValue);if(d.some(y=>y===null))return null;const h=d.reduce((y,E)=>y+cents(E),0),f=cents(i)-h;if(f<=0||f!==s)return null;const g=h/100,b=plusJoin(l.map(y=>y==="__"?"__":tokenToValue(y))),x=plusJoin(l.map(y=>y==="__"?Number(e):tokenToValue(y)));return buildExample({generatorId:this.id,question:n,title:`What is the missing part of ${a}?`,computed:{whole:i,known:g,answer:Number(e)},steps:[{visual_data:{type:"equation_display",terms:[i,"=",...b]},animation:"show_start",caption:"One part of the decimal is hidden.",spoken:`The decimal ${dwP(a)} is split into place-value parts, but one part is hidden. The parts always add up to the whole, so we can work it out.`},{visual_data:{type:"equation_display",terms:[i,"−",g,"=","__"]},animation:"pulse_unknown",caption:"Take the known parts from the whole.",spoken:`Taking away undoes adding. So we take the parts we already know, ${dwP(String(g))}, away from ${dwP(a)}. What is left over is the missing part.`},{visual_data:{type:"equation_display",terms:[i,"=",...x]},animation:"write_answer",caption:`The missing part is ${e}.`,spoken:`The missing part is ${dwP(e)}. Put it back in, and all the parts add up to ${dwP(a)} again.`}]})}const r=t.match(PART_WHOLE);if(r){const a=r[1].split(" + ").map(h=>h.trim());if(a.length<2||!a.every(h=>isTwoDp(h)))return null;const i=a.reduce((h,f)=>h+cents(Number(f)),0);if(i<=0||i>=1e3||i!==s)return null;const l=Math.floor(i/100),u=i-l*100;if(u===0)return null;const d=u/100,m=plusJoin(a.map(h=>Number(h)));return buildExample({generatorId:this.id,question:n,title:`Which decimal is ${e}?`,computed:{whole:l,frac:d,answer:Number(e)},steps:[{visual_data:{type:"equation_display",terms:[...m,"=","__"]},animation:"show_start",caption:"Add the place-value parts.",spoken:"Each part tells us how many ones, tenths or hundredths there are. So we add the parts to build the whole decimal."},{visual_data:{type:"equation_display",terms:[l,"+",d,"=","__"]},animation:"pulse_unknown",caption:"Ones, then the decimal part.",spoken:"The whole ones stay in front of the point; the tenths and hundredths join together after it."},{visual_data:{type:"equation_display",terms:[l,"+",d,"=",Number(e)]},animation:"write_answer",caption:`The decimal is ${e}.`,spoken:`Put together, that is ${dwP(e)}. So the decimal is ${dwP(e)}.`}]})}return null}},EQC_TENTHS=/^(\d*\.\d+) (?:is the same as|has the same value as) __ tenths$/,EQC_HUNDREDTHS=/^How many hundredths are in (\d*\.\d+)\?$/,equationCompletion={id:"NPV.P6.M02.completion.v1",microSkillId:MICRO$c,forms:["equation_completion"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem);let e,s,o;const r=t.match(EQC_TENTHS),a=r?null:t.match(EQC_HUNDREDTHS);if(r)e=r[1],s="tenths";else if(a)e=a[1],s="hundredths";else return null;if(!isTwoDp(e))return null;const i=Number(e);if(i<=0||i>=1)return null;const l=cents(i);if(s==="tenths"&&l%10!==0||(o=s==="tenths"?l/10:l,!/^\d+$/.test(n.correctAnswer.trim()))||Number(n.correctAnswer.trim())!==o)return null;if(s==="hundredths"&&i>=.1){if(o%10!==0)return null;const b=o/10,x={from:0,to:i,label:`${b} ${unitWord$2(b,"tenths")}`},y={from:0,to:.1,label:"10 hundredths"};return buildExample({generatorId:this.id,question:n,title:`How many hundredths are in ${e}?`,computed:{value:i,tenths:b,count:o,answer:o},steps:[{visual_data:{type:"number_line",min:0,max:1,step:.1,marked:[i],hops:[],unknown:null},animation:"show_start",caption:"The line is marked in tenths.",spoken:`Let's find how many hundredths make ${dwP(e)}. The line is marked in tenths, and every tenth holds ten hundredths, so we count tenths first.`},{visual_data:{type:"number_line",min:0,max:1,step:.1,marked:[i],hops:[x],unknown:null},animation:"read_places",caption:`${e} is ${b} ${unitWord$2(b,"tenths")}.`,spoken:`Count the tenth ticks from nought up to the mark: ${dwP(e)} is ${numberToWordsLong(b)} ${unitWord$2(b,"tenths")}.`},{visual_data:{type:"number_line",min:0,max:.1,step:.01,marked:[.1],hops:[y],unknown:.1,conclude_label:`${o} hundredths`},animation:"land",caption:`One tenth is ten hundredths, so ${e} = ${o} hundredths.`,spoken:`Here is a close-up of the number line, showing just one tenth. It holds ten little hundredth steps. So ${numberToWordsLong(b)} ${unitWord$2(b,"tenths")} is ${numberToWordsLong(o)} hundredths, and that is how many hundredths make ${dwP(e)}.`}]})}const u=i<.1,d=0,m=u?.1:1,h=u?.01:.1,f=s==="tenths"?"tenth":"hundredth",g={from:0,to:i,label:`${o} ${unitWord$2(o,s)}`};return buildExample({generatorId:this.id,question:n,title:`How many ${s} are in ${e}?`,computed:{value:i,count:o,answer:o},steps:[{visual_data:{type:"number_line",min:d,max:m,step:h,marked:[i],hops:[],unknown:null},animation:"show_start",caption:`How many ${s} make ${e}?`,spoken:`Let's find how many ${s} make ${dwP(e)}. The number line shows it, so we can count the steps up to the mark.`},{visual_data:{type:"number_line",min:d,max:m,step:h,marked:[i],hops:[g],unknown:null},animation:"read_places",caption:`Count in ${s} from 0.`,spoken:`Each step on this line is one ${f}. We count the steps from nought up to the mark.`},{visual_data:{type:"number_line",min:d,max:m,step:h,marked:[i],hops:[g],unknown:i},animation:"land",caption:`${e} = ${o} ${unitWord$2(o,s)}.`,spoken:`We land on the mark after ${numberToWordsLong(o)} ${o===1?"step":"steps"}. So ${dwP(e)} is the same as ${numberToWordsLong(o)} ${unitWord$2(o,s)}.`}]})}},DIGIT_PLACE=/^Which digit is in the (tenths|hundredths) place in (\d)\.(\d\d)\?$/,DIGIT_VALUE_OF=/^What is the value of the digit (\d) in (\d)\.(\d\d)\?$/;function buildDigitPartition(n,t,e,s,o,r="NPV.P6.M02.digit.v1"){const a=Number(t.split(".")[0]),i=Number(t.split(".")[1][0]),l=Number(t.split(".")[1][1]),u=e==="tenths"?"First":"Second",d=[{value:a,isTarget:!1},{value:i/10,isTarget:e==="tenths"},{value:l/100,isTarget:e==="hundredths"}].filter(f=>f.value>0||f.isTarget),m=[t,"="],h=[t,"="];return d.forEach((f,g)=>{g>0&&(m.push("+"),h.push("+")),m.push(f.isTarget?"__":f.value),h.push(f.value)}),buildExample({generatorId:r,question:n,title:o.title,computed:{decimal:Number(t),digit:s,answer:o.answer},steps:[{visual_data:{type:"equation_display",terms:[t]},animation:"show_start",caption:`Look at the number ${t}.`,spoken:`Let's look at ${dwP(t)}. Each place after the point has a name — tenths, then hundredths — and the name tells us what a digit there is worth.`},{visual_data:{type:"equation_display",terms:m},animation:"pulse_unknown",caption:`${u} place after the point is ${e}.`,spoken:`To find the right digit, we find the right place. The ${u.toLowerCase()} place after the point is always the ${e} place, so we look at the digit sitting there.`},{visual_data:{type:"equation_display",terms:h},animation:"write_answer",caption:o.step3Caption,spoken:o.step3Spoken}]})}const digitValue$2={id:"NPV.P6.M02.digit.v1",microSkillId:MICRO$c,forms:["digit_value_entry"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=t.match(DIGIT_PLACE);if(e){const o=e[1],r=Number(o==="tenths"?e[3][0]:e[3][1]);if(!/^\d$/.test(n.correctAnswer.trim())||Number(n.correctAnswer.trim())!==r)return null;const a=`${e[2]}.${e[3]}`,i=`${numberToWordsLong(r)} ${unitWord$2(r,o)}`;return buildDigitPartition(n,a,o,r,{title:`Which digit is in the ${o} place in ${a}?`,step3Caption:`The ${o} digit is ${r}.`,step3Spoken:`The digit in the ${o} place is ${numberToWordsLong(r)} — it is worth ${i}.`,answer:r})}const s=t.match(DIGIT_VALUE_OF);if(s){const o=Number(s[1]),r=`${s[2]}.${s[3]}`,a=Number(s[3][0]),i=Number(s[3][1]);let l;if(o===a&&o!==i)l="tenths";else if(o===i&&o!==a)l="hundredths";else return null;const u=`${o} ${unitWord$2(o,l)}`;return n.correctAnswer.trim()!==u?null:buildDigitPartition(n,r,l,o,{title:`What is the value of the digit ${o} in ${r}?`,step3Caption:`${o} is worth ${o} ${unitWord$2(o,l)}.`,step3Spoken:`The digit in the ${l} place is worth ${numberToWordsLong(o)} ${unitWord$2(o,l)}.`,answer:o})}return null}},DIGIT_SEARCH=/^Which decimal has an? (\d) in the (tenths|hundredths) place\?$/,DIGIT_WORTH=/^In which decimal is the (\d) worth ([a-z]+) (tenths|hundredths)\?$/i,TWO_DP_OPTION=/^(\d+)\.(\d)(\d)$/;function parseDigitSearchStem(n){const t=normaliseStem(n),e=t.match(DIGIT_SEARCH);if(e)return{digit:Number(e[1]),place:e[2]};const s=t.match(DIGIT_WORTH);if(s){const o=Number(s[1]),r=wordToNum(s[2]);return r===null||r!==o?null:{digit:o,place:s[3]}}return null}const digitSearch={id:"NPV.P6.M02.digit-search.v1",microSkillId:MICRO$c,forms:["place_value_model_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=parseDigitSearchStem(n.stem);if(!t)return null;const{digit:e,place:s}=t;if(!n.options||n.options.length===0)return null;let o=null,r=0;for(const a of n.options){const i=a.match(TWO_DP_OPTION);if(!i)continue;Number(s==="tenths"?i[2]:i[3])===e&&(r++,o=a)}return r!==1||o===null||n.correctAnswer.trim()!==o?null:buildDigitPartition(n,o,s,e,{title:`Which decimal has ${e} in the ${s} place?`,step3Caption:`${o} has ${e} in the ${s} place.`,step3Spoken:`The digit ${numberToWordsLong(e)} sits in the ${s} place of ${dwP(o)}, so that is the decimal we are looking for.`,answer:Number(o)},this.id)}},CLAIM_EQUAL=/^(\w+) says (\d*\.\d+) and (\d*\.\d+) are equal\. Which statement is true\?$/;function placeDescribe(n){const t=cents(n);return t%10===0?{count:t/10,unit:"tenths"}:{count:t,unit:"hundredths"}}const claimEqual={id:"NPV.P6.M02.claim-equal.v1",microSkillId:MICRO$c,forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CLAIM_EQUAL);if(!t)return null;const[,e,s,o]=t;if(!isTwoDp(s)||!isTwoDp(o))return null;const r=Number(s),a=Number(o);if(r<=0||r>=10||a<=0||a>=10)return null;const i=placeDescribe(r),l=placeDescribe(a),u=unitWord$2(i.count,i.unit),d=unitWord$2(l.count,l.unit),m=`${s} is ${i.count} ${u}; ${o} is ${l.count} ${d}`;return n.correctAnswer.trim()!==m?null:buildExample({generatorId:this.id,question:n,title:`${e} says ${s} and ${o} are equal. True?`,computed:{a:r,b:a,aCount:i.count,bCount:l.count},steps:[{visual_data:{type:"equation_display",terms:[s,"=",o,"?"]},animation:"show_start",caption:`${e} says these are equal.`,spoken:`${e} says ${dwP(s)} and ${dwP(o)} are equal. The same digit can be worth different amounts in different places, so let's check the place value of each.`},{visual_data:{type:"equation_display",terms:[s,"=",i.count,u]},animation:"pulse_unknown",caption:`${s} is ${i.count} ${u}
71.`,spoken:`Let's read the first number by its places: ${dwP(s)} means ${numberToWordsLong(i.count)} ${u}.`},{visual_data:{type:"equation_display",terms:[s,"=",i.count,u,"≠",o,"=",l.count,d]},animation:"write_answer",caption:m,spoken:`Now the second number: ${dwP(o)} means ${numberToWordsLong(l.count)} ${d}. ${cents(r)===cents(a)?"":"The digits sit in different places, so the two are not equal. "}${capitalised(dwP(s))} is ${numberToWordsLong(i.count)} ${u}, and ${dwP(o)} is ${numberToWordsLong(l.count)} ${d}.`}]})}},SAME_VALUE=/^Which shows the same value as (\d+\.\d+)\?$/,sameValue$2={id:"NPV.P6.M02.same-value.v1",microSkillId:MICRO$c,forms:["place_value_model_choice"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(SAME_VALUE);if(!t)return null;const e=t[1];if(!/^\d+\.\d$/.test(e))return null;const s=cents(Number(e));if(!n.options||n.options.length===0)return null;let o=null,r=0;for(const u of n.options)u!==e&&/^\d+\.\d{2}$/.test(u)&&cents(Number(u))===s&&(r++,o=u);if(r!==1||o===null||n.correctAnswer.trim()!==o)return null;const a=Math.floor(Number(e)),i=Number(e.split(".")[1][0]),l=i/10;return buildExample({generatorId:this.id,question:n,title:`Which shows the same value as ${e}?`,computed:{base:Number(e),winner:Number(o),baseCents:s,answer:Number(o)},steps:[{visual_data:{type:"equation_display",terms:[e]},animation:"show_start",caption:`Look at ${e}.`,spoken:`Let's look at ${dwP(e)}. To find the same value written another way, remember that every decimal has a hundredths place, even when it is not written.`},{visual_data:{type:"equation_display",terms:[e,"=",a,"+",l,"+","__"]},animation:"pulse_unknown",caption:`${e} has 0 hundredths.`,spoken:`Let's split it into places. There is nothing written in the hundredths place, so ${dwP(e)} has ${numberToWordsLong(i)} ${unitWord$2(i,"tenths")} and no hundredths at all.`},{visual_data:{type:"equation_display",terms:[e,"=",o]},animation:"write_answer",caption:`${e} = ${o}.`,spoken:`Writing the hundredths as a zero does not change the value, so ${dwP(e)} is the same as ${dwP(o)}.`}]})}},ADJ=/^Which decimal is (\d+) (tenths?|hundredths?) (more|less) than (\d+(?:\.\d+)?)\?$/,adjustment$3={id:"NPV.P6.M02.adjust.v1",microSkillId:MICRO$c,forms:["place_value_adjustment"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(ADJ);if(!t)return null;const e=Number(t[1]),s=t[2].replace(/s$/,""),o=t[3],r=t[4];if(e<1||e>9||!isTwoDp(r))return null;const a=Number(r);if(a<=0||a>=10)return null;const i=s==="tenth"?e*10:e,l=o==="more"?cents(a)+i:cents(a)-i;if(l<=0||l>=1e3)return null;const u=n.correctAnswer.trim();if(!isTwoDp(u)||cents(Number(u))!==l)return null;const d=i/100,m=o==="more"?"+":"−",h=o==="more"?"Add":"Take away",f=`${s}${e===1?"":"s"}`;return buildExample({generatorId:this.id,question:n,title:`${e} ${f} ${o} than ${r}?`,computed:{base:a,amount:d,count:e,answer:Number(u)},steps:[{visual_data:{type:"equation_display",terms:[a]},animation:"show_start",caption:`Start at ${r}.`,spoken:`We start at ${dwP(r)} and change it by ${numberToWordsLong(e)} ${f}.`},{visual_data:{type:"equation_display",terms:[a,m,d,"=","__"]},animation:"pulse_unknown",caption:`${h} ${e} ${f}.`,spoken:`We are only changing ${s}s, so only the ${s}s place moves. ${h} ${numberToWordsLong(e)} ${f} in that place.`},{visual_data:{type:"equation_display",terms:[a,m,d,"=",Number(u)]},animation:"write_answer",caption:`${r} ${m} ${e} ${f} = ${u}.`,spoken:`The other places stay the same. So ${numberToWordsLong(e)} ${f} ${o} than ${dwP(r)} is ${dwP(u)}.`}]})}};function parseComposition(n){const t=normaliseStem(n.stem),e=t.match(/(\d+) ones?\b/),s=t.match(/(\d+) tenths?\b/),o=t.match(/(\d+) hundredths?\b/),r=/\bno tenths\b/i.test(t);if(e||s||o||r){const i=e?Number(e[1]):0,l=s?Number(s[1]):0,u=o?Number(o[1]):0;if(i>9||l>9||u>9)return null;const d=i*100+l*10+u;return{value:d/100,cents:d,O:i,T:l,H:u}}const a=t.match(/\b([a-z]+) and ([a-z]+) (tenths?|hundredths?)\b/i);if(a){const i=wordToNum(a[1]),l=wordToNum(a[2]);if(i===null||l===null||i>9||l>9)return null;const u=/hundred/i.test(a[3]),d=i*100+(u?l:l*10);return{value:d/100,cents:d,O:i,T:u?0:l,H:u?l:0}}return null}const composition={id:"NPV.P6.M02.compose.v1",microSkillId:MICRO$c,forms:["place_value_model_choice","misconception_contrast_probe","word_to_numeral"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=parseComposition(n);if(!t)return null;const{value:e,cents:s,O:o,T:r,H:a}=t,i=r*10+a;if(i<=0||s>=1e3)return null;const l=n.correctAnswer.trim();if(!isTwoDp(l)||cents(Number(l))!==s)return null;const u=i/100,d=r*10/100,m=a/100,h=plusJoin([o,d,m]);return buildExample({generatorId:this.id,question:n,title:`Which decimal is ${l}?`,computed:{whole:o,tenths:r,hundredths:a,value:e,answer:e},steps:[{visual_data:{type:"equation_display",terms:[...h,"=","__"]},animation:"show_start",caption:"Ones, tenths and hundredths — each in its place.",spoken:"We build the decimal from its parts: the ones in front of the point, then the tenths and hundredths after it."},{visual_data:{type:"equation_display",terms:[o,"+",u,"=","__"]},animation:"pulse_unknown",caption:"Join the parts after the point.",spoken:"The tenths and hundredths join together into the part after the point."},{visual_data:{type:"equation_display",terms:[o,"+",u,"=",e]},animation:"write_answer",caption:`Altogether that is ${l}.`,spoken:`Put together, that is ${decimalToWords(l)}.`}]})}},CLAIM_PARTITION=/^(\w+) says (\d+\.\d\d) = (\d+) \+ (\d+(?:\.\d+)?) \+ (\d+(?:\.\d+)?)\. Is (he|she|they) right\?$/,claimPartition={id:"NPV.P6.M02.claim-partition.v1",microSkillId:MICRO$c,forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CLAIM_PARTITION);if(!t)return null;const[,e,s,o,r,a,i]=t;if(!isTwoDp(s))return null;const l=Math.floor(Number(s)),u=cents(Number(s)),d=Number(s.split(".")[1][0]),m=Number(s.split(".")[1][1]),h=Number(o)*100+cents(Number(r))+cents(Number(a)),f=h===u,g=i==="they"?"they're":`${i}'s`,b=f?`Yes, ${g} right`:`No, ${g} wrong`;if(n.correctAnswer.trim()!==b)return null;const x=plusJoin([Number(o),Number(r),Number(a)]),y=plusJoin([l,d/10,m/100]),E=h/100;return buildExample({generatorId:this.id,question:n,title:`${e} says ${s} = ${o} + ${r} + ${a}. Right?`,computed:{decimal:Number(s),whole:l,tenthValue:d/10,hundredthValue:m/100,claimCents:h,decCents:u,claimSum:E},steps:[{visual_data:{type:"equation_display",terms:[s,"=",...x]},animation:"show_start",caption:`${e}'s claim: ${s} = ${o} + ${r} + ${a}.`,spoken:`${e} splits ${dwP(s)} into three parts: ${dwP(o)}, ${dwP(r)} and ${dwP(a)}. The parts must add back to the decimal we started with, so let's check.`},{visual_data:{type:"equation_display",terms:[...x,"=",E]},animation:"pulse_unknown",caption:f?`${e}'s parts add to ${s}.`:`${e}'s parts add to ${E}, not ${s}.`,spoken:f?`Add ${e}'s parts together: they really do make ${dwP(s)}.`:`Add ${e}'s parts together: they make ${dwP(String(E))}, not ${dwP(s)}.`},{visual_data:{type:"equation_display",terms:[s,"=",...y]},animation:"write_answer",caption:f?`${e} is right: the parts add back to ${s}.`:`${e} is wrong: the last part should be ${m/100}.`,spoken:f?`${e} is right — the parts really do add back to ${dwP(s)}.`:`${e} is wrong. The last digit is in the hundredths place, so the last part should be ${dwP(String(m/100))}. Then the parts add back to ${dwP(s)}.`}]})}},PARTITION_CONTEXT=/£(\d+\.\d\d)\.\s*As a partition, that's (\d+) \+ (\d+(?:\.\d+)?) \+ __\.\s*What is the missing part\?$/,partitionContext={id:"NPV.P6.M02.partition-context.v1",microSkillId:MICRO$c,forms:["partition_equation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(PARTITION_CONTEXT);if(!t)return null;const[,e,s,o]=t;if(!isTwoDp(e))return null;const r=cents(Number(e)),a=Number(s)*100+cents(Number(o)),i=r-a;if(i<=0)return null;const l=n.correctAnswer.trim();if(!isTwoDp(l)||cents(Number(l))!==i)return null;const u=i/100;return buildExample({generatorId:this.id,question:n,title:`£${e} = ${s} + ${o} + __. What's missing?`,computed:{whole:Number(e),known:a/100,answer:u},steps:[{visual_data:{type:"equation_display",terms:[e,"=",s,"+",o,"+","__"]},animation:"show_start",caption:`£${e} split into parts — one part is hidden.`,spoken:"We split the amount into place-value parts, but one part is hidden."},{visual_data:{type:"equation_display",terms:[e,"−",s,"−",o,"=","__"]},animation:"pulse_unknown",caption:"Take the known parts from the whole.",spoken:"Take the parts we already know away from the whole amount. What's left is the missing part."},{visual_data:{type:"equation_display",terms:[e,"=",s,"+",o,"+",u]},animation:"write_answer",caption:`The missing part is ${l}.`,spoken:`The missing part is ${decimalToWords(l)} — the hundredths that are left over.`}]})}},TWO_PARTITION=/^Which partition correctly matches (\d+\.\d\d)\? (A: \d+ \+ \d+(?:\.\d+)? \+ \d+(?:\.\d+)?) (B: \d+ \+ \d+(?:\.\d+)? \+ \d+(?:\.\d+)?)$/,PARTITION_EXPR=/(\d+) \+ (\d+(?:\.\d+)?) \+ (\d+(?:\.\d+)?)$/,twoPartitionChoice={id:"NPV.P6.M02.two-partition.v1",microSkillId:MICRO$c,forms:["place_value_model_choice"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(TWO_PARTITION);if(!t)return null;const[,e,s,o]=t;if(!isTwoDp(e))return null;const r=cents(Number(e)),a=s.match(PARTITION_EXPR),i=o.match(PARTITION_EXPR);if(!a||!i)return null;const l=Number(a[1])*100+cents(Number(a[2]))+cents(Number(a[3])),u=Number(i[1])*100+cents(Number(i[2]))+cents(Number(i[3])),d=l===r,m=u===r;if(!d&&!m)return null;let h;if(d&&m?h="Both are correct":h=d?s:o,n.correctAnswer.trim()!==h)return null;const f=d&&!m?s:m&&!d?o:null,g=y=>y.replace(/^A: |^B: /,""),b=f??s,x=f!==null?[e,"=",g(f)]:[e,"=",g(s),"=",g(o)];return buildExample({generatorId:this.id,question:n,title:`Which partition correctly matches ${e}
71?`,computed:{decimal:Number(e),sumA:l,sumB:u,decCents:r},steps:[{visual_data:{type:"equation_display",terms:[e]},animation:"show_start",caption:`Two partitions claim to match ${e}.`,spoken:"Two different splits are offered for the same decimal. We check each one against the real place values."},{visual_data:{type:"equation_display",terms:[e,"=",g(b),"?"]},animation:"pulse_unknown",caption:`${b} — check tenths and hundredths line up.`,spoken:"Every digit after the point keeps its own place — tenths first, then hundredths — never swapped."},{visual_data:{type:"equation_display",terms:x},animation:"write_answer",caption:f!==null?`${f} correctly matches ${e}.`:`Both partitions correctly match ${e}.`,spoken:f!==null?`Only one partition keeps every digit in its true place — that one matches ${decimalToWords(e)}.`:`Both partitions keep every digit in its true place, so both match ${decimalToWords(e)}.`}]})}},NPV_P6_M02_GENERATORS=[partition$2,equationCompletion,digitValue$2,adjustment$3,composition,claimPartition,partitionContext,twoPartitionChoice,digitSearch,claimEqual,sameValue$2],nw$4=n=>decimalToWords(String(n)).replace(/^zero point/,"nought point");function centsOf(n){return Math.round(n*100)}function wholeOf(n){return Math.floor(centsOf(n)/100)}function tenthsOf(n){return Math.floor(centsOf(n)/10)%10}function hundredthsOf(n){return centsOf(n)%10}function dpOf$5(n){return(String(n).split(".")[1]??"").length}function makeDecimalCompareGenerator(n,t,e){const s=o=>decimalToWords(o).replace(/^zero point/,"nought point");return{id:n,microSkillId:t,forms:["comparison","comparison_statement"],generate(o){if(o.microSkillId!==t)return null;const r=o.visualData;if(!r||r.type!=="comparison")return null;const a=r.left,i=r.right;if(typeof a!="number"||typeof i!="number"||Math.abs(a-i)<1e-9||!(a>=0&&a<10)||!(i>=0&&i<10)||dpOf$5(a)>2||dpOf$5(i)>2)return null;const l=Math.max(a,i),u=Math.min(a,i),d=wholeOf(a)===wholeOf(i),m=tenthsOf(a)===tenthsOf(i);let h,f,g;const b=d&&m;if(b){const G=Math.floor(centsOf(a)/10);h=G/10,f=(G+1)/10,g=.01}else if(d)h=wholeOf(a),f=h+1,g=.1;else{if(h=Math.floor(u),f=Math.ceil(l),f-h>3||centsOf(a)%50!==0||centsOf(i)%50!==0)return null;g=.5}if(Math.abs(a-i)<g/2-1e-9)return null;const x=normaliseStem(o.stem),y=o.correctAnswer.trim();let E,k,T=null;if(/symbol/i.test(x)){if(T=a<i?"<":">",y!==T)return null;E="symbol",k=l}else if(/larger|greater/i.test(x)){if(!/^\d*\.?\d+$/.test(y)||Math.abs(Number(y)-l)>1e-9)return null;E="greater",k=l}else if(/smaller/i.test(x)){if(!/^\d*\.?\d+$/.test(y)||Math.abs(Number(y)-u)>1e-9)return null;E="smaller",k=u}else return null;const A=Math.abs(k-l)<1e-9,N=String(l),S=String(u),M=String(k),L=G=>G===1?"tenth":"tenths",P=G=>G===1?"whole":"wholes",F=G=>G===1?"beats":"beat",D={left:a,right:i,greater:l,smaller:u,target:k,windowMin:h,windowMax:f,answer:Number.isFinite(Number(y))?Number(y):k};let U,q;if(d)if(m){const G=tenthsOf(l),X=hundredthsOf(l),ne=hundredthsOf(u);D.tieTenths=G,D.greaterHundredths=X,D.smallerHundredths=ne,U=A?`Tenths tie ${G}=${G} — hundredths: ${X} beats ${ne}, so ${M}.`:`Tenths tie ${G}=${G} — hundredths: ${ne} under ${X}, so ${M}.`,q=A?`The tenths are equal. So the hundredths decide: ${numberToWords(X)} beats ${numberToWords(ne)}. ${capitalised(s(N))} is greater.`:`The tenths are equal. So the hundredths decide: ${numberToWords(ne)} is less than ${numberToWords(X)}. ${capitalised(s(M))} is smaller.`}else{const G=tenthsOf(l),X=tenthsOf(u);D.greaterTenths=G,D.smallerTenths=X,U=A?`${M} is greater: ${G} ${L(G)} ${F(G)} ${X}.`:`${M} is smaller: ${X} ${L(X)} under ${G}.`,q=A?`Tenths are worth more than hundredths, so we compare the tenths first. ${capitalised(numberToWords(G))} ${L(G)} ${F(G)} ${numberToWords(X)} ${L(X)}, so ${s(N)} is greater.`:`Tenths are worth more than hundredths, so we compare the tenths first. ${capitalised(numberToWords(X))} ${L(X)} is less than ${numberToWords(G)} ${L(G)}, so ${s(M)} is smaller.`}else{const G=wholeOf(l),X=wholeOf(u);D.greaterWhole=G,D.smallerWhole=X,U=A?`${M} is greater: ${G} ${P(G)} ${F(G)} ${X}.`:`${M} is smaller: ${X} ${P(X)} under ${G}.`,q=A?`Whole numbers are worth more than tenths, so we compare the whole numbers first. ${capitalised(numberToWords(G))} beats ${numberToWords(X)}, so ${s(N)} is greater.`:`Whole numbers are worth more than tenths, so we compare the whole numbers first. ${capitalised(numberToWords(X))} is less than ${numberToWords(G)}, so ${s(M)} is smaller.`}E==="symbol"&&T&&(U=`${String(a)} ${T} ${String(i)}.`);let V,O;b?(V=`Close-up: ${h} to ${f} in hundredths.`,O=`Both decimals have the same tenths, so we need a close-up of the number line. Here it is, from ${s(String(h))} to ${s(String(f))}, split into hundredths.`):(V=`Compare on a ${h} to ${f} line.`,O=`To compare, we place both decimals on a number line from ${s(String(h))} to ${s(String(f))}. Numbers get bigger as we go right.`);const z=(G,X)=>({type:"number_line",min:h,max:f,step:g,marked:G,...X!==void 0?{unknown:X}:{}}),K=E==="smaller",Z=K?`Which is smaller, ${String(a)} or ${String(i)}?`:`Which is greater, ${String(a)} or ${String(i)}?`,Q=K?`Place both: ${S} sits further left.`:`Place both: ${N} sits further right.`,B=K?`We place both numbers. Further left means smaller, and ${s(S)} sits further to the left than ${s(N)}.`:`We place both numbers. Further right means greater, and ${s(N)} sits further to the right than ${s(S)}.`;return buildExample({generatorId:n,question:o,title:Z,computed:D,steps:[{visual_data:z([]),animation:"show_scale",caption:V,spoken:O},{visual_data:z([a,i]),animation:"place_points",caption:Q,spoken:B},{visual_data:z([a,i],k),animation:"reveal_greater",caption:U,spoken:q}]})}}}function firstDiffPlace(n,t){return wholeOf(n)!==wholeOf(t)?"whole":tenthsOf(n)!==tenthsOf(t)?"tenths":"hundredths"}const CONTEXT_COMPARE=/(\d+(?:\.\d+)?)\s*([a-zA-Z]+) and (\d+(?:\.\d+)?)\s*[a-zA-Z]+\. Which \w+ is (further|longer|bigger|greater|more)\?$/,contextCompare={id:"NPV.P6.M03.context-compare.v1",microSkillId:"NPV.P6.M03",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CONTEXT_COMPARE);if(!t)return null;const[,e,s,o]=t,r=Number(e),a=Number(o);if(dpOf$5(r)>2||dpOf$5(a)>2||r===a||!(r>0&&r<100&&a>0&&a<100))return null;const i=n.visualData;if(!i||i.type!=="comparison"||Math.abs(i.left-r)>1e-9||Math.abs(i.right-a)>1e-9)return null;const l=Math.max(r,a),u=Math.min(r,a),m=`${l===r?e:o} ${s}`;if(n.correctAnswer.trim()!==m)return null;const h=firstDiffPlace(l,u),f=h==="whole"?wholeOf(l):h==="tenths"?tenthsOf(l):hundredthsOf(l),g=h==="whole"?wholeOf(u):h==="tenths"?tenthsOf(u):hundredthsOf(u);return buildExample({generatorId:this.id,question:n,title:`Which is further: ${e} ${s} or ${o} ${s}?`,computed:{A:r,B:a,winner:l,loser:u,wDigit:f,lDigit:g},steps:[{visual_data:{type:"comparison",left:r,right:a,symbol:null},animation:"show_both",caption:`Compare ${e} ${s} and ${o} ${s}.`,spoken:"To see which is further, we compare the two measurements place by place, starting from the biggest column."},{visual_data:{type:"comparison",left:r,right:a,symbol:r>a?">":"<"},animation:"pulse_unknown",caption:`Same until the ${h}: ${f} vs ${g}.`,spoken:`They share every earlier column, so the ${h} column decides. ${capitalised(numberToWords(f))} beats ${numberToWords(g)}.`},{visual_data:{type:"comparison",left:r,right:a,symbol:r>a?">":"<"},animation:"write_answer",caption:`${m}
71 is further.`,spoken:`So ${nw$4(l)} is more than ${nw$4(u)}, and that one is further.`}]})}},NAMES_HEAVIER=/^(\w+)'s (\w+) is (\d+(?:\.\d+)?) (\w+) and (\w+)'s is (\d+(?:\.\d+)?) \4\. Whose \2 is heavier\?$/,contextHeavier={id:"NPV.P6.M03.context-heavier.v1",microSkillId:"NPV.P6.M03",forms:["comparison_statement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(NAMES_HEAVIER);if(!t)return null;const[,e,s,o,r,a,i]=t,l=Number(o),u=Number(i);if(dpOf$5(l)>2||dpOf$5(u)>2||l===u||!(l>0&&l<100&&u>0&&u<100))return null;const d=n.visualData;if(!d||d.type!=="comparison"||Math.abs(d.left-l)>1e-9||Math.abs(d.right-u)>1e-9)return null;const m=Math.max(l,u),h=Math.min(l,u),f=m===l,g=f?e:a,x=`${g} (${f?o:i} ${r})`;if(n.correctAnswer.trim()!==x)return null;const y=firstDiffPlace(m,h),E=y==="whole"?wholeOf(m):y==="tenths"?tenthsOf(m):hundredthsOf(m),k=y==="whole"?wholeOf(h):y==="tenths"?tenthsOf(h):hundredthsOf(h);return buildExample({generatorId:this.id,question:n,title:`Whose ${s} is heavier: ${o} ${r} or ${i} ${r}?`,computed:{A:l,B:u,winner:m,loser:h,wDigit:E,lDigit:k},steps:[{visual_data:{type:"comparison",left:l,right:u,symbol:null},animation:"show_both",caption:`${e}'s ${s}: ${o} ${r}. ${a}'s: ${i} ${r}.`,spoken:"To see which is heavier, we compare the two weights place by place, starting from the biggest column."},{visual_data:{type:"comparison",left:l,right:u,symbol:l>u?">":"<"},animation:"pulse_unknown",caption:`Same until the ${y}: ${E} vs ${k}.`,spoken:`They share every earlier column, so the ${y} column decides. ${capitalised(numberToWords(E))} beats ${numberToWords(k)}.`},{visual_data:{type:"comparison",left:l,right:u,symbol:l>u?">":"<"},animation:"write_answer",caption:`${x}'s ${s} is heavier.`,spoken:`So ${nw$4(m)} is more than ${nw$4(h)}, and ${g}'s ${s} is heavier.`}]})}},LAST_DIGIT_CLAIM=/^(\w+) says (\d+\.\d+) is bigger than (\d+\.\d+) because (he|she|they) compared the last digits: (\d) is bigger than (\d)\. Is \4 right\?$/,lastDigitClaim={id:"NPV.P6.M03.last-digit-claim.v1",microSkillId:"NPV.P6.M03",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(LAST_DIGIT_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o);if(dpOf$5(a)>2||dpOf$5(i)>2||a===i||!(a>=0&&a<10&&i>=0&&i<10))return null;const l=a>i,u=r==="they"?"they're":`${r}'s`,d=l?`Yes, ${u} right`:`No, ${u} wrong`;if(n.correctAnswer.trim()!==d)return null;const m=Math.max(a,i),h=Math.min(a,i),f=tenthsOf(m),g=tenthsOf(h);return buildExample({generatorId:this.id,question:n,title:`${e} compares ${s} and ${o} by the last digit. Right?`,computed:{A:a,B:i,winner:m,loser:h,wT:f,lT:g},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`${e} compares the LAST digits of ${s} and ${o}.`,spoken:`${e} looks only at the very last digit of each decimal. Let's check that method properly.`},{visual_data:{type:"comparison",left:a,right:i,symbol:m===a?">":"<"},animation:"pulse_unknown highlight_tenths",caption:`Compare the tenths first: ${f} vs ${g}.`,spoken:"The tenths column comes right after the point, and it must be compared FIRST — before the last digit."},{visual_data:{type:"comparison",left:a,right:i,symbol:m===a?">":"<"},animation:"write_answer highlight_tenths",caption:l?`${e} is right: the tenths already decide it.`:`${e} is wrong: the tenths decide, not the last digit.`,spoken:l?`${e} is right, but for the wrong reason — the tenths decide, and they happen to agree with the last digit here.`:`${e} is wrong. The tenths column decides: ${numberToWords(f)} beats ${numberToWords(g)}, regardless of the last digit.`}]})}},BETWEEN_MARKS=/^A number line is marked at (\d+\.\d+), (\d+\.\d+), (\d+\.\d+) and (\d+\.\d+)\. Between which two (?:neighbouring )?marks does (\d+\.\d+) sit\?$/,betweenMarks={id:"NPV.P6.M03.between-marks.v1",microSkillId:"NPV.P6.M03",forms:["number_line_location"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(BETWEEN_MARKS);if(!t)return null;const e=[t[1],t[2],t[3],t[4]],s=e.map(Number);for(let u=1;u<s.length;u++)if(s[u]<=s[u-1])return null;const o=Number(t[5]),r=s.findIndex((u,d)=>d<s.length-1&&o>u&&o<s[d+1]);if(r<0)return null;const a=e[r],i=e[r+1],l=`${a} and ${i}`;return n.correctAnswer.trim()!==l?null:buildExample({generatorId:this.id,question:n,title:`Between which two marks does ${t[5]} sit?`,computed:{target:o,lower:s[r],upper:s[r+1]},steps:[{visual_data:{type:"number_line",min:s[0],max:s[3],step:.1,marked:s},animation:"show_scale",caption:`The line is marked at ${e.join(", ")}.`,spoken:"Here is a number line with four labelled marks. To place our decimal, we find the two marks it sits between."},{visual_data:{type:"number_line",min:s[0],max:s[3],step:.1,marked:s,unknown:o},animation:"pulse_unknown",caption:`Compare ${t[5]} to each labelled mark.`,spoken:`The marks go up in tenths, so we look at the tenths digit of ${nw$4(t[5])} and compare it to each mark.`},{visual_data:{type:"number_line",min:s[0],max:s[3],step:.1,marked:[s[r],s[r+1]],unknown:o},animation:"write_answer",caption:`${t[5]} sits between ${a} and ${i}
71.`,spoken:`So ${nw$4(t[5])} sits just after ${nw$4(a)} and before ${nw$4(i)}. It lies between those two marks.`}]})}};function makeEqualityCompareGenerator(n,t){return{id:n,microSkillId:t,forms:["comparison"],generate(e){if(e.microSkillId!==t)return null;const s=normaliseStem(e.stem);let o,r,a,i;const l=e.visualData;if(l&&l.type==="comparison"&&typeof l.left=="number"&&typeof l.right=="number")o=l.left,r=l.right,a=String(o),i=String(r);else{const m=s.match(/(\d+(?:\.\d+)?)\s*__\s*(\d+(?:\.\d+)?)/);if(!m)return null;a=m[1],i=m[2],o=Number(a),r=Number(i)}if(Math.abs(o-r)>1e-9||dpOf$5(o)>2||dpOf$5(r)>2||!(o>=0&&o<10)||!(r>=0&&r<10)||!/symbol/i.test(s)||e.correctAnswer.trim()!=="=")return null;const d=a!==i?{caption:`${a} is the same as ${i} — a trailing zero adds no value.`,spoken:`Adding a zero on the end of a decimal does not change its value. So ${nw$4(a)} and ${nw$4(i)} are the same amount.`}:{caption:`Every place matches: ${a} and ${i}.`,spoken:"Every column is exactly the same in both decimals: the ones, the tenths, and the hundredths."};return buildExample({generatorId:n,question:e,title:`Which symbol compares ${a} and ${i}?`,computed:{left:o,right:r},steps:[{visual_data:{type:"comparison",left:o,right:r,symbol:null},animation:"show_both",caption:`Compare ${a} and ${i}.`,spoken:"Let's compare these two decimals carefully, place by place."},{visual_data:{type:"comparison",left:o,right:r,symbol:null},animation:"pulse_unknown",caption:d.caption,spoken:d.spoken},{visual_data:{type:"comparison",left:o,right:r,symbol:"="},animation:"write_answer",caption:`${a} = ${i}.`,spoken:`Neither decimal is bigger: ${nw$4(a)} and ${nw$4(i)} are the same amount, so the two are equal.`}]})}}}const NPV_P6_M03_GENERATORS=[makeDecimalCompareGenerator("NPV.P6.M03.compare.v1","NPV.P6.M03"),makeEqualityCompareGenerator("NPV.P6.M03.equal.v1","NPV.P6.M03"),contextCompare,contextHeavier,lastDigitClaim,betweenMarks],nw$3=n=>decimalToWords(String(n)).replace(/^zero point/,"nought point"),round4$2=n=>Math.round(n*1e4)/1e4,dpOf$4=n=>(String(n).split(".")[1]??"").length;function cap$e(n){return n.charAt(0).toUpperCase()+n.slice(1)}function parseM04(n){const t=n.visualData;if(!t||t.type!=="comparison")return null;const e=t.left,s=t.right;if(typeof e!="number"||typeof s!="number")return null;const o=normaliseStem(n.stem);let r;if(/symbol/i.test(o))r="symbol";else if(/larger|greater/i.test(o))r="greater";else if(/smaller/i.test(o))r="smaller";else return null;return{a:e,b:s,aStr:String(e),bStr:String(s),ask:r}}const centsOfM04=n=>Math.round(n*100),wholeOfM04=n=>Math.floor(centsOfM04(n)/100),tenthsOfM04=n=>Math.floor(centsOfM04(n)/10)%10,DIGIT_COUNT_CLAIM=/^(\w+) says (\d+\.\d+) is greater than (\d+\.\d+) because \d+ is greater than \d+\. Is (he|she|they) right\?$/,digitCountClaim={id:"NPV.P6.M04.digit-count-claim.v1",microSkillId:"NPV.P6.M04",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(DIGIT_COUNT_CLAIM);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(o);if(dpOf$4(a)>2||dpOf$4(i)>2||a===i||!(a>=0&&a<10&&i>=0&&i<10))return null;const l=a>i,u=r==="they"?"they're":`${r}'s`,d=l?`Yes, ${u} right`:`No, ${u} wrong`;if(n.correctAnswer.trim()!==d)return null;const m=Math.max(a,i),h=Math.min(a,i),f=tenthsOfM04(m),g=tenthsOfM04(h);return buildExample({generatorId:this.id,question:n,title:`${e} compares ${s} and ${o} by digit count. Right?`,computed:{A:a,B:i,winner:m,loser:h,wT:f,lT:g},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`${e} compares ${s} and ${o} as if they were whole numbers.`,spoken:`${e} looks at the digits as one long number, ignoring the decimal point. Let's check that properly.`},{visual_data:{type:"comparison",left:a,right:i,symbol:m===a?">":"<"},animation:"pulse_unknown highlight_tenths",caption:`Line up the point, then compare tenths: ${f} vs ${g}.`,spoken:"A decimal's size depends on its place value, not how many digits it has. We line up the decimal point and compare the tenths first."},{visual_data:{type:"comparison",left:a,right:i,symbol:m===a?">":"<"},animation:"write_answer highlight_tenths",caption:l?`${e} is right, but more digits does not mean bigger.`:`${e} is wrong: more digits does not mean bigger.`,spoken:l?`${e} happens to be right here, but the reason is wrong — the tenths decide, not the digit count.`:`${e} is wrong. ${cap$e(numberToWords(f))} tenths beats ${numberToWords(g)} tenths — a longer decimal is not always bigger.`}]})}},CONTEXT_WHO_LONGER=/^Two (\w+) record (?:their|its) \w+ \w+: (\d+(?:\.\d+)?) (\w+) and (\d+(?:\.\d+)?) \3\. Who \w+ for longer\?$/,contextWhoLonger={id:"NPV.P6.M04.context-who-longer.v1",microSkillId:"NPV.P6.M04",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(CONTEXT_WHO_LONGER);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(r);
71if(dpOf$4(a)>2||dpOf$4(i)>2||a===i||!(a>0&&a<100&&i>0&&i<100))return null;const l=n.visualData;if(!l||l.type!=="comparison"||Math.abs(l.left-a)>1e-9||Math.abs(l.right-i)>1e-9)return null;const u=Math.max(a,i),d=u===a,m=e.replace(/s$/,""),h=d?s:r,f=d?"first":"second",g=`The ${f} ${m} (${h} ${o})`;if(n.correctAnswer.trim()!==g)return null;const b=Math.min(a,i),x=wholeOfM04(u)!==wholeOfM04(b)?"whole":"tenths",y=x==="whole"?wholeOfM04(u):tenthsOfM04(u),E=x==="whole"?wholeOfM04(b):tenthsOfM04(b);return buildExample({generatorId:this.id,question:n,title:`Who walked longer: ${s} ${o} or ${r} ${o}?`,computed:{A:a,B:i,winner:u,loser:b,wDigit:y,lDigit:E},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`Compare ${s} ${o} and ${r} ${o}.`,spoken:"To see who walked longer, we compare the two times place by place, starting with the biggest column."},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:x==="tenths"?"pulse_unknown highlight_tenths":"pulse_unknown",caption:`The ${x} decides: ${y} vs ${E}.`,spoken:`The ${x} column decides it here. ${cap$e(numberToWords(y))} beats ${numberToWords(E)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:x==="tenths"?"write_answer highlight_tenths":"write_answer",caption:`${g} walked longer.`,spoken:`So ${nw$3(u)} is more than ${nw$3(b)}, and the ${f} ${m} walked longer.`}]})}},ONE_ANOTHER_LONGER=/^One (\w+) is (\d+(?:\.\d+)?) (\w+) long\. Another is (\d+(?:\.\d+)?) \3 long\. Which \1 is longer\?$/,contextOneAnotherLonger={id:"NPV.P6.M04.one-another-longer.v1",microSkillId:"NPV.P6.M04",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(ONE_ANOTHER_LONGER);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(r);if(dpOf$4(a)>2||dpOf$4(i)>2||a===i||!(a>0&&a<100&&i>0&&i<100))return null;const l=n.visualData;if(!l||l.type!=="comparison"||Math.abs(l.left-a)>1e-9||Math.abs(l.right-i)>1e-9)return null;const u=Math.max(a,i),d=u===a?s:r,m=Math.min(a,i),h=`${d} ${o}`;if(n.correctAnswer.trim()!==h)return null;const f=Math.floor(round4$2(u*10))%10,g=Math.floor(round4$2(m*10))%10,b=Math.floor(u),x=Math.floor(m),y=b!==x?"whole number":"tenths",E=y==="whole number"?b:f,k=y==="whole number"?x:g,T=y==="tenths"?"highlight_tenths":b<10&&x<10?"highlight_ones":null;return buildExample({generatorId:this.id,question:n,title:`Which ${e} is longer: ${s} ${o} or ${r} ${o}?`,computed:{A:a,B:i,winner:u,loser:m,wDigit:E,lDigit:k},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`One ${e}: ${s} ${o}. Another: ${r} ${o}.`,spoken:`A decimal with more digits is not always bigger. To see which ${e} is longer, we compare the two lengths place by place.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:`pulse_unknown${T?` ${T}`:""}`,caption:`The ${y} decides: ${E} vs ${k}.`,spoken:`The ${y} column decides it here. ${cap$e(numberToWords(E))} beats ${numberToWords(k)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:`write_answer${T?` ${T}
71`:""}`,caption:`${h} is longer.`,spoken:`So ${nw$3(u)} is more than ${nw$3(m)}, and the ${u===a?"first":"second"} ${e} is longer.`}]})}},WEIGHS_HEAVIER=/^An? (\w+) weighs (\d+(?:\.\d+)?) (\w+)\. An? (\w+) weighs (\d+(?:\.\d+)?) \3\. Which (\w+) is heavier\?$/,contextWeighsHeavier={id:"NPV.P6.M04.weighs-heavier.v1",microSkillId:"NPV.P6.M04",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(WEIGHS_HEAVIER);if(!t)return null;const[,,e,s,,o,r]=t,a=Number(e),i=Number(o);if(dpOf$4(a)>2||dpOf$4(i)>2||a===i||!(a>0&&a<100&&i>0&&i<100))return null;const l=n.visualData;if(!l||l.type!=="comparison"||Math.abs(l.left-a)>1e-9||Math.abs(l.right-i)>1e-9)return null;const u=Math.max(a,i),d=u===a?e:o,m=Math.min(a,i),h=`${d} ${s}`;if(n.correctAnswer.trim()!==h)return null;const f=Math.floor(round4$2(u*10))%10,g=Math.floor(round4$2(m*10))%10,b=Math.floor(u),x=Math.floor(m),y=b!==x?"whole number":"tenths",E=y==="whole number"?b:f,k=y==="whole number"?x:g,T=y==="tenths"?"highlight_tenths":b<10&&x<10?"highlight_ones":null;return buildExample({generatorId:this.id,question:n,title:`Which ${r} is heavier: ${e} ${s} or ${o} ${s}?`,computed:{A:a,B:i,winner:u,loser:m,wDigit:E,lDigit:k},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`One ${r}: ${e} ${s}. Another: ${o} ${s}.`,spoken:"A longer decimal doesn't always mean heavier. We compare the two weights place by place."},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:`pulse_unknown${T?` ${T}`:""}`,caption:`The ${y} decides: ${E} vs ${k}.`,spoken:`The ${y} column decides it here. ${cap$e(numberToWords(E))} beats ${numberToWords(k)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:`write_answer${T?` ${T}
71`:""}`,caption:`${h} is heavier.`,spoken:`So ${nw$3(u)} is more than ${nw$3(m)}, and that ${r} is heavier.`}]})}},MONEY_MORE=/^(\w+) has £(\d+(?:\.\d+)?)\. (\w+) has £(\d+(?:\.\d+)?)\. Who has more money\?$/,contextMoneyMore={id:"NPV.P6.M04.money-more.v1",microSkillId:"NPV.P6.M04",forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(MONEY_MORE);if(!t)return null;const[,e,s,o,r]=t,a=Number(s),i=Number(r);if(dpOf$4(a)>2||dpOf$4(i)>2||a===i||!(a>0&&a<100&&i>0&&i<100))return null;const l=n.visualData;if(!l||l.type!=="comparison"||Math.abs(l.left-a)>1e-9||Math.abs(l.right-i)>1e-9)return null;const u=Math.max(a,i),d=u===a?e:o;if(n.correctAnswer.trim()!==d)return null;const m=Math.min(a,i),h=Math.floor(round4$2(u*10))%10,f=Math.floor(round4$2(m*10))%10,g=Math.floor(u),b=Math.floor(m),x=g!==b?"whole number":"tenths",y=x==="whole number"?g:h,E=x==="whole number"?b:f,k=x==="tenths"?"highlight_tenths":g<10&&b<10?"highlight_ones":null;return buildExample({generatorId:this.id,question:n,title:`${e} has £${s}. ${o} has £${r}. Who has more?`,computed:{A:a,B:i,winner:u,loser:m,wDigit:y,lDigit:E},steps:[{visual_data:{type:"comparison",left:a,right:i,symbol:null},animation:"show_both",caption:`${e}: £${s}. ${o}: £${r}.`,spoken:"More decimal digits doesn't always mean more money. We compare the two amounts place by place."},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:`pulse_unknown${k?` ${k}`:""}`,caption:`The ${x} decides: ${y} vs ${E}.`,spoken:`The ${x} column decides it here. ${cap$e(numberToWords(y))} beats ${numberToWords(E)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:a>i?">":"<"},animation:`write_answer${k?` ${k}`:""}`,caption:`${d} has more.`,spoken:`So ${nw$3(u)} is more than ${nw$3(m)}, and ${d} has more.`}]})}},BETWEEN_MARKS_3=/^A number line is marked at (\d+\.\d+), (\d+\.\d+) and (\d+\.\d+)\. Between which two (?:neighbouring )?marks does (\d+\.\d+) sit\?$/,betweenMarks3={id:"NPV.P6.M04.between-marks.v1",microSkillId:"NPV.P6.M04",forms:["number_line_location"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(BETWEEN_MARKS_3);if(!t)return null;const e=[t[1],t[2],t[3]],s=e.map(Number);for(let u=1;u<s.length;u++)if(s[u]<=s[u-1])return null;const o=Number(t[4]),r=s.findIndex((u,d)=>d<s.length-1&&o>u&&o<s[d+1]);if(r<0)return null;const a=e[r],i=e[r+1],l=`${a} and ${i}`;return n.correctAnswer.trim()!==l?null:buildExample({generatorId:this.id,question:n,title:`Between which two marks does ${t[4]} sit?`,computed:{target:o,lower:s[r],upper:s[r+1]},steps:[{visual_data:{type:"number_line",min:s[0],max:s[2],step:.1,marked:s},animation:"show_scale",caption:`The line is marked at ${e.join(", ")}.`,spoken:"Here is a number line with three labelled marks. To place our decimal, we find the two marks it sits between."},{visual_data:{type:"number_line",min:s[0],max:s[2],step:.1,marked:s,unknown:o},animation:"pulse_unknown",caption:`Compare ${t[4]} to each labelled mark.`,spoken:`The marks go up in tenths, so we look at the tenths digit of ${nw$3(t[4])} and compare it to each mark.`},{visual_data:{type:"number_line",min:s[0],max:s[2],step:.1,marked:[s[r],s[r+1]],unknown:o},animation:"write_answer",caption:`${t[4]} sits between ${a} and ${i}.`,spoken:`So ${nw$3(t[4])} sits just after ${nw$3(a)} and before ${nw$3(i)}. It lies between those two marks.`}]})}};function buildEqualCompareM04(){const n="NPV.P6.M04.equal.v1",t="NPV.P6.M04";return{id:n,microSkillId:t,forms:["comparison"],generate(e){if(e.microSkillId!==t)return null;const s=normaliseStem(e.stem);let o,r,a,i;const l=e.visualData;if(l&&l.type==="comparison"&&typeof l.left=="number"&&typeof l.right=="number")o=l.left,r=l.right,a=String(o),i=String(r);else{const m=s.match(/(\d+(?:\.\d+)?)\s*__\s*(\d+(?:\.\d+)?)/);if(!m)return null;a=m[1],i=m[2],o=Number(a),r=Number(i)}if(Math.abs(o-r)>1e-9||dpOf$4(o)>2||dpOf$4(r)>2||!(o>=0&&o<10)||!(r>=0&&r<10)||!/symbol/i.test(s)||e.correctAnswer.trim()!=="=")return null;const d=a!==i?{caption:`${a} is the same as ${i} — a trailing zero adds no value.`,spoken:`Adding a zero on the end of a decimal does not change its value. So ${nw$3(a)} and ${nw$3(i)} are the same amount.`}:{caption:`Every place matches: ${a} and ${i}.`,spoken:"Every column is exactly the same in both decimals: the ones, the tenths, and the hundredths."};return buildExample({generatorId:n,question:e,title:`Which symbol compares ${a} and ${i}?`,computed:{left:o,right:r},steps:[{visual_data:{type:"comparison",left:o,right:r,symbol:null,left_text:a,right_text:i},animation:"show_both",caption:`Compare ${a} and ${i}.`,spoken:"Let's compare these two decimals carefully, place by place."},{visual_data:{type:"comparison",left:o,right:r,symbol:null,left_text:a,right_text:i},animation:"pulse_unknown",caption:d.caption,spoken:d.spoken},{visual_data:{type:"comparison",left:o,right:r,symbol:"=",left_text:a,right_text:i},animation:"write_answer",caption:`${a} = ${i}.`,spoken:`Neither decimal is bigger: ${nw$3(a)} and ${nw$3(i)} are the same amount, so the two are equal.`}]})}}}const NPV_P6_M04_GENERATORS=[digitCountClaim,contextWhoLonger,contextO
71neAnotherLonger,contextWeighsHeavier,contextMoneyMore,betweenMarks3,buildEqualCompareM04(),{id:"NPV.P6.M04.compare-zoom.v1",microSkillId:"NPV.P6.M04",forms:["comparison","comparison_statement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=parseM04(n);if(!t)return null;const{a:e,b:s,aStr:o,bStr:r,ask:a}=t;if(!(e>0&&e<10&&s>0&&s<10)||dpOf$4(e)>2||dpOf$4(s)>2||e===s||Math.floor(e)!==Math.floor(s))return null;const i=round4$2(Math.floor(10*Math.min(e,s))/10);let l=round4$2(Math.ceil(10*Math.max(e,s))/10);l-i<.2&&(l=round4$2(i+.2));const d=round4$2(l-i)>.2?.1:.01,m=d===.1?.05:.01;if(Math.abs(e-s)<m)return null;const h=e>s?e:s,f=e>s?s:e,g=e>s?o:r,b=e>s?r:o,x=a==="smaller";let y;if(a==="symbol"){const V=e<s?"<":">";if(n.correctAnswer.trim()!==V)return null;y=h}else{const V=x?b:g;if(n.correctAnswer.trim()!==V)return null;y=x?f:h}const E=x?f:h,k=Math.floor(round4$2(h*10))%10,T=Math.floor(round4$2(f*10))%10,A=Math.round(h*100)%10,N=Math.round(f*100)%10,S=k===T,M=V=>V===1?"tenth":"tenths",L=V=>V===1?"hundredth":"hundredths",P=V=>decimalToWords(V).replace(/^zero point/,"nought point"),F=(V={})=>({type:"number_line",min:i,max:l,step:d,marked:[],...V}),D=x?S?`Tenths tie — hundredths decide: ${N} is less than ${A}.`:`Tenths decide: ${T} is less than ${k}.`:S?`Tenths tie — hundredths decide: ${A} beats ${N}.`:`Tenths decide: ${k} beats ${T}.`,U=a==="symbol"?`${o} ${e<s?"<":">"} ${r}.`:D,q=x?S?`The tenths are equal, so the hundredths decide. ${cap$e(numberToWords(N))} ${L(N)} is less than ${numberToWords(A)} ${L(A)}, so ${P(b)} is smaller.`:`Tenths are worth more than hundredths, so the tenths decide it. ${cap$e(numberToWords(T))} ${M(T)} is less than ${numberToWords(k)} ${M(k)}, so ${P(b)} is smaller — a longer decimal is not always bigger.`:S?`The tenths are equal, so the hundredths decide. ${cap$e(numberToWords(A))} ${L(A)} beats ${numberToWords(N)} ${L(N)}, so ${P(g)} is greater.`:`Tenths are worth more than hundredths, so the tenths decide it. ${cap$e(numberToWords(k))} ${M(k)} is more than ${numberToWords(T)} ${M(T)}, so ${P(g)} is greater — a longer decimal is not always bigger.`;return buildExample({generatorId:this.id,question:n,title:x?`Which is smaller: ${o} or ${r}?`:`Which is greater: ${o} or ${r}?`,computed:{left:e,right:s,winner:h,wTenth:k,lTenth:T,wHund:A,lHund:N,answer:y},steps:[{visual_data:F({marked:[e,s]}),animation:"show_start",caption:`Place ${o} and ${r} on the line.`,spoken:x?"To compare two decimals, we can place them on a number line, where numbers get bigger as we go right. Here is a close-up of the number line with both decimals on it. Let's see which one sits further to the left.":"To compare two decimals, we can place them on a number line, where numbers get bigger as we go right. Here is a close-up of the number line with both decimals on it. Let's see which one sits further to the right."},{visual_data:F({marked:[e,s],hops:[x?{from:h,to:f,label:"further left"}:{from:f,to:h,label:"further right"}]}),animation:"compare_place",caption:x?"Further left means smaller.":"Further right means greater.",spoken:x?`${cap$e(P(b))} sits further to the left than ${P(g)}, and further left always means smaller. Let's check with the digits.`:`${cap$e(P(g))} sits further to the right than ${P(b)}, and further right always means greater. Let's check with the digits.`},{visual_data:F({marked:[e,s],hops:[x?{from:h,to:f,label:""}:{from:f,to:h,label:""}],unknown:E}),animation:"land",caption:U,spoken:q}]})}}],ALLOWED_FACTORS=new Set([2,3,4,5,10]),MAX_SCALED_AMOUNT=2e3,ITEM_EMOJI={flour:"🌾",sugar:"🍬",butter:"🧈",milk:"🥛",rice:"🍚",egg:"🥚",eggs:"🥚",oats:"🌾",potatoes:"🥔",cheese:"🧀",carrots:"🥕",mince:"🥩",beans:"🫘",pasta:"🍝",squash:"🧃",juice:"🧃"};function emojiFor(n){return ITEM_EMOJI[n.toLowerCase()]??"🍽️"}function amountLabel(n,t){return t?`${n}${t}`:`${n}`}function amountWords(n,t,e){return t==="g"?`${numberToWordsLong(n)} grams of ${e}`:t==="ml"?`${numberToWordsLong(n)} millilitres of ${e}`:`${numberToWordsLong(n)} ${e}`}function factorFor(n,t){const e=t/n;return Number.isInteger(e)&&ALLOWED_FACTORS.has(e)?e:null}function buildScaleExample(n){const{generatorId:t,question:e,A:s,B:o,N:r,unit:a,item:i,factor:l,answerValue:u,ask:d}=n,m=r*l,h=emojiFor(i),f=a===void 0;if(f&&m>60)return null;const g=f?r:1,b={type:"objects_grid",emoji:h,count:g,group_size:g,hide_subtotals:!0},x={type:"objects_grid",emoji:h,count:l*g,group_size:g,hide_subtotals:!0},y=`${o} ÷ ${s} = ${l} batches.`,E=`${capitalised(numberToWordsLong(o))}
71 divided by ${numberToWordsLong(s)} is ${numberToWordsLong(l)}. So we need ${numberToWordsLong(l)} batches.`,k=Math.floor(r/100)*100,T=r-k,A=r>=100&&T>0,N=k*l,S=T*l;let M,L;d==="factor"?(M=`From ${s} to ${o}: multiply by ${l}.`,L=`To go from ${numberToWordsLong(s)} to ${numberToWordsLong(o)}, we multiply by ${numberToWordsLong(l)}. So the number we multiply by is ${numberToWordsLong(l)}.`):d==="expression"?(M=`The right calculation is ${r} × ${l}.`,L=`${capitalised(numberToWordsLong(r))} multiplied by ${numberToWordsLong(l)} — that is the calculation that scales the recipe correctly.`):A?(M=`${amountLabel(r,a)} × ${l} = ${k}×${l} + ${T}×${l} = ${amountLabel(m,a)} ${i}.`,L=`${capitalised(amountWords(r,a,i))}, ${numberToWordsLong(l)} times over: ${numberToWordsLong(k)} lots of ${numberToWordsLong(l)} is ${numberToWordsLong(N)}, and ${numberToWordsLong(T)} lots of ${numberToWordsLong(l)} is ${numberToWordsLong(S)}. Together that is ${amountWords(m,a,i)}.`):(M=`${amountLabel(r,a)} × ${l} = ${amountLabel(m,a)} ${i}.`,L=`${capitalised(amountWords(r,a,i))}, ${numberToWordsLong(l)} times over, is ${amountWords(m,a,i)}.`);const P=d==="factor"?`× ${l}`:d==="expression"?`${r} × ${l}`:`${amountLabel(m,a)} ${i}`,F={...x,total_label:P};return buildExample({generatorId:t,question:e,title:`How much ${i} for ${o} instead of ${s}?`,computed:{A:s,B:o,N:r,factor:l,total:m,answer:u,hundreds:k,remainder:T,hundredsProduct:N,remainderProduct:S},steps:[{visual_data:b,animation:"grow",caption:`One batch needs ${amountLabel(r,a)} ${i}.`,spoken:`One batch of the recipe needs ${amountWords(r,a,i)}. Here it is.`},{visual_data:x,animation:"grow",caption:y,spoken:E},{visual_data:F,animation:"total",caption:M,spoken:L}]})}const T1=/^A (?:recipe|smoothie) for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. how (?:much|many) (?:(?:grams|millilitres|g|ml) of )?\4 do you need for (\d+) [a-z ]+?\??$/i,T2=/^A recipe for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. a baker makes enough for (\d+) [a-z ]+?\. how (?:much|many) \4 does the baker use\??$/i,T5=/^A jug holds enough ([a-z]+) for (\d+) cups\. each batch uses (\d+)\s*(g|ml)?\s*(?:of\s+)?\1\. how much \1 do you need for (\d+) cups\??$/i,T6=/^(\d+)\s*(g|ml)? of ([a-z]+) makes (\d+) [a-z]+\. how much \3 makes (\d+) [a-z]+\??$/i,T7=/^(\d+)\s*(g|ml)? of ([a-z]+) makes a batch of [a-z]+ for (\d+) [a-z ]+?\. how much \3 do you need for (\d+) [a-z ]+?\??$/i,T8=/^(\d+)\s*(g|ml)? of ([a-z]+) makes enough [a-z]+ for (\d+) [a-z ]+?\. how much \3 do you need for (\d+) [a-z ]+?\??$/i,T9=/^(\d+)\s*(g|ml)? of ([a-z]+) serves (\d+) [a-z ]+?\. how much \3 serves (\d+) [a-z ]+?\??$/i,T13=/^(\d+)\s*(g|ml)? of ([a-z]+) makes (\d+) [a-z]+\. how (?:much|many) (?:(?:grams|millilitres|g|ml) of )?\3 do you need for (\d+) [a-z]+\??$/i,forwardAmount={id:"RP.P1.M03.scale-amount.v1",microSkillId:"RP.P1.M03",forms:["context_calculation","varied_symbolic_calculation"],generate(n){const t=normaliseStem(n.stem);let e,s,o,r,a,i=t.match(T1);if(i)e=Number(i[1]),s=Number(i[2]),o=i[3],r=i[4],a=Number(i[5]);else if(i=t.match(T2))e=Number(i[1]),s=Number(i[2]),o=i[3],r=i[4],a=Number(i[5]);else if(i=t.match(T5))r=i[1],e=Number(i[2]),s=Number(i[3]),o=i[4],a=Number(i[5]);else if(i=t.match(T6))s=Number(i[1]),o=i[2],r=i[3],e=Number(i[4]),a=Number(i[5]);else if(i=t.match(T7))s=Number(i[1]),o=i[2],r=i[3],e=Number(i[4]),a=Number(i[5]);else if(i=t.match(T8))s=Number(i[1]),o=i[2],r=i[3],e=Number(i[4]),a=Number(i[5]);else if(i=t.match(T9))s=Number(i[1]),o=i[2],r=i[3],e=Number(i[4]),a=Number(i[5]);else if(i=t.match(T13))s=Number(i[1]),o=i[2],r=i[3],e=Number(i[4]),a=Number(i[5]);else return null;const l=factorFor(e,a);if(l===null)return null;const u=s*l;if(u>MAX_SCALED_AMOUNT)return null;const d=Number(n.correctAnswer);return!Number.isFinite(d)||d!==u?null:buildScaleExample({generatorId:this.id,question:n,A:e,B:a,N:s,unit:o,item:r,factor:l,answerValue:u,ask:"amount"})}},unitaryScaleAmount={id:"RP.P1.M03.unitary-scale-amount.v1",microSkillId:"RP.P1.M03",forms:["context_calculation","varied_symbolic_calculation"],generate(n){const e=normaliseStem(n.stem).match(T1);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]);if(factorFor(s,i)!==null||i<=s||s<=0||o%s!==0)return null;const l=o/s,u=l*i;if(u<=0||u>MAX_SCALED_AMOUNT)return null;const d=Number(n.correctAnswer);if(!Number.isFinite(d)||d!==u)return null;const m=emojiFor(a),h={type:"objects_grid",emoji:m,count:1,group_size:1,hide_subtotals:!0},f={type:"objects_grid",emoji:m,count:s,group_size:1,chosen:[0],total_label:`${amountLabel(l,r)} per person`},g={type:"objects_grid",emoji:m,count:i,group_size:1,hide_subtotals:!0,total_label:`${amountLabel(u,r)} ${a}`};return buildExample({generatorId:this.id,question:n,title:`How much ${a} for ${i} instead of ${s}?`,computed:{A:s,B:i,N:o,perUnit:l,total:u,answer:u},steps:[{visual_data:h,animation:"grow",caption:`For ${s} people: ${amountLabel(o,r)} ${a}.`,spoken:`The whole recipe serves ${numberToWordsLong(s)} people and needs ${amountWords(o,r,a)}.`},{visual_data:f,animation:"grow",caption:`${amountLabel(o,r)} ÷ ${s} = ${amountLabel(l,r)} each.`,spoken:`${capitalised(amountWords(o,r,a))} shared between ${numberToWordsLong(s)} people is ${amountWords(l,r,a)} for one person.`},{visual_data:g,animation:"total",caption:`${amountLabel(l,r)} × ${i} = ${amountLabel(u,r)} ${a}.`,spoken:`${capitalised(amountWords(l,r,a))}, for each of ${numberToWordsLong(i)} people, is ${amountWords(u,r,a)}.`}]})}},T3=/^A recipe for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. to make (\d+) [a-z ]+? you multiply (\d+)\s*(?:g|ml)? by what number\??$/i,T4=/^A recipe for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. to make enough for (\d+) [a-z ]+? you multiply by what number\??$/i,forwardFactor={id:"RP.P1.M03.scale-factor.v1",microSkillId:"RP.P1.M03",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem);let e,s,o,r,a,i=t.match(T3);if(i){if(e=Number(i[1]),s=Number(i[2]),o=i[3],r=i[4],a=Number(i[5]),Number(i[6])!==s)return null}else if(i=t.match(T4))e=Number(i[1]),s=Number(i[2]),o=i[3],r=i[4],a=Number(i[5]);else return null;const l=factorFor(e,a);if(l===null||s*l>MAX_SCALED_AMOUNT)return null;const u=Number(n.correctAnswer);return!Number.isFinite(u)||u!==l?null:buildScaleExample({generatorId:this.id,question:n,A:e,B:a,N:s,unit:o,item:r,factor:l,answerValue:l,ask:"factor"})}},T10=/^A recipe for (\d+) [a-z]+ uses (\d+)\s*(g|ml)? of ([a-z]+) and (\d+)\s*(g|ml)? of ([a-z]+)\. how much of each do you need for (\d+) [a-z]+\??$/i,twoIngredientEach={id:"RP.P1.M03.two-ingredient-each.v1",microSkillId:"RP.P1.M03",forms:["context_reasoning"],generate(n){const e=normaliseStem(n.stem).match(T10);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]),l=e[6],u=e[7],d=Number(e[8]),m=factorFor(s,d);if(m===null)return null;const h=o*m,f=i*m;if(h>MAX_SCALED_AMOUNT||f>MAX_SCALED_AMOUNT)return null;const g=`${amountLabel(h,r)} ${a}, ${amountLabel(f,l)} ${u}`;if(n.correctAnswer.trim()!==g)return null;const b=buildScaleExample({generatorId:this.id,question:n,A:s,B:d,N:o,unit:r,item:a,factor:m,answerValue:h,ask:"amount"});if(!b)return null;const x=b.steps[2],y=emojiFor(u);return x.visual_data={type:"objects_grid",emoji:y,count:m,group_size:1,hide_subtotals:!0,total_label:`${amountLabel(f,l)} ${u}`},x.caption=`${amountLabel(i,l)} × ${m} = ${amountLabel(f,l)} ${u}; ${a} needs ${amountLabel(h,r)} too.`,x.spoken=`${capitalised(amountWords(i,l,u))}, ${numberToWordsLong(m)} times over, is ${amountWords(f,l,u)}. ${capitalised(a)} needs ${amountWords(h,r,a)} too, scaled the same way.`,b.provenance.computed.total2=f,b}},T11=/^A recipe for (\d+) [a-z]+ uses (\d+)\s*(g|ml)? of ([a-z]+) and (\d+)\s*(g|ml)? of ([a-z]+)\. the recipe is (doubled|tripled) for (\d+) [a-z]+\. how much ([a-z]+) do you need\??$/i,twoIngredientTarget={id:"RP.P1.M03.two-ingredient-target.v1",microSkillId:"RP.P1.M03",forms:["context_reasoning"],generate(n){const e=normaliseStem(n.stem).match(T11);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]),l=e[6],u=e[7],d=e[8],m=Number(e[9]),h=e[10],f=factorFor(s,m);
71if(f===null||d==="doubled"&&f!==2||d==="tripled"&&f!==3)return null;let g,b,x;if(h===a)g=o,b=r,x=a;else if(h===u)g=i,b=l,x=u;else return null;const y=g*f;return y>MAX_SCALED_AMOUNT||n.correctAnswer.trim()!==amountLabel(y,b)?null:buildScaleExample({generatorId:this.id,question:n,A:s,B:m,N:g,unit:b,item:x,factor:f,answerValue:y,ask:"amount"})}},T12=/^A recipe for (\d+) [a-z]+ needs (\d+)\s*(g|ml)? of ([a-z]+)\. which calculation scales it correctly for (\d+) [a-z]+\??$/i,whichCalculation={id:"RP.P1.M03.which-calculation.v1",microSkillId:"RP.P1.M03",forms:["context_reasoning"],generate(n){const e=normaliseStem(n.stem).match(T12);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]),l=factorFor(s,i);if(l===null||o*l>MAX_SCALED_AMOUNT)return null;const u=`${o} × ${l}`;return n.correctAnswer.trim()!==u?null:buildScaleExample({generatorId:this.id,question:n,A:s,B:i,N:o,unit:r,item:a,factor:l,answerValue:l,ask:"expression"})}},MAX_PEOPLE=40,INVERSE_PEOPLE=/^A recipe for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. [a-z ]+ needs (\d+)\s*(?:g|ml)? of \4\. how many [a-z ]+? is that recipe for\??$/i,inversePeopleFromAmount={id:"RP.P1.M03.inverse-people-from-amount.v1",microSkillId:"RP.P1.M03",forms:["context_reasoning"],generate(n){const e=normaliseStem(n.stem).match(INVERSE_PEOPLE);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]);if(o<=0||i<=0||i%o!==0)return null;const l=i/o;if(l<2)return null;const u=s*l;if(u>MAX_PEOPLE)return null;const d=Number(n.correctAnswer);if(!Number.isFinite(d)||d!==u)return null;const m=emojiFor(a),h={type:"objects_grid",emoji:m,count:1,group_size:1,hide_subtotals:!0},f={type:"objects_grid",emoji:m,count:l,group_size:1,hide_subtotals:!0,total_label:`${amountLabel(i,r)} ${a}`},g={type:"objects_grid",emoji:"🧑",count:u,group_size:s,total_label:`${u} people`};return buildExample({generatorId:this.id,question:n,title:`${amountLabel(i,r)} of ${a} — how many people does that serve?`,computed:{A:s,N:o,target:i,batches:l,peopleTotal:u,answer:u},steps:[{visual_data:h,animation:"grow",caption:`One batch: ${amountLabel(o,r)} ${a}, for ${s} people.`,spoken:`One batch of the recipe needs ${amountWords(o,r,a)} and serves ${numberToWordsLong(s)} people.`},{visual_data:f,animation:"grow",caption:`${amountLabel(i,r)} ÷ ${amountLabel(o,r)} = ${l} batches.`,spoken:`${capitalised(amountWords(i,r,a))} divided by ${amountWords(o,r,a)} is ${numberToWordsLong(l)} batches.`},{visual_data:g,animation:"total",caption:`${l} batches × ${s} people = ${u} people.`,spoken:`${capitalised(numberToWordsLong(l))} batches, ${numberToWordsLong(s)} people each, is ${numberToWordsLong(u)} people in total.`}]})}};function buildHalveExample(n,t,e){const{A:s,B:o,N:r,unit:a,item:i,half:l}=e,u={type:"fraction_shape",shape:"rectangle",total_parts:2,shaded_parts:2,color:"#3b82f6"},d={...u,show_part_numbers:!0},m={type:"fraction_shape",shape:"rectangle",total_parts:2,shaded_parts:1,color:"#3b82f6",conclude_label:`${amountLabel(l,a)} ${i}`};return buildExample({generatorId:n,question:t,title:`How much ${i} for ${o} instead of ${s}?`,computed:{A:s,B:o,N:r,half:l,answer:l},steps:[{visual_data:u,animation:"grow",caption:`For ${s} people: ${amountLabel(r,a)} ${i}.`,spoken:`One full recipe serves ${numberToWordsLong(s)} people and needs ${amountWords(r,a,i)}.`},{visual_data:d,animation:"grow",caption:`${o} people is half of ${s}: cut it into 2 equal parts.`,spoken:`${capitalised(numberToWordsLong(o))} people is half of ${numberToWordsLong(s)}. So we cut the ${i} into two equal halves and keep one of them.`},{visual_data:m,animation:"total",caption:`Half of ${amountLabel(r,a)} is ${amountLabel(l,a)}.`,spoken:`Half of ${amountWords(r,a,i)} is ${amountWords(l,a,i)}. So for ${numberToWordsLong(o)} people you need ${amountWords(l,a,i)}.`}]})}const halveGenerator={id:"RP.P1.M03.halve.v1",microSkillId:"RP.P1.M03",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(T1);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]);if(s!==2*i||o%2!==0)return null;const l=o/2,u=Number(n.correctAnswer);return!Number.isFinite(u)||u!==l?null:buildHalveExample(this.id,n,{A:s,B:i,N:o,unit:r,item:a,half:l})}},tableScaleGenerator={id:"RP.P1.M03.table-scale.v1",microSkillId:"RP.P1.M03",forms:["context_reasoning"],generate(n){const t=n.visualData;if(!t||t.type!=="two_way_table")return null;const e=normaliseStem(n.stem).toLowerCase(),s=t.col_headers.map(f=>{const g=String(f).match(/\d+/);return g?Number(g[0]):null});if(s.length!==2||s[0]===null||s[1]===null)return null;const o=s[0],r=s[1],a=[];
71if(t.row_headers.forEach((f,g)=>{const b=t.cells[g];!Array.isArray(b)||b.length!==2||e.includes(f.toLowerCase())&&typeof b[0]=="number"&&b[1]===null&&a.push({item:f.toLowerCase(),known:b[0]})}),a.length!==1)return null;const{item:i,known:l}=a[0],u=factorFor(o,r);if(u===null||/\bdoubled\b/.test(e)&&u!==2||/\btripled\b/.test(e)&&u!==3)return null;const d=l*u;if(d>MAX_SCALED_AMOUNT)return null;const m=n.correctAnswer.trim().match(/^(\d+)\s*(g|ml)?$/i);if(!m||Number(m[1])!==d)return null;const h=m[2]?m[2].toLowerCase():void 0;return buildScaleExample({generatorId:this.id,question:n,A:o,B:r,N:l,unit:h,item:i,factor:u,answerValue:d,ask:"amount"})}},CLAIM_SCALE=/^A recipe for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. .+ scales it up for (\d+) [a-z ]+? and says (?:he|she|they) needs? (\d+)\s*(?:g|ml)? of \4\. Is .+ correct\?$/i,claimScale={id:"RP.P1.M03.claim-scale.v1",microSkillId:"RP.P1.M03",forms:["context_reasoning"],generate(n){const e=normaliseStem(n.stem).match(CLAIM_SCALE);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]),l=Number(e[6]),u=factorFor(s,i);if(u===null)return null;const d=o*u;if(d>MAX_SCALED_AMOUNT)return null;const m=n.correctAnswer.trim();if(!/^(yes|no)$/i.test(m))return null;const h=/^yes$/i.test(m),f=d===l;if(f!==h)return null;const g=emojiFor(a),b={type:"objects_grid",emoji:g,count:1,group_size:1,hide_subtotals:!0},x={type:"objects_grid",emoji:g,count:u,group_size:1,hide_subtotals:!0},y={...x,total_label:`${amountLabel(d,r)} ${a}`},E=o>0?l/o:NaN,k=!f&&Number.isInteger(E)&&E>0&&E!==u;return buildExample({generatorId:this.id,question:n,title:`Is ${amountLabel(l,r)} of ${a} right for ${i} instead of ${s}?`,computed:{A:s,B:i,N:o,factor:u,total:d,claimed:l,wrongFactor:k?E:0},steps:[{visual_data:b,animation:"grow",caption:`One batch needs ${amountLabel(o,r)} ${a}.`,spoken:`One batch of the recipe needs ${amountWords(o,r,a)}. Here it is.`},{visual_data:x,animation:"grow",caption:`${i} ÷ ${s} = ${u} batches.`,spoken:`${capitalised(numberToWordsLong(i))} divided by ${numberToWordsLong(s)} is ${numberToWordsLong(u)}. So we need ${numberToWordsLong(u)} batches.`},{visual_data:y,animation:"total",caption:f?`${amountLabel(o,r)} × ${u} = ${amountLabel(d,r)} — the claim is right.`:k?`${amountLabel(o,r)} × ${u} = ${amountLabel(d,r)}, not ${amountLabel(l,r)} (that's ×${E}).`:`${amountLabel(o,r)} × ${u} = ${amountLabel(d,r)}, not ${amountLabel(l,r)}.`,spoken:f?`${capitalised(amountWords(o,r,a))}, ${numberToWordsLong(u)} times over, is ${amountWords(d,r,a)} — that matches the claim, so it is correct.`:k?`${capitalised(amountWords(o,r,a))}, ${numberToWordsLong(u)} times over, is ${amountWords(d,r,a)}, not ${amountWords(l,r,a)}. That wrong number comes from multiplying by ${numberToWordsLong(E)} instead of ${numberToWordsLong(u)}, so the claim is wrong.`:`${capitalised(amountWords(o,r,a))}, ${numberToWordsLong(u)} times over, is ${amountWords(d,r,a)}, not ${amountWords(l,r,a)} — so the claim is wrong.`}]})}},ONE_BATCH_FACTOR=/^A recipe uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+) for one batch\. It is scaled up to use (\d+)\s*(?:g|ml)? of \3\. What is the scale factor\?$/i,oneBatchFactor={id:"RP.P1.M03.one-batch-factor.v1",microSkillId:"RP.P1.M03",forms:["symbolic_numeric_entry"],generate(n){const e=normaliseStem(n.stem).match(ONE_BATCH_FACTOR);if(!e)return null;const s=Number(e[1]),o=e[2],r=e[3],a=Number(e[4]);if(s<=0||a<=0)return null;const i=a/s;if(!Number.isInteger(i)||i<2||i>12)return null;const l=Number(n.correctAnswer);if(!Number.isFinite(l)||l!==i)return null;const u=emojiFor(r),d={type:"objects_grid",emoji:u,count:1,group_size:1,hide_subtotals:!0},m={type:"objects_grid",emoji:u,count:1,group_size:1,chosen:[0],total_label:amountLabel(a,o)},h={type:"objects_grid",emoji:u,count:i,group_size:1,hide_subtotals:!0,total_label:`× ${i}`};return buildExample({generatorId:this.id,question:n,title:`${amountLabel(s,o)} scaled to ${amountLabel(a,o)} — what is the factor?`,computed:{N:s,total:a,factor:i,answer:i},steps:[{visual_data:d,animation:"grow",caption:`One batch: ${amountLabel(s,o)} ${r}.`,spoken:`One batch of the recipe needs ${amountWords(s,o,r)}.`},{visual_data:m,animation:"grow",caption:`It is scaled up to ${amountLabel(a,o)}.`,spoken:`It is scaled up to ${amountWords(a,o,r)} instead.`},{visual_data:h,animation:"total",caption:`${amountLabel(a,o)} ÷ ${amountLabel(s,o)} = ${i}.`,spoken:`${capitalised(numberToWordsLong(a))} is ${numberToWordsLong(i)} times ${numberToWordsLong(s)}. So the scale factor is ${numberToWordsLong(i)}.`}]})}},TWO_RECIPE_FACTOR_COMPARE=/^Recipe A uses (\d+)\s*(g|ml)? of ([a-z]+) and is scaled up by a factor of (\d+)\. Recipe B uses (\d+)\s*(?:g|ml)? of \3 and is scaled up by a factor of (\d+)\. Which recipe uses more \3 after scaling\?$/i,twoRecipeFactorCompare={id:"RP.P1.M03.two-recipe
71-factor-compare.v1",microSkillId:"RP.P1.M03",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(TWO_RECIPE_FACTOR_COMPARE);if(!e)return null;const s=Number(e[1]),o=e[2],r=e[3],a=Number(e[4]),i=Number(e[5]),l=Number(e[6]);if([s,a,i,l].some(k=>k<1||k>999))return null;const u=s*a,d=i*l;if(u===d)return null;const m=u>d?"Recipe A":"Recipe B";if(n.correctAnswer.trim()!==m||a+l>20)return null;const f=emojiFor(r),g={type:"objects_grid",emoji:f,count:a,group_size:1,hide_subtotals:!0,total_label:`${amountLabel(u,o)}`},b={type:"objects_grid",emoji:f,count:l,group_size:1,hide_subtotals:!0,total_label:`${amountLabel(d,o)}`},y=u>d?Array.from({length:a},(k,T)=>T):Array.from({length:l},(k,T)=>a+T),E={type:"objects_grid",emoji:f,count:a+l,group_size:1,chosen:y,total_label:`A ${amountLabel(u,o)}, B ${amountLabel(d,o)}`};return buildExample({generatorId:this.id,question:n,title:`Recipe A × ${a} vs Recipe B × ${l}: which uses more ${r}?`,computed:{N1:s,f1:a,product1:u,N2:i,f2:l,product2:d},steps:[{visual_data:g,animation:"grow",caption:`Recipe A: ${amountLabel(s,o)} × ${a} = ${amountLabel(u,o)}.`,spoken:`To compare fairly, we scale each recipe first. Recipe A needs ${amountWords(s,o,r)}, scaled up ${numberToWordsLong(a)} times: ${amountWords(u,o,r)}.`},{visual_data:b,animation:"grow",caption:`Recipe B: ${amountLabel(i,o)} × ${l} = ${amountLabel(d,o)}.`,spoken:`Recipe B needs ${amountWords(i,o,r)}, scaled up ${numberToWordsLong(l)} times: ${amountWords(d,o,r)}.`},{visual_data:E,animation:"total",caption:`A ${amountLabel(u,o)} vs B ${amountLabel(d,o)} — ${m} uses more.`,spoken:`Now we compare the scaled amounts. ${capitalised(amountWords(Math.max(u,d),o,r))} is more than ${amountWords(Math.min(u,d),o,r)}, so ${m} uses more after scaling.`}]})}},SCALE_DOWN_GENERAL=/^A recipe for (\d+) [a-z ]+? uses (\d+)\s*(g|ml)?\s*(?:of\s+)?([a-z]+)\. It is scaled down to serve (\d+) [a-z ]+?\. How much \4 is needed now\??$/i;function buildScaleDownExample(n,t,e){const{A:s,B:o,N:r,unit:a,item:i,divisor:l,amount:u}=e,d=emojiFor(i),m={type:"objects_grid",emoji:d,count:1,group_size:1,hide_subtotals:!0},h={type:"objects_grid",emoji:d,count:l,group_size:1,chosen:[],total_label:""},f={type:"objects_grid",emoji:d,count:l,group_size:1,chosen:[0],total_label:`${amountLabel(u,a)} ${i}`};return buildExample({generatorId:n,question:t,title:`How much ${i} for ${o} instead of ${s}?`,computed:{A:s,B:o,N:r,divisor:l,amount:u,answer:u},steps:[{visual_data:m,animation:"grow",caption:`For ${s} people: ${amountLabel(r,a)} ${i}.`,spoken:`One full recipe serves ${numberToWordsLong(s)} people and needs ${amountWords(r,a,i)}.`},{visual_data:h,animation:"grow",caption:`${s} ÷ ${o} = ${l} equal parts.`,spoken:`We need a recipe for fewer people, so we find how many times smaller it is. ${capitalised(numberToWordsLong(s))} divided by ${numberToWordsLong(o)} is ${numberToWordsLong(l)}. So we split the ${i} into ${numberToWordsLong(l)} equal parts.`},{visual_data:f,animation:"total",caption:`${amountLabel(r,a)} ÷ ${l} = ${amountLabel(u,a)}.`,spoken:`${capitalised(amountWords(r,a,i))} shared into ${numberToWordsLong(l)} equal parts is ${amountWords(u,a,i)}. So for ${numberToWordsLong(o)} people you need ${amountWords(u,a,i)}.`}]})}const scaleDownGeneral={id:"RP.P1.M03.scale-down.v1",microSkillId:"RP.P1.M03",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(SCALE_DOWN_GENERAL);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=e[4],i=Number(e[5]);if(i>=s||i<=0)return null;const l=s/i;if(!Number.isInteger(l)||l<2||o%l!==0)return null;const u=o/l;if(u<=0)return null;const d=Number(n.correctAnswer);return!Number.isFinite(d)||d!==u?null:buildScaleDownExample(this.id,n,{A:s,B:i,N:o,unit:r,item:a,divisor:l,amount:u})}},RP_P1_M03_GENERATORS=[forwardAmount,unitaryScaleAmount,forwardFactor,twoIngredientEach,twoIngredientTarget,whichCalculation,inversePeopleFromAmount,halveGenerator,tableScaleGenerator,claimScale,oneBatchFactor,twoRecipeFactorCompare,scaleDownGeneral],round2$1=n=>Math.round(n*100)/100,round4$1=n=>Math.round(n*1e4)/1e4,fmt$3=n=>String(round2$1(n)),dpOf$3=n=>{const t=String(round4$1(n)),e=t.indexOf(".");return e<0?0:t.length-e-1},ASC_RE$1=/\b(?:smallest to largest|shortest to longest|lightest to heaviest|fastest to slowest)\b/i,DESC_RE$1=/\b(?:largest to smallest|longest to shortest|heaviest to lightest|slowest to fastest)\b/i,ASC_BRACKET_RE=/\(smallest\)[\s\S]*?\(largest\)/i,DESC_BRACKET_RE=/\(largest\)[\s\S]*?\(smallest\)/i;function parseDirection(n){const t=normaliseStem(n),e=ASC_RE$1.test(t)||ASC_BRACKET_RE.test(t),s=DESC_RE$1.test(t)||DESC_BRACKET_RE.test(t);return e&&!s?"asc":s&&!e?"desc":null}const tenthsDigitOf=n=>Math.floor(n*10+1e-6)%10;function findMixedLengthPair(n){for(let t=0;t<n.length-1;t++)if(dpOf$3(n[t])!==dpOf$3(n[t+1]))return[n[t],n[t+1]];return null}function numbersFrom(n){if(!n||n.type!=="drag_order")return null;const t=n.numbers;if(!Array.isArray(t)||t.length<2||t.length>4)return null;const e=t.filter(s=>typeof s=="number"&&Number.isFinite(s));return e.length!==t.length?null:e.map(round4$1)}function makeOrderingGenerator(n,t){return{id:n,microSkillId:t,forms:["comparison_statement"],generate(e){const s=numbersFrom(e.visualData);if(!s)return null;const o=parseDirection(e.stem);if(!o||(e.visualData?.type==="drag_order"?e.visualData.direction:void 0)!==o||s.some(V=>dpOf$3(V)>2)||s.some(V=>!(V>=0&&V<100)))return null;const a=s.map(V=>Math.round(V*100));
71if(new Set(a).size!==s.length)return null;const i=[...s].sort((V,O)=>o==="asc"?V-O:O-V);if(i.map(fmt$3).join(",")!==e.correctAnswer.trim())return null;const u=round4$1(Math.floor(10*Math.min(...s))/10),d=round4$1(Math.ceil(10*Math.max(...s))/10),m=round4$1(d-u);if(m<=0)return null;const h=m<=.2?.01:.1;if(Math.round(m/h)+1>21)return null;const g=(V={})=>({type:"number_line",min:u,max:d,step:h,marked:[],...V}),b=s[0],x=i[0],y=i.map(fmt$3),E=t==="NPV.P6.M04",k=V=>E?decimalToWords(V).replace(/^zero point/,"nought point"):decimalToWords(V),T=i.map(V=>k(fmt$3(V))).join(", "),A={windowMin:u,windowMax:d,step:h,first:b,readStart:x,answer:x};i.forEach((V,O)=>{A[`v${O}`]=V});const N=o==="asc"?"smallest":"largest",S=o==="asc"?`Read left to right: ${y.join(", ")}`:`Read right to left: ${y.join(", ")}`;let M="Now place every decimal on the line.",L="Now every decimal is on the line. Where each one sits shows how big it is — further to the right means larger.";if(t==="NPV.P6.M04"){const V=findMixedLengthPair(i);if(V){const[O,z]=V,K=fmt$3(O),Z=fmt$3(z),Q=tenthsDigitOf(O),B=tenthsDigitOf(z),G=Math.floor(O),X=Math.floor(z);if(O>z||G>0||X>0||Q===B){const ne=Math.round(O*100)%10,se=Math.round(z*100)%10,te=(ke,Te,Se)=>`${Se?numberToWordsLong(ke):ke} ${Te}${ke===1?"":"s"}`,oe=(ke,Te,Se)=>`${te(ke,"whole",!0)}, ${te(Te,"tenth",!0)} and ${te(Se,"hundredth",!0)}`,le=[...G?[te(G,"whole",!1)]:[],te(Q,"tenth",!1),...ne?[te(ne,"hundredth",!1)]:[]].join(", "),ae=O<z?"before":"after",ie=O<z?"left":"right",we=G!==X?"We compare whole parts first, before tenths or hundredths.":Q!==B?"The whole parts are equal, so compare the tenths before hundredths.":"The whole parts and tenths are equal, so compare the hundredths.";M=`${K} is ${le} — ${ae} ${Z}.`,L=`${we} ${capitalised(k(K))} is ${oe(G,Q,ne)}, and ${k(Z)} is ${oe(X,B,se)}. So ${k(K)} sits ${ae} ${k(Z)}, to the ${ie} on the number line.`}else dpOf$3(O)>1?(M=`${K} is ${Q} tenths and a bit — before ${Z}.`,L=`We compare the tenths first, because tenths are worth more than hundredths. ${capitalised(k(K))} is ${numberToWordsLong(Q)} tenths and a bit, so it sits before ${k(Z)}, which is ${numberToWordsLong(B)} tenths.`):(M=`${K} is exactly ${Q} tenths — before ${Z}, which is ${B} tenths and a bit.`,L=`We compare the tenths first, because tenths are worth more than hundredths. ${capitalised(k(K))} is exactly ${numberToWordsLong(Q)} tenths, so it sits before ${k(Z)}, which is ${numberToWordsLong(B)} tenths and a bit.`)}}const P=i.map(tenthsDigitOf),F=t==="FDP.P4.M04"&&new Set(s.map(Math.floor)).size===1&&new Set(P).size===s.length,D=o==="asc"?"<":">",U=Math.floor(b),q=Object.fromEntries(P.map(V=>[round2$1(U+V/10),`${V} tenths`]));return F&&(P.forEach((V,O)=>{A[`tenths${O}`]=V}),A.whole=U),buildExample({generatorId:this.id,question:e,title:`Order these decimals: ${N} first`,computed:A,steps:[{visual_data:g(F?{step:.1,under_labels:q}:{marked:[b]}),animation:"show_start",caption:F?`Compare tenths first: ${P.join(` ${D} `)}.`:`Place ${fmt$3(b)} on the line first.`,spoken:F?`The whole parts are equal, so compare tenths first. ${i.map((V,O)=>`${capitalised(decimalToWords(fmt$3(V)))} has ${numberToWordsLong(P[O])} tenths`).join("; ")}. The tenths decide the order before we look at hundredths.`:E?`To put decimals in order, we place them on a number line, where numbers get bigger as we go right. Let's place them one at a time, starting with ${k(fmt$3(b))}.`:`Here are the decimals we need to put in order. Let's place them on a number line one at a time, starting with ${decimalToWords(fmt$3(b))}.`},{visual_data:g({marked:[...s]}),animation:"compare_place",caption:F?"Now place each decimal using its tenths and hundredths.":M,spoken:F?`${i.map(V=>{const O=tenthsDigitOf(V),z=Math.round(V*100)%10;return`${capitalised(decimalToWords(fmt$3(V)))} sits at ${decimalToWords(fmt$3(round2$1(U+O/10)))}${z?`, then ${numberToWordsLong(z)} hundredths further`:" exactly"}`}).join(". ")}.`:L},{visual_data:g({marked:[...s],unknown:x}),animation:"land",caption:S,spoken:o==="asc"?`Reading from left to right puts them in order, smallest first: ${T}.`:`Reading from right to left puts them in order, largest first: ${T}.`}]})}}}const DECIMAL_ORDERING_GENERATORS=[makeOrderingGenerator("FDP.P4.M04.order-line.v1","FDP.P4.M04"),makeOrderingGenerator("NPV.P6.M03.order-line.v1","NPV.P6.M03"),makeOrderingGenerator("NPV.P6.M04.order-line.v1","NPV.P6.M04")],Y4_GENERATORS=[...AS_P5_M03_GENERATORS,...AS_P5_M04_GENERATORS,...AS_P5_M05_GENERATORS,...DS_P3_M02_GENERATORS,...DS_P3_M03_GENERATORS,...FDP_P4_M01_GENERATORS,...FDP_P4_M02_GENERATORS,...FDP_P4_M03_GENERATORS,...FDP_P4_M04_GENERATORS,...GEO_P4_M03_GENERATORS,...GEO_P4_M05_GENERATORS,...GEO_P5_M03_GENERATORS,...GEO_P5_M04_GENERATORS,...MD_P3_M02_GENERATORS,...MD_P4_M01_GENERATORS,...MD_P4_M02_GENERATORS,...MD_P4_M03_GENERATORS,...MEA_P7_M02_GENERATORS,...MEA_P7_M03_GENERATORS,...NPV_P5_M05_GENERATORS,...NPV_P5_M06_GENERATORS,...NPV_P6_M01_GENERATORS,...NPV_P6_M02_GENERATORS,...NPV_P6_M03_GENERATORS,...NPV_P6_M04_GENERATORS,...RP_P1_M03_GENERATORS,...DECIMAL_ORDERING_GENERATORS];function stripInt$1(n){return Number(n.replace(/,/g,""))}function acceptedInt$1(n){const t=stripInt$1(n.correctAnswer);return Number.isInteger(t)?t:null}function allInts(n){return(normaliseStem(n).match(/\d[\d,]*/g)??[]).map(stripInt$1).filter(e=>Number.isInteger(e))}function flankingSum(n){const t=normaliseStem(n).match(/(\d[\d,]*)\s*\+\s*(\d[\d,]*)/);return t?[stripInt$1(t[1]),stripInt$1(t[2])]:null}const OPERAND_MIN=1e3,OPERAND_MAX=999999,inOperandRange=n=>Number.isInteger(n)&&n>=OPERAND_MIN&&n<=OPERAND_MAX,PLACE_NAMES$3=["ones","tens","hundreds","thousands","ten-thousands","hundred-thousands","millions"],CARRY_UNIT=["ten","hundred","thousand","ten thousand","hundred thous
71and","million"],PLACE_SPOKEN=n=>n.replace(/-/g," ");function spellBig$1(n){if(n>=1e6){const t=Math.floor(n/1e6),e=n%1e6;return`${numberToWordsLong(t)} million${e?` ${e<100?"and ":""}${numberToWordsLong(e)}`:""}`}return numberToWordsLong(n)}function digitsRightPadded$1(n,t){return[...String(n).padStart(t,"0")].reverse().map(Number)}function firstCarryColumn(n,t){const e=Math.max(String(n).length,String(t).length)+1,s=digitsRightPadded$1(n,e),o=digitsRightPadded$1(t,e);let r=0;for(let a=0;a<e;a++){if(s[a]+o[a]+r>=10)return a;r=0}return null}function firstBorrowColumn$1(n,t){const e=Math.max(String(n).length,String(t).length),s=digitsRightPadded$1(n,e),o=digitsRightPadded$1(t,e);let r=0;for(let a=0;a<e;a++){const i=o[a]+r;if(s[a]<i)return a;r=0}return null}function additionSteps(n,t,e){const s=Math.max(String(n).length,String(t).length)+1,o=digitsRightPadded$1(n,s),r=digitsRightPadded$1(t,s),a=firstCarryColumn(n,t)??0,i=o[a],l=r[a],u=i+l,d=u>=10,m=u%10,h=PLACE_NAMES$3[a]??`column ${a+1}`,f=String(e).length>Math.max(String(n).length,String(t).length),g={type:"column_method",operation:"add",top:n,bottom:t,focus_column:a},b=a>0?`Working from the right, the ${PLACE_NAMES$3.slice(0,a).join(", ")} don't carry. `:"",x=d?f?`Now we add each column moving left, carrying one to the next column whenever a column reaches ten or more. The very last carry has nowhere to sit, so it opens a brand-new column on the left. So the total is ${spellBig$1(e)}.`:`Now we add each column moving left, carrying one to the next column whenever a column reaches ten or more. So the total is ${spellBig$1(e)}.`:`Now we add each column moving left. Every column stays under ten, so no carrying is needed. So the total is ${spellBig$1(e)}.`,y=CARRY_UNIT[a]??"ten",E=PLACE_SPOKEN(PLACE_NAMES$3[a+1]??"next column");return[{visual_data:g,animation:"stack_columns",caption:"Line up by place value. Start on the right.",spoken:"Adding only works when each digit is added to a digit of the same value. So we stack the two numbers with ones under ones, tens under tens, and so on. We always begin at the ones column, on the right."},{visual_data:g,animation:d?"carry_the_ten":"add_ones",caption:d?`The ${h} make ten or more — carry one.`:`Add the ${h} — under ten, nothing carries.`,spoken:d?`${b}In the ${PLACE_SPOKEN(h)}, ${numberToWords(i)} and ${numberToWords(l)} make ${numberToWords(u)}. A column can only hold nine, so we carry: ${numberToWords(u)} ${PLACE_SPOKEN(h)} is one ${y} and ${numberToWords(m)} ${PLACE_SPOKEN(h)}. The ${numberToWords(m)} stays, and the one ${y} carries into the ${E} column.`:`${b}In the ${PLACE_SPOKEN(h)}, ${numberToWords(i)} and ${numberToWords(l)} make ${numberToWords(u)}. That is under ten, so it stays and nothing carries yet.`},{visual_data:g,animation:"complete_sum",caption:d?"Carry through each column to the total.":"Add each column to the total.",spoken:x}]}const twoAddend={id:"AS.P6.M01.column-add.v1",microSkillId:"AS.P6.M01",forms:["symbolic_calculation","context_calculation","varied_symbolic_calculation","symbolic_choice","misconception_contrast_probe"],generate(n){const t=acceptedInt$1(n);if(t===null)return null;let e=null;const s=n.visualData;if(s&&s.type==="column_method"&&s.operation==="add"&&(e=[s.top,s.bottom]),!e){const u=flankingSum(n.stem);u&&u[0]+u[1]===t&&(e=u)}if(!e){const u=allInts(n.stem),d=[];for(let m=0;m<u.length;m++)for(let h=m+1;h<u.length;h++)u[m]+u[h]===t&&d.push([u[m],u[h]]);d.length===1&&(e=d[0])}if(!e)return null;const[o,r]=e;if(!inOperandRange(o)||!inOperandRange(r)||o+r!==t)return null;const[a,i]=[o,r],l=additionSteps(a,i,t);if(n.mathematicalForm==="misconception_contrast_probe"){const u=normaliseStem(n.stem).match(/\bgot\s+(\d[\d,]*)\b/i),d=normaliseStem(n.stem).match(/^([A-Z][a-z]+)\s+added\b/);if(u){const m=stripInt$1(u[1]),h=t-m,f=Math.log10(h);if(h>0&&Number.isInteger(f)&&PLACE_NAMES$3[f]&&t<=999999&&m<=999999){const g=PLACE_NAMES$3[f],b=d?d[1]:"The working";l[2]={...l[2],caption:`${b} dropped the carry into the ${g}.`,spoken:`${b}'s columns matched everywhere except one: the carry into the ${g} was dropped, which is why the claimed total of ${numberToWordsLong(m)} is short. Carrying that one through properly gives ${numberToWordsLong(t)}.`}}}}return buildExample({generatorId:this.id,question:n,title:`How do we work out ${a.toLocaleStr
71ing("en-GB")} + ${i.toLocaleString("en-GB")}?`,computed:{top:a,bottom:i,total:t,answer:t},steps:l})}},threeAddend={id:"AS.P6.M01.three-addend.v1",microSkillId:"AS.P6.M01",forms:["varied_symbolic_calculation","context_calculation"],generate(n){const t=acceptedInt$1(n);if(t===null)return null;const e=allInts(n.stem);if(e.length!==3)return null;const[s,o,r]=e;if(![s,o,r].every(inOperandRange)||s+o+r!==t)return null;const a=s+o;if(a+r!==t)return null;const i=a%10,l=r%10,d=i+l>=10,m=firstCarryColumn(a,r)!==null,h={type:"column_method",operation:"add",top:Math.max(s,o),bottom:Math.min(s,o),show_answer:!0},f={type:"column_method",operation:"add",top:Math.max(a,r),bottom:Math.min(a,r)};return buildExample({generatorId:this.id,question:n,title:`How do we add ${s.toLocaleString("en-GB")}, ${o.toLocaleString("en-GB")} and ${r.toLocaleString("en-GB")}?`,computed:{a:s,b:o,c:r,subtotal:a,total:t,answer:t},steps:[{visual_data:h,animation:"add_first_pair",caption:"Add two numbers first to get a subtotal.",spoken:`With three numbers, we add just two at a time, so every column stays lined up. First we work out ${spellBig$1(s)} plus ${spellBig$1(o)} in columns. That gives a subtotal of ${spellBig$1(a)}.`},{visual_data:f,animation:d?"carry_the_ten":"add_ones",caption:"Now add the third number onto the subtotal.",spoken:d?`Now we add the third number, ${spellBig$1(r)}, onto the subtotal. Starting at the ones, they make ten or more, so a ten carries across — then we keep going left.`:`Now we add the third number, ${spellBig$1(r)}, onto the subtotal, starting again at the ones column on the right and working left.`},{visual_data:f,animation:"complete_sum",caption:m?"Carry through to the grand total.":"Add the columns to the grand total.",spoken:m?`We finish adding the columns, carrying one to the next column whenever a column reaches ten or more. So all three numbers add up to ${spellBig$1(t)}.`:`We finish adding the columns. Every column stays under ten, so no carrying is needed. So all three numbers add up to ${spellBig$1(t)}.`}]})}},missingAddend$1={id:"AS.P6.M01.missing-addend.v1",microSkillId:"AS.P6.M01",forms:["missing_addend"],generate(n){const t=acceptedInt$1(n);if(t===null||t<=0)return null;let e=null,s=null;const o=normaliseStem(n.stem).match(/(\d[\d,]*)\s*\+\s*__\s*=\s*(\d[\d,]*)/);if(o)e=stripInt$1(o[1]),s=stripInt$1(o[2]);else{const h=allInts(n.stem);h.length===2&&(s=Math.max(h[0],h[1]),e=Math.min(h[0],h[1]))}if(e===null||s===null||!inOperandRange(e)||s<=e||s>9999999||s-e!==t)return null;const r=firstBorrowColumn$1(s,e),a=r!==null,i=r??0,l={type:"column_method",operation:"subtract",top:s,bottom:e,focus_column:i},u={...l,keep_exchange:!0},d=PLACE_NAMES$3[i]??`column ${i+1}`,m=i>0?`Working from the right, the ${PLACE_NAMES$3.slice(0,i).join(", ")} need no exchange. `:"";return buildExample({generatorId:this.id,question:n,title:`What must we add to reach ${s.toLocaleString("en-GB")}?`,computed:{total:s,known:e,gap:t,answer:t},steps:[{visual_data:l,animation:"setup_subtraction",caption:"Missing part = total − the part we have.",spoken:`To find the missing number, we work out the gap. We subtract the part we already have, ${spellBig$1(e)}, from the total, ${spellBig$1(s)}, lining the digits up by place value.`},{visual_data:l,animation:a?"exchange":"subtract_ones",caption:a?`Exchange at the ${d} — the digit there is too small.`:"Subtract each column — no exchange needed.",spoken:a?`${m}At the ${d}, the top digit is too small to subtract, so we exchange a ten from the next column along. If that column is also empty, the exchange travels further left, turning each zero it passes into a nine.`:"Start at the ones and work left. Every top digit is big enough to subtract the digit below it, so no exchange is needed."},{visual_data:u,animation:"complete_difference",caption:"The gap is the missing number.",spoken:`${capitalised(spellBig$1(s))} take away ${spellBig$1(e)} is ${spellBig$1(t)}. That is the gap — the amount we must add to the part we have to reach the total. So the missing number is ${spellBig$1(t)}.`}]})}},roundingEstimate={id:"AS.P6.M01.rounding-estimate.v1",microSkillId:"AS.P6.M01",forms:["rounding_entry"],generate(n){const t=acceptedInt$1(n);if(t===null)return null;const e=normaliseStem(n.stem).match(/estimate\s+(\d[\d,]*)\s*\+\s*(\d[\d,]*)\s+by rounding each number to the nearest\s+(\d[\d,]*)/i);if(!e)return null;const s=stripInt$1(e[1]),o=stripInt$1(e[2]),r=stripInt$1(e[3]);if(![1e3,1e4,1e5].includes(r)||!inOperandRange(s)||!inOperandRange(o))return null;const a=Math.round(s/r)*r,i=Math.round(o/r)*r;if(a+i!==t||!inOperandRange(a)||!inOperandRange(i))return null;const l=r===1e3?"thousand":r===1e4?"ten-thousand":"hundred-thousand",u=[s.toLocaleStr
71ing("en-GB"),"+",o.toLocaleString("en-GB")],d=[a.toLocaleString("en-GB"),"+",i.toLocaleString("en-GB")],m=[...d,"=",t.toLocaleString("en-GB")];return buildExample({generatorId:this.id,question:n,title:`Estimate ${s.toLocaleString("en-GB")} + ${o.toLocaleString("en-GB")} by rounding`,computed:{rounded_a:a,rounded_b:i,estimate:t,answer:t},steps:[{visual_data:{type:"equation_display",terms:u},animation:"show_exact",caption:"We only need a close answer, not the exact one.",spoken:`We only need a close answer, not the exact one. The trick is to round each number to the nearest ${l} first — that makes the adding much easier.`},{visual_data:{type:"equation_display",terms:d},animation:"round_each",caption:`Round each to the nearest ${l}.`,spoken:`We round each number to the nearest ${l}, watching which way each one moves: ${spellBig$1(s)} rounds to ${spellBig$1(a)}, and ${spellBig$1(o)} rounds to ${spellBig$1(i)}. Each rounded number now ends in zeros, ready for an easy add.`},{visual_data:{type:"equation_display",terms:m},animation:"reveal_estimate",caption:"Add the rounded numbers — this is the estimate.",spoken:`Now we add the two rounded numbers: ${spellBig$1(a)} add ${spellBig$1(i)} is ${spellBig$1(t)}. Because we rounded first, this is close to the exact total but far simpler to work out. So the estimate is ${spellBig$1(t)}.`}]})}};function addPairNarration(n,t,e){const s=Math.max(String(n).length,String(t).length)+1,o=digitsRightPadded$1(n,s),r=digitsRightPadded$1(t,s),a=firstCarryColumn(n,t)??0,i=o[a]+r[a],l=PLACE_NAMES$3[a]??`column ${a+1}`;return i>=10?`In the ${l}, ${numberToWords(o[a])} and ${numberToWords(r[a])} make ${numberToWords(i)} — a ten carries across. Working through every column the same way gives ${numberToWordsLong(e)}.`:`Adding column by column from the right, with no carries needed, gives ${numberToWordsLong(e)}.`}const LABELLED_PAIRS=/^Which pair has the (?:greater|larger) total\?\s*A:\s*(\d[\d,]*)\s*\+\s*(\d[\d,]*)\s+B:\s*(\d[\d,]*)\s*\+\s*(\d[\d,]*)$/i,NARRATIVE_PAIR=/^(.+?)\s+(?:walked|had|collected|raised|scored|made|read|earned|ran|sold)\s+(\d[\d,]*)\s+then\s+(\d[\d,]*)\s+[\s\S]*?\.\s*(.+?)\s+(?:walked|had|collected|raised|scored|made|read|earned|ran|sold)\s+(\d[\d,]*)\s+then\s+(\d[\d,]*)[\s\S]*?\.\s*Which\s+(?:team|pair|person|one|group)\s+[\s\S]*\?$/i,comparisonStatement={id:"AS.P6.M01.compare-totals.v1",microSkillId:"AS.P6.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem),e=n.correctAnswer.trim();let s,o,r,a,i,l,u,d,m,h=t.match(LABELLED_PAIRS);
71if(h)[s,o,r,a]=[stripInt$1(h[1]),stripInt$1(h[2]),stripInt$1(h[3]),stripInt$1(h[4])],i="Pair A",l="Pair B",u="They are equal",d=`${s.toLocaleString("en-GB")} + ${o.toLocaleString("en-GB")}`,m=`${r.toLocaleString("en-GB")} + ${a.toLocaleString("en-GB")}`;else if(h=t.match(NARRATIVE_PAIR))[s,o,r,a]=[stripInt$1(h[2]),stripInt$1(h[3]),stripInt$1(h[5]),stripInt$1(h[6])],i=h[1].trim(),l=h[4].trim(),u=null,d=`${s.toLocaleString("en-GB")} + ${o.toLocaleString("en-GB")}`,m=`${r.toLocaleString("en-GB")} + ${a.toLocaleString("en-GB")}`;else return null;if(![s,o,r,a].every(inOperandRange))return null;const f=s+o,g=r+a;if(f>999999||g>999999)return null;let b;if(f>g)b=i;else if(g>f)b=l;else if(u)b=u;else return null;if(b!==e)return null;const x=f.toLocaleString("en-GB"),y=g.toLocaleString("en-GB"),E=f>g?">":f<g?"<":"=";return buildExample({generatorId:this.id,question:n,title:`Which has the greater total: ${d}, or ${m}?`,computed:{n1:s,n2:o,n3:r,n4:a,sumA:f,sumB:g,answer:f>g?f:g},steps:[{visual_data:{type:"equation_display",terms:[d,"vs",m]},animation:"show_pair",caption:"Compare two totals.",spoken:`We compare two totals: ${numberToWordsLong(s)} plus ${numberToWordsLong(o)}, and ${numberToWordsLong(r)} plus ${numberToWordsLong(a)}. Work each one out fully first — never judge from the first digits alone.`},{visual_data:{type:"equation_display",terms:[d,"=",x,"and",m,"=",y]},animation:"show_sums",caption:"Work out each total.",spoken:`For the first pair, ${addPairNarration(s,o,f)} For the second pair, ${addPairNarration(r,a,g)}`},{visual_data:{type:"equation_display",terms:[x,E,y]},animation:"reveal_winner",caption:`${b} has the greater total.`,spoken:f>g?"The first total is more than the second, so it is the greater one.":"The second total is more than the first, so it is the greater one."}]})}},TWO_STEP_MORE=/^.+? made (\d[\d,]*) \S+ in week one\. In week two it made (\d[\d,]*) more \S+ than in week one\. How many \S+ did it make over the two weeks in total\?$/i,contextReasoningTwoStep={id:"AS.P6.M01.two-step-more.v1",microSkillId:"AS.P6.M01",forms:["context_reasoning"],generate(n){const t=acceptedInt$1(n);if(t===null)return null;const e=normaliseStem(n.stem).match(TWO_STEP_MORE);if(!e)return null;const s=stripInt$1(e[1]),o=stripInt$1(e[2]);if(!inOperandRange(s)||!inOperandRange(o))return null;const r=s+o;if(!inOperandRange(r)||s+r!==t)return null;const i={type:"column_method",operation:"add",top:s,bottom:o,show_answer:!0},l=firstCarryColumn(s,r)??0,u={type:"column_method",operation:"add",top:s,bottom:r,focus_column:l};return buildExample({generatorId:this.id,question:n,title:"How many were made over the two weeks?",computed:{week1:s,more:o,week2:r,total:t,answer:t},steps:[{visual_data:i,animation:"add_first_pair",caption:"First find week two: week one plus the extra.",spoken:`This is a two-step story. Week two is more than week one, so first we add week one's amount and the extra to find week two's amount: ${spellBig$1(s)} add ${spellBig$1(o)} is ${spellBig$1(r)}.`},{visual_data:u,animation:"add_ones",caption:"Add week one and week two.",spoken:`The question asks for both weeks together, so now we add week one's amount and week two's amount in columns: ${spellBig$1(s)} add ${spellBig$1(r)}.`},{visual_data:u,animation:"complete_sum",caption:"Carry through to the grand total.",spoken:`Adding those two amounts, carrying a ten whenever a column reaches ten or more, gives ${spellBig$1(t)}. That is the total made over the two weeks.`}]})}},AS_P6_M01_GENERATORS=[threeAddend,twoAddend,missingAddend$1,roundingEstimate,comparisonStatement,contextReasoningTwoStep];function spellLong(n){if(!Number.isInteger(n)||n<0||n>9999999)throw new Error(`spellLong out of range: ${n}`);if(n<1e3)return numberToWords(n);if(n<1e6){const o=Math.floor(n/1e3),r=n%1e3,a=`${numberToWords(o)} thousand`;return r===0?a:r<100?`${a} and ${numberToWords(r)}`:`${a} ${numberToWords(r)}`}const t=Math.floor(n/1e6),e=n%1e6,s=`${numberToWords(t)} million`;return e===0?s:e<100?`${s} and ${numberToWords(e)}`:`${s} ${spellLong(e)}`}function stemInts$1(n){return(normaliseStem(n).match(/\d[\d,]*/g)??[]).map(e=>Number(e.replace(/,/g,"")))}const MAX=9999999,inRange=n=>Number.isInteger(n)&&n>=0&&n<=MAX,PLACE_NAMES$2=["ones","tens","hundreds","thousands","ten-thousands","hundred-thousands","millions"];function digitsRightPadded(n,t){return[...String(n).padStart(t,"0")].reverse().map(Number)}function firstBorrowColumn(n,t){const e=String(n).length,s=digitsRightPadded(n,e),o=digitsRightPadded(t,e);let r=0;for(let a=0;a<e;a++){const i=o[a]+r;if(s[a]<i)return a;r=0}return 0}function buildColumnDemo(n,t,e){const{top:s,bottom:o,operation:r,lead:a}=e,i=r==="subtract"?s-o:s+o,l=r==="subtract"?"take away":"add",u=r==="subtract"?firstBorrowColumn(s,o):0,d=PLACE_NAMES$2[u]??`column ${u+1}`,m={type:"column_method",operation:r,top:s,bottom:o,focus_column:u},h=r==="subtract"?{type:"column_method",operation:r,top:s,bottom:o,focus_column:u,keep_exchange:!0}:{type:"column_method",operation:r,top:s,bottom:o,show_answer:!0},f=r==="subtract"&&u>0?`Working from the right, the ${PLACE_NAMES$2.slice(0,u).join(", ")} need no exchange. `:"",g=r==="subtract"?`${f}${borrowNarration(s,o)}`:"Add each column from the ones upward, carrying a ten into the next column whenever a column reaches ten or more.";return buildExample({generatorId:n,question:t,title:normaliseStem(t.stem),computed:{top:s,bottom:o,answer:i},steps:[{visual_data:m,animation:"stack_columns",caption:"Line up ones under ones.",spoken:`${a?a+" ":""}We stack ${spellLong(s)} ${l} ${spellLong(o)} by place value, ones under ones, and start at the ones column.`},{visual_data:m,animation:"work_ones",caption:r==="subtract"?`Work the ${d}, exchanging if needed.`:"Work the ones, carrying if needed.",spoken:g},{visual_data:h,animation:"reveal_answer",caption:`Answer: ${i}.`,spoken:`Finishing every column gives ${spellLong(i)}.${r==="subtract"?" Exchanging never changed the amount — it only regrouped it so each column had enough to take away.":" Each carry moved a full ten up to the next place, keeping the total the same."}`}]})}function borrowNarration(n,t){const e=String(n),s=String(t).padStart(e.length,"0"),o=[...e].reverse().map(Number),r=[...s].reverse().map(Number);let a=0,i=!1,l=!1;for(let u=0;u<o.length;u++){const d=r[u]+a;o[u]<d?(i=!0,u+1<o.length&&o[u+1]===0&&(l=!0),a=1):a=0}return i?l?"Where the top digit is too small we exchange from the next column; but some of those columns are zero, so the exchange travels further left, turning each zero it passes into a nine.":"Where a top digit is too small to subtract, we exchange a ten from the next column along, then take away.":"Every top digit is big enough here, so each column simply subtracts straight down with 
71no exchanging."}const SUBTRACT_FROM=/^Subtract (\d[\d,]*) from (\d[\d,]*)$/i,DIFFERENCE_BETWEEN=/^Find the difference between (\d[\d,]*) and (\d[\d,]*)$/i,A_MINUS_B=/^(?:Work out )?(\d[\d,]*)\s*-\s*(\d[\d,]*)(?:\s*=\s*__)?$/i,num=n=>Number(n.replace(/,/g,"")),symbolicSubtraction={id:"AS.P6.M02.symbolic-subtraction.v1",microSkillId:"AS.P6.M02",forms:["symbolic_calculation"],generate(n){const t=answerInt(n);if(t===null)return null;const e=normaliseStem(n.stem);let s=null,o=null,r=e.match(SUBTRACT_FROM);if(r&&(s=num(r[2]),o=num(r[1])),s===null&&(r=e.match(DIFFERENCE_BETWEEN))){const a=num(r[1]),i=num(r[2]);s=Math.max(a,i),o=Math.min(a,i)}return s===null&&(r=e.match(A_MINUS_B))&&(s=num(r[1]),o=num(r[2])),s===null&&n.visualData&&n.visualData.type==="column_method"&&n.visualData.operation==="subtract"&&(s=n.visualData.top,o=n.visualData.bottom),s===null||o===null||!inRange(s)||!inRange(o)||o>s||s-o!==t?null:buildColumnDemo(this.id,n,{top:s,bottom:o,operation:"subtract"})}},contextSubtraction={id:"AS.P6.M02.context-subtraction.v1",microSkillId:"AS.P6.M02",forms:["context_calculation","subtraction_story_context"],generate(n){const t=answerInt(n);if(t===null)return null;const e=stemInts$1(n.stem).filter(inRange);if(e.length<2)return null;const s=[...e].sort((a,i)=>i-a),o=s[0],r=s[1];return o===r||o-r!==t?null:buildColumnDemo(this.id,n,{top:o,bottom:r,operation:"subtract"})}},A_MINUS_BLANK=/(\d[\d,]*)\s*-\s*__\s*=\s*(\d[\d,]*)/,BLANK_MINUS_B=/__\s*-\s*(\d[\d,]*)\s*=\s*(\d[\d,]*)/,missingNumber$1={id:"AS.P6.M02.missing-number.v1",microSkillId:"AS.P6.M02",forms:["equation_completion"],generate(n){const t=answerInt(n);if(t===null)return null;const e=normaliseStem(n.stem);let s=e.match(BLANK_MINUS_B);if(s){const o=num(s[1]),r=num(s[2]),a=r+o;return!inRange(o)||!inRange(r)||!inRange(a)||a!==t?null:buildColumnDemo(this.id,n,{top:r,bottom:o,operation:"add",lead:"The start was reduced to leave this result, so we add the two parts back together to rebuild it."})}if(s=e.match(A_MINUS_BLANK),s){const o=num(s[1]),r=num(s[2]),a=o-r;return!inRange(o)||!inRange(r)||a<0||a!==t?null:buildColumnDemo(this.id,n,{top:o,bottom:r,operation:"subtract",lead:"The missing number is how much was taken away, which is the difference, so we subtract the result from the start."})}return null}},twoStepReasoning={id:"AS.P6.M02.two-step-reasoning.v1",microSkillId:"AS.P6.M02",forms:["context_reasoning"],generate(n){const t=answerInt(n);if(t===null)return null;const e=stemInts$1(n.stem);if(e.length!==3)return null;const[s,o,r]=e;if(![s,o,r].every(inRange))return null;const i=[[-1,-1],[-1,1],[1,-1],[1,1]].filter(([L,P])=>{const F=s+L*o;if(F<0)return!1;const D=F+P*r;return D>=0&&D===t});if(i.length!==1)return null;const[l,u]=i[0];let d,m,h,f,g,b;l===-1&&u===-1?(d="add",m=o,h=r,f="subtract",g=s,b=o+r):l===-1&&u===1?(d="subtract",m=s,h=o,f="add",g=s-o,b=r):l===1&&u===-1?(d="add",m=s,h=o,f="subtract",g=s+o,b=r):(d="add",m=s,h=o,f="add",g=s+o,b=r);const x=d==="add"?m+h:m-h,y=f==="add"?g+b:g-b;if(x<0||y<0||y!==t||x===y)return null;const E=d==="add"?"+":"−",k=f==="add"?"+":"−",T=d==="add"?"add":"take away",A=f==="add"?"add":"take away",N={type:"column_method",operation:d,top:m,bottom:h,show_answer:!0},S={type:"column_method",operation:f,top:g,bottom:b},M={type:"column_method",operation:f,top:g,bottom:b,show_answer:!0};return buildExample({generatorId:this.id,question:n,title:normaliseStem(n.stem),computed:{S:s,A:o,B:r,s1top:m,s1bottom:h,stage1:x,s2top:g,s2bottom:b,answer:y},steps:[{visual_data:N,animation:"stage_one",caption:`First: ${m} ${E} ${h} = ${x}.`,spoken:`This story takes two stages. First we ${T} ${spellLong(m)} and ${spellLong(h)}, which gives ${spellLong(x)}.`},{visual_data:S,animation:"stage_two",caption:`Then: ${g} ${k} ${b}.`,spoken:`That ${spellLong(x)} begins the second stage. Now we ${A} ${spellLong(b)}, exchanging across any zeros as we go.`},{visual_data:M,animation:"reveal_total",caption:`Answer: ${y}.`,spoken:`Working both stages through gives ${spellLong(y)}.`}]})}};function subtractPairNarration(n,t,e){const s=firstBorrowColumn(n,t),o=String(n).length,r=digitsRightPadded(n,o),a=digitsRightPadded(t,o),i=PLACE_NAMES$2[s]??`column ${s+1}`;return r[s]<a[s]?`At the ${i}, ${numberToWords(r[s])} is too small to take away ${numberToWords(a[s])}, so we exchange a ten from the next column along. Working through every column this way gives ${spellLong(e)}.`:`Subtracting column by column from the right, with no exchange needed, gives ${spellLong(e)}.`}const COMPARE_DIFFERENCES=/^Which difference is larger:\s*(\d[\d,]*)\s*-\s*(\d[\d,]*),\s*or\s*(\d[\d,]*)\s*-\s*(\d[\d,]*)\?$/i,comparisonOfDifferences={id:"AS.P6.M02.compare-differences.v1",microSkillId:"AS.P6.M02",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(COMPARE_DIFFERENCES);if(!e)return null;const[s,o,r,a]=[num(e[1]),num(e[2]),num(e[3]),num(e[4])];
71if(![s,o,r,a].every(inRange)||o>s||a>r)return null;const i=s-o,l=r-a,u=`${s.toLocaleString("en-GB")} − ${o.toLocaleString("en-GB")}`,d=`${r.toLocaleString("en-GB")} − ${a.toLocaleString("en-GB")}`;let m;if(i>l?m=`${u} is larger`:l>i?m=`${d} is larger`:m="They are equal",m!==n.correctAnswer.trim())return null;const h=i.toLocaleString("en-GB"),f=l.toLocaleString("en-GB"),g=i>l?">":i<l?"<":"=";return buildExample({generatorId:this.id,question:n,title:`Which difference is larger: ${u}, or ${d}?`,computed:{A:s,B:o,C:r,D:a,diffA:i,diffB:l,answer:Math.max(i,l)},steps:[{visual_data:{type:"equation_display",terms:[u,"vs",d]},animation:"show_pair",caption:"Compare two differences.",spoken:"We cannot judge a difference from its first digits alone. Both subtractions must be worked out fully first."},{visual_data:{type:"equation_display",terms:[u,"=",h,"and",d,"=",f]},animation:"show_differences",caption:"Work out each difference.",spoken:`For the first pair, ${subtractPairNarration(s,o,i)} For the second pair, ${subtractPairNarration(r,a,l)}`},{visual_data:{type:"equation_display",terms:[h,g,f]},animation:"reveal_larger",caption:`${m}.`,spoken:i===l?"Both differences come out the same, so neither is larger.":i>l?"The first difference is bigger, so it is the larger one.":"The second difference is bigger, so it is the larger one."}]})}},CHECK_ADDITION=/^\w+ worked out (\d[\d,]*)\s*-\s*(\d[\d,]*)\s*=\s*(\d[\d,]*)\.\s*Which addition would check (?:her|his|their) answer is right\?$/i,DIAGNOSE_MISTAKE=/^\w+ works out (\d[\d,]*)\s*-\s*(\d[\d,]*)\s+and writes (\d[\d,]*), but the correct answer is (\d[\d,]*)\.\s*What is (?:her|his|their) most likely mistake\?$/i;function borrowCrossesZero(n,t){const e=String(n),s=String(t).padStart(e.length,"0"),o=[...e].reverse().map(Number),r=[...s].reverse().map(Number);let a=0;for(let i=0;i<o.length;i++){const l=r[i]+a;if(o[i]<l){if(i+1<o.length&&o[i+1]===0)return!0;a=1}else a=0}return!1}const checkReasoningProbe={id:"AS.P6.M02.check-reasoning.v1",microSkillId:"AS.P6.M02",forms:["check_reasoning_probe"],generate(n){const t=normaliseStem(n.stem);let e=t.match(CHECK_ADDITION);if(e){const s=num(e[1]),o=num(e[2]),r=num(e[3]);if(![s,o,r].every(inRange)||s-o!==r||`${r.toLocaleString("en-GB")} + ${o.toLocaleString("en-GB")}`!==n.correctAnswer.trim())return null;const i={type:"column_method",operation:"add",top:r,bottom:o},l={...i,show_answer:!0};return buildExample({generatorId:this.id,question:n,title:`How do we check ${s.toLocaleString("en-GB")} − ${o.toLocaleString("en-GB")} = ${r.toLocaleString("en-GB")}?`,computed:{top:s,bottom:o,diff:r,answer:s},steps:[{visual_data:{type:"column_method",operation:"subtract",top:s,bottom:o,show_answer:!0},animation:"show_worked",caption:"This subtraction has already been worked out.",spoken:"Subtraction and addition undo each other. To check a subtraction, we add the answer back to the part we took away."},{visual_data:i,animation:"add_ones",caption:"Add the difference and the part taken away.",spoken:"We add the difference to the number we subtracted. In columns, carrying where a column reaches ten or more."},{visual_data:l,animation:"reveal_answer",caption:"This should rebuild the starting number.",spoken:"If that addition gives back the number we started with, the subtraction was right. So the check is that addition — the difference plus the part taken away."}]})}if(e=t.match(DIAGNOSE_MISTAKE),e){const s=num(e[1]),o=num(e[2]),r=num(e[3]),a=num(e[4]);if(![s,o,r,a].every(inRange)||s-o!==a||r===a||!borrowCrossesZero(s,o))return null;const i=n.correctAnswer.trim(),l=s.toLocaleString("en-GB");if(!/exchange/i.test(i)||!/zero/i.test(i)||!i.includes(l))return null;const u=firstBorrowColumn(s,o),d={type:"column_method",operation:"subtract",top:s,bottom:o,focus_column:u},m={...d,keep_exchange:!0};return buildExample({generatorId:this.id,question:n,title:`What went wrong working out ${l} − ${o.toLocaleString("en-GB")}?`,computed:{top:s,bottom:o,wrong:r,correct:a,answer:a},steps:[{visual_data:{type:"column_method",operation:"subtract",top:s,bottom:o},animation:"show_claim",caption:"This answer is wrong — where did it go wrong?",spoken:`This subtraction was worked out and got ${spellLong(r)}, but that answer does not match the correct one. Let's find the slip.`},{visual_data:d,animation:"exchange",caption:"Zeros can't lend a ten directly — exchange travels further left.",spoken:"Where the top number has a run of zeros, a column that is too small can't exchange straight from its neighbour, because that neighbour is also empty. The exchange has to travel further left, turning each zero it passes into a nine."},{visual_data:m,animation:"reveal_answer",caption:i,spoken:"Skipping that chain of exchanges is exactly the mistake here — the correct working exchanges all the way across every zero before subtracting."}]})}return null}}
71,ROUNDING_UNITS={"hundred thousand":1e5,"ten thousand":1e4,thousand:1e3,hundred:100},ROUNDING_SUBTRACT=/^Estimate\s+(\d[\d,]*)\s*-\s*(\d[\d,]*)\s+by rounding each number to the nearest\s+(hundred thousand|ten thousand|thousand|hundred)$/i,roundingSubtractEstimate={id:"AS.P6.M02.rounding-estimate.v1",microSkillId:"AS.P6.M02",forms:["rounding_entry"],generate(n){const t=answerInt(n);if(t===null)return null;const s=normaliseStem(n.stem).match(ROUNDING_SUBTRACT);if(!s)return null;const o=num(s[1]),r=num(s[2]),a=ROUNDING_UNITS[s[3].toLowerCase()];if(!inRange(o)||!inRange(r)||!a)return null;const i=Math.round(o/a)*a,l=Math.round(r/a)*a;if(i-l!==t||i<l)return null;const u=s[3].toLowerCase(),d=[o.toLocaleString("en-GB"),"−",r.toLocaleString("en-GB")],m=[i.toLocaleString("en-GB"),"−",l.toLocaleString("en-GB")],h=[...m,"=",t.toLocaleString("en-GB")];return buildExample({generatorId:this.id,question:n,title:`Estimate ${o.toLocaleString("en-GB")} − ${r.toLocaleString("en-GB")} by rounding`,computed:{rounded_a:i,rounded_b:l,estimate:t,answer:t},steps:[{visual_data:{type:"equation_display",terms:d},animation:"show_exact",caption:"We only need a close answer, not the exact one.",spoken:`To estimate, we round each number to the nearest ${u} first — that makes the subtracting much easier than using the exact figures.`},{visual_data:{type:"equation_display",terms:m},animation:"round_each",caption:`Round each to the nearest ${u}
71.`,spoken:`We round each number to the nearest ${u}, watching which way each one moves: ${spellLong(o)} rounds to ${spellLong(i)}, and ${spellLong(r)} rounds to ${spellLong(l)}. Each rounded number now ends in zeros, ready for an easy subtraction.`},{visual_data:{type:"equation_display",terms:h},animation:"reveal_estimate",caption:"Subtract the rounded numbers — this is the estimate.",spoken:`Now we subtract the rounded numbers: ${spellLong(i)} take away ${spellLong(l)} is ${spellLong(t)}. Because we rounded first, this is close to the exact difference but far simpler to work out. So the estimate is ${spellLong(t)}.`}]})}},SAME_DIFFERENCE=/^(\w+) says\s+(\d[\d,]*)\s*-\s*(\d[\d,]*)\s+gives the same answer as\s+(\d[\d,]*)\s*-\s*(\d[\d,]*)\.\s*Is \1 right\?$/i,constantDifferenceJudgement={id:"AS.P6.M02.constant-difference.v1",microSkillId:"AS.P6.M02",forms:["context_reasoning_judgement"],generate(n){const e=normaliseStem(n.stem).match(SAME_DIFFERENCE);if(!e)return null;const[s,o,r,a]=[num(e[2]),num(e[3]),num(e[4]),num(e[5])];if(![s,o,r,a].every(inRange)||o>s||a>r)return null;const i=s-o,l=r-a,u=i===l,d=n.correctAnswer.trim();if(u&&!isAffirmative(d)||!u&&!isNegative(d))return null;const m=`${s.toLocaleString("en-GB")} − ${o.toLocaleString("en-GB")}`,h=`${r.toLocaleString("en-GB")} − ${a.toLocaleString("en-GB")}`,f=i.toLocaleString("en-GB"),g=l.toLocaleString("en-GB");return buildExample({generatorId:this.id,question:n,title:`Is ${m} the same as ${h}?`,computed:{A:s,B:o,C:r,D:a,diffA:i,diffB:l,answer:i},steps:[{visual_data:{type:"equation_display",terms:[m,"vs",h]},animation:"show_claim",caption:"Are these two differences equal?",spoken:"Someone claims two subtractions give the same answer. Let's work both out and check."},{visual_data:{type:"equation_display",terms:[m,"=",f,"and",h,"=",g]},animation:"show_both",caption:"Work out both differences.",spoken:`Subtracting each pair in columns, the first gives ${spellLong(i)}, and the second gives ${spellLong(l)}.`},{visual_data:{type:"equation_display",terms:[f,u?"=":"≠",g]},animation:"verdict",caption:u?"Yes — both differences match.":"No — the differences are different.",spoken:u?"Both differences really are the same. Both numbers in the second pair are the same amount smaller than the first pair, so the gap between them never changes.":"The differences do not match, so the claim is wrong."}]})}},AS_P6_M02_GENERATORS=[symbolicSubtraction,contextSubtraction,missingNumber$1,twoStepReasoning,comparisonOfDifferences,checkReasoningProbe,roundingSubtractEstimate,constantDifferenceJudgement];function decPlaces(n){const t=n.indexOf(".");return t===-1?0:n.length-t-1}function stripNum$1(n){return parseFloat(n.replace(/[^0-9.]/g,""))}function answerCore(n){return n.replace(/[^0-9.]/g,"")}function trimZeros(n,t){let e=n.toFixed(t);return e.indexOf(".")!==-1&&(e=e.replace(/0+$/,"").replace(/\.$/,"")),e}function addChain(n,t){const e=[];return n.forEach((s,o)=>{o>0&&e.push("+"),e.push(s)}),e.push("="),e.push(t),e}const PLACE_WORDS=["tenths","hundredths","thousandths"],SINGULAR_PLACE=["tenth","hundredth","thousandth"];function carryUnitName(n,t){return t===1?n===0?"a whole":`one ${SINGULAR_PLACE[n-1]}`:`${numberToWords(t)} ${n===0?"wholes":PLACE_WORDS[n-1]}`}function firstDecimalCarry(n,t){const e=(r,a)=>Number(r.split(".")[1]?.[a]??"0");let s=0,o=null;for(let r=t-1;r>=0;r--){const a=n.map(u=>e(u,r)),i=a.reduce((u,d)=>u+d,0)+s,l=Math.floor(i/10);l>0&&!o&&(o={place:r,digits:a,digitsSum:a.join(" + "),colSum:i}),s=l}return o?{...o,wholeCarry:s}:null}const dw$1=n=>decimalToWords(n).replace(/^zero point/,"nought point");function joinPlusWords(n){return n.map(t=>dw$1(t)).join(" plus ")}const MICRO$b="AS.P6.M03",eq$9=n=>({type:"equation_display",terms:n});function addition(n,t){const s=normaliseStem(n.stem).match(/\d+\.\d+/g);if(!s||s.length<2||s.length>3)return null;const o=stripNum$1(n.correctAnswer);if(!Number.isFinite(o))return null;const r=Math.max(...s.map(decPlaces),decPlaces(answerCore(n.correctAnswer)));if(r<1||r>3)return null;const a=10**r,i=s.map(E=>{const k=Math.round(parseFloat(E)*a),T=Math.floor(k/a),A=k-T*a;return{p:E,u:k,whole:T,frac:A,fracStr:`0.${String(A).padStart(r,"0")}`,padStr:parseFloat(E).toFixed(r)}}),l=i.reduce((E,k)=>E+k.u,0),u=Math.round(o*a);if(l!==u)return null;const d=l/a;if(d<=0||d>=1e6)return null;const m=trimZeros(d,r),h=i.reduce((E,k)=>E+k.whole,0),f=i.map(E=>E.padStr),g={visual_data:eq$9(addChain(s,"__")),animation:"line_up_points",caption:"Line up the decimal points.",spoken:`We need to work out ${joinPlusWords(s)}. Decimals only add up correctly when the points line up, so we line them up: tenths under tenths, hundredths under hundredths.`},b=`How do we work out ${s.join(" + ")}?`,x=firstDecimalCarry(f,r),y=h+(x?.wholeCarry??0);if(y>0){let E;if(x){const T=PLACE_WORDS[x.place]??"decimal",A=x.digits.map(P=>numberToWords(P)),N=A.length===2?`${A[0]} and ${A[1]}`:`${A.slice(0,-1).join(", ")} and ${A[A.length-1]}`,S=x.colSum%10,M=Math.floor(x.colSum/10);let L="";if(x.place>0){const P=x.place-1,F=(Z,Q)=>Number(Z.split(".")[1]?.[Q]??"0"),D=f.map(Z=>F(Z,P)),U=D.reduce((Z,Q)=>Z+Q,0)+M,q=PLACE_WORDS[P]??"decimal",V=U%10,O=U>=10,z=D.map(Z=>numberToWords(Z)),K=z.length===2?`${z[0]} and ${z[1]}`:`${z.slice(0,-1).join(", ")} and ${z[z.length-1]}`;L=` Then in the ${q}, ${K} and the carried ${numberToWords(M)} make ${numberToWords(U)} — we write ${numberToWords(V)} and carry ${O?carryUnitName(P,Math.floor(U/10)):"nothing"} across.`}E={visual_data:eq$9(addChain(f,"__")),animation:"add_decimal_parts",caption:`Add the ${T}
71 first.`,spoken:`So that every column lines up, we give every number the same number of decimal places. Then we add from the smallest place, on the right. In the ${T}, ${N} make ${numberToWords(x.colSum)} — we write ${numberToWords(S)} and carry ${carryUnitName(x.place,M)} across to the next place.${L}`}}else E={visual_data:eq$9(addChain(f,"__")),animation:"add_decimal_parts",caption:"Add the parts after the point first.",spoken:"So that every column lines up, we give every number the same number of decimal places. Then we add the parts after the point, starting on the right."};let k;if(h>0){const T=i.map(P=>String(P.whole)),A=trimZeros(d-y,r),N=y-h,S=N===1?` The carried whole from the point makes that ${numberToWordsLong(y)}.`:N>1?` The ${numberToWords(N)} carried wholes from the point make that ${numberToWordsLong(y)}.`:"",M=A!=="0",L=M?[String(y),"+",A,"=",m]:[String(y),"=",m];k={visual_data:eq$9([...addChain(T,String(h)),"→",...L]),caption:`Total: ${m}.`,spoken:`Now we add the whole numbers, which gives ${numberToWordsLong(h)}.${S}${M?` Joining on the part after the point, the total is ${dw$1(m)}.`:` So the total is ${dw$1(m)}.`}`}}else k={visual_data:eq$9(addChain(s,m)),caption:`Total: ${m}.`,spoken:`Every part after the point adds up to make ${numberToWordsLong(y)} whole${y===1?"":"s"}${d>y?" with some left over":""}. So the total is ${dw$1(m)}.`};return buildExample({generatorId:t,question:n,title:b,computed:{answer:d},steps:[g,E,{...k,animation:"reveal_total"}]})}return f.every((E,k)=>E===s[k])?null:buildExample({generatorId:t,question:n,title:b,computed:{answer:d},steps:[g,{visual_data:eq$9(addChain(f,"__")),animation:"same_places",caption:"Give the numbers the same number of places.",spoken:"So that every column lines up, we give both numbers the same number of decimal places, writing a placeholder zero where a digit is missing."},{visual_data:eq$9(addChain(f,m)),animation:"reveal_total",caption:`Total: ${m}.`,spoken:`Now we add each column, starting from the right, just as we do with whole numbers. The total is ${dw$1(m)}.`}]})}function missingAddend(n,t){const e=normaliseStem(n.stem);let s=null,o=null,r=e.match(/(\d+\.?\d*)\s*\+\s*__\s*=\s*(\d+\.?\d*)/);if(r?(s=r[1],o=r[2]):(r=e.match(/__\s*\+\s*(\d+\.?\d*)\s*=\s*(\d+\.?\d*)/),r&&(s=r[1],o=r[2])),!s||!o)return null;const a=stripNum$1(n.correctAnswer);if(!Number.isFinite(a))return null;const i=Math.max(decPlaces(s),decPlaces(o),decPlaces(answerCore(n.correctAnswer)));if(i<1||i>3)return null;const l=10**i,u=Math.round(parseFloat(o)*l)-Math.round(parseFloat(s)*l);if(u<=0||u!==Math.round(a*l))return null;const d=u/l,m=trimZeros(d,i),h=Math.round(parseFloat(s)*l),f=Math.round(parseFloat(o)*l),g=Math.ceil(h/l)*l,b=g>h&&g<f;let x,y;if(b){const E=trimZeros((g-h)/l,i),k=trimZeros((f-g)/l,i),T=trimZeros(g/l,i);x=[s,"+",E,"=",T,"→",T,"+",k,"=",o,"so",E,"+",k,"=",m],y=`Counting up is easier than taking away here. From ${dw$1(s)}, we jump ${dw$1(E)} to reach the whole number ${dw$1(T)}, then ${dw$1(k)} more to reach ${dw$1(o)}. The two jumps together make the missing part.`}else x=[s,"+",m,"=",o],y=`Working it out, ${dw$1(o)} take away ${dw$1(s)} leaves ${dw$1(m)}.`;return buildExample({generatorId:t,question:n,title:`How do we find the missing number in ${s} + __ = ${o}?`,computed:{answer:d},steps:[{visual_data:eq$9([s,"+","__","=",o]),animation:"show_equation",caption:"Find the missing part.",spoken:`We need the number that goes with ${dw$1(s)} to make ${dw$1(o)}. We know the whole and one part, but the other part is missing.`},{visual_data:eq$9(["__","=",o,"−",s]),animation:"rearrange_to_inverse",caption:`So: ${o} − ${s}.`,spoken:`To find a missing part, we take the part we have away from the whole. So we start with ${dw$1(o)} and take away ${dw$1(s)}, keeping the decimal points lined up.`},{visual_data:eq$9(x),animation:"reveal_missing",caption:`The missing part is ${m}.`,spoken:`${y} So the missing part is ${dw$1(m)}.`}]})}function comparison$2(n,t){const s=normaliseStem(n.stem).match(/greater:?\s*(\d+\.?\d*)\s*\+\s*(\d+\.?\d*),?\s*or\s*(\d+\.?\d*)\s*\+\s*(\d+\.?\d*)/i);if(!s)return null;const[o,r,a,i]=[s[1],s[2],s[3],s[4]],l=Math.max(...[o,r,a,i].map(decPlaces));if(l<1||l>3)return null;const u=10**l,d=Math.round(parseFloat(o)*u)+Math.round(parseFloat(r)*u),m=Math.round(parseFloat(a)*u)+Math.round(parseFloat(i)*u);if(d===m)return null;const h=`${o} + ${r}`,f=`${a} + ${i}`,g=d>m,b=g?h:f,x=F=>F.replace(/\s+/g," ").trim();if(x(n.correctAnswer)!==x(b))return null;const y=trimZeros(d/u,l),E=trimZeros(m/u,l),k=g?">":"<",T=parseFloat(o).toFixed(l),A=parseFloat(r).toFixed(l),N=parseFloat(a).toFixed(l),S=parseFloat(i).toFixed(l),M=firstDecimalCarry([T,A],l),L=firstDecimalCarry([N,S],l),P=(F,D,U,q)=>{if(q){const V=PLACE_WORDS[q.place]??"decimal",[O,z]=q.digits;return`In the ${V}, ${numberToWords(O)} and ${numberToWords(z)} make ${numberToWords(q.colSum)} and carry across. Lining up the points, ${dw$1(F)} plus ${dw$1(D)} makes ${dw$1(U)}.`}return`Lining up the points, ${dw$1(F)} plus ${dw$1(D)} makes ${dw$1(U)}.`};return buildExample({generatorId:t,question:n,title:`Which total is greater: ${h} or ${f}?`,computed:{answer:(g?d:m)/u},steps:[{visual_data:eq$9([h,"vs",f]),animation:"show_pair",caption:"Which total is greater?",spoken:`To see which total is greater, we must work each one out first, because the numbers on their own can trick us. The totals are ${dw$1(o)} plus ${dw$1(r)}, and ${dw$1(a)} plus ${dw$1(i)}.`},{visual_data:eq$9([`${T} + ${A}`,"=",y,"and",`${N} + ${S}`,"=",E]),animation:"show_sums",caption:"Work out each total.",spoken:`${P(o,r,y,M)} ${P(a,i,E,L)}`},{visual_data:eq$9([y,k,E]),animation:"reveal_greater",caption:`${b} is greater.`,spoken:g?`Now we compare the answers: ${dw$1(y)} is more than ${dw$1(E)}. So ${dw$1(o)} plus ${dw$1(r)} is the greater total.`:`Now we compare the answers: ${dw$1(E)} is more than ${dw$1(y)}. So ${dw$1(a)} plus ${dw$1(i)} is the greater total.`}]})}
71function judgement(n,t){const s=normaliseStem(n.stem).match(/(\d+\.?\d*)\s*\+\s*(\d+\.?\d*)\s+and writes\s+(\d+\.?\d*)/i);if(!s)return null;const[o,r,a]=[s[1],s[2],s[3]],i=Math.max(decPlaces(o),decPlaces(r),decPlaces(a));if(i<1||i>3)return null;const l=10**i,u=Math.round(parseFloat(o)*l)+Math.round(parseFloat(r)*l),d=Math.round(parseFloat(a)*l),m=u===d,h=n.correctAnswer.trim();if(m&&!isAffirmative(h)||!m&&!isNegative(h))return null;const f=trimZeros(u/l,i),g=parseFloat(o).toFixed(i),b=parseFloat(r).toFixed(i),x=firstDecimalCarry([g,b],i),y=x?(()=>{const E=PLACE_WORDS[x.place]??"decimal",[k,T]=x.digits.map(A=>numberToWords(A));return`In the ${E}, ${k} and ${T} make ${numberToWords(x.colSum)} — that carries a whole across, which is exactly the carry a wrong answer like ${dw$1(a)} drops. Adding the whole numbers too, ${dw$1(o)} plus ${dw$1(r)} really makes ${dw$1(f)}.`})():`Adding the parts after the point, then the whole numbers, ${dw$1(o)} plus ${dw$1(r)} really makes ${dw$1(f)}.`;return buildExample({generatorId:t,question:n,title:`Is ${o} + ${r} = ${a} correct?`,computed:{answer:u/l},steps:[{visual_data:eq$9([o,"+",r,"=",`${a}?`]),animation:"show_claim",caption:"Is this total right?",spoken:`A pupil added ${dw$1(o)} and ${dw$1(r)} and wrote ${dw$1(a)}. To check, we work it out ourselves with the decimal points lined up.`},{visual_data:eq$9([o,"+",r,"=",f]),animation:"work_it_out",caption:`Line them up and add: ${f}.`,spoken:y},{visual_data:eq$9(m?[a,"=",f]:[a,"≠",f]),animation:"verdict",caption:m?`Yes — ${a} is correct.`:`No — it should be ${f}.`,spoken:m?`The pupil's answer matches, so ${dw$1(a)} is correct.`:`The pupil's answer does not match, so it is wrong: the total should be ${dw$1(f)}.`}]})}function sequence$2(n,t){const s=normaliseStem(n.stem).match(/adds\s+(\d+\.?\d*)\s+each time:\s*([\d.,\s]+?)__/i);if(!s)return null;const o=s[1],r=s[2].match(/\d+\.?\d*/g)??[];if(r.length<2)return null;const a=stripNum$1(n.correctAnswer);if(!Number.isFinite(a))return null;const i=r[r.length-1],l=Math.max(decPlaces(o),...r.map(decPlaces),decPlaces(answerCore(n.correctAnswer)));if(l<1||l>3)return null;const u=10**l,d=Math.round(parseFloat(i)*u)+Math.round(parseFloat(o)*u);if(d!==Math.round(a*u))return null;const m=trimZeros(d/u,l);return buildExample({generatorId:t,question:n,title:`What is the next number in ${r.join(", ")}, __?`,computed:{answer:d/u},steps:[{visual_data:eq$9([...r,"__"]),animation:"show_pattern",caption:`The pattern adds ${o} each time.`,spoken:`To find the next number, we first look at how the pattern grows. Each number is ${dw$1(o)} more than the one before it.`},{visual_data:eq$9([i,"+",o,"=","__"]),animation:"add_step",caption:`Add ${o} to the last number.`,spoken:`So we add ${dw$1(o)} to the last number, ${dw$1(i)}, keeping the decimal points lined up.`},{visual_data:eq$9([...r,m]),animation:"reveal_next",caption:`The next number is ${m}.`,spoken:`Adding on, ${dw$1(i)} plus ${dw$1(o)} is ${dw$1(m)}. So the next number in the pattern is ${dw$1(m)}.`}]})}const AS_P6_M03_GENERATORS=[{id:"AS.P6.M03.align-decimals.v1",microSkillId:MICRO$b,forms:["symbolic_calculation","context_calculation","missing_addend","comparison_statement","worked_example_judgement","varied_symbolic_calculation"],generate(n){switch(n.mathematicalForm){case"missing_addend":return missingAddend(n,this.id);case"comparison_statement":return comparison$2(n,this.id);case"worked_example_judgement":return judgement(n,this.id);case"varied_symbolic_calculation":return sequence$2(n,this.id);case"symbolic_calculation":case"context_calculation":return addition(n,this.id);default:return null}}}],MICRO$a="AS.P6.M04",isDecimal=n=>/^\d+(?:\.\d+)?$/.test(n),dp=n=>n.includes(".")?n.split(".")[1].length:0,scaled=(n,t)=>Math.round(Number(n)*10**t);function pad(n,t){if(t===0)return n.replace(/\.$/,"");const[e,s=""]=n.split(".");return`${e}.${s.padEnd(t,"0")}`}const cleanAnswer=n=>n.trim().replace(/[^\d.]/g,""),decimalTokens=n=>normaliseStem(n).match(/\d+(?:\.\d+)?/g)??[],capWord=n=>n.charAt(0).toUpperCase()+n.slice(1);function countUpHops(n,t,e){const s=10**e,o=scaled(t,e),r=scaled(n,e),a=[];let i=r,l=!1;if(i%s!==0){let h=(Math.floor(i/s)+1)*s;if(l=!0,h>=o&&e>=1){const g=s/10,b=(Math.floor(i/g)+1)*g;b>i&&b<o&&(h=b,l=!1)}const f=Math.min(h,o);f>i&&(a.push(f-i),i=f)}i<o&&a.push(o-i);const u=h=>{if(e===0)return String(h);const f=(h/s).toFixed(e);if(!f.includes("."))return f;const g=f.replace(/0+$/,"").replace(/\.$/,"");return g===""?"0":g},d=a.map(u),m=d.map(Number);return{hopStrs:d,hopNums:m,bridgesToWhole:l}}function detailedHop(n,t,e,s){const o=10**e,r=Math.round(t*o),a=[];for(let f=1;f<=r;f*=10){const g=Math.floor(r/f)%10;g&&a.push(g*f)}s||a.reverse();const i=f=>String(Number((f/o).toFixed(e)));let l=Math.round(n*o);const u=[],d=[],m=[n,t];for(const f of a){const g=i(l),b=i(f);l+=f;const x=i(l);u.push(`${g} + ${b} = ${x}`),d.push(`From ${decimalToWords(g)}, add ${decimalToWords(b)} to reach ${decimalToWords(x)}.`),m.push(Number(g),Number(b),Number(x))}const h=a.map(i);return u.push(`${h.join(" + ")} = ${i(r)}`),d.push(`Those jumps total ${decimalToWords(i(r))}.`),{equations:u,spoken:d.join(" "),values:m}}function buildCountUpSubtraction(n){const{q:t,generatorId:e,title:s,step1Terms:o,aStr:r,bStr:a,scale:i,answerNum:l,answerDisplay:u,mistakeNote:d,step1Note:m}=n,{hopStrs:h,hopNums:f,bridgesToWhole:g}=countUpHops(a,r,i),b=[];h.forEach((D,U)=>{U>0&&b.push("+"),b.push(D)});const x={answer:l};f.forEach((D,U)=>x[`hop${U+1}`]=D);const y=h.length===2,E=(scaled(a,i)+scaled(h[0],i))/10**i,T=i===3&&scaled(r,i)%10<scaled(a,i)%10&&y?0:i<=2&&y&&f[1]>=10&&E%10!==0?1:-1,A=T<0?null:detailedHop(T===0?Number(a):E,f[T],i,T===0);
71A&&(A.values.forEach((D,U)=>{x[`detail${U}`]=D}),x.start=Number(a),x.bridge=E,x.end=Number(r));const N=[];A?.equations.forEach((D,U)=>{U>0&&N.push(U===A.equations.length-1?"so":"→"),N.push(D)});const S=g?"a whole":"a friendly point",M=y?`Count up ${h[0]} to ${S}, then ${h[1]} more.`:`Count up ${h[0]} to reach the answer.`,L=y?`We count up from the smaller number. First a small jump of ${decimalToWords(h[0])} reaches ${g?"a whole number":"a friendly point in between"}, then a bigger jump of ${decimalToWords(h[1])} reaches the larger number.`:`We count up from the smaller number. One jump of ${decimalToWords(h[0])} reaches the larger number — that whole jump is the gap between them.`,P=y?`${h[0]} + ${h[1]} = ${u}`:`The gap is ${u}.`,F=y?`${capWord(decimalToWords(h[0]))} add ${decimalToWords(h[1])} makes ${decimalToWords(u)} — the total distance we counted, and the answer.${d??""}`:`That jump of ${decimalToWords(h[0])} is the whole gap, so the answer is ${decimalToWords(u)}.${d??""}`;return buildExample({generatorId:e,question:t,title:s,computed:x,steps:[{visual_data:{type:"equation_display",terms:o},animation:"show_start",caption:"Count up from the smaller number to the larger.",spoken:`To subtract, we can count up from the smaller number until we reach the larger one. The total distance we travel is the answer.${m??""}`},{visual_data:{type:"equation_display",terms:[...N,...A?["so"]:[],...b,"=","__"],...A?{fit:!0}:{}},animation:"count_up_jumps",caption:A?"Build the difficult jump one place at a time.":M.length<=72?M:"Count up in friendly jumps.",spoken:A?T===0?`${A.spoken} We have reached ${decimalToWords(String(E))}. Add ${decimalToWords(h[1])} more to reach ${decimalToWords(r)}. Now add the two jump totals.`:`First add ${decimalToWords(h[0])} to ${decimalToWords(a)} to reach ${decimalToWords(String(E))}. ${A.spoken} Now add the two jump totals.`:L},{visual_data:{type:"equation_display",terms:[...N,...A?["so"]:[],...b,"=",u],...A?{fit:!0}:{}},animation:"write_answer",caption:P.length<=72?P:`The answer is ${u}.`,spoken:F}]})}function buildMoneyPenceThenPounds(n){const{q:t,generatorId:e,aPad:s,bPad:o,answerNum:r,answerDisplay:a}=n,[i,l]=s.split(".").map(Number),[u,d]=o.split(".").map(Number),m=l<d,h=m?l+100:l,f=m?i-1:i,g=h-d,b=f-u,x=S=>`${S}p`,y=S=>`${numberToWords(S)} pence`,E=S=>`${numberToWords(S)} pound${S===1?"":"s"}`,k=m?`Pence: ${x(l)} − ${x(d)} won't go. Swap £1 for 100p: ${x(h)} − ${x(d)} = ${x(g)}.`:`Pence first: ${x(l)} − ${x(d)} = ${x(g)}.`,T=m?`Pence first. ${capWord(y(l))} take away ${y(d)} won't go, so we swap one pound for a hundred pence. That makes ${y(h)}, and ${y(h)} take away ${y(d)} is ${y(g)}.`:`Pence first. ${capWord(y(l))} take away ${y(d)} is ${y(g)}.`,A=m?`Pounds: £${f} − £${u} = £${b}. So £${a}.`:`Then pounds: £${i} − £${u} = £${b}. So £${a}.`,N=`${m?`We swapped one pound, so ${E(i)} is now ${E(f)}. `:"Then the pounds. "}${capWord(E(f))} take away ${E(u)} is ${E(b)}. Put the pounds and pence together: the answer is ${E(b)} and ${y(g)}.`;return buildExample({generatorId:e,question:t,title:"Take away the pence, then the pounds",computed:{answer:r,aPounds:i,aPence:l,bPounds:u,bPence:d,topPence:h,topPounds:f,pence:g,pounds:b,...m?{exchangePence:100,exchangePound:1}:{}},steps:[{visual_data:{type:"equation_display",terms:[`£${s}`,"−",`£${o}`]},animation:"show_start",caption:`Line up the pounds and pence: £${s} − £${o}.`,spoken:"Line up the pounds and the pence. We take away the pence first, then the pounds."},{visual_data:{type:"equation_display",terms:[x(h),"−",x(d),"=",x(g)]},animation:"pence_first",caption:k,spoken:T},{visual_data:{type:"equation_display",terms:[`£${f}`,"−",`£${u}`,"=",`£${b}`]},animation:"write_answer",caption:A,spoken:N}]})}function buildTwoStepSubtraction(n){const{q:t,generatorId:e,title:s,operands:o,answerNum:r,answerDisplay:a}=n,i=Math.max(...o.map(dp),dp(a)),l=o.map(y=>pad(y,i)),u=y=>i===0?String(y):(y/10**i).toFixed(i);let d=scaled(l[0],i);const m=[l[0]];for(let y=1;y<l.length;y++)d-=scaled(l[y],i),m.push(u(d));const h=l.length-1,f={answer:r};m.forEach((y,E)=>f[`p${E}`]=Number(y));const g=`First: ${l[0]} − ${l[1]} = ${m[1]}
71.`,b=`${m[h-1]} − ${l[h]} = ${a}`,x=[l[0]];for(let y=1;y<=h;y++)x.push("−",l[y]);return buildExample({generatorId:e,question:t,title:s,computed:f,steps:[{visual_data:{type:"equation_display",terms:x},animation:"show_start",caption:"This happens in two steps — work them out one at a time.",spoken:"Two things happen one after the other. We work out the first step, then use that result for the second step."},{visual_data:{type:"equation_display",terms:[l[0],"−",l[1],"=",m[1]]},animation:"work_first_step",caption:g.length<=72?g:`First find ${m[1]}, then subtract again.`,spoken:`First, ${decimalToWords(l[0])} take away ${decimalToWords(l[1])} leaves ${decimalToWords(m[1])}. Now take away the next amount as well.`},{visual_data:{type:"equation_display",terms:[m[h-1],"−",l[h],"=",a]},animation:"write_answer",caption:b.length<=72?b:`The answer is ${a}.`,spoken:`Then ${decimalToWords(m[h-1])} take away ${decimalToWords(l[h])} leaves ${decimalToWords(a)}, the amount remaining.`}]})}function moneyCarryNarration(n,t){const e=(n.split(".")[1]??"00").padEnd(2,"0"),s=(t.split(".")[1]??"00").padEnd(2,"0"),o=Number(e[1]),r=Number(s[1]),a=Number(e[0]),i=Number(s[0]),l=o+r,u=l>=10?1:0,d=a+i+u,m=d>=10?1:0,h=`In the hundredths, the pence, ${numberToWords(o)} and ${numberToWords(r)} make ${numberToWords(l)}${u?` — write ${numberToWords(l%10)} and carry one tenth across`:""}.`,f=` Then in the tenths, ${numberToWords(a)} and ${numberToWords(i)}${u?" plus the carried one":""} make ${numberToWords(d)}${m?` — write ${numberToWords(d%10)} and carry a whole across`:""}.`;return h+f}function buildReasonedAddition(n){const{q:t,generatorId:e,spentStr:s,leftStr:o,answerNum:r}=n,a=Math.max(dp(s),dp(o)),i=pad(s,a),l=pad(o,a),u=10**a,m=scaled(i,a)%u+scaled(l,a)%u>=u,h=pad(String(r),a),f=`${i} + ${l} = ${h}`,g=m&&a===2?moneyCarryNarration(i,l):"";return buildExample({generatorId:e,question:t,title:"Find the start amount",computed:{answer:r,spent:Number(i),left:Number(l)},steps:[{visual_data:{type:"equation_display",terms:[i,"+",l]},animation:"show_start",caption:"Put the spent amount back with what is left.",spoken:"Some money was spent, and some is left over. To find out how much there was at the start, put the part that was spent back together with the part that is left — the start is both amounts added."},{visual_data:{type:"equation_display",terms:[i,"+",l,"=","__"]},animation:"work_columns",caption:m?"Work the hundredths, then the tenths — carry into the next place.":"Add each place, starting from the right.",spoken:m?`We add the smallest place first, then work left.${g?` ${g}`:""}`:"With the points lined up, add each column, starting from the right."},{visual_data:{type:"equation_display",terms:[i,"+",l,"=",h]},animation:"write_answer",caption:f.length<=72?f:`The start amount is ${h}.`,spoken:`Putting them together, ${decimalToWords(i)} add ${decimalToWords(l)} makes ${decimalToWords(h)}, the amount at the start.`}]})}function buildAligned(n){const{q:t,generatorId:e,title:s,operands:o,op:r,answerNum:a,answerDisplay:i,padded:l}=n,u=Math.max(...o.map(dp),dp(i)),d=o.map(T=>pad(T,u)),m=o.slice(),h=T=>{const A=[];return T.forEach((N,S)=>{S>0&&A.push(r),A.push(N)}),A},f=h(m);if(r==="−"&&o.length===2)return buildCountUpSubtraction({q:t,generatorId:e,title:s,step1Terms:f,aStr:d[0],bStr:d[1],scale:u,answerNum:a,answerDisplay:i});const g=[...h(d),"=","__"],b=[...h(d),"=",i],x=l?"Add zeros so every number has the same places.":"Add each place, starting from the right.",y=l?"We fill the empty places with zeros so both numbers reach the same length. Now every column lines up.":"With the points lined up, we add each column, starting from the right.",E=`${d.join(` ${r} `)} = ${i}`;let k;return o.length===2?k=`${decimalToWords(d[0])} add ${decimalToWords(d[1])} makes ${decimalToWords(i)}.`:k=`Working column by column, the answer is ${decimalToWords(i)}.`,buildExample({generatorId:e,question:t,title:s,computed:{answer:a},steps:[{visual_data:{type:"equation_display",terms:f},animation:"show_start",caption:"Line up the decimal points.",spoken:"Before we can work this out, the decimal points must line up so each digit sits in its own place."},{visual_data:{type:"equation_display",terms:g},animation:l?"pad_zeros":"work_columns",caption:x,spoken:y},{visual_data:{type:"equation_display",terms:b},animation:"write_answer",caption:E.length<=72?E:`The answer is ${i}.`,spoken:k}]})}const SYMBOLIC$1=/^(\d+(?:\.\d+)?) - (\d+(?:\.\d+)?) = __$/,symbolic$2={id:"AS.P6.M04.symbolic.v1",microSkillId:MICRO$a,forms:["symbolic_calculation","varied_symbolic_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(SYMBOLIC$1);if(!t)return null;const[,e,s]=t,o=cleanAnswer(n.correctAnswer);if(!isDecimal(e)||!isDecimal(s)||!isDecimal(o))return null;const r=Math.max(dp(e),dp(s),dp(o));if(scaled(e,r)-scaled(s,r)!==scaled(o,r))return null;const a=pad(o,Math.max(dp(e),dp(s)));return buildAligned({q:n,generatorId:this.id,title:`Work out ${e} − ${s}`,operands:[e,s],op:"−",answerNum:Number(o),answerDisplay:a,padded:dp(e)!==dp(s)})}},COMPLETION=/^(\d+(?:\.\d+)?) - __ = (\d+(?:\.\d+)?)\. What number is missing\?$/,completion={id:"AS.P6.M04.completion.v1",microSkillId:MICRO$a,forms:["equation_completion"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem).match(COMPLETION);if(!t)return null;const[,e,s]=t,o=cleanAnswer(n.correctAnswer);if(!isDecimal(e)||!isDecimal(s)||!isDecimal(o))return null;const r=Math.max(dp(e),dp(s),dp(o));if(scaled(e,r)-scaled(s,r)!==scaled(o,r))return null;const a=Math.max(dp(e),dp(s)),i=pad(e,a),l=pad(s,a),u=pad(o,a),d=Number(o),{hopStrs:m,hopNums:h,bridgesToWhole:f}=countUpHops(l,i,a),g=[];m.forEach((N,S)=>{S>0&&g.push("+"),g.push(N)});const b=m.length===2,x={answer:d};h.forEach((N,S)=>x[`hop${S+1}`]=N);const y=f?"a whole":"a friendly point",E=b?`Turn it around: count up ${m[0]} to ${y}, then ${m[1]} more.`:`Turn it around: count up ${m[0]}.`,k=b?`To find the missing part, take the answer away from the start — count up from ${decimalToWords(l)} instead. First a jump of ${decimalToWords(m[0])} reaches ${f?"a whole number":"a friendly point in between"}, then ${decimalToWords(m[1])} more reaches the start.`:`To find the missing part, take the answer away from the start — count up from ${decimalToWords(l)} instead. One jump of ${decimalToWords(m[0])} reaches the start.`,T=b?`${m[0]} + ${m[1]} = ${u}`:`The missing number is ${u}.`,A=b?`${capWord(decimalToWords(m[0]))} add ${decimalToWords(m[1])} makes ${decimalToWords(u)}, so the missing number is ${decimalToWords(u)}.`:`That jump of ${decimalToWords(m[0])} is the missing number: ${decimalToWords(u)}.`;return buildExample({generatorId:this.id,question:n,title:`Find the missing number: ${e} − __ = ${s}`,computed:x,steps:[{visual_data:{type:"equation_display",terms:[e,"−","?","=",s]},animation:"show_start",caption:`Something taken from ${e} leaves ${s}
71.`,spoken:`Something is taken away from ${decimalToWords(e)} and leaves ${decimalToWords(s)}. We need the part that was taken.`},{visual_data:{type:"equation_display",terms:[...g,"=","__"]},animation:"count_up_jumps",caption:E.length<=72?E:"Turn it around and count up.",spoken:k},{visual_data:{type:"equation_display",terms:[...g,"=",u]},animation:"write_answer",caption:T.length<=72?T:`The missing number is ${u}.`,spoken:A}]})}},SPENT_LEFT_RE=/spent (£?\d+(?:\.\d+)?) and had (£?\d+(?:\.\d+)?) left/i,CHECK_MISTAKE_NOTE=" Compare that result with the number in the question: the count-up gives the correct difference.",worded={id:"AS.P6.M04.worded.v1",microSkillId:MICRO$a,forms:["context_calculation","subtraction_story_context","comparison_statement","check_reasoning_probe","context_reasoning"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=cleanAnswer(n.correctAnswer);if(!isDecimal(e))return null;const s=Number(e);if(n.mathematicalForm==="context_reasoning"){const u=t.match(SPENT_LEFT_RE);if(u){const d=cleanAnswer(u[1]),m=cleanAnswer(u[2]),h=Math.max(dp(d),dp(m),dp(e));if(scaled(d,h)+scaled(m,h)===scaled(e,h))return buildReasonedAddition({q:n,generatorId:this.id,spentStr:d,leftStr:m,answerNum:s})}}const o=decimalTokens(n.stem);if(o.length<2)return null;const r=n.correctAnswer.includes("£"),a="Subtract the decimals",i=n.mathematicalForm==="check_reasoning_probe"?CHECK_MISTAKE_NOTE:void 0,l=n.mathematicalForm==="comparison_statement"&&/\bfaster\b|\bquicker\b/i.test(t)?" Faster means a smaller time, so the gap is the slower time minus the faster time.":void 0;for(let u=0;u<o.length;u++)for(let d=0;d<o.length;d++){if(u===d)continue;const m=o[u],h=o[d],f=Math.max(dp(m),dp(h),dp(e)),g=scaled(m,f),b=scaled(h,f);if(g>b&&g-b===scaled(e,f)){const x=Math.max(dp(m),dp(h));if(r&&x<=2&&!i&&!l){const y=pad(m,2),E=pad(h,2),{hopNums:k}=countUpHops(E,y,2);if(Math.max(...k)>=10&&Number(E.split(".")[0])>=1)return buildMoneyPenceThenPounds({q:n,generatorId:this.id,aPad:y,bPad:E,answerNum:s,answerDisplay:pad(e,2)})}return buildCountUpSubtraction({q:n,generatorId:this.id,title:a,step1Terms:[m,"−",h],aStr:pad(m,x),bStr:pad(h,x),scale:x,answerNum:s,answerDisplay:pad(e,x),mistakeNote:i,step1Note:l})}}for(let u=0;u<o.length;u++)for(let d=u+1;d<o.length;d++){const m=o[u],h=o[d],f=Math.max(dp(m),dp(h),dp(e));if(scaled(m,f)+scaled(h,f)===scaled(e,f)){const g=Math.max(dp(m),dp(h));return buildAligned({q:n,generatorId:this.id,title:r?"Add the amounts":"Add the decimals",operands:[m,h],op:"+",answerNum:s,answerDisplay:pad(e,g),padded:dp(m)!==dp(h)})}}if(o.length>=3){const u=Math.max(...o.map(dp),dp(e)),d=o.map(g=>({t:g,v:scaled(g,u)})),m=d.reduce((g,b)=>b.v>g.v?b:g),h=d.filter(g=>g!==m),f=h.reduce((g,b)=>g+b.v,0);if(m.v-f===scaled(e,u)){const g=[m.t,...h.map(x=>x.t)],b=Math.max(...g.map(dp));return buildTwoStepSubtraction({q:n,generatorId:this.id,title:a,operands:g,answerNum:s,answerDisplay:pad(e,b)})}}return null}},AS_P6_M04_GENERATORS=[symbolic$2,completion,worded];function leadingPlace(n){const t=Math.floor(Math.abs(n));return t<1?1:10**(String(t).length-1)}function estimatePlaceCap(n){const t=leadingPlace(Math.max(...n));return t<1e4||n.every(e=>e%(t/10)===0)?t:t/10}function roundToPlace(n,t){return Math.round(n/t)*t}function say(n){return Number.isInteger(n)?numberToWordsLong(n):decimalToWords(n)}function placeWord$1(n){switch(n){case 1:return"whole number";case 10:return"ten";case 100:return"hundred";case 1e3:return"thousand";case 1e4:return"ten thousand";case 1e5:return"hundred thousand";default:return"friendly number"}}function stemNumbers(n){return(normaliseStem(n).match(/\d[\d,]*(?:\.\d+)?/g)??[]).map(s=>Number(s.replace(/,/g,"")))}function bestChoiceOperands(n){return stemNumbers(normaliseStem(n).replace(/:[^:?]*\bor\b[^:?]*\?/i,""))}function labelValue(n){const t=n.replace(/,/g,"").replace(/[^0-9.]/g,"");return t===""?NaN:Number(t)}const opSymbol=n=>n==="+"?"+":"−";function interleave(n,t){const e=[n[0]];for(let s=0;s<t.length;s++)e.push(opSymbol(t[s]),n[s+1]);return e}function exprString(n,t){let e=String(n[0]);for(let s=0;s<t.length;s++)e+=` ${opSymbol(t[s])} ${n[s+1]}`;return e}function evaluate(n,t){let e=n[0];for(let s=0;s<t.length;s++)e=t[s]==="+"?e+n[s+1]:e-n[s+1];return e}function findCombo(n,t){const e=n.some(r=>!Number.isInteger(r)),s=estimatePlaceCap(n),o=n.length===2?[["+"],["-"]
71]:n.length===3?[["+","+"],["+","-"]]:n.length===4?[["+","+","+"]]:[];for(let r=s;r>=1&&!(r<10&&!e);r/=10){const a=n.map(i=>roundToPlace(i,r));if(!a.some(i=>i===0)){for(const i of o)if(evaluate(a,i)===t)return{nums:n,rounded:a,ops:i,place:r,result:t}}}return null}function verbForOps(n){return n.every(t=>t==="+")?"add the tidy numbers":n.length===1&&n[0]==="-"?"take one tidy number from the other":"work left to right with the tidy numbers"}function buildExpressionDemo(n,t,e){const{nums:s,rounded:o,ops:r,place:a,result:i}=e,l=interleave(s,r),u=interleave(o,r),d=[...u,"=",i],m={answer:i,result:i};s.forEach((f,g)=>m[`n${g}`]=f),o.forEach((f,g)=>m[`r${g}`]=f);const h=s.map((f,g)=>`${say(f)} becomes ${say(o[g])}`).join(", ");return buildExample({generatorId:n,question:t,title:`How do we estimate ${exprString(s,r)}?`,computed:m,steps:[{visual_data:{type:"equation_display",terms:l},animation:"show_exact",caption:`Estimate ${exprString(s,r)}.`,spoken:"We only need a close answer, not the exact one. The trick is to round each number to a friendly value first, then the sum is easy."},{visual_data:{type:"equation_display",terms:u},animation:"round_each",caption:`Round each to the nearest ${placeWord$1(a)}.`,spoken:`Round each number to the nearest ${placeWord$1(a)}: ${h}.`},{visual_data:{type:"equation_display",terms:d},animation:"reveal_estimate",caption:`${exprString(o,r)} = ${i}.`,spoken:`Now ${verbForOps(r)}: that gives about ${say(i)}. That is our estimate.`}]})}const roundingEntry$1={id:"AS.P6.M05.rounding-entry.v1",microSkillId:"AS.P6.M05",forms:["rounding_entry"],generate(n){const t=stemNumbers(n.stem);if(t.length<2||t.length>4)return null;const e=answerInt(n);if(e===null)return null;const s=findCombo(t,e);return s?buildExpressionDemo(this.id,n,s):null}},bestChoice={id:"AS.P6.M05.best-choice.v1",microSkillId:"AS.P6.M05",forms:["estimate_best_choice"],generate(n){const t=comparePairsDemo(this.id,n);if(t)return t;const e=labelValue(n.correctAnswer);if(!Number.isFinite(e))return null;const s=bestChoiceOperands(n.stem);if(s.length<2||s.length>4)return null;const o=findCombo(s,e);return o?buildExpressionDemo(this.id,n,o):null}},COMPARE_PAIRS=/closer to (\d[\d,]*): the first \((\d[\d,]*) \+ (\d[\d,]*)\) or the second \((\d[\d,]*) \+ (\d[\d,]*)\)/i;function comparePairsDemo(n,t){const e=normaliseStem(t.stem).match(COMPARE_PAIRS);if(!e)return null;const[s,o,r,a,i]=e.slice(1).map(A=>Number(A.replace(/,/g,"")));if([s,o,r,a,i].some(A=>A<1||A>999999))return null;const l=leadingPlace(Math.max(o,r,a,i)),u=roundToPlace(o,l),d=roundToPlace(r,l),m=roundToPlace(a,l),h=roundToPlace(i,l);if([u,d,m,h].some(A=>A===0))return null;const f=u+d,g=m+h,b=Math.abs(f-s),x=Math.abs(g-s);if(b===x)return null;const y=b<x,E=/first/i.test(t.correctAnswer),k=/second/i.test(t.correctAnswer);if(!E&&!k||y!==E)return null;const T=y?f:g;return buildExample({generatorId:n,question:t,title:`Which pair totals closer to ${s}?`,computed:{answer:T,target:s,a:o,b:r,c:a,d:i,ra:u,rb:d,rc:m,rd:h,s1:f,s2:g},steps:[{visual_data:{type:"equation_display",terms:[u,"+",d,"=",f]},animation:"round_first_pair",caption:`First pair rounds to about ${f}.`,spoken:`Round the first pair and add: ${say(u)} and ${say(d)} make about ${say(f)}.`},{visual_data:{type:"equation_display",terms:[m,"+",h,"=",g]},animation:"round_second_pair",caption:`Second pair rounds to about ${g}.`,spoken:`Round the second pair and add: ${say(m)} and ${say(h)} make about ${say(g)}.`},{visual_data:{type:"equation_display",terms:[T]},animation:"pick_closer",caption:`${T} is closer to ${s}.`,spoken:`Compare each estimate with ${say(s)}. ${capitalised(say(T))} is nearer, so the ${y?"first":"second"} pair is closer.`}]})}const REQUIREMENT_FIRST=/\bneeds\b[^.]*\bfor\b/i,sufficiency={id:"AS.P6.M05.sufficiency.v1",microSkillId:"AS.P6.M05",forms:["estimate_sufficiency"],generate(n){let t=stemNumbers(n.stem);if(t.length===4&&t[3]===t[0]&&(t=t.slice(0,3)),t.length!==3)return null;const[e,s,o]=t;if([e,s,o].some(l=>l<1||l>999999))return null;const r=isAffirmative(n.correctAnswer),a=REQUIREMENT_FIRST.test(normaliseStem(n.stem)),i=estimatePlaceCap([s,o]
71);for(let l=i;l>=10;l/=10){const u=roundToPlace(s,l),d=roundToPlace(o,l);if(u===0||d===0)continue;const m=u+d;if(m===e)continue;const h=a?m>=e:m<=e;if(h===r)return buildSufficiencyDemo(this.id,n,{A:s,B:o,L:e,rA:u,rB:d,S:m,place:l,enough:h,requirementFirst:a})}return null}};function buildSufficiencyDemo(n,t,e){const{A:s,B:o,L:r,rA:a,rB:i,S:l,place:u,enough:d,requirementFirst:m}=e,h=d?`About ${l} — that's enough.`:m?`About ${l} — not enough.`:`About ${l} — over ${r}.`,f=l>r?"more":"less",g=d?`${capitalised(say(l))} is ${f} than ${say(r)}, so there is enough.`:m?`${capitalised(say(l))} is ${f} than ${say(r)}, so there is not enough.`:`${capitalised(say(l))} is ${f} than ${say(r)}, so it goes over.`;return buildExample({generatorId:n,question:t,title:`Is there enough? Estimate ${s} + ${o}.`,computed:{answer:l,total:l,limit:r,aExact:s,bExact:o,rA:a,rB:i},steps:[{visual_data:{type:"equation_display",terms:[s,"+",o]},animation:"show_exact",caption:`Estimate ${s} + ${o}.`,spoken:"To check quickly we do not need the exact total — a rough one is enough. Round each amount to a friendly value first."},{visual_data:{type:"equation_display",terms:[a,"+",i]},animation:"round_each",caption:`Round each to the nearest ${placeWord$1(u)}.`,spoken:`Round each to the nearest ${placeWord$1(u)}: ${say(s)} becomes ${say(a)}, and ${say(o)} becomes ${say(i)}.`},{visual_data:{type:"equation_display",terms:[a,"+",i,"=",l]},animation:"compare_to_limit",caption:h,spoken:`Add the tidy amounts to get about ${say(l)}. ${g}`}]})}const CLAIM_STEM=/(\d[\d,]*(?:\.\d+)?) ([+\-]) (\d[\d,]*(?:\.\d+)?) and (?:writes|gets) (\d[\d,]*(?:\.\d+)?)/i,reasonableness={id:"AS.P6.M05.reasonableness.v1",microSkillId:"AS.P6.M05",forms:["estimate_reasonableness_check"],generate(n){const t=normaliseStem(n.stem).match(CLAIM_STEM);if(!t)return null;const e=Number(t[1].replace(/,/g,"")),s=t[2]==="+"?"+":"-",o=Number(t[3].replace(/,/g,"")),r=Number(t[4].replace(/,/g,""));if([e,o,r].some(m=>!Number.isFinite(m)||m<0||m>999999))return null;const a=Math.max(leadingPlace(Math.max(e,o))/10,1),i=roundToPlace(e,a),l=roundToPlace(o,a);if(i===0||l===0)return null;const u=s==="+"?i+l:i-l;if(u<0)return null;const d=Math.abs(r-u)<=a;return d!==isAffirmative(n.correctAnswer)?null:buildExample({generatorId:this.id,question:n,title:`Is the answer reasonable? Estimate ${e} ${opSymbol(s)} ${o}.`,computed:{answer:u,est:u,claim:r,aExact:e,bExact:o,rA:i,rB:l},steps:[{visual_data:{type:"equation_display",terms:[e,opSymbol(s),o]},animation:"show_exact",caption:`Estimate ${e} ${opSymbol(s)} ${o}.`,spoken:"To check an answer, work out roughly how big it should be. Round each number to a friendly value first."},{visual_data:{type:"equation_display",terms:[i,opSymbol(s),l]},animation:"round_each",caption:`Round each to the nearest ${placeWord$1(a)}.`,spoken:`Round each to the nearest ${placeWord$1(a)}: ${say(e)} becomes ${say(i)}, and ${say(o)} becomes ${say(l)}.`},{visual_data:{type:"equation_display",terms:[i,opSymbol(s),l,"=",u]},animation:"compare_to_claim",caption:d?`About ${u} — the answer fits.`:`About ${u}, not ${r} — no.`,spoken:d?`A good estimate is about ${say(u)}, which is close to ${say(r)}, so the answer is reasonable.`:`A good estimate is about ${say(u)}. ${capitalised(say(r))} is far from ${say(u)}, so the answer is not reasonable.`}]})}},KM_CLAIM_STEM=/(\d[\d,]*) m .*? and (\d[\d,]*) m .*?'about (\d+(?:\.\d+)?) km'/i,reasonablenessKm={id:"AS.P6.M05.reasonableness-km.v1",microSkillId:"AS.P6.M05",forms:["estimate_reasonableness_check"],generate(n){const t=normaliseStem(n.stem).match(KM_CLAIM_STEM);if(!t)return null;const e=Number(t[1].replace(/,/g,"")),s=Number(t[2].replace(/,/g,"")),o=Number(t[3]);if([e,s,o].some(h=>!Number.isFinite(h)||h<0||h>999999))return null;const r=1e3,a=h=>Math.round(h/r)*r,i=a(e),l=a(s);if(i===0||l===0)return null;const u=i+l,d=u/1e3,m=Math.abs(o-d)<=2;return m&&!isAffirmative(n.correctAnswer)||!m&&!isNegative(n.correctAnswer)?null:buildExample({generatorId:this.id,question:n,title:`Is 'about ${o} km' sensible?`,computed:{answer:u,estM:u,estKm:d,claimKm:o,aExact:e,bExact:s,rA:i,rB:l},steps:[{visual_data:{type:"equation_display",terms:[e,"+",s]},animation:"show_exact",caption:`Estimate ${e} m + ${s} m.`,spoken:"To check a distance claim, first estimate. Round each distance to the nearest thousand metres — that is a whole number of kilometres."},{visual_data:{type:"equation_display",terms:[i,"+",l]},animation:"round_each",caption:"Round each to the nearest thousand metres.",spoken:`${cap$d(say(e))} metres rounds to ${say(i)}, and ${say(s)} metres rounds to ${say(l)}.`},{visual_data:{type:"equation_display",terms:[i,"+",l,"=",u]},animation:"compare_to_claim",caption:m?`About ${d} km — close to the claim.`:`About ${d} km, not ${o} km — no.`,spoken:m?`Every thousand metres is one kilometre, so the estimate is about ${say(d)} kilometres — close to the claim, so it is sensible.`:`Every thousand metres is one kilometre, so the estimate is about ${say(d)} kilometres — far from the claim, so it is not sensible.`}]})}};function cap$d(n){return n.charAt(0).toUpperCase()+n.slice(1)}const AS_P6_M05_GENERATORS=[roundingEntry$1,bestChoice,sufficiency,reasonableness,reasonablenessKm],NUM=String.raw`\d[\d,]*(?:\.\d+)?`;function parseNum(n){return Number(n.replace(/[£,\s]/g,""))}function dpOf$2(...n){return n.reduce((t,e)=>{const s=e.match(/\.(\d+)/);return Math.max(t,s?s[1].length:0)},0)}function grp(n){return n.replace(/\B(?=(\d{3})+(?!\d))/g,",")}function fmtTerm(n,t){const e=t>0?n.toFixed(t):String(Math.round(n)),s=e.startsWith("-"),[o,r]=(s?e.slice(1):e).split(".");return(s?"-":"")+grp(o)+(r?"."+r:"")}function fmtCap(n,t){return t>0?n.toFixed(t):String(Math.round(n))}function words$3(n,t){return t>0?decimalToWords(n.toFixed(t)):numberToWordsLong(Math.round(n))}function eqAt(n,t,e){const s=10**e;return Math.round(n*s)===Math.round(t*s)}const opSym$1=n=>n==="+"?"+":"−";function eqVisual$5(n){return{type:"equation_display",terms:n}}function inWordRange(...n){return n.every(t=>Number.isFinite(t)&&t>=0&&Math.round(t)<=999999)}const PLACES$1={ten:10,hundred:100,thousand:1e3,"ten thousand":1e4},ROUNDING_RE=new RegExp(`^Estimate (${NUM}) ([+\\-]) (${NUM}) by rounding each number to the nearest (ten thousand|thousand|hundred|ten)\\. What is your estimate\\?$`),roundingEntry={id:"AS.P6.M06.rounding-estimate.v1",microSkillId:"AS.P6.M06",forms:["rounding_entry"],generate(n){const t=normaliseStem(n.stem).match(ROUNDING_RE);if(!t)return null;const e=parseNum(t[1]),s=t[2],o=parseNum(t[3]),r=PLACES$1[t[4]];if(!r||!Number.isInteger(e)||!Number.isInteger(o))return null;const a=d=>Math.round(d/r)*r,i=a(e),l=a(o),u=s==="+"?i+l:i-l;return u<0||!inWordRange(e,o,i,l,u)||u!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`Estimate ${fmtTerm(e,0)} ${opSym$1(s)} ${fmtTerm(o,0)} by rounding`,computed:{a:e,b:o,ra:i,rb:l,answer:u},steps:[{visual_data:eqVisual$5([fmtTerm(e,0),opSym$1(s),fmtTerm(o,0),"=","__"]),animation:"show_sum",caption:"Round each number to estimate.",spoken:"To estimate quickly, round each number to a friendly value instead of working with the exact amounts."},{visual_data:eqVisual$5([fmtTerm(i,0),opSym$1(s),fmtTerm(l,0),"=","__"]),animation:"round_each",caption:`Rounds to ${fmtCap(i,0)} and ${fmtCap(l,0)}.`,spoken:`${cap$c(words$3(e,0))} rounds to ${words$3(i,0)}, and ${words$3(o,0)} rounds to ${words$3(l,0)}. Now they are easy to work with.`},{visual_data:eqVisual$5([fmtTerm(i,0),opSym$1(s),fmtTerm(l,0),"=",fmtTerm(u,0)]),animation:"add_rounded",caption:`About ${fmtCap(u,0)} — the estimate.`,spoken:`${cap$c(words$3(i,0))}
71 ${s==="+"?"and":"take away"} ${words$3(l,0)} ${s==="+"?"make":"leaves"} ${words$3(u,0)}, so the estimate is about ${words$3(u,0)}.`}]})}};function cap$c(n){return n.charAt(0).toUpperCase()+n.slice(1)}function buildEstimateVerdict(n,t,e){const{a:s,b:o,c:r,claimOp:a,dp:i,verdict:l}=e,u=normaliseStem(t.stem).match(/nearest (ten thousand|thousand|hundred|ten)\b/i),d=u?PLACES$1[u[1].toLowerCase()]:1e3,m=y=>Math.round(y/d)*d,h=m(s),f=m(o),g=a==="+"?h+f:h-f;if(g<0||!inWordRange(h,f,g))return null;const b=l?`About ${fmtCap(g,0)} — ${fmtCap(r,i)} fits.`:`About ${fmtCap(g,0)}, not ${fmtCap(r,i)} — no.`,x=l?`Rounding gives about ${words$3(g,0)}, close to ${words$3(r,i)}, so the answer is reasonable.`:`Rounding gives about ${words$3(g,0)}, nowhere near ${words$3(r,i)}, so the answer cannot be right.`;return buildExample({generatorId:n,question:t,title:`Estimate to check ${fmtTerm(s,0)} ${opSym$1(a)} ${fmtTerm(o,0)}`,computed:{a:s,b:o,c:r,ra:h,rb:f,estimate:g,answer:l?1:0},steps:[{visual_data:eqVisual$5([fmtTerm(s,0),opSym$1(a),fmtTerm(o,0),"=",fmtTerm(r,i)]),animation:"show_claim",caption:`Someone got ${fmtCap(r,i)}.`,spoken:`Someone worked this out and got ${words$3(r,i)}. To check quickly, round each number to a friendly value.`},{visual_data:eqVisual$5([fmtTerm(h,0),opSym$1(a),fmtTerm(f,0),"=","__"]),animation:"round_each",caption:`Rounds to ${fmtCap(h,0)} and ${fmtCap(f,0)}.`,spoken:`${cap$c(words$3(s,0))} rounds to ${words$3(h,0)}, and ${words$3(o,0)} rounds to ${words$3(f,0)}.`},{visual_data:eqVisual$5([fmtTerm(h,0),opSym$1(a),fmtTerm(f,0),"=",fmtTerm(g,0)]),animation:l?"compare_ok":"compare_mismatch",caption:b,spoken:x}]})}const VERDICT_RE=new RegExp(`(${NUM})\\s*([+\\-])\\s*(${NUM})\\s*(?:=|and gets|and writes)\\s*£?(${NUM})`),ESTIMATE_INSTRUCTION_RE=/estimat|round each number/i,INVERSE_INSTRUCTION_RE=/inverse/i,verdictCheck={id:"AS.P6.M06.inverse-verdict.v1",microSkillId:"AS.P6.M06",forms:["check_reasoning_probe"],generate(n){const t=normaliseStem(n.stem),e=t.match(VERDICT_RE);if(!e)return null;const s=dpOf$2(e[1],e[3],e[4]),o=parseNum(e[1]),r=e[2],a=parseNum(e[3]),i=parseNum(e[4]);if(!inWordRange(o,a,i))return null;const l=r==="+"?o+a:o-a,u=eqAt(l,i,s),d=n.correctAnswer.trim();if(u&&!isAffirmative(d)||!u&&!isNegative(d))return null;if(ESTIMATE_INSTRUCTION_RE.test(t)&&!INVERSE_INSTRUCTION_RE.test(t))return buildEstimateVerdict("AS.P6.M06.estimate-verdict.v1",n,{a:o,b:a,c:i,claimOp:r,dp:s,verdict:u});const m=r==="+"?"-":"+",h=m==="+"?i+a:i-a;if(h<0||!inWordRange(h))return null;const f=u?`Back to ${fmtCap(o,s)} — it checks out.`:`${fmtCap(h,s)}, not ${fmtCap(o,s)} — wrong.`,g=u?`Undoing it lands back on ${words$3(o,s)}, the other part exactly. The calculation was right.`:`Undoing it lands on ${words$3(h,s)}, but the other part was ${words$3(o,s)}. They do not match, so the calculation had a mistake — something the estimate alone could miss.`;return buildExample({generatorId:this.id,question:n,title:`Is ${fmtTerm(o,s)} ${opSym$1(r)} ${fmtTerm(a,s)} = ${fmtTerm(i,s)} correct?`,computed:{a:o,b:a,c:i,checkResult:h,answer:u?1:0},steps:[{visual_data:eqVisual$5([fmtTerm(o,s),opSym$1(r),fmtTerm(a,s),"=",fmtTerm(i,s)]),animation:"show_claim",caption:`Someone got ${fmtCap(i,s)}.`,spoken:`Someone worked this out and got ${words$3(i,s)}. An estimate shows the size, but only undoing it with the opposite calculation can prove every digit.`},{visual_data:eqVisual$5([fmtTerm(i,s),opSym$1(m),fmtTerm(a,s),"=","__"]),animation:"undo_with_inverse",caption:"Undo it with the opposite calculation.",spoken:`Every ${r==="+"?"addition":"subtraction"} has a partner that undoes it. ${m==="+"?"Add":"Take"} the part ${m==="+"?"back on to":"back off"} the answer.`},{visual_data:eqVisual$5([fmtTerm(i,s),opSym$1(m),fmtTerm(a,s),"=",fmtTerm(h,s)]),animation:"reveal_result",caption:f,spoken:g}]})}},SCREEN_RE=new RegExp(`(${NUM}) - (${NUM}) and the screen shows (${NUM})\\. To check.*?adds (${NUM}) \\+ (${NUM})\\. What total should he get\\?`),screenCheck={id:"AS.P6.M06.screen-rebuild.v1",microSkillId:"AS.P6.M06",forms:["check_reasoning_probe"],generate(n){const t=normaliseStem(n.stem).match(SCREEN_RE);if(!t)return null;const e=parseNum(t[1]),s=parseNum(t[2]),o=parseNum(t[3]),r=parseNum(t[4]),a=parseNum(t[5]);return!inWordRange(e,s,o)||r!==o||a!==s||e-s!==o||e!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`What should ${fmtTerm(o,0)} + ${fmtTerm(s,0)} give?`,computed:{a:e,b:s,screen:o,answer:e},steps:[{visual_data:eqVisual$5([fmtTerm(e,0),"−",fmtTerm(s,0),"=",fmtTerm(o,0)]),animation:"show_claim",caption:`The screen shows ${fmtCap(o,0)}
71.`,spoken:`The calculator took ${words$3(s,0)} away and showed ${words$3(o,0)}. To trust it, we check by undoing it with the opposite calculation.`},{visual_data:eqVisual$5([fmtTerm(o,0),"+",fmtTerm(s,0),"=","__"]),animation:"undo_with_inverse",caption:"Add the part back on.",spoken:"Adding undoes subtracting, so add the part that was taken away back on to the answer."},{visual_data:eqVisual$5([fmtTerm(o,0),"+",fmtTerm(s,0),"=",fmtTerm(e,0)]),animation:"reveal_result",caption:`Back to ${fmtCap(e,0)} — the total.`,spoken:`It rebuilds ${words$3(e,0)}, the number he started from. That is the total the check should give.`}]})}},SYMBOLIC_RE$1=new RegExp(`(${NUM})\\s*([+\\-])\\s*(${NUM})\\s*=\\s*(${NUM})`),PLANK_RE=new RegExp(`(${NUM}) m long has (${NUM}) m sawn off, leaving (${NUM}) m\\. Which addition would check`);function optionNums(n){return(n.match(new RegExp(NUM,"g"))??[]).map(parseNum)}function optionOp(n){return/\+/.test(n)?"+":/[−-]/.test(n)?"-":null}const symbolicCheck={id:"AS.P6.M06.which-operation.v1",microSkillId:"AS.P6.M06",forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem),e=t.match(PLANK_RE);if(e){const h=dpOf$2(e[1],e[2],e[3]),f=parseNum(e[1]),g=parseNum(e[2]),b=parseNum(e[3]);if(!eqAt(b+g,f,h))return null;const x=optionNums(n.correctAnswer);return optionOp(n.correctAnswer)!=="+"||!(x[0]===b&&x[1]===g)?null:buildRebuild(this.id,n,{a:f,b:g,c:b,claimOp:"-",dp:h})}const s=t.match(SYMBOLIC_RE$1);if(!s)return null;const o=dpOf$2(s[1],s[3],s[4]),r=parseNum(s[1]),a=s[2],i=parseNum(s[3]),l=parseNum(s[4]);if(!inWordRange(r,i,l))return null;const u=a==="+"?r+i:r-i;if(!eqAt(u,l,o))return null;const d=a==="+"?"-":"+",m=optionNums(n.correctAnswer);return optionOp(n.correctAnswer)!==d||!(m[0]===l&&m[1]===i)||m.length>=3&&m[2]!==r?null:buildRebuild(this.id,n,{a:r,b:i,c:l,claimOp:a,dp:o})}};function buildRebuild(n,t,e){const{a:s,b:o,c:r,claimOp:a,dp:i}=e,l=a==="+"?"-":"+";return buildExample({generatorId:n,question:t,title:`Which calculation checks ${fmtTerm(s,i)} ${opSym$1(a)} ${fmtTerm(o,i)} = ${fmtTerm(r,i)}?`,computed:{a:s,b:o,c:r,answer:s},steps:[{visual_data:eqVisual$5([fmtTerm(s,i),opSym$1(a),fmtTerm(o,i),"=",fmtTerm(r,i)]),animation:"show_claim",caption:"Which calculation checks it?",spoken:`Someone ${a==="+"?"added these":"took one from the other"} and got ${words$3(r,i)}. Which calculation would check it?`},{visual_data:eqVisual$5([fmtTerm(r,i),opSym$1(l),fmtTerm(o,i),"=","__"]),animation:"undo_with_inverse",caption:`Undo it: ${l==="+"?"add the part back":"take one part off"}.`,spoken:`The check does the opposite. ${l==="+"?"Add":"Take"} the part ${l==="+"?"back on to":"off"} the answer, ${words$3(r,i)}.`},{visual_data:eqVisual$5([fmtTerm(r,i),opSym$1(l),fmtTerm(o,i),"=",fmtTerm(s,i)]),animation:"reveal_result",caption:`Lands on ${fmtCap(s,i)} — that check works.`,spoken:`It rebuilds ${words$3(s,i)}, the number they started with, so that is the calculation that checks the answer.`}]})}function buildMultiPartEstimate(n,t,e,s,o,r,a,i){const u=x=>Math.round(x/1e3)*1e3,d=s.map(u);if(d.some(x=>x===0))return null;let m=d[0];for(let x=0;x<o.length;x++)m=o[x]==="+"?m+d[x+1]:m-d[x+1];if(m<0||!inWordRange(...s,...d,m,r))return null;const h=x=>{const y=[fmtTerm(x[0],0)];for(let E=0;E<o.length;E++)y.push(opSym$1(o[E]),fmtTerm(x[E+1],0));return y},f=i?`About ${fmtCap(m,0)} — ${fmtCap(r,a)} fits.`:`About ${fmtCap(m,0)} — ${fmtCap(r,a)} is wrong.`,g=i?`The estimate is about ${words$3(m,0)}, and the total ${words$3(r,a)} is close to that, so it is reasonable.`:`The estimate is about ${words$3(m,0)}, but the total ${words$3(r,a)} is nowhere near it, so it cannot be right.`,b={claim:r,estimate:m,answer:i?1:0};return s.forEach((x,y)=>b[`p${y}`]=x),d.forEach((x,y)=>b[`r${y}`]=x),buildExample({generatorId:n,question:t,title:e,computed:b,steps:[{visual_data:eqVisual$5([...h(s),"=","__"]),animation:"show_sum",caption:"Estimate to check — round each number first.",spoken:"To judge whether the total makes sense, first estimate: round each number to a friendly value."},{visual_data:eqVisual$5([...h(d),"=","__"]),animation:"round_each",caption:"Round each to the nearest thousand.",spoken:"Round each number to the nearest thousand so they are easy to work with."},{visual_data:eqVisual$5([...h(d),"=",fmtTerm(m,0)]),animation:"add_rounded",caption:f,spoken:g}]})}const SHIPPED_ARRIVE_RE=/holds (\d[\d,]*)[^.]*\.\s*(\d[\d,]*) are shipped out, then (\d[\d,]*) arrive\.[^.]*?(\d[\d,]*)[^.]*left/i,THREE_PART_TOTAL_RE=/three [\w']+ (?:monthly )?costs? at £(\d[\d,]*), £(\d[\d,]*) and £(\d[\d,]*), with a stated total of £(\d[\d,]*)/i,SELL_TWICE_RE=/holds (\d[\d,]*) people\.\s*(\d[\d,]*) [\w ]*sell, then (\d[\d,]*) more\.[^.]*?says (\d[\d,]*)[^.]*empty/i,contextEstimate={id:"AS.P6.M06.context-estimate.v1",microSkillId:"AS.P6.M06",forms:["context_reasoning_judgement"],generate(n){const t=normaliseStem(n.stem),e=t.match(SHIPPED_ARRIVE_RE);if(e){const[k,T,A,N]=e.slice(1).map(parseNum);
71if(inWordRange(k,T,A,N)){const S=k-T+A,M=eqAt(S,N,0),L=n.correctAnswer.trim();if(M&&isAffirmative(L)||!M&&isNegative(L)){const P=buildMultiPartEstimate(this.id,n,`Is ${fmtTerm(N,0)} left reasonable?`,[k,T,A],["-","+"],N,0,M);if(P)return P}}}const s=t.match(THREE_PART_TOTAL_RE);if(s){const[k,T,A,N]=s.slice(1).map(parseNum);if(inWordRange(k,T,A,N)){const S=k+T+A,M=eqAt(S,N,0),L=n.correctAnswer.trim();if(M&&isAffirmative(L)||!M&&isNegative(L)){const P=buildMultiPartEstimate(this.id,n,`Is a total of ${fmtTerm(N,0)} sensible?`,[k,T,A],["+","+"],N,0,M);if(P)return P}}}const o=t.match(SELL_TWICE_RE);if(o){const[k,T,A,N]=o.slice(1).map(parseNum);if(inWordRange(k,T,A,N)){const S=k-T-A,M=eqAt(S,N,0),L=n.correctAnswer.trim();if(M&&isAffirmative(L)||!M&&isNegative(L)){const P=buildMultiPartEstimate(this.id,n,`Is ${fmtTerm(N,0)} empty reasonable?`,[k,T,A],["-","-"],N,0,M);if(P)return P}}}let r=t.match(new RegExp(NUM,"g"))??[];if(r.length===4&&parseNum(r[2])===parseNum(r[3])&&(r=r.slice(0,3)),r.length!==3)return null;const a=dpOf$2(...r),[i,l,u]=r.map(parseNum);if(!inWordRange(i,l,u))return null;const d=eqAt(i+l,u,a),m=n.correctAnswer.trim();if(d&&!isAffirmative(m)||!d&&!isNegative(m))return null;let h;if(a>0)h=1;
71else{const k=Math.min(i,l);h=10**Math.floor(Math.log10(k))}const f=k=>Math.round(k/h)*h,g=f(i),b=f(l),x=g+b;if(!inWordRange(g,b,x))return null;const y=d?`About ${fmtCap(x,0)} — ${fmtCap(u,a)} fits.`:`About ${fmtCap(x,0)} — ${fmtCap(u,a)} is wrong.`,E=d?`The estimate is about ${words$3(x,0)}, and the total ${words$3(u,a)} is close to that, so it is reasonable.`:`The estimate is about ${words$3(x,0)}, but the total ${words$3(u,a)} is nowhere near it, so it cannot be right.`;return buildExample({generatorId:this.id,question:n,title:`Is a total of ${fmtTerm(u,a)} reasonable?`,computed:{p1:i,p2:l,claim:u,r1:g,r2:b,est:x,answer:d?1:0},steps:[{visual_data:eqVisual$5([fmtTerm(i,a),"+",fmtTerm(l,a),"=","__"]),animation:"show_sum",caption:"Estimate the total to check it.",spoken:"To judge whether the total makes sense, first estimate: round each part to a friendly value."},{visual_data:eqVisual$5([fmtTerm(g,0),"+",fmtTerm(b,0),"=","__"]),animation:"round_each",caption:`Round to ${fmtCap(g,0)} and ${fmtCap(b,0)}.`,spoken:`${cap$c(words$3(i,a))} is about ${words$3(g,0)}, and ${words$3(l,a)} is about ${words$3(b,0)}.`},{visual_data:eqVisual$5([fmtTerm(g,0),"+",fmtTerm(b,0),"=",fmtTerm(x,0)]),animation:"add_rounded",caption:y,spoken:E}]})}},CONTRAST_CALC=new RegExp(`(${NUM})\\s*([+\\-])\\s*(${NUM})(?:\\s*=\\s*(${NUM}))?`),WHO_RE=new RegExp(`(\\w+) checks (${NUM}) - (${NUM}) = (${NUM}) by adding (${NUM}) \\+ (${NUM})\\..*?friend says you should subtract instead\\. Who is using the correct check\\?`,"i"),whoChecks={id:"AS.P6.M06.who-checks.v1",microSkillId:"AS.P6.M06",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(WHO_RE);if(!t)return null;const e=t[1],s=parseNum(t[2]),o=parseNum(t[3]),r=parseNum(t[4]),a=parseNum(t[5]),i=parseNum(t[6]);return!inWordRange(s,o,r)||s-o!==r||!(a===r&&i===o)||n.correctAnswer.trim()!==e?null:buildExample({generatorId:this.id,question:n,title:`Who checks ${fmtTerm(s,0)} − ${fmtTerm(o,0)} = ${fmtTerm(r,0)} correctly?`,computed:{a:s,b:o,c:r,answer:s},steps:[{visual_data:eqVisual$5([fmtTerm(s,0),"−",fmtTerm(o,0),"=",fmtTerm(r,0)]),animation:"show_claim",caption:`${e} adds to check.`,spoken:`${e} checks a take-away by adding, while the friend wants to subtract again. Which one rebuilds the start?`},{visual_data:eqVisual$5([fmtTerm(r,0),"+",fmtTerm(o,0),"=","__"]),animation:"undo_with_inverse",caption:"Adding undoes taking away.",spoken:"Adding undoes taking away, so add the part that was taken away back on to the answer."},{visual_data:eqVisual$5([fmtTerm(r,0),"+",fmtTerm(o,0),"=",fmtTerm(s,0)]),animation:"reveal_result",caption:`Back to ${fmtCap(s,0)} — ${e} is right.`,spoken:`Adding rebuilds ${words$3(s,0)}, the number we started with, so ${e} is using the correct check.`}]})}},sureCheck={id:"AS.P6.M06.sure-inverse.v1",microSkillId:"AS.P6.M06",forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(CONTRAST_CALC);if(!e)return null;const s=dpOf$2(e[1],e[3],e[4]??""),o=parseNum(e[1]),r=e[2],a=parseNum(e[3]);if(!inWordRange(o,a))return null;const i=e[4]!==void 0?parseNum(e[4]):r==="-"?o-a:o+a;if(r==="-"&&o<a||e[4]!==void 0&&!eqAt(r==="+"?o+a:o-a,i,s)||!inWordRange(i))return null;const l=r==="-"?[a,o]:[a,i],u=r==="-"?"add":"subtract";if(!new RegExp(u,"i").test(n.correctAnswer))return null;const d=optionNums(n.correctAnswer);if(!l.every(h=>d.some(f=>f===h)))return null;const m=r==="+"?"-":"+";return buildExample({generatorId:this.id,question:n,title:`The sure check for ${fmtTerm(o,s)} ${opSym$1(r)} ${fmtTerm(a,s)}`,computed:{a:o,b:a,c:i,answer:o},steps:[{visual_data:eqVisual$5([fmtTerm(o,s),opSym$1(r),fmtTerm(a,s),"=",fmtTerm(i,s)]),animation:"show_claim",caption:"An estimate only checks the size.",spoken:"An estimate tells you the size an answer should be, but a small slip keeps the size and still changes a digit. Undoing it with the opposite calculation checks every digit."},{visual_data:eqVisual$5([fmtTerm(i,s),opSym$1(m),fmtTerm(a,s),"=","__"]),animation:"undo_with_inverse",caption:`Sure check: ${m==="+"?"add the answer back":"take one part off"}.`,spoken:`Do the opposite operation on the answer: ${m==="+"?"add the part back on":"take the part off"}.`},{visual_data:eqVisual$5([fmtTerm(i,s),opSym$1(m),fmtTerm(a,s),"=",fmtTerm(o,s)]),animation:"reveal_result",caption:`Back to ${fmtCap(o,s)} — now you are certain.`,spoken:`It rebuilds ${words$3(o,s)} exactly, so you can be certain the answer is right — the check an estimate cannot give.`}]})}},BEST_ROUNDING_STEM_RE=new RegExp(`^To check (${NUM})\\s*([+\\-])\\s*(${NUM}) quickly, which rounding gives the most useful estimate\\?$`),ROUND_PLACES_DESC=[1e5,1e4,1e3,100,10],PLACE_WORD_RE=/nearest (hundred thous
71and|ten thousand|thousand|hundred|ten)\b/i;function placeWordFor(n){switch(n){case 10:return"ten";case 100:return"hundred";case 1e3:return"thousand";case 1e4:return"ten thousand";case 1e5:return"hundred thousand";default:return null}}function bestRoundingPlace(n,t){for(const e of ROUND_PLACES_DESC){const s=Math.round(n/e)*e,o=Math.round(t/e)*e;if(s!==0&&o!==0)return e}return null}const bestRounding={id:"AS.P6.M06.best-rounding.v1",microSkillId:"AS.P6.M06",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(BEST_ROUNDING_STEM_RE);if(!t)return null;const e=parseNum(t[1]),s=t[2],o=parseNum(t[3]);if(!inWordRange(e,o))return null;const r=bestRoundingPlace(e,o);if(!r)return null;const a=placeWordFor(r);if(!a)return null;const i=n.correctAnswer.match(PLACE_WORD_RE);if(!i||i[1].toLowerCase()!==a)return null;const l=Math.round(e/r)*r,u=Math.round(o/r)*r,d=s==="+"?l+u:l-u;return d<0||!inWordRange(l,u,d)?null:buildExample({generatorId:this.id,question:n,title:`Which rounding checks ${fmtTerm(e,0)} ${opSym$1(s)} ${fmtTerm(o,0)} quickly?`,computed:{a:e,b:o,ra:l,rb:u,estimate:d,answer:d},steps:[{visual_data:eqVisual$5([fmtTerm(e,0),opSym$1(s),fmtTerm(o,0),"=","__"]),animation:"show_sum",caption:"A good estimate is quick but still close.",spoken:"To check quickly, round first. Too fine a rounding barely changes the numbers; too coarse a rounding loses them completely."},{visual_data:eqVisual$5([fmtTerm(l,0),opSym$1(s),fmtTerm(u,0),"=","__"]),animation:"round_each",caption:`Round to the nearest ${a}.`,spoken:`Rounding to the nearest ${a} keeps both numbers easy to work with, without losing them.`},{visual_data:eqVisual$5([fmtTerm(l,0),opSym$1(s),fmtTerm(u,0),"=",fmtTerm(d,0)]),animation:"reveal_result",caption:`About ${fmtCap(d,0)} — the most useful estimate.`,spoken:`Neither too fine nor too coarse, so this rounding gives the most useful estimate: about ${words$3(d,0)}.`}]})}},AS_P6_M06_GENERATORS=[roundingEntry,verdictCheck,screenCheck,symbolicCheck,contextEstimate,whoChecks,sureCheck,bestRounding];function asLineGraph$1(n){if(!n||typeof n!="object")return null;const t=n,e=t.x_labels,s=t.values;if(!Array.isArray(e)||!Array.isArray(s))return null;const o=Math.min(e.length,s.length);if(o<2||o>8)return null;const r=e.slice(0,o).map(f=>String(f)),a=s.slice(0,o).map(f=>Number(f));if(a.some(f=>!Number.isInteger(f)||f<0))return null;const i=t.axis_step,l=typeof t.axis_max=="number"?t.axis_max:void 0,u=Math.max(...a,1),d=Math.max(l??0,u),m=i===1||i===2||i===5||i===10?i:void 0,h=m!==void 0&&Math.ceil(d/m)<=12?m:void 0;return{x_labels:r,values:a,axis_step:h,axis_max:l}}const DAY$1={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"},MONTH$1={jan:"january",feb:"february",mar:"march",apr:"april",may:"may",jun:"june",jul:"july",aug:"august",sep:"september",oct:"october",nov:"november",dec:"december"};function escapeRegExp$2(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function labelSynonyms$1(n){const t=n.trim().toLowerCase(),e=new Set([t]);DAY$1[t]&&e.add(DAY$1[t]),MONTH$1[t]&&e.add(MONTH$1[t]);const s=t.match(/^(\d+)\s*min$/);return s&&(e.add(`${s[1]} minutes`),e.add(`${s[1]} minute`)),t==="12pm"&&(e.add("midday"),e.add("noon")),[...e]}function findLabelPos$1(n,t){let e=-1;for(const s of labelSynonyms$1(t)){const o=new RegExp(`(?<![a-z0-9])${escapeRegExp$2(s)}(?![a-z0-9])`,"i"),r=n.match(o);r&&r.index!=null&&(e===-1||r.index<e)&&(e=r.index)}return e}function matchedIndices(n,t){const e=[];for(let s=0;s<t.length;s++){const o=findLabelPos$1(n,t[s]);o>=0&&e.push({index:s,pos:o})}return e.sort((s,o)=>s.pos-o.pos),e}function fullLabel(n){const t=n.trim().toLowerCase(),e=DAY$1[t]??MONTH$1[t];return e?e.charAt(0).toUpperCase()+e.slice(1):n}function indexOfName(n,t){const e=n.trim().toLowerCase();for(let s=0;s<t.length;s++){const o=t[s].trim().toLowerCase();if(o===e||(DAY$1[o]??MONTH$1[o])===e)return s}return-1}function speakLabel$1(n){let t=n.trim().toLowerCase();t=t.replace(/\bmin\b/g,"minutes");const e=DAY$1[t]??MONTH$1[t];return e?e.charAt(0).toUpperCase()+e.slice(1):(t=t.replace(/(\d+)\s*(am|pm)\b/g,(s,o,r)=>`${numberToWordsLong(Number(o))} ${r}`),t=t.replace(/\d+/g,s=>numberToWordsLong(Number(s))),t.replace(/\s+/g," ").trim())}function capitalise$2(n){return n.charAt(0).toUpperCase()+n.slice(1)}function lgVisual$1(n,t){return{type:"line_graph",x_labels:n.x_labels,values:n.values,...n.axis_step!==void 0?{axis_step:n.axis_step}:{},...n.axis_max!==void 0?{axis_max:n.axis_max}:{},...t}}const GEN_ID$6="DS.P4.M01.line_graph.v1";function labelNums(n){const t={};let e=0;for(const s of n.x_labels)for(const o of s.match(/\d+/g)??[])t[`x${e++}`]=Number(o);return t}function pointRead(n,t,e,s){if(t.values[e]!==s)return null;const o=t.x_labels[e],r=speakLabel$1(o),a=()=>lgVisual$1(t,{highlight_index:e});return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),index:e,value:s,answer:s},steps:[{visual_data:a(),animation:"find_x",caption:`Find ${o} along the bottom.`,spoken:`To read a value from a line graph, we go up from the bottom to the line, then across to the scale. First find ${r} along the bottom.`},{visual_data:a(),animation:"trace_up",caption:"Go straight up to the point.",spoken:`Now go straight up from ${r} until you reach the point on the line.`},{visual_data:a(),animation:"trace_across",caption:"Trace across to the scale to read it.",spoken:`Then trace across to the scale and read the number it lines up with: ${numberToWordsLong(s)}. So the value at ${r} is ${numberToWordsLong(s)}.`}]})}function localTrapIndex(n,t,e){for(let s=0;s<t;s++){const o=s===0?null:n[s-1],r=s===n.length-1?null:n[s+1];if((o===null||(e?n[s]>o:n[s]<o))&&(r===null||(e?n[s]>r:n[s]<r)))return s}return null}function extremumWithTrap(n,t,e,s,o,r,a){const i=t.values[o],l=fullLabel(t.x_labels[o]),u=fullLabel(t.x_labels[s]),d=speakLabel$1(t.x_labels[o]),m=speakLabel$1(t.x_labels[s]),h=e?"higher":"lower";return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),trap:i,value:r,index:s},steps:[{visual_data:lgVisual$1(t,{read_index:o}),animation:"read_first",caption:`${l}: read ${i}.`,spoken:`Check every point, not just the first one that looks ${a}. ${d} is ${numberToWordsLong(i)}.`},{visual_data:lgVisual$1(t,{read_index:s,carry_indices:[o]}),animation:"read_second",caption:`${u}: read ${r}.`,spoken:`Keep checking the rest of the line. ${m} is ${numberToWordsLong(r)} — ${h} still.`},{visual_data:lgVisual$1(t,{read_index:s,carry_indices:[o],conclude_label:u}),animation:"find_gap",caption:`${u} is the ${a}.`,spoken:`Now every point has been checked, and nothing is ${h} than ${numberToWordsLong(r)}. It even beats ${numberToWordsLong(i)}, the point that looked ${a} at first, so ${m} is the answer.`}]})}function extremum(n,t,e,s,o,r){const a=e?Math.max(...t.values):Math.min(...t.values);if(t.values.filter(f=>f===a).length!==1)return null;const i=t.values.indexOf(a),l=e?"highest":"lowest",u=e?"peak":"dip";if(r){if(a!==o)return null}else if(fullLabel(t.x_labels[i]).toLowerCase()!==s.trim().toLowerCase())return null;if(!r&&n.options?.length){const f=localTrapIndex(t.values,i,e);if(f!==null){const g=fullLabel(t.x_labels[f]);if(n.options.some(x=>x.trim().toLowerCase()===g.toLowerCase()))return extremumWithTrap(n,t,e,i,f,a,l)}}const d=()=>lgVisual$1(t,{highlight_index:i}),m=r?`Trace across to read the ${l} value.`:`${fullLabel(t.x_labels[i])} is the ${l}.`,h=r?`Trace across to the scale: that point reads ${numberToWordsLong(a)}. So the ${l} value on the whole graph is ${numberToWordsLong(a)}.`:`Trace across to the scale: that point reads ${numberToWordsLong(a)}, the ${l} on the graph. So ${speakLabel$1(t.x_labels[i])} is the answer.`;return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),index:i,value:a,...r?{answer:a}:{}},steps:[{visual_data:d(),animation:"find_x",caption:`Look for the ${u} of the line.`,spoken:`To find the ${l} value, look along the line for its ${u} — the ${l} point.`},{visual_data:d(),animation:"trace_up",caption:"Go up to that point.",spoken:"Go up to that point on the line, so we can read its value on the scale."},{visual_data:d(),animation:"trace_across",caption:m,spoken:h}]})}function whenReached(n,t,e,s){const o=e.match(/reach(?:ed|es)?\s*£?\s*(\d+)/);if(!o)return null;const r=Number(o[1]),a=t.values.findIndex(d=>d>=r);if(a<1||t.values[a-1]>=r)return null;const i=t.x_labels[a],l=s.trim().toLowerCase();if(fullLabel(i).toLowerCase()!==l&&i.toLowerCase()!==l)return null;const u=a-1;return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),threshold:r,before:t.values[u],reached:t.values[a],index:a},steps:[{visual_data:lgVisual$1(t,{read_index:u,threshold:r}),animation:"read_first",caption:`The line must reach ${r}.`,spoken:`The dashed line marks the level we want, ${numberToWordsLong(r)}. We read along the line from the start until it first climbs up to that level.`},{visual_data:lgVisual$1(t,{read_index:a,carry_indices:[u],threshold:r}),animation:"read_second",caption:`At ${fullLabel(i)} it reaches ${r}.`,spoken:"Here the point touches the dashed line for the first time, so this is when it reaches the level."},{visual_data:lgVisual$1(t,{read_index:u,carry_indices:[a],threshold:r,conclude_label:i}),animation:"find_gap",caption:`First reaches ${r} at ${fullLabel(i)}.`,spoken:`The point before it was only ${numberToWordsLong(t.values[u])}, still below the dashed line, so this really is the first time. The answer is ${speakLabel$1(i)}.`}
71]})}function pairwiseDiff(n,t,e,s){const o=e[0].index,r=e[1].index,a=t.values[o],i=t.values[r];if(Math.abs(a-i)!==s)return null;const l=Math.max(a,i),u=Math.min(a,i);return buildDifferenceDemo(n,t,{firstIdx:o,secondIdx:r,firstVal:a,secondVal:i,big:l,small:u,answer:s,computed:{a,b:i,answer:s}})}function rangeDiff(n,t,e){const s=Math.max(...t.values),o=Math.min(...t.values);if(s-o!==e)return null;const r=t.values.indexOf(s),a=t.values.indexOf(o);return r===a?null:buildDifferenceDemo(n,t,{firstIdx:r,secondIdx:a,firstVal:s,secondVal:o,big:s,small:o,answer:e,computed:{max:s,min:o,answer:e},firstIsExtreme:"highest",secondIsExtreme:"lowest"})}function buildDifferenceDemo(n,t,e){const s=e.firstIsExtreme?`the ${e.firstIsExtreme} point`:`${fullLabel(t.x_labels[e.firstIdx])}`,o=e.secondIsExtreme?`the ${e.secondIsExtreme} point`:`${fullLabel(t.x_labels[e.secondIdx])}`,r=e.firstIsExtreme?`the ${e.firstIsExtreme} point`:speakLabel$1(t.x_labels[e.firstIdx]),a=e.secondIsExtreme?`the ${e.secondIsExtreme} point`:speakLabel$1(t.x_labels[e.secondIdx]);return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),...e.computed},steps:[{visual_data:lgVisual$1(t,{read_index:e.firstIdx}),animation:"read_first",caption:`${s}: read ${e.firstVal}.`,spoken:`To find the difference between two points, we read both values first. First ${r}: go up and across — it is ${numberToWordsLong(e.firstVal)}.`},{visual_data:lgVisual$1(t,{read_index:e.secondIdx,carry_indices:[e.firstIdx]}),animation:"read_second",caption:`${o}: read ${e.secondVal}.`,spoken:`Now ${a} the same way, keeping the first point in view — it is ${numberToWordsLong(e.secondVal)}.`},{visual_data:lgVisual$1(t,{read_index:e.firstIdx,carry_indices:[e.secondIdx],show_gap:!0,conclude_label:`${e.big} − ${e.small} = ${e.answer}`}),animation:"find_gap",caption:`The gap: ${e.big} − ${e.small} = ${e.answer}.`,spoken:`The difference is the gap between them. ${capitalise$2(numberToWordsLong(e.big))} take away ${numberToWordsLong(e.small)} is ${numberToWordsLong(e.answer)}.`}]})}function sumFirstTwo(n,t,e){if(t.values.length<2)return null;const s=t.values[0],o=t.values[1];return s+o!==e?null:buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),p0:s,p1:o,answer:e},steps:[{visual_data:lgVisual$1(t,{read_index:0}),animation:"read_first",caption:`${fullLabel(t.x_labels[0])}: read ${s}.`,spoken:`To find the total, read both points and add. First ${speakLabel$1(t.x_labels[0])}: it is ${numberToWordsLong(s)}.`},{visual_data:lgVisual$1(t,{read_index:1,carry_indices:[0]}),animation:"read_second",caption:`${fullLabel(t.x_labels[1])}: read ${o}.`,spoken:`Now ${speakLabel$1(t.x_labels[1])}: it is ${numberToWordsLong(o)}.`},{visual_data:lgVisual$1(t,{read_index:0,carry_indices:[1],conclude_label:`${s} + ${o} = ${e}`}),animation:"find_gap",caption:`Add them: ${s} + ${o} = ${e}.`,spoken:`Now add the two readings together. ${capitalise$2(numberToWordsLong(s))} add ${numberToWordsLong(o)} is ${numberToWordsLong(e)}.`}]})}function biggestChange(n,t,e,s){const o=/\b(fall|fell|drop|dropped|decrease|decreased|cool|cooled|lost)\b/.test(e);let r=-1,a=o?1/0:-1/0,i=!1;for(let h=0;h<t.values.length-1;h++){const f=t.values[h+1]-t.values[h];(o?f<a:f>a)?(a=f,r=h,i=!1):f===a&&(i=!0)}if(r<0||i)return null;const l=`${t.x_labels[r]} to ${t.x_labels[r+1]}`;if(l.trim().toLowerCase()!==s.trim().toLowerCase())return null;const u=Math.abs(a),d=o?"fall":"jump",m=t.values.length;return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),left:t.values[r],right:t.values[r+1],change:u},steps:[{visual_data:lgVisual$1(t,{read_mode:"trend",segment_highlight:[0,m-1]}),animation:"draw_line",caption:"Compare how much it changes between each pair.",spoken:`To find the biggest ${d}, compare how much it changes between each pair of points.`},{visual_data:lgVisual$1(t,{read_mode:"trend",segment_highlight:[0,m-1],show_slope:!0}),animation:"show_all_gaps",caption:"Look at how steep each gap is.",spoken:`Look at each gap in turn — the steeper it is, the more it ${o?"dropped":"grew"} in that step.`},{visual_data:lgVisual$1(t,{read_mode:"trend",segment_highlight:[r,r+1],show_slope:!0,conclude_label:l}),animation:"conclude_biggest",caption:`Biggest ${d}: ${l}.`,spoken:`This gap is the steepest of all of them, so the biggest ${d} is from ${speakLabel$1(t.x_labels[r])} to ${speakLabel$1(t.x_labels[r+1])}.`}]})}function sameValue$1(n,t,e){const s=e.split(/\s+and\s+/i).map(i=>i.trim());if(s.length!==2)return null;const o=indexOfName(s[0],t.x_labels),r=indexOfName(s[1],t.x_labels);if(o<0||r<0||o===r||t.values[o]!==t.values[r])return null;const a=t.values[o];return buildExample({generatorId:GEN_ID$6,question:n,title:n.stem,computed:{...labelNums(t),value:a},steps:[{visual_data:lgVisual$1(t,{read_index:o}),animation:"read_first",caption:`${fullLabel(t.x_labels[o])}: read ${a}.`,spoken:`Read each point and look for a match. First ${speakLabel$1(t.x_labels[o])}: it is ${numberToWordsLong(a)}.`},{visual_data:lgVisual$1(t,{read_index:r,carry_indices:[o]}),animation:"read_second",caption:`${fullLabel(t.x_labels[r])}: read ${a}.`,spoken:`Now ${speakLabel$1(t.x_labels[r])}: it is also ${numberToWordsLong(a)} — the same height.`},{visual_data:lgVisual$1(t,{read_index:o,carry_indices:[r],conclude_label:`Both ${a}`}),animation:"find_gap",caption:`Both points show ${a}.`,spoken:"Both points sit at the same height, so these two are the pair that are the same."}]})}const DS_P4_M01_GENERATORS=[{id:GEN_ID$6,microSkillId:"DS.P4.M01",forms:["symbolic_numeric_entry","symbolic_choice","line_graph_point_read","line_graph_difference"],generate(n){const t=asLineGraph$1(n.visualData);if(!t)return null;const e=normaliseStem(n.stem).toLowerCase(),s=matchedIndices(e,t.x_labels),o=n.correctAnswer.trim(),r
71=Number(o),a=Number.isInteger(r);if(/\bthe same\b/.test(e))return sameValue$1(n,t,o);if(/between which two/.test(e))return biggestChange(n,t,e,o);if(/\breach(?:ed|es)?\b/.test(e))return whenReached(n,t,e,o);if(/difference between the highest/.test(e)&&a)return rangeDiff(n,t,r);if(/\baltogether\b/.test(e)&&/\bfirst two\b/.test(e)&&a)return sumFirstTwo(n,t,r);if(a&&s.length>=2&&(/\bthan\b/.test(e)||/\bbetween\b/.test(e)))return pairwiseDiff(n,t,s,r);const i=/\b(highest|greatest|largest|most|warmest|maximum|tallest)\b/.test(e),l=/\b(lowest|least|smallest|fewest|coldest|minimum|shortest)\b/.test(e);return(i||l)&&s.length===0?extremum(n,t,i,o,r,a):s.length>=1&&a?pointRead(n,t,s[0].index,r):null}}],W$4=n=>Number.isInteger(n)?numberToWordsLong(n):decimalToWords(String(n)).replace(/^zero point/,"nought point");function lineOf(n){const t=n.visualData;if(!t||t.type!=="line_graph")return null;const e=t.values,s=t.x_labels;if(!Array.isArray(e)||!Array.isArray(s)||e.length<2||e.length!==s.length||!e.every(r=>Number.isFinite(r)))return null;const o=e.slice(1).map((r,a)=>r-e[a]);return{values:e,labels:s,deltas:o,axisStep:t.axis_step,axisMax:t.axis_max,yLabel:t.y_axis_label,unit:t.unit,xLabel:t.x_label}}function lg(n,t,e,s){return{type:"line_graph",read_mode:"trend",x_labels:e,values:t,axis_step:n.axisStep,axis_max:n.axisMax,y_axis_label:n.yLabel,unit:n.unit,x_label:n.xLabel,...s}}function pairIndex(n,t){const e=t.toLowerCase(),s=[];for(let o=0;o<n.length-1;o++)e.includes(n[o].toLowerCase())&&e.includes(n[o+1].toLowerCase())&&s.push(o);return s.length===1?s[0]:null}const CAP=n=>n,steepestGen={id:"DS.P4.M02.steepest.v1",microSkillId:"DS.P4.M02",forms:["steepest_section_identification"],generate(n){const t=lineOf(n);if(!t)return null;const{values:e,labels:s,deltas:o}=t,r=n.correctAnswer.toLowerCase();if(/\b(first|last)\b/.test(r)&&/\btwo\b/.test(r)&&e.length>=4){const m=e[2]-e[0],h=e[e.length-1]-e[e.length-3],f=m>h?"first":h>m?"last":"same",g=/last/.test(r)?"last":/first/.test(r)?"first":"same";if(f!==g||f==="same")return null;const b=f==="last"?[e.length-3,e.length-1]:[0,2];return buildExample({generatorId:this.id,question:n,title:"Which part rose faster?",computed:{firstGain:m,lastGain:h,answer:f==="last"?1:0},steps:[{visual_data:lg(t,e,s,{}),animation:"draw_line",caption:CAP("Compare how steep each part is."),spoken:"To see where the change was fastest, compare how steeply the line climbs in each part."},{visual_data:lg(t,e,s,{segment_highlight:[0,2]}),animation:"compare_first",caption:CAP("Look at the first part and the last part."),spoken:"Look at the slope over the first part, then over the last part. The steeper one is the faster gain."},{visual_data:lg(t,e,s,{segment_highlight:b,show_slope:!0,conclude_label:n.correctAnswer}),animation:"conclude_span",caption:CAP("The steeper part gained faster."),spoken:"The part with the steeper slope gained more in the same time, so that is where the change was faster."}]})}const a=pairIndex(s,n.correctAnswer);if(a===null)return null;const i=o[a];if(i===0)return null;for(let m=0;m<o.length;m++)if(m!==a&&Math.abs(o[m])>=Math.abs(i))return null;const l=i>0;let u=0;for(let m=1;m<e.length;m++)e[m]>e[u]&&(u=m);const d=u!==a&&u!==a+1?u:void 0;return buildExample({generatorId:this.id,question:n,title:l?"Which part rose the fastest?":"Which part fell the fastest?",computed:{segment:a,delta:i,answer:a},steps:[{visual_data:lg(t,e,s,{}),animation:"draw_line",caption:CAP("Read how steep the line is, part by part."),spoken:"The steepest part of the line is where the value changes fastest. Look along the line to find the biggest jump."},{visual_data:lg(t,e,s,{segment_highlight:[0,e.length-1],show_slope:!0,slope_value_labels:!0}),animation:"show_slope",caption:CAP("Compare how steep each part is."),spoken:l?"Look at how steeply the line climbs in each part, and compare them.":"Look at how steeply the line drops in each part, and compare them."},{visual_data:lg(t,e,s,{segment_highlight:[a,a+1],show_slope:!0,slope_value_labels:!0,dim_index:d,dim_label_safe:!0,conclude_label:n.correctAnswer}),animation:"conclude_steepest",caption:`${s[a]} to ${s[a+1]} jumps the most.`,spoken:d!=null?"Another point may be higher, but the line barely changes there. The steepest slope, not the highest point, shows the fastest change.":"The steepest slope, not the highest point, is what shows the fastest change."}]})}},RISE$1=/ris|rose|\bup\b|went up|climb|grew|increas|gain/i,FALL$1=/fall|fell|\bdown\b|went down|drop|decreas|lost/i,SAME=/same|flat|level|unchang|no change|steady/i,directionGen={id:"DS.P4.M02.direction.v1",microSkillId:"DS.P4.M02",forms:["trend_direction_read"],generate(n){const t=lineOf(n);if(!t)return null;const{values:e,labels:s}=t,o=normaliseStem(n.stem).toLowerCase();let r=0,a=e.length-1,i=!1;if(/\bbetween\b/.test(o)&&!/\boverall\b/.test(o)){const y=s.map((E,k
71)=>o.includes(E.toLowerCase())?k:-1).filter(E=>E>=0);y.length===2&&(r=Math.min(y[0],y[1]),a=Math.max(y[0],y[1]),i=!0)}const l=e[a]-e[r],u=l>0?"rise":l<0?"fall":"same",d=n.correctAnswer,m=u==="rise"&&RISE$1.test(d)&&!FALL$1.test(d),h=u==="fall"&&FALL$1.test(d)&&!RISE$1.test(d),f=u==="same"&&SAME.test(d)&&!RISE$1.test(d)&&!FALL$1.test(d);if(!m&&!h&&!f)return null;const g=u==="rise"?"Rising":u==="fall"?"Falling":"Staying level",b=i?lg(t,e,s,{segment_highlight:[r,a]}):lg(t,e,s,{segment_highlight:[0,e.length-1]}),x=i?lg(t,e,s,{segment_highlight:[r,a],conclude_label:d}):lg(t,e,s,{trace_line:!0,conclude_label:d});return buildExample({generatorId:this.id,question:n,title:i?"Did this part rise or fall?":"Is it rising, falling, or staying the same?",computed:{lo:r,hi:a,net:l,answer:l},steps:[{visual_data:lg(t,e,s,{}),animation:"draw_line",caption:CAP(i?"Look at the slope of this part.":"Read the line from left to right."),spoken:i?"To read the trend over this part, follow the line across it and see whether it ends higher or lower.":"To read the overall trend, follow the whole line and see whether it climbs, drops, or stays flat."},{visual_data:b,animation:"trace_trend",caption:CAP(u==="rise"?"The line ends higher than it started.":u==="fall"?"The line ends lower than it started.":"The line stays at the same height."),spoken:u==="rise"?"Following the line, it finishes higher than where it began.":u==="fall"?"Following the line, it finishes lower than where it began.":"Following the line, it stays at the same height the whole way."},{visual_data:x,animation:"conclude_direction",caption:CAP(i?`${g} from ${s[r]} to ${s[a]}.`:`${g} overall.`),spoken:u==="rise"?"Because it ends higher, the trend is rising.":u==="fall"?"Because it ends lower, the trend is falling.":"Because it neither climbs nor drops, it is staying the same."}]})}},POSITION_STEM=/position\s*\d+\s*is\s*the\s*(?:race\s*)?lead/i,IMPROVE=/moving up|towards the lead|closer to (?:the )?lead|catching up|improv|gaining ground/i,WORSEN=/falling(?: further)? behind|further behind|losing (?:ground|places)|drop(?:ping)? back|slipping back/i,positionGen={id:"DS.P4.M02.position.v1",microSkillId:"DS.P4.M02",forms:["trend_direction_read"],generate(n){if(!POSITION_STEM.test(normaliseStem(n.stem)))return null;const t=lineOf(n);if(!t)return null;const{values:e,labels:s}=t,o=e[0],r=e[e.length-1];if(o===r)return null;const a=r<o,i=n.correctAnswer,l=a&&IMPROVE.test(i)&&!WORSEN.test(i),u=!a&&WORSEN.test(i)&&!IMPROVE.test(i);if(!l&&!u)return null;const d=a?"smaller":"bigger";return buildExample({generatorId:this.id,question:n,title:"Is the runner moving up or falling behind?",computed:{first:o,last:r,answer:r-o},steps:[{visual_data:lg(t,e,s,{scale_note:"Position 1 = the lead"}),animation:"draw_line",caption:CAP("Position 1 is the lead — a smaller number is better."),spoken:"On this graph the position number tells you the place. The smaller the number, the closer to the lead."},{visual_data:lg(t,e,s,{segment_highlight:[0,e.length-1],scale_note:"Position 1 = the lead"}),animation:"trace_trend",caption:CAP(`The position number gets ${d}.`),spoken:a?"Follow the line: the position number drops lap by lap, so the runner is getting closer to first place.":"Follow the line: the position number climbs lap by lap, so the runner is dropping further from first place."},{visual_data:lg(t,e,s,{trace_line:!0,conclude_label:i,scale_note:"Position 1 = the lead"}),animation:"conclude_direction",caption:CAP(`A ${d} position number means ${a?"closer to":"further from"} the lead.`),spoken:a?"Because the position number keeps getting smaller, the runner is moving up towards the lead.":"Because the position number keeps getting bigger, the runner is falling further behind."}]})}},sign=n=>n>0?1:n<0?-1:0,shapeGen={id:"DS.P4.M02.shape.v1",microSkillId:"DS.P4.M02",forms:["trend_shape_description","trend_statement_evaluation"],generate(n){const t=lineOf(n);if(!t)return null;const{values:e,labels:s,deltas:o}=t,r=o.map(sign),a=n.correctAnswer,i=a.toLowerCase(),l=e.length-1,u=r.filter(S=>S===0).length,d=r.filter(S=>S===-1).length,m=r.filter(S=>S===1).length,h=e.indexOf(Math.max(...e)),f=e.indexOf(Math.min(...e)),g=S=>S>0&&S<l&&r.slice(0,S).every(M=>M===1),b=S=>r.slice(S).every(M=>M===-1),x=S=>S>0&&S<l&&r.slice(0,S).every(M=>M===-1),y=S=>r.slice(S).every(M=>M===1),E=[],k="Read the line's slope.",T="Read the line in parts.";if(m===o.length&&o.every(S=>S===o[0])&&/same amount|equal|steady/i.test(i))E.push({step2:[0,l],step3:[0,l],slope3:!0,answer:o[0],captions:[k,"The line climbs steadily.","Equal steps each time."],spokens:["Look at the slope of the line to describe the trend.","The line goes up by the same amount between each point, so it climbs at a steady rate.","Because every step is the same size, the value grew by the same amount each time."]});else{const S=r.findIndex(M=>M!==1);S>=1&&r.slice(0,S).every(M=>M===1)&&r.slice(S).every(M=>M===0)&&/level|off|stopped|steady|plateau/i.test(i)&&E.push({step2:[0,S],step3:[S,l],answer:S,captions:[T,"First it rises.","Then it flattens off."],spokens:["Break the line into parts and read the slope of each.","At the start the line climbs, so the value is increasing.","After that the line goes flat, so it levelled off and stopped changing."]})}{const S=r.findIndex(M=>M!==0);S>=1&&d>=1&&m===0&&r.slice(S).every(M=>M<=0)&&/same|flat|level/i.test(i)&&/then (fell|dropped|fall)/i.test(i)&&E.push({step2:[0,S],step3:[S,l],answer:S,captions:[T,"First it stays level.","Then it falls."],spokens:["Break the line into parts and read each part's slope.","At the start the line is flat, so the value stayed the same.","After that the line drops, so it fell — it stayed the same, then fell."]})}d===0&&u>=1&&m>=1&&/never (went|goes) down|never fell|never dropped/i.test(i)&&E.push({step2:[0,l],step3:"trace",answer:m,captions:[k,"The line never dips downward.","It never went down."],spokens:["Read the slope along the whole line.","Some parts climb and some stay flat, but no part slopes downward.","Because the line never slopes down, the value never went down."]}),g(h)&&b(h)&&/(rose|up|increas).*(then|,).*(fell|fall|down)/i.test(i)&&E.push({step2:[0,h],step3:[h,l],answer:h,captions:[T,"First it rises.","Then it falls."],spokens:["Break the line into parts and read each slope.","The line climbs to its highest point, so first the value rose.","After the peak the line drops, so it fell — it rose, then fell."]}),x(f)&&y(f)&&/(fell|fall|down).*(then|,).*(rose|rise|up)/i.test(i)&&E.push({step2:[0,f],step3:[f,l],answer:f,captions:[T,"First it falls.","Then it rises."],spokens:["Break the line into parts and read each slope.","The line drops to its lowest point, so first the value fell.","After the lowest point the line climbs, so it rose — it fell, then rose."]});{const S=i.match(/(?:dropped|fell)\s+(?:in|at)\s+([a-z0-9 ]+?),?\s+the
71n\s+(?:rose|rise|climbed|recovered|went up)/i);if(S){const M=S[1].trim().toLowerCase(),L=s.findIndex(P=>M.includes(P.toLowerCase())||P.toLowerCase().includes(M));L>0&&L<l&&e[L]<e[L-1]&&e[L]<e[L+1]&&E.push({step2:[L-1,L],step3:[L,L+1],answer:L,captions:[T,"It drops at one point.","Then it climbs back up."],spokens:["Break the line into parts and read the slope either side of the dip.","At one point the line drops, so the value fell there.","Just after that the line climbs back up, so it rose again — it dropped, then rose."]})}}if(d===1&&u===0&&m>=2&&e[l]>e[0]&&/(mostly rose|one .*drop|small drop|dropped .*then rose)/i.test(i)){const S=r.indexOf(-1);E.push({step2:"trace",step3:[S,S+1],answer:S,captions:[k,"Mostly the line climbs.","One small dip, then up again."],spokens:["Read the slope along the line.","Almost every part of the line slopes upward, so the value mostly rose.","Just one short part dips down before it climbs again — mostly rising with one small drop."]})}if(d===o.length&&o.every((S,M)=>M===0||Math.abs(S)<Math.abs(o[M-1]))&&/quick.*(then|,).*slow|steep.*(then|,).*gentl/i.test(i)&&E.push({step2:[0,1],step3:[l-1,l],answer:-1,captions:["Read how steep the fall is.","It drops steeply at first.","Then it falls more gently."],spokens:["Read how steep the fall is in each part.","At the start the line drops very steeply, so it fell quickly.","Later the line drops far more gently, so it fell quickly at first, then more slowly."]}),u===1&&(m===0||d===0)){const S=r.indexOf(0);pairIndex(s,a)===S&&E.push({step2:"trace",step3:[S,S+1],answer:S,captions:["Find the flat part.","The line climbs, then one part is flat.","Flat means no change there."],spokens:["Look for the part of the line with no slope at all.","Most of the line slopes upward, but one part runs completely flat.","A flat part means the value did not change there — nothing was moving."]})}if(/fastest between|rose fastest|steepest/i.test(i)){const S=pairIndex(s,a);if(S!==null){let M=o[S]>0;for(let L=0;L<o.length&&M;L++)L!==S&&Math.abs(o[L])>=Math.abs(o[S])&&(M=!1);M&&E.push({step2:[S,S+1],step3:[S,S+1],slope2:!0,slope3:!0,answer:S,captions:["Compare the slopes.","This section is the steepest.","Steepest rise means fastest."],spokens:["Compare how steeply the line climbs in each part.","This section climbs more steeply than any other part of the line.","The steepest section is where the value rose fastest."]})}}if(E.length!==1)return null;const A=E[0],N=(S,M,L,P)=>S==="trace"?lg(t,e,s,{trace_line:!0,dim_index:L,conclude_label:P}):lg(t,e,s,{segment_highlight:S,show_slope:M,dim_index:L,conclude_label:P});return buildExample({generatorId:this.id,question:n,title:"How would you describe the line?",computed:{answer:A.answer},steps:[{visual_data:lg(t,e,s,{}),animation:"draw_line",caption:A.captions[0],spoken:A.spokens[0]},{visual_data:N(A.step2,A.slope2,void 0,void 0),animation:"phase_a",caption:A.captions[1],spoken:A.spokens[1]},{visual_data:N(A.step3,A.slope3,A.dim3,a),animation:"phase_b",caption:A.captions[2],spoken:A.spokens[2]}]})}},predictionGen={id:"DS.P4.M02.prediction.v1",microSkillId:"DS.P4.M02",forms:["trend_prediction"],generate(n){const t=lineOf(n);if(!t)return null;const{values:e,labels:s}=t;if(e.length<3)return null;const o=e[1]-e[0];if(o===0||!e.slice(1).every((g,b)=>g-e[b]===o))return null;const r=e[e.length-1]+o;if(r<0)return null;const a=s[s.length-1].match(/^(.*?)(\d+)(\D*)$/);if(!a)return null;const i=`${a[1]}${Number(a[2])+1}${a[3]}`,l=n.correctAnswer.match(/-?\d+/);if(!l||Number(l[0])!==r)return null;const u=e.slice(),d=s.slice(),m=[...u,r],h=[...d,i],f=t.unit?` ${t.unit}`:"";return buildExample({generatorId:this.id,question:n,title:"What is the most sensible next value?",computed:{step:o,stepSize:Math.abs(o),last:e[e.length-1],answer:r},steps:[{visual_data:lg(t,u,d,{}),animation:"draw_line",caption:CAP("The line changes in equal steps."),spoken:`Each point changes by the same amount as the one before, so the pattern is steady: every step is ${W$4(Math.abs(o))}.`},{visual_data:lg(t,u,d,{segment_highlight:[0,u.length-1],show_slope:!0}),animation:"show_steps",caption:CAP("Same jump each time."),spoken:`Because every step is the same size, we can carry the pattern on with one more equal step of ${W$4(Math.abs(o))}, starting from the last value, ${W$4(e[e.length-1])}.`},{visual_data:lg(t,m,h,{projected:!0,segment_highlight:[u.length-1,u.length],conclude_label:`${i}: ${r}${f}`}),animation:"project_next",caption:CAP("One more equal step gives the next value."),spoken:`${capitalised(W$4(e[e.length-1]))} ${o>0?"add":"take away"} ${W$4(Math.abs(o))} is ${W$4(r)}, so the most sensible next value is ${W$4(r)}.`}]})}},slowingPredictionGen={id:"DS.P4.M02.prediction_slowing.v1",microSkillId:"DS.P4.M02",forms:["trend_prediction"],generate(n){const t=lineOf(n);if(!t)return null;const{values:e,labels:s}=t;if(e.length<4)return null;const o=e.slice(1).map((k,T)=>k-e[T]);if(o.some(k=>k===0))return null;const r=o[0]>0?1:-1;if(!o.every(k=>Math.sign(k)===r))return null;const a=o.map(Math.abs);for(let k=1;k<a.length;k++)if(a[k]>a[k-1])return null;const i=a[a.length-1];if(i>=a[a.length-2])return null;const l=Math.max(1,Math.round(i/2)),u=e[e.length-1]+r*l;if(u<0)return null;const d=n.correctAnswer.match(/-?\d+/);if(!d||Number(d[0])!==u)return null;const m=s[s.length-1].match(/^(.*?)(\d+)(\D*)$/);if(!m)return null;const h=`${m[1]}${Number(m[2])+1}${m[3]}`,f=e.slice(),g=s.slice(),b=[...f,u],x=[...g,h],y=t.unit?` ${t.unit}`:"",E=f.length;return buildExample({generatorId:this.id,question:n,title:"What is the most sensible next value?",computed:{last:e[e.length-1],lastStep:i,nextStep:l,answer:u},steps:[{visual_data:lg(t,f,g,{segment_highlight:[0,E-1]}),animation:"draw_line",caption:CAP("The steps get smaller as you go on."),spoken:"Look along the line: the steps it climbs get smaller as you go on, so the growth is slowing down."},{visual_data:lg(t,f,g,{segment_highlight:[0,E-1],show_slope:!0}),animation:"show_steps",caption:CAP("The steps are shrinking overall."),spoken:`Compare each step to the one before it — the jumps get smaller as you go on. The last jump is only ${W$4(i)}.`},{visual_data:lg(t,b,x,{projected:!0,segment_highlight:[f.length-1,f.length],conclude_label:`${h}: about ${u}${y}`,proj_label_safe:!0}),animation:"project_next",caption:CAP("One smaller step gives a sensible next value."),spoken:`Because the steps keep shrinking, the next jump should be small too — about ${W$4(l)}, no bigger than the last one. ${capitalised(W$4(e[e.length-1]))} ${r>0?"add":"take away"} ${W$4(l)} is ${W$4(u)}, so about ${W$4(u)} is a sensible next value.`}]})}},DS_P4_M02_GENERATORS=[steepestGen,directionGen,positionGen,shapeGen,predictionGen,slowingPredictionGen];function readSectors(n){const t=n.visualData;if(!t||t.type!=="pie_chart"||!Array.isArray(t.sectors))return null;const e=[];for(const s of t.sectors){const o=s.fraction;if(!o||typeof o!="object"||!Number.isInteger(o.num)||!Number.isInteger(o.den)||o.den<=0||o.num<0)return null;e.push({label:String(s.label??""),num:o.num,den:o.den})}return e.length>=2?e:null}function readTotal(n){const t=n.visualData;return t&&Number.isInteger(t.total)?t.total:null}function pieVisual$1(n,t,e){const s=n.visualData;return{type:"pie_chart",sectors:s.sectors,total:s.total,highlight_index:t,fit_labels:!0,...e?{guide_divisions:e}:{}}}function eq$8(n){return{type:"equation_display",terms:n}}function gcd$4(n,t){return t===0?n:gcd$4(t,n%t)}function lcm$1(n,t){return n*t/gcd$4(n,t)}function reduce(n,t){const e=gcd$4(n,t)||1;return{num:n/e,den:t/e}}const fracStr=(n,t)=>`${n}/${t}`,DEN_UNIT={2:["half","halves"],3:["third","thirds"],4:["quarter","quarters"],5:["f
71ifth","fifths"],8:["eighth","eighths"],10:["tenth","tenths"]};function fractionWords$1(n,t){const e=DEN_UNIT[t];return!e||n<1||n>30?null:n===1?`one ${e[0]}`:`${numberWord(n)} ${e[1]}`}function fractionOf$1(n,t,e){return n*e%t!==0?null:n*e/t}const cap$b=n=>capitalised(n);function deriveCount(n,t,e){const s=n%t.den===0?n/t.den:null;return t.num===1?{terms:[n,"÷",t.den,"=",e],caption:`${n} ÷ ${t.den} = ${e}`,extra:{}}:s!=null?{terms:[n,"÷",t.den,"=",s,"  ",s,"×",t.num,"=",e],caption:`${n} ÷ ${t.den} = ${s}, then ${s} × ${t.num} = ${e}`,extra:{[`unit_${t.label.replace(/\s+/g,"")}`]:s}}:{terms:[fracStr(t.num,t.den),"×",n,"=",e],caption:`${fracStr(t.num,t.den)} × ${n} = ${e}`,extra:{}}}function genFractionOfTotal(n){if(n.mathematicalForm!=="context_calculation")return null;const t=n.stem.toLowerCase();if(/how many more|one of these|times as many|difference between/.test(t))return null;const e=readSectors(n),s=readTotal(n);if(!e||s==null)return null;const o=Number(n.correctAnswer);if(!Number.isInteger(o)||o<0)return null;let r=e.map((k,T)=>({s:k,i:T,val:fractionOf$1(k.num,k.den,s)})).filter(k=>k.val===o);if(r.length>1&&(r=r.filter(k=>t.includes(k.s.label.toLowerCase()))),r.length!==1)return null;const{s:a,i}=r[0],l=fractionWords$1(a.num,a.den);if(!l)return null;const u=pieVisual$1(n,i,a.den),d=numberToWordsLong(s),m=numberToWordsLong(o),h=a.label.toLowerCase();if(e.length===3&&/\bthe rest\b/.test(t)){const k=e.filter(A=>A!==a),T=k.map(A=>fractionOf$1(A.num,A.den,s));if(T.every(A=>A!=null)&&s-T[0]-T[1]===o){const[A,N]=k,[S,M]=T,L=deriveCount(s,A,S),P=deriveCount(s,N,M),F=fractionWords$1(A.num,A.den),D=fractionWords$1(N.num,N.den);if(F&&D)return buildExample({generatorId:"DS.P4.M03.fraction-of-total.v1",question:n,title:`How many chose ${h}?`,computed:{total:s,value:o,answer:o,c1:S,c2:M,whole:1,...L.extra,...P.extra},steps:[{visual_data:u,animation:"show_whole",caption:"The whole circle is everyone.",spoken:`The whole pie chart stands for all ${d}. ${cap$b(h)} is the rest, so first find the other two groups.`},{visual_data:eq$8([...L.terms,"  ",...P.terms]),animation:"derive_counts",caption:`${A.label}: ${L.caption}. ${N.label}: ${P.caption}.`,spoken:`${cap$b(A.label.toLowerCase())} is ${F} of ${d}, which is ${numberToWordsLong(S)}. ${cap$b(N.label.toLowerCase())} is ${D}, which is ${numberToWordsLong(M)}.`},{visual_data:eq$8([s,"−",S,"−",M,"=",o]),animation:"combine",caption:`${s} − ${S} − ${M} = ${o}.`,spoken:`The rest is everyone left over: ${d} take away ${numberToWordsLong(S)} take away ${numberToWordsLong(M)} leaves ${m}.`}]})}}let f=u,g=`The ${h} slice fills ${a.num} of ${a.den} equal parts: ${fracStr(a.num,a.den)}.`,b=`The dashed lines split the circle into ${numberToWordsLong(a.den)} equal parts. The ${h} slice fills ${numberToWordsLong(a.num)} of them, so it is ${l} of the whole.`;if(e.length===3&&/\bthe rest\b/.test(t)){const k=e.filter(T=>T!==a);if(k.length===2){const T=lcm$1(lcm$1(k[0].den,k[1].den),a.den),A=k.reduce((M,L)=>M+L.num*T/L.den,0),N=T-A,S=reduce(N,T);if(N>0&&S.num===a.num&&S.den===a.den){const M=fractionWords$1(k[0].num,k[0].den),L=fractionWords$1(k[1].num,k[1].den);M&&L&&(f=eq$8(["1","−",fracStr(k[0].num,k[0].den),"−",fracStr(k[1].num,k[1].den),"=",fracStr(a.num,a.den)]),g=`1 − ${fracStr(k[0].num,k[0].den)} − ${fracStr(k[1].num,k[1].den)} = ${fracStr(a.num,a.den)}.`,b=`The slices must add up to the whole. ${cap$b(k[0].label.toLowerCase())} is ${M} and ${k[1].label.toLowerCase()} is ${L}, so ${h} must be the rest: one whole take away ${M} take away ${L} leaves ${l}.`)}}}const x=deriveCount(s,a,o),y=s%a.den===0?s/a.den:null,E=a.num===1?`To find how many, share ${d} into ${numberToWordsLong(a.den)} equal parts: ${d} divided by ${numberToWordsLong(a.den)} is ${m}.`:y!=null?`First divide ${d} by ${numberToWordsLong(a.den)} to find one part: that is ${numberToWordsLong(y)}. The slice is ${numberToWordsLong(a.num)} of those parts, so multiply: ${numberToWordsLong(y)} times ${numberToWordsLong(a.num)} is ${m}.`:`${cap$b(l)} of ${d} is ${m}.`;return buildExample({generatorId:"DS.P4.M03.fraction-of-total.v1",question:n,title:`What is ${fracStr(a.num,a.den)} of ${s}?`,computed:{total:s,num:a.num,den:a.den,value:o,answer:o,whole:1,...x.extra},steps:[{visual_data:u,animation:"show_whole",caption:"The whole circle is everyone.",spoken:`The whole pie chart stands for all ${d}. Every slice is one part of that whole.`},{visual_data:f,animation:"pop_sector",caption:g,spoken:b},{visual_data:eq$8(x.terms),animation:"multiply",caption:`${x.caption}.`,spoken:E}]})}function genCombineSectors(n){if(n.mathematicalForm!=="context_calculation"&&n.mathematicalForm!=="comparison_statement")return null;const t=n.stem.toLowerCase(),e=/how many more/.test(t)?"diff":/one of these/.test(t)?"sum":null;if(!e)return null;const s=readSectors(n),o=readTotal(n);if(!s||o==null)return null;const r=Number(n.correctAnswer);if(!Number.isInteger(r)||r<0)return null;const a=s.map((A,N)=>({s:A,i:N,val:fractionOf$1(A.num,A.den,o)})).filter(A=>A.val!=null&&t.includes(A.s.label.toLowerCase()));if(a.length<2)return null;let i=null,l=null;for(let A=0;A<a.length;A++){for(let N=0;N<a.length;N++){if(A===N)continue;if((e==="diff"?a[A].val-a[N].val:a[A].val+a[N].val)===r&&a[A].val>=(e==="diff"?a[N].val:0)){i=a[A],l=a[N];break}}if(i)break}if(!i||!l)return null;const u=pieVisual$1(n,i.i),d=numberToWordsLong(o),m=i.val,h=l.val,f=fractionWords$1(i.s.num,
71i.s.den),g=fractionWords$1(l.s.num,l.s.den);if(!f||!g)return null;const b=e==="diff"?"−":"+",x=e==="diff"?`${m} − ${h} = ${r} more.`:`${m} + ${h} = ${r} altogether.`,y=e==="diff"?`Take ${numberToWordsLong(h)} away from ${numberToWordsLong(m)} to get ${numberToWordsLong(r)} more.`:`Add ${numberToWordsLong(m)} and ${numberToWordsLong(h)} to get ${numberToWordsLong(r)} altogether.`,E=deriveCount(o,i.s,m),k=deriveCount(o,l.s,h),T=[...E.terms,"  ",...k.terms];return buildExample({generatorId:"DS.P4.M03.combine-sectors.v1",question:n,title:e==="diff"?"How many more chose one than the other?":"How many chose one of the two?",computed:{total:o,aVal:m,bVal:h,answer:r,whole:1,...E.extra,...k.extra},steps:[{visual_data:u,animation:"show_whole",caption:"The whole circle is everyone.",spoken:`The whole pie chart stands for all ${d}. First find how many are in each slice.`},{visual_data:eq$8(T),animation:"derive_counts",caption:`${i.s.label}: ${E.caption}. ${l.s.label}: ${k.caption}.`,spoken:`The ${i.s.label.toLowerCase()} slice is ${f} of the whole, which is ${numberToWordsLong(m)}. The ${l.s.label.toLowerCase()} slice is ${g}, which is ${numberToWordsLong(h)}.`},{visual_data:eq$8([`${m}`,b,`${h}`,"=",`${r}`]),animation:"combine",caption:x,spoken:y}]})}function genTimesAsMany(n){if(n.mathematicalForm!=="comparison_statement")return null;const t=n.stem.toLowerCase();if(!/times as many/.test(t))return null;const e=readSectors(n);if(!e)return null;const s=Number(n.correctAnswer);if(!Number.isInteger(s)||s<=0)return null;const o=e.filter(x=>t.includes(x.label.toLowerCase()));if(o.length<2)return null;let r=null,a=null;for(const x of o){for(const y of o){if(x===y)continue;const E=x.num*y.den,k=x.den*y.num;if(k!==0&&E%k===0&&E/k===s){r=x,a=y;break}}if(r)break}if(!r||!a)return null;const i=e.indexOf(r),l=e.indexOf(a),u=lcm$1(r.den,a.den),d=pieVisual$1(n,i),m=pieVisual$1(n,i,u),h=pieVisual$1(n,l,u),f=fractionWords$1(r.num,r.den),g=fractionWords$1(a.num,a.den);if(!f||!g)return null;const b=numberToWordsLong(s);return buildExample({generatorId:"DS.P4.M03.times-as-many.v1",question:n,title:"How many times as many chose one as the other?",computed:{bigNum:r.num,bigDen:r.den,smallNum:a.num,smallDen:a.den,answer:s},steps:[{visual_data:d,animation:"show_whole",caption:"Compare the two slices as fractions.",spoken:`The ${r.label.toLowerCase()} slice and the ${a.label.toLowerCase()} slice are each a fraction of the whole circle.`},{visual_data:m,animation:"pop_sector",caption:`The bigger slice is ${f}.`,spoken:`The ${r.label.toLowerCase()} slice is ${f} of the whole and the ${a.label.toLowerCase()} slice is ${g}.`},{visual_data:h,animation:"divide",caption:`${fracStr(r.num,r.den)} ÷ ${fracStr(a.num,a.den)} = ${s} — ${g} slices fit inside ${f}.`,spoken:`Count how many ${g} slices fit inside ${f}. ${cap$b(g)} fits ${b} times, so ${b} times as many chose it.`}]})}function genStatementTrueHalf(n,t){const s=n.correctAnswer.trim().toLowerCase();if(!/more than half/.test(s))return null;const o=t.find(g=>s.includes(g.label.toLowerCase()));if(!o)return null;const r=lcm$1(o.den,2),a=o.num*(r/o.den),i=r/2;if(!(a>i))return null;const l=t.indexOf(o),u=pieVisual$1(n,l,2),d=pieVisual$1(n,l,r),m=fractionWords$1(o.num,o.den),h=fractionWords$1(i,r);if(!m||!h)return null;const f=o.label.toLowerCase();return buildExample({generatorId:"DS.P4.M03.compare-slices.v1",question:n,title:"Which statement is true?",computed:{num:o.num,den:o.den,sNum:a,halfNum:i,commonDen:r},steps:[{visual_data:u,animation:"show_whole",caption:"The dashed line splits the circle exactly in half.",spoken:"The dashed line splits the whole circle exactly in half — that's the line to beat."},{visual_data:d,animation:"pop_sector",caption:`${cap$b(f)} is ${fracStr(a,r)} of the circle; half is ${fracStr(i,r)}.`,spoken:`The ${f} slice is ${m} of the whole. On the same guides, half of the circle is ${h}.`},{visual_data:eq$8([fracStr(a,r),">",fracStr(i,r)]),animation:"verdict",caption:`${fracStr(a,r)} > ${fracStr(i,r)}, so more than half chose ${f}.`,spoken:`${cap$b(numberWord(a))} out of ${numberWord(r)} is more than ${numberWord(i)} out of ${numberWord(r)}, so more than half chose ${f}.`}]})}function genStatementTrueTimes(n,t,e){const o=n.correctAnswer.trim().toLowerCase().match(/twice as many chose (.+?) as (.+)/);if(!o)return null;const r=o[1].trim(),a=o[2].trim(),i=t.find(k=>r.includes(k.label.toLowerCase())),l=t.find(k=>a.includes(k.label.toLowerCase()));if(!i||!l||i===l||i.num*l.den!==2*l.num*i.den)return null;const u=t.indexOf(i),d=t.indexOf(l),m=lcm$1(i.den,l.den),h=pieVisual$1(n,u,m),f=pieVisual$1(n,d,m),g=fractionWords$1(i.num,i.den),b=fractionWords$1(l.num,l.den);if(!g||!b)return null;let x="",y="";const E={};if(e!=null){const k=fractionOf$1(i.num,i.den,e),T=fractionOf$1(l.num,l.den,e);if(k==null||T==null||k!==2*T)return null;const A=deriveCount(e,l,T);x=` ${A.caption}, and ${cap$b(i.label.toLowerCase())} is double that: ${k}.`,y=` ${cap$b(l.label.toLowerCase())} is ${numberToWordsLong(e)} divided by ${numberToWordsLong(l.den)}, which is ${numberToWordsLong(T)}. ${cap$b(i.label.toLowerCase())} is double that count, ${numberToWordsLong(k)}.`,E.total=e,E.smallVal=T,E.bigVal=k,Object.assign(E,A.extra)}return buildExample({generatorId:"DS.P4.M03.compare-slices.v1",question:n,title:"Which statement is true?",computed:{bigNum:i.num,bigDen:i.den,smallNum:l.num,smallDen:l.den,ratio:2,...E},steps:[{visual_data:h,animation:"show_whole",caption:`${cap$b(i.label.toLowerCase())} is ${g} of the circle.`,spoken:`To compare two slices, we see what fraction of the whole circle each one covers. The ${i.label.toLowerCase()} slice is ${g} of the whole circle.`},{visual_data:f,animation:"pop_sector",caption:`${cap$b(l.label.toLowerCase())} is ${b} — one of the same guide wedges.`,spoken:`The ${l.label.toLowerCase()} slice is ${b} of the whole — one of the same size wedges the guides just split ${i.label.toLowerCase()} into.`},{visual_data:eq$8([fracStr(i.num,i.den),"=","2","×",fracStr(l.num,l.den)]),animation:"verdict",caption:`${fracStr(i.num,i.den)} = 2 × ${fracStr(l.num,l.den)}.${x}`,spoken:`${cap$b(g)} is exactly two of the ${b} wedges, so twice as many chose ${i.label.toLowerCase()} as ${l.label.toLowerCase()}.${y}`}]})}function genCompareSlices(n){if(n.mathematicalForm!=="comparison_statement"&&n.mathematicalForm!=="symbolic_choice")return null;const t=n.stem.toLowerCase(),e=readSectors(n);if(!e)return null;if(n.mathematicalForm==="symbolic_choice"){const h=readTotal(n),f=genStatementTrueHalf(n,e);if(f)return f;const g=genStatementTrueTimes(n,e,h);if(g)return g}const s=h=>h.num/h.den;if(/true or false/.test(t)&&e.length===2){const h=n.correctAnswer.trim().toLowerCase();if(h!=="true"&&h!=="false")return null;const f=e.find(E=>t.indexOf(E.label.toLowerCase())>=0);if(!f)return null;const g=e.find(E=>E!==f),b=s(f)>s(g);if((b?"true":"false")!==h)return null;const x=b?f:g,y=e.indexOf(x);return buildExample({generatorId:"DS.P4.M03.compare-slices.v1",question:n,title:"Is the claim about the two slices true?",computed:{firstNum:f.num,firstDen:f.den,otherNum:g.num,otherDen:g.den},steps:[{visual_data:pieVisual$1(n,y),animation:"show_whole",caption:"The bigger slice means more people.",spoken:"A bigger slice fills more of the circle, so more people chose it."},{visual_data:pieVisual$1(n,y),animation:"pop_sector",caption:"Find which slice is bigger.",spoken:`The ${x.label.toLowerCase()} slice fills the larger part of the circle here.`},{visual_data:eq$8([fracStr(f.num,f.den),b?">":"<",fracStr(g.num,g.den)]),animation:"verdict",caption:b?"So the claim is true.":"So the claim is false.",spoken:`Comparing the two fractions of the whole tells you whether the statement is ${h}.`}]})}const o=/fewest|least|smallest|lowest/.test(t),r=/most|more popular|biggest|largest|greatest|highe
71st/.test(t);if(!o&&!r)return null;const a=e.reduce((h,f)=>o?s(f)<s(h)?f:h:s(f)>s(h)?f:h);if(e.filter(h=>s(h)===s(a)).length>1||a.label.trim().toLowerCase()!==n.correctAnswer.trim().toLowerCase())return null;const l=e.indexOf(a),u=e.filter(h=>h!==a).sort((h,f)=>s(f)-s(h)),d=o?u[u.length-1]:u[0],m=fractionWords$1(a.num,a.den);return m?buildExample({generatorId:"DS.P4.M03.compare-slices.v1",question:n,title:o?"Which slice is the smallest?":"Which slice is the biggest?",computed:{targetNum:a.num,targetDen:a.den,runnerNum:d.num,runnerDen:d.den},steps:[{visual_data:pieVisual$1(n,l),animation:"show_whole",caption:o?"The smallest slice means the fewest.":"The biggest slice means the most.",spoken:o?"The smallest slice fills the least of the circle, so the fewest people chose it.":"The biggest slice fills the most of the circle, so the most people chose it."},{visual_data:pieVisual$1(n,l),animation:"pop_sector",caption:`Look for the ${o?"smallest":"biggest"} slice.`,spoken:`The ${a.label.toLowerCase()} slice is ${m} of the whole — the ${o?"smallest":"biggest"} part here.`},{visual_data:eq$8([fracStr(a.num,a.den),o?"<":">",fracStr(d.num,d.den)]),animation:"verdict",caption:`So ${a.label.toLowerCase()} is the answer.`,spoken:`Comparing the slices as fractions of the whole shows ${a.label.toLowerCase()} is the ${o?"fewest":"most"}.`}]}):null}function genFractionRead(n){if(n.mathematicalForm!=="pie_chart_fraction_read")return null;const t=n.stem.toLowerCase(),e=readSectors(n);if(!e)return null;const s=n.correctAnswer.trim(),o=e.filter(d=>t.includes(d.label.toLowerCase()));if(/percentage|percent|%/.test(t)){const d=o[0];if(!d||d.num*100%d.den!==0)return null;const m=d.num*100/d.den;if(`${m}%`!==s)return null;const h=fractionWords$1(d.num,d.den);if(!h)return null;const f=pieVisual$1(n,e.indexOf(d)),g=pieVisual$1(n,e.indexOf(d),d.den);return buildExample({generatorId:"DS.P4.M03.fraction-read.v1",question:n,title:`${fracStr(d.num,d.den)} as a percentage`,computed:{num:d.num,den:d.den,pct:m},steps:[{visual_data:f,animation:"show_whole",caption:"The whole circle is one hundred percent.",spoken:"The whole circle stands for one hundred percent of the class."},{visual_data:g,animation:"pop_sector",caption:`This slice is ${h} of the whole — ${d.den===1?"the whole circle":`1 of ${d.den} equal parts`}.`,spoken:`The ${d.label.toLowerCase()} slice covers ${h} of the circle. The dashed guides split the circle into ${numberToWordsLong(d.den)} equal parts, and this slice fills ${numberToWordsLong(d.num)} of them.`},{visual_data:eq$8([fracStr(d.num,d.den),"=",`${m}%`]),animation:"convert",caption:`${cap$b(h)} of the whole is that percentage.`,spoken:`${cap$b(h)} of one hundred percent is ${numberToWordsLong(m)} percent.`}]})}if(/\bnot\b/.test(t)){const d=o[0];if(!d)return null;const m=reduce(d.den-d.num,d.den);if(fracStr(m.num,m.den)!==s)return null;const h=fractionWords$1(d.num,d.den),f=fractionWords$1(m.num,m.den);if(!h||!f)return null;const g=pieVisual$1(n,e.indexOf(d)),b=pieVisual$1(n,e.indexOf(d),d.den);return buildExample({generatorId:"DS.P4.M03.fraction-read.v1",question:n,title:`What fraction is NOT ${d.label}?`,computed:{num:d.num,den:d.den,resNum:m.num,resDen:m.den},steps:[{visual_data:g,animation:"show_whole",caption:"The whole circle is one whole.",spoken:"The whole circle is one whole — every slice added together."},{visual_data:b,animation:"pop_sector",caption:`This slice is ${h} of the whole.`,spoken:`The ${d.label.toLowerCase()} slice is ${h} of the circle. The dashed guides split the circle into ${numberToWordsLong(d.den)} equal parts, and this slice fills ${numberToWordsLong(d.num)} of them.`},{visual_data:eq$8(["1","−",fracStr(d.num,d.den),"=",fracStr(m.num,m.den)]),animation:"subtract",caption:`The rest of the whole is ${f}.`,spoken:`Take ${h} away from one whole. What is left, ${f}, is the fraction that did not choose it.`}]})}if(/one of these|these two/.test(t)&&o.length>=2){const[d,m]=o,h=reduce(d.num*m.den+m.num*d.den,d.den*m.den);if(fracStr(h.num,h.den)!==s)return null;const f=fractionWords$1(d.num,d.den),g=fractionWords$1(m.num,m.den),b=fractionWords$1(h.num,h.den);if(!f||!g||!b)return null;const x=pieVisual$1(n,e.indexOf(d)),y=pieVisual$1(n,e.indexOf(d),lcm$1(d.den,m.den));return buildExample({generatorId:"DS.P4.M03.fraction-read.v1",question:n,title:"What fraction chose one of the two?",computed:{aNum:d.num,aDen:d.den,bNum:m.num,bDen:m.den,resNum:h.num,resDen:h.den},steps:[{visual_data:x,animation:"show_whole",caption:"Add the two slices together.",spoken:"To find the fraction that chose one of the two, add the two slices of the whole."},{visual_data:y,animation:"pop_sector",caption:`One slice is ${f}, the other ${g}.`,spoken:`The ${d.label.toLowerCase()} slice is ${f} and the ${m.label.toLowerCase()} slice is ${g} of the whole.`},{visual_data:eq$8([fracStr(d.num,d.den),"+",fracStr(m.num,m.den),"=",fracStr(h.num,h.den)]),animation:"add",caption:`Together they make ${b}.`,spoken:`Add ${f} and ${g} to make ${b} of the whole.`}]})}const r=o.find(d=>fracStr(reduce(d.num,d.den).num,reduce(d.num,d.den).den)===s);if(!r)return null;const a=reduce(r.num,r.den),i=fractionWords$1(a.num,a.den);if(!i)return null;const l=pieVisual$1(n,e.indexOf(r)),u=pieVisual$1(n,e.indexOf(r),a.den);return buildExample({generatorId:"DS.P4.M03.fraction-read.v1",question:n,title:`What fraction of the circle is ${r.label}?`,computed:{num:a.num,den:a.den},steps:[{visual_data:l,animation:"show_whole",caption:"The whole circle is the whole class.",spoken:"The whole pie chart is the whole class, split into slices."},{visual_data:u,animation:"pop_sector",caption:`${a.den} equal parts; this slice fills ${a.num}.`,spoken:`Look at how much of the circle the ${r.label.toLowerCase()} slice fills. The dashed guides split the circle into ${numberToWordsLong(a.den)} equal parts, and this slice fills ${numberToWordsLong(a.num)} of them.`},{visual_data:eq$8([r.label,"=",fracStr(a.num,a.den)]),animation:"read",caption:`The slice is ${i} of the whole.`,spoken:`The ${r.label.toLowerCase()} slice covers ${i} of the whole circle, so the fraction is ${i}.`}
71]})}const DS_P4_M03_GENERATORS=[{id:"DS.P4.M03.fraction-of-total.v1",microSkillId:"DS.P4.M03",forms:["context_calculation"],generate:genFractionOfTotal},{id:"DS.P4.M03.combine-sectors.v1",microSkillId:"DS.P4.M03",forms:["context_calculation","comparison_statement"],generate:genCombineSectors},{id:"DS.P4.M03.times-as-many.v1",microSkillId:"DS.P4.M03",forms:["comparison_statement"],generate:genTimesAsMany},{id:"DS.P4.M03.compare-slices.v1",microSkillId:"DS.P4.M03",forms:["comparison_statement","symbolic_choice"],generate:genCompareSlices},{id:"DS.P4.M03.fraction-read.v1",microSkillId:"DS.P4.M03",forms:["pie_chart_fraction_read"],generate:genFractionRead}],BAR_DENOMS=new Set([2,3,4,5,6,8,10]),DENOM_PLURAL={2:"halves",3:"thirds",4:"quarters",5:"fifths",6:"sixths",7:"sevenths",8:"eighths",9:"ninths",10:"tenths",12:"twelfths"},DENOM_SINGULAR={2:"half",3:"third",4:"quarter",5:"fifth",6:"sixth",7:"seventh",8:"eighth",9:"ninth",10:"tenth",12:"twelfth"};function fractionWords(n,t){const e=n===1?DENOM_SINGULAR[t]:DENOM_PLURAL[t];return`${numberToWords(n)} ${e}`}const BLUE="#2563eb";function barVisual$1(n,t,e){return{type:"fraction_shape",shape:"rectangle",total_parts:n,shaded_parts:t,color:BLUE,...e?{conclude_label:e}:{}}}function toAdd(n,t){return!Number.isInteger(n)||n<2||!t.length||t.some(e=>!Number.isInteger(e)||e<0)?null:{denom:n,addends:t,total:t.reduce((e,s)=>e+s,0)}}function parseAdd(n){const t=normaliseStem(n.stem);if(n.mathematicalForm==="visual_quantity_to_symbol"){const r=n.visualData;if(!r||r.type!=="fraction_shape")return null;const a=r.shaded_parts;if(!Number.isInteger(a))return null;const i=[...t.matchAll(/(\d+)\s+more/gi)].map(l=>Number(l[1]));return i.length?toAdd(r.total_parts,[a,...i]):null}const e=[...t.matchAll(/(\d+)\s*\/\s*(\d+)/g)].map(r=>[Number(r[1]),Number(r[2])]),s=t.match(/__\s*\/\s*(\d+)/);let o=s?Number(s[1]):null;if(e.length){const r=e.every(i=>i[1]===e[0][1])?e[0][1]:null;if(o==null&&(o=r),o==null||r!=null&&r!==o)return null;const a=e.filter(i=>i[1]===o).map(i=>i[0]);return toAdd(o,a)}if(o!=null){const r=DENOM_PLURAL[o];if(!r)return null;const a=[...t.matchAll(new RegExp(`(\\d+)\\s+${r}`,"gi"))].map(i=>Number(i[1]));return a.length?toAdd(o,a):null}return null}function barSteps(n){const{denom:t,addends:e,total:s}=n,o=e[0],r=e.slice(1),a=s-o,i=DENOM_PLURAL[t];if(r.length>=2){const m=r[r.length-1],h=r.slice(0,-1),f=s-m,g=b=>b===1?DENOM_SINGULAR[t]:i;return[{visual_data:barVisual$1(t,o),animation:"shade_start",caption:`Start: ${o} of ${t} pieces shaded.`,spoken:`${capitalised(fractionWords(o,t))} ${o===1?"is":"are"} shaded. The bottom number tells us the pieces are all ${i}.`},{visual_data:barVisual$1(t,f),animation:"add_more",caption:`Shade ${h.join(" then ")} more: ${f} ${g(f)} so far.`,spoken:`Add ${h.map(b=>fractionWords(b,t)).join(", then ")}. Each new piece is the same size. That makes ${fractionWords(f,t)} so far.`},{visual_data:barVisual$1(t,s,`${s}/${t}`),animation:"write_total",caption:`Then ${m} more: ${s} ${g(s)}. Bottom stays ${t}.`,spoken:`Then ${fractionWords(m,t)} more makes ${fractionWords(s,t)}. We added the top numbers and kept the bottom the same.`}]}const l=`Shade ${a} more — same size ${i}.`,u=`We add ${fractionWords(a,t)} more. Each new piece is the same size, so the bottom number does not change.`,d=e.length===2?`${o} + ${a} = ${s}. Bottom stays ${t}.`:`Count them: ${s} ${i}. Bottom stays ${t}.`;return[{visual_data:barVisual$1(t,o),animation:"shade_start",caption:`Start: ${o} of ${t} pieces shaded.`,spoken:`${capitalised(fractionWords(o,t))} ${o===1?"is":"are"} shaded. The bottom number tells us the pieces are all ${i}.`},{visual_data:barVisual$1(t,s),animation:"add_more",caption:l,spoken:u},{visual_data:barVisual$1(t,s,`${s}/${t}`),animation:"write_total",caption:d,spoken:`Now ${fractionWords(s,t)} ${s===1?"is":"are"} shaded. We added the top numbers and kept the bottom the same.`}]}const frac=(n,t)=>`${n}/${t}`;function opTerms(n){const t=[];return n.addends.forEach((e,s)=>{s>0&&t.push("+"),t.push(frac(e,n.denom))}),t}function eqSteps(n){const{denom:t,total:e,addends:s}=n,o=opTerms(n),r=s.map(a=>numberToWords(a)).join(" plus ");return[{visual_data:{type:"equation_display",terms:o},animation:"show_start",caption:`Same bottom number: ${t}.`,spoken:`Every fraction here is counted in ${DENOM_PLURAL[t]} — the same size piece.`},{visual_data:{type:"equation_display",terms:[...o,"=","__"]},animation:"pulse_unknown",caption:`Add the tops: ${s.join(" + ")}.`,spoken:`To add, we add just the top numbers: ${r}. The bottom number stays the same.`},{visual_data:{type:"equation_display",terms:[...o,"=",frac(e,t)]},animation:"write_answer",caption:`Tops make ${e}. Bottom stays ${t}.`,spoken:`The tops make ${numberToWords(e)}, and the bottom is still ${numberToWords(t)}. So the answer is ${fractionWords(e,t)}.`}]}const canDrawBar=n=>BAR_DENOMS.has(n.denom)&&n.total<=n.denom&&n.addends[0]<=n.denom;function addSteps(n){return canDrawBar(n)?barSteps(n):eqSteps(n)}function computedOf(n,t={}){const e={denom:n.denom,total:n.total,answer:n.total,...t};return n.addends.forEach((s,o)=>e[`addend${o}`]=s),e.added=n.total-n.addends[0],n.addends.length>=3&&(e.soFar=n.total-n.addends[n.addends.length-1]),e}const ADD_FORMS=["symbolic_numeric_entry","varied_symbolic_calculation","context_calculation","visual_quantity_to_symbol"],barAdd={id:"FDP.P6.M01.bar-add.v1",microSkillId:"FDP.P6.M01",forms:ADD_FORMS,generate(n){const t=parseAdd(n);if(!t||!canDrawBar(t))return null;const e=answerInt(n);if(e===null||e!==t.total)return null;const s=t.addends.map(o=>frac(o,t.denom)).join(" + ");return buildExample({generatorId:this.id,question:n,title:`What is ${s}?`,computed:computedOf(t),steps:barSteps(t)})}},equationAdd={id:"FDP.P6.M01.equation-add.v1",microSkillId:"FDP.P6.M01",forms:ADD_FORMS,generate(n){const t=parseAdd(n);if(!t)return null;const e=answerInt(n);return e===null||e!==t.total?null:buildExample({generatorId:this.id,question:n,title:`What is ${opTerms(t).join(" ")}?`,computed:computedOf(t),steps:eqSteps(t)})}},selectSum={id:"FDP.P6.M01.select-sum.v1",microSkillId:"FDP.P6.M01",forms:["symbolic_calculation","context_calculation"],generate(n){const t=parseAdd(n);if(!t)return null;const e=n.correctAnswer.trim();return!/^\d+\s*\/\s*\d+$/.test(e)||e.replace(/\s+/g,"")!==frac(t.total,t.denom)?null:buildExample({generatorId:this.id,question:n,title:`What is ${opTerms(t).join(" ")}?`,computed:computedOf(t),steps:addSteps(t)})}},selectMixed={id:"FDP.P6.M01.select-mixed.v1",microSkillId:"FDP.P6.M01",forms:["symbolic_calculation","context_calculation"],generate(n){const t=parseAdd(n);if(!t||t.total<=t.denom)return null;const e=Math.floor(t.total/t.denom),s=t.total%t.denom;if(s===0)return null;const o=`${e} ${s}/${t.denom}`;if(n.correctAnswer.replace(/\s+/g," ").trim()!==o)return null;const r=opTerms(t);return buildExample({generatorId:this.id,question:n,title:`${r.j
71oin(" ")} as a mixed number`,computed:computedOf(t,{whole:e,rem:s}),steps:[{visual_data:{type:"equation_display",terms:r},animation:"show_start",caption:`Same bottom number: ${t.denom}.`,spoken:`Both fractions are counted in ${DENOM_PLURAL[t.denom]}, the same size piece.`},{visual_data:{type:"equation_display",terms:[...r,"=",frac(t.total,t.denom)]},animation:"write_answer",caption:`Add the tops: ${t.addends.join(" + ")} = ${t.total}.`,spoken:`Add the tops: ${t.addends.map(numberToWords).join(" plus ")} makes ${numberToWords(t.total)}. That is ${fractionWords(t.total,t.denom)}.`},{visual_data:{type:"equation_display",terms:[frac(t.total,t.denom),"=",`${e} ${s}/${t.denom}`]},animation:"regroup",caption:`${t.total}/${t.denom} = ${e} whole and ${s}/${t.denom}.`,spoken:`${capitalised(fractionWords(t.total,t.denom))} is more than a whole. ${capitalised(numberToWords(t.denom))} ${DENOM_PLURAL[t.denom]} make one whole, leaving ${fractionWords(s,t.denom)} — that is ${numberToWords(e)} and ${fractionWords(s,t.denom)}.`}]})}},missingNumerator={id:"FDP.P6.M01.missing-numerator.v1",microSkillId:"FDP.P6.M01",forms:["equation_completion"],generate(n){const e=normaliseStem(n.stem).split("=");if(e.length!==2)return null;const s=[...e[0].matchAll(/(\d+)\s*\/\s*(\d+)/g)].map(x=>[Number(x[1]),Number(x[2])]),o=e[0].match(/__\s*\/\s*(\d+)/),r=e[1].match(/(\d+)\s*\/\s*(\d+)/);if(!o||!r)return null;const a=Number(o[1]);if(s.some(x=>x[1]!==a)||Number(r[2])!==a)return null;const i=s.reduce((x,y)=>x+y[0],0),l=Number(r[1]),u=l-i,d=answerInt(n);if(d===null||d!==u||u<0)return null;const m=s.map(x=>x[0]),h=[];[...e[0].matchAll(/(\d+\s*\/\s*\d+|__\s*\/\s*\d+)/g)].map(x=>x[0].replace(/\s+/g,"")).forEach((x,y)=>{y>0&&h.push("+"),h.push(x.startsWith("__")?"__":x)});const g=h.map(x=>x==="__"?frac(u,a):x),b=["__","=",frac(l,a),...m.flatMap(x=>["−",frac(x,a)])];return buildExample({generatorId:this.id,question:n,title:`${h.join(" ")} = ${frac(l,a)}`,computed:{denom:a,target:l,knownSum:i,missing:u,answer:u,...Object.fromEntries(m.map((x,y)=>[`known${y}`,x]))},steps:[{visual_data:{type:"equation_display",terms:[...h,"=",frac(l,a)]},animation:"show_start",caption:`One top is missing. The total is ${l}/${a}.`,spoken:"A top number is missing. Because the bottoms are all the same, we only work with the tops."},{visual_data:{type:"equation_display",terms:b},animation:"pulse_unknown",caption:`Missing top = ${l} − ${m.join(" − ")}.`,spoken:`Take the known tops away from the total top: ${numberToWords(l)} minus ${m.map(numberToWords).join(" minus ")}.`},{visual_data:{type:"equation_display",terms:[...g,"=",frac(l,a)]},animation:"write_answer",caption:`Missing top is ${u}. Bottom stays ${a}.`,spoken:`The missing top is ${numberToWords(u)}, so the fraction is ${fractionWords(u,a)}. The bottom number never changed.`}]})}},misconception$1={id:"FDP.P6.M01.misconception.v1",microSkillId:"FDP.P6.M01",forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(/(\d+)\s*\/\s*(\d+)\s*\+\s*(\d+)\s*\/\s*(\d+)\s*=/);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]),a=Number(e[4]);if(o!==a)return null;const i=toAdd(o,[s,r]);return!i||n.correctAnswer.replace(/\s+/g,"")!==frac(i.total,o)?null:buildExample({generatorId:this.id,question:n,title:`What is really ${frac(s,o)} + ${frac(r,o)}?`,computed:computedOf(i),steps:addSteps(i)})}},whichPair={id:"FDP.P6.M01.which-pair.v1",microSkillId:"FDP.P6.M01",forms:["alternate_reasoning_prompt"],generate(n){const e=normaliseStem(n.stem).match(/adds? up to (?:make )?(\d+)\s*\/\s*(\d+)/i),s=n.correctAnswer.match(/(\d+)\s*\/\s*(\d+)\s*and\s*(\d+)\s*\/\s*(\d+)/i);if(!e||!s)return null;const o=Number(e[1]),r=Number(e[2]),a=Number(s[1]),i=Number(s[2]),l=Number(s[3]),u=Number(s[4]);if(i!==r||u!==r||a+l!==o)return null;const d=toAdd(r,[a,l]);if(!d)return null;const m=DENOM_PLURAL[r];return buildExample({generatorId:this.id,question:n,title:`Which two ${m} make ${frac(o,r)}?`,computed:computedOf(d),steps:[{visual_data:{type:"equation_display",terms:["?","+","?","=",frac(o,r)]},animation:"show_target",caption:`We need two tops that make ${o}.`,spoken:`The fractions all have the same bottom number, so when we add them only the top numbers change. We need two top numbers that add together to make ${numberToWords(o)}.`},{visual_data:{type:"equation_display",terms:[frac(a,r),"+",frac(l,r),"=","?"]},animation:"try_pair",caption:`Try ${a} + ${l}.`,spoken:`Let's check this pair by adding their top numbers: ${numberToWords(a)} add ${numberToWords(l)}.`},{visual_data:{type:"equation_display",terms:[frac(a,r),"+",frac(l,r),"=",frac(o,r)]},animation:"write_answer",caption:`${a} + ${l} = ${o}. This pair works!`,spoken:`${capitalised(numberToWords(a))} add ${numberToWords(l)} is ${numberToWords(o)}, the top number we need, so this is the matching pair.`}]})}},numberLineAdd={id:"FDP.P6.M01.number-line-add.v1",microSkillId:"FDP.P6.M01",forms:["number_line_location"],generate(n){const t=parseAdd(n);if(!t||t.addends.length!==2)return null;const e=answerInt(n);if(e===null||e!==t.total)return null;const{denom:s,total:o,addends:r}=t,[a,i]=r,l=DENOM_PLURAL[s];return buildExample({generatorId:this.id,question:n,title:`Start at ${a}/${s}, jump ${i}/${s} further`,computed:{denom:s,first:a,hop:i,total:o,answer:o},steps:[{visual_data:{type:"number_line",min:0,max:s,step:1,marked:[a]},animation:"show_start",caption:`Start at ${a} of ${s} ${l}.`,spoken:`The frog starts at ${fractionWords(a,s)} on the line.`},{visual_data:{type:"number_line",min:0,max:s,step:1,hops:[{from:a,to:o,label:`+${i}`}],marked:[a]},animation:"hop",caption:`Jump ${i} more ${l} forward.`,spoken:`It jumps ${fractionWords(i,s)} further along the line.`},{visual_data:{type:"number_line",min:0,max:s,step:1,hops:[{from:a,to:o,label:`+${i}`}],unknown:o,conclude_label:`${o}/${s}`},animation:"land",caption:`It lands on ${o} of ${s} — ${o}/${s}.`,spoken:`The frog lands on ${fractionWords(o,s)}.`}]})}},FDP_P6_M01_GENERATORS=[barAdd,equationAdd,selectSum,selectMixed,missingNumerator,misconception$1,whichPair,numberLineAdd];function aliasesFor(n){return FDP_P6_M01_GENERATORS.filter(t=>t!==selectMixed).map(t=>({...t,id:t.id.replace("FDP.P6.M01",n),microSkillId:n}))}const FDP_P6_M08_GENERATORS=aliasesFor("FDP.P6.M08"),FDP_P6_M09_GENERATORS=aliasesFor("FDP.P6.M09"),COLOR$1="#2563eb",UNIT$1={2:["half","halves"],3:["third","thirds"],4:["quarter","quarters"],5:["f
71ifth","fifths"],6:["sixth","sixths"],8:["eighth","eighths"],10:["tenth","tenths"],12:["twelfth","twelfths"]};function unitPlural(n){const t=UNIT$1[n];return t?t[1]:`${numberToWords(n)}ths`}function fw(n,t){const e=UNIT$1[t],s=e?n===1?e[0]:e[1]:`${numberToWords(t)}th${n===1?"":"s"}`;return`${numberToWords(n)} ${s}`}const DRAWABLE=new Set([4,5,6,8,10]),bar$1=(n,t,e)=>({type:"fraction_shape",shape:"rectangle",total_parts:n,shaded_parts:t,color:COLOR$1,split_columns:!0,...e?{conclude_label:e}:{}}),eq$7=n=>({type:"equation_display",terms:n});function parseTwoFractions(n){const t=[...n.matchAll(/(\d+)\/(\d+)/g)];if(t.length<2)return null;const e=t.map(s=>({n:Number(s[1]),d:Number(s[2])}));return[e[0],e[1]]}function resolvePair(n,t){if(n.d===t.d)return null;const e=Math.max(n.d,t.d),s=n.d<t.d?n:t,o=n.d<t.d?t:n;if(o.d!==e||e%s.d!==0)return null;const r=e/s.d,a=s.n*r,i=a+o.n;return{D:e,small:s,keep:o,factor:r,convertedNum:a,total:i}}function totalDemo(n,t,e,s,o){const r=resolvePair(e,s);if(r===null)return null;const{D:a,small:i,keep:l,factor:u,convertedNum:d,total:m}=r,h=Number(n.correctAnswer);if(Number.isInteger(h)){if(m!==h)return null}else if(`${m}/${a}`!==n.correctAnswer.trim())return null;const g=m,b=o??`${m}/${a}`,x={D:a,convertedNum:d,total:m,answer:g,factor:u},y=`How do we work out ${i.n}/${i.d} + ${l.n}/${l.d}?`;return DRAWABLE.has(a)&&m<=a?buildExample({generatorId:t,question:n,title:y,computed:x,steps:[{visual_data:bar$1(i.d,i.n),animation:"show_parts",caption:`${i.n}/${i.d} — count in ${unitPlural(a)}.`,spoken:`The pieces are different sizes, so first we change ${fw(i.n,i.d)} into ${unitPlural(a)}.`},{visual_data:bar$1(a,d),animation:"subdivide_parts",caption:`${i.n}/${i.d} = ${d}/${a}.`,spoken:`${capitalised(fw(i.n,i.d))} is the same as ${fw(d,a)} — the same amount, just smaller pieces.`},{visual_data:bar$1(a,m,b),animation:"write_fraction",caption:`${d}/${a} + ${l.n}/${a} = ${m}/${a}.`,spoken:`Now both are ${unitPlural(a)}, so we add. ${capitalised(fw(d,a))} and ${fw(l.n,a)} make ${fw(m,a)}.`}]}):buildExample({generatorId:t,question:n,title:y,computed:x,steps:[{visual_data:eq$7([`${e.n}/${e.d}`,"+",`${s.n}/${s.d}`]),animation:"show_problem",caption:`Change ${i.n}/${i.d} into ${unitPlural(a)}.`,spoken:`The denominators are different, so we cannot add yet. First we change ${fw(i.n,i.d)} into ${unitPlural(a)}.`},{visual_data:eq$7([`${i.n}/${i.d}`,"×",`${u}/${u}`,"=",`${d}/${a}`]),animation:"convert_term",caption:`${i.n}/${i.d} × ${u}/${u} = ${d}/${a}.`,spoken:`${capitalised(fw(i.n,i.d))} is the same as ${fw(d,a)} — multiply top and bottom by ${numberToWords(u)}. Now both fractions are ${unitPlural(a)}.`},{visual_data:eq$7([`${l.n}/${a}`,"+",`${d}/${a}`,"=",`${m}/${a}`]),animation:"write_fraction",caption:`${l.n}/${a} + ${d}/${a} = ${m}/${a}.`,spoken:`Adding the ${unitPlural(a)}: ${fw(l.n,a)} and ${fw(d,a)} make ${fw(m,a)}.`}]})}function missingAddendDemo(n,t,e,s,o){const{n:r,d:a}=s;if(a%e.d!==0||a%o!==0)return null;const i=e.n*(a/e.d),l=r-i;if(l<=0||l%(a/o)!==0)return null;const u=l/(a/o),d=Number(n.correctAnswer);if(!Number.isInteger(d)||u!==d)return null;if(DRAWABLE.has(a)&&r<=a&&o===a){const h={};for(let f=0;f<r;f++)h[f]=f<i?"#2563eb":"#f59e0b";return buildExample({generatorId:t,question:n,title:`What is missing? ${e.n}/${e.d} + □ = ${r}/${a}`,computed:{F:a,knownInF:i,answer:u,E:r},steps:[{visual_data:bar$1(e.d,e.n),animation:"show_parts",caption:`Together they make ${r}/${a}; one part is ${e.n}/${e.d}.`,spoken:`Together the two parts make ${fw(r,a)}, and one of them is ${fw(e.n,e.d)}.`},{visual_data:bar$1(a,i),animation:"subdivide_parts",caption:`${e.n}/${e.d} = ${i}/${a}.`,spoken:`First we change ${fw(e.n,e.d)} into ${unitPlural(a)} — that is ${fw(i,a)}.`},{visual_data:{...bar$1(a,r,`${u}/${a}`),part_marks:h},animation:"write_fraction",caption:`Missing = ${r} − ${i} = ${u}.`,spoken:`${capitalised(fw(r,a))} take away ${fw(i,a)} leaves ${fw(u,a)} — that is the missing part.`}]})}const m=a/o;return buildExample({generatorId:t,question:n,title:`${e.n}/${e.d} + □/${o} = ${r}/${a}`,computed:{F:a,unitDenom:o,knownInF:i,missingInF:l,answer:u,E:r,convFactor:m},steps:[{visual_data:eq$7([`${e.n}/${e.d}`,"+","□","=",`${r}/${a}`]),animation:"show_problem",caption:`One part is missing, in ${unitPlural(o)}.`,spoken:`A part is missing, and it is measured in ${unitPlural(o)}, not ${unitPlural(a)}.`},{visual_data:eq$7([`${r}/${a}`,"−",`${i}/${a}`,"=",`${l}/${a}`]),animation:"subtract",caption:`${r}/${a} − ${i}/${a} = ${l}/${a} still needed.`,spoken:`Take the known part away from the total, both counted in ${unitPlural(a)}.`},{visual_data:eq$7([`${l}/${a}`,"=",`${u}/${o}`]),animation:"convert",caption:`${l}/${a} = ${u}/${o}. Missing = ${u}
71.`,spoken:`${capitalised(fw(l,a))} is the same as ${fw(u,o)}, so the missing number is ${fw(u,o)}.`}]})}const FDP_P6_M02_GENERATORS=[{id:"FDP.P6.M02.add-different-denominators.v1",microSkillId:"FDP.P6.M02",forms:["symbolic_calculation","context_calculation","visual_quantity_to_symbol","equation_completion","misconception_contrast_probe","varied_symbolic_calculation"],generate(n){const t=normaliseStem(n.stem),e=t.match(/add up to (\d+)\/(\d+)\. One of them is (\d+)\/(\d+)\. Write the other one in (\w+)/i);if(e){const i={n:Number(e[1]),d:Number(e[2])},l={n:Number(e[3]),d:Number(e[4])},u=Object.entries(UNIT$1).find(([,[,d]])=>d===e[5].toLowerCase())?.[0];return u?missingAddendDemo(n,this.id,l,i,Number(u)):null}if(t.includes("□")){const i=t.match(/(\d+)\/(\d+) \+ (\d+)\/(\d+) = □\/(\d+)/);if(i){const d={n:Number(i[1]),d:Number(i[2])},m={n:Number(i[3]),d:Number(i[4])};return Math.max(d.d,m.d)!==Number(i[5])?null:totalDemo(n,this.id,d,m,null)}const l=t.match(/(\d+)\/(\d+) \+ □\/(\d+) = (\d+)\/(\d+)/);if(l){const d={n:Number(l[1]),d:Number(l[2])},m={n:Number(l[4]),d:Number(l[5])};return missingAddendDemo(n,this.id,d,m,Number(l[3]))}const u=t.match(/□\/(\d+) \+ (\d+)\/(\d+) = (\d+)\/(\d+)/);if(u){const d={n:Number(u[2]),d:Number(u[3])},m={n:Number(u[4]),d:Number(u[5])};return missingAddendDemo(n,this.id,d,m,Number(u[1]))}return null}const s=parseTwoFractions(t);if(!s)return null;const[o,r]=s,a=/^\d+\/\d+$/.test(n.correctAnswer.trim())?n.correctAnswer.trim():null;return totalDemo(n,this.id,o,r,a)}}],COLOR="#2563eb";function multiplierTo100(n){return!Number.isInteger(n)||n<2||n>100||100%n!==0?null:100/n}function percentOf(n,t){const e=multiplierTo100(t);if(e===null)return null;const s=n*e;return Number.isInteger(s)?s:null}const w$d=n=>numberToWords(n),eq$6=n=>({type:"equation_display",terms:n}),square=(n,t,e)=>({type:"fraction_shape",shape:"square",total_parts:n,shaded_parts:t,color:COLOR,...e?{conclude_label:e}:{}});function convertDemo(n,t,e,s,o){if(s===100)return buildExample({generatorId:t,question:n,title:`How do we write ${e}/100 as a percentage?`,computed:{N:e,D:s,pct:o,answer:o},steps:[{visual_data:eq$6([`${e}/100`,"=","?","%"]),animation:"show_parts",caption:`${e}/100 — per cent means out of 100.`,spoken:"A percentage is a number out of one hundred. This fraction already has one hundred on the bottom."},{visual_data:eq$6([`${e}/100`,"=",`${e}`,"per 100"]),animation:"read_top",caption:"The bottom is already 100.",spoken:`Because the bottom is already one hundred, the top number, ${w$d(e)}, is the count out of a hundred.`},{visual_data:eq$6([`${e}/100`,"=",`${o}%`]),animation:"write_percent",caption:`${e}/100 = ${o}%`,spoken:`So ${w$d(e)} out of one hundred is ${w$d(o)} per cent.`}]});const r=multiplierTo100(s);if(s<=20){const a=i=>({type:"percent_bar",parts:s,whole_label:"100%",...i});return buildExample({generatorId:t,question:n,title:`How do we write ${e}/${s} as a percentage?`,computed:{N:e,D:s,k:r,pct:o,answer:o},steps:[{visual_data:a({}),animation:"pb_split",caption:`Write ${e}/${s} as a percentage.`,spoken:`A percentage is a number out of one hundred. The whole bar is one hundred per cent, split into ${w$d(s)} equal parts, because the bottom of the fraction is ${w$d(s)}.`},{visual_data:a({part_label:`${r}%`,first_label:`1/${s} = ${r}%`}),animation:"pb_value",caption:`${s} × ${r} = 100, so each part is ${r}%.`,spoken:`${capitalised(w$d(s))} times ${w$d(r)} makes one hundred, so each part is ${w$d(r)} per cent.`},{visual_data:a({part_label:`${r}%`,lit:e,lit_label:`${e}/${s} = ${o}%`}),animation:"pb_shade",caption:`${e}/${s} = ${o}/100 = ${o}%`,spoken:`Shade ${w$d(e)} of the parts: ${w$d(e)} times ${w$d(r)} per cent is ${w$d(o)} out of one hundred, so ${w$d(e)} over ${w$d(s)} is ${w$d(o)} per cent.`}]})}return buildExample({generatorId:t,question:n,title:`How do we write ${e}/${s} as a percentage?`,computed:{N:e,D:s,k:r,pct:o,answer:o},steps:[{visual_data:eq$6([`${e}/${s}`,"=","?/100"]),animation:"show_parts",caption:`Write ${e}/${s} as a percentage.`,spoken:`A percentage is a number out of one hundred. The bottom of this fraction is ${w$d(s)}, so we make it one hundred.`},{visual_data:eq$6([`${e}/${s}`,"×",`${r}/${r}`,"=",`${o}/100`]),animation:"scale",caption:`Scale to 100: multiply by ${r}.`,spoken:`${capitalised(w$d(s))} times ${w$d(r)}
71 makes one hundred, so the top rides along: ${w$d(e)} times ${w$d(r)} is ${w$d(o)}.`},{visual_data:eq$6([`${o}/100`,"=",`${o}%`]),animation:"write_percent",caption:`${e}/${s} = ${o}/100 = ${o}%`,spoken:`So ${w$d(e)} over ${w$d(s)} is ${w$d(o)} out of one hundred — ${w$d(o)} per cent.`}]})}function fractionFromStem(n,t){const e=normaliseStem(n),s=e.match(/(\d+)\/(\d+)/);if(s){const a=Number(s[1]),i=Number(s[2]);if(percentOf(a,i)===t)return{N:a,D:i}}const o=e.match(/(\d+)\s+out of\s+(\d+)/i);if(o){const a=Number(o[1]),i=Number(o[2]);if(percentOf(a,i)===t)return{N:a,D:i}}const r=(e.match(/\d+/g)??[]).map(Number);for(let a=0;a<r.length;a++)for(let i=0;i<r.length;i++){if(a===i)continue;const l=r[a],u=r[i];if(l>0&&l<u&&percentOf(l,u)===t)return{N:l,D:u}}return null}function convertFraction(n,t){const e=Number(n.correctAnswer.trim());if(!Number.isInteger(e)||e<1||e>100)return null;const s=fractionFromStem(n.stem,e);return s?convertDemo(n,t,s.N,s.D,e):null}function visualPercent(n,t){const e=n.visualData;if(!e||e.type!=="fraction_shape")return null;const s=e.shaded_parts,o=e.total_parts;if(!Number.isInteger(s)||s<1||s>=o)return null;const r=percentOf(s,o);if(r===null||Number(n.correctAnswer.trim())!==r||o!==2&&o!==4&&o!==5&&o!==10)return null;const a=multiplierTo100(o);return buildExample({generatorId:t,question:n,title:"What percentage of the shape is shaded?",computed:{N:s,D:o,k:a,pct:r,answer:r},steps:[{visual_data:square(o,0),animation:"show_parts",caption:`The shape has ${o} equal parts.`,spoken:`The shape is split into ${w$d(o)} equal parts.`},{visual_data:square(o,s),animation:"shade_parts",caption:`${s} of ${o} shaded — that is ${s}/${o}.`,spoken:`${capitalised(w$d(s))} of the ${w$d(o)} parts are shaded, so the shaded fraction is ${w$d(s)} over ${w$d(o)}.`},{visual_data:square(o,s,`${r}%`),animation:"write_percent",caption:`${s}/${o} = ${r}/100 = ${r}%`,spoken:`Each part is worth ${w$d(a)} out of a hundred, so ${w$d(s)} over ${w$d(o)} is ${w$d(r)} per cent.`}]})}function percentToFraction(n,t){const s=normaliseStem(n.stem).match(/(\d+)\s*%/);if(!s)return null;const o=Number(s[1]);if(!Number.isInteger(o)||o<1||o>99)return null;const r=n.correctAnswer.trim().match(/^(\d+)\/(\d+)$/);if(!r)return null;const a=Number(r[1]),i=Number(r[2]);if(percentOf(a,i)!==o)return null;const l=100/i;return!Number.isInteger(l)||l<2?null:buildExample({generatorId:t,question:n,title:`Which fraction is equal to ${o}%?`,computed:{P:o,N:a,D:i,f:l,answer:o},steps:[{visual_data:eq$6([`${o}%`,"=",`${o}/100`]),animation:"show_percent",caption:`${o}% means ${o} out of 100.`,spoken:`Per cent means out of one hundred, so ${w$d(o)} per cent is ${w$d(o)} out of a hundred.`},{visual_data:eq$6([`${o}/100`,"=",`${a}/${i}`]),animation:"simplify",caption:`Simplify ${o}/100: divide by ${l}.`,spoken:`We make it simpler. ${capitalised(w$d(o))} and one hundred both divide by ${w$d(l)}, giving ${w$d(a)} over ${w$d(i)}.`},{visual_data:eq$6([`${o}%`,"=",`${a}/${i}`]),animation:"land",caption:`${o}% = ${a}/${i}`,spoken:`So ${w$d(o)} per cent is the same as ${w$d(a)} over ${w$d(i)}.`}]})}function tokenPercent(n){const t=n.trim(),e=t.match(/^(\d+)\s*%$/);if(e)return Number(e[1]);const s=t.match(/^(\d+)\/(\d+)$/);return s?percentOf(Number(s[1]),Number(s[2])):null}function scaleChain(n){const t=n.match(/^(\d+)\/(\d+)$/);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=multiplierTo100(s);return o===null?null:[`${e}/${s}`,"×",`${o}/${o}`,"=",`${e*o}/100`]}function compareGreater(n,t){const s=normaliseStem(n.stem).match(/(?:greater|bigger|larger)[,:]?\s*(.+?)\s+or\s+(.+?)\?/i);if(!s)return null;const o=s[1].trim(),r=s[2].trim(),a=tokenPercent(o),i=tokenPercent(r);if(a===null||i===null||a===i)return null;const l=a>i?o:r;if(n.correctAnswer.trim()!==l)return null;const u=scaleChain(o),d=scaleChain(r),m=u!==null||d!==null,h=[...u??[],...u&&d?[","]:[],...d??[]];return buildExample({generatorId:t,question:n,title:`Which is greater, ${o} or ${r}?`,computed:{aPct:a,bPct:i,answer:Math.max(a,i)},steps:[{visual_data:eq$6([o,"?",r]),animation:"show_pair",caption:`Compare ${o} and ${r}.`,spoken:"To compare them fairly, we turn each one into a percentage."},m?{visual_data:eq$6(h),animation:"scale",caption:"Scale each fraction's bottom to 100.",spoken:"We scale the bottom number up to one hundred, and the top number scales with it."}:{visual_data:eq$6([`${a}%`,"?",`${i}%`]),animation:"convert",caption:`${o} = ${a}%, ${r} = ${i}%.`,spoken:`The first works out to ${w$d(a)} per cent, and the second to ${w$d(i)} per cent.`},{visual_data:eq$6([`${a}%`,a>i?">":"<",`${i}%`]),animation:"land",caption:`${l} is greater.`,spoken:`${capitalised(w$d(Math.max(a,i)))} per cent is larger, so the ${a>i?"first":"second"} one is greater.`}]})}function boxOutOf100(n,t){const s=normaliseStem(n.stem).match(/(\d+)\s*%/);if(!s)return null;const o=Number(s[1]);return!Number.isInteger(o)||o<1||o>99||Number(n.correctAnswer.trim())!==o?null:buildExample({generatorId:t,question:n,title:`What number completes: ? out of 100 = ${o}%?`,computed:{P:o,answer:o},steps:[{visual_data:eq$6(["?","/","100","=",`${o}%`]),animation:"show_parts",caption:`? out of 100 equals ${o}%.`,spoken:`We need the top number so that this many out of one hundred equals ${w$d(o)} per cent.`},{visual_data:eq$6([`${o}%`,"=",`${o}/100`]),animation:"rewrite",caption:`${o}% means ${o} out of 100.`,spoken:`Per cent means out of one hundred, so ${w$d(o)} per cent is ${w$d(o)} out of a hundred.`},{visual_data:eq$6([`${o}`,"/","100","=",`${o}%`]),animation:"land",caption:`The box is ${o}
71.`,spoken:`So the missing number is ${w$d(o)}.`}]})}function multiOptionCompare(n,t){const e=normaliseStem(n.stem),s=n.options??[];if(s.length<3)return null;const o=[];for(const b of s){const x=b.trim().match(/^(\d+)\/(\d+)$/);if(!x)return null;const y=percentOf(Number(x[1]),Number(x[2]));if(y===null)return null;o.push(y)}const r=e.match(/not equal to (\d+)\s*%/i),a=/greatest percentage/i.test(e),i=/smallest percentage/i.test(e);if(!r&&!a&&!i)return null;let l;if(r){const b=Number(r[1]),x=o.map((y,E)=>y!==b?E:-1).filter(y=>y>=0);if(x.length!==1)return null;l=x[0]}else{const b=a?Math.max(...o):Math.min(...o),x=o.map((y,E)=>y===b?E:-1).filter(y=>y>=0);if(x.length!==1)return null;l=x[0]}if(n.correctAnswer.trim()!==s[l].trim())return null;const u=s[l].trim(),d=o[l],m=s.map((b,x)=>`${b.trim()} = ${o[x]}%`),h={winnerPct:d,answer:d};o.forEach((b,x)=>h[`pct${x}`]=b);const f=r?`${u} = ${d}% breaks the pattern.`:a?`${u} = ${d}% is the greatest.`:`${u} = ${d}% is the smallest.`,g=r?"That option converts to a different percentage from the rest — that is the odd one out.":a?"That option converts to the highest percentage.":"That option converts to the lowest percentage.";return buildExample({generatorId:t,question:n,title:n.stem.replace(/\.$/,""),computed:h,steps:[{visual_data:eq$6(s.map(b=>b.trim())),animation:"show_options",caption:"Convert each fraction to a percentage.",spoken:"To compare them, we turn every fraction into a percentage out of one hundred."},{visual_data:eq$6(m),animation:"convert_all",caption:"Every option scaled to a percent out of 100.",spoken:"Each fraction's bottom number is scaled up to one hundred, and the top number scales with it."},{visual_data:eq$6([u,"=",`${d}%`]),animation:"land",caption:f,spoken:g}]})}const FDP_P6_M03_GENERATORS=[{id:"FDP.P6.M03.fraction-to-percent.v1",microSkillId:"FDP.P6.M03",forms:["symbolic_calculation","varied_symbolic_calculation","symbolic_numeric_entry","visual_quantity_to_symbol","context_calculation","fraction_identification","comparison_statement","equation_completion"],generate(n){switch(n.mathematicalForm){case"fraction_identification":return percentToFraction(n,this.id);case"comparison_statement":return compareGreater(n,this.id)??multiOptionCompare(n,this.id);case"visual_quantity_to_symbol":return visualPercent(n,this.id);case"equation_completion":return boxOutOf100(n,this.id)??convertFraction(n,this.id);default:return convertFraction(n,this.id)}}}],MICRO$9="GEO.P6.M01",FORMS$7=["symbolic_numeric_entry","symbolic_choice","visual_quantity_to_symbol","misconception_contrast_probe","comparison_statement"],w$c=numberToWords;function buildReflGrid(n,t,e="low"){const s=t+1,o=a=>e==="high"?2*s-a:a;return n==="vertical"?{type:"reflection_grid",grid:{w:2*t+2,h:6},mirror:{axis:"vertical",position:s},shape_points:[[o(1),1],[o(2),1],[o(1),3]]}:{type:"reflection_grid",grid:{w:6,h:2*t+2},mirror:{axis:"horizontal",position:s},shape_points:[[1,o(1)],[3,o(1)],[1,o(2)]]}}function sideOf(n,t,e,s){return(n==="vertical"?t:e)>s?"high":"low"}function buildReflectionExample(n,t,e,s,o,r){const a=r??buildReflGrid(t,e);return buildExample({generatorId:`${MICRO$9}.reflect.v1`,question:n,title:s.title,steps:[{visual_data:a,animation:"pulse_mirror",caption:s.cap1,spoken:s.sp1},{visual_data:a,animation:"measure_to_mirror",caption:s.cap2,spoken:s.sp2},{visual_data:a,animation:"step_across",caption:s.cap3,spoken:s.sp3}],computed:o})}function reflGridVisual(n){const t=n.visualData;return!t||t.type!=="reflection_grid"?null:t}function reflectPoint([n,t],e,s){return e==="vertical"?[2*s-n,t]:[n,2*s-t]}function axisFromStem(n){return/horizontal|above|below|y\s*=/.test(n.toLowerCase())?"horizontal":"vertical"}function firstInt(n){const t=normaliseStem(n).match(/\d+/);return t?Number(t[0]):null}function farthestIndex(n){const t=n.mirror.position,e=n.mirror.axis==="vertical",s=([r,a])=>Math.abs(e?r-t:a-t);let o=0;for(let r=1;r<n.shape_points.length;r++)s(n.shape_points[r])>s(n.shape_points[o])&&(o=r);return o}function farthestDistance(n){const t=n.mirror.position,e=n.mirror.axis==="vertical",[s,o]=n.shape_points[farthestIndex(n)];return Math.abs(e?s-t:o-t)}function mismatchIndex(n){const t=n.mirror.axis,e=n.mirror.position;return n.shape_points.findIndex((s,o)=>{const r=reflectPoint(s,t,e),a=n.reflected_points[o];return r[0]!==a[0]||r[1]!==a[1]})}function inDomain(n){return Number.isInteger(n)&&n>=2&&n<=20}const toLine=n=>"straight to the mirror line",levelWith=(n,t)=>`directly opposite ${t}`;function sameDistanceText(n,t){return{title:"How far is the reflected corner from the mirror line?",cap1:"Here is a corner and the mirror line.",cap2:`The corner is ${n} squares from the mirror.`,cap3:`Its image is the same ${n} squares the other side.`,sp1:"A mirror flips a shape so that every corner ends up the same distance from the line, on the other side. So first we measure how far this corner is from the mirror line.",sp2:`Count the squares ${toLine()}: ${w$c(n)} squares.`,sp3:`A reflection keeps the same distance, so the image sits ${w$c(n)} squares from the line on the other side. The answer is ${w$c(n)} squares.`}}function apartText(n,t){return{title:"How far apart are a corner and its reflection?",cap1:"Here is the nearest corner and the mirror.",cap2:`This corner is ${n} squares from the mirror.`,cap3:`${n} squares each side makes ${2*n} squares apart.`,sp1:"We want the gap between a corner and its reflection. The mirror line sits exactly halfway between them, so we measure one side first.",sp2:`Counting ${toLine()}, the corner is ${w$c(n)} squares from the line.`,sp3:`The image is ${w$c(n)} squares the other side, so from corner to image is ${w$c(n)} and ${w$c(n)} more, which is ${w$c(2*n)} squares apart.`}}function coordText(n,t,e,s,o,r,a){const i=n==="vertical"?"x":"y",l=r?`The image is at (${a[0]}, ${a[1]}).`:`The image ${i}-coordinate is ${n==="vertical"?a[0]:a[1]}.`;return{title:"Where does the point land after reflecting?",cap1:`The point is at (${t}, ${e}).`,cap2:`It is ${o} squares from the line ${i} = ${s}.`,cap3:l,sp1:`The point is ${w$c(t)} across and ${w$c(e)} up. To reflect it, we first find how far it is from the mirror line.`,sp2:`The mirror is the line ${i} equals ${w$c(s)}, and the point is ${w$c(o)} squares from it. Along the line, the position does not change.`,sp3:n==="vertical"?`The image is the same ${w$c(o)} squares on the other side, so it lands ${w$c(a[0])} across, level with where it started.`:`The image is the same ${w$c(o)} squares on the other side, straight ${a[1]<e?"down":"up"} from where it started — the x stays at ${w$c(t)}, only the height changes.`}}function whichPointText(n,t){return{title:"Which point is the reflection of the corner?",cap1:"Corner A sits on one side of the mirror.",cap2:`A is ${n} squares from the mirror line.`,cap3:`The reflection is ${n} squares the other side.`,sp1:"A reflection puts each corner the same distance from the mirror, on the other side. So we start by measuring corner A.",sp2:`Measure from A ${toLine()}: ${w$c(n)} squares.`,sp3:`The reflection is the point exactly ${w$c(n)} squares on the other side, ${levelWith(t,"A")}. That is the matching point.`}}function isCorrectText(n,t){return{title:"Is this a correct reflection?",cap1:"Here is the shape and the mirror line.",cap2:`Measure a corner to the mirror: ${n}
71 squares.`,cap3:"Same distance both sides means it is correct.",sp1:"In a correct reflection, every corner is the same distance from the mirror as its match. So to check it, we test the corners against the mirror line.",sp2:`Take a corner and count the squares ${toLine()}: ${w$c(n)} squares.`,sp3:`Its matching corner on the reflection is the same ${w$c(n)} squares away on the other side. Every corner matches like this, so the reflection is correct.`}}function classifyMismatch(n,t,e){const s=n.mirror.axis,o=n.mirror.position,r=s==="vertical",a=n.reflected_points,i=d=>r?d[0]:d[1],l=n.shape_points.every(d=>i(d)<o)?"low":n.shape_points.every(d=>i(d)>o)?"high":"mixed",u=a.every(d=>i(d)<o)?"low":a.every(d=>i(d)>o)?"high":"mixed";return l!=="mixed"&&u===l?"same_side":t===e?"off_position":"distance"}function sq(n){return`${w$c(n)} square${n===1?"":"s"}`}function notCorrectText(n,t,e,s){const o=e==="same_side"?"Green never crossed the line — not a reflection.":e==="off_position"?"This corner isn't level with the original — not a correct reflection.":`Blue is ${n} squares, green is ${t} — they don't match.`,r=e==="same_side"?"A correct reflection always crosses to the OTHER side of the mirror line. This green shape stayed on the same side, so it cannot be a correct reflection.":e==="off_position"?`This corner is the right distance from the mirror line, but it is not ${levelWith(s,"the original corner")} — a correct reflection keeps every corner directly opposite its match. So it is not a correct reflection.`:`The blue corner is ${w$c(n)} squares from the line, but the matching green corner is ${sq(t)} from the line. The distances don't match, so it is not a correct reflection.`;return{title:"Is this a correct reflection?",cap1:"Here is the shape (blue) and the green shape to test.",cap2:`Measure blue: ${n} squares. Now measure the matching green corner.`,cap3:o,sp1:"In a correct reflection, every corner is the same distance from the mirror as its match. So we test a corner on the shape we know, then the matching corner on the green shape.",sp2:`Counting ${toLine()}, blue's corner is ${w$c(n)} squares from the line. Now we measure the matching corner on the green shape.`,sp3:r}}function whichSideText(n,t){return{title:"Which side does the reflection go?",cap1:"The shape sits on one side of the mirror.",cap2:"Measure each corner to the mirror line.",cap3:"The image lands the same distance, opposite side.",sp1:"The whole shape is on one side of the mirror line. A reflection flips it over the line, so we measure how far each corner is from the mirror.",sp2:`Measure each corner ${toLine()}. The farthest corner is ${w$c(n)} squares away.`,sp3:`Every corner lands the same distance away on the opposite side, so the farthest corner's image is ${w$c(n)} squares from the line on the other side. The reflection is on the other side of the line.`}}
71const GEO_P6_M01_REFLECT={id:`${MICRO$9}.reflect.v1`,microSkillId:MICRO$9,forms:FORMS$7,generate(n){const t=normaliseStem(n.stem),e=(n.correctAnswer??"").trim(),s=n.mathematicalForm??"",o=t.match(/\((\d+),\s*(\d+)\)/),r=t.match(/x\s*=\s*(\d+)/),a=t.match(/y\s*=\s*(\d+)/);if(o&&(r||a)){const i=Number(o[1]),l=Number(o[2]),u=r?"vertical":"horizontal",d=Number((r??a)[1]),m=Math.abs(u==="vertical"?i-d:l-d);if(!inDomain(m))return null;const h=reflectPoint([i,l],u,d),f=reflGridVisual(n),g=f?{...f,measure_point:[i,l]}:buildReflGrid(u,m,sideOf(u,i,l,d)),b=e.match(/\((\d+),\s*(\d+)\)/);if(b)return Number(b[1])!==h[0]||Number(b[2])!==h[1]?null:buildReflectionExample(n,u,m,coordText(u,i,l,d,m,!0,h),{px:i,py:l,m:d,d:m,ix:h[0],iy:h[1],answer:u==="vertical"?h[0]:h[1]},g);const x=Number(e);if(!Number.isInteger(x))return null;const E=(/x-coordinate/i.test(t)?"x":/y-coordinate/i.test(t)?"y":u==="vertical"?"x":"y")==="x"?h[0]:h[1];return E!==x?null:buildReflectionExample(n,u,m,coordText(u,i,l,d,m,!1,h),{px:i,py:l,m:d,d:m,ix:h[0],iy:h[1],answer:E},g)}if(s==="symbolic_numeric_entry"){const i=reflGridVisual(n),l=i?i.mirror.axis:axisFromStem(t),u=firstInt(t);if(u===null||!inDomain(u))return null;const d=Number(e);if(!Number.isInteger(d))return null;const m=i??void 0;return/apart/i.test(t)||/between the corner and its image/i.test(t)?2*u!==d?null:buildReflectionExample(n,l,u,apartText(u),{d:u,answer:2*u},m):u!==d?null:buildReflectionExample(n,l,u,sameDistanceText(u),{d:u,answer:u},m)}if(s==="visual_quantity_to_symbol"){const i=reflGridVisual(n);if(!i||!i.labelled_points)return null;const l=i.labelled_points.find(g=>g.label.toUpperCase()==="A");if(!l)return null;const u=i.mirror.axis,d=reflectPoint([l.x,l.y],u,i.mirror.position),m=i.labelled_points.find(g=>g.label.toUpperCase()!=="A"&&g.x===d[0]&&g.y===d[1]);if(!m||e.replace(/\s+/g," ").trim()!==`Point ${m.label.toUpperCase()}`)return null;const f=Math.abs(u==="vertical"?l.x-i.mirror.position:l.y-i.mirror.position);return inDomain(f)?buildReflectionExample(n,u,f,whichPointText(f,u),{d:f,answer:f}):null}if(s==="misconception_contrast_probe"){const i=reflGridVisual(n);if(!i||!i.reflected_points)return null;const l=i.mirror.axis,u=i.shape_points.map(S=>reflectPoint(S,l,i.mirror.position)),d=S=>S.map(M=>`${M[0]},${M[1]}`).sort().join("|"),m=d(u)===d(i.reflected_points);if(m?!isAffirmative(e):!isNegative(e))return null;const h={...i,given_points:i.reflected_points};if(m){const S=farthestDistance(i);return inDomain(S)?buildReflectionExample(n,l,S,isCorrectText(S),{d:S,answer:S},h):null}const f=l==="vertical",g=mismatchIndex(i),b=g!==-1?g:farthestIndex(i),[x,y]=i.shape_points[b],E=Math.abs(f?x-i.mirror.position:y-i.mirror.position);if(!inDomain(E))return null;const[k,T]=i.reflected_points[b],A=Math.abs(f?k-i.mirror.position:T-i.mirror.position),N=classifyMismatch(i,E,A);return buildReflectionExample(n,l,E,notCorrectText(E,A,N,l),{d:E,candidateD:A,answer:E},h)}if(s==="comparison_statement"){const i=reflGridVisual(n),l=e.toLowerCase();if(i){const h=i.mirror.axis,f=i.mirror.position,g=h==="vertical",b=i.shape_points.map(([T,A])=>g?T:A),x=b.every(T=>T<f),y=b.every(T=>T>f);if(!x&&!y)return null;const E=g?x?"right":"left":x?"above":"below";if(!l.includes(E))return null;const k=farthestDistance(i);return inDomain(k)?buildReflectionExample(n,h,k,whichSideText(k),{d:k,answer:k}):null}const u=firstInt(t);if(u===null||!inDomain(u))return null;const d=/\bleft\b/.test(t)?"right":/\bright\b/.test(t)?"left":/\babove\b/.test(t)?"below":/\bbelow\b/.test(t)?"above":"opposite";if(!l.includes(d)||!l.includes(String(u)))return null;const m=axisFromStem(t);return buildReflectionExample(n,m,u,whichSideText(u),{d:u,answer:u})}return null}},GEO_P6_M01_GENERATORS=[GEO_P6_M01_REFLECT],MICRO$8="GEO.P6.M02",FORMS$6=["shape_property","misconception_contrast_probe"];function sidesFromLengths(n){if(n.length!==3)return null;const t=new Set(n).size;return t===1?"equilateral":t===2?"isosceles":"scalene"}function sidesFromAngles(n){if(n.length!==3)return null;const t=new Set(n.map(e=>Math.round(e))).size;return t===1?"equilateral":t===2?"isosceles":"scalene"}function sidesFromMarks(n){if(!n||n.length===0)return null;const t=Math.max(...n.map(e=>e.length));return t>=3?"equilateral":t===2?"isosceles":null}function angleType(n){if(n.length!==3)return null;const t=Math.max(...n.map(e=>Math.round(e)));return Math.abs(t-90)<.5?"right":t>90?"obtuse":"acute"}function measurements(n){return[...n.matchAll(/(\d+)\s*(?:cm|mm|m)\b/gi)].map(t=>Number(t[1]))}function sideLengthTriple(n){const t=measurements(n);return t.length===3?t:t.length===1&&/\beach\b|three sides/i.test(n)?[t[0],t[0],t[0]]:null}function angleTripleFromStem(n){const t=n.match(/(\d+)\s*°[^0-9]+?(\d+)\s*°[^0-9]+?(\d+)\s*°/);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function twoEqualAngles(n){const t=n.match(/two of a triangle's angles (?:measure|are) (\d+)\s*°?/i);if(!t)return null;const e=Number(t[1]),s=n.match(/(?:and|,)\s*(\d+)\s*°/i);return s&&Number(s[1])!==e?null:e}function classifySidesFromStem(n){const t=sideLengthTriple(n);if(t)return sidesFromLengths(t);const e=angleTripleFromStem(n);if(e)return sidesFromAngles(e);const s=twoEqualAngles(n);return s!==null?sidesFromAngles([s,s,180-2*s]):/no equal sides|all (?:three )?sides (?:are )?different|all sides different/i.test(n)?"scalene":/three equal sides|all (?:three )?sides (?:are )?equal/i.test(n)?"equilateral":/exactly two equal sides|two equal sides/i.test(n)?"isosceles":null}function groupNameToClass(n){return/no equal sides|all sides different/i.test(n)?"scalene":/two equal sides/i.test(n)?"isosceles":/three equal sides|all sides equal/i.test(n)?"equilateral":null}function demoVisual(n){switch(n){case"equilateral":return{type:"triangle_figure",angles:[60,60,60],labels:[null,null,null],equal_side_marks:[[0,1,2]],rotation:0};case"isosceles":return{type:"triangle_figure",angles:[64,64,52],labels:[null,null,null],equal_side_marks:[[1,2]],rotation:0};case"scalene":return{type:"triangle_figure",angles:[40,60,80],labels:[null,null,null],equal_side_marks:[[0],[1],[2]],rotation:0};case"right":return{type:"triangle_figure",angles:[90,55,35],labels:[null,null,null],mark_right_angle:!0,rotation:0}}}function demoText(n){return{isosceles:{title:"What is the best name for this triangle?",cap1:"Name it by its sides, not by its slant.",cap2:"Two sides carry matching tick marks.",cap3:"Two equal sides — so it is isosceles.",sp1:"Here is a triangle. Its slant can fool the eye, so we name it by how many of its sides are equal.",sp2:"Matching tick marks show equal sides. Two sides carry the same tick, so those two sides are equal.",sp3:"Two sides are equal, so this triangle is isosceles, whatever it looks like."},equilateral:{title:"What is the best name for this triangle?",cap1:"Name it by its sides, not by its slant.",cap2:"All three sides carry matching ticks.",cap3:"Three equal sides — so it is equilateral.",sp1:"Here is a triangle. We name it by how many of its sides are equal, so we compare its sides.",sp2:"Matching tick marks show equal sides. Every side carries the same tick, so all three sides are the same length.",sp3:"All three sides are equal, so this triangle is equilateral."},scalene:{title:"What is the best name for this triangle?",cap1:"Name it by its sides, not by its slant.",cap2:"Each side has a different number of ticks.",cap3:"All sides different — so it is scalene.",sp1:"Here is a triangle. We name it by how many of its sides are equal, so we compare its sides.",sp2:"Matching tick marks show equal sides. Here each side has a different number of ticks, so no two sides are the same length.",sp3:"No two sides are equal, so this triangle is scalene."},right:{title:"What type of triangle is this, by its angles?",cap1:"Name it by its biggest angle.",cap2:"A small square marks a square corner.",cap3:"It has a square corner — so it is right-angled.",sp1:"Here is a triangle. To name it by its angles, we find its biggest angle.",sp2:"One corner has a small square. That square marks a corner of exactly ninety degrees.",sp3:"The biggest angle is a square corner, so this is a right-angled triangle."}}[n]}function computedFor$1(n){switch(n){case"equilateral":return{equal_sides:3};case"isosceles":return{equal_sides:2};case"scalene":return{equal_sides:0};case"right":return{right_angle:90}}}function buildTriExample(n,t){const e=demoVisual(t),s=demoText(t);return buildExample({generatorId:`${MICRO$8}.classify.v1`,question:n,title:s.title,steps:[{visual_data:e,animation:"classify_draw",caption:s.cap1,spoken:s.sp1},{visual_data:e,animation:"classify_marks",caption:s.cap2,spoken:s.sp2},{visual_data:e,animation:"classify_conclude",caption:s.cap3,spoken:s.sp3}],computed:computedFor$1(t)})}function buildTriExampleFromAngles(n,t,e,s){const o=t.map(m=>Math.round(m)),r=o.map(m=>`${m}°`),a={type:"triangle_figure",angles:t,labels:r,rotation:0},i=s?`Two angles are ${o[0]}°. The third is 180° − ${o[0]}° − ${o[1]}° = ${o[2]}°.`:`The three angles are ${o[0]}°, ${o[1]}° and ${o[2]}°.`,l=digitsToWords(s?`Two of the angles are ${o[0]} degrees each. Angles in a triangle add to 180, so the third is 180 minus ${o[0]} minus ${o[1]}, which is ${o[2]} degrees.`:`The three angles are ${o[0]}, ${o[1]} and ${o[2]}
71 degrees.`)+(s?"":e==="equilateral"?" All three are the same.":e==="isosceles"?" Two of them are the same.":" No two of them are the same."),u=e==="equilateral"?"All angles equal → all sides equal, so equilateral.":e==="isosceles"?"Two angles equal → two sides equal, so isosceles.":"No angles equal → no sides equal, so scalene.",d=e==="equilateral"?"All three angles are equal, so all three sides are equal too. This triangle is equilateral.":e==="isosceles"?"Two angles are equal, so the two sides opposite them are equal too. This triangle is isosceles.":"All three angles are different, so no two sides are equal. This triangle is scalene.";return buildExample({generatorId:`${MICRO$8}.classify.v1`,question:n,title:"What is the best name for this triangle?",steps:[{visual_data:a,animation:"classify_sides_draw",caption:"Name it from its angles, not its slant.",spoken:"We name this triangle by its equal sides, and the angles tell us about the sides: equal angles always sit opposite equal sides."},{visual_data:a,animation:"classify_sides_marks",caption:i,spoken:l},{visual_data:a,animation:"classify_sides_conclude",caption:u,spoken:d}],computed:{...computedFor$1(e),a0:o[0],a1:o[1],a2:o[2],...s?{angle_sum:180}:{}}})}function lengthVisual(n){const[t,e,s]=n,o=(l,u,d)=>Math.acos(Math.max(-1,Math.min(1,(l*l+u*u-d*d)/(2*l*u))))*180/Math.PI,r=o(t,s,e),a=o(t,e,s),i=[...new Set(n)].map(l=>n.flatMap((u,d)=>u===l?[d]:[]));return{type:"triangle_figure",angles:[r,a,180-r-a],labels:[null,null,null],side_labels:n.map(l=>`${l} cm`),equal_side_marks:i}}function buildTriExampleFromLengths(n,t,e){const s=lengthVisual(t),o=`${t[0]} cm, ${t[1]} cm and ${t[2]} cm`,r=e==="equilateral"?`${o} — all three match.`:e==="isosceles"?`${o} — two of them match.`:`${o} — no two match.`,a=`${t[0]}, ${t[1]} and ${t[2]} centimetres`,i=digitsToWords(e==="equilateral"?`The sides measure ${a}. All three numbers are the same.`:e==="isosceles"?`The sides measure ${a}. Two of those numbers are the same.`:`The sides measure ${a}. No two of those numbers are the same.`),l=e==="equilateral"?"Three equal sides — so it is equilateral.":e==="isosceles"?"Two equal sides — so it is isosceles.":"All sides different — so it is scalene.",u=e==="equilateral"?"All three sides are the same length, so this triangle is equilateral.":e==="isosceles"?"Two sides are the same length, so this triangle is isosceles.":"No two sides are the same length, so this triangle is scalene.";return buildExample({generatorId:`${MICRO$8}.classify.v1`,question:n,title:"What is the best name for this triangle?",steps:[{visual_data:s,animation:"classify_sides_draw",caption:"Compare the side lengths, not the slant.",spoken:"We name a triangle by how many of its sides are equal, so we compare its actual side lengths."},{visual_data:s,animation:"classify_sides_marks",caption:r,spoken:i},{visual_data:s,animation:"classify_sides_conclude",caption:l,spoken:u}],computed:{...computedFor$1(e),len0:t[0],len1:t[1],len2:t[2]}})}function buildTriExampleFromRealVisual(n,t,e){const s={...t,labels:[null,null,null]},o=demoText(e);return buildExample({generatorId:`${MICRO$8}.classify.v1`,question:n,title:o.title,steps:[{visual_data:s,animation:"classify_sides_draw",caption:t.mark_right_angle?"A right angle, but we are naming it by its sides.":"Its shape can fool the eye — name it by its sides.",spoken:t.mark_right_angle?"The square marks a right angle. That names an angle, but this question asks about the sides, so we compare the side marks.":"Here is the triangle. It may look stretched or tipped, but that never decides its name — we name it by its sides."},{visual_data:s,animation:"classify_sides_marks",caption:o.cap2,spoken:o.sp2},{visual_data:s,animation:"classify_sides_conclude",caption:o.cap3,spoken:o.sp3}],computed:computedFor$1(e)})}function buildAngleClassifyExample(n,t,e){const s=t.map(d=>Math.round(d)),o=Math.max(...s),r=s.map(d=>`${d}°`),a={type:"triangle_figure",angles:t,labels:r,rotation:0},i={type:"angle_diagram",angles:[{degrees:o}]},l=e==="right"?"It matches a square corner":e==="obtuse"?"It is wider than a square corner":"It is narrower than a square corner",u=e==="right"?"It matches a square corner exactly":e==="obtuse"?"It opens wider than a square corner":"It opens narrower than a square corner";return buildExample({generatorId:`${MICRO$8}.classify.v1`,question:n,title:"What type of triangle is this, by its angles?",steps:[{visual_data:a,animation:"classify_draw",caption:`The angles are ${s[0]}°, ${s[1]}° and ${s[2]}°. Find the biggest.`,spoken:digitsToWords(`To name a triangle by its angles, we find its biggest angle. Here the angles are ${s[0]}, ${s[1]} and ${s[2]} degrees.`)},{visual_data:i,animation:"classify_marks",caption:`Compare the biggest angle, ${o}°, to a square corner.`,spoken:digitsToWords(`The biggest angle is ${o} degrees. We compare it with a square corner, which is ninety degrees.`)},{visual_data:i,animation:"classify_conclude",caption:`${l} — ${e}-angled.`,spoken:`${u}, so this triangle is ${e}-angled.`}],computed:{biggest_angle:o,a0:s[0],a1:s[1],a2:s[2]}})}const GEO_P6_M02_CLASSIFY={id:`${MICRO$8}.classify.v1`,microSkillId:MICRO$8,forms:FORMS$6,generate(n){const t=normaliseStem(n.stem),e=(n.correctAnswer??"").trim().toLowerCase(),s=(n.options??[]).map(l
71=>l.trim()),o=n.visualData&&n.visualData.type==="triangle_figure"?n.visualData:null,r=s.length===3&&s.every(l=>/^(equilateral|isosceles|scalene)$/i.test(l)),a=s.length===3&&s.every(l=>/^(acute|right|obtuse)-angled$/i.test(l));let i=null;if(a){let l=null,u=null;if(o)u=o.angles,l=o.mark_right_angle||o.angles.some(m=>Math.abs(m-90)<.5)?"right":angleType(o.angles);else{const m=angleTripleFromStem(t);m&&m.length===3?(u=m,l=angleType(m)):/greater than 90|more than 90|over 90|obtuse/i.test(t)?l="obtuse":/exactly 90|equal to 90|right angle/i.test(t)?l="right":/less than 90|acute/i.test(t)&&(l="acute")}if(l===null)return null;const d=`${l}-angled`;return e.includes(d)?l==="right"?buildTriExample(n,"right"):(u||(u=l==="obtuse"?[30,40,110]:[55,60,65]),buildAngleClassifyExample(n,u,l)):null}else if(r){const l=sideLengthTriple(t);if(l){const d=sidesFromLengths(l);return!d||!e.includes(d)?null:buildTriExampleFromLengths(n,l,d)}if(!o&&/right angle/i.test(t)&&/two equal sides/i.test(t))return e.includes("isosceles")?buildTriExampleFromRealVisual(n,{type:"triangle_figure",angles:[90,45,45],labels:[null,null,null],mark_right_angle:!0,equal_side_marks:[[0,2]]},"isosceles"):null;const u=o?sidesFromMarks(o.equal_side_marks):null;if(u){if(o)return e.includes(u)?buildTriExampleFromRealVisual(n,o,u):null;i=u}else{const d=o?o.angles:angleTripleFromStem(t);if(d&&sidesFromAngles(d)){const f=sidesFromAngles(d);return e.includes(f)?buildTriExampleFromAngles(n,d,f,!1):null}const m=o?null:twoEqualAngles(t);if(m!==null){const f=[m,m,180-2*m],g=sidesFromAngles(f);return!g||!e.includes(g)?null:buildTriExampleFromAngles(n,f,g,!0)}const h=o?null:sideLengthTriple(t);if(h&&h.length===3){const f=sidesFromLengths(h);return!f||!e.includes(f)?null:buildTriExampleFromLengths(n,h,f)}i=classifySidesFromStem(t)}}else if(/\bsays\b/i.test(t)&&/\bright\b\??\s*$/i.test(t)&&sideLengthTriple(t)){const l=sideLengthTriple(t);if(!l||l.length!==3)return null;const u=sidesFromLengths(l);return!u||!e.includes(u)?null:buildTriExampleFromLengths(n,l,u)}else if(/sides?\s+\d+\s*(?:cm|mm|m)\b/i.test(n.correctAnswer??"")){const l=measurements(n.correctAnswer??""),u=sidesFromLengths(l),d=groupNameToClass(t);if(!u||d&&d!==u)return null;const m=s.map(b=>({label:b,lens:measurements(b)}));if(m.length!==3||m.some(b=>b.lens.length!==3))return null;const h=m.filter(b=>b.label!==n.correctAnswer.trim()),f=m.find(b=>b.label===n.correctAnswer.trim());if(!f||h.length!==2||h.some(b=>sidesFromLengths(b.lens)===u))return null;const g=[h[0],h[1],f];return buildExample({generatorId:this.id,question:n,title:"Check each triangle's side lengths",computed:Object.fromEntries(g.flatMap((b,x)=>b.lens.map((y,E)=>[`side_${x}_${E}`,y]))),steps:g.map((b,x)=>({visual_data:lengthVisual(b.lens),animation:`classify_sides_${x===2?"conclude":"compare"}`,caption:x===2?`${b.lens.join(", ")} cm: all different — choose the scalene triangle.`:`${b.lens.join(", ")} cm: matching lengths — rule it out.`,spoken:digitsToWords(x===2?`The lengths ${b.lens.join(", ")} centimetres are all different. No lengths match, so choose this scalene triangle.`:x===0?`Compare ${b.lens.join(", ")} centimetres. A length repeats, so this triangle has equal sides. Rule it out of the no equal sides group.`:`For the second option, compare ${b.lens.join(", ")} centimetres. A repeated length again shows equal sides. This option cannot belong in the no equal sides group either.`)}))})}else return null;return i===null||!e.includes(i)?null:buildTriExample(n,i)}};function equalAnglePair(n){for(let t=0;t<3;t++)for(let e=t+1;e<3;e++)if(n[t]===n[e])return[t,e];return null}const allDifferentCannotBeEquilateral={id:`${MICRO$8}.all-different-not-equilateral.v1`,microSkillId:MICRO$8,forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem);if(!/^A triangle has three angles that are all different sizes\. Which of these could not be true\?$/i.test(t)||!/equilateral/i.test(n.correctAnswer??""))return null;const s={type:"triangle_figure",angles:[40,60,80],labels:["40°","60°","80°"],rotation:0};return buildExample({generatorId:`${MICRO$8}.all-different-not-equilateral.v1`,question:n,title:"Which of these could NOT be true?",steps:[{visual_data:s,animation:"classify_draw",caption:"All three angles are different sizes.",spoken:"Here is a triangle whose three angles are all different sizes, just like in the question."},{visual_data:s,animation:"classify_marks",caption:"Equilateral needs all three angles equal.",spoken:"Equal angles sit opposite equal sides. Equilateral means all three angles — and all three sides — are equal."},{visual_data:s,animation:"classify_conclude",caption:"All different, so it could NEVER be equilateral.",spoken:"It could be scalene, or it could have a right angle — but with three different angles, it could never be equilateral."}
71],computed:{answer:0}})}},rightAngledAndIsoscelesClaim={id:`${MICRO$8}.right-and-isosceles-claim.v1`,microSkillId:MICRO$8,forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(/^A bracket.s angles are (\d+)°,\s*(\d+)°\s*and\s*(\d+)°\.\s*\w+ says a right-angled triangle can.t be isosceles\. Is (?:he|she|they) right\?$/i);if(!e)return null;const s=[Number(e[1]),Number(e[2]),Number(e[3])];if(angleType(s)!=="right")return null;const o=equalAnglePair(s);if(!o||sidesFromAngles(s)!=="isosceles")return null;const r=(n.correctAnswer??"").toLowerCase();if(!r.includes("right-angled")||!r.includes("isosceles"))return null;const a=s.map(u=>`${u}°`),i=[(o[0]+1)%3,(o[1]+1)%3],l={type:"triangle_figure",angles:s,labels:a,mark_right_angle:!0,equal_side_marks:[i],rotation:0};return buildExample({generatorId:`${MICRO$8}.right-and-isosceles-claim.v1`,question:n,title:"Can a triangle be right-angled AND isosceles?",steps:[{visual_data:l,animation:"classify_draw",caption:`The bracket's angles: ${a[0]}, ${a[1]}, ${a[2]}.`,spoken:digitsToWords(`Here is the bracket's triangle: ${s[0]}, ${s[1]} and ${s[2]} degrees.`)},{visual_data:l,animation:"classify_marks",caption:"90° = right-angled. Two matching angles = two equal sides = isosceles.",spoken:"The ninety degree angle makes it right-angled. The two matching angles sit opposite two equal sides, which makes it isosceles too."},{visual_data:l,animation:"classify_conclude",caption:"Both at once — right-angled AND isosceles.",spoken:"A triangle can be right-angled and isosceles at the same time, so the claim that it can't is wrong."}],computed:{answer:0,a0:s[0],a1:s[1],a2:s[2]}})}},triangleClaimTrueFalse={id:`${MICRO$8}.claim-true-false.v1`,microSkillId:MICRO$8,forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem);if(/right-angled and isosceles at the same time/i.test(t)){if(!/^true\b/i.test((n.correctAnswer??"").trim()))return null;const e=[90,45,45],s={type:"triangle_figure",angles:e,labels:["90°","45°","45°"],mark_right_angle:!0,equal_side_marks:[[0,2]],rotation:0};return buildExample({generatorId:`${MICRO$8}.claim-true-false.v1`,question:n,title:"Can a triangle be right-angled AND isosceles?",steps:[{visual_data:s,animation:"classify_draw",caption:"A 90°, 45°, 45° triangle.",spoken:"Here is a triangle with a right angle and two equal angles."},{visual_data:s,animation:"classify_marks",caption:"90° = right-angled. 45° = 45° = two equal sides = isosceles.",spoken:"The ninety degree angle makes it right-angled. The two matching forty-five degree angles mean two equal sides, which makes it isosceles too."},{visual_data:s,animation:"classify_conclude",caption:"Both at once — the statement is true.",spoken:"A triangle can be right-angled and isosceles at the same time, so the statement is true."}],computed:{answer:1,a0:e[0],a1:e[1],a2:e[2]}})}if(/a scalene triangle has no equal sides/i.test(t)){if(!/^true\b/i.test((n.correctAnswer??"").trim()))return null;const s={type:"triangle_figure",angles:[40,60,80],labels:[null,null,null],equal_side_marks:[[0],[1],[2]],rotation:0};return buildExample({generatorId:`${MICRO$8}.claim-true-false.v1`,question:n,title:"Does a scalene triangle have equal sides?",steps:[{visual_data:s,animation:"classify_draw",caption:"A scalene triangle.",spoken:"Here is a scalene triangle."},{visual_data:s,animation:"classify_marks",caption:"Each side has a different number of ticks — no two sides match.",spoken:"Each side carries a different number of ticks, so no two sides are the same length."},{visual_data:s,animation:"classify_conclude",caption:"No equal sides — the statement is true.",spoken:"A scalene triangle never has equal sides, so the statement is true."}],computed:{answer:1,equal_sides:0}})}if(/every equilateral triangle is also right-angled/i.test(t)){if(!/^false\b/i.test((n.correctAnswer??"").trim()))return null;const e=[60,60,60],s={type:"triangle_figure",angles:e,labels:["60°","60°","60°"],equal_side_marks:[[0,1,2]],rotation:0};return buildExample({generatorId:`${MICRO$8}.claim-true-false.v1`,question:n,title:"Is every equilateral triangle right-angled?",steps:[{visual_data:s,animation:"classify_draw",caption:"Three equal angles: 180° ÷ 3 = 60° each.",spoken:"An equilateral triangle has three equal angles. Angles in a triangle total one hundred and eighty degrees. Divide by three: each angle is sixty degrees."},{visual_data:s,animation:"classify_marks",caption:"No angle is 90° — there is no right angle.",spoken:"None of the angles is ninety degrees, so there is no right angle here."},{visual_data:s,animation:"classify_conclude",caption:"No right angle — the statement is false.",spoken:"An equilateral triangle never has a right angle, so the statement is false."}],computed:{answer:0,a0:e[0],a1:e[1],a2:e[2],right_angle_reference:90,angle_sum:180,divisor:3}})}return null}},GEO_P6_M02_GENERATORS=[GEO_P6_M02_CLASSIFY,allDifferentCannotBeEquilateral,rightAngledAndIsoscelesClaim,triangleClaimTrueFalse],MICRO$7="GEO.P6.M03",w$b=numberToWords,capFirst=n=>n.charAt(0).toUpperCase()+n.slice(1),oneEighty="one hundred and eighty";function degValues(n){const t=[],e=/(\d+)\s*°/g;let s;for(;(s=e.exec(n))!==null;)t.push(Number(s[1]));return t}function leadingInt$1(n){const t=n.match(/-?\d+/);return t?Number(t[0]):null}const norm=n=>n.replace(/\s+/g," ").trim().toLowerCase();function readTriangle(n){if(!n||n.type!=="triangle_figure")return null;const t=n;if(!Array.isArray(t.angles)||t.angles.length!==3||!Array.isArray(t.labels)||t.labels.length!==3)return null;const e=t.labels.every(a=>a==="?"),s=t.labels.findIndex(a=>a==="?"),o=[],r=[];
71return t.labels.forEach((a,i)=>{i!==s&&(typeof a=="string"&&/\d/.test(a)?o.push(t.angles[i]):a===null&&(Math.round(t.angles[i])===90?o.push(90):r.push(t.angles[i])))}),{knowns:o,twins:r,unknownIdx:s,allUnknown:e}}function buildTwoGiven(n,t,e,s,o){const r=t+e,a=t===90||e===90,i={type:"triangle_figure",angles:[t,e,s],labels:[`${t}°`,`${e}°`,"?"],mark_right_angle:a},l=o?{visual_data:i,animation:"straight_line",caption:`${o} only subtracted one angle — add both ${t}° and ${e}° first.`,spoken:`${o} took just one angle away from ${oneEighty}, but there are two angles given here. Both ${w$b(t)} and ${w$b(e)} need to be added together before we subtract.`}:{visual_data:i,animation:"straight_line",caption:"The three angles always add up to 180°.",spoken:`In any triangle, the three angles always add up to ${oneEighty} degrees. Two of them are shown.`};return buildExample({generatorId:`${MICRO$7}.two_given.v1`,question:n,title:"How do we find the missing angle in this triangle?",steps:[l,{visual_data:i,animation:"add_known",caption:`Add the angles we know: ${t}° + ${e}° = ${r}°.`,spoken:`Add the two angles we can see: ${w$b(t)} and ${w$b(e)} makes ${w$b(r)}.`},{visual_data:i,animation:"subtract_reveal",caption:`180° − ${r}° = ${s}°.`,spoken:`Take that total away from ${oneEighty}. The missing angle is ${w$b(s)} degrees.`}],computed:{a:t,b:e,sum:r,total:180,answer:s}})}function buildIsosceles(n,t,e){const s=180-t;return buildExample({generatorId:`${MICRO$7}.isosceles.v1`,question:n,title:"How do we find the base angles of an isosceles triangle?",steps:[{visual_data:{type:"equation_display",terms:[t,"+","__","+","__","=",180]},animation:"iso_setup",caption:`${t}° sits between the equal sides — the base angles are equal.`,spoken:`The angles add up to ${oneEighty}. The ${w$b(t)} degree angle sits between the two equal sides, so the other two angles — the base angles — are equal to each other.`},{visual_data:{type:"equation_display",terms:[180,"−",t,"=",s]},animation:"iso_share",caption:`180° − ${t}° = ${s}° for the two equal angles.`,spoken:`Take the ${w$b(t)} degree angle away from ${oneEighty}. That leaves ${w$b(s)} to share between the two equal base angles.`},{visual_data:{type:"equation_display",terms:[t,"+",e,"+",e,"=",180]},animation:"iso_reveal",caption:`Share it equally: each base angle is ${e}°.`,spoken:`Split ${w$b(s)} equally into two. Each base angle is ${w$b(e)} degrees.`}],computed:{top:t,share:s,base:e,total:180,answer:e}})}function buildRatioSplit(n,t,e,s,o){const r=180-t,a=o?s:e;return buildExample({generatorId:`${MICRO$7}.ratio_split.v1`,question:n,title:"How do we split the remaining angle in this ratio?",steps:[{visual_data:{type:"equation_display",terms:[t,"+","__","=",180]},animation:"ratio_setup",caption:`The right angle uses ${t}° of the 180°.`,spoken:`One angle is a right angle, ${w$b(t)} degrees. The other two angles share what is left of ${oneEighty}.`},{visual_data:{type:"equation_display",terms:[180,"−",t,"=",r]},animation:"ratio_share",caption:`180° − ${t}° = ${r}°, shared as one part and double.`,spoken:`Take ${w$b(t)} away from ${oneEighty}. That leaves ${w$b(r)} degrees to share between the two angles — one is twice the other, so think of it as three equal parts.`},{visual_data:{type:"equation_display",terms:[r,"÷",3,"=",e]},animation:"ratio_reveal",caption:`${r}° ÷ 3 = ${e}°, so the ${o?"larger":"smaller"} angle is ${a}°.`,spoken:`${capFirst(w$b(r))} divided by three is ${w$b(e)} degrees for one part. The smaller angle is ${w$b(e)} degrees${o?`, so the larger angle — double that — is ${w$b(s)} degrees`:""}.`}],computed:{rightDeg:t,remaining:r,smaller:e,larger:s,parts:3,total:180,answer:a}})}function buildEquilateral(n){return buildExample({generatorId:`${MICRO$7}.equilateral.v1`,question:n,title:"How big is each angle of an equilateral triangle?",steps:[{visual_data:{type:"equation_display",terms:["__","+","__","+","__","=",180]},animation:"equi_setup",caption:"All three angles are equal, and add to 180°.",spoken:`All three angles of this triangle are equal, and together they add up to ${oneEighty}.`},{visual_data:{type:"equation_display",terms:[180,"÷",3,"=",60]},animation:"equi_share",caption:"180° ÷ 3 = 60° each.",spoken:`Share ${oneEighty} equally between the three angles. Each one is sixty degrees.`},{visual_data:{type:"equation_display",terms:[60,"+",60,"+",60,"=",180]},animation:"equi_reveal",caption:"Each angle is 60°.",spoken:"Sixty and sixty and sixty makes one hundred and eighty. Every angle is sixty degrees."}],computed:{each:60,parts:3,total:180,answer:60}})}function buildImpossible(n,t,e){const s=e>180,o=s?">":"<",r=s?"more":"less",[a,i,l]=t;return buildExample({generatorId:`${MICRO$7}.impossible.v1`,question:n,title:"Could these three angles make a triangle?",steps:[{visual_data:{type:"equation_display",terms:[a,"+",i,"+",l,"=","__"]},animation:"add_all",caption:"Add all three angles first.",spoken:"To check if three angles can make a triangle, add them all together."},{visual_data:{type:"equation_display",terms:[a,"+",i,"+",l,"=",e]},animation:"show_total",caption:`They add up to ${e}°.`,spoken:`Add the three angles: ${w$b(a)} and ${w$b(i)} and ${w$b(l)} makes ${w$b(e)} degrees.`},{visual_data:{type:"equation_display",terms:[e,o,180]},animation:"compare_180",caption:`${e}° is ${r} than 180° — impossible.`,spoken:`The total is ${w$b(e)} degrees, which is ${r} than ${oneEighty}. A triangle's angles must add up to exactly ${oneEighty}, so these cannot make a triangle.`}],computed:{a,b:i,c:l,sum:e,total:180,answer:e}})}function buildWhichSet(n,t,e){const[s,o,r]=t,[a,i,l]=e,u=a+i+l;return buildExample({generatorId:`${MICRO$7}.which_set.v1`,question:n,title:"Which three angles could be a triangle?",steps:[{visual_data:{type:"equation_display",terms:[s,"+",o,"+",r,"=","__"]},animation:"sum_set",caption:"Add each set of three angles.",spoken:"A triangle's angles always add up to one hundred and eighty. Add each set to test it."},{visual_data:{type:"equation_display",terms:[s,"+",o,"+",r,"=",180]},animation:"set_ok",caption:`${s}° + ${o}° + ${r}° = 180°. This set works.`,spoken:`Adding this set, ${w$b(s)} and ${w$b(o)} and ${w$b(r)} makes exactly ${oneEighty}
71, so this set can be a triangle.`},{visual_data:{type:"equation_display",terms:[a,"+",i,"+",l,"=",u]},animation:"set_bad",caption:`${a}° + ${i}° + ${l}° = ${u}° — not 180°.`,spoken:`A set like ${w$b(a)}, ${w$b(i)} and ${w$b(l)} makes ${w$b(u)}, which is not ${oneEighty}, so it cannot be a triangle.`}],computed:{x:s,y:o,z:r,px:a,py:i,pz:l,wsum:u,total:180,answer:180}})}const GEO_P6_M03_MISSING_ANGLE={id:`${MICRO$7}.missing_angle.v1`,microSkillId:MICRO$7,forms:["symbolic_numeric_entry","context_calculation","symbolic_choice","visual_quantity_to_symbol","impossible_triangle_check","shape_property","misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=(n.correctAnswer??"").trim(),s=n.mathematicalForm??"",o=readTriangle(n.visualData);if(s==="impossible_triangle_check"){const l=degValues(t);if(l.length!==3)return null;const u=l[0]+l[1]+l[2];return u===180||!isNegative(e)?null:buildImpossible(n,l,u)}if(/could be the angles of a triangle|sets? of three angles/i.test(t)){const u=(n.options??[]).map(h=>({label:h,angs:degValues(h)})).filter(h=>h.angs.length===3),d=u.find(h=>h.angs[0]+h.angs[1]+h.angs[2]===180),m=u.find(h=>h.angs[0]+h.angs[1]+h.angs[2]!==180);return!d||!m||norm(e)!==norm(d.label)?null:buildWhichSet(n,d.angs,m.angs)}if(/right angle/i.test(t)&&/twice the size|twice as (?:big|large)|is double\b|double\s+the\s+other/i.test(t)){const l=t.match(/right angle(?:\s+of)?\s*(\d+)\s*°/i),u=l?Number(l[1]):90,d=180-u;if(d>0&&d%3===0){const m=d/3,h=m*2,f=/larger|bigger|greater/i.test(t),g=f?h:m,b=leadingInt$1(e);if(b!==null&&b===g)return buildRatioSplit(n,u,m,h,f)}return null}const r=/all three (sides|angles)[^.]*equal/i.test(t);if(o&&o.allUnknown||r)return leadingInt$1(e)!==60?null:buildEquilateral(n);let a=null;if(o&&o.knowns.length===1&&o.twins.length>=1)a=o.knowns[0];else if(!o){const l=degValues(t),u=/isosceles|two equal sides|equal base|base angles?|corners? (are|is) equal|equal/i.test(t);l.length===1&&u&&(a=l[0])}if(a!==null){if(a<=0||a>=180||(180-a)%2!==0)return null;const l=(180-a)/2,u=leadingInt$1(e);return u===null||u!==l?null:buildIsosceles(n,a,l)}let i=null;if(o&&o.knowns.length===2)i=o.knowns;else if(!o){const l=degValues(t);l.length===2&&(i=l)}if(i){const[l,u]=i,d=180-l-u,m=leadingInt$1(e);if(d<=0||m===null||m!==d)return null;let h;if(s==="misconception_contrast_probe"){const f=t.match(/^([A-Z][a-z]+)\b/);f&&(h=f[1])}return buildTwoGiven(n,l,u,d,h)}return null}},GEO_P6_M03_GENERATORS=[GEO_P6_M03_MISSING_ANGLE],w$a=n=>numberToWordsLong(n),W$3=n=>capitalised(numberToWordsLong(n)),ok$1=n=>Number.isInteger(n)&&n>=0&&n<=999999,twoDigit=n=>Number.isInteger(n)&&n>=10&&n<=99,eq$5=n=>({type:"equation_display",terms:n}),WORD_ONES={zero:0,one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12,thirteen:13,fourteen:14,fifteen:15,sixteen:16,seventeen:17,eighteen:18,nineteen:19},WORD_TENS={twenty:20,thirty:30,forty:40,fifty:50,sixty:60,seventy:70,eighty:80,ninety:90};function substituteNumberWords(n){return n.replace(/\b(twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety)(?:[- ](one|two|three|four|five|six|seven|eight|nine))?\b/gi,(t,e,s)=>String(WORD_TENS[e.toLowerCase()]+(s?WORD_ONES[s.toLowerCase()]:0))).replace(/\b(zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen)\b/gi,t=>String(WORD_ONES[t.toLowerCase()]))}function ints$2(n){const t=normaliseStem(n).match(/\d+/g);return t?t.map(Number):[]}function buildLongMultExample(n){const{q:t,top:e,bottom:s}=n;if(!twoDigit(e)||!twoDigit(s))return null;const o=s%10,r=Math.floor(s/10);if(o===0||r===0)return null;const a=e*o,i=e*r,l=i*10,u=e*s;if(![a,i,l,u].every(ok$1)||u!==Number(t.correctAnswer))return null;const d={type:"long_multiplication",top:e,bottom:s};return buildExample({generatorId:n.generatorId,question:t,title:n.title,computed:{top:e,bottom:s,ones:o,tens:r,p1:a,p2:i,p2Shifted:l,total:u,answer:u},steps:[{visual_data:d,animation:"mult_ones",caption:`Multiply by the ones: ${e} × ${o} = ${a}.`,spoken:`${W$3(e)} times ${w$a(s)}. First we multiply by the ones digit. ${W$3(e)} times ${w$a(o)} is ${w$a(a)} — that is the first row.`},{visual_data:d,animation:"mult_tens",caption:`Tens: ${e} × ${r} = ${i}, then × 10 → ${l}.`,spoken:`Now the tens digit. It stands for ${w$a(r)} tens, so we write a zero to hold the ones place, then ${w$a(e)} times ${w$a(r)} is ${w$a(i)}. The second row is ${w$a(l)}.`},{visual_data:d,animation:"add_rows",caption:`Add the rows: ${a} + ${l} = ${u}
71.`,spoken:`Last, we add the two rows together. ${W$3(a)} plus ${w$a(l)} is ${w$a(u)}. So ${w$a(e)} times ${w$a(s)} is ${w$a(u)}.${n.extraSpoken?` ${n.extraSpoken}`:""}`}]})}const straightProduct={id:"MD.P4.M04.straight.v1",microSkillId:"MD.P4.M04",forms:["symbolic_calculation"],generate(n){const t=normaliseStem(n.stem).match(/^(\d+) × (\d+) = __$/);return t?buildLongMultExample({q:n,generatorId:this.id,top:Number(t[1]),bottom:Number(t[2]),title:`How do we work out ${t[1]} × ${t[2]}?`}):null}},round10=n=>Math.round(n/10)*10,estimate={id:"MD.P4.M04.estimate.v1",microSkillId:"MD.P4.M04",forms:["symbolic_calculation"],generate(n){const t=normaliseStem(n.stem);if(!/estimat|round/i.test(t))return null;const e=t.match(/(\d+) × (\d+)/);if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(!twoDigit(s)||!twoDigit(o))return null;const r=round10(s),a=round10(o),i=r*a;if(![r,a,i].every(ok$1)||i!==Number(n.correctAnswer))return null;const l=r/10,u=a/10,d=l*u;return buildExample({generatorId:this.id,question:n,title:`Estimate ${s} × ${o}`,computed:{a:s,b:o,ra:r,rb:a,raTens:l,rbTens:u,core:d,est:i,answer:i},steps:[{visual_data:eq$5([`${s} × ${o}`,"≈","__"]),animation:"plan",caption:"Estimate: round each number first.",spoken:"To estimate we do not need the exact answer. We round each number to the nearest ten first."},{visual_data:eq$5([`${s} → ${r}`,",",`${o} → ${a}`]),animation:"round",caption:`Round to the nearest 10: ${s} → ${r}, ${o} → ${a}.`,spoken:`${W$3(s)} rounds to ${w$a(r)}. ${W$3(o)} rounds to ${w$a(a)}.`},{visual_data:eq$5([`${r} × ${a}`,"=",`${i}`]),animation:"multiply",caption:`${l} × ${u} = ${d}, then two zeros: ${r} × ${a} = ${i}.`,spoken:`${W$3(l)} times ${w$a(u)} is ${w$a(d)}, then we write two zeros. ${W$3(r)} times ${w$a(a)} is ${w$a(i)}, so the estimate is ${w$a(i)}.`}]})}},context$2={id:"MD.P4.M04.context.v1",microSkillId:"MD.P4.M04",forms:["context_calculation"],generate(n){const t=ints$2(substituteNumberWords(n.stem));return t.length!==2?null:buildLongMultExample({q:n,generatorId:this.id,top:t[0],bottom:t[1],title:`How do we work out ${t[0]} × ${t[1]}?`})}},GRID_PARTS=/^The grid method splits (\d+) × (\d+) into four parts worth (\d+), (\d+), (\d+) and (\d+)\. What is \d+ × \d+\?$/,gridParts={id:"MD.P4.M04.grid-parts.v1",microSkillId:"MD.P4.M04",forms:["varied_symbolic_calculation"],generate(n){const t=matchInts(n.stem,GRID_PARTS);if(!t)return null;const[e,s,o,r,a,i]=t;if(!twoDigit(e)||!twoDigit(s))return null;const l=o+r,u=a+i,d=l+u;return d!==e*s||d!==Number(n.correctAnswer)?null:buildExample({generatorId:this.id,question:n,title:`Add the grid parts for ${e} × ${s}`,computed:{top:e,bottom:s,w1:o,w2:r,w3:a,w4:i,topRow:l,botRow:u,total:d,answer:d},steps:[{visual_data:eq$5([`${o}`,"+",`${r}`,"+",`${a}`,"+",`${i}`,"=","__"]),animation:"show_parts",caption:`The grid gives four parts: ${o}, ${r}, ${a} and ${i}.`,spoken:"The grid method splits the multiplication into four parts. To find the total we add all four parts together."},{visual_data:eq$5([`${o}`,"+",`${r}`,"=",`${l}`,",",`${a}`,"+",`${i}`,"=",`${u}`]),animation:"add_rows",caption:`Add in rows: ${o} + ${r} = ${l}. ${a} + ${i} = ${u}.`,spoken:`We add in two rows. The first row gives ${w$a(l)}. The second row gives ${w$a(u)}.`},{visual_data:eq$5([`${l}`,"+",`${u}`,"=",`${d}`]),animation:"total",caption:`${l} + ${u} = ${d}, so ${e} × ${s} = ${d}.`,spoken:`${W$3(l)} plus ${w$a(u)} is ${w$a(d)}. So ${w$a(e)} times ${w$a(s)} is ${w$a(d)}.`}]})}},gridProduct={id:"MD.P4.M04.grid.v1",microSkillId:"MD.P4.M04",forms:["varied_symbolic_calculation"],generate(n){const t=normaliseStem(n.stem).match(/What is (\d+) × (\d+)\?$/);return t?buildLongMultExample({q:n,generatorId:this.id,top:Number(t[1]),bottom:Number(t[2]),title:`How do we work out ${t[1]} × ${t[2]}?`}):null}},CROSS_CLAIM=/^(\w+) says (\d+) × (\d+) = \d+ by working out \d+ × \d+ and \d+ × \d+ separately\. What is \2 × \3 really\?$/,SHIFT_CLAIM=/^(\w+) works out (\d+) × (\d+) as \2 × \d+ = \d+ plus \2 × \d+ = \d+, giving \d+\. What is \2 × \3 really\?$/,misconception={id:"MD.P4.M04.misconception.v1",microSkillId:"MD.P4.M04",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=t.match(CROSS_CLAIM),s=e?null:t.match(SHIFT_CLAIM),o=e??s;if(!o)return null;const r=o[1],a=e?`${r} missed the tens-times-ones parts. Every digit multiplies every digit — that is why we need two rows.`:`${r} multiplied by both digits, but never shifted the tens-digit row along — it was added in as if it were still a ones row.`;return buildLongMultExample({q:n,generatorId:this.id,top:Number(o[2]),bottom:Number(o[3]),title:`What is ${o[2]} × ${o[3]} really?`,extraSpoken:a})}},partialRow={id:"MD.P4.M04.partial-row.v1",microSkillId:"MD.P4.M04",forms:["equation_completion"],generate(n){const t=[...normaliseStem(n.stem).matchAll(/(\d+) × (\d+)/g)];if(t.length===0)return null;const e=t[t.length-1],s=Number(e[1]),o=Number(e[2]);
71if(!twoDigit(s))return null;const r=s*o;if(!ok$1(r)||r!==Number(n.correctAnswer))return null;if(o>=1&&o<=9){const a=Math.floor(s/10)*10,i=s%10,l=a*o,u=i*o;return[a,i,l,u].every(ok$1)?buildExample({generatorId:this.id,question:n,title:`Work out one row: ${s} × ${o}`,computed:{a:s,b:o,tensPart:a,onesPart:i,tp:l,op:u,product:r,answer:r},steps:[{visual_data:eq$5([`${s} × ${o}`,"=","__"]),animation:"plan",caption:`This is one row: ${s} × ${o}.`,spoken:`This is one row of the long multiplication. We work out ${w$a(s)} times ${w$a(o)}.`},{visual_data:eq$5([`${a} × ${o}`,"+",`${i} × ${o}`,"=","__"]),animation:"split",caption:`Split ${s} into ${a} and ${i}.`,spoken:`Split ${w$a(s)} into ${w$a(a)} and ${w$a(i)}, and multiply each part by ${w$a(o)}.`},{visual_data:eq$5([`${l}`,"+",`${u}`,"=",`${r}`]),animation:"add",caption:`Add: ${l} + ${u} = ${r}.`,spoken:`${W$3(l)} plus ${w$a(u)} is ${w$a(r)}.`}]}):null}if(o%10===0){const a=o/10,i=s*a;return!twoDigit(o)||!ok$1(i)||a<1?null:buildExample({generatorId:this.id,question:n,title:`Work out the tens row: ${s} × ${o}`,computed:{a:s,b:o,tensDigit:a,half:i,product:r,answer:r},steps:[{visual_data:eq$5([`${s} × ${o}`,"=","__"]),animation:"plan",caption:`This is the tens row: ${s} × ${o}.`,spoken:`This is the tens row. We work out ${w$a(s)} times ${w$a(o)}.`},{visual_data:eq$5([`${s} × ${a}`,"=",`${i}`]),animation:"half",caption:`First ${s} × ${a} = ${i}.`,spoken:`First we multiply by the ${w$a(a)}. ${W$3(s)} times ${w$a(a)} is ${w$a(i)}.`},{visual_data:eq$5([`${i}`,"× 10","=",`${r}`]),animation:"shift",caption:`Then × 10: write the zero → ${r}.`,spoken:`Then, because it really means ${w$a(o)}, we multiply by ten by writing a zero on the end. The row is ${w$a(r)}.`}]})}return null}},tensRowZero={id:"MD.P4.M04.tens-zero.v1",microSkillId:"MD.P4.M04",forms:["true_false_check"],generate(n){if(!/^true$/i.test(n.correctAnswer.trim()))return null;const t=normaliseStem(n.stem).match(/second row is (\d+) × (\d+)\b/);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(!twoDigit(e)||!twoDigit(s)||s%10!==0)return null;const o=s/10,r=e*o,a=e*s;return![r,a].every(ok$1)||a%10!==0?null:buildExample({generatorId:this.id,question:n,title:`Does the tens row ${e} × ${s} end in a zero?`,computed:{a:e,b:s,tensDigit:o,half:r,row:a,answer:a},steps:[{visual_data:eq$5([`${e} × ${s}`,"=","__"]),animation:"plan",caption:`The tens row: ${e} × ${s}.`,spoken:`In the tens row we work out ${w$a(e)} times ${w$a(s)}.`},{visual_data:eq$5([`${e} × ${o}`,"=",`${r}`]),animation:"half",caption:`First ${e} × ${o} = ${r}.`,spoken:`${W$3(s)} is ${w$a(o)} tens, so first we multiply by the tens digit: ${w$a(e)} times ${w$a(o)} is ${w$a(r)}.`},{visual_data:eq$5([`${r}`,"× 10","=",`${a}`]),animation:"shift",caption:"× 10 always puts a zero on the end.",spoken:`Then we multiply by ten: ${w$a(r)} times ten is ${w$a(a)}. Multiplying by ten always writes a zero on the end, so yes, the tens row ends in a zero.`}]})}},MD_P4_M04_GENERATORS=[context$2,misconception,gridParts,gridProduct,straightProduct,estimate,partialRow,tensRowZero];function divisionColumns(n,t){const e=String(n).split("").map(Number),s=[];let o=0;for(const r of e){const a=o*10+r,i=Math.floor(a/t),l=a-i*t;s.push({digit:r,carryIn:o,working:a,q:i,carryOut:l}),o=l}return s}function isLeadingZeroCol(n,t){return t<n.length-1&&n.slice(0,t+1).every(e=>e.q===0)}function columnsComputed(n,t){const e={};return n.forEach((s,o)=>{e[`col${o}Working`]=s.working,e[`col${o}CarryIn`]=s.carryIn,e[`col${o}Q`]=s.q,e[`col${o}Product`]=t*s.q}),e}function narrateColumns(n,t){const e=[],s=[],o=n.length-1;for(let r=0;r<n.length;r++){if(isLeadingZeroCol(n,r))continue;const a=n[r],i=r===o,l=a.carryIn>0?`${capitalised(numberToWordsLong(a.carryIn))} remainder makes ${numberToWordsLong(a.working)}. `:"";if(a.q>0){const u=t*a.q;i&&a.carryOut>0?(e.push(`${t}s in ${a.working}? ${a.q}, r${a.carryOut} (${a.working}−${u}=${a.carryOut}).`),s.push(`${l}${capitalised(numberToWords(t))} goes into ${numberToWordsLong(a.working)} ${numberToWords(a.q)} times. ${capitalised(numberToWordsLong(a.working))} minus ${numberToWordsLong(u)} leaves ${numberToWordsLong(a.carryOut)} — that is the remainder.`)):(e.push(`${t}s in ${a.working}? ${a.q}.`),s.push(`${l}${capitalised(numberToWords(t))} goes into ${numberToWordsLong(a.working)} ${numberToWords(a.q)} times.`))}else i?(e.push(`${t} won't go into ${a.working}: 0 r${a.carryOut}.`),s.push(`${l}${capitalised(numberToWords(t))} will not go into ${numberToWordsLong(a.working)}. Write zero; the ${numberToWordsLong(a.carryOut)} left over is the remainder.`)):(e.push(`${t} won't go into ${a.working}.`),s.push(`${l}${capitalised(numberToWords(t))} will not go into ${numberToWordsLong(a.working)}, so we carry it on.`))}return{caption:e.join(" "),spoken:s.join(" ")}}function busStop(n,t){if(n<100||n>9999||t<2||t>9)return null;const e=Math.floor(n/t),s=n%t,r=String(n).split("").map(Number)[0],a=divisionColumns(n,t),{caption:i,spoken:l}=narrateColumns(a,t),u={visual_data:{type:"short_division",dividend:n,divisor:t},animation:"set_up",caption:"Set up the bus stop.",spoken:"First we set up the bus stop. The number we are sharing goes inside, and the number we divide by sits just outside on the left."},d={visual_data:{type:"short_division",dividend:n,divisor:t,reveal_columns:a.length},animation:"divide_digits",caption:i,spoken:`We work from the left. ${l}`};return{quotient:e,remainder:s,leadDigit:r,step1:u,step2:d,computedCore:{dividend:n,divisor:t,quotient:e,remainder:s,leadDigit:r,...columnsComputed(a,t)}}}const vdReveal=(n,t)=>({type:"short_division",dividend:n,divisor:t}),DIV_EQ=/^(\d+) ÷ (\d+) = __$/,DIV_REM=/^Work out (\d+) ÷ (\d+)\. What is the remainder\?$/,symbolicDivision={id:"MD.P4.M05.busstop.v1",microSkillId:"MD.P4.M05",forms:["symbolic_calculation","symbolic_numeric_entry"],generate(n){const t=matchInts(n.stem,DIV_EQ),e=t?null:matchInts(n.stem,DIV_REM),s=t??e;if(!s)return null;const[o,r]=s,a=busStop(o,r);if(!a)return null;const i=e!==null;if(i){if(a.remainder!==answerInt(n))return null}else if(a.remainder!==0||a.quotient!==answerInt(n))return null;const l=i?{visual_data:vdReveal(o,r),animation:"reveal_quotient",caption:`${o} ÷ ${r} = ${a.quotient} remainder ${a.remainder}.`,spoken:`Reading above the bar, ${numberToWordsLong(o)}
71 divided by ${numberToWords(r)} is ${numberToWordsLong(a.quotient)}, with ${numberToWordsLong(a.remainder)} left over. The remainder is ${numberToWordsLong(a.remainder)}.`}:{visual_data:vdReveal(o,r),animation:"reveal_quotient",caption:`${o} ÷ ${r} = ${a.quotient}.`,spoken:`Reading above the bar, ${numberToWordsLong(o)} divided by ${numberToWords(r)} is ${numberToWordsLong(a.quotient)}.`};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${o} ÷ ${r}?`,computed:{...a.computedCore,answer:i?a.remainder:a.quotient},steps:[a.step1,a.step2,l]})}},DIV_CHOICE=/^Work out (\d+) ÷ (\d+)\. Choose the answer with its remainder\.?$/,symbolicChoice$1={id:"MD.P4.M05.busstop-choice.v1",microSkillId:"MD.P4.M05",forms:["symbolic_choice"],generate(n){const t=matchInts(n.stem,DIV_CHOICE);if(!t)return null;const[e,s]=t,o=busStop(e,s);if(!o)return null;const r=o.remainder>0?`${o.quotient} r ${o.remainder}`:`${o.quotient}`;if(n.correctAnswer.trim()!==r)return null;const a={visual_data:vdReveal(e,s),animation:"reveal_quotient",caption:`${e} ÷ ${s} = ${o.quotient} remainder ${o.remainder}.`,spoken:`Reading above the bar, ${numberToWordsLong(e)} divided by ${numberToWords(s)} is ${numberToWordsLong(o.quotient)}, remainder ${numberToWordsLong(o.remainder)}.`};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} ÷ ${s}?`,computed:{...o.computedCore,answer:o.quotient},steps:[o.step1,o.step2,a]})}},CTX_PACK_EACH=/packs (\d+) \w+ equally into (\d+) \w+\. How many \w+ go in each/,CTX_RIBBON=/A (\d+) \w+ ribbon is cut into (\d+) equal pieces/,CTX_MACHINE=/divides every number that enters it by (\d+)\. If (\d+) goes in/,CTX_LEFT_OVER=/(\d+) \w+ are shared equally among (\d+) \w+\. How many \w+ are left over/,contextCalculation={id:"MD.P4.M05.busstop-context.v1",microSkillId:"MD.P4.M05",forms:["context_calculation"],generate(n){const t=matchInts(n.stem,CTX_PACK_EACH),e=t?null:matchInts(n.stem,CTX_RIBBON),s=t||e?null:matchInts(n.stem,CTX_MACHINE),o=t??e??(s?[s[1],s[0]]:null);if(o){const[a,i]=o,l=busStop(a,i);if(!l||l.remainder!==0||l.quotient!==answerInt(n))return null;const u={visual_data:vdReveal(a,i),animation:"reveal_quotient",caption:`${a} ÷ ${i} = ${l.quotient}.`,spoken:`Reading above the bar, ${numberToWordsLong(a)} divided by ${numberToWords(i)} is ${numberToWordsLong(l.quotient)}.`};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${a} ÷ ${i}?`,computed:{...l.computedCore,answer:l.quotient},steps:[l.step1,l.step2,u]})}const r=matchInts(n.stem,CTX_LEFT_OVER);if(r){const[a,i]=r,l=busStop(a,i);if(!l||l.remainder!==answerInt(n))return null;const u={visual_data:vdReveal(a,i),animation:"reveal_quotient",caption:`${a} ÷ ${i} = ${l.quotient} remainder ${l.remainder}.`,spoken:`${capitalised(numberToWordsLong(a))} divided by ${numberToWords(i)} is ${numberToWordsLong(l.quotient)}, and ${numberToWordsLong(l.remainder)} cannot be shared. The leftover is ${numberToWordsLong(l.remainder)}.`};return buildExample({generatorId:this.id,question:n,title:`What is left over when ${a} ÷ ${i}?`,computed:{...l.computedCore,answer:l.remainder},steps:[l.step1,l.step2,u]})}return null}},LABEL_COUNT=/^(\d+) \w+$/,LABEL_EACH_LEFT=/^(\d+) \w+, (\d+) left over$/,LABEL_EACH_NONE=/^(\d+) \w+, none left over$/;function readDividendDivisor(n){const t=(normaliseStem(n).match(/\d+/g)??[]).map(Number),e=t.filter(o=>o>=100),s=t.filter(o=>o>=2&&o<=9);return e.length!==1||s.length!==1?null:{dividend:e[0],divisor:s[0]}}const contextReasoning$3={id:"MD.P4.M05.remainder-interpret.v1",microSkillId:"MD.P4.M05",forms:["context_reasoning"],generate(n){const t=readDividendDivisor(n.stem);if(!t)return null;const{dividend:e,divisor:s}=t,o=busStop(e,s);if(!o)return null;const{quotient:r,remainder:a}=o,i=n.correctAnswer.trim(),l=i.match(LABEL_EACH_LEFT),u=i.match(LABEL_EACH_NONE);if(l||u){const h=Number((l??u)[1]),f=l?Number(l[2]):0;if(h!==r||f!==a)return null;const g={visual_data:vdReveal(e,s),animation:"reveal_quotient",caption:`${e} ÷ ${s} = ${r} each, ${a} left over.`,spoken:`${capitalised(numberToWordsLong(e))} divided by ${numberToWords(s)} is ${numberToWordsLong(r)} each, with ${numberToWordsLong(a)} that cannot be shared.`};return buildExample({generatorId:this.id,question:n,title:`How do we share ${e} between ${s}?`,computed:{...o.computedCore,answer:r},steps:[o.step1,o.step2,g]})}const d=i.match(LABEL_COUNT);if(!d)return null;const m=Number(d[1]);if(m===r){const h={visual_data:vdReveal(e,s),animation:"reveal_quotient",caption:`${r} full — the ${a} left over don't fill another.`,spoken:`${capitalised(numberToWordsLong(e))} divided by ${numberToWords(s)} is ${numberToWordsLong(r)}, remainder ${numberToWordsLong(a)}. The ${numberToWordsLong(a)} left over cannot fill another, so the answer is ${numberToWordsLong(r)}.`};return buildExample({generatorId:this.id,question:n,title:`How many full groups in ${e} ÷ ${s}?`,computed:{...o.computedCore,answer:r},steps:[o.step1,o.step2,h]})}if(m===r+1&&a>0){const h=r+1,f={visual_data:{type:"equation_display",terms:[`${r}`,"+","1","=",`${h}`]},animation:"round_up",caption:`The ${a} left over need one more, so round up to ${h}.`,spoken:`Using the bus stop method we found ${numberToWordsLong(r)} remainder ${numberToWordsLong(a)}. The ${numberToWordsLong(a)} left over still need a place, so we cannot ignore the remainder. We round up: ${numberToWordsLong(r)} plus one more makes ${numberToWordsLong(h)}.`};return buildExample({generatorId:this.id,question:n,title:`How many groups for ${e}, ${s} in each?`,computed:{...o.computedCore,answer:h,rounded:h,one:1},steps:[o.step1,o.step2,f]})}return null}},TF_MISSED=/^(\w+) worked out (\d+) ÷ (\d+) and wrote the answer as (\d+)\. Has (?:he|she|they) answered it fully\?$/,trueFalseRemainder={id:"MD.P4.M05.answered-fully.v1",microSkillId:"MD.P4.M05",forms:["true_false_check"],generate(n){const t=normaliseStem(n.stem).match(TF_MISSED);if(!t)return null;const e=t[1],s=Number(t[2]),o=Number(t[3]),r=Number(t[4]),a=busStop(s,o);if(!a||r!==a.quotient)return null;const i=a.remainder===0,l=n.correctAnswer;if(i?!isAffirmative(l):!isNegative(l))return null;const u={visual_data:vdReveal(s,o),animation:"reveal_quotient",caption:i?`${s} ÷ ${o} = ${a.quotient} exactly — nothing missed.`:`${a.quotient} misses the remainder ${a.remainder}.`,spoken:i?`${capitalised(numberToWordsLong(s))}
71 divided by ${numberToWords(o)} is ${numberToWordsLong(a.quotient)} with nothing left over, so the answer is complete.`:`${capitalised(numberToWordsLong(s))} divided by ${numberToWords(o)} is ${numberToWordsLong(a.quotient)} remainder ${numberToWordsLong(a.remainder)}. ${e} stopped at ${numberToWordsLong(a.quotient)} and left out the remainder, so the answer is not complete.`};return buildExample({generatorId:this.id,question:n,title:`Did ${e} finish ${s} ÷ ${o}?`,computed:{...a.computedCore,written:r,answer:a.remainder},steps:[a.step1,a.step2,u]})}},COMPARE_LARGER=/^Which division gives the larger answer: (\d+) ÷ (\d+) or (\d+) ÷ (\d+)\?$/,compareLarger={id:"MD.P4.M05.compare-larger.v1",microSkillId:"MD.P4.M05",forms:["comparison_statement"],generate(n){const t=matchInts(n.stem,COMPARE_LARGER);if(!t)return null;const[e,s,o,r]=t;if(s<2||s>9||r<2||r>9||e%s!==0||o%r!==0)return null;const a=e/s,i=o/r;if(a>999||i>999)return null;const l=a===i?"They give the same answer":a>i?`${e} ÷ ${s}`:`${o} ÷ ${r}`;if(n.correctAnswer.trim()!==l)return null;const u=a===i?"=":a>i?">":"<",d=(h,f)=>h===f?"highlight_ones":Math.floor(h/100)%10!==Math.floor(f/100)%10?"highlight_hundreds":Math.floor(h/10)%10!==Math.floor(f/10)%10?"highlight_tens":"highlight_ones";if(e===o){const h={visual_data:{type:"equation_display",terms:[`${e} ÷ ${s}`,"?",`${e} ÷ ${r}`]},animation:"show_both",caption:`Both divide the same total: ${e}.`,spoken:`We need to compare two divisions. Both share out the same total, ${numberToWordsLong(e)}, so we only need to look at how many groups each one shares it between.`},f={visual_data:{type:"comparison",left:s,right:r,symbol:s===r?"=":s<r?"<":">"},animation:`show_divisors ${d(s,r)}`,caption:`Same total, ${Math.min(s,r)} groups vs ${Math.max(s,r)} groups.`,spoken:"The same total shared between fewer groups gives each group more — we can compare without dividing at all."},g={visual_data:{type:"comparison",left:a,right:i,symbol:u},animation:`write_answer ${d(a,i)}`,caption:a===i?`${e} ÷ ${s} and ${o} ÷ ${r} are equal.`:`${l} is larger — fewer groups, bigger share.`,spoken:a===i?`Both come to ${numberToWordsLong(a)}, so the two divisions are equal.`:`Dividing by the smaller number, ${numberToWords(Math.min(s,r))}, gives the larger answer: ${numberToWordsLong(Math.max(a,i))}.`};return buildExample({generatorId:this.id,question:n,title:`Which is larger: ${e} ÷ ${s} or ${o} ÷ ${r}?`,computed:{a:e,b:s,c:o,d:r,left:a,right:i,answer:Math.max(a,i)},steps:[h,f,g]})}const m={visual_data:{type:"comparison",left:a,right:i,symbol:u},animation:`write_answer ${d(a,i)}`,caption:a===i?`${e} ÷ ${s} and ${o} ÷ ${r} are equal.`:`${l} is larger (${Math.max(a,i)}).`,spoken:a===i?`Both come to ${numberToWordsLong(a)}, so the two divisions are equal.`:`${capitalised(numberToWordsLong(Math.max(a,i)))} is more than ${numberToWordsLong(Math.min(a,i))}, so the ${a>i?"first":"second"} division gives the larger answer.`};return buildExample({generatorId:this.id,question:n,title:`Which is larger: ${e} ÷ ${s} or ${o} ÷ ${r}?`,computed:{a:e,b:s,c:o,d:r,left:a,right:i,answer:Math.max(a,i)},steps:[{visual_data:{type:"equation_display",terms:[`${e} ÷ ${s}`,"=",`${a}`]},animation:"show_start",caption:`First: ${e} ÷ ${s} = ${a}.`,spoken:`${capitalised(numberToWordsLong(e))} divided by ${numberToWords(s)} is ${numberToWordsLong(a)}.`},{visual_data:{type:"equation_display",terms:[`${o} ÷ ${r}`,"=",`${i}`]},animation:"show_second",caption:`Second: ${o} ÷ ${r} = ${i}.`,spoken:`${capitalised(numberToWordsLong(o))} divided by ${numberToWords(r)} is ${numberToWordsLong(i)}.`},m]})}},MD_P4_M05_GENERATORS=[symbolicDivision,symbolicChoice$1,contextCalculation,contextReasoning$3,trueFalseRemainder,compareLarger],w$9=n=>numberToWordsLong(n),W$2=n=>capitalised(numberToWordsLong(n));function stemInts(n){const t=normaliseStem(n).match(/\d+/g);return t?t.map(Number):[]}function ok(n){return Number.isInteger(n)&&n>=0&&n<=999999}const eq$4=n=>({type:"equation_display",terms:n});function methodHint(n,t,e){return n==="multiply"?t>=10&&e>=10?" using column multiplication":"":t>144||e>12?" using the bus stop method":""}function scalarTwoStep(n){const{q:t,op:e,second:s,xVal:o,answer:r,xExpr:a}=n;return buildExample({generatorId:n.generatorId,question:t,title:n.title,computed:{...n.computed,xVal:o,answer:r},steps:[{visual_data:eq$4([a,e,`${s}`,"=","__"]),animation:"plan",caption:n.planCaption,spoken:n.planSpoken},{visual_data:eq$4([a,"=",`${o}`]),animation:"step_one",caption:n.findCaption,spoken:n.findSpoken},{visual_data:eq$4([`${o}`,e,`${s}`,"=",`${r}`]),animation:"step_two",caption:n.useCaption,spoken:n.useSpoken}]})}const mulThenAddSub={id:"MD.P5.M01.mul-addsub.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==3)return null;const[e,s,o]=t;if(![e,s,o].every(ok))return null;const r=e*s;if(!ok(r))return null;const a=Number(n.correctAnswer);let i;if(r+o===a)i="+";else if(r-o===a)i="−";else return null;if(!ok(a))return null;const l=i==="+"?"add on":"take away";return scalarTwoStep({q:n,generatorId:this.id,title:`First find ${e} × ${s}, then ${i==="+"?"add":"subtract"} ${o}`,xExpr:`${e} × ${s}`,xVal:r,op:i,second:o,answer:a,computed:{a:e,b:s,c:o,product:r},planCaption:`Plan: find ${e} × ${s}, then ${l} ${o}
71.`,planSpoken:`A two-step problem. First we multiply to find the total, then we ${l} the ${w$9(o)}.`,findCaption:`First find the total: ${e} × ${s} = ${r}.`,findSpoken:`Step one. ${W$2(e)} times ${w$9(s)} is ${w$9(r)}. That is our hidden value.`,useCaption:`Then ${i==="+"?"add":"take away"} ${o}: ${r} ${i} ${o} = ${a}.`,useSpoken:`Step two. We use that total: ${w$9(r)} ${i==="+"?"plus":"take away"} ${w$9(o)} is ${w$9(a)}.`})}},divThenAddSub={id:"MD.P5.M01.div-addsub.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==3)return null;const[e,s,o]=t;if(![e,s,o].every(ok))return null;const r=Math.max(e,s),a=Math.min(e,s);if(a<2||r%a!==0)return null;const i=r/a,l=Number(n.correctAnswer);let u;if(i+o===l)u="+";else if(i-o===l)u="−";else return null;if(!ok(l)||!ok(i))return null;const d=u==="+"?"add on":"take away",m=n.stem.match(/\b(bags|boxes|packs|groups|trays|crates|baskets)\s+of\s+\d+/i),h=m?m[1].toLowerCase():null;return scalarTwoStep({q:n,generatorId:this.id,title:`First find ${r} ÷ ${a}, then ${u==="+"?"add":"subtract"} ${o}`,xExpr:`${r} ÷ ${a}`,xVal:i,op:u,second:o,answer:l,computed:{dividend:r,divisor:a,c:o,share:i},planCaption:`Plan: find ${r} ÷ ${a}, then ${d} ${o}.`,planSpoken:`A two-step problem. First we divide to find ${h?`how many ${h}`:"one share"}, then we ${d} the ${w$9(o)}.`,findCaption:`First share out: ${r} ÷ ${a} = ${i}.`,findSpoken:`Step one. ${W$2(r)} divided by ${w$9(a)}${methodHint("divide",r,a)} is ${w$9(i)}. ${h?`That tells us how many ${h} there are.`:"That is our hidden value."}`,useCaption:`Then ${u==="+"?"add":"take away"} ${o}: ${i} ${u} ${o} = ${l}.`,useSpoken:`Step two. We use that share: ${w$9(i)} ${u==="+"?"plus":"take away"} ${w$9(o)} is ${w$9(l)}.`})}},unitRate={id:"MD.P5.M01.unit-rate.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==3)return null;const[e,s,o]=t;if(![e,s,o].every(ok)||e<2||s%e!==0)return null;const r=s/e,a=Number(n.correctAnswer);return r*o!==a||!ok(a)||!ok(r)?null:scalarTwoStep({q:n,generatorId:this.id,title:`${e} make ${s} — how much do ${o} make?`,xExpr:`${s} ÷ ${e}`,xVal:r,op:"×",second:o,answer:a,computed:{count:e,total:s,newCount:o,unit:r},planCaption:`Plan: find one (${s} ÷ ${e}), then × ${o}.`,planSpoken:`First we find the amount for just one, then we multiply by ${w$9(o)}.`,findCaption:`First find one: ${s} ÷ ${e} = ${r}.`,findSpoken:`Step one. ${W$2(s)} divided by ${w$9(e)} is ${w$9(r)} for one. That is our hidden value.`,useCaption:`Then scale up: ${r} × ${o} = ${a}.`,useSpoken:`Step two. We use the amount for one: ${w$9(r)} times ${w$9(o)} is ${w$9(a)}.`})}},packRate={id:"MD.P5.M01.pack-rate.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==3)return null;const[e,s,o]=t;if(![e,s,o].every(ok)||e<2||o%e!==0)return null;const r=o/e,a=Number(n.correctAnswer);return r*s!==a||!ok(a)||!ok(r)?null:scalarTwoStep({q:n,generatorId:this.id,title:`${o} items in packs of ${e} — what's the cost?`,xExpr:`${o} ÷ ${e}`,xVal:r,op:"×",second:s,answer:a,computed:{perPack:e,price:s,loose:o,packs:r},planCaption:`Plan: find packs (${o} ÷ ${e}), then × ${s}.`,planSpoken:"First we find how many packs, then we multiply by the price of one pack.",findCaption:`First find the packs: ${o} ÷ ${e} = ${r}.`,findSpoken:`Step one. ${W$2(o)} divided by ${w$9(e)} is ${w$9(r)} packs. That is our hidden value.`,useCaption:`Then pay per pack: ${r} × ${s} = ${a}.`,useSpoken:`Step two. We use the number of packs: ${w$9(r)} times ${w$9(s)} is ${w$9(a)}.`})}},mulThenDivide={id:"MD.P5.M01.mul-divide.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==3)return null;const[e,s,o]=t;if(![e,s,o].every(ok))return null;const r=e*s;if(!ok(r)||o<2||r%o!==0)return null;const a=r/o,i=Number(n.correctAnswer);return a!==i||!ok(i)||!ok(a)?null:scalarTwoStep({q:n,generatorId:this.id,title:`First find ${e} × ${s}, then share between ${o}`,xExpr:`${e} × ${s}`,xVal:r,op:"÷",second:o,answer:i,computed:{a:e,b:s,c:o,product:r},planCaption:`Plan: find ${e} × ${s}, then ÷ ${o}.`,planSpoken:`First we multiply to find the total, then we share it equally between ${w$9(o)}.`,findCaption:`First find the total: ${e} × ${s} = ${r}.`,findSpoken:`Step one. ${W$2(e)} times ${w$9(s)}${methodHint("multiply",e,s)} is ${w$9(r)}. That is our hidden value.`,useCaption:`Then share out: ${r} ÷ ${o} = ${i}.`,useSpoken:`Step two. We use that total: ${w$9(r)} divided by ${w$9(o)}${methodHint("divide",r,o)} is ${w$9(i)}.`})}},tripleProduct={id:"MD.P5.M01.triple-product.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==3)return null;const[e,s,o]=t;if(![e,s,o].every(ok))return null;const r=e*s,a=r*o;if(!ok(r)||!ok(a))return null;const i=Number(n.correctAnswer);return a!==i?null:scalarTwoStep({q:n,generatorId:this.id,title:`Work out ${e} × ${s} × ${o}`,xExpr:`${e} × ${s}`,xVal:r,op:"×",second:o,answer:i,computed:{a:e,b:s,c:o,first:r,total:a},planCaption:`Plan: find ${e} × ${s}, then × ${o}.`,planSpoken:`First we multiply the first two numbers, then we multiply that answer by ${w$9(o)}.`,findCaption:`First product: ${e} × ${s} = ${r}.`,findSpoken:`Step one. ${W$2(e)} times ${w$9(s)}${methodHint("multiply",e,s)} is ${w$9(r)}. That is our hidden value.`,useCaption:`Then multiply on: ${r} × ${o} = ${i}.`,useSpoken:`Step two. We use that product: ${w$9(r)} times ${w$9(o)}${methodHint("multiply",r,o)} is ${w$9(i)}.`})}},twoProducts={id:"MD.P5.M01.two-products.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==4||!t.every(ok))return null;const e=t[0]*t[2],s=t[1]*t[3];if(!ok(e)||!ok(s))return null;const o=Number(n.correctAnswer);let r;if(e+s===o)r="+";else if(e-s===o)r="−";else return null;return ok(o)?buildExample({generatorId:this.id,question:n,title:`Work out ${t[0]} × ${t[2]} ${r} ${t[1]} × ${t[3]}`,computed:{a:t[0],b:t[1],q1:t[2],q2:t[3],p1:e,p2:s,answer:o},steps:[{visual_data:eq$4([`${t[0]} × ${t[2]}`,r,`${t[1]} × ${t[3]}`,"=","__"]),animation:"plan",caption:`Plan: work out each part, then ${r==="+"?"add":"subtract"}.`,spoken:`Two parts here. We work out each part on its own, then ${r==="+"?"add them together":"take the second from the first"}.`},{visual_data:eq$4([`${e}`,r,`${s}`,"=","__"]),animation:"step_one",caption:`Each part: ${t[0]} × ${t[2]} = ${e}, ${t[1]} × ${t[3]} = ${s}.`,spoken:`${W$2(t[0])} times ${w$9(t[2])} is ${w$9(e)}. ${W$2(t[1])} times ${w$9(t[3])} is ${w$9(s)}.`},{visual_data:eq$4([`${e}`,r,`${s}`,"=",`${o}`]),animation:"step_two",caption:`Combine: ${e} ${r} ${s} = ${o}.`,spoken:`Now we combine them: ${w$9(e)} ${r==="+"?"plus":"take away"} ${w$9(s)} is ${w$9(o)}.`}]}):null}},chainMulSubDiv={id:"MD.P5.M01.chain-mul-sub-div.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=stemInts(n.stem);if(t.length!==4||!t.every(ok))return null;const[e,s,o,r]=t,a=e*s;if(!ok(a))return null;const i=a-o;if(i<0||r<2||i%r!==0)return null;const l=i/r,u=Number(n.correctAnswer);return l!==u||!ok(i)||!ok(l)?null:buildExample({generatorId:this.id,question:n,title:`First ${e} × ${s}, take ${o}, then share between ${r}`,computed:{a:e,b:s,c:o,d:r,product:a,afterSub:i,share:l,answer:u},steps:[{visual_data:eq$4([`${e} × ${s}`,"=","__"]),animation:"plan",caption:`Plan: times, then take ${o} away, then share by ${r}.`,spoken:"A three-part problem. First we find the total, then we take some away, then we share what is left."},{visual_data:eq$4([`${a}`,"−",`${o}`,"=",`${i}`]),animation:"step_one",caption:`${e} × ${s} = ${a}, then − ${o} = ${i}.`,spoken:`${W$2(e)} times ${w$9(s)}${methodHint("multiply",e,s)} is ${w$9(a)}. Take away ${w$9(o)} and we have ${w$9(i)} left.`},{visual_data:eq$4([`${i}`,"÷",`${r}`,"=",`${u}`]),animation:"step_two",caption:`Share the rest: ${i} ÷ ${r} = ${u}
71.`,spoken:`Now we share the ${w$9(i)} that is left between ${w$9(r)}${methodHint("divide",i,r)}, which is ${w$9(u)} each.`}]})}},remainder={id:"MD.P5.M01.remainder.v1",microSkillId:"MD.P5.M01",forms:["context_reasoning"],generate(n){const t=stemInts(n.stem);if(t.length!==2)return null;const[e,s]=t;if(![e,s].every(ok)||s<2||e<=s)return null;const o=Math.floor(e/s),r=o*s,a=e-r,i=Number(n.correctAnswer);return a!==i||!ok(o)||!ok(r)||!ok(a)?null:buildExample({generatorId:this.id,question:n,title:`${e} shared into groups of ${s} — how many left over?`,computed:{total:e,group:s,groups:o,filled:r,left:a,answer:i},steps:[{visual_data:eq$4([`${e} ÷ ${s}`,"=","__"]),animation:"plan",caption:`Plan: fit as many ${s}s into ${e} as we can.`,spoken:"We share into equal groups, then look at the amount that will not make a full group."},{visual_data:eq$4([`${e}`,"÷",`${s}`,"=",`${o}`,"r","__"]),animation:"step_one",caption:`${e} ÷ ${s} = ${o} r ?`,spoken:`${W$2(e)} divided by ${w$9(s)}${methodHint("divide",e,s)} is ${w$9(o)}, with some left over — let's check exactly how many.`},{visual_data:eq$4([`${s} × ${o}`,"=",`${r}`,",",`${e}`,"−",`${r}`,"=",`${a}`]),animation:"step_two",caption:`Check: ${s} × ${o} = ${r}, ${e} − ${r} = ${a}.`,spoken:`We check it: ${w$9(o)} full groups of ${w$9(s)} use up ${w$9(r)}, and ${w$9(e)} take away ${w$9(r)} leaves ${w$9(a)} left over.`}]})}},roundUpDivision={id:"MD.P5.M01.round-up.v1",microSkillId:"MD.P5.M01",forms:["context_reasoning"],generate(n){const t=stemInts(n.stem);if(t.length!==2)return null;const[e,s]=t;if(![e,s].every(ok)||s<2||e<=s||e%s===0)return null;const o=Math.floor(e/s),r=o*s,a=e-r,i=o+1,l=Number(n.correctAnswer);return i!==l||![o,r,a,i].every(ok)?null:buildExample({generatorId:this.id,question:n,title:`${e} in groups of ${s} — how many groups needed?`,computed:{total:e,group:s,whole:o,carried:r,left:a,needed:i,answer:l},steps:[{visual_data:eq$4([`${e} ÷ ${s}`,"=","__"]),animation:"plan",caption:"Plan: divide, then check for any left over.",spoken:"We divide into equal groups, then decide what to do with anyone left over."},{visual_data:eq$4([`${s} × ${o}`,"=",`${r}`]),animation:"step_one",caption:`${o} full groups hold ${s} × ${o} = ${r}.`,spoken:`${W$2(o)} full groups hold ${w$9(r)}, but that leaves ${w$9(a)} still waiting.`},{visual_data:eq$4([`${o}`,"+","1","=",`${i}`]),animation:"step_two",caption:`The last ${a} need one more group: ${i}.`,spoken:`The ${w$9(a)} left over still need a group, so we round up. One more group makes ${w$9(i)}.`}]})}},compareTwoDivisions={id:"MD.P5.M01.compare-divisions.v1",microSkillId:"MD.P5.M01",forms:["context_reasoning"],generate(n){const t=stemInts(n.stem);if(t.length!==4)return null;const[e,s,o,r]=t;if(![e,s,o,r].every(ok)||s<1||r<1||e%s!==0||o%r!==0)return null;const a=e/s,i=o/r,l=Math.abs(a-i),u=Number(n.correctAnswer);if(l!==u||![a,i,l].every(ok))return null;const d=Math.max(a,i),m=Math.min(a,i);return buildExample({generatorId:this.id,question:n,title:`${e} ÷ ${s} versus ${o} ÷ ${r} — how many more?`,computed:{a:e,b:s,c:o,d:r,left:a,right:i,diff:l,hi:d,lo:m,answer:u},steps:[{visual_data:eq$4([`${e} ÷ ${s}`,"?",`${o} ÷ ${r}`]),animation:"plan",caption:"Plan: find each group size, then compare.",spoken:"First we find the size of one group in each class, then we compare the two."},{visual_data:eq$4([`${e} ÷ ${s} = ${a}`,",",`${o} ÷ ${r} = ${i}`]),animation:"step_one",caption:`${e} ÷ ${s} = ${a}, and ${o} ÷ ${r} = ${i}.`,spoken:`${W$2(e)} divided by ${w$9(s)} is ${w$9(a)}. ${W$2(o)} divided by ${w$9(r)} is ${w$9(i)}.`},{visual_data:eq$4([`${d}`,"−",`${m}`,"=",`${l}`]),animation:"step_two",caption:`Compare: ${d} − ${m} = ${l} more.`,spoken:`To find how many more, we subtract: ${w$9(d)} take away ${w$9(m)} is ${w$9(l)}.`}]})}},COMPARE_BOXES=/holds (\d+)[^.]*\.[^.]*holds (\d+)[^.]*\.[^?]*?(\d+) large [^?]*?or (\d+) small/i,compareTwoProducts={id:"MD.P5.M01.compare-products.v1",microSkillId:"MD.P5.M01",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(COMPARE_BOXES);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(![e,s,o,r].every(ok))return null;const a=o*e,i=r*s;if(!ok(a)||!ok(i))return null;const l=`${o} large boxes`,u=`${r} small boxes`,d=a===i?"They hold the same number":a>i?l:u;if(n.correctAnswer.trim()!==d)return null;const m=a===i?"=":a>i?">":"<";return buildExample({generatorId:this.id,question:n,title:`Which holds more: ${o} large or ${r} small boxes?`,computed:{perLarge:e,perSmall:s,largeCount:o,smallCount:r,largeTotal:a,smallTotal:i},steps:[{visual_data:eq$4([`${o} × ${e}`,"=","__"]),animation:"plan",caption:"Plan: work out each side, then compare.",spoken:"To compare, we must find the total in each group first, then see which is bigger."},{visual_data:eq$4([`${o} × ${e} = ${a}`,",",`${r} × ${s} = ${i}`]),animation:"step_one",caption:`Large: ${o} × ${e} = ${a}. Small: ${r} × ${s} = ${i}
71.`,spoken:`${W$2(o)} large boxes hold ${w$9(a)}${methodHint("multiply",o,e)}. ${W$2(r)} small boxes hold ${w$9(i)}${methodHint("multiply",r,s)}.`},{visual_data:{type:"comparison",left:a,right:i,symbol:m},animation:"step_two",caption:m==="="?`Equal: both hold ${a}.`:`${d} hold more (${Math.max(a,i)}).`,spoken:m==="="?`Both come to ${w$9(a)}, so they hold the same.`:`${W$2(Math.max(a,i))} is more than ${w$9(Math.min(a,i))}, so the ${a>i?"large":"small"} boxes hold more.`}]})}},bookingNoShows={id:"MD.P5.M01.booking-noshows.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem).match(/cost £?(\d+) each[\s\S]*?(\d+) people do not turn up[\s\S]*?paid £?(\d+) in total/i);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(![e,s,o].every(ok)||e<2||o%e!==0)return null;const r=o/e,a=Number(n.correctAnswer);return r+s!==a||!ok(a)||!ok(r)?null:scalarTwoStep({q:n,generatorId:this.id,title:`First find ${o} ÷ ${e}, then add ${s}`,xExpr:`${o} ÷ ${e}`,xVal:r,op:"+",second:s,answer:a,computed:{price:e,noShows:s,total:o,attended:r},planCaption:`Plan: find ${o} ÷ ${e} who came, then add the ${s} who didn't.`,planSpoken:"A two-step problem. First we find how many people paid, then we add on the ones who did not turn up.",findCaption:`Who came: ${o} ÷ ${e} = ${r}.`,findSpoken:`Step one. ${W$2(o)} pounds shared at ${w$9(e)} pounds each is ${w$9(r)} people who came. That is our hidden value.`,useCaption:`Add the no-shows: ${r} + ${s} = ${a}.`,useSpoken:`Step two. We add back the ${w$9(s)} who did not come: ${w$9(r)} plus ${w$9(s)} is ${w$9(a)} in the original booking.`})}},PROFIT=/buys (\d+) [a-z]+ of (\d+) [a-z]+ for £(\d+)[\s\S]*?sells every [a-z]+ at (\d+)p[\s\S]*?profit/i,profitFromSales={id:"MD.P5.M01.profit.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=n.stem.match(PROFIT);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]),r=Number(t[4]);if(![e,s,o,r].every(ok))return null;const a=e*s;if(!ok(a))return null;const i=a*r;if(!ok(i)||i%100!==0)return null;const l=i/100,u=l-o,d=Number(n.correctAnswer);return u!==d||!ok(u)||u<0?null:buildExample({generatorId:this.id,question:n,title:`${e} × ${s} at ${r}p, minus £${o} cost — what's the profit?`,computed:{groups:e,perGroup:s,cost:o,pence:r,totalItems:a,revenuePence:i,revenuePounds:l,answer:u,profit:u},steps:[{visual_data:eq$4([`${e} × ${s}`,"=","__"]),animation:"plan",caption:"Plan: find the items sold, then the money in, then take away the cost.",spoken:"A three-part problem: first the total items, then how much money came in, then we subtract what the shop paid."},{visual_data:eq$4([`${a} × ${r}p`,"=",`${i}p`,"=",`£${l}`]),animation:"revenue",caption:`${a} × ${r}p = ${i}p = £${l}.`,spoken:`${W$2(e)} lots of ${w$9(s)}${methodHint("multiply",e,s)} makes ${w$9(a)} items sold. ${W$2(a)} times ${w$9(r)}${methodHint("multiply",a,r)} is ${w$9(i)} pence, which is ${w$9(l)} pounds — a hundred pence make a pound.`},{visual_data:eq$4([`£${l}`,"−",`£${o}`,"=",`£${u}`]),animation:"subtract_cost",caption:`£${l} − £${o} = £${u} profit.`,spoken:`${W$2(l)} pounds take away ${w$9(o)} pounds the shop paid is ${w$9(u)} pounds profit.`}]})}},CONVERT_CM_TO_M=/made of (\d+) identical [a-z]+\. Each [a-z]+ is (\d+) cm long[\s\S]*?in metres/i,convertCmToM={id:"MD.P5.M01.convert-cm-m.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=n.stem.match(CONVERT_CM_TO_M);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(![e,s].every(ok))return null;const o=e*s;if(!ok(o)||o%100!==0)return null;const r=o/100,a=Number(n.correctAnswer);return r!==a||!ok(r)?null:buildExample({generatorId:this.id,question:n,title:`${e} × ${s} cm — how many metres?`,computed:{count:e,eachCm:s,totalCm:o,answer:r,totalM:r},steps:[{visual_data:eq$4([`${e} × ${s}`,"=","__"]),animation:"plan",caption:"Plan: find the length in cm, then convert to metres.",spoken:"A two-step problem. First we find the length in centimetres, then we change it into metres."},{visual_data:eq$4([`${e} × ${s}`,"=",`${o} cm`]),animation:"multiply",caption:`${e} × ${s} = ${o} cm.`,spoken:`${W$2(e)} lots of ${w$9(s)} centimetres makes ${w$9(o)} centimetres.`},{visual_data:eq$4([`${o} cm`,"÷","100","=",`${r} m`]),animation:"convert",caption:`${o} cm ÷ 100 = ${r}
71 m.`,spoken:`${W$2(o)} centimetres divided by one hundred is ${w$9(r)} metres.`}]})}},DECIMAL_UNIT_RATE=/(\d+) [a-z]+ cost £(\d+)\.(\d{2}) in total\. At the same price, how much do (\d+) [a-z]+ cost\?/i,poundsLabel=n=>`£${Math.floor(n/100)}.${String(n%100).padStart(2,"0")}`,moneyWords$1=n=>n<100?`${w$9(n)} pence`:n%100===0?`${w$9(n/100)} pounds`:`${w$9(Math.floor(n/100))} pounds and ${w$9(n%100)} pence`,decimalUnitRate={id:"MD.P5.M01.decimal-unit-rate.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=n.stem.match(DECIMAL_UNIT_RATE);if(!t)return null;const e=Number(t[1]),s=Number(t[2])*100+Number(t[3]),o=Number(t[4]);if(![e,s,o].every(ok)||e<1||s%e!==0)return null;const r=s/e,a=r*o;if(!ok(a))return null;const i=poundsLabel(a);if(n.correctAnswer.trim()!==i)return null;const l=poundsLabel(s),u=r<100?`${r}p`:poundsLabel(r);return buildExample({generatorId:this.id,question:n,title:`${e} for ${l} — what do ${o} cost?`,computed:{count:e,totalPence:s,newCount:o,perItemPence:r,newTotalPence:a,totalPounds:s/100,perItemPounds:r/100,newTotalPounds:a/100},steps:[{visual_data:eq$4([l,"÷",`${e}`,"=","__"]),animation:"plan",caption:`Plan: find the cost of one, then × ${o}.`,spoken:`First we find the cost of just one, then we multiply by ${w$9(o)}.`},{visual_data:eq$4([l,"÷",`${e}`,"=",u]),animation:"find_one",caption:`${l} ÷ ${e} = ${u}.`,spoken:`${capitalised(moneyWords$1(s))} shared between ${w$9(e)} is ${moneyWords$1(r)} for one.`},{visual_data:eq$4([u,"×",`${o}`,"=",i]),animation:"scale_up",caption:`${u} × ${o} = ${i}.`,spoken:`The cost of one times ${w$9(o)} is ${moneyWords$1(a)}.`}]})}},CYCLE_WEEKS=/cycles (\d+) (\w+) each day for (\d+) weeks?\.[\s\S]*?cycles (\d+) \w+ in total\. How many more/,cycleWeeksCompare={id:"MD.P5.M01.cycle-weeks-compare.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=n.stem.match(CYCLE_WEEKS);if(!t)return null;const e=Number(t[1]),s=t[2],o=Number(t[3]),r=Number(t[4]);if(![e,o,r].every(ok))return null;const a=o*7,i=e*a;if(!ok(a)||!ok(i))return null;const l=i-r,u=Number(n.correctAnswer);if(l!==u||!ok(l)||l<0)return null;const d={km:"kilometres",m:"metres",mi:"miles"}[s.toLowerCase()]??s;return buildExample({generatorId:this.id,question:n,title:`${e} ${s} a day for ${o} weeks vs ${r} ${s} — how many more?`,computed:{rate:e,weeks:o,other:r,days:a,total:i,answer:l,diff:l},steps:[{visual_data:eq$4([`${o} weeks`,"=",`${a} days`]),animation:"convert_weeks",caption:`${o} weeks = ${a} days.`,spoken:`The distance is given for each day, so first we turn the weeks into days: ${w$9(o)} weeks is ${w$9(a)} days, because there are seven days in a week.`},{visual_data:eq$4([`${e} × ${a}`,"=",`${i}`]),animation:"find_total",caption:`${e} × ${a} = ${i}.`,spoken:`${W$2(e)} ${d} a day for ${w$9(a)} days${methodHint("multiply",e,a)} is ${w$9(i)} ${d} in total.`},{visual_data:eq$4([`${i}`,"−",`${r}`,"=",`${l}`]),animation:"compare",caption:`${i} − ${r} = ${l}.`,spoken:`To find how many more, we take the smaller total away: ${w$9(i)} take away ${w$9(r)} is ${w$9(l)} ${d} more.`}]})}},PRINTER_HOURS_MIN=/prints (\d+) \w+ every minute\.[\s\S]*?in (\d+) hour(?:s)? and (\d+) minutes?/,printerHoursMinutes={id:"MD.P5.M01.rate-hours-minutes.v1",microSkillId:"MD.P5.M01",forms:["context_calculation"],generate(n){const t=n.stem.match(PRINTER_HOURS_MIN);if(!t)return null;const e=Number(t[1]),s=Number(t[2]),o=Number(t[3]);if(![e,s,o].every(ok))return null;const r=s*60+o,a=e*r;if(!ok(r)||!ok(a))return null;const i=Number(n.correctAnswer);return a!==i?null:buildExample({generatorId:this.id,question:n,title:`${e} a minute for ${s}h ${o}m — how many in total?`,computed:{rate:e,hours:s,mins:o,totalMinutes:r,answer:a,sixty:60,hoursInMinutes:s*60},steps:[{visual_data:eq$4([`${s} h ${o} m`,"=","__"]),animation:"plan",caption:"Plan: turn the time into minutes, then multiply by the rate.",spoken:"A two-step problem. First we turn the time into one number of minutes, then we multiply by how many pages print each minute."},{visual_data:eq$4([`${s} × 60`,"+",`${o}`,"=",`${r}`]),animation:"convert_time",caption:`${s} × 60 + ${o} = ${r} minutes.`,spoken:`${W$2(s)} hour${s===1?"":"s"} is ${w$9(s*60)} minutes, plus ${w$9(o)} more makes ${w$9(r)} minutes in total.`},{visual_data:eq$4([`${e} × ${r}`,"=",`${a}`]),animation:"find_total",caption:`${e} × ${r} = ${a}.`,spoken:`${W$2(e)} a minute for ${w$9(r)} minutes${methodHint("multiply",e,r)} is ${w$9(a)} in total.`}]})}},MD_P5_M01_GENERATORS=[cycleWeeksCompare,printerHoursMinutes,bookingNoShows,profitFromSales,convertCmToM,decimalUnitRate,compareTwoProducts,compareTwoDivisions,remainder,roundUpDivision,twoProducts,chainMulSubDiv,unitRate,packRate,mulThenDivide,tripleProduct,divThenAddSub,mulThenAddSub],struckThrough$1=n=>String(n).split("").map(t=>`${t}̶`).join(""),gcd$3=(n,t)=>t===0?n:gcd$3(t,n%t),lcm=(n,t)=>n/gcd$3(n,t)*t,divisorsOf=n=>{const t=[];for(let e=1;e<=n;e++)n%e===0&&t.push(e);return t},factorPairs=n=>{const t=[];for(let e=1;e*e<=n;e++)n%e===0&&t.push([e,n/e]);return t},ints$1=n=>(n.match(/\d+/g)??[]).map(Number),w$8=numberToWords,eqVD=n=>({type:"equation_display",terms:n}
71),ffVD=(n,t,e=!1)=>e?{type:"fact_family",rows:n,cols:t,show_group_counts:!0}:{type:"fact_family",rows:n,cols:t},multiplesRow=(n,t)=>Array.from({length:t},(e,s)=>n*(s+1));function factorArrayDemo(n){const{rows:t,cols:e}=n;if(t<1||t>10||e<1||e>10)return null;const s=t*e;return buildExample({generatorId:n.id,question:n.q,title:n.title,computed:{rows:t,cols:e,product:s,answer:n.answer,...n.computedExtra},steps:[{visual_data:ffVD(t,e),animation:"build_array",caption:n.step1Caption,spoken:n.spoken1},{visual_data:ffVD(t,e),animation:"divide",caption:n.step2Caption,spoken:n.spoken2},{visual_data:ffVD(t,e,!0),animation:"family",caption:n.step3Caption,spoken:n.spoken3}]})}function eqDemo(n){const t=(e,s)=>s&&s.length?{...eqVD(e),highlight:s}:eqVD(e);return buildExample({generatorId:n.id,question:n.q,title:n.title,computed:n.computed,steps:[{visual_data:t(n.terms1,n.highlight1),animation:"list",caption:n.caption1,spoken:n.spoken1},{visual_data:t(n.terms2,n.highlight2),animation:"narrow",caption:n.caption2,spoken:n.spoken2},{visual_data:t(n.terms3,n.highlight3),animation:"conclude",caption:n.caption3,spoken:n.spoken3}]})}const multipleIdentification={id:"MD.P5.M02.multiple-identify.v1",microSkillId:"MD.P5.M02",forms:["multiple_identification"],generate(n){const t=normaliseStem(n.stem);let e=t.match(/is a multiple of (\d+)\?$/);const s=t.match(/counts up in \w+: (\d+), (\d+)/);if(e||s){const o=e?Number(e[1]):Number(s[2])-Number(s[1]);if(o<2)return null;const r=(n.options??[]).map(m=>Number(m)).filter(m=>Number.isFinite(m)),a=Number(n.correctAnswer);if(!Number.isInteger(a)||a%o!==0||r.some(m=>m!==a&&m%o===0))return null;const i=a/o;if(i<1||i>20)return null;const l=multiplesRow(o,Math.min(i,8));if(l[l.length-1]!==a&&i<=8)return null;const u=r.length?r:[a],d=u.filter(m=>m!==a);return eqDemo({q:n,id:this.id,title:`Which number is a multiple of ${o}?`,computed:{base:o,answer:a,mult:i},terms1:multiplesRow(o,Math.min(Math.max(i,5),8)),terms2:u.map(m=>m===a?m:struckThrough$1(m)),terms3:[a,"=",i,"×",o],caption1:`Count in ${o}s to list the multiples.`,caption2:`Keep ${a}; cross out choices outside the ${o}s count.`,caption3:`${a} = ${i} × ${o}, a multiple of ${o}.`,spoken1:`The multiples of ${w$8(o)} are the numbers we say counting in ${w$8(o)}s.`,spoken2:`${capitalised(w$8(a))} appears when we count in ${w$8(o)}s.${d.length?` The other ${d.length===1?"choice does":"choices do"} not, so we cross ${d.length===1?"it":"them"} out.`:""}`,highlight2:u.map((m,h)=>m===a?h:-1).filter(m=>m>=0),spoken3:`${capitalised(w$8(a))} is ${w$8(i)} ${w$8(o)}s, so it is a multiple of ${w$8(o)}.`})}if(e=t.match(/Write a multiple of (\d+) that is between (\d+) and (\d+)/),e){const o=Number(e[1]),r=Number(e[2]),a=Number(e[3]),i=Number(n.correctAnswer);if(o<2)return null;const l=Math.ceil((r+1)/o)*o;if(l>=a||l!==i)return null;const u=l/o;if(u<1||u>20)return null;const d=multiplesRow(o,Math.min(u+1,8)),m=d.map((h,f)=>f).filter(h=>d[h]>r&&d[h]<a);return eqDemo({q:n,id:this.id,title:`A multiple of ${o} between ${r} and ${a}`,computed:{base:o,answer:l,mult:u,lo:r,hi:a},terms1:d,terms2:d,terms3:[l,"=",u,"×",o],caption1:`Count in ${o}s.`,caption2:`Find one between ${r} and ${a}.`,caption3:`${l} = ${u} × ${o}, between ${r} and ${a}.`,spoken1:`We count in ${w$8(o)}s to list its multiples.`,spoken2:`We look for a multiple that lands between ${w$8(r)} and ${w$8(a)}.`,spoken3:`${capitalised(w$8(l))} is ${w$8(u)} ${w$8(o)}s and sits between ${w$8(r)} and ${w$8(a)}.`,highlight2:m.length?m:void 0})}if(e=t.match(/every multiple of (\d+)\. Will the number (\d+) be coloured/),e){const o=Number(e[1]),r=Number(e[2]);if(o<2)return null;const a=r%o===0,i=n.correctAnswer.trim().toLowerCase();if(a?!i.startsWith("yes"):!i.startsWith("no"))return null;const l=Math.floor(r/o),u=a?[o*(l-1),r,o*(l+1)].filter(f=>f>0):[o*l,o*Math.ceil(r/o)],d=Array.from(new Set([...u,r])).sort((f,g)=>f-g),m=d.map(f=>f===r?struckThrough$1(r):f),h=u.map((f,g)=>g);return eqDemo({q:n,id:this.id,title:`Is ${r} a multiple of ${o}?`,computed:{base:o,answer:r,target:r,around:l,...a?{}:{lowNeighbor:u[0],highNeighbor:u[u.length-1]}},terms1:multiplesRow(o,Math.min(Math.max(l+1,4),8)),terms2:a?d:m,terms3:a?[r,"=",l,"×",o]:u,caption1:`Count in ${o}s.`,caption2:`Does ${r} appear in the ${o}s count?`,caption3:a?`${r} = ${l} × ${o}: a multiple of ${o}.`:`${u[0]} and ${u[u.length-1]}: ${r} isn't in the ${o}s count.`,spoken1:`We count in ${w$8(o)}s to list the multiples of ${w$8(o)}.`,spoken2:`We look to see whether ${w$8(r)} lands on the ${w$8(o)}s count.`,spoken3:a?`${capitalised(w$8(r))} is ${w$8(l)} ${w$8(o)}s, so it would be coloured.`:`${capitalised(w$8(r))} is jumped over, so it is not a multiple of ${w$8(o)} and stays uncoloured.`,highlight3:!a&&h.length?h:void 0})}return null}},trueFalseCheck={id:"MD.P5.M02.true-false.v1",microSkillId:"MD.P5.M02",forms:["true_false_check"],generate(n){const t=normaliseStem(n.stem),e=n.correctAnswer.trim().toLowerCase();let s=t.match(/^Is (\d+) a multiple of (\d+)\?/);if(s){const o=Number(s[1]),r=Number(s[2]);if(r<2||o<1)return null;const a=o%r===0;if(a?!e.startsWith("yes"):!e.startsWith("no"))return null;const i=Math.floor(o/r),l=o%r;if(a){const u=factorArrayDemo({q:n,id:this.id,title:`Is ${o} a multiple of ${r}?`,rows:r,cols:i,answer:o,step1Caption:`Try to make ${o} from rows of ${r}.`,step2Caption:`${o} ÷ ${r} = ${i}, none left.`,step3Caption:`Yes: ${o} is a multiple of ${r}.`,spoken1:`We check if ${w$8(o)} can be built from equal rows of ${w$8(r)}.`,spoken2:`${capitalised(w$8(o))}
71 divided by ${w$8(r)} is ${w$8(i)} exactly, with nothing left over.`,spoken3:`Because ${w$8(r)} divides ${w$8(o)} exactly, ${w$8(o)} is a multiple of ${w$8(r)}.`,computedExtra:{val:o}});if(u)return u}return eqDemo({q:n,id:this.id,title:`Is ${o} a multiple of ${r}?`,computed:{base:r,val:o,answer:o,quotient:i,rem:l},terms1:multiplesRow(r,Math.min(Math.max(i+1,4),8)),terms2:[`${o} ÷ ${r}`],terms3:[o,"÷",r,"=",i,"r",l],caption1:`Count in ${r}s.`,caption2:`Does ${o} land on the ${r}s count?`,caption3:`${o} ÷ ${r} leaves ${l}: not a multiple.`,spoken1:`We count in ${w$8(r)}s to list the multiples of ${w$8(r)}.`,spoken2:`We look for ${w$8(o)} in that count.`,spoken3:`${capitalised(w$8(o))} divided by ${w$8(r)} leaves ${w$8(l)} over, so ${w$8(o)} is not a multiple of ${w$8(r)}.`})}if(s=t.match(/^Is (\d+) a factor of (\d+)\?/)??t.match(/says (\d+) is a factor of (\d+)\./),s){const o=Number(s[1]),r=Number(s[2]);if(o<1||r<1)return null;const a=r%o===0;if(a?!e.startsWith("yes"):!e.startsWith("no"))return null;const i=Math.floor(r/o),l=r%o;if(a){const m=factorArrayDemo({q:n,id:this.id,title:`Is ${o} a factor of ${r}?`,rows:o,cols:i,answer:r,step1Caption:`Share ${r} into ${o} equal rows.`,step2Caption:`${r} ÷ ${o} = ${i}, nothing left.`,step3Caption:`Yes: ${o} is a factor of ${r}.`,spoken1:`We try to share ${w$8(r)} into ${w$8(o)} equal rows.`,spoken2:`${capitalised(w$8(r))} divided by ${w$8(o)} is ${w$8(i)} exactly, with no remainder.`,spoken3:`Because ${w$8(o)} divides ${w$8(r)} exactly, ${w$8(o)} is a factor of ${w$8(r)}.`,computedExtra:{n:r,f:o}});if(m)return m}const u=o*i,d=u+o;return eqDemo({q:n,id:this.id,title:`Is ${o} a factor of ${r}?`,computed:{f:o,n:r,answer:r,co:i,rem:l,lowMult:u,highMult:d},terms1:[`${r} ÷ ${o}`],terms2:[u,struckThrough$1(r),d],terms3:[r,"÷",o,"=",i,"r",l],caption1:`A factor divides ${r} with none left over.`,caption2:`${o}s land on ${u}, then ${d}: ${r} is skipped.`,caption3:`${r} ÷ ${o} leaves ${l}: not a factor.`,spoken1:`A factor of ${w$8(r)} divides it exactly, leaving no remainder.`,spoken2:`Counting in ${w$8(o)}s lands on ${w$8(u)}, then ${w$8(d)} — ${w$8(r)} is stepped over.`,spoken3:`${capitalised(w$8(r))} divided by ${w$8(o)} leaves ${w$8(l)} over, so ${w$8(o)} is not a factor of ${w$8(r)}.`})}return null}},factorIdentification={id:"MD.P5.M02.factor-identify.v1",microSkillId:"MD.P5.M02",forms:["factor_identification"],generate(n){const t=normaliseStem(n.stem),e=n.options??[];let s=t.match(/is a factor of (\d+)\?$/);if(s&&!/NOT a factor/.test(t)){const o=Number(s[1]),r=Number(n.correctAnswer);if(!Number.isInteger(r)||r<1||o%r!==0||e.map(Number).some(l=>l!==r&&Number.isInteger(l)&&o%l===0))return null;const a=o/r,i=factorArrayDemo({q:n,id:this.id,title:`Which number is a factor of ${o}?`,rows:r,cols:a,answer:r,step1Caption:`Test each choice: does it divide ${o}?`,step2Caption:`Does it divide ${o} exactly, with none left?`,step3Caption:`${r} is a factor of ${o}.`,spoken1:`A factor of ${w$8(o)} divides it exactly. We test each choice.`,spoken2:`${capitalised(w$8(o))} divided by ${w$8(r)} is ${w$8(a)} with nothing left over.`,spoken3:`So ${w$8(r)} divides ${w$8(o)} exactly and is a factor of ${w$8(o)}.`,computedExtra:{n:o}});if(i)return i}if(s=t.match(/NOT a factor of (\d+)\?$/),s){const o=Number(s[1]),r=Number(n.correctAnswer);if(!Number.isInteger(r)||r<1||o%r===0||e.map(Number).some(l=>l!==r&&Number.isInteger(l)&&o%l!==0))return null;const a=Math.floor(o/r),i=o%r;return eqDemo({q:n,id:this.id,title:`Which number is NOT a factor of ${o}?`,computed:{n:o,answer:r,co:a,rem:i},terms1:e.map(l=>l),terms2:[`${o} ÷ ${r}`],terms3:[o,"÷",r,"=",a,"r",i],caption1:`A factor divides ${o} exactly.`,caption2:`Try ${r}: does it divide ${o}?`,caption3:`${o} ÷ ${r} leaves ${i}: ${r} is not a factor.`,spoken1:`A factor of ${w$8(o)} divides it with no remainder.`,spoken2:`We test ${w$8(r)} by sharing ${w$8(o)} into groups of ${w$8(r)}.`,spoken3:`${capitalised(w$8(o))} divided by ${w$8(r)} leaves ${w$8(i)} over, so ${w$8(r)} is not a factor of ${w$8(o)}.`})}if(s=t.match(/Write one factor of (\d+) that is greater than (\d+)/),s){const o=Number(s[1]),r=Number(s[2]),a=Number(n.correctAnswer),i=divisorsOf(o).find(d=>d>r);if(i===void 0||i!==a)return null;const l=o/i,u=factorArrayDemo({q:n,id:this.id,title:`A factor of ${o} greater than ${r}`,rows:i,cols:l,answer:i,step1Caption:`Find a times fact for ${o}: ${i} × ${l}.`,step2Caption:`${i} × ${l} = ${o}, so ${i} is a factor.`,step3Caption:`${i} is a factor of ${o} and is over ${r}.`,spoken1:`We look for a number that divides ${w$8(o)} exactly and is bigger than ${w$8(r)}.`,spoken2:`${capitalised(w$8(i))} times ${w$8(l)} is ${w$8(o)}, so ${w$8(i)} divides ${w$8(o)} exactly.`,spoken3:`${capitalised(w$8(i))} is a factor of ${w$8(o)} and it is greater than ${w$8(r)}.`,computedExtra:{n:o,k:r}});if(u)return u}if(s=t.match(/both factors of (\d+)\?$/),s){const o=Number(s[1]),r=ints$1(n.correctAnswer);if(r.length!==2)return null;const[a,i]=r;if(o%a!==0||o%i!==0)return null;for(const l of e){if(l===n.correctAnswer)continue;const u=ints$1(l);if(u.length===2&&o%u[0]===0&&o%u[1]===0)return null}return eqDemo({q:n,id:this.id,title:`Which pair are both factors of ${o}?`,computed:{n:o,a,b:i,answer:o},terms1:divisorsOf(o),terms2:[a,"and",i],terms3:[`${a} × ${o/a}`,"=",o],caption1:`List every factor of ${o}.`,caption2:`Are ${a} and ${i} both in the list?`,caption3:`${a} and ${i} both divide ${o} exactly.`,spoken1:`We list the factors of ${w$8(o)} — the numbers that divide it exactly.`,spoken2:`We check whether ${w$8(a)} and ${w$8(i)} are both factors.`,spoken3:`Both ${w$8(a)} and ${w$8(i)} divide ${w$8(o)} with nothing left over, so both are factors.`})}if(s=t.match(/Does the card (\d+) win the number (\d+)\?/),s){const o=Number(s[1]),r=Number(s[2]),a=r%o===0,i=n.correctAnswer.trim().toLowerCase();if(a?!i.startsWith("yes"):!i.startsWith("no"))return null;const l=Math.floor(r/o),u=r%o;if(a){const d=factorArrayDemo({q:n,id:this.id,title:`Does ${o} divide ${r} exactly?`,rows:o,cols:l,answer:r,step1Caption:`Share ${r} into ${o} equal rows.`,step2Caption:`${r} ÷ ${o} = ${l}, none left.`,step3Caption:`${o} divides ${r} exactly — the card wins.`,spoken1:`The card wins if ${w$8(o)}
71 divides ${w$8(r)} exactly. We share ${w$8(r)} into ${w$8(o)} equal rows.`,spoken2:`${capitalised(w$8(r))} divided by ${w$8(o)} is ${w$8(l)} with nothing left over.`,spoken3:`So ${w$8(o)} is a factor of ${w$8(r)} and the card wins the number.`,computedExtra:{n:r}});if(d)return d}return eqDemo({q:n,id:this.id,title:`Does ${o} divide ${r} exactly?`,computed:{f:o,n:r,answer:r,co:l,rem:u},terms1:[`${r} ÷ ${o}`],terms2:[`${r} ÷ ${o}`],terms3:[r,"÷",o,"=",l,"r",u],caption1:`The card wins if ${o} divides ${r} exactly.`,caption2:`Share ${r} into groups of ${o}.`,caption3:`${r} ÷ ${o} leaves ${u}: the card loses.`,spoken1:`The card wins only if ${w$8(o)} divides ${w$8(r)} exactly.`,spoken2:`We share ${w$8(r)} into groups of ${w$8(o)}.`,spoken3:`${capitalised(w$8(r))} divided by ${w$8(o)} leaves ${w$8(u)} over, so ${w$8(o)} does not win.`})}return null}},factorPairFind={id:"MD.P5.M02.factor-pair.v1",microSkillId:"MD.P5.M02",forms:["factor_pair_find"],generate(n){const t=normaliseStem(n.stem);let e=t.match(/(\d+) × __ = (\d+)$/);if(e){const s=Number(e[1]),o=Number(e[2]),r=Number(n.correctAnswer);return!Number.isInteger(r)||s<1||s*r!==o?null:s>=1&&s<=10&&r>=1&&r<=10?buildExample({generatorId:this.id,question:n,title:`${s} × ? = ${o}`,computed:{a:s,p:o,answer:r},steps:[{visual_data:eqVD([s,"×","__","=",o]),animation:"ask",caption:`${s} times what makes ${o}?`,spoken:`We need the number that pairs with ${w$8(s)} to make ${w$8(o)}.`},{visual_data:{type:"array",rows:s,cols:r},animation:"divide_share",caption:`Share ${o} into ${s} equal rows.`,spoken:`We share ${w$8(o)} counters into ${w$8(s)} equal rows to see how many go in each.`},{visual_data:{type:"array",rows:s,cols:r},animation:"divide_conclude",caption:`Each row gets ${r}: ${s} × ${r} = ${o}.`,spoken:`Each row ends up with ${w$8(r)}, so ${w$8(s)} times ${w$8(r)} is ${w$8(o)}.`}]}):eqDemo({q:n,id:this.id,title:`${s} × ? = ${o}`,computed:{a:s,p:o,answer:r},terms1:[s,"×","__","=",o],terms2:[s,"×","__","=",o],terms3:[s,"×",r,"=",o],caption1:`${s} times what makes ${o}?`,caption2:`Count in ${s}s up to ${o}.`,caption3:`${s} × ${r} = ${o}, so the answer is ${r}.`,spoken1:`We need the number that multiplies ${w$8(s)} to make ${w$8(o)}.`,spoken2:`We count in ${w$8(s)}s until we reach ${w$8(o)}.`,spoken3:`${capitalised(w$8(s))} times ${w$8(r)} is ${w$8(o)}, so the missing number is ${w$8(r)}.`})}if(e=t.match(/Which multiplication makes (\d+)\?$/),e){const s=Number(e[1]),o=ints$1(n.correctAnswer);if(o.length!==2||o[0]*o[1]!==s)return null;const r=n.options??[];for(const f of r){if(f===n.correctAnswer)continue;const g=ints$1(f);if(g.length===2&&g[0]*g[1]===s)return null}const[a,i]=o,l=r.filter(f=>f!==n.correctAnswer).map(f=>ints$1(f)).filter(f=>f.length===2);if(l.length===0)return null;const u=[];l.forEach(([f,g],b)=>{b>0&&u.push(","),u.push(`${f} × ${g} = ${f*g}`)});const d=l.map(([f,g])=>`${f} × ${g}`).join(", "),m={p:s,a,b:i,answer:s};l.forEach(([f,g],b)=>{m[`w${b}`]=f*g});const h=a>=1&&a<=10&&i>=1&&i<=10;return buildExample({generatorId:this.id,question:n,title:`Which multiplication makes ${s}?`,computed:m,steps:[{visual_data:eqVD(["?","×","?","=",s]),animation:"ask",caption:`Test each: which product is ${s}?`,spoken:`We try each multiplication in turn to see which one makes ${w$8(s)}.`},{visual_data:eqVD(u),animation:"eliminate",caption:`${d} — not ${s}.`,spoken:`Testing the others: they do not make ${w$8(s)}, so we rule them out.`},{visual_data:h?{type:"array",rows:a,cols:i}:eqVD([a,"×",i,"=",s]),animation:"conclude",caption:`${a} × ${i} = ${s}: that is the pair.`,spoken:`${capitalised(w$8(a))} rows of ${w$8(i)} make ${w$8(s)} exactly, so that is the correct multiplication.`}]})}if(e=t.match(/How many factors does (\d+) have/),e){const s=Number(e[1]),o=divisorsOf(s),r=o.length;if(r!==Number(n.correctAnswer))return null;const a=factorPairs(s);return eqDemo({q:n,id:this.id,title:`How many factors does ${s} have?`,computed:{n:s,answer:r,count:r},terms1:[`1 × ${s}`],terms2:a.map(([i,l])=>`${i} × ${l}`),terms3:o,caption1:`List factor pairs of ${s} in order.`,caption2:"Each pair gives two factors.",caption3:`${s} has ${r} factors in total.`,spoken1:`We find every factor pair of ${w$8(s)}, starting from one times ${w$8(s)}.`,spoken2:"We work up through the pairs until they meet in the middle, so none are missed.",spoken3:`Counting every factor in the list, ${w$8(s)} has ${w$8(r)} factors altogether.`})}return null}},factorMultipleClassify={id:"MD.P5.M02.classify.v1",microSkillId:"MD.P5.M02",forms:["factor_multiple_classify"],generate(n){const t=normaliseStem(n.stem);let e=t.match(/^Is (\d+) a factor or a multiple of (\d+)\?/);if(e){const s=Number(e[1]),o=Number(e[2]);if(s===o)return null;const r=n.correctAnswer.trim().toLowerCase();let a=null;if(s<o&&o%s===0?a="factor":s>o&&s%o===0&&(a="multiple"),!a||(a==="factor"?!r.startsWith("a factor"):!r.startsWith("a multiple")))return null;const i=Math.min(s,o),l=Math.max(s,o),u=l/i,d=factorArrayDemo({q:n,id:this.id,title:`Is ${s} a factor or a multiple of ${o}?`,rows:i,cols:u,answer:s,step1Caption:`Arrange ${l} into ${i} equal rows.`,step2Caption:`${l} ÷ ${i} = ${u}, none left.`,step3Caption:a==="factor"?`${s} is a factor of ${o}.`:`${s} is a multiple of ${o}.`,spoken1:`We compare ${w$8(s)} and ${w$8(o)} by arranging the bigger number, ${w$8(l)}, into ${w$8(i)} equal rows.`,spoken2:`${capitalised(w$8(l))} divided by ${w$8(i)} is ${w$8(u)} exactly, so ${w$8(i)}
71 fits into ${w$8(l)}.`,spoken3:a==="factor"?`The smaller number is the factor, so ${w$8(s)} is a factor of ${w$8(o)}.`:`The bigger number is the multiple, so ${w$8(s)} is a multiple of ${w$8(o)}.`,computedExtra:{a:s,b:o,small:i,big:l}});if(d)return d}if(e=t.match(/in (\d+) equal rows of (\d+), making (\d+) in total/),e){const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]);if(s*o!==r||!/is a factor of/.test(n.correctAnswer))return null;const a=ints$1(n.correctAnswer);if(a.length<2||r%a[0]!==0||a[1]!==r)return null;const i=factorArrayDemo({q:n,id:this.id,title:`${s} rows of ${o} — factor or multiple?`,rows:s,cols:o,answer:r,step1Caption:`${s} rows of ${o} make ${r}.`,step2Caption:`${r} ÷ ${o} = ${s}, none left.`,step3Caption:`${o} is a factor of ${r}.`,spoken1:`The counters show ${w$8(s)} rows of ${w$8(o)}, which make ${w$8(r)}.`,spoken2:`${capitalised(w$8(r))} divided by ${w$8(o)} is ${w$8(s)} exactly, so ${w$8(o)} fits into ${w$8(r)}.`,spoken3:`Because ${w$8(o)} divides ${w$8(r)} exactly, ${w$8(o)} is a factor of ${w$8(r)}.`});if(i)return i}if(e=t.match(/(\d+) stickers are arranged in (\d+) equal rows[\s\S]*is a __ of (\d+)/),e){const s=Number(e[1]);Number(e[3]);const o=Number(e[2]);if(s%o!==0||n.correctAnswer.trim().toLowerCase()!=="factor")return null;const r=s/o,a=factorArrayDemo({q:n,id:this.id,title:`${o} rows share ${s} — factor or multiple?`,rows:o,cols:r,answer:s,step1Caption:`${s} stickers in ${o} equal rows.`,step2Caption:`${s} ÷ ${o} = ${r}, none left.`,step3Caption:`${o} is a factor of ${s}.`,spoken1:`The ${w$8(s)} stickers are arranged in ${w$8(o)} equal rows.`,spoken2:`${capitalised(w$8(s))} divided by ${w$8(o)} is ${w$8(r)} exactly, so the rows are equal.`,spoken3:`Because ${w$8(o)} divides ${w$8(s)} exactly, ${w$8(o)} is a factor of ${w$8(s)}.`});if(a)return a}return null}};function commonMultipleDemo(n,t,e,s,o,r){const a=m=>Math.min(Math.max(Math.ceil(s/m)+1,4),10),i=multiplesRow(e[0],a(e[0])),l=multiplesRow(e[1]??e[0],a(e[1]??e[0])),u=i.indexOf(s),d=l.indexOf(s);return eqDemo({q:n,id:t,title:o,computed:{answer:s,common:s,...Object.fromEntries(e.map((m,h)=>[`b${h}`,m]))},terms1:i,terms2:l,terms3:[s],caption1:`Count in ${e[0]}s.`,caption2:`Count in ${e[1]??e[0]}s.`,caption3:r?.caption3??`${s} is in every list: a common multiple.`,spoken1:`First we list the multiples of ${w$8(e[0])}.`,spoken2:`Then we list the multiples of ${e.slice(1).map(w$8).join(" and ")}.`,spoken3:r?.spoken3??`${capitalised(w$8(s))} appears in every list, so it is a common multiple.`,highlight1:u>=0?[u]:void 0,highlight2:d>=0?[d]:void 0})}const commonMultipleFind={id:"MD.P5.M02.common-multiple.v1",microSkillId:"MD.P5.M02",forms:["common_multiple_find"],generate(n){const t=normaliseStem(n.stem);let e=t.match(/common multiple of (\d+), (\d+) and (\d+)\?$/),s=e?[Number(e[1]),Number(e[2]),Number(e[3])]:null;if(s||(e=t.match(/common multiple of (\d+) and (\d+)\?$/),e&&(s=[Number(e[1]),Number(e[2])])),s){const o=Number(n.correctAnswer);if(!Number.isInteger(o)||s.some(r=>o%r!==0))return null;for(const r of n.options??[]){const a=Number(r);if(Number.isInteger(a)&&a!==o&&s.every(i=>a%i===0))return null}return commonMultipleDemo(n,this.id,s,o,`A common multiple of ${s.join(", ")}`)}if(e=t.match(/common multiple of (\d+) and (\d+) that is between (\d+) and (\d+)/),e){const o=Number(e[1]),r=Number(e[2]),a=Number(e[3]),i=Number(e[4]),l=lcm(o,r),u=Math.ceil((a+1)/l)*l;return u>=i||u!==Number(n.correctAnswer)?null:commonMultipleDemo(n,this.id,[o,r],u,`A common multiple of ${o} and ${r}`,{caption3:`${u} is in both lists AND is between ${a} and ${i}.`,spoken3:`${capitalised(w$8(u))} is a common multiple, and it also sits between ${w$8(a)} and ${w$8(i)}, so it fits both clues.`})}if(e=t.match(/trays of (\d+) \w+ or trays of (\d+) \w+ with none left over/),e){const o=Number(e[1]),r=Number(e[2]),a=lcm(o,r);return a!==Number(n.correctAnswer)?null:commonMultipleDemo(n,this.id,[o,r],a,`Smallest number that fits ${o}s and ${r}s`)}if(e=t.match(/every (\d+) seconds[\s\S]*every (\d+) seconds/),e){const o=Number(e[1]),r=Number(e[2]),a=lcm(o,r);return a!==Number(n.correctAnswer)?null:commonMultipleDemo(n,this.id,[o,r],a,`When do ${o}s and ${r}s next meet?`,{caption3:`${a} is shared — next together after ${a} secs.`,spoken3:`${capitalised(w$8(a))} is the first number in both lists, so they next reset together after ${w$8(a)} seconds.`})}return null}};function commonFactorDemo(n,t,e,s,o,r,a,i){return eqDemo({q:n,id:t,title:r,computed:{a:e,b:s,answer:o},terms1:divisorsOf(e),terms2:divisorsOf(s),terms3:divisorsOf(e).filter(l=>s%l===0),caption1:`List the factors of ${e}.`,caption2:`List the factors of ${s}.`,caption3:a,spoken1:`We list every factor of ${w$8(e)}.`,spoken2:`Then we list every factor of ${w$8(s)}.`,spoken3:i}
71)}const commonFactorFind={id:"MD.P5.M02.common-factor.v1",microSkillId:"MD.P5.M02",forms:["common_factor_find"],generate(n){const t=normaliseStem(n.stem);let e=t.match(/common factor of (\d+) and (\d+)\?$/);if(e){const s=Number(e[1]),o=Number(e[2]),r=Number(n.correctAnswer);if(!Number.isInteger(r)||s%r!==0||o%r!==0)return null;for(const f of n.options??[]){const g=Number(f);if(Number.isInteger(g)&&g!==r&&s%g===0&&o%g===0)return null}const a=divisorsOf(s),i=divisorsOf(o),l=a.filter(f=>o%f===0),u=[...a,"|",...i],d=[];a.forEach((f,g)=>{l.includes(f)&&d.push(g)});const m=a.length+1;i.forEach((f,g)=>{l.includes(f)&&d.push(m+g)});const h=l.indexOf(r);return eqDemo({q:n,id:this.id,title:`A common factor of ${s} and ${o}`,computed:{a:s,b:o,answer:r},terms1:a,terms2:u,terms3:l,caption1:`List the factors of ${s}.`,caption2:"Both lists together — matches highlighted.",caption3:`${r} is one of the shared factors.`,spoken1:`We list every factor of ${w$8(s)}.`,spoken2:`Now we put the factors of ${w$8(o)} alongside them and mark every number that appears in both lists.`,spoken3:`${capitalised(w$8(r))} is marked in both lists, so it is a common factor of ${w$8(s)} and ${w$8(o)}.`,highlight2:d,highlight3:h>=0?[h]:void 0})}if(e=t.match(/(\d+) red \w+ and (\d+) blue \w+[\s\S]*longest row length/),e){const s=Number(e[1]),o=Number(e[2]),r=gcd$3(s,o);if(r!==Number(n.correctAnswer))return null;const a=divisorsOf(s),i=divisorsOf(o),l=a.filter(d=>o%d===0),u=l.indexOf(r);return eqDemo({q:n,id:this.id,title:`Longest equal row for ${s} and ${o}`,computed:{a:s,b:o,answer:r},terms1:a,terms2:i,terms3:l,caption1:`Every row length must divide ${s} exactly: factors of ${s}.`,caption2:`It must also divide ${o} exactly: factors of ${o}.`,caption3:`${r} is the longest length that works for both.`,spoken1:`Every row must use the same number of beads with none left over, so we list every factor of ${w$8(s)}.`,spoken2:`The rows must also divide ${w$8(o)} exactly, so we list every factor of ${w$8(o)} too.`,spoken3:`${capitalised(w$8(r))} is the biggest number that divides both, so it is the longest row length that works for both colours.`,highlight3:u>=0?[u]:void 0})}if(e=t.match(/largest number that is a factor of both (\d+) and (\d+)/),e){const s=Number(e[1]),o=Number(e[2]),r=gcd$3(s,o);return r!==Number(n.correctAnswer)?null:commonFactorDemo(n,this.id,s,o,r,`Largest common factor of ${s} and ${o}`,`${r} is the biggest factor in both lists.`,`${capitalised(w$8(r))} is the largest factor shared by ${w$8(s)} and ${w$8(o)}, so it is the biggest common factor.`)}if(e=t.match(/factor of (\d+) but not a factor of (\d+)\?$/),e){const s=Number(e[1]),o=Number(e[2]),r=Number(n.correctAnswer);if(!Number.isInteger(r)||s%r!==0||o%r===0)return null;for(const a of n.options??[]){const i=Number(a);if(Number.isInteger(i)&&i!==r&&s%i===0&&o%i!==0)return null}return eqDemo({q:n,id:this.id,title:`A factor of ${s} but not of ${o}`,computed:{a:s,b:o,answer:r},terms1:divisorsOf(s),terms2:divisorsOf(o),terms3:[r],caption1:`List the factors of ${s}.`,caption2:`List the factors of ${o}.`,caption3:`${r} divides ${s} but not ${o}.`,spoken1:`We list every factor of ${w$8(s)}.`,spoken2:`Then every factor of ${w$8(o)}.`,spoken3:`${capitalised(w$8(r))} is in the ${w$8(s)} list but not the ${w$8(o)} list, so it is a factor of ${w$8(s)} only.`})}if(e=t.match(/How many common factors do (\d+) and (\d+) have/),e){const s=Number(e[1]),o=Number(e[2]),r=divisorsOf(s),a=divisorsOf(o),i=r.filter(m=>o%m===0);if(i.length!==Number(n.correctAnswer))return null;const l=[...r,"|",...a],u=[];r.forEach((m,h)=>{i.includes(m)&&u.push(h)});const d=r.length+1;return a.forEach((m,h)=>{i.includes(m)&&u.push(d+h)}),eqDemo({q:n,id:this.id,title:`How many common factors do ${s} and ${o} share?`,computed:{a:s,b:o,answer:i.length,count:i.length},terms1:r,terms2:l,terms3:i,caption1:`List the factors of ${s}.`,caption2:"Both lists together — matches highlighted.",caption3:`${i.length} shared factors: the common factors.`,spoken1:`We list every factor of ${w$8(s)}.`,spoken2:`Now we put the factors of ${w$8(o)} alongside them and mark every number that appears in both lists.`,spoken3:`The numbers marked in both lists are the common factors — there are ${w$8(i.length)} of them.`,highlight2:u})}return null}},contextReasoning$2={id:"MD.P5.M02.context.v1",microSkillId:"MD.P5.M02",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem);let e=t.match(/(\d+) chairs set out in equal rows of (\d+)\. How many rows/)??t.match(/(\d+) stickers are shared equally into bags of (\d+)\. How many bags/);if(e){const s=Number(e[1]),o=Number(e[2]);if(o<1||s%o!==0)return null;const r=s/o;if(r!==Number(n.correctAnswer))return null;const a=/chairs/.test(t),i=factorArrayDemo({q:n,id:this.id,title:a?`${s} chairs in rows of ${o}`:`${s} shared into groups of ${o}`,rows:r,cols:o,answer:r,step1Caption:`Share ${s} into groups of ${o}.`,step2Caption:`${s} ÷ ${o} = ${r}, none left.`,step3Caption:a?`${s} ÷ ${o} = ${r} rows.`:`${s} ÷ ${o} = ${r} bags.`,spoken1:`We share ${w$8(s)} equally into groups of ${w$8(o)}.`,spoken2:`${capitalised(w$8(s))}
71 divided by ${w$8(o)} is ${w$8(r)} with nothing left over.`,spoken3:a?`So ${w$8(s)} chairs make ${w$8(r)} equal rows.`:`So ${w$8(s)} fills ${w$8(r)} equal bags.`,computedExtra:{n:s}});if(i)return i}if(e=t.match(/multiple of (\d+), it is a factor of (\d+), and it is between (\d+) and (\d+)/),e){const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]),a=Number(e[4]),i=Number(n.correctAnswer),l=[];for(let m=s;m<=o;m+=s)l.push(m);const u=l.filter(m=>o%m===0),d=u.filter(m=>m>r&&m<a);return d.length!==1||d[0]!==i?null:eqDemo({q:n,id:this.id,title:`Multiple of ${s}, factor of ${o}, between ${r} and ${a}`,computed:{mult:s,fac:o,lo:r,hi:a,answer:i,...Object.fromEntries(l.map((m,h)=>[`m${h}`,m]))},terms1:l,terms2:u,terms3:[i],caption1:`List multiples of ${s} up to ${o}.`,caption2:`Keep the factors of ${o}: ${u.join(", ")}.`,caption3:`Between ${r} and ${a}: only ${i} fits.`,spoken1:`First we list the multiples of ${w$8(s)}.`,spoken2:`Then we keep only the ones that also divide ${w$8(o)} exactly.`,spoken3:`Of those, only ${w$8(i)} sits between ${w$8(r)} and ${w$8(a)}, so it matches every clue.`})}if(e=t.match(/every (\d+) minutes and[\s\S]*every (\d+) minutes/),e){const s=Number(e[1]),o=Number(e[2]),r=lcm(s,o);return r!==Number(n.correctAnswer)?null:commonMultipleDemo(n,this.id,[s,o],r,`When do ${s}s and ${o}s next meet?`,{caption3:`${r} is shared — next together after ${r} mins.`,spoken3:`${capitalised(w$8(r))} is the first number in both lists, so they next leave together after ${w$8(r)} minutes.`})}return null}},MD_P5_M02_GENERATORS=[multipleIdentification,trueFalseCheck,factorIdentification,factorPairFind,factorMultipleClassify,commonMultipleFind,commonFactorFind,contextReasoning$2];function isPrime(n){if(!Number.isInteger(n)||n<2)return!1;for(let t=2;t*t<=n;t++)if(n%t===0)return!1;return!0}function smallestFactor(n){for(let t=2;t*t<=n;t++)if(n%t===0)return t;return n}const struckThrough=n=>String(n).split("").map(t=>`${t}̶`).join("");function packPrime(n){let t=null;for(let e=2;e<=6;e++){const s=Math.floor(n/e);if(s<2||s>7)continue;const o=n-e*s;o<=0||(!t||o<t.leftover||o===t.leftover&&s<t.cols)&&(t={rows:e,cols:s,leftover:o})}return t}function answerValue(n,t){const e=Number(n.correctAnswer);return Number.isFinite(e)?e:t}function buildPrimeArray(n,t,e){const s=packPrime(t);if(!s)return null;const{rows:o,cols:r,leftover:a}=s,i=o*r,l=u=>({type:"array",rows:o,cols:r,n:t,leftover:a,outcome:"prime",...u});return buildExample({generatorId:e,question:n,title:`How do we check if ${t} is a prime number?`,computed:{n:t,rows:o,cols:r,leftover:a,one:1,answer:answerValue(n,t)},steps:[{visual_data:l(),animation:"prime_gather",caption:`Is ${t} prime? Try to make equal rows.`,spoken:`Here are ${numberToWords(t)} counters. We check by trying to split them into equal rows bigger than one. If none work, it has only two factors. Let's try.`},{visual_data:l(),animation:"prime_try",caption:`${o} rows of ${r} — ${a} left over.`,spoken:`${capitalised(numberToWords(o))} rows of ${numberToWords(r)} use ${numberToWords(i)} counters, but ${numberToWords(a)} ${a===1?"is":"are"} left over. However we group them, some are always left over.`},{visual_data:l({conclude_label:`${t} = 1 × ${t} → prime`}),animation:"prime_conclude",caption:`Only 1 row of ${t} works, so ${t} is prime.`,spoken:`The only arrangement is one single row of ${numberToWords(t)}. It has exactly two factors, one and itself, so ${numberToWords(t)} is a prime number.`}]})}function buildCompositeArray(n,t,e,s){const o=t/e,r=a=>({type:"array",rows:e,cols:o,n:t,leftover:0,outcome:"composite",...a});return buildExample({generatorId:s,question:n,title:`How do we check if ${t} is a prime number?`,computed:{n:t,factor:e,cols:o,answer:answerValue(n,t)},steps:[{visual_data:r(),animation:"prime_gather",caption:`Is ${t} prime? Try to make equal rows.`,spoken:`Here are ${numberToWords(t)} counters. Let's see if they can be shared into equal rows bigger than one.`},{visual_data:r(),animation:"prime_try",caption:`${e} rows of ${o} fit exactly.`,spoken:`${capitalised(numberToWords(t))} makes ${numberToWords(e)} equal rows of ${numberToWords(o)}, with none left over.`},{visual_data:r({conclude_label:`${t} = ${e} × ${o} → not prime`}),animation:"prime_conclude",caption:`${t} = ${e} × ${o}, so not prime.`,spoken:`${capitalised(numberToWords(t))} equals ${numberToWords(e)} times ${numberToWords(o)}. It has more than two factors, so it is not a prime number.`}]})}function primeTrialReason(n,t,e,s){return e===2?s?"It is even, so two is a factor.":"It is not even, so two is not a factor.":e===3?s?`Its digits add to ${numberToWords(t)}, a multiple of three, so three is a factor.`:`Its digits add to ${numberToWords(t)}, not a multiple of three, so three is not a factor.`:s?`It ends in ${numberToWords(n%10)}, so five is a factor.`:"It does not end in zero or five, so five is not a factor."}function buildCompositeEquation(n,t,e,s){const o=t/e,r=String(t).split("").reduce((h,f)=>h+Number(f),0),i=[2,3,5].filter(h=>h<e),l=i.map(h=>primeTrialReason(t,r,h,!1)),u=e===2||e===3||e===5?primeTrialReason(t,r,e,!0):"We try seven next.",d=i.length?`Ruled out ${i.join(", ")} — try ${e} next.`:`Does ${e} divide ${t}? Try it.`,m=i.length||e===7?`Let's test ${numberToWords(t)} for factors. ${[...l,u].join(" ")}`:`A prime has no factors except one and itself. Let's test whether ${numberToWords(e)} divides ${numberToWords(t)}.`;return buildExample({generatorId:s,question:n,title:`How do we check if ${t} is a prime number?`,computed:{n:t,factor:e,other:o,answer:answerValue(n,t),...Object.fromEntries(i.map((h,f)=>[`ruled${f}`,h]))},steps:[{visual_data:{type:"equation_display",terms:[`${t}`,"÷",`${e}`,"=","__"]},animation:"prime_divide",caption:d,spoken:m},{visual_data:{type:"equation_display",terms:[`${t}`,"÷",`${e}`,"=",`${o}`]},animation:"prime_divides",caption:`${t} ÷ ${e} = ${o} exactly.`,spoken:`${capitalised(numberToWords(t))} divided by ${numberToWords(e)} is ${numberToWords(o)}, with nothing left over — so ${numberToWords(e)} is a factor.`},{visual_data:{type:"equation_display",terms:[`${t}`,"=",`${e} × ${o}`]},animation:"prime_conclude",caption:`${t} = ${e} × ${o}, so not prime.`,spoken:`${capitalised(numberToWords(t))} equals ${numberToWords(e)} times ${numberToWords(o)}. It has factors other than one and itself, so it is not prime.`}]})}
71function buildTwoDemo(n,t){const e=s=>({type:"array",rows:2,cols:1,n:2,leftover:0,outcome:"prime",...s});return buildExample({generatorId:t,question:n,title:"Is 2 a prime number?",computed:{n:2,one:1,six:6,three:3,answer:answerValue(n,2)},steps:[{visual_data:e(),animation:"prime_gather",caption:"Is 2 prime? Look at its factors.",spoken:"Here are two counters. Two is the only even number with just two factors — let's see why."},{visual_data:e(),animation:"prime_try",caption:"2 = 1 × 2: only one row fits.",spoken:"Two is one times two, so it only makes one row of two — there is no smaller number that splits it evenly."},{visual_data:{type:"equation_display",terms:["2","=","1 × 2",",","6","=","2 × 3"]},animation:"prime_conclude",caption:"2 has only one row; a bigger even number like 6 splits into 2s.",spoken:"A bigger even number such as six always splits into equal rows of two, but two itself cannot make any smaller row — so two is the only even prime number."}]})}function buildDefinitionDemo(n,t){const e=packPrime(7);if(!e)return null;const{rows:s,cols:o,leftover:r}=e,a=i=>({type:"array",rows:s,cols:o,n:7,leftover:r,outcome:"prime",...i});return buildExample({generatorId:t,question:n,title:"How many factors does a prime number have?",computed:{example:7,one:1,two:2,answer:2},steps:[{visual_data:a(),animation:"prime_gather",caption:"How many factors does a prime have?",spoken:"Let's use seven, a prime number, to see how many factors a prime has."},{visual_data:a(),animation:"prime_try",caption:"7 can't make equal rows — only 1 × 7.",spoken:"Seven cannot be shared into equal rows bigger than one — there is always one left over."},{visual_data:a({conclude_label:"factors of 7: 1 and 7"}),animation:"prime_conclude",caption:"A prime has exactly 2 factors: 1 and itself.",spoken:"So the only numbers that divide seven are one and seven. A prime number has exactly two factors, one and itself."}]})}function buildOneIsNotPrimeDemo(n,t){return buildExample({generatorId:t,question:n,title:"Is 1 a prime number?",computed:{one:1,answer:answerValue(n,1)},steps:[{visual_data:{type:"equation_display",terms:["prime","=","2 factors"]},animation:"define",caption:"A prime number needs exactly two different factors.",spoken:"A prime number must have exactly two different factors: one, and itself."},{visual_data:{type:"equation_display",terms:["Factors of 1","=","1"]},animation:"list_factors",caption:"Factors of 1: just 1.",spoken:"The only number that divides one is one itself, so it has only one factor."},{visual_data:{type:"equation_display",terms:["1 factor","≠","2 factors"]},animation:"conclude",caption:"1 has one factor, not two — it is not prime.",spoken:"One factor is not two factors, so one is not a prime number."}]})}function renderNumberDemo(n,t,e,s){if(t===1)return e?null:buildOneIsNotPrimeDemo(n,s);if(!Number.isInteger(t)||t<2)return null;const o=isPrime(t);if(o!==e)return null;if(t===2)return buildTwoDemo(n,s);if(o)return t>25?null:buildPrimeArray(n,t,s);const r=smallestFactor(t);return t<=16?buildCompositeArray(n,t,r,s):buildCompositeEquation(n,t,r,s)}const YES_NO_PATTERNS=[/\bIs (\d+) a prime number/i,/whether (\d+) is a prime number/i,/True or false\? (\d+) is a prime number/i,/\bIs (\d+) shaded/i],isPrimeYesNo={id:"MD.P5.M03.is-prime.v1",microSkillId:"MD.P5.M03",forms:["prime_identification"],generate(n){let t=null;for(const o of YES_NO_PATTERNS)if(t=n.stem.match(o),t)break;if(!t)return null;const e=Number(t[1]),s=/^(yes|true)/i.test(n.correctAnswer.trim());return renderNumberDemo(n,e,s,this.id)}},whichPrimeSelect={id:"MD.P5.M03.which-prime.v1",microSkillId:"MD.P5.M03",forms:["prime_identification","context_reasoning"],generate(n){const t=Number((n.correctAnswer??"").trim());return!Number.isInteger(t)||!(n.options??[]).map(s=>Number(s)).includes(t)?null:renderNumberDemo(n,t,isPrime(t),this.id)}};function buildNextPrimeDemo(n,t,e){const s=[];let o=t+1;for(;s.length<=6&&!isPrime(o);)s.push(o),o++;const r=o;if(s.length>6||r!==answerValue(n,NaN))return null;const a=s.map(m=>{const h=smallestFactor(m);return{n:m,f:h,co:m/h}}),i=[];a.forEach((m,h)=>{h>0&&i.push(","),i.push(`${m.n} = ${m.f} × ${m.co}`)});const l=a.map(m=>m.n).join(", "),u=a.map(m=>`${capitalised(numberToWords(m.n))} equals ${numberToWords(m.f)} times ${numberToWords(m.co)}`).join(". "),d={after:t,answer:r};return a.forEach((m,h)=>{d[`e${h}n`]=m.n,d[`e${h}f`]=m.f,d[`e${h}co`]=m.co}),buildExample({generatorId:e,question:n,title:`The next prime number after ${t}`,computed:d,steps:[{visual_data:{type:"equation_display",terms:[`${t}`,"→","?"]},animation:"search",caption:`Test each number after ${t} for a smaller factor.`,spoken:`We check the numbers straight after ${numberToWords(t)}, one at a time, until we find one with no smaller factor.`},{visual_data:{type:"equation_display",terms:i.length?i:[r]},animation:"eliminate",caption:a.length?`${l} each split into two smaller factors.`:`${t+1} has no smaller factor.`,spoken:a.length?`${u}. Each of those has a smaller factor, so none of them are prime.`:`We check ${numberToWords(t+1)} straight away.`},{visual_data:{type:"equation_display",terms:[`${r}`]},animation:"conclude",caption:`${r} has no smaller factor: it is the next prime.`,spoken:`${capitalised(numberToWords(r))} has no smaller factor, so it is the next prime number after ${numberToWords(t)}.`}]})}const candidateElimination={id:"MD.P5.M03.candidate-elim.v1",microSkillId:"MD.P5.M03",forms:["prime_identification"],generate(n){const t=(n.options??[]).map(d=>Number(d));if(t.length<3||t.some(d=>!Number.isInteger(d)||d<2))return null;const e=Number((n.correctAnswer??"").trim());if(!t.includes(e)||!isPrime(e))return null;const s=t.filter(d=>d!==e);if(s.length<2||s.some(d=>isPrime(d)))return null;const o=s.map(d=>{const m=smallestFactor(d);return{n:d,f:m,co:d/m}}),r=[];t.forEach((d,m)=>{m>0&&r.push(","),r.push(d)});const a=[];o.forEach((d,m)=>{m>0&&a.push(","),a.push(`${d.n} = ${d.f} × ${d.co}`)});const i=o.map(d=>d.n).join(", "),l=o.map(d=>`${capitalised(numberToWords(d.n))} equals ${numberToWords(d.f)} times ${numberToWords(d.co)}`).join(". "),u={answer:e};return o.forEach((d,m)=>{u[`c${m}n`]=d.n,u[`c${m}f`]=d.f,u[`c${m}co`]=d.co}),buildExample({generatorId:this.id,question:n,title:`Which of ${t.join(", ")} is prime?`,computed:u,steps:[{visual_data:{type:"equation_display",terms:r},animation:"list_candidates",caption:"Test each number for a smaller factor.",spoken:"We check every number here to see whether a smaller number divides it exactly."},{visual_data:{type:"equation_display",terms:a},animation:"eliminate",caption:`${i} each split into two smaller factors.`,spoken:`${l}. Each of these has a smaller factor, so none of them are prime.`},{visual_data:{type:"equation_display",terms:[`${e}`]},animation:"conclude",caption:`${e} has no smaller factor, so it is prime.`,spoken:`Only ${numberToWords(e)} is left with no smaller factor, so ${numberToWords(e)} is the prime number.`}]})}},typedPrime={id:"MD.P5.M03.typed.v1",microSkillId:"MD.P5.M03",forms:["symbolic_numeric_entry","context_reasoning"],generate(n){if(/how many factors does a prime/i.test(n.stem))return Number(n.correctAnswer)!==2?null:buildDefinitionDemo(n,this.id);const t=normaliseStem(n.stem).match(/comes (?:straight )?after (\d+)/i);if(t){const s=buildNextPrimeDemo(n,Number(t[1]),this.id);if(s)return s}if(/how many/i.test(n.stem))return null;const e=Number((n.correctAnswer??"").trim());return!Number.isInteger(e)||e<2?null:renderNumberDemo(n,e,isPrime(e),this.id)}},COUNT_PRIMES_RANGE=/How many prime numbers are there between (\d+) and (\d+)\?/i,countPrimesInRange={id:"MD.P5.M03.count-range.v1",microSkillId:"MD.P5.M03",forms:["context_reasoning"],generate(n){const t=n.stem.match(COUNT_PRIMES_RANGE);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(!Number.isInteger(e)||!Number.isInteger(s)||s-e<2||s-e>12)return null;const o=[];for(let u=e+1;u<s;u++)o.push(u);const r=o.filter(isPrime),a=r.length;if(a!==answerValue(n,NaN))return null;const i={lo:e,hi:s,answer:a,count:a};r.forEach((u,d)=>{i[`prime${d}`]=u});const l=r.length?r.map(u=>numberToWords(u)).join(", "):"";return buildExample({generatorId:this.id,question:n,title:`How many primes between ${e} and ${s}?`,computed:i,steps:[{visual_data:{type:"equation_display",terms:o,fit:!0},animation:"list_candidates",caption:`Test every number between ${e} and ${s} for a smaller factor.`,spoken:`We check every number between ${numberToWords(e)} and ${numberToWords(s)}, one at a time, for a smaller factor.`},{visual_data:{type:"equation_display",terms:o.map(u=>{if(isPrime(u))return u;if(u%2===0)return struckThrough(u);const d=smallestFactor(u);return`${struckThrough(u)}=${d}×${u/d}`}),highlight:r.length?o.map((u,d)=>isPrime(u)?d:-1).filter(u=>u>=0):void 0,fit:!0},animation:"mark_primes",caption:r.length?"Cross out every number with a smaller factor.":"Every number is crossed out: all have a smaller factor.",spoken:r.length?`We cross out every number that has a smaller factor, leaving only ${l}
71 with none.`:"Every number in the range has a smaller factor."},{visual_data:{type:"equation_display",terms:[`${a}`]},animation:"conclude",caption:`${a} prime number${a===1?"":"s"} between ${e} and ${s}.`,spoken:`${capitalised(numberToWords(a))} number${a===1?"":"s"} in that range ${a===1?"is":"are"} prime.`}]})}},contextClaim={id:"MD.P5.M03.context-claim.v1",microSkillId:"MD.P5.M03",forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem),e=n.correctAnswer??"";let s=t.match(/(\d+) buns/i);if(s&&/prime/i.test(e)&&!/not|composite/i.test(e)){const o=Number(s[1]);if(isPrime(o))return renderNumberDemo(n,o,!0,this.id)}if(s=t.match(/says (\d+) (?:must be|is) prime/i),s&&/^\d+ = \d+ × \d+/.test(e.trim())){const o=Number(s[1]);if(!isPrime(o))return renderNumberDemo(n,o,!1,this.id)}if(s=t.match(/says (\d+) cannot be prime/i),s&&/is prime/i.test(e)){const o=Number(s[1]);if(isPrime(o))return renderNumberDemo(n,o,!0,this.id)}if(s=t.match(/prime number between (\d+) and (\d+)(?:, but I am not ([^.]+))?/i),s){const o=Number(s[1]),r=Number(s[2]),a=new Set((s[3]?.match(/\d+/g)??[]).map(Number)),i=[];for(let d=o+1;d<r;d++)a.has(d)||i.push(d);const l=i.filter(d=>isPrime(d)),u=i.filter(d=>!isPrime(d));if(l.length===1&&u.length>=1&&u.length<=6){const d=l[0];if(d===Number(e)){const m=u.map(y=>{const E=smallestFactor(y);return{n:y,f:E,co:y/E}}),h=[];i.forEach((y,E)=>{E>0&&h.push(","),h.push(y)});const f=[];m.forEach((y,E)=>{E>0&&f.push(","),f.push(`${y.n} = ${y.f} × ${y.co}`)});const g=m.map(y=>y.n).join(", "),b=m.map(y=>`${capitalised(numberToWords(y.n))} equals ${numberToWords(y.f)} times ${numberToWords(y.co)}`).join(". "),x={lo:o,hi:r,answer:d};return i.forEach((y,E)=>x[`cand${E}`]=y),m.forEach((y,E)=>{x[`f${E}n`]=y.n,x[`f${E}f`]=y.f,x[`f${E}co`]=y.co}),buildExample({generatorId:this.id,question:n,title:`A prime between ${o} and ${r}`,computed:x,steps:[{visual_data:{type:"equation_display",terms:h},animation:"search",caption:`Test each number between ${o} and ${r} for a smaller factor.`,spoken:`We check every number between ${numberToWords(o)} and ${numberToWords(r)} to see whether a smaller number divides it exactly.`},{visual_data:{type:"equation_display",terms:f},animation:"eliminate",caption:`${g} each split into two smaller factors.`,spoken:`${b}. Each of these has a smaller factor, so none of them are prime.`},{visual_data:{type:"equation_display",terms:[`${d}`]},animation:"conclude",caption:`${d} has no smaller factor, so it is prime.`,spoken:`Only ${numberToWords(d)} is left with no smaller factor, so ${numberToWords(d)} is the number.`}]})}}}return null}},MD_P5_M03_GENERATORS=[isPrimeYesNo,whichPrimeSelect,candidateElimination,countPrimesInRange,typedPrime,contextClaim],w$7=n=>numberToWordsLong(n),W$1=n=>capitalised(numberToWordsLong(n)),countNoun=n=>n===6?"sixes":`${w$7(n)}s`,ARRAY_MAX=9;function buildSquare(n,t,e,s){if(!Number.isInteger(t)||t<2||t>12)return null;const o=t*t;if(o!==answerInt(n))return null;if(t<=ARRAY_MAX){const m={type:"array",rows:t,cols:t};return buildExample({generatorId:e,question:n,title:s,computed:{n:t,square:o,answer:o},steps:[{visual_data:m,animation:"build_rows",caption:`${t} squared: ${t} rows of ${t}.`,spoken:`${W$1(t)} squared. The small two means the number times itself, so we make a square with ${w$7(t)} rows of ${w$7(t)}.`},{visual_data:m,animation:"skip_count",caption:`Skip-count the rows in ${t}s.`,spoken:`We skip-count the rows in ${countNoun(t)} to add up the whole square.`},{visual_data:m,animation:"conclude",caption:`${t} × ${t} = ${o}.`,spoken:`${W$1(t)} ${countNoun(t)} make ${w$7(o)}. So ${w$7(t)} squared is ${w$7(o)}.`}]})}const r=t*10,a=t-10,i=t*a,l={type:"equation_display",terms:[`${t} × ${t}`,"=","__"]},u={type:"equation_display",terms:[`${t} × 10`,"+",`${t} × ${a}`,"=","__"]},d={type:"equation_display",terms:[`${r}`,"+",`${i}`,"=",`${o}`]};return buildExample({generatorId:e,question:n,title:s,computed:{n:t,tens:r,rem:a,remProduct:i,square:o,answer:o},steps:[{visual_data:l,animation:"show_square",caption:`${t} squared means ${t} × ${t}.`,spoken:`${W$1(t)} squared means ${w$7(t)} times ${w$7(t)} — the number times itself.`},{visual_data:u,animation:"partition",caption:`${t} × 10 = ${r}, ${t} × ${a} = ${i}.`,spoken:`We split the second ${w$7(t)} into ten and ${w$7(a)}. ${W$1(t)} tens is ${w$7(r)}, and ${w$7(t)} ${a===1?"one":w$7(a)}s is ${w$7(i)}.`},{visual_data:d,animation:"recombine",caption:`${r} + ${i} = ${o}.`,spoken:`Adding the parts, ${w$7(r)} plus ${w$7(i)} is ${w$7(o)}. So ${w$7(t)} squared is ${w$7(o)}.`}]})}function buildCube(n,t,e,s){if(!Number.isInteger(t)||t<2||t>12)return null;const o=t*t,r=t*t*t;if(r!==answerInt(n))return null;const a={type:"equation_display",terms:[`${t} × ${t} × ${t}`,"=","__"]},i={type:"equation_display",terms:[`${t} × ${t}`,"=",`${o}`]},l={type:"equation_display",terms:[`${t} × ${t} × ${t}`,"=",`${r}`]};return buildExample({generatorId:e,question:n,title:s,computed:{n:t,square:o,cube:r,answer:r},steps:[{visual_data:a,animation:"show_cube",caption:`${t} cubed means ${t} × ${t} × ${t}.`,spoken:`${W$1(t)} cubed. The small three means ${w$7(t)} times itself three times, not times three.`},{visual_data:i,animation:"first_layer",caption:`First ${t} × ${t} = ${o}, one layer.`,spoken:`First we build one layer: ${w$7(t)} times ${w$7(t)} is ${w$7(o)}, a flat square.`},{visual_data:l,animation:"stack_layers",caption:`Stack ${t}: ${t} × ${t} × ${t} = ${r}.`,spoken:`Then we stack ${w$7(t)} of those layers: ${w$7(o)} times ${w$7(t)} is ${w$7(r)}. So ${w$7(t)} cubed is ${w$7(r)}.`}]})}function buildInverseSquare(n,t,e,s){const o=Math.round(Math.sqrt(t));if(o<2||o*o!==t||o!==answerInt(n))return null;const r=o-1,a=r*r;return buildExample({generatorId:e,question:n,title:s,computed:{value:t,root:o,lower:r,lowerSq:a,answer:o},steps:[{visual_data:{type:"equation_display",terms:["__","×","__","=",`${t}`]},animation:"show_target",caption:`Which number times itself makes ${t}?`,spoken:`We need the number that, multiplied by itself, gives ${w$7(t)}.`},{visual_data:{type:"equation_display",terms:[`${r}`,"×",`${r}`,"=",`${a}`]},animation:"try_lower",caption:`${r} × ${r} = ${a}, too small.`,spoken:`${W$1(r)} times ${w$7(r)} is ${w$7(a)}, which is too small.`},{visual_data:{type:"equation_display",terms:[`${o}`,"×",`${o}`,"=",`${t}`]},animation:"found",caption:`${o} × ${o} = ${t}, so it is ${o}.`,spoken:`${W$1(o)} times ${w$7(o)} is ${w$7(t)}. So the number is ${w$7(o)}.`}]})}function buildInverseCube(n,t,e,s){const o=Math.round(Math.cbrt(t));if(o<2||o*o*o!==t||o!==answerInt(n))return null;const r=o-1,a=r*r*r;return buildExample({generatorId:e,question:n,title:s,computed:{value:t,root:o,lower:r,lowerCube:a,answer:o},steps:[{visual_data:{type:"equation_display",terms:["__","×","__","×","__","=",`${t}`]},animation:"show_target",caption:`Times itself three times makes ${t}?`,spoken:`We need the number that, multiplied by itself three times, gives ${w$7(t)}.`},{visual_data:{type:"equation_display",terms:[`${r}`,"×",`${r}`,"×",`${r}`,"=",`${a}`]},animation:"try_lower",caption:`${r} × ${r} × ${r} = ${a}, too small.`,spoken:`${W$1(r)} times ${w$7(r)} times ${w$7(r)} is ${w$7(a)}, too small.`},{visual_data:{type:"equation_display",terms:[`${o}`,"×",`${o}`,"×",`${o}`,"=",`${t}`]},animation:"found",caption:`${o} × ${o} × ${o} = ${t}, so it is ${o}.`,spoken:`${W$1(o)} times ${w$7(o)} times ${w$7(o)} is ${w$7(t)}. So the number is ${w$7(o)}.`}]})}const SQ_WORD=/(\d+) squared/,SQ_SUP=/(\d+)²/;function squareBaseFromStem(n){const t=normaliseStem(n),e=t.match(SQ_WORD)??t.match(SQ_SUP);return e?Number(e[1]):null}const squareEvaluate={id:"MD.P5.M04.square-evaluate.v1",microSkillId:"MD.P5.M04",forms:["square_number_evaluate","equation_completion"],generate(n){const t=squareBaseFromStem(n.stem);return t===null?null:buildSquare(n,t,this.id,`What is ${t} squared?`)}},CU_WORD=/(\d+) cubed/,CU_SUP=/(\d+)³/;function cubeBaseFromStem(n){const t=normaliseStem(n),e=t.match(CU_WORD)??t.match(CU_SUP);return e?Number(e[1]):null}const cubeEvaluate={id:"MD.P5.M04.cube-evaluate.v1",microSkillId:"MD.P5.M04",forms:["cube_number_evaluate"],generate(n){const t=cubeBaseFromStem(n.stem);return t===null?null:buildCube(n,t,this.id,`What is ${t} cubed?`)}},PATIO=/(\d+) tiles long and (\d+) tiles wide/,DICE=/rolls an? (\d+)/,squareContext={id:"MD.P5.M04.square-context.v1",microSkillId:"MD.P5.M04",forms:["context_calculation"],generate(n){const t=normaliseStem(n.stem),e=t.match(PATIO);if(e&&e[1]===e[2])return buildSquare(n,Number(e[1]),this.id,`A ${e[1]} by ${e[1]} square — how many?`);if(/multiplied by itself/.test(t)){const s=t.match(DICE);if(s)return buildSquare(n,Number(s[1]),this.id,`${s[1]} multiplied by itself.`)}return null}},LAYERS=/(\d+) layers of cubes\. Each layer is (\d+) rows of (\d+) cubes/,EDGE=/(\d+) small cubes along each edge/,cubeContext={id:"MD.P5.M04.cube-context.v1",microSkillId:"MD.P5.M04",forms:["context_calculation","context_reasoning"],generate(n){const t=normaliseStem(n.stem),e=t.match(LAYERS);if(e&&e[1]===e[2]&&e[2]===e[3])return buildCube(n,Number(e[1]),this.id,`A ${e[1]} by ${e[1]} by ${e[1]} box — how many cubes?`);const s=t.match(EDGE);return s?buildCube(n,Number(s[1]),this.id,`A cube ${s[1]} small cubes along each edge.`):null}},INV_CUBE=/itself three times gives (\d+)/,INV_SQUARE=/itself (?:gives|and gets) (\d+)/,INV_BULBS=/uses (\d+) bulbs/,inverseRoot={id:"MD.P5.M04.inverse-root.v1",microSkillId:"MD.P5.M04",forms:["symbolic_numeric_entry","context_reasoning"],generate(n){const t=normaliseStem(n.stem),e=t.match(INV_CUBE);if(e){const o=Number(e[1]);return buildInverseCube(n,o,this.id,`What number cubed makes ${o}?`)}const s=t.match(INV_SQUARE);if(s){const o=Number(s[1]);return buildInverseSquare(n,o,this.id,`What number squared makes ${o}?`)}if(/square/.test(t)){const o=t.match(INV_BULBS);if(o){const r=Number(o[1]);return buildInverseSquare(n,r,this.id,`A square of ${r} — how many in each row?`)}}return null}},notationChoice={id:"MD.P5.M04.notation-choice.v1",microSkillId:"MD.P5.M04",forms:["symbolic_choice"],generate(n){const t=squareBaseFromStem(n.stem);if(t===null||t<2||t>ARRAY_MAX)return null;const e=n.correctAnswer.trim(),s=`${t} × ${t}`,o=new RegExp(`^${t}\\s+multiplied by itself$`,"i");if(e!==s&&!o.test(e))return null;const r=t*t,a={type:"array",rows:t,cols:t};return buildExample({generatorId:this.id,question:n,title:`What does ${t} squared mean?`,computed:{n:t,square:r,answer:r},steps:[{visual_data:a,animation:"build_rows",caption:"The small two means times itself.",spoken:"The small two after a number means multiply that number by itself."},{visual_data:a,animation:"skip_count",caption:`${t} squared is ${t} rows of ${t}.`,spoken:`${W$1(t)} squared makes a square: ${w$7(t)} rows of ${w$7(t)}.`},{visual_data:a,animation:"conclude",caption:`${t} squared means ${t} × ${t}
71.`,spoken:`So ${w$7(t)} squared is the same as ${w$7(t)} times ${w$7(t)} — the number times itself.`}]})}},squareCubeIdentify={id:"MD.P5.M04.identify.v1",microSkillId:"MD.P5.M04",forms:["square_cube_identify"],generate(n){const t=answerInt(n);if(t===null||t<1)return null;const e=normaliseStem(n.stem),s=Math.round(Math.sqrt(t)),o=Math.round(Math.cbrt(t)),r=s>=2&&s*s===t,a=o>=2&&o*o*o===t;if(/both/i.test(e)){if(!r||!a)return null;const i=(n.options??[]).map(Number).filter(f=>Number.isInteger(f)&&f>=1);if(i.length<2)return null;const l=i.filter(f=>{const g=Math.round(Math.sqrt(f));return g>=2&&g*g===f}),u=l.filter(f=>{const g=Math.round(Math.cbrt(f));return g>=2&&g*g*g===f});if(u.length!==1||u[0]!==t)return null;const d=[];i.forEach((f,g)=>{g>0&&d.push(","),d.push(f)});const m=[];l.forEach((f,g)=>{g>0&&m.push(","),m.push(f)});const h=l.map(f=>w$7(f)).join(" and ");return buildExample({generatorId:this.id,question:n,title:`Which of ${i.join(", ")} is both a square and a cube?`,computed:{value:t,a:s,b:o,answer:t,...Object.fromEntries(i.map((f,g)=>[`opt${g}`,f]))},steps:[{visual_data:{type:"equation_display",terms:d},animation:"list_options",caption:"Check each option: is it a square number?",spoken:"We check each option to see which ones are square numbers."},{visual_data:{type:"equation_display",terms:m},animation:"mark_squares",caption:"Mark the options that are square numbers.",spoken:`The square numbers among the options are ${h}.`},{visual_data:{type:"equation_display",terms:[`${s} × ${s}`,"=",`${t}`,"=",`${o} × ${o} × ${o}`]},animation:"conclude_both",caption:`${t} = ${s} × ${s} = ${o} × ${o} × ${o}.`,spoken:`Of those, only ${w$7(t)} is also a cube number: ${w$7(o)} times ${w$7(o)} times ${w$7(o)}.`}]})}if(/cube number/i.test(e)){if(!a)return null;const i=o*o;return buildExample({generatorId:this.id,question:n,title:`Is ${t} a cube number?`,computed:{value:t,root:o,square:i,answer:t},steps:[{visual_data:{type:"equation_display",terms:[`${o} × ${o} × ${o}`,"=","__"]},animation:"show_cube",caption:"Try a number times itself three times.",spoken:`A cube number is some number multiplied by itself three times. We try ${w$7(o)}.`},{visual_data:{type:"equation_display",terms:[`${o} × ${o}`,"=",`${i}`]},animation:"first_layer",caption:`${o} × ${o} = ${i}.`,spoken:`${W$1(o)} times ${w$7(o)} is ${w$7(i)}.`},{visual_data:{type:"equation_display",terms:[`${i}`,"×",`${o}`,"=",`${t}`]},animation:"conclude",caption:`${i} × ${o} = ${t}, a cube number.`,spoken:`${W$1(i)} times ${w$7(o)} is ${w$7(t)}. So ${w$7(t)} is a cube number.`}]})}if(!r)return null;if(s<=ARRAY_MAX){const i={type:"array",rows:s,cols:s};return buildExample({generatorId:this.id,question:n,title:`Is ${t} a square number?`,computed:{value:t,root:s,answer:t},steps:[{visual_data:i,animation:"build_rows",caption:"A square number is a number times itself.",spoken:"A square number is what you get when a whole number is multiplied by itself."},{visual_data:i,animation:"skip_count",caption:`Try ${s} rows of ${s}.`,spoken:`We try ${w$7(s)} rows of ${w$7(s)} and skip-count them.`},{visual_data:i,animation:"conclude",caption:`${s} × ${s} = ${t}, a square number.`,spoken:`${W$1(s)} ${w$7(s)}s make ${w$7(t)}. So ${w$7(t)} is a square number.`}]})}return null}},COMPARE=/Which is larger, (\d+) (squared|cubed) or (\d+) (squared|cubed)\?/;function powVal(n,t){return t==="cubed"?n*n*n:n*n}function powExpr(n,t){return t==="cubed"?`${n} × ${n} × ${n}`:`${n} × ${n}`}const comparison$1={id:"MD.P5.M04.compare.v1",microSkillId:"MD.P5.M04",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(COMPARE);if(!t)return null;const[,e,s,o,r]=t,a=Number(e),i=Number(o),l=powVal(a,s),u=powVal(i,r);if(l===u)return null;const d=l>u?`${a} ${s}`:`${i} ${r}`;if(n.correctAnswer.trim()!==d)return null;const m=l>u?`${w$7(a)} ${s}`:`${w$7(i)} ${r}`,h=(g,b)=>b==="cubed"?`${w$7(g)} times ${w$7(g)} times ${w$7(g)}`:`${w$7(g)} times ${w$7(g)}`,f=l>u?">":"<";return buildExample({generatorId:this.id,question:n,title:`Which is larger, ${a} ${s} or ${i} ${r}?`,computed:{a,b:i,leftVal:l,rightVal:u,answer:Math.max(l,u)},steps:[{visual_data:{type:"equation_display",terms:[powExpr(a,s),"=",`${l}`]},animation:"first",caption:`${a} ${s} = ${l}.`,spoken:`To compare, we work out each one. ${W$1(a)} ${s} means ${h(a,s)}, which is ${w$7(l)}.`},{visual_data:{type:"equation_display",terms:[powExpr(i,r),"=",`${u}`]},animation:"second",caption:`${i} ${r} = ${u}
71.`,spoken:`Then ${w$7(i)} ${r} means ${h(i,r)}, which is ${w$7(u)}.`},{visual_data:{type:"comparison",left:l,right:u,symbol:f},animation:"compare",caption:`${d} is larger.`,spoken:`${W$1(Math.max(l,u))} is more than ${w$7(Math.min(l,u))}, so ${m} is larger.`}]})}},SEQ=/begin ([\d, ]+?),? __/,sequence$1={id:"MD.P5.M04.sequence.v1",microSkillId:"MD.P5.M04",forms:["sequence_continue"],generate(n){const t=normaliseStem(n.stem).match(SEQ);if(!t)return null;const e=t[1].split(",").map(i=>Number(i.trim())).filter(i=>Number.isFinite(i));if(e.length<2)return null;for(let i=0;i<e.length;i++)if(e[i]!==(i+1)*(i+1))return null;const s=e.length+1,o=s*s;if(o!==answerInt(n))return null;const r=e.map(i=>`${i}`),a=e.map((i,l)=>`${l+1} × ${l+1}`);return buildExample({generatorId:this.id,question:n,title:`The next square number after ${e[e.length-1]}?`,computed:{root:s,next:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[...r,"__"]},animation:"show_sequence",caption:"Square numbers so far, then what?",spoken:"These are the square numbers so far, and we want the next one."},{visual_data:{type:"equation_display",terms:[...a,"__"]},animation:"reveal_pattern",caption:"Each one is a number times itself.",spoken:"Each square number is a whole number multiplied by itself, counting up one at a time."},{visual_data:{type:"equation_display",terms:[...r,`${o}`]},animation:"conclude",caption:`Next is ${s} × ${s} = ${o}.`,spoken:`The next number is ${w$7(s)} times ${w$7(s)}, which is ${w$7(o)}.`}]})}},SQ_CLAIM=/^(\w+) says (\d+) squared is (\d+)\. Is \w+ correct\?$/,squareClaim={id:"MD.P5.M04.square-claim.v1",microSkillId:"MD.P5.M04",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(SQ_CLAIM);if(!t)return null;const[,e,s,o]=t,r=Number(s),a=Number(o);if(r<2||r>ARRAY_MAX)return null;const i=r*r;if(i===a||!isNegative(n.correctAnswer))return null;const u={type:"array",rows:r,cols:r};return buildExample({generatorId:this.id,question:n,title:`${e} says ${r} squared is ${a}. Right?`,computed:{n:r,real:i,claim:a,answer:i},steps:[{visual_data:u,animation:"build_rows",caption:`${r} squared is ${r} rows of ${r}.`,spoken:`${W$1(r)} squared means ${w$7(r)} rows of ${w$7(r)} — the number times itself, not times two.`},{visual_data:u,animation:"skip_count",caption:`Count the rows in ${r}s.`,spoken:`We skip-count the rows in ${countNoun(r)} to find the real total.`},{visual_data:u,animation:"conclude",caption:`${r} × ${r} = ${i}, not ${a}. No.`,spoken:`${W$1(r)} ${countNoun(r)} make ${w$7(i)}, not ${w$7(a)}. So the claim is wrong.`}]})}},CU_REALLY=/^(\w+) says (\d+) cubed is (\d+)\. What is \d+ cubed really\?$/,cubeReally={id:"MD.P5.M04.cube-really.v1",microSkillId:"MD.P5.M04",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem).match(CU_REALLY);if(!t)return null;const[,e,s,o]=t,r=Number(s),a=Number(o);if(r<2||r>12)return null;const i=r*r,l=r*r*r;return l!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`${e} says ${r} cubed is ${a}. Really?`,computed:{n:r,nsq:i,claim:a,cube:l,answer:l},steps:[{visual_data:{type:"equation_display",terms:[`${r} × ${r}`,"=",`${i}`]},animation:"show_square",caption:`${e} stopped at ${r} × ${r} = ${i}.`,spoken:`${e} only worked out ${w$7(r)} times ${w$7(r)}, which is ${w$7(i)}. That is just squaring.`},{visual_data:{type:"equation_display",terms:[`${i}`,"×",`${r}`,"=","__"]},animation:"one_more",caption:`Cubing needs one more × ${r}.`,spoken:`Cubing needs one more multiply, so we take that answer and multiply by ${w$7(r)} again.`},{visual_data:{type:"equation_display",terms:[`${r} × ${r} × ${r}`,"=",`${l}`]},animation:"conclude",caption:`${r} × ${r} × ${r} = ${l}.`,spoken:`${W$1(r)} times ${w$7(r)} times ${w$7(r)} is ${w$7(l)}. So ${w$7(r)} cubed is really ${w$7(l)}.`}]})}},SAME_CLAIM=/(\d+)² and \d+ × \d+ give different answers/,sameValue={id:"MD.P5.M04.same-value.v1",microSkillId:"MD.P5.M04",forms:["true_false_check"],generate(n){const t=normaliseStem(n.stem).match(SAME_CLAIM);if(!t)return null;const e=Number(t[1]);if(e<2||e>12||!isNegative(n.correctAnswer))return null;const s=e*e;return buildExample({generatorId:this.id,question:n,title:`Do ${e} squared and ${e} × ${e} differ?`,computed:{n:e,nsq:s,answer:s},steps:[{visual_data:{type:"equation_display",terms:[`${e} squared`,"and",`${e} × ${e}`]},animation:"show_both",caption:`Compare ${e} squared and ${e} × ${e}.`,spoken:`We compare ${w$7(e)} squared with ${w$7(e)} times ${w$7(e)}.`},{visual_data:{type:"equation_display",terms:[`${e} × ${e}`,"=",`${s}`]},animation:"evaluate",caption:`${e} squared just means ${e} × ${e}.`,spoken:`${W$1(e)} squared is only another way of writing ${w$7(e)} times ${w$7(e)}.`},{visual_data:{type:"equation_display",terms:[`${s}`,"=",`${s}`]},animation:"conclude",caption:"Same answer — so the claim is wrong.",spoken:`Both give ${w$7(s)}, so they are equal and the poster is wrong.`}]})}},VARIED=/What is (\d+) squared (add|take away) (\d+) squared\?/,variedArithmetic={id:"MD.P5.M04.varied.v1",microSkillId:"MD.P5.M04",forms:["varied_symbolic_calculation"],generate(n){const t=normaliseStem(n.stem).match(VARIED);if(!t)return null;const e=Number(t[1]),s=t[2],o=Number(t[3]);if(e<2||e>12||o<2||o>12)return null;const r=e*e,a=o*o,i=s==="add"?r+a:r-a;if(i!==answerInt(n))return null;const l=s==="add"?"+":"−",u=s==="add"?"plus":"take away";return buildExample({generatorId:this.id,question:n,title:`What is ${e} squared ${s} ${o} squared?`,computed:{a:e,b:o,a2:r,b2:a,result:i,answer:i},steps:[{visual_data:{type:"equation_display",terms:[`${e} squared`,l,`${o} squared`,"=","__"]},animation:"show_start",caption:"Find each square first.",spoken:"First we work out each square on its own, then we combine them."},{visual_data:{type:"equation_display",terms:[`${r}`,l,`${a}`,"=","__"]},animation:"each_square",caption:`${e} squared = ${r}, ${o} squared = ${a}.`,spoken:`${W$1(e)} squared means ${w$7(e)} times ${w$7(e)}, which is ${w$7(r)}. ${W$1(o)} squared means ${w$7(o)} times ${w$7(o)}, which is ${w$7(a)}.`},{visual_data:{type:"equation_display",terms:[`${r}`,l,`${a}`,"=",`${i}`]},animation:"combine",caption:`${r} ${l} ${a} = ${i}.`,spoken:`Now we combine the two squares: ${w$7(r)} ${u} ${w$7(a)} is ${w$7(i)}.`}]})}},MD_P5_M04_GENERATORS=[squareEvaluate,cubeEvaluate,squareContext,cubeContext,inverseRoot,notationChoice,squareCubeIdentify,comparison$1,sequence$1,squareClaim,cubeReally,sameValue,variedArithmetic],MICRO$6="MEA.P7.M04",BASE_MAX=20,HEIGHT_MAX=12;function numericAnswer(n){const t=Number(n.correctAnswer);return Number.isFinite(t)?t:null}function leadingInt(n){const t=n.trim().match(/^(\d+)/);return t?Number(t[1]):null}function article(n){const t=numberToWordsLong(n).trim().toLowerCase().split(/\s+/)[0]??"";return t.startsWith("one")?"a":/^[aeiou]/.test(t)?"an":"a"}function pluraliseUnit(n){return n==="unit"?"units":n}function readDims(n){const t=n.visualData;if(t&&t.type==="triangle_area"){const r=t;return!Number.isInteger(r.base)||!Number.isInteger(r.height)?null:{base:r.base,height:r.height,unit:pluraliseUnit(r.unit_label??"cm"),orientation:r.orientation??"right",otherSide:r.other_side_label,grid:r.show_grid===!0}}const e=normaliseStem(n.stem),s=e.match(/\bbase (?:of )?(\d+)/i),o=e.match(/(?:perpendicular )?\bheight (?:of )?(\d+)/i);return!s||!o?null:{base:Number(s[1]),height:Number(o[1]),unit:/cm/i.test(e)?"cm":"m",orientation:"right",grid:!1}}function triangleVisual(n){const t={type:"triangle_area",base:n.base,height:n.height,unit_label:n.unit,orientation:n.orientation};
71return n.otherSide&&(t.other_side_label=n.otherSide),n.grid&&(t.show_grid=!0),t}function areaWords(n){return Number.isInteger(n)?numberToWordsLong(n):decimalToWords(String(n))}function squareUnitWords(n){const t=n.toLowerCase();return t==="cm"?"square centimetres":t==="m"?"square metres":t==="mm"?"square millimetres":"square units"}function halfRectangleSteps(n,t,e={}){const s=triangleVisual(n),o=e.slantWarn?"Use the perpendicular height, not the slant.":n.grid?"Box the triangle in a rectangle of squares.":`Box it in ${article(n.base)} ${n.base} by ${n.height} rectangle.`,r=`${article(n.base)} ${numberToWordsLong(n.base)} by ${numberToWordsLong(n.height)} rectangle`,a=e.sayNumbers&&!e.slantWarn?`To find a triangle's area, we box it inside a rectangle. The rectangle is as wide as the base and as tall as the perpendicular height — the straight-up height, at a right angle to the base. Here that is ${r}.`:e.slantWarn?"This is our triangle. The slanting side is longer, but the area uses the perpendicular height — the straight-up distance to the base. We box the triangle inside a rectangle exactly that tall.":"This is our triangle. To find its area, we box it inside a rectangle exactly as wide as the base and exactly as tall as the perpendicular height, measured straight up at a right angle to the base.",i=n.grid?"The triangle covers half the squares.":"The triangle is half the rectangle.",l=n.base*n.height,u=n.grid?"squares":squareUnitWords(n.unit),d=`The rectangle's area is the base times the height: ${numberToWordsLong(n.base)} times ${numberToWordsLong(n.height)} is ${numberToWordsLong(l)} ${u}.`,m=e.sayNumbers&&e.calcChoice?`${d} The triangle is half of the rectangle, so we divide by two. So the calculation for the area is ${numberToWordsLong(n.base)} times ${numberToWordsLong(n.height)} divided by two, which is ${areaWords(t)} ${u}.`:e.sayNumbers&&e.halveWarn?`${d} The triangle is only half of it, so we must divide by two — forgetting to halve is the commonest slip. The triangle's area is ${areaWords(t)} ${u}.`:e.sayNumbers?`${d} The triangle is half of the rectangle, so we divide by two. The triangle's area is ${areaWords(t)} ${u}.`:e.halveWarn?"The rectangle's area is the base times the height. The triangle is only half of it, so we must divide by two — forgetting to halve is the commonest slip.":"The rectangle's area is the base times the height. The triangle is half of that, so we multiply the base by the height and then divide by two to get the area.",h=n.orientation==="general"?"The height splits the triangle into two right triangles, each half of its part of the rectangle.":"The rectangle splits along the triangle's slanting edge into two matching triangles of equal size. Our triangle is one of them, so it covers exactly half of the rectangle.";return[{visual_data:s,animation:"setup",caption:o,spoken:a},{visual_data:s,animation:"halves",caption:i,spoken:h},{visual_data:s,animation:"result",caption:`${n.base} × ${n.height} ÷ 2 = ${t}.`,spoken:m}]}const directCompute={id:"MEA.P7.M04.direct-area.v1",microSkillId:MICRO$6,forms:["context_calculation","area_grid_count"],generate(n){const t=readDims(n);if(!t||t.base<1||t.base>BASE_MAX||t.height<1||t.height>HEIGHT_MAX)return null;const e=t.base*t.height/2,s=numericAnswer(n);if(s===null||s!==e)return null;const o=halfRectangleSteps(t,e,{slantWarn:!!t.otherSide,sayNumbers:!0});return buildExample({generatorId:this.id,question:n,title:t.grid?"How many squares does this triangle cover?":`How do we find the area of a triangle with base ${t.base} and height ${t.height}?`,computed:{base:t.base,height:t.height,area:e,rectangle:t.base*t.height,two:2,answer:e},steps:[o[0],o[1],o[2]]})}},SYMBOLIC=/a base of (\d+) cm and a height of (\d+) cm\. which calculation gives its area/i,symbolicChoice={id:"MEA.P7.M04.symbolic-choice.v1",microSkillId:MICRO$6,forms:["symbolic_choice"],generate(n){const t=normaliseStem(n.stem).match(SYMBOLIC);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);if(e<1||e>BASE_MAX||s<1||s>HEIGHT_MAX)return null;const o=e*s/2;if(n.correctAnswer.trim()!==`${e} × ${s} ÷ 2`)return null;const a=halfRectangleSteps({base:e,height:s,unit:"cm",orientation:"right",grid:!1},o,{sayNumbers:!0,calcChoice:!0});return buildExample({generatorId:this.id,question:n,title:"Which calculation finds a triangle's area?",computed:{base:e,height:s,area:o,rectangle:e*s,two:2,answer:o},steps:[a[0],a[1],a[2]]})}},PROBE=/a base of (\d+) (?:cm|m) and a height of (\d+) (?:cm|m)\. what is its area/i,misconceptionProbe={id:"MEA.P7.M04.misconception-probe.v1",microSkillId:MICRO$6,forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=t.match(PROBE);if(!e)return null;const s=Number(e[1]),o=Number(e[2]);if(s<1||s>
71BASE_MAX||o<1||o>HEIGHT_MAX)return null;const r=s*o/2;if(leadingInt(n.correctAnswer)!==r)return null;const a={base:s,height:o,unit:/cm/i.test(t)?"cm":"m",orientation:"right",grid:!1},i=halfRectangleSteps(a,r,{halveWarn:!0,sayNumbers:!0});return buildExample({generatorId:this.id,question:n,title:"What is the area of this triangle?",computed:{base:s,height:o,area:r,rectangle:s*o,two:2,answer:r},steps:[i[0],i[1],i[2]]})}},INVERSE=/an area of (\d+) (?:cm|m)² and a (base|height) of (\d+) (?:cm|m)\. what is its (base|height) in (?:cm|m)/i,inverseSide={id:"MEA.P7.M04.inverse-side.v1",microSkillId:MICRO$6,forms:["inverse_area_missing_side"],generate(n){const t=normaliseStem(n.stem).match(INVERSE);if(!t)return null;const e=Number(t[1]),s=t[2].toLowerCase(),o=Number(t[3]),r=t[4].toLowerCase();if(s===r||o<1)return null;const a=e*2;if(a%o!==0)return null;const i=a/o,l=numericAnswer(n);return l===null||l!==i?null:buildExample({generatorId:this.id,question:n,title:`A triangle's area is ${e}. Find its ${r}.`,computed:{area:e,known:o,doubled:a,missing:i,two:2,answer:i},steps:[{visual_data:{type:"equation_display",terms:[`${e}`,"=",`${o}`,"×","__","÷","2"]},animation:"setup",caption:`Area ${e}, ${s} ${o}. Find the ${r}.`,spoken:`We know the area and the ${s}, but not the ${r}. The area is the base times the height, divided by two.`},{visual_data:{type:"equation_display",terms:[`${o}`,"×","__","=",`${a}`]},animation:"double",caption:`Undo ÷ 2: double the area to ${a}.`,spoken:`Dividing by two made it smaller. To undo that, we double the area, so the base times the height is ${numberToWordsLong(a)}.`},{visual_data:{type:"equation_display",terms:[`${a}`,"÷",`${o}`,"=",`${i}`]},animation:"divide",caption:`${a} ÷ ${o} = ${i}.`,spoken:`Now divide by the ${s}. ${cap$a(numberToWordsLong(a))} divided by ${numberToWordsLong(o)} is ${numberToWordsLong(i)}, so the ${r} is ${numberToWordsLong(i)}.`}]})}},COMPARE_TRIANGLE=/triangle a has base (\d+) cm and height (\d+) cm\. triangle b has base (\d+) cm and height (\d+) cm\./i,COMPARE_STICKER=/sticker a is a triangle with base (\d+) cm and height (\d+) cm\. sticker b has base (\d+) cm and height (\d+) cm\./i,setComparison={id:"MEA.P7.M04.compare-area.v1",microSkillId:MICRO$6,forms:["set_comparison_judgement"],generate(n){const t=normaliseStem(n.stem),e=t.match(COMPARE_TRIANGLE)??t.match(COMPARE_STICKER);if(!e)return null;const[s,o,r,a]=e.slice(1).map(Number);if([s,o,r,a].some(g=>g<1))return null;const i=s*o/2,l=r*a/2,u=n.correctAnswer.trim();let d,m;if(i===l){if(!/equal/i.test(u))return null;d="=",m=""}else if(i>l){if(!/\bA$/.test(u))return null;d=">",m="A"}else{if(!/\bB$/.test(u))return null;d="<",m="B"}const h=d==="="?`Equal areas: ${i} = ${l}.`:`${Math.max(i,l)} beats ${Math.min(i,l)}: ${m} wins.`,f=d==="="?`Both areas come out at ${numberToWordsLong(i)}, so the two triangles are equal in area.`:`${cap$a(numberToWordsLong(Math.max(i,l)))} is more than ${numberToWordsLong(Math.min(i,l))}, so ${m==="A"?"the first":"the second"} triangle has the larger area.`;return buildExample({generatorId:this.id,question:n,title:"Which triangle has the larger area?",computed:{bA:s,hA:o,areaA:i,bB:r,hB:a,areaB:l,two:2,answer:Math.max(i,l)},steps:[{visual_data:{type:"equation_display",terms:[`${s} × ${o} ÷ 2`,"=",`${i}`]},animation:"first",caption:`First: ${s} × ${o} ÷ 2 = ${i}.`,spoken:`To compare, we find each triangle's area. A triangle is half of its rectangle, so its area is the base times the height, divided by two: ${numberToWordsLong(s)} times ${numberToWordsLong(o)}
71 divided by two is ${numberToWordsLong(i)}.`},{visual_data:{type:"equation_display",terms:[`${r} × ${a} ÷ 2`,"=",`${l}`]},animation:"second",caption:`Second: ${r} × ${a} ÷ 2 = ${l}.`,spoken:`Then the second triangle, the same way: ${numberToWordsLong(r)} times ${numberToWordsLong(a)} divided by two is ${numberToWordsLong(l)}.`},{visual_data:{type:"comparison",left:i,right:l,symbol:d},animation:"compare",caption:h,spoken:f}]})}},TWO_IDENTICAL=/^.*two identical triangles, each with base (\d+) cm and height (\d+) cm\. what is the total area/i,N_IDENTICAL=/has base (\d+) cm and height (\d+) cm\. you make (\d+) identical \w+\. what is (?:their|the) total area/i,RECT_HALF=/triangle and a rectangle share the same base and height\. the rectangle's area is (\d+) cm²\. what is the triangle's area/i,TWO_SAILS=/a sail with area (\d+) m² has a base of (\d+) m\. a second sail has the same height but a base of (\d+) m\. what is the second sail's area/i,contextReasoning$1={id:"MEA.P7.M04.reasoning.v1",microSkillId:MICRO$6,forms:["context_reasoning"],generate(n){const t=normaliseStem(n.stem),e=numericAnswer(n);if(e===null)return null;let s=t.match(TWO_IDENTICAL);if(s){const[o,r]=s.slice(1).map(Number),a=o*r/2,i=a*2;return i!==e?null:multiplyDemo(this.id,n,o,r,a,2,i,"Two identical triangles — what is the total area?")}if(s=t.match(N_IDENTICAL),s){const[o,r,a]=s.slice(1).map(Number),i=o*r/2,l=i*a;return a<1||l!==e?null:multiplyDemo(this.id,n,o,r,i,a,l,`${a} identical triangles — what is the total area?`)}if(s=t.match(RECT_HALF),s){const o=Number(s[1]);if(o%2!==0)return null;const r=o/2;if(r!==e)return null;let a=null,i=null;for(let u=1;u<=BASE_MAX;u++){if(o%u!==0)continue;const d=o/u;if(d>=1&&d<=HEIGHT_MAX){a=u,i=d;break}}if(a===null||i===null)return null;const l=triangleVisual({base:a,height:i,unit:"cm",orientation:"right",grid:!1});return buildExample({generatorId:this.id,question:n,title:`The rectangle's area is ${o}. What is the triangle's area?`,computed:{rect:o,tri:r,illBase:a,illHeight:i,two:2,answer:r},steps:[{visual_data:l,animation:"setup",caption:`Example: a ${a} by ${i} rectangle has area ${o}.`,spoken:"A triangle with the same base and height as a rectangle always fills exactly half of it — here is an example rectangle with that area."},{visual_data:l,animation:"halves",caption:"The triangle is half the rectangle.",spoken:"The rectangle splits along the triangle's slanting edge into two matching triangles of equal size. Our triangle is one of them, so it covers exactly half of the rectangle."},{visual_data:{type:"equation_display",terms:[`${o}`,"÷","2","=",`${r}`]},animation:"result",caption:`${o} ÷ 2 = ${r}.`,spoken:`${cap$a(numberToWordsLong(o))} divided by two is ${numberToWordsLong(r)}, so the triangle's area is ${numberToWordsLong(r)}.`}]})}if(s=t.match(TWO_SAILS),s){const[o,r,a]=s.slice(1).map(Number);if(r<1||o*2%r!==0)return null;const i=o*2/r;if(i<1||i>HEIGHT_MAX||a<1||a>BASE_MAX||a*i%2!==0)return null;const l=a*i/2;return l!==e?null:buildExample({generatorId:this.id,question:n,title:`A second sail with base ${a} — what is its area?`,computed:{area1:o,base1:r,height:i,base2:a,newArea:l,two:2,answer:l},steps:[{visual_data:{type:"equation_display",terms:[`${o} × 2 ÷ ${r}`,"=",`${i}`]},animation:"find-height",caption:`First sail gives height ${i}.`,spoken:"The first sail tells us the height: double its area, then divide by its base."},{visual_data:{type:"equation_display",terms:[`${a} × ${i} ÷ 2`,"=","__"]},animation:"apply",caption:`Same height, base ${a}.`,spoken:"The second sail has the same height, so we use it with the new base."},{visual_data:{type:"equation_display",terms:[`${a} × ${i} ÷ 2`,"=",`${l}`]},animation:"result",caption:`${a} × ${i} ÷ 2 = ${l}.`,spoken:`${cap$a(numberToWordsLong(a))} times ${numberToWordsLong(i)}, divided by two, is ${numberToWordsLong(l)}.`}]})}return null}};function multiplyDemo(n,t,e,s,o,r,a,i){const l=triangleVisual({base:e,height:s,unit:"cm",orientation:"right",grid:!1});return buildExample({generatorId:n,question:t,title:i,computed:{base:e,height:s,single:o,n:r,total:a,two:2,answer:a},steps:[{visual_data:l,animation:"setup",caption:`One triangle: ${e} × ${s} ÷ 2 = ${o}
71.`,spoken:`First we find the area of one triangle. It is half of the rectangle that boxes it in, so we take its base times its height, divided by two: ${numberToWordsLong(e)} times ${numberToWordsLong(s)} divided by two is ${numberToWordsLong(o)}.`},{visual_data:{type:"equation_display",terms:[`${o}`,"×",`${r}`,"=","__"]},animation:"times",caption:r===2?`Two the same: ${o} × 2.`:`${r} the same: ${o} × ${r}.`,spoken:`They are all the same size, so we multiply one triangle's area, ${numberToWordsLong(o)}, by how many there are, ${numberToWordsLong(r)}.`},{visual_data:{type:"equation_display",terms:[`${o}`,"×",`${r}`,"=",`${a}`]},animation:"result",caption:`${o} × ${r} = ${a}.`,spoken:`${cap$a(numberToWordsLong(o))} times ${numberToWordsLong(r)} is ${numberToWordsLong(a)} altogether.`}]})}function cap$a(n){return n.charAt(0).toUpperCase()+n.slice(1)}const MEA_P7_M04_GENERATORS=[directCompute,symbolicChoice,misconceptionProbe,inverseSide,setComparison,contextReasoning$1];function toMinsOfDay(n,t,e){let s=n;if(e){const o=/p/i.test(e);s===12?s=o?12:0:o&&(s+=12)}return s%24*60+t}const TIME_RE=/(\d{1,2}):(\d{2})\s*(a\.?m\.?|p\.?m\.?)?/gi;function findTimes(n){const t=[];for(const o of n.matchAll(TIME_RE))t.push({h:Number(o[1]),m:Number(o[2]),mer:o[3]});const e=t.some(o=>o.mer);let s=t.find(o=>o.mer)?.mer;return t.map(o=>{o.mer&&(s=o.mer);const r=o.mer??(e?s:void 0);return toMinsOfDay(o.h,o.m,r)})}function parseAnswerTime(n){TIME_RE.lastIndex=0;const t=TIME_RE.exec(n);return TIME_RE.lastIndex=0,t?toMinsOfDay(Number(t[1]),Number(t[2]),t[3]):null}function parseDuration(n){let t=0,e=!1;for(const s of n.matchAll(/(\d+)\s*hours?\b/gi))t+=Number(s[1])*60,e=!0;for(const s of n.matchAll(/(\d+)\s*min(?:ute)?s?\b/gi))t+=Number(s[1]),e=!0;return e?t:null}function parseAnswerDuration(n){return parseDuration(n)}function wordsToMins(n){const t=n.trim().toLowerCase();if(/^half(?: an)? hour$/.test(t))return 30;if(/^(?:a|one) hour$/.test(t))return 60;const e=parseDuration(t);return e!==null?e:null}function formatClock(n){const t=(n%1440+1440)%1440,e=Math.floor(t/60),s=t%60;return`${String(e).padStart(2,"0")}:${String(s).padStart(2,"0")}`}function decomposeForward(n,t){const e=(60-n%60)%60;return splitChunks(e,t,"+")}function decomposeBack(n,t){const e=n%60;return splitChunks(e,t,"")}function splitChunks(n,t,e){const s=l=>`${e}${l} min`,o=l=>`${e}${l} h`,r=[];let a=t;n>0&&n<t&&(r.push({amount:n,label:s(n),role:"bridge"}),a-=n);const i=Math.floor(a/60);return i>0&&(r.push({amount:i*60,label:o(i),role:"hours"}),a-=i*60),a>0&&r.push({amount:a,label:s(a),role:"rest"}),r}function chooseStep(n){for(const t of[5,10,15,30,60])if(n/t<=12)return t;return 60}function countOnCaption(n,t){const e=o=>o==="bridge"?t?"back to the hour":"to the hour":o==="hours"?"the hours":"the rest";if(n.length===1){const o=n[0].role;return o==="bridge"?t?"Count back to the hour.":"Count on to the next hour.":o==="hours"?t?"Count back in whole hours.":"Count on in whole hours.":t?"Count back the minutes.":"Count on the minutes."}const s=n.map(o=>e(o.role));return`First ${s[0]}, then ${s.slice(1).join(", then ")}.`}function countOnSpoken(n,t){const e=o=>o==="bridge"?t?"step back to the nearest o'clock":"jump up to the next o'clock":o==="hours"?t?"count back the whole hours":"jump the whole hours":t?"count back the last few minutes":"count on the last few minutes";if(n.length===1){const o=n[0].role;return o==="bridge"?t?"We step back to the nearest o'clock.":"We jump up to the next o'clock.":o==="hours"?t?"We count back in whole hours.":"We count on in whole hours.":t?"We count back the minutes.":"We count on the minutes."}const s=n.map(o=>e(o.role));return`First we ${s[0]}. Then we ${s.slice(1).join(". Then we ")}.`}function sumLine(n,t){return n.length<2?"":`${n.map(e=>e.amount).join(" + ")} = ${t}`}function buildTimeExample(n){const t=chooseStep(n.D);let e=0;const s=n.chunks.map(i=>{const l={from:e,to:e+i.amount,label:i.label};return i.role==="hours"&&(l.sublabel=`= ${i.amount} min`),e+=i.amount,l}),o=[0,n.D,...s.map(i=>i.to)],r={};for(const i of o)r[i]=formatClock(n.timeAt(i));const a=(i,l)=>({type:"number_line",min:0,max:n.D,step:t,marked:[0],hops:i?s:[],unknown:l?n.D:null,tick_labels:r,...l?{conclude_label:n.concludeLabel}:{},...l&&n.resultIsClockTime?{hide_result_number:!0}:{}});return buildExample({generatorId:n.generatorId,question:n.q,title:n.title,computed:n.computed,steps:[{visual_data:a(!1,!1),animation:"setup",caption:n.captions.s1,spoken:n.spoken.s1},{visual_data:a(!0,!1),animation:"count_on",caption:n.captions.s2,spoken:n.spoken.s2},{visual_data:a(!0,!0),animation:"total",caption:n.captions.s3,spoken:n.spoken.s3}]})}function computedFor(n,t){const e={answer:n,total:n}
71;return t.forEach((s,o)=>{e[`jump_${o+1}`]=s.amount}),e}const minutesWords=n=>`${numberToWordsLong(n)} minute${n===1?"":"s"}`,hoursWords=n=>`${numberToWordsLong(n)} hour${n===1?"":"s"}`;function hoursAndMinutes(n){const t=Math.floor(n/60),e=n%60;return t===0?minutesWords(e):e===0?hoursWords(t):`${hoursWords(t)} and ${minutesWords(e)}`}function durationJumpsSpoken(n){if(n.length===1){const e=n[0];return e.role==="hours"?`The end is a whole number of hours later, so we jump ${hoursWords(e.amount/60)}. That is ${minutesWords(e.amount)}.`:`The end is in the same hour, so we simply count on ${minutesWords(e.amount)}.`}return n.map((e,s)=>{const o=s===0?"First":"Then";return e.role==="bridge"?`${o} we jump ${minutesWords(e.amount)} up to the next o'clock.`:e.role==="hours"?`${o} we jump ${hoursWords(e.amount/60)}, which is ${minutesWords(e.amount)}.`:`${o} we count on the last ${minutesWords(e.amount)} to the end time.`}).join(" ")}function durationTotalSpoken(n,t){const e=t>=60?`${minutesWords(t)}, which is ${hoursAndMinutes(t)}`:minutesWords(t);return n.length<2?`That one jump is the whole time. So it lasts ${e}.`:`Now add the jumps together: ${n.map(o=>numberToWordsLong(o.amount)).join(" add ")} is ${numberToWordsLong(t)}. So it lasts ${e}.`}function jumpsTotalSpoken(n,t){return n.length<2?"":`Adding the jumps, ${n.map(e=>numberToWordsLong(e.amount)).join(" add ")} is ${minutesWords(t)} altogether. `}const DURATION={id:"MEA.P7.M06.duration.v1",microSkillId:"MEA.P7.M06",forms:["time_duration_calculation"],generate(n){const t=findTimes(n.stem);let e,s;if(t.length===2)[e,s]=t;else if(t.length===1&&n.visualData&&n.visualData.type==="clock"){const u=n.visualData;if(!Number.isInteger(u.hour)||!Number.isInteger(u.minute))return null;e=u.hour%24*60+u.minute,s=t[0]}else return null;let o=s-e;if(o<=0&&(o+=1440),o<=0||o>1440)return null;const r=/^\d+$/.test(n.correctAnswer.trim());let a;if(r){if(Number(n.correctAnswer)!==o)return null;a=`${o} minutes`}else{const u=parseAnswerDuration(n.correctAnswer);if(u===null||u!==o)return null;a=n.correctAnswer.trim()}const i=decomposeForward(e,o);if(i.length===0)return null;const l=sumLine(i,o);return buildTimeExample({generatorId:DURATION.id,q:n,title:"How long does it last?",D:o,chunks:i,concludeLabel:a,captions:{s1:"Count on from the start to the end.",s2:countOnCaption(i,!1),s3:l?`${l}.`:r?`That's ${o} minutes.`:"That's the total."},spoken:{s1:"We want to know how long this lasts. An hour has sixty minutes, not a hundred, so we don't take one time away from the other. Instead we count on from the start time to the end time in easy jumps.",s2:durationJumpsSpoken(i),s3:durationTotalSpoken(i,o)},computed:computedFor(o,i),timeAt:u=>(e+u)%1440})}},LUNCH_RE=/runs from (\d{1,2}:\d{2}) to (\d{1,2}:\d{2}), but (.+?) of it is [a-z]+/i,lunchSubtract={id:"MEA.P7.M06.lunch-subtract.v1",microSkillId:"MEA.P7.M06",forms:["time_duration_calculation"],generate(n){const t=n.stem.match(LUNCH_RE);if(!t)return null;const e=t[1].match(/(\d{1,2}):(\d{2})/),s=t[2].match(/(\d{1,2}):(\d{2})/);if(!e||!s)return null;const o=Number(e[1])*60+Number(e[2]),r=Number(s[1])*60+Number(s[2]),a=wordsToMins(t[3]);if(a===null||a<=0)return null;let i=r-o;if(i<=0&&(i+=1440),i<=a||i>1440)return null;const l=i-a,u=Number(n.correctAnswer);if(!Number.isFinite(u)||u!==l)return null;const d=decomposeForward(o,i);if(d.length===0)return null;const m=chooseStep(i);let h=0;const f=d.map(E=>{const k={from:h,to:h+E.amount,label:E.label};return E.role==="hours"&&(k.sublabel=`= ${E.amount} min`),h+=E.amount,k}),g=[0,i,...f.map(E=>E.to)],b={};for(const E of g)b[E]=formatClock((o+E)%1440);const x={type:"number_line",min:0,max:i,step:m,marked:[0],hops:f,unknown:null,tick_labels:b},y=sumLine(d,i);return buildExample({generatorId:this.id,question:n,title:"How many minutes are spent in lessons?",computed:{start:o,end:r,raw:i,breakMins:a,lessons:l,answer:l,...Object.fromEntries(d.map((E,k)=>[`jump_${k+1}`,E.amount]))},steps:[{visual_data:x,animation:"setup",caption:y?`First count on the whole day: ${y}.`:"First count on the whole day.",spoken:`First we count on from the start of the day to the end, in easy jumps, to find the whole span. ${jumpsTotalSpoken(d,i)}`.trim()},{visual_data:{type:"equation_display",terms:[`${i} min`,"-",`${a} min`,"=","__"]},animation:"subtract_break",caption:`Take away the break: ${i} − ${a}.`,spoken:`But part of that time is a break, and a break isn't spent doing the activity, so we take it away: ${numberToWordsLong(i)} take away ${numberToWordsLong(a)}.`},{visual_data:{type:"equation_display",terms:[`${i}`,"-",`${a}`,"=",`${l}`]},animation:"result",caption:`${i} − ${a} = ${l}.`,spoken:`${capitalised(numberToWordsLong(i))} minus ${numberToWordsLong(a)} is ${numberToWordsLong(l)}, so that's how many minutes are left.`}]})}},TWO_LEG_RE=/two \w+: (\d{1,2}:\d{2}) to (\d{1,2}:\d{2}), then (\d{1,2}:\d{2}) to (\d{1,2}:\d{2})\.\s*how many minutes\b.*\baltogether/i,twoLegJourney={id:"MEA.P7.M06.two-leg.v1",microSkillId:"MEA.P7.M06",forms:["time_duration_calculation"],generate(n){const t=n.stem.match(TWO_LEG_RE);if(!t)return null;const e=[t[1],t[2],t[3],t[4]].map(h=>{const f=h.match(/(\d{1,2}):(\d{2})/);return Number(f[1])*60+Number(f[2])}),[s,o,r,a]=e;let i=o-s;i<=0&&(i+=1440);let l=a-r;if(l<=0&&(l+=1440),i>1440||l>1440)return null;const u=i+l,d=Number(n.correctAnswer);if(!Number.isFinite(d)||d!==u)return null;const m=(h,f,g)=>{const b=chooseStep(f),x={from:0,to:f,label:g},y={0:formatClock(h),[f]:formatClock((h+f)%1440)};return{type:"number_line",min:0,max:f,step:b,marked:[0],hops:[x],unknown:f,tick_labels:y,conclude_label:`${f} min`}};return buildExample({generatorId:this.id,question:n,title:"How many minutes altogether?",computed:{leg1:i,leg2:l,total:u,answer:u},steps:[{visual_data:m(s,i,`+${i} min`),animation:"leg_one",caption:`First train: ${i} minutes.`,spoken:"First we count on the first train's own journey, from when it left to when it arrived."},{visual_data:m(r,l,`+${l} min`),animation:"leg_two",caption:`Second train: ${l} minutes.`,spoken:"Then we count on the second train's journey the same way."},{visual_data:{type:"equation_display",terms:[`${i}`,"+",`${l}`,"=",`${u}`]},animation:"sum",caption:`${i} + ${l} = ${u} minutes altogether.`,spoken:`${capitalised(numberToWordsLong(i))} and ${numberToWordsLong(l)} make ${numberToWordsLong(u)} minutes spent travelling altogether.`}]})}},COMPARE_RE=/(\w+)'s \w+ runs from (\d{1,2}:\d{2}) to (\d{1,2}:\d{2})\. (\w+)'s \w+ runs from (\d{1,2}:\d{2}) to (\d{1,2}:\d{2})\.\s*whose journey is longer, and by how many minutes/i,compareJourneys={id:"MEA.P7.M06.compare-journeys.v1",microSkillId:"MEA.P7.M06",forms:["context_reasoning"],generate(n){const t=n.stem.match(COMPARE_RE);if(!t)return null;const e=t[1],s=t[4],o=(y,E)=>{const k=y.match(/(\d{1,2}):(\d{2})/),T=E.match(/(\d{1,2}):(\d{2})/),A=Number(k[1])*60+Number(k[2]);let S=Number(T[1])*60+Number(T[2])-A;return S<=0&&(S+=1440),S},r=o(t[2],t[3]),a=o(t[5],t[6]);if(r===a)return null;const i=r>a?e:s,l=Math.abs(r-a),u=`${i}, by ${l} minutes`;if(n.correctAnswer.trim()!==u)return null;const d=Number(t[2].match(/(\d{1,2})/)[1])*60+Number(t[2].match(/:(\d{2})/)[1]),m=Number(t[5].match(/(\d{1,2})/)[1])*60+Number(t[5].match(/:(\d{2})/)[1]),h=(y,E)=>({type:"number_line",min:0,max:E,step:chooseStep(E),marked:[0],hops:[{from:0,to:E,label:`+${E} min`}],unknown:E,tick_labels:{0:formatClock(y),[E]:formatClock((y+E)%1440)},conclude_label:`${E} min`}),f=r>a?">":"<",g="write_answer_highlight_none",b=Math.max(r,a),x=Math.min(r,a);return buildExample({generatorId:this.id,question:n,title:"Whose journey is longer, and by how many minutes?",computed:{durA:r,durB:a,diff:l,answer:l},steps:[{visual_data:h(d,r),animation:"first",caption:`${e}: ${r} minutes.`,spoken:`To compare, we find how long each journey takes. First we count on ${e==="Mia"?"her":"the
71ir"} journey: it takes ${minutesWords(r)}.`},{visual_data:h(m,a),animation:"second",caption:`${s}: ${a} minutes.`,spoken:`Then we count on the other journey the same way: it takes ${minutesWords(a)}.`},{visual_data:{type:"comparison",left:r,right:a,symbol:f},animation:g,caption:`${b} − ${x} = ${l}. ${i} wins by ${l} minutes.`,spoken:`${capitalised(numberToWordsLong(b))} is more than ${numberToWordsLong(x)}, so that journey is longer. To find by how much, we take the smaller time from the bigger one: ${numberToWordsLong(b)} take away ${numberToWordsLong(x)} is ${minutesWords(l)}.`}]})}},FIND_END={id:"MEA.P7.M06.find_end.v1",microSkillId:"MEA.P7.M06",forms:["time_find_end_time"],generate(n){const t=findTimes(n.stem);if(t.length!==1)return null;const e=t[0],s=parseDuration(n.stem);if(s===null||s<=0||s>1440)return null;const o=(e+s)%1440,r=parseAnswerTime(n.correctAnswer);if(r===null||r!==o)return null;const a=decomposeForward(e,s);if(a.length===0)return null;const i=sumLine(a,s);return buildTimeExample({generatorId:FIND_END.id,q:n,title:"What time does it end?",D:s,chunks:a,concludeLabel:n.correctAnswer.trim(),captions:{s1:"Count the time on from the start.",s2:countOnCaption(a,!1),s3:i?`Where you land is the end time (${i}).`:"Where you land is the end time."},spoken:{s1:"We know the start time and how long it takes. We count that time on from the start.",s2:countOnSpoken(a,!1),s3:`${jumpsTotalSpoken(a,s)}Where we land on the line is the finishing time.`},resultIsClockTime:!0,computed:{...computedFor(s,a),answer:o},timeAt:l=>(e+l)%1440})}},DELAY_RE=/was (\d+) minutes? late\.\s*it was meant to arrive at (\d{1,2}:\d{2})/i,delayFindEnd={id:"MEA.P7.M06.delay-find-end.v1",microSkillId:"MEA.P7.M06",forms:["time_find_end_time"],generate(n){const t=n.stem.match(DELAY_RE);if(!t)return null;const e=Number(t[1]),s=t[2].match(/(\d{1,2}):(\d{2})/),o=Number(s[1])*60+Number(s[2]);if(e<=0||e>1440)return null;const r=(o+e)%1440,a=parseAnswerTime(n.correctAnswer);if(a===null||a!==r)return null;const i=decomposeForward(o,e);if(i.length===0)return null;const l=sumLine(i,e);return buildTimeExample({generatorId:this.id,q:n,title:"What time did it actually arrive?",D:e,chunks:i,concludeLabel:n.correctAnswer.trim(),captions:{s1:"Count the delay on from the time it was due.",s2:countOnCaption(i,!1),s3:l?`Where you land is the actual arrival (${l}).`:"Where you land is the actual arrival."},spoken:{s1:"We know when it was meant to arrive, and how many minutes late it was. We count the delay on from the scheduled time.",s2:`${countOnSpoken(i,!1)} Altogether we count on ${minutesWords(e)}, the time it was late.`,s3:`${jumpsTotalSpoken(i,e)}Where we land on the line is when it actually arrived.`},resultIsClockTime:!0,computed:{...computedFor(e,i),answer:r},timeAt:u=>(o+u)%1440})}},FIND_START={id:"MEA.P7.M06.find_start.v1",microSkillId:"MEA.P7.M06",forms:["time_find_start_time"],generate(n){const t=findTimes(n.stem);if(t.length!==1)return null;const e=t[0],s=parseDuration(n.stem);if(s===null||s<=0||s>1440)return null;let o=(e-s)%1440;o<0&&(o+=1440);const r=parseAnswerTime(n.correctAnswer);if(r===null||r!==o)return null;const a=decomposeBack(e,s);if(a.length===0)return null;const i=sumLine(a,s);return buildTimeExample({generatorId:FIND_START.id,q:n,title:"What time did it start?",D:s,chunks:a,concludeLabel:n.correctAnswer.trim(),captions:{s1:"Count the time back from the end.",s2:countOnCaption(a,!0),s3:i?`Where you land is the start time (${i}).`:"Where you land is the start time."},spoken:{s1:"We know the end time and how long it took. We count that time backwards from the end.",s2:countOnSpoken(a,!0),s3:`${jumpsTotalSpoken(a,s)}Where we land on the line is the time it started.`},resultIsClockTime:!0,computed:{...computedFor(s,a),answer:o},timeAt:l=>((e-l)%1440+1440)%1440})}},MEA_P7_M06_GENERATORS=[DURATION,lunchSubtract,twoLegJourney,compareJourneys,FIND_END,delayFindEnd,FIND_START],round2=n=>Math.round(n*100)/100,round4=n=>Math.round(n*1e4)/1e4,fmt$2=n=>String(round4(n)),words$2=n=>decimalToWords(fmt$2(n)),dpOf$1=n=>{const t=String(round4(n)),e=t.indexOf(".");return e<0?0:t.length-e-1},MIN_GAP=.004;function minSeparated(n){const t=[...n].sort((e,s)=>e-s);for(let e=1;e<t.length;e++)if(round4(t[e]-t[e-1])<MIN_GAP)return!1;return!0}
71function windowFor(n){const t=round4(Math.floor(10*Math.min(...n))/10),e=round4(Math.ceil(10*Math.max(...n))/10),s=round4(e-t);if(s<=0)return null;const o=s<=.2?.01:.1;return Math.round(s/o)+1>21?null:{min:t,max:e,step:o}}function decimalsIn(n){const t=n.match(/\d+(?:\.\d+)?/g);return t?t.map(e=>round4(Number(e))):[]}function lineFactory(n){return(t={})=>({type:"number_line",min:n.min,max:n.max,step:n.step,marked:[],...t})}function plottable(n){return!(n.length<2||n.length>4||!n.every(t=>Number.isFinite(t)&&t>=0&&t<100)||n.some(t=>dpOf$1(t)>3)||!minSeparated(n))}const BETWEEN_RE=/\bbetween\s+(\d+(?:\.\d+)?)\s+and\s+(\d+(?:\.\d+)?)\b/i,betweenGen={id:"NPV.P6.M05.between.v1",microSkillId:"NPV.P6.M05",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem).match(BETWEEN_RE);if(!t)return null;const e=round4(Number(t[1])),s=round4(Number(t[2]));if(!(e<s))return null;const o=round4(Number(n.correctAnswer));if(!Number.isFinite(o)||!(o>e&&o<s)||[e,s,o].some(i=>dpOf$1(i)>3))return null;const r=windowFor([e,s]);if(!r)return null;const a=lineFactory(r);return buildExample({generatorId:this.id,question:n,title:`Which decimal lies between ${fmt$2(e)} and ${fmt$2(s)}?`,computed:{min:r.min,max:r.max,step:r.step,lo:e,hi:s,answer:o},steps:[{visual_data:a({marked:[e,s]}),animation:"show_bounds",caption:`The answer sits between ${fmt$2(e)} and ${fmt$2(s)}.`,spoken:`We need a decimal that is bigger than ${nw$2(e)} but smaller than ${nw$2(s)}. Let's mark those two ends on the line.`},{visual_data:a({marked:[e,s],hops:[{from:e,to:s,label:"?"}]}),animation:"show_gap",caption:"Look for a decimal in this gap.",spoken:"The answer must fall inside this gap. Compare the tenths and hundredths of each choice to see which one lands between the two ends."},{visual_data:a({marked:[e,s],unknown:o}),animation:"reveal_between",caption:`${fmt$2(o)} fits: past ${fmt$2(e)}, before ${fmt$2(s)}.`,spoken:`The answer is ${nw$2(o)}. It is greater than ${nw$2(e)} and less than ${nw$2(s)}, so it sits right inside the gap.`}]})}},CLOSEST_RE=/\bclosest to\s+(\d+(?:\.\d+)?)\b/i,MAX_RE=/\b(largest|greatest|biggest|furthest|longest|heaviest|larger|greater|bigger)\b/i,MIN_RE=/\b(smallest|least|shortest|lightest|smaller|less|lowest)\b/i,nw$2=n=>decimalToWords(fmt$2(n)).replace(/^zero point/,"nought point"),extremumGen={id:"NPV.P6.M05.extremum.v1",microSkillId:"NPV.P6.M05",forms:["comparison_statement","comparison"],generate(n){const t=normaliseStem(n.stem),e=t.match(CLOSEST_RE),s=e?"closest":MAX_RE.test(t)?"max":MIN_RE.test(t)?"min":null;if(!s)return null;const o=(n.options??[]).map(b=>Number(b)).filter(b=>Number.isFinite(b)).map(round4),r=e?round4(Number(e[1])):null;let a=o.length>=2?o:decimalsIn(n.stem);if(r!=null&&(a=a.filter(b=>b!==r)),a=[...new Map(a.map(b=>[b,b])).values()],!plottable(a))return null;let i;if(s==="max")i=Math.max(...a);else if(s==="min")i=Math.min(...a);else{const b=a.map(y=>Math.abs(y-r)),x=Math.min(...b);if(b.filter(y=>Math.abs(y-x)<1e-9).length!==1)return null;i=a[b.indexOf(x)]}const l=round4(Number(n.correctAnswer));if(!Number.isFinite(l)||round2(l)!==round2(i))return null;const u=r!=null?[...a,r]:a;if(!minSeparated(u))return null;const d=windowFor(u);if(!d)return null;const m=lineFactory(d),h={min:d.min,max:d.max,step:d.step,answer:i};a.forEach((b,x)=>h[`v${x}`]=b),r!=null&&(h.target=r);const f=s==="max"?`${fmt$2(i)} is furthest right — the largest.`:s==="min"?`${fmt$2(i)} is furthest left — the smallest.`:`${fmt$2(i)} is closest to ${fmt$2(r)}.`,g=s==="max"?`On the line, ${nw$2(i)} sits furthest to the right, so ${nw$2(i)} is the largest. Remember, more digits does not mean bigger.`:s==="min"?`On the line, ${nw$2(i)} sits furthest to the left, so ${nw$2(i)} is the smallest, even if another decimal looks longer.`:`On the line, ${nw$2(i)} sits nearest to ${nw$2(r)}, so ${nw$2(i)} is the closest.`;return buildExample({generatorId:this.id,question:n,title:s==="closest"?`Which decimal is closest to ${fmt$2(r)}?`:s==="max"?"Which decimal is the largest?":"Which decimal is the smallest?",computed:h,steps:[{visual_data:m({marked:r!=null?[r]:[]}),animation:"show_scale",caption:`A close-up of the line: ${fmt$2(d.min)} to ${fmt$2(d.max)}.`,spoken:r!=null?`To see which is closest to ${nw$2(r)}, we put everything on a number line and mark ${nw$2(r)} first. This is a close-up of the line from ${nw$2(d.min)} to ${nw$2(d.max)}.`:`To compare decimals fairly, we put each one on a number line. This is a close-up of the line from ${nw$2(d.min)} to ${nw$2(d.max)}.`},{visual_data:m({marked:r!=null?[r,...a]:[...a]}),animation:"place_points",caption:"Place each one. Compare tenths first.",spoken:"Now every decimal is on the line. The further right a number sits, the larger it is. We check the tenths first, then the hundredths, to place each one."},{visual_data:m({marked:r!=null?[r,...a]:[...a],unknown:i}),animation:"reveal_extreme",caption:f,spoken:g}]})}},NAME_PAIR_RE=/([A-Z][a-z]+)\s+\w+\s+(\d+(?:\.\d+)?)/g,NAME_MAX_RE=/\b(furthest|longest|highe
71st|greatest|most|farthest)\b/i,NAME_MIN_RE=/\b(shortest|least|lowest|fewest|quickest|fastest|smallest)\b/i,nameTransferGen={id:"NPV.P6.M05.name-transfer.v1",microSkillId:"NPV.P6.M05",forms:["comparison_statement"],generate(n){const t=n.stem,e=[];for(const m of t.matchAll(NAME_PAIR_RE))e.push({name:m[1],value:round4(Number(m[2]))});if(e.length<2||e.length>4)return null;const s=normaliseStem(t),o=NAME_MAX_RE.test(s)?"max":NAME_MIN_RE.test(s)?"min":null;if(!o)return null;const r=e.map(m=>m.value);if(!plottable(r))return null;const a=o==="max"?e.reduce((m,h)=>h.value>m.value?h:m):e.reduce((m,h)=>h.value<m.value?h:m);if(a.name.toLowerCase()!==n.correctAnswer.trim().toLowerCase())return null;const i=windowFor(r);if(!i)return null;const l=lineFactory(i),u={min:i.min,max:i.max,step:i.step,answer:a.value};r.forEach((m,h)=>u[`v${h}`]=m);const d=o==="max"?"the biggest value":"the smallest value";return buildExample({generatorId:this.id,question:n,title:o==="max"?"Whose measurement is the largest?":"Whose measurement is the smallest?",computed:u,steps:[{visual_data:l({marked:[]}),animation:"show_scale",caption:"Put each measurement on the line.",spoken:"Ignore the names for a moment and put each measurement on a number line, so we can see which is biggest and which is smallest."},{visual_data:l({marked:[...r]}),animation:"place_points",caption:"Compare tenths first, then hundredths.",spoken:"Now compare the tenths first, then the hundredths. The measurement further along the line is the larger one, even if it is written with fewer digits."},{visual_data:l({marked:[...r],unknown:a.value}),animation:"reveal_winner",caption:`${a.name} has ${d}: ${fmt$2(a.value)}.`,spoken:o==="max"?`${a.name} has the measurement furthest to the right, so ${a.name} is the answer.`:`${a.name} has the measurement furthest to the left, so ${a.name} is the answer.`}]})}},numberLineGen={id:"NPV.P6.M05.read-scale.v1",microSkillId:"NPV.P6.M05",forms:["number_line_identify"],generate(n){const t=round4(Number(n.correctAnswer));if(!Number.isFinite(t)||t<0||t>=100||dpOf$1(t)>3)return null;const e=round4(Math.floor(t*10)/10),s=round4(e+.1),o=round4(t-e);if(!(o>0))return null;const r=windowFor([e,s]);if(!r)return null;const a=lineFactory(r),i=[{from:e,to:t,label:`+${fmt$2(o)}`}],l=Math.round(o*100),u=l<=2?`just past ${fmt$2(e)}`:l>=8?`just before ${fmt$2(s)}`:l===5?`exactly halfway between ${fmt$2(e)} and ${fmt$2(s)}`:`between ${fmt$2(e)} and ${fmt$2(s)}`;return buildExample({generatorId:this.id,question:n,title:"Which decimal does the arrow point to?",computed:{min:r.min,max:r.max,step:r.step,start:e,hundredths:l,answer:t},steps:[{visual_data:a({marked:[e]}),animation:"show_scale",caption:`The arrow is ${u}.`,spoken:`First find the two tenths the arrow sits between, ${words$2(e)} and ${words$2(s)}.`},{visual_data:a({marked:[e],hops:i}),animation:"count_on",caption:`Hop ${l} hundredths on from ${fmt$2(e)}.`,spoken:"Each small tick is one hundredth. Hop on from that tenth by the right number of hundredths to reach the arrow."},{visual_data:a({marked:[e],hops:i,unknown:t}),animation:"reveal_value",caption:`The arrow points to ${fmt$2(t)}.`,spoken:`Adding those hundredths on gives ${decimalToWords(fmt$2(t))}, the decimal the arrow points to.`}]})}},ASC_RE=/\b(?:smallest to largest|shortest to longest|lightest to heaviest)\b/i,DESC_RE=/\b(?:largest to smallest|longest to shortest|heaviest to lightest)\b/i,orderingGen={id:"NPV.P6.M05.order-line.v1",microSkillId:"NPV.P6.M05",forms:["decimal_ordering"],generate(n){const t=normaliseStem(n.stem),e=ASC_RE.test(t)?"asc":DESC_RE.test(t)?"desc":null;if(!e)return null;const s=n.correctAnswer.trim().split(",").map(f=>round4(Number(f)));if(!plottable(s))return null;const o=[...s].sort((f,g)=>e==="asc"?f-g:g-f);if(o.map(fmt$2).join(",")!==n.correctAnswer.trim())return null;const r=windowFor(s);if(!r)return null;const a=lineFactory(r),i=s[0],l=o[0],u=o.map(fmt$2),d=o.map(f=>words$2(f)).join(", "),m={min:r.min,max:r.max,step:r.step,first:i,readStart:l,answer:l};o.forEach((f,g)=>m[`v${g}`]=f);const h=e==="asc"?`Read left to right: ${u.join(", ")}`:`Read right to left: ${u.join(", ")}`;return buildExample({generatorId:this.id,question:n,title:`Order these decimals: ${e==="asc"?"smallest":"largest"} first`,computed:m,steps:[{visual_data:a({marked:[i]}),animation:"place_first",caption:`Place ${fmt$2(i)} on the line first.`,spoken:`Let's place each decimal on a number line, one at a time, starting with ${words$2(i)}.`},{visual_data:a({marked:[...s]}),animation:"place_all",caption:"Now place every decimal on the line.",spoken:"Now every decimal is on the line. Compare the tenths first — where each one sits shows how big it is, further right meaning larger."},{visual_data:a({marked:[...s],unknown:l}),animation:"reveal_order",caption:h,spoken:e==="asc"?`Reading from left to right puts them in order, smallest first: ${d}.`:`Reading from right to left puts them in order, largest first: ${d}.`}]})}},NPV_P6_M05_GENERATORS=[betweenGen,extremumGen,nameTransferGen,numberLineGen,orderingGen];function wordsFor(n){if(!Number.isInteger(n)||n<0||n>9999999)throw new Error(`wordsFor out of range: ${n}`);if(n<1e6)return numberToWordsLong(n);const t=Math.floor(n/1e6),e=n%1e6,s=`${numberToWords(t)} million`;return e===0?s:e<1e5?`${s} and ${numberToWordsLong(e)}`:`${s} ${numberToWordsLong(e)}`}function groupCommas(n){return String(n).replace(/\B(?=(\d{3})+(?!\d))/g,",")}function stripThousandCommas(n){return n.replace(/,(?=\d)/g,"")}function acceptedInt(n){const t=n.trim().replace(/,/g,"");return/^\d+$/.test(t)?Number(t):null}function buildAddendsDemo(n,t,e,s,o,r){const a=[];t.forEach((l,u)=>{u>0&&a.push("+"),a.push(groupCommas(l))});const i={total:e,answer:e};return t.forEach((l,u)=>{i[`a${u}`]=l}),buildExample({generatorId:s,question:n,title:o,computed:i,steps:[{visual_data:{type:"equation_display",terms:[...a],fit:!0},animation:"show_parts",caption:"Line up the place-value parts.",spoken:r[0]},{visual_data:{type:"equation_display",terms:[...a,"=","__"],fit:!0},animation:"pulse_unknown",caption:"Combine the parts into one number.",spoken:r[1]},{visual_data:{type:"equation_display",terms:[...a,"=",groupCommas(e)],fit:!0},animation:"write_answer",caption:`That makes ${e}.`,spoken:r[2]}]})}function buildNumeralDemo(n,t,e,s,o){const r=t>
71=1e6,a=r?Math.floor(t/1e6)*1e6:Math.floor(t/1e3)*1e3,i=t-a,l=wordsFor(a);if(i>0)return buildAddendsDemo(n,[a,i],t,e,s,[`This number is ${l}, and ${wordsFor(i)} more.`,"Now we write the two chunks as one whole number.",o]);const u=Math.floor(r?t/1e6:t/1e3),d=r?"million":"thousand",m=wordsFor(u);return buildExample({generatorId:e,question:n,title:s,computed:{numeral:t,groupCount:u,answer:t},steps:[{visual_data:{type:"equation_display",terms:[groupCommas(u)]},animation:"show_group",caption:`Start with the ${d}s: ${u}.`,spoken:`There are ${m} ${d}s. Start by writing ${m}.`},{visual_data:{type:"equation_display",terms:[groupCommas(u),d]},animation:"name_group",caption:`${u} ${d}s — now hold the empty columns.`,spoken:`These are ${d}s, so empty columns follow and each one needs a zero to hold its place.`},{visual_data:{type:"equation_display",terms:[groupCommas(t)]},animation:"write_answer",caption:`That makes ${t}.`,spoken:o}]})}const PLACE_NAMES$1=["ones","tens","hundreds","thousands","ten-thousands","hundred-thousands","millions"];function digitInfo(n,t){if(n<1e3||n>9999999)return null;const e=String(n);if(e.split("").filter(a=>a===String(t)).length!==1)return null;const o=e.indexOf(String(t)),r=e.length-1-o;return r>6?null:{placeFromRight:r,placeName:PLACE_NAMES$1[r],value:t*10**r,idxFromLeft:o,numStr:e}}function expansionParts(n,t){const e=n.split("").map(Number),s=e.length,o=e.map((i,l)=>({value:i*10**(s-1-l),isTarget:l===t})).filter(i=>i.value>0||i.isTarget),r=[],a=[];return o.forEach((i,l)=>{l>0&&(r.push("+"),a.push("+")),r.push(groupCommas(i.value)),a.push(i.isTarget?"__":groupCommas(i.value))}),{fullTerms:r,step2Terms:a}}const DIGIT_VALUE=/value of the digit (\d) in ([\d,]+)\?$/,DIGIT_VALUE_INLINE=/value of the digit (\d) in this (?:reading|number|total|amount|display)\?$/;function findTargetNumber(n,t){const e=n.match(/\d{1,3}(?:,\d{3})+|\d{4,7}/g);if(!e)return null;for(const s of e){const o=Number(s.replace(/,/g,""));if(!(o<1e3||o>9999999)&&String(o).split("").filter(r=>r===String(t)).length===1)return o}return null}function buildDigitValueDemo(n,t,e){if(e===0)return null;const s=digitInfo(t,e);if(!s)return null;const{placeName:o,value:r,idxFromLeft:a,numStr:i}=s;if(acceptedInt(n.correctAnswer)!==r)return null;const l=wordsFor(e),{fullTerms:u,step2Terms:d}=expansionParts(i,a);return buildExample({generatorId:"NPV.P7.M01.digit-value.v1",question:n,title:`What is the ${e} worth in ${t}?`,computed:{num:t,digit:e,value:r,answer:r},steps:[{visual_data:{type:"equation_display",terms:[groupCommas(t)],fit:!0},animation:"show_number",caption:"One number, its place-value columns.",spoken:"What a digit is 
71worth depends on the column it sits in — hundred-thousands, ten-thousands, thousands, and so on."},{visual_data:{type:"equation_display",terms:d,fit:!0},animation:"pulse_unknown",caption:`Split by place: the ${e} sits in ${o}.`,spoken:`We split it by place. The ${l} sits in the ${o} column. So what is that ${l} really worth?`},{visual_data:{type:"equation_display",terms:u,fit:!0},animation:"write_answer",caption:`The ${e} is worth ${r}.`,spoken:`The ${l} sits in the ${o} place, so it is worth ${wordsFor(r)}. Position tells worth.`}]})}const wordToNumeral$1={id:"NPV.P7.M01.word-to-numeral.v1",microSkillId:"NPV.P7.M01",forms:["word_to_typed_numeral"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=answerInt(n);return t===null||t<1e3||t>9999999?null:buildNumeralDemo(n,t,this.id,`Write ${wordsFor(t)} in digits.`,`That makes ${wordsFor(t)}, written in digits. Every empty column shows a zero to hold its place.`)}},partition$1={id:"NPV.P7.M01.partition.v1",microSkillId:"NPV.P7.M01",forms:["partition_equation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=answerInt(n);if(t===null)return null;const s=(stripThousandCommas(normaliseStem(n.stem)).match(/\d+/g)??[]).map(Number).filter(o=>o>0);return s.length<2||s.length>6||s.reduce((o,r)=>o+r,0)!==t||!s.every(isSinglePlaceChunk)?null:buildAddendsDemo(n,s,t,this.id,`Combine the parts to make ${t}.`,["Each part is one place-value column of the number.","Slot each digit into its column and read straight across.",`${capitalise$1(wordsFor(t))}. Any empty column shows a zero.`])}};function isSinglePlaceChunk(n){if(n<=0)return!1;const t=String(n);return t[0]!=="0"&&/^[1-9]0*$/.test(t)}const numeralToWordChoice={id:"NPV.P7.M01.numeral-to-word.v1",microSkillId:"NPV.P7.M01",forms:["numeral_to_word_choice"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const e=stripThousandCommas(normaliseStem(n.stem)).match(/match the number (\d+)/);if(!e)return null;const s=Number(e[1]);return s<1e3||s>9999999||normalizeWords(wordsFor(s))!==normalizeWords(n.correctAnswer)?null:buildNumeralDemo(n,s,this.id,`Read ${wordsFor(s)} in words.`,`We read it as ${wordsFor(s)}.`)}};function placeIndex(n){return String(n).length-1}function buildColumnGapTerms(n){const t=[...n].sort((s,o)=>o-s),e=[t[0]];for(let s=1;s<t.length;s++){const o=placeIndex(t[s-1]),r=placeIndex(t[s]);for(let a=o-1;a>r;a--)e.push(0);e.push(t[s])}return e}function normalizeWords(n){return n.toLowerCase().replace(/[,]/g,"").replace(/\s+/g," ").trim()}const additionChoice={id:"NPV.P7.M01.addition-choice.v1",microSkillId:"NPV.P7.M01",forms:["place_value_model_choice"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const e=stripThousandCommas(normaliseStem(n.stem)).match(/[Ww]hich addition makes (\d+)\?$/);if(!e)return null;const s=Number(e[1]),o=n.correctAnswer.split("+").map(d=>Number(d.trim().replace(/,/g,""))).filter(d=>Number.isFinite(d));if(o.length<2||o.reduce((d,m)=>d+m,0)!==s||!o.every(isSinglePlaceChunk))return null;const r=[];o.forEach((d,m)=>{m>0&&r.push("+"),r.push(groupCommas(d))});const a={total:s,answer:s};o.forEach((d,m)=>{a[`a${m}`]=d});const i=buildColumnGapTerms(o),l=i.map((d,m)=>m>0?["+",groupCommas(d)]:[groupCommas(d)]).flat(),u=i.join(" + ");return buildExample({generatorId:this.id,question:n,title:`Which addition makes ${s}?`,computed:a,steps:[{visual_data:{type:"equation_display",terms:[groupCommas(s)],fit:!0},animation:"show_target",caption:`Which addition makes ${s}?`,spoken:`We want the two place-value parts that add together to make ${wordsFor(s)}, so we split it up column by column.`},{visual_data:{type:"equation_display",terms:[...l,"=",groupCommas(s)],fit:!0},animation:"show_columns",caption:`${u} = ${s}.`,spoken:`Read the number one column at a time: some columns hold a digit, the rest are empty and contribute nothing. So ${wordsFor(s)} is made from ${o.map(d=>wordsFor(d)).join(" and ")}.`},{visual_data:{type:"equation_display",terms:[...r,"=",groupCommas(s)],fit:!0},animation:"write_answer",caption:`${o.join(" + ")} = ${s}.`,spoken:`${capitalise$1(o.map(d=>wordsFor(d)).join(" add "))} makes ${wordsFor(s)}, so that is the addition we want. The empty column contributes nothing.`}]})}},digitValue$1={id:"NPV.P7.M01.digit-value.v1",microSkillId:"NPV.P7.M01",forms:["digit_value_entry","place_value_model_choice"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem);let e,s;const o=t.match(DIGIT_VALUE);if(o)e=Number(o[1]),s=Number(o[2].replace(/,/g,""));else{const r=t.match(DIGIT_VALUE_INLINE);if(!r||(e=Number(r[1]),e===0))return null;const a=findTargetNumber(t,e);if(a===null)return null;s=a}return buildDigitValueDemo(n,s,e)}}
71,adjustment$2={id:"NPV.P7.M01.adjustment.v1",microSkillId:"NPV.P7.M01",forms:["place_value_adjustment"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=answerInt(n);if(t===null)return null;const s=stripThousandCommas(normaliseStem(n.stem)).match(/(\d+) (more|less) than (\d+)/);if(!s)return null;const o=Number(s[1]),r=Number(s[3]),a=s[2]==="more";if((a?r+o:r-o)!==t||!isSinglePlaceChunk(o))return null;const l=a?"+":"−",u={base:r,delta:o,answer:t},d=wordsFor(o),m=String(o).length-1,h=m<=6?PLACE_NAMES$1[m]:"top",f=(m<=6?PLACE_NAMES$1[m]:"ones").replace(/-/g,"_");return buildExample({generatorId:this.id,question:n,title:`What is ${o} ${s[2]} than ${r}?`,computed:u,steps:[{visual_data:{type:"equation_display",terms:[groupCommas(r),l,groupCommas(o)]},animation:"show_parts",caption:`Start from ${r}.`,spoken:`Adding or taking ${d} changes only one column, unless that column rolls past nine.`},{visual_data:{type:"equation_display",terms:[groupCommas(r),l,groupCommas(o),"=","__"]},animation:"pulse_unknown",caption:`${a?"Add":"Subtract"} ${o} in its own column.`,spoken:`We ${a?"add":"take"} ${d} in its own column and leave the other columns the same.`},{visual_data:{type:"comparison",left:r,right:t,symbol:"none"},animation:`write_answer highlight_${f}`,caption:`${r} → ${t}. Only the ${h} changed.`,spoken:`${capitalise$1(wordsFor(t))}. Only one column changed.`}]})}},sequence={id:"NPV.P7.M01.sequence.v1",microSkillId:"NPV.P7.M01",forms:["sequence_continue","sequence_fill_gap"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=answerInt(n);if(t===null)return null;const s=stripThousandCommas(normaliseStem(n.stem)).match(/((?:\d+|\?)(?:\s*,\s*(?:\d+|\?))+)\s*$/);if(!s)return null;const r=s[1].split(/\s*,\s*/).map(f=>f==="?"?null:Number(f));if(r.length<3||r.length>5||r.filter(f=>f===null).length!==1||r.some(f=>f!==null&&!Number.isInteger(f)))return null;let a=null;for(let f=0;f<r.length-1;f++){const g=r[f],b=r[f+1];if(g!==null&&b!==null){a=b-g;break}}if(a===null||a===0)return null;const i=r.indexOf(null);let l=-1;for(let f=0;f<r.length;f++)if(r[f]!==null){l=f;break}if(l<0)return null;const u=r[l]+a*(i-l);if(!Number.isInteger(u)||u<0||u!==t)return null;for(let f=0;f<r.length;f++){const g=r[l]+a*(f-l);if(r[f]!==null&&r[f]!==g)return null}const d=f=>f.map(g=>g===null?null:groupCommas(g)),m=r.map((f,g)=>g===i?u:f),h={step:a,stepSize:Math.abs(a),answer:t};return m.forEach((f,g)=>{h[`v${g}`]=f}),buildExample({generatorId:this.id,question:n,title:"Find the missing number in the sequence.",computed:h,steps:[{visual_data:{type:"sequence",values:[...r],step:a,labels:d(r),wide:!0},animation:"show_sequence",caption:"A sequence with one number missing.",spoken:`The numbers count on in equal steps of ${wordsFor(Math.abs(a))}. Which number fills the gap?`},{visual_data:{type:"sequence",values:[...r],step:a,labels:d(r),emphasis:l,wide:!0},animation:"arc_forward",caption:`Each step ${a>0?"adds":"subtracts"} ${Math.abs(a)}.`,spoken:`Each step ${a>0?"adds":"takes"} ${wordsFor(Math.abs(a))}, changing just one column. Count on or back to the gap.`},{visual_data:{type:"sequence",values:m,step:a,labels:d(m),wide:!0},animation:"fill_blank",caption:`The missing number is ${u}.`,spoken:`${capitalise$1(wordsFor(u))} fills the gap and keeps the equal steps going.`}]})}},comparison={id:"NPV.P7.M01.comparison.v1",microSkillId:"NPV.P7.M01",forms:["comparison"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.visualData;if(!t||t.type!=="comparison")return null;const{left:e,right:s}=t;if(!Number.isInteger(e)||!Number.isInteger(s)||e<0||s<0||e===s)return null;const o=e>s?">":"<";if(n.correctAnswer.trim()!==o)return null;const r=Math.max(String(e).length,String(s).length),a=String(e).padStart(r,"0"),i=String(s).padStart(r,"0");let l=-1;for(let g=0;g<r;g++)if(a[g]!==i[g]){l=g;break}if(l<0)return null;const u=r-1-l,d=Number(a[l])*10**u,m=Number(i[l])*10**u,h=u<=6?PLACE_NAMES$1[u]:"top",f=(u<=6?PLACE_NAMES$1[u]:"ones").replace(/-/g,"_");return buildExample({generatorId:this.id,question:n,title:`Which symbol makes it true: ${e} or ${s}?`,computed:{left:e,right:s,ld:d,rd:m},steps:[{visual_data:{type:"comparison",left:e,right:s,symbol:null},animation:"show_both",caption:"Compare column by column, from the left.",spoken:"A longer-looking number is not always bigger. Line them up and compare each column from the left."}
71,{visual_data:{type:"comparison",left:d,right:m,symbol:null},animation:"compare_place",caption:`First difference in the ${h}.`,spoken:`The first column that differs is the ${h}. There, one number is worth ${wordsFor(d)} and the other ${wordsFor(m)}.`},{visual_data:{type:"comparison",left:e,right:s,symbol:o},animation:`write_answer highlight_${f}`,caption:`${e} ${o} ${s}.`,spoken:`So ${wordsFor(e)} ${o===">"?"is greater than":"is less than"} ${wordsFor(s)}. The open end faces the bigger number, decided by the ${h} column.`}]})}},ordering$2={id:"NPV.P7.M01.ordering.v1",microSkillId:"NPV.P7.M01",forms:["ordering_large_numbers"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.visualData;if(!t||t.type!=="drag_order")return null;const e=t.numbers;if(!Array.isArray(e)||e.length<2||e.length>4)return null;const s=e.filter(d=>typeof d=="number");if(s.length!==e.length||!s.every(d=>Number.isInteger(d)&&d>=0&&d<=9999999)||new Set(s).size!==s.length)return null;const o=t.direction;if(o!=="asc"&&o!=="desc")return null;const r=[...s].sort((d,m)=>o==="asc"?d-m:m-d);if(r.join(",")!==n.correctAnswer.trim())return null;const a=s.indexOf(r[0]),i=d=>d.map(m=>groupCommas(m)),l=o==="asc"?"smallest":"largest",u={count:s.length};return r.forEach((d,m)=>{u[`v${m}`]=d}),buildExample({generatorId:this.id,question:n,title:`Put ${s.length} large numbers in order.`,computed:u,steps:[{visual_data:{type:"sequence",values:[...s],step:0,hide_hop:!0,labels:i(s),wide:!0},animation:"show_numbers",caption:"Line the numbers up to compare.",spoken:`Here are the numbers to put in order. We compare them column by column, biggest place first, to find the ${l}.`},{visual_data:{type:"sequence",values:[...s],step:0,hide_hop:!0,labels:i(s),emphasis:a,wide:!0},animation:"spot_extreme",caption:o==="asc"?"Find the smallest first.":"Find the largest first.",spoken:`Comparing the biggest place first, ${wordsFor(r[0])} is the ${l}.`},{visual_data:{type:"sequence",values:[...r],step:0,hide_hop:!0,labels:i(r),wide:!0},animation:"reorder",caption:o==="asc"?"In order, smallest to largest.":"In order, largest to smallest.",spoken:`Placing the rest the same way puts them all in order, ${l} first.`}]})}};function capitalise$1(n){return n.charAt(0).toUpperCase()+n.slice(1)}const NPV_P7_M01_GENERATORS=[wordToNumeral$1,partition$1,numeralToWordChoice,additionChoice,digitValue$1,adjustment$2,sequence,comparison,ordering$2],MICRO$5="NPV.P7.M02",fmt$1=n=>String(n).replace(/\B(?=(\d{3})+(?!\d))/g,","),FRAME_USABLE=324,FRAME_MARGIN=28;function hopLabelPx(n){const e=(n.match(/\d/g)??[]).length>4?10:14;return n.length*e*.62}function thinCloseHopLabels(n,t,e,s,o){const r=i=>FRAME_MARGIN+(i-t)/(e-t)*FRAME_USABLE,a=[];for(const i of[...n].sort((l,u)=>l-u)){const l=s(i),u=r(i),d=a[a.length-1];d!=null&&u-d.x<(hopLabelPx(d.label)+hopLabelPx(l))/2+4?o!==void 0&&i===o&&d.v!==o?(d.label="",a.push({v:i,x:u,label:l})):a.push({v:i,x:u,label:""}):a.push({v:i,x:u,label:l})}return new Map(a.map(i=>[i.v,i.label]))}const stripNum=n=>Number(String(n).replace(/[£,\s]/g,"")),DIGIT_WORDS=["zero","one","two","three","four","five","six","seven","eight","nine"];function spell(n){if(n>=1e6){const t=Math.floor(n/1e6),e=n%1e6;return`${numberToWordsLong(t)} million${e?` ${numberToWordsLong(e)}`:""}`}return numberToWordsLong(n)}const PLACES={10:["ten","ones"],100:["hundred","tens"],1e3:["thousand","hundreds"],1e4:["ten thousand","thousands"],1e5:["hundred thousand","ten thousands"]},placeWord=n=>PLACES[n]?.[0]??null,ROUND_COLUMN={10:"tens",100:"hundreds",1e3:"thousands",1e4:"ten thousands",1e5:"hundred thousands"},decidingPlaceWord=n=>PLACES[n]?.[1]??null,VALID_P=new Set([10,100,1e3,1e4,1e5]),roundTo=(n,t)=>Math.round(n/t)*t,decidingDigit=(n,t)=>Math.floor(n/(t/10))%10;function numbersIn(n){return(normaliseStem(n).match(/\d[\d,]*/g)??[]).map(stripNum)}function nearestP(n){const t=normaliseStem(n).match(/nearest\s+£?([\d,]+)/i);return t?stripNum(t[1]):null}const roundOnLine={id:"NPV.P7.M02.round-line.v1",microSkillId:MICRO$5,forms:["rounding_entry","symbolic_choice","context_calculation"],generate(n){if(n.microSkillId!==this.microSkillId||n.mathematicalForm&&!this.forms.includes(n.mathematicalForm))return null;const t=normaliseStem(n.stem),e=nearestP(t);if(e===null||!VALID_P.has(e))return null;const s=numbersIn(t),o=s.indexOf(e),r=o===-1?s.slice():[...s.slice(0,o),...s.slice(o+1)];if(r.length===0)return null;const a=Math.max(...r);if(!Number.isInteger(a)||a<=0)return null;const i=roundTo(a,e);if(i!==stripNum(n.correctAnswer))return null;const l=Math.floor(a/e)*e,u=l+e,d=l+e/2,m=e/10,h=decidingDigit(a,e),f=h>=5,g=a===d,b=fmt$1(a),x={from:l,to:a,label:b},y=placeWord(e),E=k=>({type:"number_line",min:l,max:u,step:m,marked:[d],...k});return buildExample({generatorId:this.id,question:n,title:`How do we round ${b} to the nearest ${y}?`,computed:{number:a,place:e,deciding:h,answer:i},steps:[{visual_data:E({}),animation:"show_neighbours",caption:`Round to the nearest ${y}.`,spoken:`Rounding to the nearest ${y} means finding the ${y}
71 that ${spell(a)} is closest to. It sits between ${spell(l)} and ${spell(u)}, and the halfway mark between them is ${spell(d)}.`},{visual_data:E({hops:[x]}),animation:"place_value",caption:g?`${capitalised(decidingPlaceWord(e))} digit ${h}: exactly halfway.`:`${capitalised(decidingPlaceWord(e))} digit ${h}: ${f?"past":"before"} halfway.`,spoken:g?`To round to the nearest ${y}, we look one column to the right of the ${ROUND_COLUMN[e]}, at the ${decidingPlaceWord(e)}. The ${decidingPlaceWord(e)} digit is ${DIGIT_WORDS[h]}, so the number sits exactly on the halfway mark. When a number is exactly halfway, we always round up.`:`To round to the nearest ${y}, we look one column to the right of the ${ROUND_COLUMN[e]}, at the ${decidingPlaceWord(e)} — not at the last digit. The ${decidingPlaceWord(e)} digit is ${DIGIT_WORDS[h]}. ${f?"Five or more means past halfway, so we round up.":"Below five means before halfway, so we round down."}`},{visual_data:E({hops:[x],unknown:i,conclude_label:`${b} rounds to ${fmt$1(i)}`}),animation:"land",caption:`${f?"Round up":"Round down"} to the nearest ${y}.`,spoken:`${f?"Rounding up":"Rounding down"} takes us to the ${f?"higher":"lower"} ${y} on the line. So ${spell(a)} rounded to the nearest ${y} is ${spell(i)}.`}]})}},reverseIdentify={id:"NPV.P7.M02.reverse.v1",microSkillId:MICRO$5,forms:["rounding_reverse_identify"],generate(n){if(n.microSkillId!==this.microSkillId||n.mathematicalForm&&!this.forms.includes(n.mathematicalForm))return null;const t=normaliseStem(n.stem),e=nearestP(t);if(e===null||!VALID_P.has(e))return null;const s=numbersIn(t).find(h=>h!==e&&h>0&&h%e===0);if(s===void 0)return null;const o=e/2,r=s-o,a=s+o,i=placeWord(e),l=h=>({type:"equation_display",terms:h});if(/\blargest\b/i.test(t)){const h=a-1;return h!==stripNum(n.correctAnswer)?null:buildExample({generatorId:this.id,question:n,title:`Largest whole number that rounds to ${fmt$1(s)}?`,computed:{target:s,place:e,answer:h},steps:[{visual_data:l([fmt$1(s),"nearest",fmt$1(e)]),animation:"show_target",caption:"Round to this — find the biggest.",spoken:`We want the largest whole number that rounds to ${spell(s)} at the nearest ${i}.`},{visual_data:l([fmt$1(r),"to",fmt$1(a)]),animation:"show_range",caption:"This whole range rounds here.",spoken:`Every number from ${spell(r)} up to just below ${spell(a)} rounds to ${spell(s)}.`},{visual_data:l([fmt$1(h)]),animation:"land",caption:"The largest is just below the top.",spoken:`The largest is ${spell(h)} — one less than ${spell(a)}, where numbers start rounding up.`}]})}if(/\bsmallest\b/i.test(t)){const h=r;return h!==stripNum(n.correctAnswer)?null:buildExample({generatorId:this.id,question:n,title:`Smallest whole number that rounds to ${fmt$1(s)}?`,computed:{target:s,place:e,half:o,answer:h},steps:[{visual_data:l([fmt$1(s),"nearest",fmt$1(e)]),animation:"show_target",caption:"Round to this — find the smallest.",spoken:`We want the smallest whole number that rounds to ${spell(s)} at the nearest ${i}.`},{visual_data:l([fmt$1(s),"−",fmt$1(o)]),animation:"show_range",caption:"Take half the rounding step off.",spoken:`Numbers round down to ${spell(s)} until we drop half a step, ${spell(o)}, below it.`},{visual_data:l([fmt$1(h)]),animation:"land",caption:"The smallest that still rounds here.",spoken:`The smallest is ${spell(h)}. Below it, numbers round to the next figure down.`}]})}const u=n.options??[];if(u.length===0)return null;const d=u.find(h=>roundTo(stripNum(h),e)===s);if(d===void 0||d.trim()!==n.correctAnswer.trim())return null;const m=stripNum(d);return buildExample({generatorId:this.id,question:n,title:`Which number rounds to ${fmt$1(s)}?`,computed:{target:s,place:e,answer:m},steps:[{visual_data:l([fmt$1(s),"nearest",fmt$1(e)]),animation:"show_target",caption:"Which choice rounds here?",spoken:`Which of the choices rounds to ${spell(s)} at the nearest ${i}?`},{visual_data:l([fmt$1(r),"to",fmt$1(a)]),animation:"show_range",caption:"Only this range rounds here.",spoken:`A number rounds to ${spell(s)} only if it is between ${spell(r)} and ${spell(a)}.`},{visual_data:l([fmt$1(m)]),animation:"land",caption:"This one is inside the range.",spoken:`Only ${spell(m)} sits inside that range, so it rounds to ${spell(s)}.`}]})}},contrastProbe={id:"NPV.P7.M02.probe.v1",microSkillId:MICRO$5,forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId||n.mathematicalForm&&!this.forms.includes(n.mathematicalForm))return null;const t=normaliseStem(n.stem),e=nearestP(t);if(e===null||!VALID_P.has(e))return null;const s=n.options??[];if(s.length===0)return null;const o=placeWord(e),r=e/2,a=f=>({type:"equation_display",terms:f});if(/\bodd one out\b/i.test(t)){const f=numbersIn(t).find(P=>P!==e&&P>0&&P%e===0);if(f===void 0)return null;const g=s.find(P=>roundTo(stripNum(P),e)!==f);if(g===void 0||g.trim()!==n.correctAnswer.trim())return null;const b=stripNum(g),x=f-r,y=f+r,E=s.map(P=>stripNum(P)),k=e/10,T=x-k,A=y+k,N=thinCloseHopLabels(E,T,A,fmt$1,b),S=E.filter(P=>P!==b),M=thinCloseHopLabels(S,T,A,fmt$1),L=(P,F)=>P.map(D=>({from:D,to:D,label:F.get(D)??fmt$1(D)}));return buildExample({generatorId:this.id,question:n,title:`Which number does NOT round to ${fmt$1(f)}?`,computed:{target:f,place:e,answer:b},steps:[{visual_data:a([fmt$1(f),"nearest",fmt$1(e)]),animation:"show_target",caption:"Three match this, one does not.",spoken:`Three of these round to ${spell(f)} at the nearest ${o}; one does not.`},{visual_data:{type:"number_line",min:T,max:A,step:k,marked:[x,y],hops:L(E,N)}
71,animation:"show_range",caption:"These are the ones that round here.",spoken:`Only numbers from ${spell(x)} up to just below ${spell(y)} round to ${spell(f)}.`},{visual_data:{type:"number_line",min:T,max:A,step:k,marked:[x,y],hops:L(S,M),unknown:b},animation:"land",caption:"This one is outside — the odd one.",spoken:`But ${spell(b)} is outside that range, so it is the odd one out.`}]})}const i=t.match(/rounds?\s+£?([\d,]+)\s+to the nearest/i);if(!i)return null;const l=stripNum(i[1]);if(!Number.isInteger(l)||l<=0)return null;const u=roundTo(l,e),d=s.find(f=>stripNum(f)===u);if(d===void 0||d.trim()!==n.correctAnswer.trim())return null;const m=decidingDigit(l,e),h=m>=5;return buildExample({generatorId:this.id,question:n,title:`What is ${fmt$1(l)} rounded to the nearest ${o}?`,computed:{number:l,place:e,deciding:m,answer:u},steps:[{visual_data:a([fmt$1(l),"nearest",fmt$1(e)]),animation:"show_target",caption:"Round it properly.",spoken:`We want ${spell(l)} rounded to the nearest ${o}. The digit that decides is the one just after that place — the ${decidingPlaceWord(e)} digit — not the last digit.`},{visual_data:a([fmt$1(l),"→","digit",m]),animation:"place_value",caption:`Look at the ${decidingPlaceWord(e)} digit.`,spoken:`The ${decidingPlaceWord(e)} digit is ${DIGIT_WORDS[m]}, so that is the digit that tells us which way to round.`},{visual_data:a([fmt$1(u)]),animation:"land",caption:h?"Round up to the right figure.":"Round down to the right figure.",spoken:`${capitalised(DIGIT_WORDS[m])} is ${h?"five or more, so round up":"below five, so round down"}: ${spell(l)} rounds to ${spell(u)}.`}]})}},reasoningJudgement={id:"NPV.P7.M02.judge.v1",microSkillId:MICRO$5,forms:["context_reasoning_judgement"],generate(n){if(n.microSkillId!==this.microSkillId||n.mathematicalForm&&!this.forms.includes(n.mathematicalForm))return null;const t=normaliseStem(n.stem),e=nearestP(t);if(e===null||!VALID_P.has(e))return null;const s=numbersIn(t).filter(b=>b!==e);if(s.length<2)return null;const[o,r]=s,a=roundTo(o,e),i=roundTo(r,e),l=a===i;if(isAffirmative(n.correctAnswer)!==l)return null;const u=placeWord(e),d=b=>({type:"equation_display",terms:b}),m=decidingDigit(o,e),h=decidingDigit(r,e),f=m>=5,g=h>=5;return buildExample({generatorId:this.id,question:n,title:`Do ${fmt$1(o)} and ${fmt$1(r)} round to the same figure?`,computed:{a:o,b:r,place:e,roundedA:a,roundedB:i,decidingA:m,decidingB:h,answer:l?a:i},steps:[{visual_data:d([fmt$1(o),"and",fmt$1(r)]),animation:"show_target",caption:`Round both to the nearest ${u}.`,spoken:`Round both figures to the nearest ${u}, then compare them.`},{visual_data:d([fmt$1(o),"→",fmt$1(a)]),animation:"place_value",caption:`${capitalised(decidingPlaceWord(e))} digit ${m}: ${f?"up":"down"}.`,spoken:`First, ${spell(o)}: the ${decidingPlaceWord(e)} digit is ${DIGIT_WORDS[m]}, ${f?"five or more, so round up":"below five, so round down"} — it rounds to ${spell(a)}.`},{visual_data:d([fmt$1(r),"→",fmt$1(i),l?"— same":"— different"]),animation:"land",caption:`${capitalised(decidingPlaceWord(e))} digit ${h}: ${g?"up":"down"}.`,spoken:`Then ${spell(r)}: the ${decidingPlaceWord(e)} digit is ${DIGIT_WORDS[h]}, ${g?"five or more, so round up":"below five, so round down"} — it rounds to ${spell(i)}, ${l?"the same figure — yes":"a different figure — no"}.`}]})}},NPV_P7_M02_GENERATORS=[roundOnLine,reverseIdentify,contrastProbe,reasoningJudgement];function parseRatioAnswer(n){if(!n.includes(":"))return null;const t=n.split(":").map(s=>s.trim());if(t.length<2)return null;const e=t.map(Number);return e.some(s=>!Number.isInteger(s)||s<0||s>999)?null:e}const WHOLE_WORDS=/\b(total|whole|all|altogether)\b/i;function light(n){return n.toLowerCase().replace(/[^a-z]/g,"").replace(/(ing|es|s|ed)$/,"")}const NOUN_STOP$1=new Set(["and","with","for"]),NOUN_FILLER=new Set(["the","a","an","of","to","in","on","at"]);function extractPairs(n){const t=normaliseStem(n).replace(/[;,.?]/g," ").split(/\s+/),e=[];for(let s=0;s<t.length;s++){const o=t[s].match(/^[£$]?(\d+)$/);if(!o)continue;const r=Number(o[1]),a=[];for(let i=s+1;i<t.length&&a.length<4&&!/^[£$]?\d+$/.test(t[i]);i++){const l=t[i].toLowerCase().replace(/[^a-z']/g,"");if(!l)continue;if(NOUN_STOP$1.has(l))break;if(NOUN_FILLER.has(l))continue;const u=light(l);u&&a.push(u)}e.push({n:r,words:a})}return e}function extractComparisonPairs(n){const t=normaliseStem(n).replace(/[;,.?]/g," "),e=t.search(/\b(write|which)\b/i),s=(e>
710?t.slice(0,e):t).split(/\s+/).filter(Boolean),o=i=>/^[£$]?\d+$/.test(i),r=i=>{const l=i.toLowerCase().replace(/[^a-z']/g,"");return!l||NOUN_FILLER.has(l)?null:NOUN_STOP$1.has(l)?"":light(l)||null},a=[];return s.forEach((i,l)=>{const u=i.match(/^[£$]?(\d+)$/);if(!u)return;const d=[];for(let m=l+1;m<s.length&&d.length<3&&!o(s[m]);m++){const h=r(s[m]);if(h==="")break;h&&d.push(h)}for(let m=l-1,h=0;m>=0&&h<2&&!o(s[m]);m--){const f=r(s[m]);if(f==="")break;f&&(d.push(f),h++)}a.push({n:Number(u[1]),words:d})}),a}function ownerLabel(n){const t=n.split(/\s+/)[0]??n,e=light(t);return e?capitalised(e):shortLabel(n)}function overlap(n,t){let e=0;for(const s of n)t.words.includes(s)&&e++;return e}function nameWordsOf(n){return n.split(/\s+/).map(light).filter(t=>t.length>0)}function countsForNames(n,t){const e=new Set,s=[];for(const o of n){const r=nameWordsOf(o);let a=-1,i=0;if(t.forEach((l,u)=>{if(e.has(u))return;const d=overlap(r,l);d>i&&(i=d,a=u)}),a===-1||i===0)return null;e.add(a),s.push(t[a].n)}return s}function parseAsk(n){const t=normaliseStem(n).replace(/\?$/,""),e=t.match(/ratio of (.+?) to (.+?) to (.+?)$/i);if(e)return[e[1].trim(),e[2].trim(),e[3].trim()];const s=t.match(/ratio of (.+?) to (.+?)$/i);return s?[s[1].trim(),s[2].trim()]:null}function shortLabel(n){const s=(n.replace(WHOLE_WORDS,"").replace(/\b(the|number|of|slices|slice|parts|part|time|cups|cup|beads|bead|pens|pen|players|player|children|child)\b/gi," ").replace(/[^a-zA-Z ]/g," ").trim()||n).split(/\s+/).filter(Boolean)[0]||n.split(/\s+/).slice(-1)[0],o=capitalised(s.toLowerCase());return o.length>12?o.slice(0,12):o}function verbAgreement(n){const e=(n.trim().split(/\s+/).pop()??"").toLowerCase().replace(/[^a-z]/g,"");return e.endsWith("ss")?"is":e.endsWith("s")?"are":"is"}function contrastNote(n,t,e){if(!e||!e.length)return"";const s=n+t,o=[];return e.some(a=>/^\s*\d+\s*\/\s*\d+\s*$/.test(a))&&o.push("A ratio like this compares one part to another directly — that is different from a fraction, which compares a part to the whole."),e.some(a=>{const i=a.trim().match(/^(\d+)\s*:\s*(\d+)$/);if(!i)return!1;const l=Number(i[1]),u=Number(i[2]);return l===s&&u!==s||u===s&&l!==s})&&o.push(`${capitalised(w$6(s))} is the total, not one of the two parts — this ratio compares the two parts to each other.`),o.length?` ${o.join(" ")}`:""}function axisStep(n){return n<=12?1:2}function w$6(n){return numberToWords(n)}function buildRatioDemo(n){const{labels:t,values:e}=n,s=Math.max(...e,1),o=t.map((u,d)=>({label:u,count:e[d]})),r={type:"bar_builder",categories:o,max:s,step:axisStep(s),built:e.map(()=>0)},a={type:"bar_builder",categories:o,max:s,step:axisStep(s),built:e.slice()},i=[];e.forEach((u,d)=>{d>0&&i.push(":"),i.push(String(u))});const l={type:"equation_display",terms:i};return buildExample({generatorId:n.generatorId,question:n.question,title:n.title,computed:n.computed,steps:[{visual_data:r,animation:"show_data",caption:n.step1.caption,spoken:n.step1.spoken},{visual_data:a,animation:"grow_bars",caption:n.step2.caption,spoken:n.step2.spoken},{visual_data:l,animation:"write",caption:n.step3.caption,spoken:n.step3.spoken}]})}const directWriteRatio={id:"RP.P2.M01.write-ratio.v1",microSkillId:"RP.P2.M01",forms:["ratio_notation","comparison_statement","misconception_contrast_probe"],generate(n){const t=parseRatioAnswer(n.correctAnswer);if(!t||t.length!==2)return null;const e=parseAsk(n.stem);if(!e||e.length!==2||WHOLE_WORDS.test(e[1]))return null;const s=extractPairs(n.stem),o=countsForNames(e,s);if(!o||o[0]!==t[0]||o[1]!==t[1]||o.some(m=>m<1||m>20))return null;
71const[r,a]=[shortLabel(e[0]),shortLabel(e[1])],[i,l]=o,u=verbAgreement(r),d=contrastNote(i,l,n.options);return buildRatioDemo({generatorId:this.id,question:n,title:`Write the ratio of ${r.toLowerCase()} to ${a.toLowerCase()}`,labels:[r,a],values:[i,l],computed:{first:i,second:l},step1:{caption:`${r} ${u} named first, then ${a.toLowerCase()}.`.slice(0,72),spoken:`We want the ratio of ${r.toLowerCase()} to ${a.toLowerCase()}. ${capitalised(r.toLowerCase())} ${u} named first, so that group comes first.`},step2:{caption:`First ${w$6(i)}, then ${w$6(l)} — keep that order.`.slice(0,72),spoken:`There are ${w$6(i)} ${r.toLowerCase()} and ${w$6(l)} ${a.toLowerCase()}. We keep them in the order they were named.`},step3:{caption:`${r} to ${a.toLowerCase()} = ${i} : ${l}.`.slice(0,72),spoken:`So the ratio is ${w$6(i)} to ${w$6(l)}. We write ${w$6(i)}, then a colon, then ${w$6(l)}.${d}`}})}};function extractListCounts(n){const e=normaliseStem(n).match(/repeats?:?\s*([a-z]+(?:\s*,\s*[a-z]+)+)/i);if(!e)return[];const s=e[1].split(/\s*,\s*/).map(r=>light(r)).filter(Boolean),o=new Map;for(const r of s)o.set(r,(o.get(r)??0)+1);return Array.from(o.entries()).map(([r,a])=>({n:a,words:[r]}))}const listRepeatRatio={id:"RP.P2.M01.list-ratio.v1",microSkillId:"RP.P2.M01",forms:["ratio_notation"],generate(n){const t=parseRatioAnswer(n.correctAnswer);if(!t||t.length!==2)return null;const e=parseAsk(n.stem);if(!e||e.length!==2||WHOLE_WORDS.test(e[1]))return null;const s=extractListCounts(n.stem);if(s.length<2)return null;const o=countsForNames(e,s);if(!o||o[0]!==t[0]||o[1]!==t[1]||o.some(d=>d<1||d>20))return null;
71const[r,a]=[shortLabel(e[0]),shortLabel(e[1])],[i,l]=o,u=verbAgreement(r);return buildRatioDemo({generatorId:this.id,question:n,title:`Write the ratio of ${r.toLowerCase()} to ${a.toLowerCase()} in one repeat`,labels:[r,a],values:[i,l],computed:{first:i,second:l},step1:{caption:"Count each colour in one repeat of the pattern.",spoken:"The pattern repeats a run of colours. To write the ratio, we count how many of each colour appear in one repeat."},step2:{caption:`${capitalised(w$6(i))} ${r.toLowerCase()}, then ${w$6(l)} ${a.toLowerCase()}.`.slice(0,72),spoken:`In one repeat there are ${w$6(i)} ${r.toLowerCase()} and ${w$6(l)} ${a.toLowerCase()}. ${capitalised(r.toLowerCase())} ${u} named first, so ${r.toLowerCase()} comes first.`},step3:{caption:`${r} to ${a.toLowerCase()} = ${i} : ${l}.`.slice(0,72),spoken:`So the ratio is ${w$6(i)} to ${w$6(l)}. We write ${w$6(i)}, then a colon, then ${w$6(l)}.`}})}},comparisonReverse={id:"RP.P2.M01.comparison-reverse.v1",microSkillId:"RP.P2.M01",forms:["comparison_statement"],generate(n){const t=parseRatioAnswer(n.correctAnswer);if(!t||t.length!==2)return null;const e=parseAsk(n.stem);if(!e||e.length!==2||WHOLE_WORDS.test(e[1]))return null;const s=e.map(d=>d.replace(/['’]s\b/gi,"").trim()),o=extractComparisonPairs(n.stem),r=countsForNames(s,o);if(!r||r.length!==2||r[0]!==t[0]||r[1]!==t[1]||r.some(d=>d<1||d>20))return null;const[a,i]=[ownerLabel(s[0]),ownerLabel(s[1])],[l,u]=r;return buildRatioDemo({generatorId:this.id,question:n,title:`Write the ratio of ${a} to ${i}`,labels:[a,i],values:[l,u],computed:{first:l,second:u},step1:{caption:`${a} is named first, then ${i}.`.slice(0,72),spoken:`The ratio asks for ${a} first, then ${i} — we keep that order, whichever way round the sentence gives them.`},step2:{caption:`${a} has ${w$6(l)}, ${i} has ${w$6(u)}.`.slice(0,72),spoken:`We match each amount to its group: ${w$6(l)} for ${a} and ${w$6(u)} for ${i}.`},step3:{caption:`${a} to ${i} = ${l} : ${u}.`.slice(0,72),spoken:`So the ratio is ${w$6(l)} to ${w$6(u)}: we write ${w$6(l)}, a colon, then ${w$6(u)}.`}})}},partWholeRatio={id:"RP.P2.M01.part-whole.v1",microSkillId:"RP.P2.M01",forms:["ratio_notation"],generate(n){const t=parseRatioAnswer(n.correctAnswer);if(!t||t.length!==2)return null;const e=parseAsk(n.stem);if(!e||e.length!==2||!WHOLE_WORDS.test(e[1]))return null;const s=extractPairs(n.stem);if(s.length<1)return null;const[o,r]=t,a=countsForNames([e[0]],s);if(!a||a[0]!==o)return null;const i=s.map(y=>y.n),u=(i.length>=2?i[0]+i[1]:null)===r,d=i.includes(r);if(!u&&!d||o<1||r<=o||r>40)return null;const m=shortLabel(e[0]),h=u?i.find(y=>y!==o)??r-o:r-o,f=Math.max(o,r,1),g={type:"bar_builder",categories:[{label:m,count:o}],max:f,step:axisStep(f),built:[o]},b={type:"bar_builder",categories:[{label:m,count:o},{label:"Total",count:r}],max:f,step:axisStep(f),built:[o,r]},x={type:"equation_display",terms:[String(o),":",String(r)]};return buildExample({generatorId:this.id,question:n,title:`Write the ratio of ${m.toLowerCase()} to the total`,computed:{part:o,whole:r,other:h},steps:[{visual_data:g,animation:"show_data",caption:`We start with ${m.toLowerCase()}: ${o}.`.slice(0,72),spoken:`This ratio compares one part with the whole group. We start with what is given — ${capitalised(m.toLowerCase())}.`},{visual_data:b,animation:"grow_bars",caption:u?`Total = ${o} + ${h} = ${r}.`.slice(0,72):`The whole is ${r} altogether.`.slice(0,72),spoken:u?`To find the total we add every group together: ${w$6(o)} and ${w$6(h)} make ${w$6(r)}.`:`The whole group is ${w$6(r)} altogether — that is the second number.`},{visual_data:x,animation:"write",caption:`${m} to total = ${o} : ${r}.`.slice(0,72),spoken:`So the ratio of the part to the whole is ${w$6(o)} to ${w$6(r)}. We write ${w$6(o)}, a colon, then ${w$6(r)}.`}]})}},threePartRatio={id:"RP.P2.M01.three-part.v1",microSkillId:"RP.P2.M01",forms:["ratio_notation"],generate(n){const t=parseRatioAnswer(n.correctAnswer);if(!t||t.length!==3)return null;const e=parseAsk(n.stem);if(!e||e.length!==3)return null;const s=extractPairs(n.stem),o=countsForNames(e,s);if(!o||o.length!==3||o.some((d,m)=>d!==t[m])||o.some(d=>d<1||d>20))return null;const r=e.map(shortLabel),[a,i,l]=o,u=`${w$6(a)}, ${w$6(i)} and ${w$6(l)}`;return buildRatioDemo({generatorId:this.id,question:n,title:`Write the ratio of ${r[0].toLowerCase()} to ${r[1].toLowerCase()} to ${r[2].toLowerCase()}`,labels:r,values:o,computed:{first:a,second:i,third:l},step1:{caption:"Three groups, named in order.",spoken:"A ratio can compare more than two groups. We name them in the order given."},step2:{caption:`Counts in order: ${w$6(a)}, ${w$6(i)}, ${w$6(l)}.`.slice(0,72),spoken:`There are ${u}. We keep them in that order.`},step3:{caption:`The ratio is ${a} : ${i} : ${l}
71.`.slice(0,72),spoken:`So the ratio is ${w$6(a)} to ${w$6(i)} to ${w$6(l)}, each separated by a colon.`}})}},RATIO_IN_STEM=/(\d+)\s*:\s*(\d+)/,interpretRatio={id:"RP.P2.M01.interpret.v1",microSkillId:"RP.P2.M01",forms:["ratio_interpretation"],generate(n){const t=Number(n.correctAnswer);if(!Number.isInteger(t))return null;const e=normaliseStem(n.stem),s=e.match(RATIO_IN_STEM);if(!s)return null;const o=Number(s[1]),r=Number(s[2]);if(o<1||r<1||o>20||r>20)return null;const a=e.match(/ratio of (.+?) to (.+?)(?: is| are|,| in|\.|$)/i),i=a?shortLabel(a[1]):"First",l=a?shortLabel(a[2]):"Second";if(/how many equal parts|one full set|in total|altogether|how many .* in (?:one|1) (?:full )?(?:set|group)/i.test(e)){if(t!==o+r)return null;const y=o+r,E=Math.max(o,r,1),k={type:"bar_builder",categories:[{label:i,count:o},{label:l,count:r}],max:E,step:axisStep(E),built:[0,0]},T={type:"bar_builder",categories:[{label:i,count:o},{label:l,count:r}],max:E,step:axisStep(E),built:[o,r]},A={type:"equation_display",terms:[String(o),"+",String(r),"=",String(y)]};return buildExample({generatorId:this.id,question:n,title:`How many equal parts in the ratio ${o} : ${r}?`,computed:{first:o,second:r,answer:y},steps:[{visual_data:k,animation:"show_data",caption:`The ratio ${o} : ${r} has two groups.`.slice(0,72),spoken:`In this ratio, the first group has ${w$6(o)} parts and the second has ${w$6(r)} parts.`},{visual_data:T,animation:"grow_bars",caption:"Count every part in both groups.",spoken:"To find how many equal parts there are altogether, we add every group's parts together."},{visual_data:A,animation:"stack_total",caption:`${o} + ${r} = ${y} equal parts.`.slice(0,72),spoken:`${capitalised(w$6(o))} parts and ${w$6(r)} parts make ${w$6(y)} equal parts in total.`}]})}const d=e.match(/how many ([a-z]+)/i);let m=null;if(d){const y=light(d[1]);a&&nameWordsOf(a[1]).includes(y)?m=o:a&&nameWordsOf(a[2]).includes(y)&&(m=r)}if(m===null)if(t===o)m=o;else return null;if(m!==t)return null;const h=m===o&&t===o,f=Math.max(o,r,1),g={type:"bar_builder",categories:[{label:"First",count:o},{label:"Second",count:r}],max:f,step:axisStep(f),built:[o,r]},b={type:"bar_builder",categories:[{label:i,count:o},{label:l,count:r}],max:f,step:axisStep(f),built:[o,r]},x={type:"equation_display",terms:[String(m)]};return buildExample({generatorId:this.id,question:n,title:`Reading a ratio of ${o} : ${r}`,computed:{first:o,second:r,answer:m},steps:[{visual_data:g,animation:"show_data",caption:`The ratio ${o} : ${r} has a first and a second group.`.slice(0,72),spoken:"In a ratio, the groups are named in order — a first group and a second group."},{visual_data:b,animation:"grow_bars",caption:`${i} come first, ${l.toLowerCase()} second.`.slice(0,72),spoken:`In the ratio, ${i.toLowerCase()} are named first, so they are the first number. ${capitalised(l.toLowerCase())} are the second number.`},{visual_data:x,animation:"write",caption:h?`We want ${i.toLowerCase()} — the first number: ${m}.`.slice(0,72):`We want ${l.toLowerCase()} — the second number: ${m}.`.slice(0,72),spoken:h?`We are asked about ${i.toLowerCase()}, which is the first-named group — so the answer is ${w$6(m)}.`:`We are asked about ${l.toLowerCase()}, which is the second-named group — so the answer is ${w$6(m)}.`}]})}},REST_OF_TOTAL_A=/^(\d+) of the (\d+) [a-z ]+ are ([a-z]+); the rest are ([a-z]+)/i,REST_OF_TOTAL_B=/^of (\d+) [a-z ]+, (\d+) are ([a-z]+) and the rest are ([a-z]+)/i,FOR_EVERY_TO_ALL=/^A [a-z ]+ has (\d+) ([a-z]+) for every (\d+) ([a-z]+)\. write the ratio of ([a-z]+) to all/i,HALF_SOLD=/^A [a-z ]+ has (\d+) ([a-z]+) and (\d+) ([a-z]+) [a-z]+\. half the \2 [a-z]+ are sold/i;function contextDemo(n,t,e,s,o,r){const[a,i]=s,l=Math.max(...o.values,a,i,1),u=o.labels.map((g,b)=>({label:g,count:o.values[b]})),d=e.map((g,b)=>({label:g,count:s[b]})),m={type:"bar_builder",categories:u,max:l,step:axisStep(l),built:o.values.slice()},h={type:"bar_builder",categories:d,max:l,step:axisStep(l),built:s.slice()},f={type:"equation_display",terms:[String(a),":",String(i)]};return buildExample({generatorId:n,question:t,title:`Work out the ratio of ${e[0].toLowerCase()} to ${e[1].toLowerCase()}`,computed:{first:a,second:i},steps:[{visual_data:m,animation:"show_data",caption:`Start with what is given: ${o.labels[0]} ${o.values[0]}, ${o.labels[1].toLowerCase()} ${o.values[1]}.`.slice(0,72),spoken:"Before we can write the ratio, we work out how many are in each group."},{visual_data:h,animation:"derive",caption:r.caption,spoken:r.spoken},{visual_data:f,animation:"write",caption:`${e[0]} to ${e[1].toLowerCase()} = ${a} : ${i}
71.`.slice(0,72),spoken:`So the ratio, first-named group first, is ${w$6(a)} to ${w$6(i)}.`}]})}const contextReasoning={id:"RP.P2.M01.context.v1",microSkillId:"RP.P2.M01",forms:["context_reasoning"],generate(n){const t=parseRatioAnswer(n.correctAnswer);if(!t||t.length!==2)return null;const e=normaliseStem(n.stem),[s,o]=t;let r;if((r=e.match(REST_OF_TOTAL_A))||(r=e.match(REST_OF_TOTAL_B))){Number(r[1]===void 0?NaN:r[1]);const a=/the rest are/i.test(e)&&/^(\d+) of the/i.test(e),i=Number(a?r[1]:r[2]),l=Number(a?r[2]:r[1]),u=r[3],d=r[4],m=l-i;return i<1||m<1||l<2||i!==s||m!==o?null:contextDemo(this.id,n,[shortLabel(u),shortLabel(d)],[i,m],{labels:["Total",shortLabel(u)],values:[l,i]},{caption:`Rest = ${l} − ${i} = ${m}.`.slice(0,72),spoken:`There are ${w$6(l)} in all and ${w$6(i)} of the first kind, so the rest — ${w$6(m)} — are the second kind.`})}if(r=e.match(FOR_EVERY_TO_ALL)){const a=Number(r[1]),i=r[2],l=Number(r[3]),u=r[4],d=light(r[5]),m=a+l;let h;if(nameWordsOf(i).includes(d))h=a;else if(nameWordsOf(u).includes(d))h=l;else return null;if(h!==s||m!==o)return null;const f=shortLabel(r[5]);return contextDemo(this.id,n,[f,"Total"],[h,m],{labels:[shortLabel(i),shortLabel(u)],values:[a,l]},{caption:`Total = ${a} + ${l} = ${m}.`.slice(0,72),spoken:`One group has ${w$6(a)} and the other ${w$6(l)}, so the whole is ${w$6(m)}. The part named first is ${w$6(h)}.`})}if(r=e.match(HALF_SOLD)){const a=Number(r[1]),i=r[2],l=Number(r[3]),u=r[4];if(a%2!==0)return null;const d=a/2;return d!==s||l!==o?null:contextDemo(this.id,n,[shortLabel(i),shortLabel(u)],[d,l],{labels:[shortLabel(i),shortLabel(u)],values:[a,l]},{caption:`Half of ${a} is ${d}; ${l} unchanged.`.slice(0,72),spoken:`Half of the first group are sold, so ${w$6(d)} are left. The second group stays at ${w$6(l)}.`})}return null}},RP_P2_M01_GENERATORS=[directWriteRatio,listRepeatRatio,comparisonReverse,partWholeRatio,threePartRatio,interpretRatio,contextReasoning],MICRO$4="RP.P2.M02",FORMS$5=["symbolic_numeric_entry","context_calculation","context_reasoning","equation_completion","comparison_statement","misconception_contrast_probe"];function gcd$2(n,t){for(;t;)[n,t]=[t,n%t];return n}function readRatio(n){const t=normaliseStem(n),e=t.match(/(\d+)\s*:\s*(\d+)/);if(e)return[Number(e[1]),Number(e[2])];const s=t.match(/\d+/g);return!s||s.length<2?null:[Number(s[0]),Number(s[1])]}function askedPart(n){const t=normaliseStem(n);return/\d+\s*:\s*\?/.test(t)?2:/\?\s*:\s*\d+/.test(t)||/\bfirst (number|part)\b/i.test(t)?1:/\bsecond (number|part)\b/i.test(t)?2:null}function buildRatioExample(n,t,e,s,o,r,a,i){const l=numberToWords;let u=2;for(;t%u!==0||e%u!==0;)u++;const d=t/u,m=e/u,h=s/u,f=h>1,g=[];for(let S=2;S<u;S++)g.push(S);const b=S=>{const M=t%S===0,L=e%S===0;return!M&&!L?`${l(S)} does not divide ${l(t)} or ${l(e)}`:M?`${l(S)} does not divide ${l(e)}`:`${l(S)} does not divide ${l(t)}`},x=(g.length?`Try ${g.join(", ")} — no. ${t} ÷ ${u} = ${d}, ${e} ÷ ${u} = ${m}.`:`${t} ÷ ${u} = ${d}, ${e} ÷ ${u} = ${m}.`).slice(0,72),y=g.length?`We try each number from two: ${g.map(b).join("; ")}. Then ${l(u)}: does ${l(u)} divide both ${l(t)} and ${l(e)}? Yes`:`Try the smallest number bigger than one: does ${l(u)} divide both ${l(t)} and ${l(e)}? Yes`,E=f?`${y} — ${l(t)} divided by ${l(u)} is ${l(d)}, and ${l(e)} divided by ${l(u)} is ${l(m)}. But ${l(d)} and ${l(m)} still share a factor, so we keep going.`:`${y} — ${l(t)} divided by ${l(u)} is ${l(d)}, and ${l(e)} divided by ${l(u)} is ${l(m)}. Now check again: nothing bigger than one divides both ${l(d)} and ${l(m)}, so we stop.`,T=((a==="first"?`${o} : ${r}. The first number is ${o} — that is the answer.`:a==="second"?`${o} : ${r}. The second number is ${r} — that is the answer.`:null)??(f?`÷ ${h} → ${o} : ${r}. Nothing left.`:`${o} : ${r} — nothing divides both. Simplest form.`)).slice(0,72),A=a==="first"?`So the simplest form is ${l(o)} to ${l(r)}. The question asks for the first number, so the answer is ${l(o)}.`:a==="second"?`So the simplest form is ${l(o)} to ${l(r)}. The question asks for the second number, so the answer is ${l(r)}.`:`${cap$9(l(t))} to ${l(e)} simplifies to ${l(o)} to ${l(r)}. ${cap$9(l(o))} and ${l(r)} share no common factor, so we stop.`,N=f?`Divide again by ${l(h)}: ${l(d)}
71 divided by ${l(h)} is ${l(o)}, and ${l(m)} divided by ${l(h)} is ${l(r)}. Now nothing bigger than one divides both, so we stop. ${A}`:A;return buildExample({generatorId:`${MICRO$4}.simplify.v1`,question:n,title:`How do we simplify ${t}:${e}?`,steps:[{visual_data:{type:"equation_display",terms:[t,":",e]},animation:"show_ratio",caption:`Simplify ${t} : ${e}.`,spoken:`We want to simplify ${l(t)} to ${l(e)}. To simplify, we find a number bigger than one that divides both parts, and keep going until nothing more divides both.`},{visual_data:{type:"equation_display",terms:[`${t} ÷ ${u}`,":",`${e} ÷ ${u}`]},animation:"divide_both",caption:x,spoken:E},{visual_data:{type:"equation_display",terms:[o,":",r]},animation:"write_answer",caption:T,spoken:N}],computed:{a:t,b:e,factor:s,f1:u,rem:h,a1:d,b1:m,simpA:o,simpB:r,answer:i,...Object.fromEntries(g.map((S,M)=>[`tried${M}`,S]))}})}function cap$9(n){return n.charAt(0).toUpperCase()+n.slice(1)}const RP_P2_M02_SIMPLIFY={id:`${MICRO$4}.simplify.v1`,microSkillId:MICRO$4,forms:FORMS$5,generate(n){const t=readRatio(n.stem);if(!t)return null;const[e,s]=t;if(!Number.isInteger(e)||!Number.isInteger(s)||e<1||s<1||e>999||s>999)return null;const o=gcd$2(e,s);if(o<2)return null;const r=e/o,a=s/o,i=(n.correctAnswer??"").trim(),l=i.match(/^(\d+)\s*:\s*(\d+)$/);if(l){const g=`${r}:${a}`,b=`${a}:${r}`,x=`${Number(l[1])}:${Number(l[2])}`;return x===g?buildRatioExample(n,e,s,o,r,a,null,r):x===b?buildRatioExample(n,s,e,o,a,r,null,a):null}const u=Number(i);if(!Number.isInteger(u))return null;const d=askedPart(n.stem);if(d===null)return null;const m=(g,b,x)=>g===x?"first":b===x?"second":null;return(d===1?r:a)===u?buildRatioExample(n,e,s,o,r,a,m(r,a,u),u):(d===1?a:r)===u?buildRatioExample(n,s,e,o,a,r,m(a,r,u),u):null}},checkSimplified={id:`${MICRO$4}.check-simplified.v1`,microSkillId:MICRO$4,forms:["context_reasoning","misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=[...t.matchAll(/(\d+)\s*:\s*(\d+)/g)];if(!e.length)return null;const s=e[e.length-1],o=Number(s[1]),r=Number(s[2]);if(!Number.isInteger(o)||!Number.isInteger(r)||o<1||r<1||o>999||r>999)return null;const a=gcd$2(o,r),i=a<2,l=(n.correctAnswer??"").trim();if(i&&!isAffirmative(l)||!i&&!isNegative(l))return null;const u=l.match(/divides?(?: both parts)? by (\d+)/i);if(u&&Number(u[1])!==a)return null;const d=t.match(/\b([A-Z][a-z]+) (?:simplifies|divides|writes|tries|says)\b/),m=d?d[1]:null,h=numberToWords,f=m?`${m} says ${o} : ${r} is finished.`.slice(0,72):`Is ${o} : ${r} already finished?`.slice(0,72),g=m?`${m} says ${h(o)} to ${h(r)} is fully simplified. A ratio is only finished when nothing but one divides both parts.`:`Someone says ${h(o)} to ${h(r)} is already fully simplified. A ratio is only finished when nothing but one divides both parts.`;if(i)return buildExample({generatorId:this.id,question:n,title:`Is ${o} : ${r} fully simplified?`,steps:[{visual_data:{type:"equation_display",terms:[String(o),":",String(r)]},animation:"check_ratio",caption:f,spoken:g},{visual_data:{type:"equation_display",terms:[String(o),":",String(r),"?"]},animation:"spot_factor",caption:`Try every small factor on ${o} and ${r}.`.slice(0,72),spoken:`Check every number bigger than one: does anything divide both ${h(o)} and ${h(r)}? No number does.`},{visual_data:{type:"equation_display",terms:[String(o),":",String(r),"✓"]},animation:"write_answer",caption:`${o} : ${r} — already fully simplified.`.slice(0,72),spoken:`${m?`${m} is`:"That claim is"} correct: no number bigger than one divides both ${h(o)} and ${h(r)}, so it is already finished.`}],computed:{a:o,b:r,factor:a}});const b=o/a,x=r/a;return buildExample({generatorId:this.id,question:n,title:`Is ${o} : ${r} fully simplified?`,steps:[{visual_data:{type:"equation_display",terms:[String(o),":",String(r)]},animation:"check_ratio",caption:f,spoken:g},{visual_data:{type:"equation_display",terms:[`${o} ÷ ${a}`,":",`${r} ÷ ${a}`]},animation:"spot_factor",caption:`${o} and ${r} both divide by ${a}.`.slice(0,72),spoken:`Check for a shared factor: ${h(o)} divided by ${h(a)} is ${h(b)}, and ${h(r)} divided by ${h(a)} is ${h(x)}. Since ${h(a)}
71 divides both, it is not finished yet.`},{visual_data:{type:"equation_display",terms:[String(b),":",String(x)]},animation:"write_answer",caption:`${o} : ${r} = ${b} : ${x} — divide again.`.slice(0,72),spoken:`${m?`${m} is`:"That claim is"} wrong: ${h(o)} to ${h(r)} simplifies further to ${h(b)} to ${h(x)}.`}],computed:{a:o,b:r,factor:a,simpA:b,simpB:x}})}},WHY_DIVIDE_FAIL=/\b([A-Z][a-z]+) tries to simplify (\d+)\s*:\s*(\d+) by dividing both parts by (\d+)\. why does this not work/i,whyDivideFail={id:`${MICRO$4}.why-divide-fail.v1`,microSkillId:MICRO$4,forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(WHY_DIVIDE_FAIL);if(!e)return null;const s=e[1],o=Number(e[2]),r=Number(e[3]),a=Number(e[4]);if(!Number.isInteger(o)||!Number.isInteger(r)||!Number.isInteger(a)||o<1||r<1||a<2||o>999||r>999)return null;const i=o%a===0,l=r%a===0;if(i===l)return null;const u=i?r:o,d=i?o:r,m=n.correctAnswer;if(!/does not (?:divide|work)|not a (?:common )?factor/i.test(m))return null;const h=(m.match(/\d+/g)??[]).map(Number);if(!h.includes(a)||!h.includes(u))return null;const f=gcd$2(o,r);if(f<2)return null;const g=o/f,b=r/f,x=numberToWords;return buildExample({generatorId:this.id,question:n,title:`Why doesn't dividing ${o} : ${r} by ${a} work?`,computed:{a:o,b:r,k:a,working:d,failing:u,factor:f,simpA:g,simpB:b},steps:[{visual_data:{type:"equation_display",terms:[String(o),":",String(r)]},animation:"check_ratio",caption:`${s} tries dividing ${o} : ${r} by ${a}.`.slice(0,72),spoken:`${s} tries to simplify ${x(o)} to ${x(r)} by dividing both parts by ${x(a)}. Let's check whether that works.`},{visual_data:{type:"equation_display",terms:[String(d),"÷",String(a),"=",String(d/a),",",String(u),"÷",String(a),"=","✗"]},animation:"spot_factor",caption:`${d} ÷ ${a} = ${d/a}, but ${u} ÷ ${a} does not work.`.slice(0,72),spoken:`${cap$9(x(d))} divided by ${x(a)} is ${x(d/a)} — that works. But ${x(u)} divided by ${x(a)} does not give a whole number, so ${x(a)} is not a common factor of ${x(o)} and ${x(r)}. Dividing by ${x(a)} does not work.`},{visual_data:{type:"equation_display",terms:[String(o),"÷",String(f),":",String(r),"÷",String(f),"=",String(g),":",String(b)]},animation:"write_answer",caption:`÷${f} → ${g} : ${b} — the true simplest form.`.slice(0,72),spoken:`The true common factor is ${x(f)}: ${x(o)} divided by ${x(f)} is ${x(g)}, and ${x(r)} divided by ${x(f)} is ${x(b)}. So the simplest form is ${x(g)} to ${x(b)}.`}]})}},WHO_SIMPLIFIED=/\b([A-Z][a-z]+) and ([A-Z][a-z]+) both simplify (\d+)\s*:\s*(\d+)\. ([A-Z][a-z]+) writes (\d+)\s*:\s*(\d+) and ([A-Z][a-z]+) writes (\d+)\s*:\s*(\d+)\. who has fully simplified/i,whoSimplified={id:`${MICRO$4}.who-simplified.v1`,microSkillId:MICRO$4,forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(WHO_SIMPLIFIED);if(!e)return null;const[,,,s,o,r,a,i,l,u,d]=e,m=Number(s),h=Number(o),f=Number(a),g=Number(i),b=Number(u),x=Number(d);if([m,h,f,g,b,x].some(D=>!Number.isInteger(D)||D<1||D>999))return null;const y=gcd$2(m,h);if(y<2)return null;const E=m/y,k=h/y,T=f===E&&g===k;if(T===(b===E&&x===k))return null;const N=T?r:l,S=T?l:r,M=T?b:f,L=T?x:g;if(n.correctAnswer.trim()!==N)return null;const P=gcd$2(M,L);if(P<2)return null;const F=numberToWords;return buildExample({generatorId:this.id,question:n,title:`Who has fully simplified ${m} : ${h}?`,computed:{a:m,b:h,factor:y,simpA:E,simpB:k,loserA:M,loserB:L,loserFactor:P},steps:[{visual_data:{type:"equation_display",terms:[String(m),":",String(h)]},animation:"check_ratio",caption:`${r} writes ${f}:${g}, ${l} writes ${b}:${x}.`.slice(0,72),spoken:`${r} and ${l} both try to simplify ${F(m)} to ${F(h)}. ${r} writes ${F(f)} to ${F(g)}, and ${l} writes ${F(b)} to ${F(x)}.`},{visual_data:{type:"equation_display",terms:[String(m),"÷",String(y),":",String(h),"÷",String(y),"=",String(E),":",String(k)]},animation:"spot_factor",caption:`True simplest form: ${m} ÷ ${y} = ${E}, ${h} ÷ ${y} = ${k}.`.slice(0,72),spoken:`The true common factor of ${F(m)} and ${F(h)} is ${F(y)}: dividing both parts gives ${F(E)} to ${F(k)} — the fully simplified form.`},{visual_data:{type:"equation_display",terms:[String(M),"÷",String(P),":",String(L),"÷",String(P)]},animation:"write_answer",caption:`${N}'s ${E}:${k} matches; ${S}'s still shares ${P}.`.slice(0,72),spoken:`${N}'s answer matches the true simplest form, so ${N} has fully simplified it. ${S}'s ${F(M)} to ${F(L)} still shares a factor of ${F(P)}, so it is not finished yet.`}]})}},RP_P2_M02_GENERATORS=[RP_P2_M02_SIMPLIFY,checkSimplified,whyDivideFail,whoSimplified
71],w$5=numberToWords,cap$8=n=>capitalised(numberToWords(n));function solveScale(n,t){const e=t===0||t===2?0:1,s=e===0?1:0,o=n[s===0?0:1],r=n[s===0?2:3];if(typeof o!="number"||typeof r!="number")return null;let a,i;if(r>o&&r%o===0)a="up",i=r/o;else if(o>r&&o%r===0)a="down",i=o/r;else return null;if(i<2)return null;const l=t===0||t===1?1:2,d=n[e===0?l===1?2:0:l===1?3:1];if(typeof d!="number")return null;let m,h;if(a==="up")if(l===2)m=d*i,h="×";else{if(d%i!==0)return null;m=d/i,h="÷"}else if(l===2){if(d%i!==0)return null;m=d/i,h="÷"}else m=d*i,h="×";return{dir:a,N:i,knownRow1:o,knownRow2:r,knownSign:a==="up"?"×":"÷",partner:d,applySign:h,blankVal:m}}function buildFromSlots(n,t,e){if(e.filter(T=>T===null).length!==1)return null;const s=e.indexOf(null),o=solveScale(e,s);if(!o)return null;const r=answerInt(t);if(r===null||r!==o.blankVal)return null;const a=e.map((T,A)=>A===s?o.blankVal:T),[i,l,u,d]=a,m=[0,2,4,6][s],h=[String(i),":",String(l),"=",String(u),":",String(d)],f=h.slice();f[m]="__";const g=[String(o.knownRow1),o.knownSign,String(o.N),"=",String(o.knownRow2)],b=h.slice();b[m]="?";const x=o.applySign==="×"?"times":"divided by",y=o.dir==="up"?"times":"divided by",E=T=>T==="?"?"what":w$5(Number(T)),k=`${E(b[0])} to ${E(b[2])} equals ${E(b[4])} to ${E(b[6])}`;return buildExample({generatorId:n,question:t,title:`Complete the ratio: ${b[0]} : ${b[2]} = ${b[4]} : ${b[6]}`,computed:{a:i,b:l,c:u,d,factor:o.N,partner:o.partner,known_row1:o.knownRow1,known_row2:o.knownRow2,answer:o.blankVal},steps:[{visual_data:{type:"equation_display",terms:f},animation:"show_ratios",caption:`${b[0]} : ${b[2]} = ${b[4]} : ${b[6]}`,spoken:`We need the missing number in ${k}. The two ratios must stay equal, so we multiply both parts by the same number — we never add.`},{visual_data:{type:"equation_display",terms:g},animation:"show_factor",caption:`${o.knownRow1} ${o.knownSign} ${o.N} = ${o.knownRow2}`,spoken:`To find that number, we look at the pair we already know: ${w$5(o.knownRow1)} ${o.dir==="up"?"becomes":"scales down to"} ${w$5(o.knownRow2)}. ${cap$8(o.knownRow1)} ${y} ${w$5(o.N)} is ${w$5(o.knownRow2)}, so the scale factor is ${w$5(o.N)}.`},{visual_data:{type:"equation_display",terms:h},animation:"apply_factor",caption:`${o.partner} ${o.applySign} ${o.N} = ${o.blankVal}`,spoken:`Now we do the very same to the other part: ${w$5(o.partner)} ${x} ${w$5(o.N)} is ${w$5(o.blankVal)}. So the missing number is ${w$5(o.blankVal)}, and ${w$5(i)} to ${w$5(l)} equals ${w$5(u)} to ${w$5(d)}.`}]})}function parseSymbolicSlots(n){const t=normaliseStem(n),e=/(\d+|_+)\s*:\s*(\d+|_+)/g,s=[];let o=0,r;for(;(r=e.exec(t))!==null;){o++;for(const a of[r[1],r[2]])s.push(/^\d+$/.test(a)?Number(a):null)}return o!==2||s.length!==4||s.filter(a=>a===null).length!==1?null:s}const symbolicScale={id:"RP.P2.M03.symbolic-scale.v1",microSkillId:"RP.P2.M03",forms:["equation_completion","symbolic_numeric_entry"],generate(n){const t=parseSymbolicSlots(n.stem);return t?buildFromSlots(this.id,n,t):null}};function parseContextRatio(n){const t=normaliseStem(n);let e;return(e=t.match(/ratio (\d+)\s*:\s*(\d+)/i))?[Number(e[1]),Number(e[2])]:(e=t.match(/(\d+) parts? [a-z]+ to (\d+) parts?/i))?[Number(e[1]),Number(e[2])]:(e=t.match(/(\d+) [a-z]+ for every (\d+)/i))?[Number(e[1]),Number(e[2])]:(e=t.match(/(\d+)\s*cm (?:stands for|represents) (\d+)/i))?[Number(e[1]),Number(e[2])]:(e=t.match(/(\d+) [a-z]+ trade for (\d+)/i))?[Number(e[1]),Number(e[2])]:(e=t.match(/for (\d+) [a-z]+ uses (\d+)/i))?[Number(e[1]),Number(e[2])]:null}const contextScale={id:"RP.P2.M03.context-scale.v1",microSkillId:"RP.P2.M03",forms:["context_calculation","context_reasoning"],generate(n){const t=parseContextRatio(n.stem);if(!t)return null;const[e,s]=t,o=answerInt(n);if(o===null)return null;const r=new Set((normaliseStem(n.stem).match(/\d+/g)??[]).map(Number)),a=d=>{const m=d?s:e;if(m===0||o%m!==0)return null;const h=o/m;if(h<2)return null;const f=d?e*h:s*h;return r.has(f)?d?[e,s,f,null]:[e,s,null,f]:null},i=a(!0),l=a(!1);let u=null;if(i&&!l)u=i;else if(l&&!i)u=l;else if(i&&l&&JSON.stringify(i)===JSON.stringify(l))u=i;else return null;return buildFromSlots(this.id,n,u)}};function headerClean(n){return n.replace(/\([^)]*\)/g,"").trim().toLowerCase()}const tableScale={id:"RP.P2.M03.table-scale.v1",microSkillId:"RP.P2.M03",forms:["context_calculation"],generate(n){const t=n.visualData;if(!t||t.type!=="two_way_table")return null;const{cells:e,row_headers:s,col_headers:o}=t;if(e.length!==2||e.some(M=>M.length!==2))return null;let r=-1,a=-1,i=0;for(let M=0;M<2;M++)for(let L=0;L<2;L++)e[M][L]===null&&(r=M,a=L,i++);if(i!==1)return null;const l=r===0?1:0,u=a===0?1:0,d=e[l][u],m=e[l][a],h=e[r][u];if(typeof d!="number"||typeof m!="number"||typeof h!="number"||d<=0||m%d!==0)return null;const f=m/d;if(f<2)return null;const g=h*f,b=answerInt(n);if(b===null||b!==g)return null;const x=e.map(M=>M.slice());x[r][a]=g;const y=headerClean(s[r]),E=headerClean(o[a]),k=`${d} × ${f} = ${m}`,T=`${h} × ${f} = ${g}`,A={type:"two_way_table",col_headers:o,row_headers:s,cells:e},N={type:"two_way_table",col_headers:o,row_headers:s,cells:e,chip_expression:k,highlight_col:a},S={type:"two_way_table",col_headers:o,row_headers:s,cells:x,chip_expression:T,highlight_col:a};return buildExample({generatorId:this.id,question:n,title:`Complete the table: ${y} for the ${E}?`,computed:{complete_other:d,complete_target:m,partner:h,factor:f,answer:g},steps:[{visual_data:A,animation:"highlight_missing",caption:`Find the missing ${s[r]}.`,spoken:`The two batches keep the same ratio. We need the ${y} for the ${E}.`},{visual_data:N,animation:"sum_known",caption:k,spoken:`Look at the complete row: ${w$5(d)} becomes ${w$5(m)}, which is ${w$5(f)} times as much. So we multiply the other row by ${w$5(f)} too — never add.`},{visual_data:S,animation:"reveal_answer",caption:T,spoken:`${cap$8(h)} times ${w$5(f)} is ${w$5(g)}. So ${w$5(g)} keeps the same ratio.`}]})}},equivalentSelect={id:"RP.P2.M03.equivalent-select.v1",microSkillId:"RP.P2.M03",forms:["comparison_statement","misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=t.match(/ratio (\d+)\s*:\s*(\d+)/i)??t.match(/equivalent to (\d+)\s*:\s*(\d+)/i);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=n.correctAnswer.trim().match(/^(\d+)\s*:\s*(\d+)$/);if(!r)return null;const a=Number(r[1]),i=Number(r[2]);if(s===0||o===0||a===0||i===0||s*i!==o*a)return null;let l,u;if(a>s&&a%s===0&&i===o*(a/s))l="up",u=a/s;else if(s>
71a&&s%a===0&&o===i*(s/a))l="down",u=s/a;else return null;if(u<2)return null;const d=l==="up"?"×":"÷",m=l==="up"?"times":"divided by";return buildExample({generatorId:this.id,question:n,title:`Which ratio equals ${s} : ${o}?`,computed:{a:s,b:o,c:a,d:i,factor:u},steps:[{visual_data:{type:"equation_display",terms:[String(s),":",String(o),"=","__",":","__"]},animation:"show_ratios",caption:`${s} : ${o} = ? : ?`,spoken:`We want a ratio equal to ${w$5(s)} to ${w$5(o)}. Equivalent ratios come from multiplying or dividing both parts by the same number.`},{visual_data:{type:"equation_display",terms:[String(s),d,String(u),"=",String(a)]},animation:"show_factor",caption:`${s} ${d} ${u} = ${a}`,spoken:`${l==="up"?"Both parts scale up by the same factor":"Both parts share a common factor"}. ${cap$8(s)} ${m} ${w$5(u)} is ${w$5(a)}.`},{visual_data:{type:"equation_display",terms:[String(s),":",String(o),"=",String(a),":",String(i)]},animation:"apply_factor",caption:`${s} : ${o} = ${a} : ${i}`,spoken:`${cap$8(o)} ${m} ${w$5(u)} is ${w$5(i)} as well. So ${w$5(s)} to ${w$5(o)} equals ${w$5(a)} to ${w$5(i)} — that is the matching ratio.`}]})}},UNIT_RATE_PEOPLE=/for (\d+) people uses (\d+) ([a-z]+)\. how many \3 (?:are|is) needed for (\d+) people/i,unitRateScale={id:"RP.P2.M03.unit-rate.v1",microSkillId:"RP.P2.M03",forms:["context_calculation","context_reasoning"],generate(n){const e=normaliseStem(n.stem).match(UNIT_RATE_PEOPLE);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=e[3],a=Number(e[4]);if(s<1||o<1||a<1||s>99||o>99||a>99)return null;const i=o/s,l=Math.round(i*10)/10;if(Math.abs(i-l)>1e-9)return null;const u=l*a;if(!Number.isInteger(Math.round(u*100)/100))return null;const d=Math.round(u);if(Math.abs(u-d)>1e-6)return null;const m=answerInt(n);if(m===null||m!==d)return null;const h=String(l),f=numberToWords,g=decimalToWords(l);return buildExample({generatorId:this.id,question:n,title:`${s} people : ${o} ${r} — how many for ${a} people?`,computed:{base_people:s,base_amount:o,target_people:a,rate:l,answer:d},steps:[{visual_data:{type:"equation_display",terms:[String(s),":",String(o)]},animation:"show_ratios",caption:`${s} people need ${o} ${r}.`.slice(0,72),spoken:`We know ${f(s)} people need ${f(o)} ${r}. ${capitalised(f(s))} does not divide evenly into ${f(a)}, so we find the rate for just one person first.`},{visual_data:{type:"equation_display",terms:[String(o),"÷",String(s),"=",h]},animation:"show_factor",caption:`${o} ÷ ${s} = ${h} ${r} per person.`.slice(0,72),spoken:`${capitalised(f(o))} divided by ${f(s)} is ${g} — that is how many ${r} one person needs.`},{visual_data:{type:"equation_display",terms:[h,"×",String(a),"=",String(d)]},animation:"apply_factor",caption:`${h} × ${a} = ${d} ${r}.`.slice(0,72),spoken:`Now multiply the rate by ${f(a)} people: ${g} times ${f(a)} is ${f(d)}. So ${f(a)} people need ${f(d)} ${r} — that is the answer.`}]})}},RP_P2_M03_GENERATORS=[tableScale,symbolicScale,contextScale,equivalentSelect,unitRateScale],Y5_GENERATORS=[...AS_P6_M01_GENERATORS,...AS_P6_M02_GENERATORS,...AS_P6_M03_GENERATORS,...AS_P6_M04_GENERATORS,...AS_P6_M05_GENERATORS,...AS_P6_M06_GENERATORS,...DS_P4_M01_GENERATORS,...DS_P4_M02_GENERATORS,...DS_P4_M03_GENERATORS,...FDP_P6_M01_GENERATORS,...FDP_P6_M08_GENERATORS,...FDP_P6_M09_GENERATORS,...FDP_P6_M02_GENERATORS,...FDP_P6_M03_GENERATORS,...GEO_P6_M01_GENERATORS,...GEO_P6_M02_GENERATORS,...GEO_P6_M03_GENERATORS,...MD_P4_M04_GENERATORS,...MD_P4_M05_GENERATORS,...MD_P5_M01_GENERATORS,...MD_P5_M02_GENERATORS,...MD_P5_M03_GENERATORS,...MD_P5_M04_GENERATORS,...MEA_P7_M04_GENERATORS,...MEA_P7_M06_GENERATORS,...NPV_P6_M05_GENERATORS,...NPV_P7_M01_GENERATORS,...NPV_P7_M02_GENERATORS,...RP_P2_M01_GENERATORS,...RP_P2_M02_GENERATORS,...RP_P2_M03_GENERATORS];function stripInt(n){return Number(n.replace(/,/g,""))}function allIntsGB(n){return(n.match(/\d[\d,]*/g)??[]).map(stripInt).filter(e=>Number.isInteger(e))}function acceptedIntGB(n){const t=stripInt(n.trim());return Number.isInteger(t)?t:null}function twoStepProgressive(n,t,e,s,o){const r={type:"two_step",start:n,step1_add:t,step2_add:e};return[{...r,step1_result:null,final_result:null},{...r,step1_result:s,final_result:null},{...r,step1_result:s,final_result:o}]}function colAdd(n,t,e=!1){return{type:"column_method",operation:"add",top:n,bottom:t,...e?{show_answer:!0}:{}}}function colSub(n,t,e=!1){return{type:"column_method",operation:"subtract",top:n,bottom:t,...e?{show_answer:!0}:{}}}function moneyToPence(n){const t=n.trim(),e=t.match(/^£\s*(\d+)(?:\.(\d{1,2}))?$/);if(e){const o=Number(e[1]),r=e[2]?Number(e[2].padEnd(2,"0")):0;return o*100+r}const s=t.match(/^(\d+)\s*p$/);return s?Number(s[1]):null}const SUBTRACT_VERBS=/\b(uses?|used|trims?|trimmed|cuts?|cut|removes?|removed|drains?|drained|scoops?|scooped|pours? out|poured out|takes? out|took out)\b/;function parseMeasureQuantities(n){const t=/(\d+(?:\.\d+)?)\s*(km|kg|cm|ml|mm|m|g|l)\b/gi,e=[];let s;for(;(s=t.exec(n))!==null;)e.push({value:Number(s[1]),unit:s[2].toLowerCase(),index:s.index});if(e.length===0)return null;const o=new Set(e.map(u=>u.unit));let r,a,i;if(o.has("km"))r="length_m",i="m",a=u=>u==="km"?1e3:u==="m"?1:null;else if(o.has("cm")||o.has("m")&&!o.has("kg")&&!o.has("g")&&!o.has("l")&&!o.has("ml"))r="length_cm",i="cm",a=u=>u==="m"?100:u==="cm"?1:null;else if(o.has("kg")||o.has("g"))r="mass",i="g",a=u=>u==="kg"?1e3:u==="g"?1:null;else if(o.has("l")||o.has("ml"))r="volume",i="ml",a=u=>u==="l"?1e3:u==="ml"?1:null;else return null;const l=[];for(let u=0;u<e.length;u++){const d=a(e[u].unit);if(d===null)return null;const m=Math.round(e[u].value*d);let h=1;if(u>0){const f=n.slice(e[u-1].index,e[u].index).toLowerCase();h=SUBTRACT_VERBS.test(f)?-1:1}l.push({base:m,sign:h})}
71return{family:r,base:i,quantities:l}}const ID$3="AS.P6.M07",RUNNING_MINUS=/\b(makes? up|made up|withdraws?|withdrew|spends?|spent|pays? out|paid out|takes? out|took out|removes?|removed|drains?|drained|released?)\b/i,fmt=n=>n.toLocaleString("en-GB"),COMPARE_MINUS=/\b(fewer|less)\b/i,COMPARE_WORD=/\b(more|fewer|less)\b/i;function w$4(n){if(n>=1e6){const t=Math.floor(n/1e6),e=n%1e6;return`${numberToWordsLong(t)} million${e?` ${e<100?"and ":""}${numberToWordsLong(e)}`:""}`}return numberToWordsLong(n)}function chainOp$2(n,t,e){return t<0?colSub(n,-t,e):colAdd(n,t,e)}function opSym(n){return n<0?"−":"+"}function capacityDemo(n,t){const e=allIntsGB(n.stem);if(e.length!==3)return null;const[s,o,r]=e;if(s<=0||o<=0||r<=0)return null;const a=s-o,i=s-o-r;if(a<0||i<0||i!==t)return null;const l=n.stem.match(/has\s+[\d,]+\s+([a-z]+(?:\s+[a-z]+)?)\s*\./i),u=l?l[1].toLowerCase():"places",d=n.stem.match(/left\s+(unsold|unclaimed|unused)/i),m=d?`are left ${d[1].toLowerCase()}`:"stay empty",[h,f,g]=twoStepProgressive(s,-o,-r,a,i);for(const b of[h,f,g])b.format_thousands=!0;return buildExample({generatorId:`${ID$3}.capacity.v1`,question:n,title:`How many ${u} are left?`,computed:{capacity:s,first:o,second:r,afterFirst:a,arrivals:o+r,answer:i},steps:[{visual_data:h,animation:"show_problem",caption:`Start with all ${fmt(s)} ${u}.`,spoken:`We start with all ${w$4(s)} ${u}. Two groups of people will arrive, and each group uses some up, so we take each group away, one after the other.`},{visual_data:f,animation:"first_step",caption:`${fmt(o)}
71 arrive: ${fmt(s)} − ${fmt(o)} = ${fmt(a)} left.`,spoken:`The first group of ${w$4(o)} arrives, so we take them away. That leaves ${w$4(a)} ${u}.`},{visual_data:g,animation:"second_step",caption:`Then ${fmt(r)} more: ${fmt(a)} − ${fmt(r)} = ${fmt(i)}.`,spoken:`Then ${w$4(r)} more arrive, so we take them away too, which leaves ${w$4(i)}. So ${w$4(i)} ${u} ${m}.`}]})}function differenceDemo(n,t){const e=allIntsGB(n.stem);if(e.length!==2)return null;const s=Math.max(e[0],e[1]),o=Math.min(e[0],e[1]),r=s-o,a=o-r;return r<=0||a<=0||a!==t?null:buildExample({generatorId:`${ID$3}.difference.v1`,question:n,title:"How many more than the other?",computed:{total:s,partA:o,partB:r,answer:a},steps:[{visual_data:colSub(s,o,!0),animation:"find_other_part",caption:`Together ${s}, one part ${o} → other = ${r}.`,spoken:`The two parts add up to the total. We know one part is ${w$4(o)}, so we take it away from the total to find the other part. The other part is ${w$4(r)}.`},{visual_data:colSub(o,r),animation:"compare_setup",caption:`Now compare the two parts: ${o} and ${r}.`,spoken:`"How many more" means we compare the two parts. To find the difference, we take the smaller part, ${w$4(r)}, away from the larger one, ${w$4(o)}.`},{visual_data:colSub(o,r),animation:"compare_reveal",caption:`${o} − ${r} = ${a} more.`,spoken:`Working it out, ${w$4(o)} take away ${w$4(r)} is ${w$4(a)}. So the larger part has ${w$4(a)} more than the other.`}]})}function startUnknownDemo(n,t){const e=allIntsGB(n.stem);if(e.length!==3)return null;const[s,o,r]=e;if(s<=0||o<=0||r<=0)return null;const a=r+s,i=a-o;return i<0||i!==t?null:buildExample({generatorId:`${ID$3}.start-unknown.v1`,question:n,title:`? − ${s} + ${o} = ${r}`,computed:{now:r,out:s,in:o,afterAddBack:a,answer:i},steps:[{visual_data:colAdd(r,s,!1),animation:"show_now",caption:`We know it has ${r} now.`,spoken:`We know the amount now, ${w$4(r)}, and we want the amount at the very start. So we work backwards, undoing each change in reverse order.`},{visual_data:colAdd(r,s,!0),animation:"add_back",caption:`Add back what left: ${r} + ${s} = ${a}.`,spoken:`Some had gone out, ${w$4(s)}, which made the amount smaller. To undo that, we add the ${w$4(s)} back on, which gives ${w$4(a)}.`},{visual_data:colSub(a,o,!0),animation:"take_off",caption:`Take off what came in: ${a} − ${o} = ${i}.`,spoken:`Some had also come in, ${w$4(o)}, which made the amount bigger. To undo that, we take it away, which leaves ${w$4(i)}. So the amount at the start was ${w$4(i)}.`}]})}function runningDemo(n,t){const e=n.stem,s=allIntsGB(e).filter(k=>k!==0);if(s.length!==3)return null;const[o,r,a]=s;if(o<=0||r<=0||a<=0)return null;const i=e.match(/\d[\d,]*/g)??[];if(i.length<3)return null;const l=e.indexOf(i[i.length-3]),u=e.indexOf(i[i.length-2],l+1),d=e.indexOf(i[i.length-1],u+1);if(l<0||u<0||d<0)return null;const m=RUNNING_MINUS.test(e.slice(l,u))?-1:1,h=RUNNING_MINUS.test(e.slice(u,d))?-1:1,f=o+m*r,g=f+h*a;if(f<0||g<0||g!==t)return null;const b=m*r,x=h*a,y=opSym(b),E=opSym(x);return buildExample({generatorId:`${ID$3}.running.v1`,question:n,title:`${o} ${y} ${r} ${E} ${a} = ?`,computed:{start:o,n1:r,n2:a,step1Result:f,answer:g},steps:[{visual_data:chainOp$2(o,b,!1),animation:"show_problem",caption:`Start at ${o}, then two changes.`,spoken:`We begin with the first amount, then make each change in turn, in the order the story tells us. We start at ${w$4(o)}.`},{visual_data:chainOp$2(o,b,!0),animation:"first_change",caption:`${o} ${y} ${r} = ${f}.`,spoken:m<0?`The first change takes some away, so the amount goes down: ${w$4(o)} take away ${w$4(r)} leaves ${w$4(f)}.`:`The first change adds some on, so the amount goes up: ${w$4(o)} add ${w$4(r)} makes ${w$4(f)}.`},{visual_data:chainOp$2(f,x,!0),animation:"second_change",caption:`${f} ${E} ${a} = ${g}.`,spoken:h<0?`The second change takes some away: ${w$4(f)} take away ${w$4(a)} leaves ${w$4(g)}. That is the amount after both changes, so it is the answer.`:`The second change adds more on: ${w$4(f)} add ${w$4(a)} makes ${w$4(g)}. That is the amount after both changes, so it is the answer.`}]})}function chainCompareDemo(n,t){const e=normaliseStem(n.stem),s=allIntsGB(e);if(s.length!==3)return null;const[o,r,a]=s;if(o<=0||r<=0||a<=0)return null;const i=e.match(/\d[\d,]*/g)??[];if(i.length!==3)return null;let l=0;const u=i.map(T=>{const A=e.indexOf(T,l);return l=A+T.length,{start:A,end:A+T.length}}),d=e.slice(u[1].end,u[2].start),m=e.slice(u[2].end);if(!COMPARE_WORD.test(d)||!COMPARE_WORD.test(m))return null;const h=COMPARE_MINUS.test(d)?-1:1,f=COMPARE_MINUS.test(m)?-1:1,g=o+h*r,b=g+f*a;if(g<0||b<0||b!==t)return null;const x=h*r,y=f*a,E=opSym(x),k=opSym(y);return buildExample({generatorId:`${ID$3}.chain-compare.v1`,question:n,title:`${o} ${E} ${r} ${k} ${a} = ?`,computed:{start:o,n1:r,n2:a,step1Result:g,answer:b},steps:[{visual_data:chainOp$2(o,x,!1),animation:"show_problem",caption:`Start at ${o}, then two comparisons.`,spoken:`Each amount is compared with the one before it, so we work them out one at a time. We start with the first amount, ${w$4(o)}.`},{visual_data:chainOp$2(o,x,!0),animation:"first_change",caption:`${o} ${E} ${r} = ${g}.`,spoken:h<0?`The next amount is smaller than the one before it, so we take some away: ${w$4(o)} take away ${w$4(r)} leaves ${w$4(g)}.`:`The next amount is bigger than the one before it, so we add some on: ${w$4(o)} add ${w$4(r)} makes ${w$4(g)}.`},{visual_data:chainOp$2(g,y,!0),animation:"second_change",caption:`${g} ${k} ${a} = ${b}.`,spoken:f<0?`The last amount is smaller again, so we take some away: ${w$4(g)} take away ${w$4(a)} leaves ${w$4(b)}. That is the amount the question asks for.`:`The last amount is bigger again, so we add some on: ${w$4(g)} add ${w$4(a)} makes ${w$4(b)}. That is the amount the question asks for.`}]})}function gapChainDemo(n,t){const e=normaliseStem(n.stem);if(!/how far (?:behind|ahead)|difference between/i.test(e))return null;const s=allIntsGB(e);if(s.length!==3)return null;const[o,r,a]=s;if(o<=0||r<=0||a<=0)return null;const i=e.match(/(\d[\d,]*)\s*(more|fewer|less)\b[^.]*\bthan\b/i);if(!i)return null;const l=/fewer|less/i.test(i[2])?-1:1,u=r+l*a;if(u<0)return null;const d=o-u;if(d<0||d!==t)return null;const m=l<0?colSub(r,a):colAdd(r,a),h=l<0?colSub(r,a,!0):colAdd(r,a,!0),f=l<0?"−":"+";return buildExample({generatorId:`${ID$3}.gap-chain.v1`,question:n,title:"How far behind the leader?",computed:{leader:o,second:r,delta:a,third:u,answer:d},steps:[{visual_data:m,animation:"find_third",caption:`Third place: ${r} ${f} ${a} = ${u}
71.`,spoken:l<0?`We can only compare with the leader once we know third place's score, so we work that out first: ${w$4(r)} take away ${w$4(a)} leaves ${w$4(u)}.`:`We can only compare with the leader once we know third place's score, so we work that out first: ${w$4(r)} add ${w$4(a)} makes ${w$4(u)}.`},{visual_data:h,animation:"compare_setup",caption:`Compare with the leader: ${o} and ${u}.`,spoken:`How far behind means the gap between two scores, so now we compare third place's ${w$4(u)} with the leader's ${w$4(o)}.`},{visual_data:colSub(o,u,!0),animation:"reveal",caption:`${o} − ${u} = ${d}.`,spoken:`To find the gap, we take the smaller score away from the bigger one: ${w$4(o)} take away ${w$4(u)} is ${w$4(d)}. So third place is ${w$4(d)} behind the leader.`}]})}const asP6M07Generator={id:`${ID$3}.two-step.v1`,microSkillId:ID$3,forms:["context_calculation","context_reasoning"],generate(n){const t=acceptedIntGB(n.correctAnswer);if(t===null)return null;const e=normaliseStem(n.stem);return/how many more\b[^?]*\bthan\b/i.test(e)?differenceDemo(n,t):/stay empty|left (?:unsold|unclaimed|unused)/i.test(e)?capacityDemo(n,t):n.mathematicalForm==="context_reasoning"?/how far (?:behind|ahead)|difference between/i.test(e)?gapChainDemo(n,t):/start with/i.test(e)?startUnknownDemo(n,t):chainCompareDemo(n,t):runningDemo(n,t)}},AS_P6_M07_GENERATORS=[asP6M07Generator],ID$2="AS.P6.M08",UNIT_WORDS={mm:"millimetres",cm:"centimetres",m:"metres",km:"kilometres",g:"grams",kg:"kilograms",ml:"millilitres",l:"litres"},unitWord$1=n=>UNIT_WORDS[n.toLowerCase()]??n,CONVERSION_FACTS={"m>cm":"One metre is a hundred centimetres","km>m":"One kilometre is a thousand metres","kg>g":"One kilogram is a thousand grams","l>ml":"One litre is a thousand millilitres"},words$1=n=>numberToWordsLong(n),dw=n=>decimalToWords(n).replace(/^zero point/,"nought point"),PENCE_MINUS=/\b(taken out|takes? out|took out|removed|removes?|drains?|drained|spent|withdrawn)\b/i;function chainOp$1(n,t,e){return t<0?colSub(n,-t,e):colAdd(n,t,e)}const MEASURE_TOKEN=/(\d+(?:\.\d+)?)\s*(km|kg|cm|ml|mm|m|g|l)\b/gi;function rawMeasureTokens(n){const t=[],e=new RegExp(MEASURE_TOKEN.source,"gi");let s;for(;(s=e.exec(n))!==null;)t.push({text:s[1],unit:s[2].toLowerCase()});return t}function conversionLine(n,t,e){const s=[];return n.forEach((o,r)=>{o.unit!==e&&s.push(`${o.text} ${o.unit} = ${t[r]} ${e}`)}),s.join("; ")}function moneyChangeDemo(n){const t=moneyToPence(n.correctAnswer.trim());if(t===null)return null;const s=(n.stem.match(/£\s*\d+(?:\.\d{1,2})?|\d+\s*p\b/gi)??[]).map(g=>moneyToPence(g)).filter(g=>g!==null);if(s.length!==3)return null;const o=Math.max(...s),r=s.indexOf(o),a=s.filter((g,b)=>b!==r);if(a.length!==2)return null;const[i,l]=a,u=o-i,d=o-i-l;if(u<0||d<0||d!==t)return null;const[m,h,f]=twoStepProgressive(o,-i,-l,u,d);return buildExample({generatorId:`${ID$2}.money-change.v1`,question:n,title:"How much change?",computed:{note:o,first:i,second:l,afterFirst:u,changePounds:d/100,answer:d},steps:[{visual_data:m,animation:"to_pence",caption:`In pence: start with ${o}.`,spoken:`To make the change easy, we work in pence, so every amount is a whole number. We start with the money that was handed over, which is ${o===1e3?"a thousand":words$1(o)} pence.`},{visual_data:h,animation:"take_first",caption:`Take off ${i}: ${o} − ${i} = ${u}.`,spoken:`The change is what is left after paying, so we take away each price in turn. First we take away ${words$1(i)} pence, which leaves ${words$1(u)} pence.`},{visual_data:f,animation:"take_second",caption:`Take off ${l}: ${u} − ${l} = ${d}p = ${n.correctAnswer.trim()}`,spoken:`Then we take away ${words$1(l)} pence, which leaves ${words$1(d)} pence. That is the change. A hundred pence make a pound, so for the answer we write it back in pounds and pence.`}]})}function acceptedBase(n){const t=n.trim().match(/^(\d+(?:\.\d+)?)\s*(km|kg|cm|ml|m|g|l)?$/i);if(!t)return null;const e=Number(t[1]),s=(t[2]??"").toLowerCase();return Math.round(e*(s==="m"?100:s==="kg"||s==="l"||s==="km"?1e3:1))}function measureDemo(n){const t=parseMeasureQuantities(n.stem);if(!t)return null;const{base:e,quantities:s}=t;if(s.length!==3)return null;const o=acceptedBase(n.correctAnswer);if(o===null)return null;const r=s[0].sign*s[0].base,a=s[1].sign*s[1].base,i=s[2].sign*s[2].base;if(r<=0)return null;const l=r+a,u=r+a+i;if(l<0||u<0||u!==o)return null;const d=a<0?"−":"+",m=i<0?"−":"+",h=/[a-z]/i.test(n.correctAnswer)?n.correctAnswer.trim():`${u} ${e}`,f=rawMeasureTokens(n.stem),g=f.length===3?conversionLine(f,[r,Math.abs(a),Math.abs(i)],e):"",b=unitWord$1(e),x=[r,Math.abs(a),Math.abs(i)],y=f.length===3?f.map((P,F)=>({...P,val:x[F]})).filter(P=>P.unit!==e):[],E=y.map(P=>`${dw(String(Number(P.text)))} ${unitWord$1(P.unit)} is ${words$1(P.val)} ${b}`).join(", and "),k=y.length?CONVERSION_FACTS[`${y[0].unit}>${e}`]:void 0,T=g?`We can only add or take away amounts in the same unit, so first we change everything into ${b}. ${k?`${k}, so `:""}${E}. We start with ${words$1(r)} ${b}.`:`All the amounts are already in ${b}, so we can add and take away straight away. We start with ${words$1(r)} ${b}.`,A=P=>P<0?"take away":"add on",N=n.correctAnswer.trim().match(/([a-z]+)$/i),S=N?N[1].toLowerCase():null,M=S!==null&&S!==e,L=Number((n.correctAnswer.match(/\d+(?:\.\d+)?/)??[u])[0]);return buildExample({generatorId:`${ID$2}.measures.v1`,question:n,title:`${r} ${d} ${Math.abs(a)} ${m} ${Math.abs(i)} = ?`,computed:{start:r,afterFirst:l,total:u,d1:Math.abs(a),d2:Math.abs(i),answerValue:L,answer:u},steps:[{visual_data:chainOp$1(r,a,!1),animation:"to_base",caption:g?`Convert: ${g}.`:`In ${e}: start with ${r}.`,spoken:T},{visual_data:chainOp$1(r,a,!0),animation:"first_change",caption:`${r} ${d} ${Math.abs(a)} = ${l}.`,spoken:`Now every amount is in ${b}
71, so we work through the changes in order. First we ${A(a)} ${words$1(Math.abs(a))} ${b}, working column by column. That gives ${words$1(l)} ${b}.`},{visual_data:chainOp$1(l,i,!0),animation:"second_change",caption:M?`${l} ${m} ${Math.abs(i)} = ${u} ${e} = ${h}`:`${l} ${m} ${Math.abs(i)} = ${h}`,spoken:M?`Then we ${A(i)} ${words$1(Math.abs(i))} ${b}, which gives ${words$1(u)} ${b}. The question wants ${unitWord$1(S??e)}, so we change it back: ${words$1(u)} ${b} is ${dw(String(L))} ${unitWord$1(S??e)}. That is the answer.`:`Then we ${A(i)} ${words$1(Math.abs(i))} ${b}, which gives ${words$1(u)} ${b}. That is the answer.`}]})}function measureAddChainDemo(n,t){const e=parseMeasureQuantities(n.stem);if(!e)return null;const{base:s,quantities:o}=e;if(o.length!==4||o.some(A=>A.sign!==1))return null;const r=rawMeasureTokens(n.stem);if(r.length!==4)return null;const[a,i,l,u]=o.map(A=>A.base),d=a+i,m=d+l,h=m+u;if(h!==t)return null;const f=conversionLine(r,[a,i,l,u],s),g=unitWord$1(s),b=[a,i,l,u],x=r.map((A,N)=>({...A,val:b[N]})).filter(A=>A.unit!==s),y=x.map(A=>`${dw(String(Number(A.text)))} ${unitWord$1(A.unit)} is ${words$1(A.val)} ${g}`).join(", and "),E=x.length?CONVERSION_FACTS[`${x[0].unit}>${s}`]:void 0,k=`${words$1(a)} add ${words$1(i)} makes ${words$1(d)}`,T=f?`We can only add amounts in the same unit, so first we change everything into ${g}. ${E?`${E}, so `:""}${y}. Then we add the first two amounts: ${k}.`:`All the amounts are already in ${g}, so we can add them straight away, one at a time. ${capitalised(k)}.`;return buildExample({generatorId:`${ID$2}.measures-chain.v1`,question:n,title:"Change to one unit, then add three amounts.",computed:{start:a,d1:i,d2:l,r1:d,r2:m,d3:u,answer:h},steps:[{visual_data:colAdd(a,i,!0),animation:"to_base",caption:f?`Convert: ${f}.`:`In ${s}: ${a} + ${i} = ${d}.`,spoken:T},{visual_data:colAdd(d,l,!0),animation:"second_add",caption:`${d} + ${l} = ${m}.`,spoken:`Now we add the next amount on: ${words$1(d)} add ${words$1(l)} makes ${words$1(m)}.`},{visual_data:colAdd(m,u),animation:"third_add",caption:`${m} + ${u} = ${h} ${s}.`,spoken:`Then we add the last amount: ${words$1(m)} add ${words$1(u)} makes ${words$1(h)} ${g}. That is the total, all in the same unit.`}]})}function rateChainSubDemo(n){const t=Number(n.correctAnswer.trim());if(!Number.isInteger(t))return null;const s=n.stem.match(/(\d+(?:\.\d+)?)\s*(g|kg|ml|l|cm|m|mm|km)\b[^.]*?\bper\b[^.]*?\.[^.]*?(\d+)\s+\w+[^.]*?(\d+(?:\.\d+)?)\s*(g|kg|ml|l|cm|m|mm|km)\b/i);if(!s)return null;const o=Number(s[1]),r=s[2].toLowerCase(),a=Number(s[3]),i=Number(s[4]),l=s[5].toLowerCase();if(r!==l||![o,a,i].every(h=>Number.isInteger(h)&&h>0))return null;const u=o*a,d=u-i;if(d<0||d!==t)return null;const m=unitWord$1(r);return buildExample({generatorId:`${ID$2}.rate-chain-sub.v1`,question:n,title:`${o} × ${a} − ${i} = ?`,computed:{rate:o,count:a,product:u,sub:i,answer:d},steps:[{visual_data:{type:"equation_display",terms:[o,"×",a,"=",u]},animation:"multiply",caption:`${o} ${r} for each of ${a}: ${o} × ${a} = ${u} ${r}.`,spoken:`First we find how much is needed altogether. Each one needs the same amount, so we multiply: ${words$1(o)} times ${words$1(a)} is ${words$1(u)} ${m}.`},{visual_data:colSub(u,i,!1),animation:"show_subtract",caption:`Already have ${i}: ${u} − ${i} = ?`,spoken:`Some of that is already set aside, so it is not needed again. We take it away from the total: ${words$1(u)} take away ${words$1(i)}.`},{visual_data:colSub(u,i,!0),animation:"subtract_reveal",caption:`${u} − ${i} = ${d} ${r}.`,spoken:`${capitalised(words$1(u))} take away ${words$1(i)} leaves ${words$1(d)} ${m}. That is how much more is still needed.`}]})}function penceRunningDemo(n){const t=Number(n.correctAnswer.trim());if(!Number.isInteger(t))return null;const e=n.stem.match(/\d+\s*p\b/gi)??[];if(e.length!==3)return null;const s=e.map(b=>Number(b.replace(/\D/g,""))),[o,r,a]=s;let i=0;const l=e.map(b=>{const x=n.stem.indexOf(b,i);return i=x+b.length,{start:x,end:x+b.length}}),u=PENCE_MINUS.test(n.stem.slice(l[1].end,l[2].start))?-1:1,d=PENCE_MINUS.test(n.stem.slice(l[2].end))?-1:1,m=o+u*r,h=m+d*a;if(m<0||h<0||h!==t)return null;const f=u<0?"−":"+",g=d<0?"−":"+";return buildExample({generatorId:`${ID$2}.pence-running.v1`,question:n,title:`${o} ${f} ${r} ${g} ${a} = ?`,computed:{start:o,afterFirst:m,total:h,n1:r,n2:a,answer:h},steps:[{visual_data:chainOp$1(o,u*r,!1),animation:"show_problem",caption:`Start with ${o} pence.`,spoken:`Every amount here is already in pence, so we can add and take away straight away. We start with ${words$1(o)} pence.`},{visual_data:chainOp$1(o,u*r,!0),animation:"first_change",caption:`${o} ${f} ${r} = ${m}.`,spoken:u<0?`The first change takes some away: ${words$1(o)} take away ${words$1(r)} leaves ${words$1(m)} pence.`:`The first change adds some on: ${words$1(o)} add ${words$1(r)} makes ${words$1(m)} pence.`},{visual_data:chainOp$1(m,d*a,!0),animation:"second_change",caption:`${m} ${g} ${a} = ${h} pence.`,spoken:d<0?`The second change takes some away: ${words$1(m)} take away ${words$1(a)} leaves ${words$1(h)} pence. That is how much there is after both changes.`:`The second change adds more on: ${words$1(m)} add ${words$1(a)} makes ${words$1(h)} pence. That is how much there is after both changes.`}]})}const asP6M08Generator={id:`${ID$2}.two-step-convert.v1`,microSkillId:ID$2,forms:["context_calculation"],generate(n){if(/^£/.test(n.correctAnswer.trim()))return moneyChangeDemo(n);const t=acceptedBase(n.correctAnswer);return measureDemo(n)??(t!==null?measureAddChainDemo(n,t):null)??rateChainSubDemo(n)??penceRunningDemo(n)}},AS_P6_M08_GENERATORS=[asP6M08Generator],ID$1="AS.P6.M09",OUT_VERB=/\b(uses?|used|sells?|sold|ships? out|shipped out|lets? out|let out|pays? out|paid out|spends?|spent|releases?|released|sent out|sends? out|lends? out|lent out|grants? out|granted out|records? leaving|leaving|drained? away|drains?|drained|makes? up|made up|withdrawn|withdrew)\b/i,CLAUSE_DELIM=/\b(?:then|and)\b/gi,LABEL_NUM=/\b(?:leg|stage|part|lap|section|round|day|week|hour)\s+\d+\b/gi;function stripLabelNumbers(n){return n.replace(LABEL_NUM,"")}function changeSigns(n,t){let e=0;const s=t.map(l=>{const u=n.indexOf(l,e);return e=u+l.length,{start:u,end:u+l.length}}),o=[];for(const l of n.matchAll(CLAUSE_DELIM))o.push(l.index??0);const r=l=>{let u=-1;for(const d of o)d<l&&d>u&&(u=d);return u},a=l=>{for(const u of o)if(u>=l)return u;return n.length},i=[];for(let l=1;l<s.length;l++){const u=Math.max(s[l-1].end,r(s[l].start)),d=n.slice(u,s[l].start),m=l+1<s.length?s[l+1].start:n.length,h=Math.min(a(s[l].end),m),f=n.slice(s[l].end,h);i.push(OUT_VERB.test(`${d} ${f}`)?-1:1)}return i}function spellBig(n){if(n>=1e6){const t=Math.floor(n/1e6),e=n%1e6;return`${numberToWordsLong(t)} million${e?` ${e<100?"and ":""}${numberToWordsLong(e)}`:""}`}return numberToWordsLong(n)}function runningStep(n,t,e,s){return e>0?{vd:colAdd(Math.max(n,t),Math.min(n,t),s),result:n+t}:n<t?null:{vd:colSub(n,t,s),result:n-t}}function runningTotalDemo(n,t,e){const s=stripLabelNumbers(normaliseStem(n.stem)),o=allIntsGB(s).filter(S=>S!==0);if(o.length!==4)return null;const[r,a,i,l]=o;if([r,a,i,l].some(S=>S<=0))return null;const u=(s.match(/\d[\d,]*/g)??[]).filter(S=>Number(S.replace(/,/g,""))!==0);if(u.length!==4)return null;const d=changeSigns(s,u);if(d.length!==3)return null;const m=[a,i,l],h=r+d[0]*a+d[1]*i+d[2]*l;let f,g;if(e){const S=r-(d[0]*a+d[1]*i+d[2]*l);if(S!==t||S<0)return null;f=[],g=r;for(let M=2;M>=0;M--){const L=d[M]>0?-1:1,P=runningStep(g,m[M],L,M>0);if(!P)return null;f.push({vd:P.vd,result:P.result,change:m[M],sign:L}),g=P.result}if(g!==t)return null}else{if(h!==t)return null;f=[],g=r;for(let S=0;S<3;S++){const M=runningStep(g,m[S],d[S],S<2);if(!M)return null;f.push({vd:M.vd,result:M.result,change:m[S],sign:d[S]}),g=M.result}if(g!==t)return null}const[b,x,y]=f,E=S=>S<0?"−":"+",k=spellBig,T=S=>S<0?"take away":"add",A=S=>S<0?"added":"took away",N=S=>S<0?"take it away":"add it back";return buildExample({generatorId:e?`${ID$1}.running-reverse.v1`:`${ID$1}.running.v1`,question:n,title:e?"How many at the start?":"What is the total at the end?",computed:{head:r,r1:b.result,r2:x.result,answer:t,c1:b.change,c2:x.change,c3:y.change},steps:[{visual_data:b.vd,animation:"step_one",caption:e?`Undo the last change: ${r} ${E(b.sign)} ${b.change} = ${b.result}.`:`Start ${r} ${E(b.sign)} ${b.change} = ${b.result}.`,spoken:e?`We know the amount at the end, ${k(r)}, and want the amount at the start. So we work backwards, undoing each change, last one first. The last change ${A(b.sign)} ${k(b.change)}, so to undo it we ${N(b.sign)}. That gives ${k(b.result)}.`:`The amount changes three times, so we work through the changes in order, one at a time. We start with ${k(r)}
71 and ${T(b.sign)} ${k(b.change)}, which gives ${k(b.result)}.`},{visual_data:x.vd,animation:"step_two",caption:`${b.result} ${E(x.sign)} ${x.change} = ${x.result}.`,spoken:e?`Before that, the change ${A(x.sign)} ${k(x.change)}. To undo it, we ${N(x.sign)}, which gives ${k(x.result)}.`:`Next, from ${k(b.result)}, we ${T(x.sign)} ${k(x.change)}. That gives ${k(x.result)}.`},{visual_data:y.vd,animation:"step_three",caption:`${x.result} ${E(y.sign)} ${y.change} = ${t}.`,spoken:e?`The very first change ${A(y.sign)} ${k(y.change)}. Undoing it, we ${N(y.sign)} and get ${k(t)}. So the amount at the start was ${k(t)}.`:`Last, we ${T(y.sign)} ${k(y.change)} and get ${k(t)}. So the amount at the end is ${k(t)}.`}]})}function tableDemo(n,t){const e=n.visualData;if(!e||e.type!=="two_way_table")return null;const s=e.cells;let o=-1,r=-1;for(let f=0;f<s.length;f++)for(let g=0;g<s[f].length;g++)if(s[f][g]===null||s[f][g]===void 0){if(o!==-1)return null;o=f,r=g}if(o===-1)return null;const a=s[o].length-1,i=s[o].slice(0,a),l=r===a;let u,d;if(l){if(i.some(g=>typeof g!="number"))return null;const f=i;u=f.reduce((g,b)=>g+b,0),d=`${f.join(" + ")} = ${u}`}else{const f=s[o][a];if(typeof f!="number")return null;const g=i.filter((x,y)=>y!==r);if(g.some(x=>typeof x!="number"))return null;const b=g;u=f-b.reduce((x,y)=>x+y,0),d=`${f} − ${b.join(" − ")} = ${u}`}if(u!==t)return null;const m=s.map(f=>f.slice());m[o][r]=u;const h=(f,g)=>({type:"two_way_table",col_headers:e.col_headers,row_headers:e.row_headers,cells:f,...e.corner_label!==void 0?{corner_label:e.corner_label}:{},...g?{chip_expression:g}:{}});return buildExample({generatorId:`${ID$1}.two-way-table.v1`,question:n,title:"Find the missing number in the table.",computed:{answer:u},steps:[{visual_data:h(s),animation:"highlight_missing",caption:"One number in the table is missing.",spoken:"One number in the table is missing. Every row and column must add up to its total, so we can work the missing one out."},{visual_data:h(s,d),animation:"sum_known",caption:l?"Add the known parts of that line.":"Use the line's total and known parts.",spoken:"We look along the line that has the gap, using the numbers we already know and the total for that line."},{visual_data:h(m,d),animation:"reveal_answer",caption:l?`They add up to ${u}.`:`That leaves ${u}.`,spoken:l?"Adding the known parts of the line gives the missing total.":"Taking the known parts away from the line's total leaves the missing number."}]})}function ratePlusExtraDemo(n,t){const s=normaliseStem(n.stem).match(/earns?\s+(\d+)\s*points?\s*per\s+(\w+)\s+and\s+(\d+)\s*per\s+(\w+)\.\s*[a-z ]*\bhave\s+(\d+)\s*\2s?\s+and\s+(\d+)\s*\4s?,?\s*then\s+(\w+)\s+(\d+)\s*more\b/i);if(!s)return null;const o=Number(s[1]),r=s[2].toLowerCase(),a=Number(s[3]),i=Number(s[5]),l=Number(s[6]),u=s[7].toLowerCase(),d=Number(s[8]);if(![o,a,i,l,d].every(x=>Number.isInteger(x)&&x>0)||!u.startsWith(r.slice(0,3))&&!r.startsWith(u.slice(0,3))||[o,i,a,l,d].some(x=>x>10))return null;const m=i*o,h=l*a,f=d*o,g=m+h,b=g+f;return b!==t?null:buildExample({generatorId:`${ID$1}.rate-plus-extra.v1`,question:n,title:"Work out each part, then add them.",computed:{r1:o,r2:a,c1:i,c2:l,c3:d,p1:m,p2:h,running1:g,p3:f,answer:b},steps:[{visual_data:{type:"array",rows:i,cols:o},animation:"product_one",caption:`${i} ${r}s at ${o} points each: ${i} × ${o} = ${m}.`,spoken:`Each kind of result is worth a different number of points, so we work out each kind on its own. We start with the first kind: ${spellBig(i)} times ${spellBig(o)} is ${spellBig(m)}.`},{visual_data:{type:"array",rows:l,cols:a},animation:"product_two",caption:`${l} more at ${a} point${a===1?"":"s"} each: ${l} × ${a} = ${h}. So far: ${m} + ${h} = ${g}.`,spoken:`Then the second kind: ${spellBig(l)} times ${spellBig(a)} is ${spellBig(h)}. We add that to the points so far: ${spellBig(m)} add ${spellBig(h)} makes ${spellBig(g)}.`},{visual_data:{type:"array",rows:d,cols:o},animation:"product_three",caption:`${d} more at ${o} points each: ${d} × ${o} = ${f}. Total: ${g} + ${f} = ${b}.`,spoken:`The extra results are the first kind again, so ${spellBig(d)} times ${spellBig(o)} is ${spellBig(f)}. Adding those on, ${spellBig(g)} add ${spellBig(f)} makes ${spellBig(b)} points altogether.`}]})}const RATE_UNIT_WORDS={g:"grams",kg:"kilograms",ml:"millilitres",l:"litres",cm:"centimetres",m:"metres"};function rateChainDemo(n,t){const s=normaliseStem(n.stem).match(/uses\s+(\d+)\s*(g|kg|ml|l|cm|m)\s*(?
71:of\s*\w+\s*)?per\s*\w+\s*for\s*(\d+)\s*\w+,?\s*adds?\s+a\s+(\d+)\s*\2\s*extra\s*\w+,?\s*then\s*scrapes\s+(\d+)\s*\2\s*back\s*off/i);if(!s)return null;const o=Number(s[1]),r=s[2].toLowerCase(),a=Number(s[3]),i=Number(s[4]),l=Number(s[5]);if(![o,a,i,l].every(f=>Number.isInteger(f)&&f>0))return null;const u=o*a,d=u+i,m=d-l;if(d<0||m<0||m!==t)return null;const h=RATE_UNIT_WORDS[r]??r;return buildExample({generatorId:`${ID$1}.rate-chain.v1`,question:n,title:`${o} × ${a} + ${i} − ${l} = ?`,computed:{rate:o,count:a,product:u,extra:i,afterExtra:d,sub:l,answer:m},steps:[{visual_data:{type:"equation_display",terms:[o,"×",a,"=",u]},animation:"multiply",caption:`${o} ${r} for each of ${a}: ${o} × ${a} = ${u} ${r}.`,spoken:`First we find how much is used across all of them. Each one uses the same amount, so we multiply: ${spellBig(o)} times ${spellBig(a)} is ${spellBig(u)} ${h}.`},{visual_data:colAdd(u,i,!0),animation:"add_extra",caption:`${u} + ${i} = ${d}.`,spoken:`The extra batch is used as well, so we add it on: ${spellBig(u)} add ${spellBig(i)} makes ${spellBig(d)}.`},{visual_data:colSub(d,l,!0),animation:"subtract_back",caption:`${d} − ${l} = ${m} ${r}.`,spoken:`The amount scraped back was not used after all, so we take it off: ${spellBig(d)} take away ${spellBig(l)} leaves ${spellBig(m)} ${h}. That is the total actually used.`}]})}const asP6M09Generator={id:`${ID$1}.multi-step.v1`,microSkillId:ID$1,forms:["context_calculation","context_reasoning"],generate(n){if(n.visualData&&n.visualData.type==="two_way_table"){const a=acceptedIntGB(n.correctAnswer);return a===null?null:tableDemo(n,a)}const t=acceptedIntGB(n.correctAnswer);if(t===null)return null;const e=normaliseStem(n.stem),s=ratePlusExtraDemo(n,t);if(s)return s;const o=rateChainDemo(n,t);if(o)return o;const r=/start with|at the start|were there at the start/i.test(e);return runningTotalDemo(n,t,r)}},AS_P6_M09_GENERATORS=[asP6M09Generator],ID="AS.P6.M10",round1000=n=>Math.round(n/1e3)*1e3,nw$1=n=>numberToWordsLong(n);function chainOp(n,t,e){return t<0?colSub(n,-t,e):colAdd(n,t,e)}const PLACE_NAMES=["ones","tens","hundreds","thousands","ten thousands","hundred thousands","millions"];function digitsFromRight(n,t){const e=String(n).padStart(t,"0"),s=[];for(let o=0;o<t;o++)s.push(Number(e[e.length-1-o]));return s}function borrowCascade(n,t){const e=Math.max(String(n).length,String(t).length),s=digitsFromRight(n,e),o=digitsFromRight(t,e);if(s[0]>=o[0])return null;const r=[];let a=1;for(;a<e&&s[a]===0;)r.push(PLACE_NAMES[a]??`place ${a}`),a++;return a>=e||r.length===0?null:{zeroPlaces:r,lenderPlace:PLACE_NAMES[a]??`place ${a}`,lenderDigit:s[a]}}const ADD_CLAIM=/adding ([\d,]+)(?: and ([\d,]+))? then (taking away|adding) ([\d,]+)/i,SUB_CLAIM=/taking away ([\d,]+) then (adding|taking away) ([\d,]+)/i,CONST_DIFF_CLAIM=/(\d[\d,]*)\s*[−-]\s*(\d[\d,]*)\s*mentally by adding (\d+) to both numbers,?\s*then subtracting/i;function constDiffDemo(n){const e=normaliseStem(n.stem).match(CONST_DIFF_CLAIM);if(!e)return null;const s=stripInt(e[1]),o=stripInt(e[2]),r=Number(e[3]);if(![s,o,r].every(d=>Number.isInteger(d)&&d>0)||s<=o||!isAffirmative(n.correctAnswer))return null;const a=s-o,i=s+r,l=o+r;return i-l!==a?null:buildExample({generatorId:`${ID}.constant-difference.v1`,question:n,title:`${s} − ${o} = (${s}+${r}) − (${o}+${r})`,computed:{A:s,B:o,k:r,A2:i,B2:l,answer:a},steps:[{visual_data:colSub(s,o,!1),animation:"show_problem",caption:`${s} − ${o} = ?`,spoken:"To subtract mentally, we can make the numbers easier to work with, as long as we keep the gap between them the same."},{visual_data:colSub(i,l,!1),animation:"add_both",caption:`Add ${r} to both numbers: ${i} − ${l} = ?`,spoken:`If we add the same small amount to both numbers, the bigger number and the smaller number both move up together, so the gap between them stays exactly the same. Adding ${nw$1(r)} to both gives ${nw$1(i)} take away ${nw$1(l)}.`},{visual_data:colSub(i,l,!0),animation:"reveal",caption:`${i} − ${l} = ${a} — the same as ${s} − ${o} = ${a}.`,spoken:`${capitalised(nw$1(i))} take away ${nw$1(l)} is ${nw$1(a)} — exactly the same answer as the original subtraction, because the gap between them never changed. So this method does work.`}]})}function judgementDemo(n){const t=normaliseStem(n.stem),e=t.match(/(\d[\d,]*)\s*([+\-])\s*(\d[\d,]*)/);if(!e)return null;const s=stripInt(e[1]),o=e[2]==="+",r=stripInt(e[3]),a=o?s+r:s-r;let i=null;if(o){const g=t.match(ADD_CLAIM);if(!g)return null;const b=stripInt(g[1]),x=g[2]?stripInt(g[2]):0,y=g[3].toLowerCase()==="adding"?1:-1,E=stripInt(g[4]);x>0?i=b+x+y*E:i=(Math.abs(s-b)<=Math.abs(r-b)?r:s)+b+y*E}else{const g=t.match(SUB_CLAIM);if(!g)return null;const b=stripInt(g[1]),x=g[2].toLowerCase()==="adding"?1:-1,y=stripInt(g[3]);i=s-b+x*y}if(i===null||i===a!==isAffirmative(n.correctAnswer))return null;const u=numberToWordsLong;if(o){const g=s-round1000(s),b=r-round1000(r),x=Math.abs(g)<=9,y=Math.abs(b)<=9;if(!x&&!y)return null;const E=x?s:r,k=x?r:s,T=round1000(E),A=E-T;if(k+T+A!==a)return null;const N=k+T,S=A<0?"−":"+";return buildExample({generatorId:`${ID}.compensation-add.v1`,question:n,title:`${k} + ${T} ${S} ${Math.abs(A)} = ${a}`,computed:{kept:k,rounded:E,round:T,adjust:Math.abs(A),step1Result:N,answer:a},steps:[{visual_data:colAdd(k,T,!1),animation:"round_up",caption:`${E} is close to ${T} — round it.`,spoken:`Adding a round number is much easier to do in your head. One number is very close to ${u(T)}, so we round it and add ${u(T)} instead.`},{visual_data:colAdd(k,T,!0),animation:"add_round",caption:`${k} + ${T} = ${N}.`,spoken:`The round number ends in zeros, so adding ${u(T)} to the other number is quick.`},{visual_data:chainOp(N,A,!0),animation:"compensate",caption:`Adjust ${S}${Math.abs(A)} → ${a}.`,spoken:A<0?`But rounding up added ${u(Math.abs(A))} too much, so we take ${u(Math.abs(A))}
71 back off to get the exact answer. That is why the adjustment must be a subtraction.`:`Rounding down left out ${u(Math.abs(A))}, so we add ${u(Math.abs(A))} back on to get the exact answer. That is why the adjustment must be an addition.`}]})}const d=round1000(r),m=d-r;if(Math.abs(m)>9||s-d+m!==a)return null;const h=s-d,f=m<0?"−":"+";return buildExample({generatorId:`${ID}.compensation-sub.v1`,question:n,title:`${s} − ${d} ${f} ${Math.abs(m)} = ${a}`,computed:{minuend:s,subtrahend:r,round:d,adjust:Math.abs(m),step1Result:h,answer:a},steps:[{visual_data:colSub(s,d,!1),animation:"round_up",caption:`${r} is close to ${d} — take that away.`,spoken:`Taking away a round number is much easier to do in your head. The number we are subtracting is very close to ${u(d)}, so we take away ${u(d)} instead.`},{visual_data:colSub(s,d,!0),animation:"sub_round",caption:`${s} − ${d} = ${h}.`,spoken:`The round number ends in zeros, so taking away ${u(d)} is quick.`},{visual_data:chainOp(h,m,!0),animation:"compensate",caption:`Adjust ${f}${Math.abs(m)} → ${a}.`,spoken:m>0?`But we took away ${u(Math.abs(m))} too much, so we add ${u(Math.abs(m))} back on to get the exact answer. That is why the adjustment must be an addition.`:`We had not taken quite enough away, so we take away ${u(Math.abs(m))} more to get the exact answer. That is why the adjustment must be a subtraction.`}]})}const TIME_CLAIM=/(\d+)\s*hours?\s*(\d+)\s*minutes?\s*to\s*(?:one of|a journey of)?\s*(\d+)\s*hours?\s*(\d+)\s*minutes?\s*by adding\s*(\d+)\s*hours?\s*then\s*(taking away|adding)\s*(\d+)\s*minutes?/i;function roundHourAdj(n,t){return t===0?{roundedH:n,adj:0}:t>=30?{roundedH:n+1,adj:-(60-t)}:{roundedH:n,adj:t}}function formatHM(n){const t=Math.floor(n/60),e=n%60;return t===0?`${e}m`:e===0?`${t}h`:`${t}h ${e}m`}function timeCompensationDemo(n){const e=normaliseStem(n.stem).match(TIME_CLAIM);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]),a=Number(e[4]),i=Number(e[5]),l=e[6].toLowerCase()==="taking away"?-1:1,u=Number(e[7]);if([s,o,r,a,i,u].some(S=>!Number.isInteger(S)||S<0)||o>=60||a>=60||u>=60)return null;const d=roundHourAdj(s,o),m=roundHourAdj(r,a);if(d.roundedH+m.roundedH!==i)return null;const h=s*60+o+r*60+a,f=d.adj+m.adj;if(i*60+f!==h)return null;const b=i*60+l*u,x=b===h;if(x!==isAffirmative(n.correctAnswer))return null;const y=S=>S<0?"−":"+",E=S=>S<0?"+":"−",k=y(f),T=Math.floor(h/60),A=h%60,N=`${nw$1(T)} hour${T===1?"":"s"}${A?` and ${nw$1(A)} minute${A===1?"":"s"}`:""}`;return buildExample({generatorId:`${ID}.time-compensation.v1`,question:n,title:`${s}h ${o}m + ${r}h ${a}m = ?`,computed:{h1:s,m1:o,h2:r,m2:a,roundedH1:d.roundedH,roundedH2:m.roundedH,roundAdj1:Math.abs(d.adj),roundAdj2:Math.abs(m.adj),netAdjustment:Math.abs(f),claimedHours:i,claimedResultMin:b,exactTotalMin:h,exactHours:Math.floor(h/60),exactMinutes:h%60,answer:h},steps:[{visual_data:{type:"equation_display",terms:[`${s}h ${o}m`,"→",`${d.roundedH}h`,`(${E(d.adj)}${Math.abs(d.adj)} min)`]},animation:"round_first",caption:`${s}h ${o}m → ${d.roundedH}h (${E(d.adj)}${Math.abs(d.adj)} min to round; undo with ${y(d.adj)}${Math.abs(d.adj)}
71 min later).`,spoken:d.adj<0?"To add these mentally, first round the first time UP to the next whole hour, and remember that we will need to take that small amount back off later.":"To add these mentally, first round the first time DOWN to the whole hour before it, and remember that we will need to add that small amount back on later."},{visual_data:{type:"equation_display",terms:[`${r}h ${a}m`,"→",`${m.roundedH}h`,`(${E(m.adj)}${Math.abs(m.adj)} min)`]},animation:"round_second",caption:`${r}h ${a}m → ${m.roundedH}h (${E(m.adj)}${Math.abs(m.adj)} min to round). Rounded hours: ${d.roundedH}h + ${m.roundedH}h = ${i}h.`,spoken:m.adj<0?`Round the second time UP the same way, then add the two whole hours — that gives the claim's rounded total, ${nw$1(i)} hours.`:`Round the second time DOWN the same way, then add the two whole hours — that gives the claim's rounded total, ${nw$1(i)} hours.`},{visual_data:{type:"equation_display",terms:[`(${y(d.adj)}${Math.abs(d.adj)})`,"+",`(${y(m.adj)}${Math.abs(m.adj)})`,"=",`${k}${Math.abs(f)} min`]},animation:"reveal",caption:`Corrections combine: (${y(d.adj)}${Math.abs(d.adj)}) + (${y(m.adj)}${Math.abs(m.adj)}) = ${k}${Math.abs(f)} min. Applied: ${i}h ${k} ${Math.abs(f)} = ${formatHM(h)}.`,spoken:x?`Every number that got rounded needs its own correction added back. Combining both corrections and applying them to the rounded total reaches the exact answer, ${N}, so the method works.`:`Every number that gets rounded needs its own correction back. The claim only corrected one of the two roundings, so combining both corrections properly gives a different, exact answer, ${N} — the claim misses it.`}]})}function misconceptionDemo(n){const t=stripInt(n.correctAnswer.trim());if(!Number.isInteger(t))return null;let e,s,o;const r=n.visualData;if(r&&r.type==="column_method")e=r.operation,s=r.top,o=r.bottom;else{const u=normaliseStem(n.stem),d=u.match(/(\d[\d,]*)\s*([+\-])\s*(\d[\d,]*)/);if(d)s=stripInt(d[1]),e=d[2]==="+"?"add":"subtract",o=stripInt(d[3]);else{const m=u.match(/adds? ([\d,]+) and ([\d,]+)/i);if(!m)return null;s=stripInt(m[1]),o=stripInt(m[2]),e="add"}}const a=e==="add"?s+o:s-o;if(e==="subtract"&&s<o||a!==t)return null;const i=e==="subtract"?borrowCascade(s,o):null,l=e==="add"?colAdd(s,o):colSub(s,o);return buildExample({generatorId:`${ID}.spot-error.v1`,question:n,title:"What is the correct answer?",computed:{top:s,bottom:o,answer:a},steps:[{visual_data:l,animation:"setup",caption:`Line up ${s} and ${o} by place value.`,spoken:"The mistake was a slip in the method, so let's work it carefully. First we line the numbers up by place value."},{visual_data:e==="add"?colAdd(s,o):colSub(s,o),animation:e==="add"?"carry_ones":"exchange_ones",caption:e==="add"?"Work each column, carrying every ten.":i?`Exchange skips the zero columns, reaching the ${i.lenderPlace}.`:"Work each column, exchanging when needed.",spoken:e==="add"?"We add each column from the right. Whenever a column reaches ten or more, we carry a ten into the next column — that is the step that is easy to miss.":i?"To exchange a ten for the ones, we look at the next column — but it is zero, so there is nothing to take. We keep moving left until we reach a column that is not zero. That column lends the ten, and every zero column we passed becomes a nine instead.":"We subtract each column from the right. When the top digit is too small, we exchange a ten from the next column — that is the step to watch."},{visual_data:e==="add"?colAdd(s,o):{...colSub(s,o),...i?{keep_exchange:!0}:{}},animation:"reveal",caption:`The correct answer is ${a}.`,spoken:"Working every column properly gives the correct answer."}]})}function estimateBestDemo(n){const t=normaliseStem(n.stem),e=allIntsGB(t);if(e.length!==2)return null;const[s,o]=e,r=/\bleft\b/i.test(t),a=r?s-o:s+o,i=n.options??[];if(i.length<2)return null;const l=i.map(x=>{const y=allIntsGB(x);return y.length?y[0]:NaN});if(l.some(x=>Number.isNaN(x)))return null;const u=stripInt(n.correctAnswer.replace(/about/i,""));let d=l[0];for(const x of l)Math.abs(x-a)<Math.abs(d-a)&&(d=x);if(d!==u)return null;const m=round1000(s),h=round1000(o);if(m<=0||h<=0)return null;const f=r?m-h:m+h;if(r&&m<h)return null;const g=r?colSub(m,h):colAdd(m,h),b=r?"−":"+";return buildExample({generatorId:`${ID}.estimate-best.v1`,question:n,title:"Roughly how many?",computed:{a:s,b:o,ra:m,rb:h,estimate:f,answer:u},steps:[{visual_data:g,animation:"round",caption:`Round: ${s}→${m}, ${o}→${h}.`,spoken:`For a rough answer we do not need to be exact. We round each number to the nearest thousand, which is much easier to work with: ${nw$1(s)} rounds to ${nw$1(m)}, and ${nw$1(o)} rounds to ${nw$1(h)}.`}
71,{visual_data:r?colSub(m,h):colAdd(m,h),animation:r?"sub_round":"add_round",caption:`${m} ${b} ${h} = ${f}.`,spoken:r?`We subtract the rounded numbers — quick because they end in zeros. ${capitalised(nw$1(m))} take away ${nw$1(h)} is ${nw$1(f)}.`:`We add the rounded numbers — quick because they end in zeros. ${capitalised(nw$1(m))} add ${nw$1(h)} is ${nw$1(f)}.`},{visual_data:r?colSub(m,h):colAdd(m,h),animation:"reveal",caption:`About ${u} — the closest choice.`,spoken:`The estimate is close to the real answer, so we pick the choice nearest to it: about ${nw$1(u)}.`}]})}function estimateSufficiencyDemo(n){const t=normaliseStem(n.stem),e=t.match(/budget of ([\d,]+)|is ([\d,]+) pounds enough/i);if(!e)return null;const s=stripInt(e[1]??e[2]),o=allIntsGB(t).filter(x=>x!==s);if(o.length!==2)return null;const[r,a]=o,i=r+a<=s,l=(x,y)=>Math.round(x/y)*y;let u=1e3,d=l(r,u),m=l(a,u),h=d+m;if(h===s&&(u=100,d=l(r,u),m=l(a,u),h=d+m),h===s)return null;const f=h<s;if(f!==i||f!==isAffirmative(n.correctAnswer))return null;const g=f?"under budget":"over budget",b=u===1e3?"thousand":"hundred";return buildExample({generatorId:`${ID}.estimate-enough.v1`,question:n,title:"Is the budget enough?",computed:{a:r,b:a,ra:d,rb:m,estimate:h,budget:s,answer:h},steps:[{visual_data:colAdd(d,m),animation:"round",caption:`Round costs: ${r}→${d}, ${a}→${m}.`,spoken:`To decide quickly, we do not need the exact total. We round each cost to the nearest ${b} and add those instead: ${nw$1(r)} rounds to ${nw$1(d)}, and ${nw$1(a)} rounds to ${nw$1(m)}.`},{visual_data:colAdd(d,m),animation:"add_round",caption:`${d} + ${m} = ${h}.`,spoken:`Adding the rounded costs gives a good estimate of the total needed: ${nw$1(d)} add ${nw$1(m)} is about ${nw$1(h)}.`},{visual_data:colAdd(d,m),animation:"reveal",caption:`About ${h} vs ${s} → ${g}.`,spoken:f?`About ${nw$1(h)} is clearly below the budget of ${nw$1(s)}, so yes, there is enough.`:`About ${nw$1(h)} is clearly above the budget of ${nw$1(s)}, so no, there is not enough.`}]})}const asP6M10Generator={id:`${ID}.method-choice.v1`,microSkillId:ID,forms:["worked_example_judgement","misconception_contrast_probe","estimate_best_choice","estimate_sufficiency"],generate(n){switch(n.mathematicalForm){case"worked_example_judgement":return timeCompensationDemo(n)??constDiffDemo(n)??judgementDemo(n);case"misconception_contrast_probe":return misconceptionDemo(n);case"estimate_best_choice":return estimateBestDemo(n);case"estimate_sufficiency":return estimateSufficiencyDemo(n);default:return null}}},AS_P6_M10_GENERATORS=[asP6M10Generator],GEN_ID$5="DS.P5.M01.mean.v1";function eqVisual$4(n,t){return{type:"equation_display",terms:n,...t&&t.length?{highlight:t}:{}}}function barsVisual(n,t,e,s){return{type:"bar_chart",bars:n,axis_max:t,hide_diff:!0,...e&&e.length?{lit_indices:e}:{},...s?{axis_step:s}:{}}}const WORD_COUNT={three:3,four:4,five:5,six:6,seven:7,eight:8};function extractList$1(n){const t=normaliseStem(n).match(/\d+(?:\s*(?:,|and)\s*\d+)+/);if(!t)return null;const e=(t[0].match(/\d+/g)??[]).map(Number);return e.length>=2?e:null}function extractListFromTable(n){if(!n||typeof n!="object")return null;const t=n;if(t.type!=="two_way_table")return null;const e=t.cells;if(!Array.isArray(e)||e.length!==1)return null;const s=e[0];if(!Array.isArray(s)||s.length<2)return null;const o=s.map(Number);return o.some(r=>!Number.isInteger(r)||r<0)?null:o}function plusTerms(n){const t=[];return n.forEach((e,s)=>{s&&t.push("+"),t.push(e)}),t}function speakList(n){const t=n.map(e=>numberToWordsLong(e));return t.length===1?t[0]:`${t.slice(0,-1).join(", ")} and ${t[t.length-1]}`}function parseValueCount(n){const t=n.match(/\b(\d+)\s+(?:numbers|values|scores)\b/i);if(t)return Number(t[1]);const e=n.match(/\b(three|four|five|six|seven|eight)\s+(?:numbers|values|scores)\b/i);return e?WORD_COUNT[e[1].toLowerCase()]??null:null}function effectOfAddingDemo(n){const t=normaliseStem(n.stem);if(!/\bmean\b/.test(t)||!/\b(added|scores?|scored)\b/.test(t))return null;const e=(t.match(/\d+/g)??[]).map(Number);if(e.length!==2)return null;const[s,o]=e,r=o===s?"same":o<s?"down":"up",a=r==="same"?"it stays the same":r==="down"?"it goes down":"it goes up";if(n.correctAnswer.trim().toLowerCase()!==a)return null;const i=numberToWordsLong(s),l=numberToWordsLong(o),u=parseValueCount(t)??3,d=u,m=Array.from({length:u},(x,y)=>({label:`${y+1}`,value:s})),h=[...m,{label:"New",value:o}],f=(u*s+o)/(u+1),g=[...m,{label:"New",value:o}].map(x=>({...x,value:f})),b=Math.max(s,o)+Math.max(2,Math.ceil(Math.max(s,o)*.15));return buildExample({generatorId:"DS.P5.M01.effect-of-adding.v1",question:n,title:n.stem,computed:{mean:s,newVal:o,count:u,newMean:f},steps:[{visual_data:barsVisual(m,b),animation:"show_level",caption:`The mean right now is ${s}.`,spoken:`The mean is the level every value would sit at if we shared them out equally. Picture the values sitting level at the mean, ${i}. A new value, ${l}, is about to join them.`},{visual_data:barsVisual(h,b,[d]),animation:"add_bar",caption:`A new bar joins: ${o}.`,spoken:`A new bar joins at ${l}. To see what happens to the mean, we compare its height with the level of ${i} that the others sit at.`},{visual_data:r==="same"?barsVisual(h,b,[d]):{...barsVisual(g,b),lit_indices:[]},animation:"verdict",caption:r==="same"?`${o} = ${s}, so the level stays the same.`:r==="down"?`${o} is below ${s} — the level drops.`:`${o} is above ${s} — the level rises.`,spoken:r==="same"?`${capitalised(l)} is exactly the same as ${i}, so the new bar is the same height as the others and does not change the level. The mean stays the same.`:r==="down"?`${capitalised(l)} is less than ${i}, so the new bar is shorter than the others and pulls the level down. Watch every bar settle at the new, lower level. The mean goes down.`:`${capitalised(l)} is more than ${i}, so the new bar is taller than the others and pulls the level up. Watch every bar settle at the new, higher level. The mean goes up.`}]})}const DS_P5_M01_GENERATORS=[{id:"DS.P5.M01.effect-of-adding.v1",microSkillId:"DS.P5.M01",forms:["comparison_statement"],generate:effectOfAddingDemo},{id:GEN_ID$5,microSkillId:"DS.P5.M01",forms:["symbolic_numeric_entry"],generate(n){const t=answerInt(n);if(t==null)return null;const e=normaliseStem(n.stem),s=e.match(/mean (?:of|over) (\d+)[a-z\s]*?\bis (\d+)/i),o=e.match(/(\w+) (?:race times|times|numbers|scores|innings) average (\d+)/i);let r=null,a=null;if(s?(r=Number(s[1]),a=Number(s[2])):o&&(r=WORD_COUNT[o[1].toLowerCase()]??null,a=Number(o[2])),r!=null&&a!=null){const d=extractList$1(e);if(!d||d.length!==r-1)return null;const m=r*a,h=d.reduce((f,g)=>f+g,0);return m-h!==t?null:buildExample({generatorId:GEN_ID$5,question:n,title:n.stem,computed:{answer:t,count:r,mean:a,total:m,knownSum:h},steps:[{visual_data:eqVisual$4([a,"×",r,"=",m],[0,1,2]),animation:"total_needed",caption:`Mean × count: ${a} × ${r} = ${m}.`,spoken:`If the mean of ${numberToWordsLong(r)} values is ${numberToWordsLong(a)}, then together they must total ${numberToWordsLong(a)} times ${numberToWordsLong(r)}, which is ${numberToWordsLong(m)}.`},{visual_data:eqVisual$4(plusTerms(d),[]),animation:"add_known",caption:`Known values add to ${h}.`,spoken:`Add up the values we already know: ${speakList(d)}. That comes to ${numberToWordsLong(h)}.`},{visual_data:eqVisual$4([m,"−",h,"=",t],[4]),animation:"finish",caption:`${m} − ${h} = ${t}.`,spoken:`The missing value is whatever is still needed, so take the known values away from the full total. ${capitalised(numberToWordsLong(m))} take away ${numberToWordsLong(h)} is ${numberToWordsLong(t)}. So the missing value is ${numberToWordsLong(t)}.`}]})}const i=extractList$1(e)??extractListFromTable(n.visualData);if(!i||i.length<2)return null;const l=i.reduce((d,m)=>d+m,0),u=i.length;if(l%u!==0||l/u!==t)return null;if(u<=7&&Math.max(...i)<=100){const d=i.map((x,y)=>({label:`${y+1}`,value:x})),m=d.map(x=>({...x,value:t})),h=Math.max(...i),f=10,g=Math.ceil((h+1)/f)*f,b=d.map((x,y)=>y);return buildExample({generatorId:GEN_ID$5,question:n,title:n.stem,computed:{answer:t,sum:l,count:u},steps:[{visual_data:barsVisual(d,g,void 0,f),animation:"show_bars",caption:"Some values are big and some are small.",spoken:`Here are the ${numberToWordsLong(u)} values as bars: ${speakList(i)}. Some bars are tall and some are short. The mean is the height they would all be if we shared everything out equally.`},{visual_data:barsVisual(d,g,b,f),animation:"add_all",caption:`Add them all: ${i.join(" + ")} = ${l}.`,spoken:`To share them out, we first put everything together. ${capitalised(speakList(i))} make ${numberToWordsLong(l)} altogether.`},{visual_data:barsVisual(m,g,b,f),animation:"level_out",caption:`${l} ÷ ${u} = ${t}. Every bar levels at ${t}.`,spoken:`Now we share the ${numberToWordsLong(l)} equally between the ${numberToWordsLong(u)} bars: ${numberToWordsLong(l)} divided by ${numberToWordsLong(u)} is ${numberToWordsLong(t)}. Watch every bar settle at the same height, ${numberToWordsLong(t)}. So the mean is ${numberToWordsLong(t)}.`}]})}return buildExample({generatorId:GEN_ID$5,question:n,title:n.stem,computed:{answer:t,sum:l,count:u},steps:[{visual_data:eqVisual$4(plusTerms(i),[]),animation:"add_all",caption:"Add every value.",spoken:`The mean shares everything out equally. To find it, first add every value together to make one total: ${speakList(i)}.`},{visual_data:eqVisual$4([l,"÷",u],[0]),animation:"share",caption:`Total ${l}. There are ${u} values, so ÷ ${u}
71.`,spoken:`Altogether the values make ${numberToWordsLong(l)}. There are ${numberToWordsLong(u)} values, so we share the ${numberToWordsLong(l)} equally between ${numberToWordsLong(u)}: ${numberToWordsLong(l)} divided by ${numberToWordsLong(u)}.`},{visual_data:eqVisual$4([l,"÷",u,"=",t],[4]),animation:"finish",caption:`${l} ÷ ${u} = ${t}.`,spoken:`${capitalised(numberToWordsLong(l))} divided by ${numberToWordsLong(u)} is ${numberToWordsLong(t)}. So the mean is ${numberToWordsLong(t)}.`}]})}}],GEN_ID$4="DS.P5.M02.mode_median.v1";function eqVisual$3(n,t){return{type:"equation_display",terms:n,...t&&t.length?{highlight:t}:{}}}function extractList(n){const t=normaliseStem(n).match(/\d+(?:\s*(?:,|and)\s*\d+)+/);if(!t)return null;const e=(t[0].match(/\d+/g)??[]).map(Number);return e.length>=3?e:null}function uniqueMode(n){const t=new Map;for(const r of n)t.set(r,(t.get(r)??0)+1);let e=-1,s=null,o=!1;for(const[r,a]of t)a>e?(e=a,s=r,o=!1):a===e&&(o=!0);return o||e<2?null:s}const isAscending=n=>n.every((t,e)=>e===0||t>=n[e-1]);function timesWord$1(n){return n===1?"once":n===2?"twice":`${numberToWordsLong(n)} times`}function modeSteps(n,t){const e=numberToWordsLong(t),s=n.filter(r=>r===t).length,o=isAscending(n);return{sortCaption:o?"Already in order — equal values sit together.":"Sort so equal values group up.",s1:"The mode is the value that appears most often. To find it, we count how many times each value appears.",s2:o?"These values are already in order, so any repeated values already sit side by side, ready to count.":"Sorting them in order puts any repeated values side by side, so they are easy to count.",s3:`${capitalised(e)} appears ${timesWord$1(s)}, more than any other value. So the mode is ${e}.`}}function medianContrastProbe(n){const t=normaliseStem(n.stem),e=extractList(t);if(!e||e.length<3)return null;const s=n.correctAnswer.match(/median[^\d]*?(\d+(?:\.\d+)?)/i);if(!s)return null;const o=Number(s[1]),r=[...e].sort((y,E)=>y-E),a=r.length,i=a%2===0,l=i?(r[a/2-1]+r[a/2])/2:r[(a-1)/2];if(l!==o)return null;const u=i?[a/2-1,a/2]:[(a-1)/2],d=uniqueMode(e),m=e[Math.floor((a-1)/2)],h=d!=null&&d!==l,f=m!==l&&!(d!=null&&m===d),g=Number.isInteger(l)?numberToWordsLong(l):decimalToWords(l);let b=`Median = ${l}`,x=`The median is ${g}, the true middle of the sorted values.`;return h&&f?(b+=` — not ${d} (the mode) or ${m} (unsorted middle).`,x=`The median is ${g}. That is not ${numberToWordsLong(d)}, which is only the mode — the value that repeats most. And it is not ${numberToWordsLong(m)}, which was just the middle position before sorting.`):h?(b+=` — not ${d}, the mode.`,x=`The median is ${g}. That is not ${numberToWordsLong(d)}, which is only the mode — the value that repeats most, not the middle.`):f?(b+=` — not ${m}, the unsorted middle.`,x=`The median is ${g}. That is not ${numberToWordsLong(m)}, which was just the middle position before sorting.`):b+=".",buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{answer:l,...d!=null?{mode:d}:{},rawMid:m},steps:[{visual_data:eqVisual$3(e,[]),animation:"show_data",caption:"Values repeat — sort before finding the middle.",spoken:"Some values repeat here, which can trick you. Before finding the median, sort the values from smallest to largest."},{visual_data:eqVisual$3(r,u),animation:"sort",caption:`Sorted — the true middle is ${l}.`,spoken:i?`Sorted from smallest to largest, two values share the middle, so the median is halfway between them: ${g}.`:`Sorted from smallest to largest, walk in from both ends to the middle value: ${g}.`},{visual_data:eqVisual$3(r,u),animation:"verdict",caption:b,spoken:x}]})}function modeMeanCompareProbe(n){const t=normaliseStem(n.stem);if(!/\bmode\b/.test(t)||!/\bmean\b/.test(t)||!/larger|bigger|greater/.test(t))return null;const e=extractList(t);if(!e||e.length<3)return null;const s=uniqueMode(e);if(s==null)return null;const o=e.reduce((b,x)=>b+x,0),r=e.length;if(o%r!==0)return null;const a=o/r,i=n.correctAnswer.trim().toLowerCase(),l=a>s?"the mean":s>a?"the mode":"they are equal";if(i!==l)return null;const u=numberToWordsLong(s),d=numberToWordsLong(a),m=a>s?">":a<s?"<":"=",h=m===">"?`Mean ${a} > mode ${s} — the mean is larger.`:m==="<"?`Mode ${s} > mean ${a} — the mode is larger.`:`Mode and mean are both ${s} — equal.`,f=l==="the mean"?`${capitalised(d)} is bigger than ${u}, so the mean is larger. A high value that appears just once can drag the mean up, even though the mode — the most frequent value — stays low.`:l==="the mode"?`${capitalised(u)} is bigger than ${d}, so the mode is larger.`:"The mode and the mean come out exactly the same here.",g=[];return e.forEach((b,x)=>{x&&g.push("+"),g.push(b)}),g.push("=",o),buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{mode:s,mean:a,sum:o,count:r},steps:[{visual_data:eqVisual$3(e,[]),animation:"find_mode",caption:`Mode: ${s} repeats most.`,spoken:`First find the mode — the value that appears most often. Here that is ${u}.`},{visual_data:eqVisual$3([...g,"  ",o,"÷",r,"=",a],[g.length+4]),animation:"find_mean",caption:`${e.join(" + ")} = ${o}, then ${o} ÷ ${r} = ${a}.`,spoken:`Now find the mean. The mean shares the total out equally, so first add every value together: that makes ${numberToWordsLong(o)}. There are ${numberToWordsLong(r)} values, and ${numberToWordsLong(o)}
71 divided by ${numberToWordsLong(r)} is ${d}.`},{visual_data:eqVisual$3([a,m,s],[0,2]),animation:"compare",caption:h,spoken:f}]})}function modeContrastProbe(n){const t=normaliseStem(n.stem),e=extractList(t);if(!e||e.length<3)return null;const s=n.correctAnswer.match(/mode[^\d]*?(\d+(?:\.\d+)?)/i);if(!s)return null;const o=Number(s[1]),r=uniqueMode(e);if(r==null||r!==o)return null;const a=[...e].sort((u,d)=>u-d),i=a.map((u,d)=>u===r?d:-1).filter(u=>u>=0),l=modeSteps(e,r);return buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{answer:r,mode:r},steps:[{visual_data:eqVisual$3(e,[]),animation:"show_data",caption:"The data, as given.",spoken:l.s1},{visual_data:eqVisual$3(a,[]),animation:"sort",caption:l.sortCaption,spoken:l.s2},{visual_data:eqVisual$3(a,i),animation:"find_mode",caption:`${r} appears most — the mode.`,spoken:l.s3}]})}const AVERAGE_CUES=[{name:"mode",label:"the mode",cueRe:/most popular|most common|comes up most|appears most|most often/i,meaning:"the value that appears most often"},{name:"median",label:"the median",cueRe:/\bmiddle\b|typical (?:score|size|value|amount)|halfway (?:value|point)/i,meaning:"the middle value once everything is sorted in order"},{name:"mean",label:"the mean",cueRe:/shared? equally|overall average|fair share|spread evenly/i,meaning:"the total shared out equally between them"}];function whichAverageProbe(n){const t=normaliseStem(n.stem);if(!/which average/i.test(t))return null;const e=(n.options??[]).map(u=>u.trim().toLowerCase()),s=new Set(AVERAGE_CUES.map(u=>u.label));if(e.length!==3||!e.every(u=>s.has(u)))return null;const o=n.correctAnswer.trim().toLowerCase(),r=AVERAGE_CUES.find(u=>u.label===o);if(!r)return null;const a=t.match(r.cueRe);if(!a)return null;const i=a[0],l=AVERAGE_CUES.filter(u=>u!==r).map(u=>u.name);return buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{},steps:[{visual_data:eqVisual$3([`"${i}"`],[]),animation:"show_cue",caption:`The phrase to match: "${i}".`,spoken:"Look closely at what the question is really asking for."},{visual_data:eqVisual$3([`"${i}"`,"=",r.name.toUpperCase()],[]),animation:"match_meaning",caption:`"${capitalised(i)}" means ${r.meaning}.`,spoken:`That phrase means ${r.meaning} — which is exactly what the ${r.name} measures.`},{visual_data:eqVisual$3([r.name.toUpperCase()],[]),animation:"verdict",caption:`Use the ${r.name}, not the ${l.join(" or ")}.`,spoken:`So the manager should use the ${r.name}, not the ${l.join(" or ")}, which measure something different.`}]})}const DS_P5_M02_GENERATORS=[{id:GEN_ID$4,microSkillId:"DS.P5.M02",forms:["symbolic_numeric_entry","symbolic_choice","misconception_contrast_probe"],generate(n){if(n.mathematicalForm==="misconception_contrast_probe")return medianContrastProbe(n)??modeMeanCompareProbe(n)??modeContrastProbe(n)??whichAverageProbe(n);const t=Number(n.correctAnswer);if(!Number.isFinite(t))return null;const e=normaliseStem(n.stem),s=extractList(e);if(!s)return null;const o=/\bmod(?:e|al)\b/i.test(e),r=/\bmedian\b/i.test(e);if(o===r)return null;if(o){const f=uniqueMode(s);if(f==null||f!==t)return null;const g=[...s].sort((y,E)=>y-E),b=g.map((y,E)=>y===f?E:-1).filter(y=>y>=0),x=modeSteps(s,f);return buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{answer:t,mode:f},steps:[{visual_data:eqVisual$3(s,[]),animation:"show_data",caption:"The data, as given.",spoken:x.s1},{visual_data:eqVisual$3(g,[]),animation:"sort",caption:x.sortCaption,spoken:x.s2},{visual_data:eqVisual$3(g,b),animation:"find_mode",caption:`${f} appears most — the mode.`,spoken:x.s3}]})}const a=[...s].sort((f,g)=>f-g),i=a.length,l=a,u=isAscending(s);if(i%2===1){const f=a[(i-1)/2];return f!==t?null:buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{answer:t,median:f},steps:[{visual_data:eqVisual$3(s,[]),animation:"show_data",caption:u?"Already in order — go straight to the middle.":"First, the jumbled data.",spoken:u?"This list is already in order from smallest to largest, so we can go straight to finding the middle.":"The median is the middle value, but we cannot find the middle until the numbers are in order."},{visual_data:eqVisual$3(l,[]),animation:"sort",caption:u?"Smallest to largest already.":"Sort smallest to largest.",spoken:u?"The values already run from smallest to largest, so no sorting is needed.":"So sort them from smallest to largest first — that is the key move."},{visual_data:eqVisual$3(l,[(i-1)/2]),animation:"find_middle",caption:`Middle value: ${f}.`,spoken:`The median is the middle value, so cross off one value from each end, again and again, until one is left in the middle. That is ${numberToWordsLong(f)}, so the median is ${numberToWordsLong(f)}.`}]})}const d=a[i/2-1],m=a[i/2],h=(d+m)/2;return h!==t?null:buildExample({generatorId:GEN_ID$4,question:n,title:n.stem,computed:{answer:t,m1:d,m2:m,median:h,halves:2},steps:[{visual_data:eqVisual$3(s,[]),animation:"show_data",caption:u?"Already in order — go straight to the middle.":"First, the jumbled data.",spoken:u?"This list is already in order from smallest to largest, and with an even set there are two numbers in the middle.":"The median is the middle value, but with an even set there are two numbers in the middle — and first they must be sorted."},{visual_data:eqVisual$3(l,[i/2-1,i/2]),animation:"sort",caption:"Two values sit in the middle.",spoken:`${u?"In order":"Sorted"} from smallest to largest, two values share the middle: ${numberToWordsLong(d)} and ${numberToWordsLong(m)}.`},{visual_data:eqVisual$3(["(",d,"+",m,")","÷",2,"=",h],[8]),animation:"average",caption:`(${d} + ${m}) ÷ 2 = ${h}.`,spoken:`The median is halfway between them, so add them and halve. ${capitalised(numberToWordsLong(d))} add ${numberToWordsLong(m)} is ${numberToWordsLong(d+m)}, and half of that is ${decimalToWords(h)}. So the median is ${decimalToWords(h)}.`}]})}}],GEN_ID$3="DS.P5.M03.conclusions.v1";function eqVisual$2(n,t){return{type:"equation_display",terms:n,...t&&t.length?{highlight:t}:{}}}function asLineGraph(n){if(!n||typeof n!="object")return null;const t=n;if(t.type!=="line_graph")return null;const e=t.x_labels,s=t.values;if(!Array.isArray(e)||!Array.isArray(s))return null;const o=Math.min(e.length,s.length);if(o<2||o>8)return null;const r=s.slice(0,o).map(i=>Number(i));if(r.some(i=>!Number.isInteger(i)||i<0))return null;const a=t.axis_step;return{x_labels:e.slice(0,o).map(String),values:r,axis_step:a===1||a===2||a===5||a===10?a:void 0,axis_max:typeof t.axis_max=="number"?t.axis_max:void 0}}function lgVisual(n,t){return{type:"line_graph",x_labels:n.x_labels,values:n.values,...n.axis_step!==void 0?{axis_step:n.axis_step}:{},...n.axis_max!==void 0?{axis_max:n.axis_max}:{},...t}}function asTable(n){if(!n||typeof n!="object")return null;const t=n;if(t.type!=="two_way_table")return null;const e=t.col_headers,s=t.row_headers,o=t.cells;
71if(!Array.isArray(e)||!Array.isArray(s)||!Array.isArray(o)||s.length!==2||o.length!==2)return null;const r=[];for(const a of o){if(!Array.isArray(a)||a.length!==e.length)return null;const i=a.map(Number);if(i.some(l=>!Number.isInteger(l)||l<0))return null;r.push(i)}return{col_headers:e.map(String),row_headers:s.map(String),cells:r,corner_label:typeof t.corner_label=="string"?t.corner_label:void 0}}function twtVisual(n,t){return{type:"two_way_table",col_headers:n.col_headers,row_headers:n.row_headers,cells:n.cells,...n.corner_label?{corner_label:n.corner_label}:{},...t?{chip_expression:t}:{}}}function asPie(n){if(!n||typeof n!="object")return null;const t=n;if(t.type!=="pie_chart")return null;const e=t.sectors,s=Number(t.total);if(!Array.isArray(e)||e.length<2||e.length>6||!Number.isInteger(s)||s<=0)return null;const o=[];for(const r of e){if(!r||typeof r!="object")return null;const a=String(r.label??"");if(!a)return null;const i=r.fraction,l=r.value;let u=null;if(typeof i=="number"?u=i:i&&typeof i=="object"&&i.den>0?u=i.num/i.den:typeof l=="number"&&(u=l/s),u==null||u<=0||u>1)return null;const d=typeof l=="number"?l:Math.round(u*s);o.push({label:a,frac:u,value:d,raw:r})}return{sectors:o,total:s}}function pieVisual(n,t){return{type:"pie_chart",sectors:n.sectors.map(s=>s.raw),total:n.total,...t!==void 0?{highlight_index:t}:{}}}function claimClause(n){const t=n.match(/\bsays\b(.+)$/i);return(t?t[1]:n).toLowerCase()}const DAY={mon:"monday",tue:"tuesday",wed:"wednesday",thu:"thursday",fri:"friday",sat:"saturday",sun:"sunday"},MONTH={jan:"january",feb:"february",mar:"march",apr:"april",may:"may",jun:"june",jul:"july",aug:"august",sep:"september",oct:"october",nov:"november",dec:"december"};function escapeRegExp$1(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function labelSynonyms(n){const t=n.trim().toLowerCase(),e=new Set([t]);DAY[t]&&e.add(DAY[t]),MONTH[t]&&e.add(MONTH[t]);const s=t.match(/^wk\s*(\d+)$/);s&&(e.add(`week ${s[1]}`),e.add(`week${s[1]}`));const o=t.match(/^m(\d+)$/);o&&(e.add(`month ${o[1]}`),e.add(`month${o[1]}`));const r=t.match(/^q(\d+)$/);return r&&(e.add(`quarter ${r[1]}`),e.add(`quarter${r[1]}`)),[...e]}function findLabelPos(n,t){let e=-1;for(const s of labelSynonyms(t)){const o=new RegExp(`(?<![a-z0-9])${escapeRegExp$1(s)}(?![a-z0-9])`,"i"),r=n.match(o);r&&r.index!=null&&(e===-1||r.index<e)&&(e=r.index)}return e}function matchedLabels(n,t){const e=[];for(let s=0;s<t.length;s++){const o=findLabelPos(n,t[s]);o>=0&&e.push({index:s,pos:o})}return e.sort((s,o)=>s.pos-o.pos),e.map(s=>s.index)}function speakLabel(n){const t=n.trim().toLowerCase(),e=t.match(/^wk\s*(\d+)$/);if(e)return`week ${digitsToWords(e[1])}`;const s=t.match(/^m(\d+)$/);if(s)return`month ${digitsToWords(s[1])}`;const o=t.match(/^q(\d+)$/);if(o)return`quarter ${digitsToWords(o[1])}`;const r=DAY[t]??MONTH[t];return r?capitalised(r):digitsToWords(t)}function speakName(n){const t=n.trim(),e=/\s/.test(t)||/\d/.test(t)||/^[A-Z]{1,2}$/.test(t);return digitsToWords(e?t:t.toLowerCase())}function namePos(n,t){const e=t.trim().toLowerCase(),s=n.indexOf(e);if(s>=0)return s;if(e.length>=4){const o=new RegExp(`(?<![a-z0-9])${escapeRegExp$1(e.slice(0,4))}[a-z]*`,"i"),r=n.match(o);if(r&&r.index!=null)return r.index}return-1}function firstRun(n){const t=normaliseStem(n).match(/\d+(?:\s*(?:,|and)\s*\d+)+/);if(!t)return null;const e=(t[0].match(/\d+/g)??[]).map(Number);return e.length>=3?e:null}function detectRule(n){const t=n.length,e=n.slice(1).map((r,a)=>r-n[a]),s=n[t-1];if(e.every(r=>r===e[0])){const r=e[0],a=s+r;return{next:a,ruleSpoken:`each step goes up by the same amount, ${numberToWordsLong(Math.abs(r))}`,ruleCaption:`Same step every time: ${r>=0?"+":"−"}${Math.abs(r)}.`,finishExpr:`${s} ${r>=0?"+":"−"} ${Math.abs(r)} = ${a}`}}if(n.every(r=>r>0)&&Number.isInteger(n[1]/n[0])&&n.slice(1).every((r,a)=>r===n[a]*(n[1]/n[0]))){const r=n[1]/n[0],a=s*r;return{next:a,ruleSpoken:`each value is ${numberToWordsLong(r)} times the one before`,ruleCaption:`Same ratio every time: × ${r}.`,finishExpr:`${s} × ${r} = ${a}`}}const o=e.slice(1).map((r,a)=>r-e[a]);if(o.length&&o.every(r=>r===o[0])){const r=e[e.length-1]+o[0],a=s+r,i=e.map(l=>`+${l}`).join(", ");
71return{next:a,ruleSpoken:`each step is bigger than the last by the same amount, ${numberToWordsLong(o[0])}`,ruleCaption:`Steps: ${i} → +${r}.`,finishExpr:`${s} + ${r} = ${a}`}}return null}function predictionDemo(n){const t=firstRun(n.stem);if(!t)return null;const e=Number(n.correctAnswer);if(!Number.isInteger(e))return null;const s=detectRule(t);if(!s||s.next!==e)return null;const o=[...t,s.next],r={x_labels:["1st","2nd","3rd","4th","5th","6th","7th","8th"].slice(0,o.length),values:o,axis_max:Math.max(...o)},a={...r,x_labels:r.x_labels.slice(0,t.length),values:t};return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{answer:e,next:s.next},steps:[{visual_data:lgVisual(a,{read_mode:"trend",segment_highlight:[0,1]}),animation:"read",caption:"Look at the step between points.",spoken:"To predict the next value, look at how the pattern changes from one point to the next."},{visual_data:lgVisual(a,{read_mode:"trend",segment_highlight:[t.length-2,t.length-1],show_slope:!0}),animation:"rule",caption:s.ruleCaption,spoken:`Check the whole pattern: ${s.ruleSpoken}.`},{visual_data:lgVisual(r,{read_mode:"trend",projected:!0,proj_label_safe:!0,conclude_label:s.finishExpr}),animation:"project",caption:`${s.finishExpr}.`,spoken:`Carry the same rule on one more step. The next value is ${numberToWordsLong(s.next)}.`}]})}function projectRule(n){const t=n.length;if(t<2)return null;const e=n.slice(1).map((o,r)=>o-n[r]);if(e.every(o=>o===e[0])&&e[0]!==0){const o=e[0];return{project:r=>n[t-1]+o*r,ruleSpoken:`each step changes by the same amount, ${numberToWordsLong(Math.abs(o))}`,stepShort:o>0?`up ${o} each step`:`down ${-o} each step`,stepNums:[Math.abs(o)]}}if(n.every(o=>o>0)&&Number.isInteger(n[1]/n[0])&&n[1]/n[0]>1&&n.slice(1).every((o,r)=>o===n[r]*(n[1]/n[0]))){const o=n[1]/n[0];return{project:r=>n[t-1]*Math.pow(o,r),ruleSpoken:`each value is ${numberToWordsLong(o)} times the one before`,stepShort:`× ${o} each step`,stepNums:[o]}}const s=e.slice(1).map((o,r)=>o-e[r]);if(s.length&&s.every(o=>o===s[0])&&s[0]!==0){const o=s[0],r=e[e.length-1];return{project:a=>{let i=n[t-1],l=r;for(let u=0;u<a;u++)l+=o,i+=l;return i},ruleSpoken:"the step grows by the same amount each time",stepShort:"a growing step",stepNums:[]}}return null}function periodOf(n){const t=n.match(/(\d+)$/);return t?Number(t[1]):null}function extendLabel(n,t){const s=n[n.length-1].match(/^(.*?)(\d+)$/);return s?`${s[1]}${Number(s[2])+t}`:"?"}function extendLg(n,t,e){const s=n.values.slice(),o=n.x_labels.slice();for(let r=1;r<=e;r++)s.push(t.project(r)),o.push(extendLabel(n.x_labels,r));return{x_labels:o,values:s,axis_step:n.axis_step,axis_max:Math.max(...s)}}function valueAtPeriod(n,t,e){const s=n.values.length,o=periodOf(n.x_labels[0]),r=periodOf(n.x_labels[s-1]);if(o==null||r==null)return null;if(e<=r){const a=e-o;return a>=0&&a<s?n.values[a]:null}return t.project(e-r)}function stepComputed(n){return n.stepNums[0]!==void 0?{step:n.stepNums[0]}:{}}function thresholdPredictionDemo(n,t,e){const s=n.correctAnswer.trim().match(/^([A-Za-z]+)\s+(\d+)$/);if(!s)return null;const o=s[1],r=Number(s[2]),i=normaliseStem(n.stem).match(/(?:above|over|more than|greater than|taller than|bigger than|higher than|exceed(?:s|ed)?)\s*([\d,]+)/i);if(!i)return null;const l=Number(i[1].replace(/,/g,""));if(!Number.isInteger(l))return null;const u=t.values.length,d=periodOf(t.x_labels[u-1]);if(d==null||r<=d||t.values[u-1]>l)return null;let m=-1;for(let f=d+1;f<=d+30;f++){const g=e.project(f-d);if(!Number.isInteger(g)||g<0||g>999999)break;if(g>l){m=f;break}}if(m!==r)return null;const h=extendLg(t,e,m-d);return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{threshold:l,atPeriod:m,...stepComputed(e)},steps:[{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[u-2,u-1],show_slope:!0}),animation:"rule",caption:`Find the steady rule: ${e.stepShort}.`,spoken:`First find how the line changes each step: ${e.ruleSpoken}.`},{visual_data:lgVisual(h,{read_mode:"trend",projected:!0,proj_label_safe:!0}),animation:"project",caption:`Keep going until it passes ${l}.`,spoken:`Carry the same rule on, step by step, until the value climbs past ${numberToWordsLong(l)}.`},{visual_data:lgVisual(h,{read_mode:"trend",projected:!0,proj_label_safe:!0,conclude_label:`${o} ${m}`}),animation:"verdict",caption:`First past ${l}: ${o} ${m}.`,spoken:`The first point above ${numberToWordsLong(l)} is ${digitsToWords(`${o.toLowerCase()} ${m}`)}.`}]})}function diffPredictionDemo(n,t,e){const s=normaliseStem(n.stem);if(!/how many more/i.test(s))return null;const o=[...s.matchAll(/\b(?:day|week|month|hour|year|quarter)\s*(\d+)/gi)].map(b=>Number(b[1]));if(o.length<2)return null;const[r,a]=o,i=valueAtPeriod(t,e,r),l=valueAtPeriod(t,e,a);if(i==null||l==null||!Number.isInteger(i)||!Number.isInteger(l))return null;const u=i-l;if(u<=0)return null;let d=1;/in hundreds|hundreds|\(100s\)/i.test(s)?d=100:/in thousands|thousands|\(1000s\)/i.test(s)&&(d=1e3);const m=u*d;if(m!==Number(n.correctAnswer))return null;const h=t.values.length,f=extendLg(t,e,Math.max(0,Math.max(r,a)-(periodOf(t.x_labels[h-1])??0))),g=d===1?"":` Each unit on this graph stands for ${d===100?"one hundred":"one thousand"}, so the real gap is ${numberToWordsLong(m)}.`;return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{answer:m,vHi:i,vLo:l,gap:u,...stepComputed(e)},steps:[{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[h-2,h-1],show_slope:!0}),animation:"rule",caption:`Find the steady rule: ${e.stepShort}.`,spoken:`First find how the line changes each step: ${e.ruleSpoken}.`},{visual_data:lgVisual(f,{read_mode:"trend",projected:!0,proj_label_safe:!0}),animation:"read",caption:`Read both points: ${l} and ${i}.`,spoken:`Carry the rule on and read the two points the question compares: ${numberToWordsLong(l)} and ${numberToWordsLong(i)}.`},{visual_data:lgVisual(f,{read_mode:"trend",projected:!0,proj_label_safe:!0,conclude_label:`${i} − ${l} = ${u}`}),animation:"verdict",caption:`${i} − ${l} = ${u}, so ${m} more.`,spoken:`${capitalised(numberToWordsLong(i))} take away ${numberToWordsLong(l)} is ${numberToWordsLong(u)}.${g}`}]})}function valuePredictionDemo(n,t,e){const s=Number(n.correctAnswer);if(!Number.isInteger(s))return null;const o=t.values.length;let r=-1;for(let l=1;l<=15;l++){const u=e.project(l);
71if(!Number.isInteger(u)||u<0||u>999999)break;if(u===s){r=l;break}}if(r<1)return null;const a=extendLg(t,e,r),i=a.x_labels[a.x_labels.length-1];return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{answer:s,...stepComputed(e)},steps:[{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[o-2,o-1],show_slope:!0}),animation:"read",caption:"Look at the step from point to point.",spoken:"To carry the pattern on, first see how much it changes from one point to the next."},{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[o-2,o-1],show_slope:!0}),animation:"rule",caption:`The rule: ${e.stepShort}.`,spoken:`Check the whole line: ${e.ruleSpoken}.`},{visual_data:lgVisual(a,{read_mode:"trend",projected:!0,proj_label_safe:!0,conclude_label:`${i}: ${s}`}),animation:"project",caption:`Carry it on to ${i}: ${s}.`,spoken:`Keep the same rule going to the point we want. The value there is ${numberToWordsLong(s)}.`}]})}function visualPredictionDemo(n){const t=asLineGraph(n.visualData);if(!t)return null;const e=projectRule(t.values);return e?thresholdPredictionDemo(n,t,e)??diffPredictionDemo(n,t,e)??valuePredictionDemo(n,t,e):null}function quantityNumbers(n){return(normaliseStem(n).replace(/\bq\s*\d+/gi," ").replace(/\b(?:week|day|round|match|game|quarter|month|innings|point|stage)s?\s*\d+/gi," ").match(/\d+/g)??[]).map(Number)}function stemNumericClaim(n){const t=normaliseStem(n.stem),e=isAffirmative(n.correctAnswer),s=quantityNumbers(t),o=t.match(/(?:rise of|rose by|went up by|up by)\s*(\d+)/i);if(o&&s.length===3){const h=Number(o[1]),f=s.indexOf(h);if(f<0)return null;const g=s.filter((k,T)=>T!==f);if(g.length!==2)return null;const b=Math.min(g[0],g[1]),x=Math.max(g[0],g[1]),y=x-b,E=y===h;return E!==e?null:verdictDemo(n,{a:g[0],b:g[1],lo:b,hi:x,calc:[x,"−",b,"=",y],calcCaption:`${x} − ${b} = ${y}.`,calcSpoken:`To test the claim, work out the real rise. ${capitalised(numberToWordsLong(x))} take away ${numberToWordsLong(b)} is ${numberToWordsLong(y)}.`,verdictCaption:E?`A rise of ${y} — the claim holds.`:`The rise is ${y}, not as claimed.`,verdictSpoken:E?`The real rise is ${numberToWordsLong(y)}. That matches the claim, so it is correct.`:`The real rise is ${numberToWordsLong(y)}, not ${numberToWordsLong(h)}. That does not match the claim, so it is not correct.`,extra:{actual:y,claimed:h}})}if(s.length!==2)return null;const r=Math.min(s[0],s[1]),a=Math.max(s[0],s[1]);let i=null,l=!1;if(/more than doubl/i.test(t)?(i=2,l=!0):/doubl|twice/i.test(t)?i=2:/tripl|three times/i.test(t)?i=3:/half|halv/i.test(t)&&(i=2),i==null)return null;const u=r*i,d=/half|halv/i.test(t),m=l?a>u:a===u;return m!==e?null:verdictDemo(n,{a:s[0],b:s[1],lo:r,hi:a,calc:[r,"×",i,"=",u],calcCaption:`${i} × ${r} = ${u}.`,calcSpoken:`${d?"Halving means the bigger value is two times the smaller one, so we multiply the smaller value by two.":`To test the claim, multiply the smaller value by ${numberToWordsLong(i)}.`} ${capitalised(numberToWordsLong(i))} times ${numberToWordsLong(r)} is ${numberToWordsLong(u)}.`,verdictCaption:l?m?`${a} is more than ${u} — true.`:`${a} is not more than ${u}.`:m?`${a} is exactly ${u} — true.`:`${a} is not ${u}.`,verdictSpoken:l?m?`${capitalised(numberToWordsLong(a))} is more than ${numberToWordsLong(u)}, so it more than doubled. The claim is correct.`:`${capitalised(numberToWordsLong(a))} is not more than ${numberToWordsLong(u)}, so it did not more than double. The claim is not correct.`:m?`The bigger value is ${numberToWordsLong(a)}, exactly ${numberToWordsLong(i)} times ${numberToWordsLong(r)}, so the claim is correct.`:`The bigger value is ${numberToWordsLong(a)}, not ${numberToWordsLong(u)}, so the claim is not correct.`,extra:{k:i,scaled:u}})}function verdictDemo(n,t){return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{answer:t.hi,a:t.a,b:t.b,lo:t.lo,hi:t.hi,...t.extra},steps:[{visual_data:eqVisual$2([t.a,"→",t.b],[]),animation:"read_values",caption:`The two values: ${t.a} and ${t.b}.`,spoken:`First read the two values the claim compares: ${numberToWordsLong(t.a)} and ${numberToWordsLong(t.b)}.`},{visual_data:eqVisual$2(t.calc,[0,1,2,3,4]),animation:"compute",caption:t.calcCaption,spoken:t.calcSpoken},{visual_data:eqVisual$2(t.calc,[4]),animation:"verdict",caption:t.verdictCaption,spoken:t.verdictSpoken}]})}function lineGraphClaim(n,t){const e=claimClause(normaliseStem(n.stem)),s=isAffirmative(n.correctAnswer),o=t.values,r=o.length,a=o.slice(1).map((b,x)=>b-o[x]),i=(b,x,y,E,k,T,A,N,S,M)=>buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:M,steps:[{visual_data:lgVisual(t,{read_mode:"trend",...b}),animation:"read_first",caption:x,spoken:y},{visual_data:lgVisual(t,{read_mode:"trend",...E}),animation:"check",caption:k,spoken:T},{visual_data:lgVisual(t,{read_mode:"trend",...A}),animation:"verdict",caption:N,spoken:S}]});
71if(/(?:rose|rise|risen|increased|grew|went up|climbed) by the same|by the same (?:amount|rise|step)|same (?:amount|rise) (?:each|every)/.test(e)&&!/biggest/.test(e)){if(a.length<2)return null;const b=a.every(k=>k===a[0]);if(b!==s)return null;const x=b?a.length-1:a.findIndex(k=>k!==a[0]),y=a[0],E=a[x];return y<=0||E<=0?null:i({segment_highlight:[0,1],show_slope:!0},`${t.x_labels[0]} to ${t.x_labels[1]}: up ${y}.`,`Measure each rise in turn. From ${speakLabel(t.x_labels[0])} to ${speakLabel(t.x_labels[1])} it goes up by ${numberToWordsLong(y)}.`,{segment_highlight:[x,x+1],show_slope:!0},`${t.x_labels[x]} to ${t.x_labels[x+1]}: up ${E}.`,`Now, from ${speakLabel(t.x_labels[x])} to ${speakLabel(t.x_labels[x+1])} it goes up by ${numberToWordsLong(E)}.`,{trace_line:!0,conclude_label:b?`Same rise: ${y}`:`${y} vs ${E}`},b?`Up ${y} each step — the claim holds.`:`Up ${y} then up ${E} — not equal.`,b?"Every rise is the same, so the claim is correct.":"The rises are not the same size, so the claim is not correct.",{rise0:y,riseK:E}
71)}const l=/(?:rose|rises|rise|increased|climbed|beat|higher|went up|got bigger)/.test(e)&&/(?:every|each|only ever|one before)/.test(e)&&!/fell|fall|drop|decreas|lower/.test(e),u=/(?:falling|fell every|dropped every|only ever decreased|decreasing|are falling|lower than the one before)/.test(e),d=/stayed the same|same all|stay the same every/.test(e);if(l||u||d){const b=N=>l?N>0:u?N<0:N===0,x=a.findIndex(N=>!b(N)),y=x===-1;if(y!==s)return null;const E=l?"climbs":u?"falls":"stays level",k=l?"rose every time":u?"fell every time":"stayed the same";if(y)return i({segment_highlight:[0,1],show_slope:!0},`First step: the line ${E}.`,`To test the claim, read the line one step at a time. From ${speakLabel(t.x_labels[0])} to ${speakLabel(t.x_labels[1])} the line ${E}.`,{segment_highlight:[1,r-1],show_slope:!0},"Check every step the same way.",`Now check every remaining step — each one ${E} too. Not a single step breaks the pattern.`,{trace_line:!0,conclude_label:`It ${k}`},"Every step agrees — the claim is right.",`Trace the whole line: it ${k}, so the claim is correct.`,{v0:o[0],vLast:o[r-1]});const T=x,A=l?a[T]<0?"falls":"stays level":u?a[T]>0?"climbs":"stays level":a[T]>0?"climbs":"falls";return i({trace_line:!0},"Read the line step by step.","To test the claim, follow the line one step at a time and watch which way each step goes.",{segment_highlight:[T,T+1],show_slope:!0},`${t.x_labels[T]} to ${t.x_labels[T+1]}: it ${A}.`,`Look here: from ${speakLabel(t.x_labels[T])} to ${speakLabel(t.x_labels[T+1])}, the value goes from ${numberToWordsLong(o[T])} to ${numberToWordsLong(o[T+1])} — the line ${A}.`,{segment_highlight:[T,T+1],conclude_label:`${o[T]} → ${o[T+1]}`},"One step breaks it — the claim is wrong.",`Because at least one step ${A} instead, the claim is not correct.`,{vBad:o[T],vNext:o[T+1]})}if(/doubl/.test(e)){const b=matchedLabels(e,t.x_labels);if(b.length!==2)return null;const[x,y]=b[0]<b[1]?[b[0],b[1]]:[b[1],b[0]],E=o[x],k=o[y];if(E<=0)return null;const T=E*2,A=k===T;return A!==s?null:buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{vA:E,vB:k,doubled:T},steps:[{visual_data:lgVisual(t,{read_index:x}),animation:"read_first",caption:`${t.x_labels[x]}: read ${E}.`,spoken:`Read the two values the claim compares. First ${speakLabel(t.x_labels[x])}: it is ${numberToWordsLong(E)}.`},{visual_data:lgVisual(t,{read_index:y,carry_indices:[x]}),animation:"read_second",caption:`${t.x_labels[y]}: read ${k}.`,spoken:`Now ${speakLabel(t.x_labels[y])}: it is ${numberToWordsLong(k)}.`},{visual_data:lgVisual(t,{read_index:y,carry_indices:[x],conclude_label:`Double ${E} = ${T}`}),animation:"verdict",caption:A?`${k} is exactly double ${E} — right.`:`Double ${E} is ${T}, not ${k} — wrong.`,spoken:A?`Double ${numberToWordsLong(E)} is ${numberToWordsLong(T)} — exactly what we read. The claim is correct.`:`Double ${numberToWordsLong(E)} is ${numberToWordsLong(T)}, but the graph shows ${numberToWordsLong(k)}. So the claim is not correct.`}]})}if(/biggest (?:jump|rise|fall|drop)|(?:rose|fell) the most/.test(e)){const b=/fall|drop|fell/.test(e),x=matchedLabels(e,t.x_labels);if(x.length!==2||x[1]!==x[0]+1)return null;const y=x[0],E=D=>b?o[D]-o[D+1]:o[D+1]-o[D];let k=-1/0,T=-1,A=!1;for(let D=0;D<r-1;D++){const U=E(D);U>k?(k=U,T=D,A=!1):U===k&&(A=!0)}if(T<0||A)return null;const N=T===y;if(N!==s)return null;let S=-1;if(!N)S=T;else{let D=-1/0;for(let U=0;U<r-1;U++)U!==y&&E(U)>D&&(D=E(U),S=U)}if(S<0)return null;const M=b?"fall":"jump",L=Math.abs(o[y+1]-o[y]),P=Math.abs(o[S+1]-o[S]),F=N?y:S;return i({segment_highlight:[y,y+1],show_slope:!0},`The claimed ${M}: ${L}.`,`First measure the ${M} the claim names, from ${speakLabel(t.x_labels[y])} to ${speakLabel(t.x_labels[y+1])}: it changes by ${numberToWordsLong(L)}.`,{segment_highlight:[S,S+1],show_slope:!0},`Compare: ${t.x_labels[S]} to ${t.x_labels[S+1]} is ${P}.`,`Now compare it with the other steps. From ${speakLabel(t.x_labels[S])} to ${speakLabel(t.x_labels[S+1])} the change is ${numberToWordsLong(P)}.`,{segment_highlight:[F,F+1],conclude_label:`Biggest: ${t.x_labels[F]}
71 → ${t.x_labels[F+1]}`},N?`${L} beats every other ${M} — right.`:`${P} beats ${L} — the claim is wrong.`,N?`A ${M} of ${numberToWordsLong(L)} is bigger than every other step, so the claim is correct.`:`A ${M} of ${numberToWordsLong(P)} beats ${numberToWordsLong(L)}, so the biggest ${M} is somewhere else — the claim is not correct.`,{claimedSize:L,rivalSize:P})}if(/bigger than/.test(e)&&/rise|rose|jump|grew/.test(e)){const b=matchedLabels(e,t.x_labels);if(b.length!==4)return null;const[x,y,E,k]=b;if(y<=x||k<=E)return null;const T=o[y]-o[x],A=o[k]-o[E],N=T>A;return N!==s?null:i({segment_highlight:[x,y],show_slope:!0},`First rise: ${Math.abs(T)}.`,`Measure the first rise, from ${speakLabel(t.x_labels[x])} to ${speakLabel(t.x_labels[y])}: it is ${numberToWordsLong(Math.abs(T))}.`,{segment_highlight:[E,k],show_slope:!0},`Second rise: ${Math.abs(A)}.`,`Now the second rise, from ${speakLabel(t.x_labels[E])} to ${speakLabel(t.x_labels[k])}: it is ${numberToWordsLong(Math.abs(A))}.`,{trace_line:!0,conclude_label:`${Math.abs(T)} vs ${Math.abs(A)}`},N?`${Math.abs(T)} beats ${Math.abs(A)} — the claim is right.`:`${Math.abs(T)} does not beat ${Math.abs(A)} — wrong.`,N?`A rise of ${numberToWordsLong(Math.abs(T))} beats a rise of ${numberToWordsLong(Math.abs(A))}, so the claim is correct.`:`A rise of ${numberToWordsLong(Math.abs(T))} does not beat ${numberToWordsLong(Math.abs(A))}, so the claim is not correct.`,{d1:Math.abs(T),d2:Math.abs(A)})}if(/faster in the later|grew faster|faster towards the end/.test(e)){const b=a.every((T,A)=>A===0||T>=a[A-1]),x=a.every((T,A)=>A===0||T<=a[A-1]);let y;if(b&&a[a.length-1]>a[0])y=!0;else if(x&&a[a.length-1]<a[0])y=!1;else return null;if(y!==s)return null;const E=Math.abs(a[0]),k=Math.abs(a[a.length-1]);return i({segment_highlight:[0,1],show_slope:!0},`Early step: ${E}.`,`Compare how big the steps are. The first step, from ${speakLabel(t.x_labels[0])} to ${speakLabel(t.x_labels[1])}, rises by ${numberToWordsLong(E)}.`,{segment_highlight:[r-2,r-1],show_slope:!0},`Late step: ${k}.`,`The last step, from ${speakLabel(t.x_labels[r-2])} to ${speakLabel(t.x_labels[r-1])}, rises by ${numberToWordsLong(k)}.`,{trace_line:!0,conclude_label:`${E} → ${k}`},y?"Later steps are bigger — right.":"Later steps are smaller — wrong.",y?"The steps get bigger towards the end, so it grew faster later — the claim is correct.":"The steps get smaller towards the end, so it grew faster at the start — the claim is not correct.",{early:E,late:k})}const m=n.correctAnswer.trim().toLowerCase(),h=m.match(/(?:dropped|fell|dipped)\s+in\s+(.+?),?\s*then\s+(?:rose|climbed|increased|recovered|went up)/i),f=m.match(/(.+?)\s+was the (?:warmest|hottest|highest|tallest|biggest|largest)\S*,?\s*then it (?:cooled|fell|dropped|decreased|shrank|went down)/i);if(h||f){const b=!!h,x=(b?h[1]:f[1]).trim();let y=-1;for(let E=0;E<t.x_labels.length;E++)if(labelSynonyms(t.x_labels[E]).some(k=>x.includes(k))){y=E;break}if(y>=1&&y<=r-2){const E=o[y-1],k=o[y],T=o.slice(y);if(b?k<E&&T.every((N,S)=>S===0||N>T[S-1]):k>E&&T.every((N,S)=>S===0||N<T[S-1])&&o.every(N=>N<=k)){const N=n.correctAnswer.trim(),S=b?"drops":"climbs",M=b?"climbs":"falls";return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{before:E,at:k,after:T[T.length-1]},steps:[{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[y-1,y],show_slope:!0}),animation:"read_first",caption:`${t.x_labels[y-1]} to ${t.x_labels[y]}: it ${S}.`,spoken:`First look at the step into ${speakLabel(t.x_labels[y])}: it ${S}.`},{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[y,r-1],show_slope:!0}),animation:"check",caption:`After ${t.x_labels[y]}: every step ${M}.`,spoken:`Now check every step after ${speakLabel(t.x_labels[y])} — each one ${M} too.`},{visual_data:lgVisual(t,{read_mode:"trend",segment_highlight:[y-1,y],conclude_label:N}),animation:"verdict",caption:N,spoken:`The data backs this up exactly: it ${S} into ${speakLabel(t.x_labels[y])}, then ${M} the rest of the way.`}]})}}}const g=n.correctAnswer.trim().toLowerCase();if(/\bmost\b/.test(g)&&/\b(?:fewest|least)\b/.test(g)){let b=0,x=0;for(let N=1;N<r;N++)o[N]>o[b]&&(b=N),o[N]<o[x]&&(x=N);const y=o.filter(N=>N===o[b]).length===1,E=o.filter(N=>
71N===o[x]).length===1,k=findLabelPos(g,t.x_labels[b]),T=findLabelPos(g,t.x_labels[x]);if(!y||!E||b===x||k<0||T<0)return null;const A=n.correctAnswer.trim();return buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{vMax:o[b],vMin:o[x]},steps:[{visual_data:lgVisual(t,{read_mode:"trend",read_index:b}),animation:"read_max",caption:`${t.x_labels[b]}: the highest point, ${o[b]}.`,spoken:`Scan every point for the highest value. ${capitalised(speakLabel(t.x_labels[b]))} is the tallest, at ${numberToWordsLong(o[b])}.`},{visual_data:lgVisual(t,{read_mode:"trend",read_index:x,carry_indices:[b]}),animation:"read_min",caption:`${t.x_labels[x]}: the lowest point, ${o[x]}.`,spoken:`Now scan for the lowest value. ${capitalised(speakLabel(t.x_labels[x]))} is the smallest, at ${numberToWordsLong(o[x])}.`},{visual_data:lgVisual(t,{read_mode:"trend",read_index:x,carry_indices:[b],conclude_label:A}),animation:"verdict",caption:A,spoken:`${capitalised(speakLabel(t.x_labels[b]))} is the highest at ${numberToWordsLong(o[b])}, and ${speakLabel(t.x_labels[x])} is the lowest at ${numberToWordsLong(o[x])} — both fully backed by the data.`}]})}return null}function tableClaim(n,t){const e=normaliseStem(n.stem),s=claimClause(e),o=t.cells[0].reduce((h,f)=>h+f,0),r=t.cells[1].reduce((h,f)=>h+f,0),a=h=>`${t.cells[h].join(" + ")} = ${h===0?o:r}`,i=h=>`${speakName(t.row_headers[h])}: ${t.cells[h].map(f=>numberToWordsLong(f)).join(" plus ")} makes ${numberToWordsLong(h===0?o:r)}`,l=(h,f,g)=>buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{tot0:o,tot1:r,...g},steps:[{visual_data:twtVisual(t),animation:"highlight_missing",caption:"Total each row to compare.",spoken:`To compare, we need each row's total. Add along the ${speakName(t.row_headers[0])} row, then along the ${speakName(t.row_headers[1])} row.`},{visual_data:twtVisual(t,a(0)),animation:"sum_known",caption:`${t.row_headers[0]}: ${a(0)}.`,spoken:`First row — ${i(0)}.`},{visual_data:twtVisual(t,a(1)),animation:"reveal_answer",caption:h,spoken:`Second row — ${i(1)}. ${f}`}]});if(/says/i.test(e)){const h=isAffirmative(n.correctAnswer),f=namePos(s,t.row_headers[0]),g=namePos(s,t.row_headers[1]);if(f<0||g<0)return null;const b=f<g?0:1,x=1-b,y=b===0?o:r,E=x===0?o:r,k=y>E;return k!==h?null:l(k?`${t.row_headers[b]} ${y} beats ${E} — right.`:`${y} does not beat ${E} — wrong.`,k?`${capitalised(numberToWordsLong(y))} is more than ${numberToWordsLong(E)}, so the claim is correct.`:`${capitalised(numberToWordsLong(y))} is not more than ${numberToWordsLong(E)}, so the claim is not correct.`,{totClaim:y,totOther:E})}const d=n.correctAnswer.trim().match(/^(.+?)\s+scored more in total,\s*but\s+(.+?)'s best round beats?\s+(.+?)'s best$/i);if(d){const[,h,f,g]=d,b=L=>t.row_headers.findIndex(P=>P.trim().toLowerCase()===L.trim().toLowerCase()),x=b(h),y=b(f),E=b(g);if(x<0||y<0||E<0||y===E)return null;const k=1-x,T=x===0?o:r,A=k===0?o:r,N=L=>Math.max(...t.cells[L]),S=N(y),M=N(E);return!(T>A)||!(S>M)?null:buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{totMore:T,totOther:A,bMax:S,oMax:M},steps:[{visual_data:twtVisual(t,a(x)),animation:"sum_known",caption:`${t.row_headers[x]} total: ${a(x)}.`,spoken:`First total each row. ${capitalised(i(x))}.`},{visual_data:twtVisual(t,a(k)),animation:"sum_known",caption:`${t.row_headers[k]} total: ${a(k)} — ${t.row_headers[x]} is ahead.`,spoken:`Now the other row. ${capitalised(i(k))}. ${capitalised(numberToWordsLong(T))} is more than ${numberToWordsLong(A)}, so ${speakName(t.row_headers[x])} scored more in total.`},{visual_data:twtVisual(t,`Best: ${t.row_headers[y]} ${S} vs ${t.row_headers[E]} ${M}
71`),animation:"reveal_answer",caption:`But ${t.row_headers[y]}'s best round (${S}) beats ${t.row_headers[E]}'s best (${M}).`,spoken:`Now check the best single round for each team. ${capitalised(speakName(t.row_headers[y]))}'s best round is ${numberToWordsLong(S)}, which beats ${speakName(t.row_headers[E])}'s best of ${numberToWordsLong(M)}. So ${speakName(t.row_headers[x])} scored more overall, but ${speakName(t.row_headers[y])} had the better single round.`}]})}const m=n.correctAnswer.trim();if(/equal/i.test(m))return o!==r?null:l(`Both rows total ${o} — equal.`,"Both rows make exactly the same total, so the totals are equal.",{});if(/more|which/i.test(e)){if(o===r)return null;const h=o>r?0:1;return t.row_headers[h].toLowerCase()!==m.toLowerCase()?null:l(`${t.row_headers[h]}: ${Math.max(o,r)} beats ${Math.min(o,r)}.`,`${capitalised(numberToWordsLong(Math.max(o,r)))} is more than ${numberToWordsLong(Math.min(o,r))}, so ${speakName(t.row_headers[h])} scored more in total.`,{})}return null}function pieClaim(n,t){const e=normaliseStem(n.stem),s=claimClause(e),o=isAffirmative(n.correctAnswer),r=t.sectors.map((m,h)=>({i:h,pos:namePos(s,m.label)})).filter(m=>m.pos>=0).sort((m,h)=>m.pos-h.pos).map(m=>m.i);if(r.length===0)return null;const a=r[0],i=t.sectors[a],l={visual_data:pieVisual(t),animation:"whole",caption:`The whole chart = ${t.total}.`,spoken:`The whole circle stands for everyone — ${numberToWordsLong(t.total)} in total. Each slice's size is its share of that whole.`},u=(m,h,f,g)=>buildExample({generatorId:GEN_ID$3,question:n,title:n.stem,computed:{total:t.total,vFirst:i.value,...g},steps:[l,{visual_data:pieVisual(t,a),animation:"read_slice",caption:`${i.label}: ${i.value} of the ${t.total}.`,spoken:`Look at the ${speakName(i.label)} slice. Its share of the circle is worth ${numberToWordsLong(i.value)} of the ${numberToWordsLong(t.total)}.`},{visual_data:pieVisual(t,f),animation:"verdict",caption:m,spoken:h}]}),d=s.match(/(\d+)\s+more\b/);if(d&&r.length>=2){const m=Number(d[1]),h=r[1],f=t.sectors[h],g=i.value-f.value;if(g<=0)return null;const b=g===m;return b!==o?null:u(b?`${i.value} − ${f.value} = ${g} more — right.`:`${i.value} − ${f.value} = ${g}, not ${m} — wrong.`,b?`To find how many more, take the ${speakName(f.label)} slice away from the ${speakName(i.label)} slice. ${capitalised(numberToWordsLong(i.value))} take away ${numberToWordsLong(f.value)} is ${numberToWordsLong(g)}, which matches the claim. It is correct.`:`To find how many more, take the ${speakName(f.label)} slice away from the ${speakName(i.label)} slice. ${capitalised(numberToWordsLong(i.value))} take away ${numberToWordsLong(f.value)} is ${numberToWordsLong(g)}, not ${numberToWordsLong(m)}. So the claim is not correct.`,h,{vSecond:f.value,actual:g,claimed:m})}if(/twice as many|two times as many/.test(s)&&r.length>=2){const m=r[1],h=t.sectors[m];if(h.value<=0)return null;const f=i.value===2*h.value;return f!==o?null:u(f?`${i.value} is twice ${h.value} — right.`:`${i.value} is not twice ${h.value} — wrong.`,f?`${capitalised(numberToWordsLong(i.value))} is exactly two times ${numberToWordsLong(h.value)}, so the claim is correct.`:`${capitalised(numberToWordsLong(i.value))} is not two times ${numberToWordsLong(h.value)}, so the claim is not correct.`,m,{vSecond:h.value})}if(/smallest/.test(s)){const h=t.sectors.every((b,x)=>x===a||i.frac<b.frac);if(h!==o)return null;let f=-1;if(!h)f=t.sectors.findIndex((b,x)=>x!==a&&b.frac<=i.frac);else{let b=1/0;t.sectors.forEach((x,y)=>{y!==a&&x.frac<b&&(b=x.frac,f=y)})}if(f<0)return null;const g=t.sectors[f];return u(h?`${i.label} ${i.value} is below every other — right.`:`${i.label} ${i.value} and ${g.label} ${g.value} match — wrong.`,h?`Every other slice is bigger, so ${speakName(i.label)} really is the smallest. The claim is correct.`:`The ${speakName(g.label)} slice is just as small — they are equal, so neither is THE smallest. The claim is not correct.`,f,{vRival:g.value})}if(/half/.test(s)){const m=/more than half|over half/.test(s),h=Math.abs(i.frac-.5)<1e-9,f=m?i.frac>.5:h;if(f!==o)return null;const g=t.total/2,b=Number.isInteger(g)?{halfVal:g}:{};return u(m?f?"Over half — the claim is right.":h?`Exactly half of ${t.total}, not more — wrong.`:"Less than half — wrong.":f?`${i.value} is exactly half of ${t.total} — right.`:`${i.value} is not half of ${t.total} — wrong.`,m?f?"The slice takes more than half the circle, so the claim is correct.":h?"The slice is exactly half of the circle, not more than half — so the claim is not correct.":"The slice is less than half of the circle, so the claim is not correct.":f?`The slice is exactly half the circle — half of ${numberToWordsLong(t.total)} is ${numberToWordsLong(Math.round(g))}. The claim is correct.`:"The slice is not half the circle, so the claim is not correct.",a,b)}return null}const DS_P5_M03_GENERATORS=[{id:GEN_ID$3,microSkillId:"DS.P5.M03",forms:["trend_prediction","trend_statement_evaluation"],generate(n){if(n.mathematicalForm==="trend_prediction")return predictionDemo(n)??visualPredictionDemo(n);if(n.mathematicalForm!=="trend_statement_evaluation")return null;const t=stemNumericClaim(n);if(t)return t;const e=asLineGraph(n.visualData);if(e)return lineGraphClaim(n,e);const s=asTable(n.visualData);if(s)return tableClaim(n,s);const o=asPie(n.visualData);return o?pieClaim(n,o):null}}],w$3=numberToWordsLong,wordsAny=n=>Number.isInteger(n)?w$3(n):decimalToWords(String(n)),FORMS$4=["symbolic_numeric_entry","context_calculation","misconception_contrast_probe"];function amountCandidates(n){const t=[],e=/(\d+)\s*(%?)/g;let s;for(;(s=e.exec(n))!==null;)s[2]!=="%"&&t.push(Number(s[1]));return t}function unitAfterAmount(n,t){const e=new RegExp(`${t}\\s*(mAh|kg|g|ml|l|km|cm|mm|kb|mb|gb|min|hrs?)\\b`),s=n.match(e);return s?s[1]:null}function parse(n){const t=normaliseStem(n.stem),e=t.match(/(\d+)\s*%/);if(!e)return null;const s=Number(e[1]);if(s<=0||s>=100)return null;const o=Number(n.correctAnswer);if(!Number.isInteger(o))return null;
71for(const r of amountCandidates(t)){if(r<5||r%5!==0)continue;const a=s*r/100;if(!Number.isInteger(a))continue;let i=null;if(o===a?i="part":o===r-a?i="less":o===r+a&&(i="more"),i)return{pct:s,amount:r,part:a,answer:o,mode:i}}return null}function breakdown(n,t){if(t%5!==0)return null;const e=t/10,s=Math.floor(n/10),o=n-s*10,r=n*t/100;if(!Number.isInteger(r))return null;if(o===5){const a=e/2;return Number.isInteger(a)?{ten:e,tens:s,rem:o,half:a,part:r}:null}return o!==0?{ten:e,tens:s,rem:o,onePercent:t/100,part:r}:{ten:e,tens:s,rem:o,part:r}}function scaleStep(n,t,e,s){if(n.tens===0&&n.rem===5&&n.half!==void 0)return{deriveCaption:"",terms:"half of 10%",words:`Five per cent is half of ten per cent: half of ${wordsAny(n.ten)} is ${wordsAny(n.part)}.`};const o=[];let r="",a=n.tens>0||n.rem===5&&n.half!==void 0?`Ten per cent of ${wordsAny(e)} is ${wordsAny(n.ten)}. `:"";n.tens>0&&o.push(n.tens===1?s(n.ten):`${n.tens} × ${s(n.ten)}`),n.rem===5&&n.half!==void 0?(o.push(s(n.half)),r+=`Half of ${s(n.ten)} is ${s(n.half)}. `,a+=`Five per cent is half of ten per cent: half of ${wordsAny(n.ten)} is ${wordsAny(n.half)}. `):n.rem!==0&&n.onePercent!==void 0&&(o.push(n.rem===1?s(n.onePercent):`${n.rem} × ${s(n.onePercent)}`),r+=`1% of ${s(e)} = ${s(e)} ÷ 100 = ${s(n.onePercent)}. `,a+=`One per cent is ${wordsAny(e)} divided by one hundred, which is ${wordsAny(n.onePercent)}. `);const i=o.join(" + "),l=`${a}${cap$7(wordsAny(t))} per cent of ${wordsAny(e)} is ${wordsAny(n.part)}.`;return{deriveCaption:r,terms:i,words:l}}const eq$3=n=>({type:"equation_display",terms:n}),DIVIDE_BY_PCT_RE=/^(\w+)\s+works out\s+\d+%\s+of\s+\d+\s+by calculating\s+\d+\s+divided by\s+(\d+),\s*giving\s+(\d+)/i,STOPPED_AT_TEN_RE=/^(\w+)\s+works out\s+\d+%\s+of\s+\d+\.\s*(?:She|He|They)\s+finds\s+10%\s+is\s+(\d+),\s*then writes\s+\d+%\s+is\s+\d+/i;function applyMisconceptionContrast(n,t,e,s,o){if(n.mathematicalForm!=="misconception_contrast_probe")return;const r=normaliseStem(n.stem),a=r.match(DIVIDE_BY_PCT_RE);if(a){const l=cap$7(a[1]),u=Number(a[2]);o[0].caption=`${l} ÷ ${u} is wrong — find 10% first. ${o[0].caption}`,o[0].spoken=`${l} divided by ${w$3(u)}, but that is not right — ${w$3(t)} per cent means ${w$3(t)} per hundred. Find ten per cent first. ${o[0].spoken}`;return}const i=r.match(STOPPED_AT_TEN_RE);if(i){const l=cap$7(i[1]),u=s===2?"double":`× ${s}`,d=s===2?"double it":`scale it up ${w$3(s)} times`;o[1].caption=`${l} stopped at 10%, forgot to ${u}. ${o[1].caption}`,o[1].spoken=`${l} found ten per cent but forgot to ${d}: ${w$3(t)} per cent is ${w$3(s)} lots of ${w$3(e)}. ${o[1].spoken}`}}function cap$7(n){return n.charAt(0).toUpperCase()+n.slice(1)}const fdpP6M04={id:"FDP.P6.M04.percent-of.v1",microSkillId:"FDP.P6.M04",forms:FORMS$4,generate(n){const t=parse(n);if(!t)return null;const{pct:e,amount:s,part:o,answer:r,mode:a}=t,i=breakdown(e,s);if(!i)return null;const{ten:l,tens:u}=i,d=/£/.test(n.stem),m=unitAfterAmount(n.stem,s),h=M=>d?`£${M}`:m?`${M} ${m}`:`${M}`,f=scaleStep(i,e,s,h),g=e/10,b=M=>({type:"percent_bar",parts:10,whole_label:h(s),part_value:m?`${l}`:h(l),...a==="more"?{reserve:g}:{},...M}),x=`${e}% = ${h(o)}`,y=i.rem===0?`${w$3(u)} ${u===1?"box":"boxes"}`:i.rem===5&&u>0?`${w$3(u)} and a half boxes`:null,E=`${cap$7(wordsAny(e))} per cent of ${wordsAny(s)} is ${wordsAny(o)}.`;let k=`${f.deriveCaption}${e}% of ${h(s)} = ${f.terms} = ${h(o)}.`,T=f.words;e===10?(k=`10% is 1 of the 10 boxes: ${h(o)}.`,T=`Ten per cent is one of the ten equal boxes, so it is ${wordsAny(o)}.`):y&&T.endsWith(E)&&(T=`${T.slice(0,-E.length)}${cap$7(wordsAny(e))} per cent is ${y}, so it is ${wordsAny(o)}.`);const A=a==="part"?{visual_data:b({lit:g,lit_label:`${e}% of ${h(s)} = ${h(o)}`}),animation:"pb_conclude",caption:`${e}% of ${h(s)} = ${h(o)}.`,spoken:`So ${w$3(e)} per cent of ${wordsAny(s)} is ${wordsAny(o)}.`}:a==="less"?{visual_data:b({lit:g,lit_label:`− ${h(o)}`,taken:!0,rest_label:`${h(r)} left`}),animation:"pb_take",caption:`${h(s)} − ${h(o)} = ${h(r)}.`,spoken:`Take the ${w$3(e)} per cent away: ${wordsAny(s)} minus ${wordsAny(o)} is ${wordsAny(r)}.`}:{visual_data:b({lit:g,lit_label:`+ ${h(o)}`,extend_label:`${h(s)} + ${h(o)} = ${h(r)}`}),animation:"pb_add",caption:`${h(s)} + ${h(o)} = ${h(r)}.`,spoken:`Add the ${w$3(e)} per cent on: ${wordsAny(s)} plus ${wordsAny(o)} is ${wordsAny(r)}.`},N=[{visual_data:b({first_label:`10% = ${h(l)}`}),animation:"pb_split",caption:`10% of ${h(s)} = ${h(s)} ÷ 10 = ${h(l)}.`,spoken:`Start with ten per cent. Ten per cent of ${wordsAny(s)} is ${wordsAny(s)} divided by ten, which is ${wordsAny(l)}.`},{visual_data:b({lit:g,lit_label:x}),animation:"pb_shade",caption:k,spoken:T},A];applyMisconceptionContrast(n,e,l,u,N);const S={pct:e,amount:s,ten:l,tens:u,rem:i.rem,part:o,answer:r};return i.half!==void 0&&(S.half=i.half),i.onePercent!==void 0&&(S.onePercent=i.onePercent),buildExample({generatorId:this.id,question:n,title:`What is ${e}% of ${h(s)}?`,computed:S,steps:N})}},fdpP6M04Fraction={id:"FDP.P6.M04.half-quarter.v1",microSkillId:"FDP.P6.M04",forms:FORMS$4,generate(n){const t=normaliseStem(n.stem),e=t.match(/(\d+)\s*%/);if(!e)return null;const s=Number(e[1]);if(![25,50,75].includes(s))return null;const o=Number(n.correctAnswer);if(!Number.isInteger(o))return null;
71for(const r of amountCandidates(t)){if(r<4||s===50&&r%2!==0||s!==50&&r%4!==0)continue;const a=s===50?r/2:s===25?r/4:3*r/4,i=o===a?"part":o===r-a?"less":o===r+a?"more":null;if(!i)continue;const l=r/2,u=r/4,d=s===50?[{c:"50% is one half.",sp:"Per cent means out of a hundred, and fifty out of a hundred is one half. So finding fifty per cent means finding half."},{c:`Half of ${r} = ${a}.`,sp:`To find half, we split the amount into two equal parts: half of ${w$3(r)} is ${w$3(a)}.`,vd:eq$3([`${r} ÷ 2`,"=",a])}]:s===25?[{c:"25% is one quarter.",sp:"Per cent means out of a hundred, and twenty-five out of a hundred is one quarter. So finding twenty-five per cent means finding a quarter."},{c:`Quarter = halve twice: half of ${r} is ${l}, half of ${l} is ${a}.`,sp:`A quarter is half of a half, so we halve twice. Half of ${w$3(r)} is ${w$3(l)}, then half of ${w$3(l)} is ${w$3(a)}.`,vd:eq$3([`${r} ÷ 2`,"=",l,";",`${l} ÷ 2`,"=",a])}]:[{c:"75% is three quarters.",sp:"Per cent means out of a hundred, and seventy-five out of a hundred is three quarters. So we find one quarter first, then take three of them."},{c:`Quarter = halve twice: half of ${r} is ${l}, half of ${l} is ${u}.`,sp:`First find one quarter by halving twice. Half of ${w$3(r)} is ${w$3(l)}, then half of ${w$3(l)} is ${w$3(u)}. Three quarters is three of those.`,vd:eq$3([`${r} ÷ 2`,"=",l,";",`${l} ÷ 2`,"=",u])}],m=i==="part"?s===75?{vd:eq$3(["3 quarters","=",a]),caption:`Three quarters = ${a}.`,spoken:`Three quarters of ${w$3(r)} is ${w$3(a)}.`}:{vd:eq$3([`${s}% of ${r}`,"=",a]),caption:`${s}% of ${r} = ${a}.`,spoken:`${s===50?"One half":"One quarter"} of ${w$3(r)} is ${w$3(a)}, so ${w$3(s)} per cent of ${w$3(r)} is ${w$3(a)} — the answer.`}:i==="less"?{vd:eq$3([r,"−",a,"=",o]),caption:`${r} − ${a} = ${o}.`,spoken:`Take it away: ${w$3(r)}
71 minus ${w$3(a)} is ${w$3(o)}.`}:{vd:eq$3([r,"+",a,"=",o]),caption:`${r} + ${a} = ${o}.`,spoken:`Add it on: ${w$3(r)} plus ${w$3(a)} is ${w$3(o)}.`};return buildExample({generatorId:this.id,question:n,title:`What is ${s}% of ${r}?`,computed:{pct:s,amount:r,half1:l,quarter:u,part:a,answer:o},steps:[{visual_data:eq$3([`${s}%`,"=",s===50?"half":s===25?"quarter":"3 quarters"]),animation:"name_fraction",caption:d[0].c,spoken:d[0].sp},{visual_data:d[1].vd??eq$3([]),animation:"find",caption:d[1].c,spoken:d[1].sp},{visual_data:m.vd,animation:"apply",caption:m.caption,spoken:m.spoken}]})}return null}},fdpP6M04OffAdd={id:"FDP.P6.M04.percent-off-add.v1",microSkillId:"FDP.P6.M04",forms:["context_calculation","symbolic_numeric_entry"],generate(n){const t=normaliseStem(n.stem),e=t.match(/(\d+)\s*%/);if(!e)return null;const s=Number(e[1]);if(s<=0||s>=100)return null;const o=Number(n.correctAnswer);if(!Number.isInteger(o))return null;const r=amountCandidates(t);for(const a of r){if(a<10||a%10!==0)continue;const i=breakdown(s,a);if(i)for(const l of r){if(l===a||l<=0)continue;const u=a-i.part;if(o!==u+l)continue;const d=/£/.test(n.stem),m=unitAfterAmount(n.stem,a),h=b=>d?`£${b}`:m?`${b} ${m}`:`${b}`,f=scaleStep(i,s,a,h),g={pct:s,amount:a,ten:i.ten,tens:i.tens,rem:i.rem,part:i.part,extra:l,afterDiscount:u,answer:o};return i.half!==void 0&&(g.half=i.half),i.onePercent!==void 0&&(g.onePercent=i.onePercent),buildExample({generatorId:this.id,question:n,title:`${h(a)} with ${s}% off, plus ${h(l)}`,computed:g,steps:[{visual_data:eq$3([`10% of ${h(a)}`,"=",`${h(a)} ÷ 10`,"=",h(i.ten)]),animation:"find_ten_percent",caption:`10% of ${h(a)} = ${h(a)} ÷ 10 = ${h(i.ten)}.`,spoken:`Start with ten per cent. Ten per cent of ${wordsAny(a)} is ${wordsAny(a)} divided by ten, which is ${wordsAny(i.ten)}.`},{visual_data:eq$3([`${s}% of ${h(a)}`,"=",f.terms,"=",h(i.part),";",h(a),"−",h(i.part),"=",h(u)]),animation:"find_percent_and_subtract",caption:`${f.deriveCaption}${s}% of ${h(a)} = ${f.terms} = ${h(i.part)}; ${h(a)} − ${h(i.part)} = ${h(u)}.`,spoken:`${f.words} Take off the discount: ${wordsAny(a)} minus ${wordsAny(i.part)} is ${wordsAny(u)}.`},{visual_data:eq$3([h(u),"+",h(l),"=",h(o)]),animation:"add",caption:`${h(u)} + ${h(l)} = ${h(o)}.`,spoken:`Add the extra charge: ${wordsAny(u)} plus ${wordsAny(l)} is ${wordsAny(o)}.`}]})}}return null}},RISE_RE=/\b(?:rises?|increases?|goes?\s+up|is\s+marked\s+up)\s+by\s+(\d+)\s*%/i,FALL_RE=/\b(?:falls?|decreases?|goes?\s+down|drops?)\s+by\s+(\d+)\s*%/i,fdpP6M04ChangeChain={id:"FDP.P6.M04.percent-change-chain.v1",microSkillId:"FDP.P6.M04",forms:["context_calculation","symbolic_numeric_entry"],generate(n){const t=normaliseStem(n.stem),e=t.match(RISE_RE),s=t.match(FALL_RE);if(!e||!s)return null;const o=Number(e[1]),r=Number(s[1]);if(o<=0||o>=100||r<=0||r>=100)return null;const a=Number(n.correctAnswer);if(!Number.isInteger(a))return null;for(const i of amountCandidates(t)){const l=breakdown(o,i);if(!l)continue;const u=i+l.part,d=breakdown(r,u);if(!d)continue;const m=u-d.part;if(m!==a)continue;const h=/£/.test(n.stem),f=unitAfterAmount(n.stem,i),g=E=>h?`£${E}`:f?`${E} ${f}`:`${E}`,b=scaleStep(l,o,i,g),x=scaleStep(d,r,u,g),y={pct1:o,amount:i,ten1:l.ten,tens1:l.tens,rem1:l.rem,part1:l.part,afterRise:u,pct2:r,ten2:d.ten,tens2:d.tens,rem2:d.rem,part2:d.part,answer:m};return l.half!==void 0&&(y.half1=l.half),l.onePercent!==void 0&&(y.onePercent1=l.onePercent),d.half!==void 0&&(y.half2=d.half),d.onePercent!==void 0&&(y.onePercent2=d.onePercent),buildExample({generatorId:this.id,question:n,title:`${g(i)} rises ${o}%, then falls ${r}%`,computed:y,steps:[{visual_data:eq$3([`${o}% of ${g(i)}`,"=",b.terms,"=",g(l.part)]),animation:"find_rise",caption:`${b.deriveCaption}${o}% of ${g(i)} = ${b.terms} = ${g(l.part)}.`,spoken:b.words},{visual_data:eq$3([g(i),"+",g(l.part),"=",g(u)]),animation:"apply_rise",caption:`${g(i)} + ${g(l.part)} = ${g(u)}.`,spoken:`Add the rise on: ${wordsAny(i)} plus ${wordsAny(l.part)} is ${wordsAny(u)}.`},{visual_data:eq$3([`${r}% of ${g(u)}`,"=",x.terms,"=",g(d.part),";",g(u),"−",g(d.part),"=",g(m)]),animation:"apply_fall",caption:`${x.deriveC
71aption}${r}% of ${g(u)} = ${x.terms} = ${g(d.part)}; ${g(u)} − ${g(d.part)} = ${g(m)}.`,spoken:`${x.words} Take the fall away: ${wordsAny(u)} minus ${wordsAny(d.part)} is ${wordsAny(m)}.`}]})}return null}},fdpP6M04Compare={id:"FDP.P6.M04.percent-compare.v1",microSkillId:"FDP.P6.M04",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem),e=/(\d+)\s*%\s*of\s*(\d+)/gi,s=[];let o;for(;(o=e.exec(t))!==null;)s.push({pct:Number(o[1]),amount:Number(o[2])});if(s.length!==2)return null;const[r,a]=s,i=breakdown(r.pct,r.amount),l=breakdown(a.pct,a.amount);if(!i||!l||i.part===l.part)return null;const u=i.part>l.part,d=u?i:l,m=u?r:a,h=`${r.pct}% of ${r.amount}`,f=`${a.pct}% of ${a.amount}`,g=u?h:f,b=n.correctAnswer.trim().toLowerCase(),x=b===String(d.part),y=b.includes(String(m.pct))&&b.includes(String(m.amount));if(!x&&!y)return null;const E=A=>`${A}`,k=scaleStep(i,r.pct,r.amount,E),T=scaleStep(l,a.pct,a.amount,E);return buildExample({generatorId:this.id,question:n,title:`Which is more: ${h} or ${f}?`,computed:{pct1:r.pct,amount1:r.amount,ten1:i.ten,tens1:i.tens,rem1:i.rem,part1:i.part,pct2:a.pct,amount2:a.amount,ten2:l.ten,tens2:l.tens,rem2:l.rem,part2:l.part,...i.half!==void 0?{half1:i.half}:{},...i.onePercent!==void 0?{onePercent1:i.onePercent}:{},...l.half!==void 0?{half2:l.half}:{},...l.onePercent!==void 0?{onePercent2:l.onePercent}:{}},steps:[{visual_data:eq$3([`10% of ${r.amount}`,"=",i.ten,";",k.terms,"=",i.part,";",h,"=",i.part]),animation:"work_first",caption:`10% of ${r.amount} = ${i.ten}. ${k.deriveCaption}${k.terms} = ${i.part}. So ${h} = ${i.part}.`,spoken:`Work out the first amount. Ten per cent of ${w$3(r.amount)} is ${wordsAny(i.ten)}. ${k.words}`},{visual_data:eq$3([`10% of ${a.amount}`,"=",l.ten,";",T.terms,"=",l.part,";",f,"=",l.part]),animation:"work_second",caption:`10% of ${a.amount} = ${l.ten}. ${T.deriveCaption}${T.terms} = ${l.part}. So ${f} = ${l.part}.`,spoken:`Now work out the second amount. Ten per cent of ${w$3(a.amount)} is ${wordsAny(l.ten)}. ${T.words}`},{visual_data:eq$3([i.part,i.part>l.part?">":"<",l.part]),animation:"compare",caption:`${i.part} ${i.part>l.part?">":"<"} ${l.part}, so ${g} is more.`,spoken:`Comparing the two amounts, ${w$3(d.part)} is bigger, so ${w$3(m.pct)} per cent of ${w$3(m.amount)} is more.`}]})}},FDP_P6_M04_GENERATORS=[fdpP6M04,fdpP6M04Fraction,fdpP6M04OffAdd,fdpP6M04ChangeChain,fdpP6M04Compare],w$2=numberToWordsLong,cap$6=capitalised;function ints(n){return[...normaliseStem(n).matchAll(/\d+/g)].map(t=>Number(t[0]))}const NUM_WORDS={one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12},UNIT_WORD_TO_DENOM={half:2,halves:2,third:3,thirds:3,quarter:4,quarters:4,fifth:5,fifths:5,sixth:6,sixths:6,seventh:7,sevenths:7,eighth:8,eighths:8,ninth:9,ninths:9,tenth:10,tenths:10,twelfth:12,twelfths:12};function fractionOf(n){const t=normaliseStem(n),e=t.match(/(\d+)\s*\/\s*(\d+)/);if(e){const o=Number(e[1]),r=Number(e[2]);if(o>=1&&r>=2&&o<r)return{a:o,b:r}}const s=t.match(/\b([a-z]+)\s+(halves|half|thirds|third|quarters|quarter|fifths|fifth|sixths|sixth|sevenths|seventh|eighths|eighth|ninths|ninth|tenths|tenth|twelfths|twelfth)\b/i);if(s){const o=NUM_WORDS[s[1].toLowerCase()],r=UNIT_WORD_TO_DENOM[s[2].toLowerCase()];if(o&&r&&o<r)return{a:o,b:r}}return null}const line$2=(n,t,e,s={})=>({type:"number_line",min:n,max:t,step:e,marked:[],...s}),eq$2=n=>({type:"equation_display",terms:n});function partHops(n,t){const e=[];for(let s=0;s<t;s++)e.push({from:s*n,to:(s+1)*n,label:s===0?"1 part":`${s+1} parts`});return e}function groupHops(n,t,e,s){const o=t>=3,r=[];for(let a=0;a<t;a++){const i=e+a*n,l=e+(a+1)*n,u=o?a===t-1?s(t):"":s(a+1);r.push({from:i,to:l,label:u})}return r}const fractionOfAmount={id:"FDP.P6.M05.fraction-of.v1",microSkillId:"FDP.P6.M05",forms:["symbolic_numeric_entry","context_calculation"],generate(n){const t=fractionOf(n.stem);if(!t)return null;const{a:e,b:s}=t,o=Number(n.correctAnswer);if(!Number.isInteger(o))return null;for(const r of ints(n.stem)){if(r<s||r%s!==0)continue;const a=r/s,i=e*a,l=o===i?"part":o===r-i?"remaining":null;if(!l)continue;const u=s-e;if(l==="part"){const h=groupHops(a,e,0,f=>`${f} part${f===1?"":"s"}`);return buildExample({generatorId:this.id,question:n,title:`What is ${e}/${s} of ${r}?`,computed:{num:e,denom:s,amount:r,onePart:a,part:i,answer:o},steps:[{visual_data:line$2(0,r,a,{hops:partHops(a,1)}),animation:"one_part",caption:`${r} ÷ ${s} = ${a}: one part.`,spoken:`First split ${w$2(r)} into ${w$2(s)} equal parts. Each part is ${w$2(r)} divided by ${w$2(s)}, which is ${w$2(a)}.`},{visual_data:line$2(0,r,a,{hops:h}),animation:"count_parts",caption:`${e}/${s} means ${e} parts.`,spoken:`The top number, ${w$2(e)}, tells us to take ${w$2(e)} of those parts.`},{visual_data:line$2(0,r,a,{hops:h,unknown:i,conclude_label:`${e}/${s} of ${r} = ${i}`}),animation:"land",caption:`${e}/${s} of ${r} = ${i}.`,spoken:`That lands on ${w$2(i)}. So the answer is ${w$2(i)}.`}]})}const d=groupHops(a,e,0,h=>`${h} spent`),m=groupHops(a,u,i,h=>`${h} left`);return buildExample({generatorId:this.id,question:n,title:`What is ${e}/${s} of ${r}?`,computed:{num:e,denom:s,amount:r,onePart:a,part:i,leftParts:u,answer:o},steps:[{visual_data:line$2(0,r,a,{hops:partHops(a,1)}),animation:"one_part",caption:`${r} ÷ ${s} = ${a}: one part.`,spoken:`First split ${w$2(r)} into ${w$2(s)} equal parts. Each part is ${w$2(r)} divided by ${w$2(s)}, which is ${w$2(a)}.`},{visual_data:line$2(0,r,a,{hops:d}),animation:"count_parts",caption:`${e}/${s} used — ${u} parts left.`,spoken:`The question asks what is left, so we take away the parts that are used: ${w$2(s)} parts take away ${w$2(e)} leaves ${w$2(u)} parts.`},{visual_data:line$2(0,r,a,{hops:m,unknown:r,hide_result_number:!0,conclude_label:`${u} × ${a} = ${o}`}),animation:"land",caption:`${u} × ${a} = ${o}: ${u}/${s} left.`,spoken:`Each part is ${w$2(a)}, so the ${w$2(u)} parts left are ${w$2(u)} times ${w$2(a)}, which is ${w$2(o)}. That is how much is left.`}]})}return null}},TWO_STEP_RX=/(\d+) \w+\.\s*(?:He|She|They) gives (\d+)\/(\d+) to (\w+), then (\d+)\/(\d+) of what is left to (\w+)/i,twoStepFractionOf={id:"FDP.P6.M05.two-step-fraction-of.v1",microSkillId:"FDP.P6.M05",forms:["context_calculation"],generate(n){const e=normaliseStem(n.stem).match(TWO_STEP_RX);if(!e)return null;const s=Number(e[1]),o=Number(e[2]),r=Number(e[3]),a=e[4],i=Number(e[5]),l=Number(e[6]),u=e[7];if(s%r!==0)return null;const d=s/r,m=o*d,h=s-m;if(h<=0||h%l!==0)return null;const f=h/l,g=i*f;return g!==Number(n.correctAnswer)?null:buildExample({generatorId:this.id,question:n,title:`${o}/${r} to ${a}, then ${i}/${l} of the rest to ${u}`,computed:{amount:s,a1:o,d1:r,a2:i,d2:l,stage1:m,remaining:h,p1:d,p2:f,answer:g},steps:[{visual_data:line$2(0,s,d,{hops:groupHops(d,o,0,b=>`${b} part${b===1?"":"s"}`),conclude_label:`${m} to ${cap$6(a)}`}),animation:"first_share",caption:`${o}/${r} of ${s} = ${m} to ${cap$6(a)}.`,spoken:`First we find ${a}'s share. ${cap$6(w$2(s))} split into ${w$2(r)} equal parts is ${w$2(d)} in each part, and ${a} gets ${w$2(o)} of them: ${w$2(o)} times ${w$2(d)} is ${w$2(m)}.`},{visual_data:line$2(0,s,d,{hops:[{from:m,to:s,label:`${h} left`}]}),animation:"subtract",caption:`${s} − ${m} = ${h} left for ${cap$6(u)}.`,spoken:`${cap$6(a)}'s share is given away, so we take it off the total to find what is left: ${w$2(s)} take away ${w$2(m)} leaves ${w$2(h)} for ${u}.`},{visual_data:line$2(0,h,f,{hops:groupHops(f,i,0,b=>`${b} part${b===1?"":"s"}`),unknown:g,conclude_label:`${g} to ${cap$6(u)}`}),animation:"second_share",caption:`${i}/${l} of ${h} = ${g}.`,spoken:`${cap$6(u)}'s fraction is of what is left, not of the whole. ${cap$6(w$2(h))} split into ${w$2(l)} equal parts is ${w$2(f)} in each part, and ${u} gets ${w$2(i)} of them: ${w$2(i)} times ${w$2(f)} is ${w$2(g)}. So ${u} gets ${w$2(g)}.`}]})}},COMPARE_RX=/which is larger:\s*(\d+)\/(\d+) of (\d+) or (\d+)\/(\d+) of (\d+)\??/i;function decisivePlaceFromRight(n,t){const e=Math.max(String(n).length,String(t).length),s=String(n).padStart(e,"0"),o=String(t).padStart(e,"0");for(let r=0;r<e;r++)if(s[r]!==o[r])return e-1-r;return 0}const HIGHLIGHT_BY_PLACE={0:"highlight_ones",1:"highlight_tens",2:"highlight_hundreds",3:"highlight_hundreds"},fractionOfCompare={id:"FDP.P6.M05.fraction-of-compare.v1",microSkillId:"FDP.P6.M05",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(COMPARE_RX);if(!e)return null;const[s,o,r,a,i,l]=e.slice(1).map(Number);if(r%o!==0||l%i!==0)return null;const u=s*(r/o),d=a*(l/i),m=`${s}/${o} of ${r}`,h=`${a}/${i} of ${l}`,f=u===d?"They are equal":u>d?m:h;if(n.correctAnswer.trim()!==f)return null;const g=u===d?"=":u>d?">":"<",b=g==="="?"write_answer":HIGHLIGHT_BY_PLACE[decisivePlaceFromRight(u,d)],x=r/o,y=l/i;return buildExample({generatorId:this.id,question:n,title:`Which is larger: ${m} or ${h}?`,computed:{left:u,right:d,onePartLeft:x,onePartRight:y,answer:g==="="?u:Math.max(u,d)},steps:[{visual_data:eq$2([m,"?",h]),animation:"show_pair",caption:"Work out each amount, then compare.",spoken:"To compare them, we work out each amount separately."},{visual_data:eq$2([`${r} ÷ ${o} = ${x}`,"→",`× ${s} = ${u}`,"|",`${l} ÷ ${i} = ${y}`,"→",`× ${a} = ${d}`]),animation:"derive_both",caption:`${r} ÷ ${o} = ${x} → × ${s} = ${u} | ${l} ÷ ${i} = ${y} → × ${a} = ${d}.`,spoken:`${cap$6(w$2(r))} divided by ${w$2(o)} is ${w$2(x)}. ${cap$6(w$2(s))} of those parts make ${w$2(u)}. ${cap$6(w$2(l))} divided by ${w$2(i)} is ${w$2(y)}, and ${w$2(a)} of those parts make ${w$2(d)}.`},{visual_data:{type:"comparison",left:u,right:d,symbol:g},animation:b,caption:g==="="?`Both equal ${u}.`:`${f} is larger (${Math.max(u,d)}).`,spoken:g==="="?"Both amounts are the same, so they are equal.":`${c
71ap$6(w$2(Math.max(u,d)))} is more than ${w$2(Math.min(u,d))}, so that one is larger.`}]})}},findWhole$1={id:"FDP.P6.M05.find-whole.v1",microSkillId:"FDP.P6.M05",forms:["equation_completion"],generate(n){const t=fractionOf(n.stem);if(!t)return null;const{a:e,b:s}=t,o=Number(n.correctAnswer);if(!Number.isInteger(o)||o<s||o%s!==0)return null;const r=o/s,a=e*r;return ints(n.stem).includes(a)?buildExample({generatorId:this.id,question:n,title:`${e}/${s} of a number is ${a}`,computed:{num:e,denom:s,given:a,onePart:r,answer:o},steps:[{visual_data:line$2(0,a,r,{hops:groupHops(r,e,0,i=>`${i} part${i===1?"":"s"}`),marked:[a]}),animation:"given_parts",caption:`${e} parts make ${a}.`,spoken:`${cap$6(w$2(e))} parts of the number make ${w$2(a)}.`},{visual_data:line$2(0,a,r,{hops:[{from:0,to:r,label:"1 part"}]}),animation:"one_part",caption:`One part = ${a} ÷ ${e} = ${r}.`,spoken:`So one part is ${w$2(a)} divided by ${w$2(e)}, which is ${w$2(r)}.`},{visual_data:line$2(0,o,r,{hops:groupHops(r,s,0,i=>`${i} part${i===1?"":"s"}`),unknown:o,conclude_label:`the number is ${o}`}),animation:"whole",caption:`${s} parts = ${o}.`,spoken:`The whole number is all ${w$2(s)} parts: ${w$2(s)} times ${w$2(r)} is ${w$2(o)}.`}]}):null}},FDP_P6_M05_GENERATORS=[fractionOfAmount,twoStepFractionOf,fractionOfCompare,findWhole$1];function gcd$1(n,t){for(n=Math.abs(n),t=Math.abs(t);t;)[n,t]=[t,n%t];return n||1}function fractionDecimal(n,t){const e=gcd$1(n,t),s=n/e,o=t/e;let r=o,a=0,i=0;for(;r%2===0;)r/=2,a++;for(;r%5===0;)r/=5,i++;if(r===1){const d=Math.max(a,i,1),m=Math.round(s/o*10**d),h=String(m).padStart(d+1,"0"),f=h.slice(0,h.length-d)||"0",g=h.slice(h.length-d).replace(/0+$/,"");return{exact:!0,str:g?`${f}.${g}`:f}}let l=s%o,u="";for(let d=0;d<3;d++)l*=10,u+=Math.floor(l/o),l%=o;return{exact:!1,str:`0.${u}…`}}function percentToDecimalStr(n){const[t,e=""]=n.split("."),s=t+e,o=e.length+2,r=s.padStart(o+1,"0"),a=r.slice(0,r.length-o)||"0",i=r.slice(r.length-o).replace(/0+$/,"");return i?`${a}.${i}`:a}function tokenInfo(n){const t=n.trim(),e=/^(\d+)\s*\/\s*(\d+)$/.exec(t);if(e){const o=Number(e[1]),r=Number(e[2]);if(r===0)return null;const{exact:a,str:i}=fractionDecimal(o,r);if(a)return{token:t,value:Number(i),display:i,kind:"fraction"};const l=o/r;return{token:t,value:l,display:i,approx:l.toFixed(2),kind:"fraction"}}const s=/^(\d+(?:\.\d+)?)\s*%$/.exec(t);if(s){const o=percentToDecimalStr(s[1]);return{token:t,value:Number(o),display:o,kind:"percent"}}return/^\d+(?:\.\d+)?$/.test(t)?{token:t,value:Number(t),display:t,kind:"decimal"}:null}function extractInfo(n){const t=n.match(/\d+\s*\/\s*\d+|\d+(?:\.\d+)?\s*%|\d+(?:\.\d+)?/);return t?tokenInfo(t[0].replace(/\s+/g,"")):null}const NAME_VALUE_RE=/([A-Z][a-zA-Z]*)\s+(?:scored|has|got|reached|achieved|earned)\s+(\d+\s*\/\s*\d+|\d+(?:\.\d+)?\s*%|\d+(?:\.\d+)?)/g;function namedValues(n){const t=[];let e;const s=new RegExp(NAME_VALUE_RE);for(;(e=s.exec(n))!==null;){const o=tokenInfo(e[2].replace(/\s+/g,""));o&&t.push({name:e[1],info:o})}return t}function captionFrag(n){return n.kind==="decimal"?`${n.token} unchanged.`:n.approx?`${n.token} = ${n.display} ≈ ${n.approx}.`:`${n.token} = ${n.display}.`}function splitHalves(n){const t=Math.ceil(n.length/2);return[n.slice(0,t),n.slice(t)]}function labelWithValue(n){return n.kind==="decimal"?n.token:`${n.token} = ${n.display}`}function wordsOf(n){return decimalToWords(n.replace("…",""))}function spokenFrag(n){const t=cap$5(tokenNarr(n.token));return n.kind==="decimal"?`${t} is already a decimal, so it needs no conversion.`:n.approx?`${t} is ${wordsOf(n.display)}, which is about ${wordsOf(n.approx)}.`:`${t} is ${wordsOf(n.display)}.`}function pushComputed(n,t,e){n[`${t}_d`]=Number(e.display.replace("…","")),e.approx&&(n[`${t}_a`]=Number(e.approx))}function np(n){return n.replace(/\bzero point\b/g,"nought point").replace(/\bZero point\b/g,"Nought point")}function posRound(n){return Math.round(n*1e4)/1e4}function axisFor(n){const t=Math.max(0,Math.floor(10*Math.min(...n))/10),e=Math.ceil(10*Math.max(...n))/10,s=e===t?t+.1:e,o=s-t<=.2?.05:.1;return{min:t,max:s,step:o}}function lineData(n,t,e={}){return{type:"number_line",min:n.min,max:n.max,step:n.step,marked:t.map(posRound),...e}}const order={id:"FDP.P6.M06.order.v1",microSkillId:"FDP.P6.M06",forms:["comparison_statement"],generate(n){const t=n.visualData;if(!t||t.type!=="drag_order")return null;const e=t.numbers.map(String);if(e.length<2)return null;const s=e.map(tokenInfo);if(s.some(f=>f===null))return null;const o=s,r=normaliseStem(n.stem),a=/smallest to largest|lowest to highest/i.test(r)?"asc":/largest to smallest|highe
71st to lowest/i.test(r)?"desc":null;if(!a)return null;const i=[...o].sort((f,g)=>a==="asc"?f.value-g.value:g.value-f.value);if(i.map(f=>f.token).join(",")!==n.correctAnswer.trim().replace(/\s+/g,""))return null;const u=axisFor(o.map(f=>f.value)),d={count:e.length};o.forEach((f,g)=>pushComputed(d,`t${g}`,f));const[m,h]=splitHalves(o);return buildExample({generatorId:this.id,question:n,title:`Order these: ${a==="asc"?"smallest":"largest"} first`,computed:d,steps:[{visual_data:lineData(u,m.map(f=>f.value)),animation:"convert_first",caption:`Change each to a decimal. ${m.map(captionFrag).join(" ")}`,spoken:np(`Fractions, decimals and percentages are easiest to compare when they are all decimals, so we change each one. ${m.map(spokenFrag).join(" ")}`)},{visual_data:lineData(u,o.map(f=>f.value)),animation:"place_all",caption:h.map(captionFrag).join(" "),spoken:np(`${h.map(spokenFrag).join(" ")} Now every value is on the line as a decimal, and where each one sits shows how big it is.`)},{visual_data:lineData(u,o.map(f=>f.value),{conclude_label:i.map(f=>f.token).join(", ")}),animation:"read_order",caption:a==="asc"?`Smallest first: ${i.map(f=>f.token).join(", ")}.`:`Largest first: ${i.map(f=>f.token).join(", ")}.`,spoken:np(`Reading ${a==="asc"?"from the left, smallest first":"from the right, largest first"}, the order is ${i.map(f=>tokenNarr(f.token)).join(", then ")}.`)}]})}},LARGE_RE=/\b(largest|biggest|greatest|most|highest|greater|charged)\b/i,SMALL_RE=/\b(smallest|least|lowest|emptiest|fewest)\b/i;function namedWhich(n,t,e){if(!n.options||n.options.length<2)return null;const s=namedValues(n.stem);if(s.length<2)return null;const o=new Map(s.map(f=>[f.name.toLowerCase(),f]));if(n.options.some(f=>!o.has(f.trim().toLowerCase())))return null;const r=s.reduce((f,g)=>e?g.info.value>f.info.value?g:f:g.info.value<f.info.value?g:f);if(n.correctAnswer.trim().toLowerCase()!==r.name.toLowerCase())return null;const a=axisFor(s.map(f=>f.info.value)),i={};s.forEach((f,g)=>pushComputed(i,`t${g}`,f.info));const l=s.filter(f=>f!==r).map(f=>f.name),[u,d]=splitHalves(s),m=f=>{const{info:g}=f;return g.kind==="decimal"?`${f.name}: ${g.token} (decimal).`:g.approx?`${f.name}: ${g.token} = ${g.display} ≈ ${g.approx}.`:`${f.name}: ${g.token} = ${g.display}.`},h=f=>{const g=cap$5(f.name),{info:b}=f;return b.kind==="decimal"?`${g} scored ${wordsOf(b.token)}, which is already a decimal.`:b.approx?`${g} scored ${tokenNarr(b.token)}, which is about ${wordsOf(b.approx)}.`:`${g} scored ${tokenNarr(b.token)}, which is ${wordsOf(b.display)}.`};return buildExample({generatorId:"FDP.P6.M06.which.v1",question:n,title:`Who scored ${e?"highest":"lowest"}?`,computed:i,steps:[{visual_data:lineData(a,u.map(f=>f.info.value)),animation:"convert",caption:`Convert each score. ${u.map(m).join(" ")}`,spoken:np(`The scores are written in different ways, so to compare them fairly we change every score into a decimal. ${u.map(h).join(" ")}`)},{visual_data:lineData(a,s.map(f=>f.info.value)),animation:"place_all",caption:d.map(m).join(" "),spoken:np(`${d.map(h).join(" ")} With every score on the line, the ${e?"furthest right is the highest":"furthest left is the lowest"}.`)},{visual_data:lineData(a,s.map(f=>f.info.value),{unknown:posRound(r.info.value),conclude_label:`${e?"Highest":"Lowest"}: ${r.name}`}),animation:"pick",caption:`${r.name} scored ${labelWithValue(r.info)} — ${l.join(", ")} did not score ${e?"higher":"lower"}.`,spoken:np(`${cap$5(r.name)}'s score, ${wordsOf(r.info.display)}, sits ${e?"furthest right":"furthest left"}. So the ${e?"highest":"lowest"} score was ${cap$5(r.name)}'s.`)}]})}const which={id:"FDP.P6.M06.which.v1",microSkillId:"FDP.P6.M06",forms:["comparison_statement"],generate(n){if(!n.options||n.options.length<2)return null;const t=normaliseStem(n.stem);if(/between/i.test(t))return null;const e=LARGE_RE.test(t),s=SMALL_RE.test(t);if(e===s)return null;const o=n.options.map(f=>extractInfo(f));if(o.some(f=>f===null))return namedWhich(n,t,e);const r=o,a=r.reduce((f,g)=>e?g.value>f.value?g:f:g.value<f.value?g:f),i=extractInfo(n.correctAnswer);if(!i||Math.abs(i.value-a.value)>1e-9)return null;const l=axisFor(r.map(f=>f.value)),u={answer:a.value};r.forEach((f,g)=>pushComputed(u,`t${g}`,f));const d=r.filter(f=>f!==a).map(f=>f.token),[m,h]=splitHalves(r);return buildExample({generatorId:this.id,question:n,title:`Which is ${e?"largest":"smallest"}?`,computed:u,steps:[{visual_data:lineData(l,m.map(f=>f.value)),animation:"convert",caption:`Change every option to a decimal. ${m.map(captionFrag).join(" ")}`,spoken:np(`The values are written in different ways, so to compare them fairly we change every value into a decimal. ${m.map(spokenFrag).join(" ")}`)},{visual_data:lineData(l,r.map(f=>f.value)),animation:"place_all",caption:h.map(captionFrag).join(" "),spoken:np(`${h.map(spokenFrag).join(" ")} With every value on the line, the ${e?"furthest right is the largest":"furthest left is the smallest"}.`)},{visual_data:lineData(l,r.map(f=>
71f.value),{unknown:posRound(a.value),conclude_label:`${e?"Largest":"Smallest"}: ${a.token}`}),animation:"pick",caption:`${labelWithValue(a)} is ${e?"largest":"smallest"} — ${d.join(", ")} ${d.length>1?"are":"is"} not.`,spoken:np(`${cap$5(wordsOf(a.display))} sits ${e?"furthest right":"furthest left"}. So the ${e?"largest":"smallest"} is ${tokenNarr(a.token)}.`)}]})}},between={id:"FDP.P6.M06.between.v1",microSkillId:"FDP.P6.M06",forms:["comparison_statement"],generate(n){if(!n.options||n.options.length<2)return null;const e=normaliseStem(n.stem).match(/between\s+(.+?)\s+and\s+([^\s?]+)/i);if(!e)return null;const s=extractInfo(e[1]),o=extractInfo(e[2]);if(!s||!o)return null;const r=Math.min(s.value,o.value),a=Math.max(s.value,o.value),i=n.options.map(b=>extractInfo(b));if(i.some(b=>b===null))return null;const l=i,u=l.filter(b=>b.value>r&&b.value<a);if(u.length!==1)return null;const d=u[0],m=extractInfo(n.correctAnswer);if(!m||Math.abs(m.value-d.value)>1e-9)return null;const h=axisFor([s.value,o.value,...l.map(b=>b.value)]),f={answer:d.value};pushComputed(f,"lo",s),pushComputed(f,"hi",o),l.forEach((b,x)=>pushComputed(f,`o${x}`,b));const g=l.filter(b=>b!==d).map(b=>b.token);return buildExample({generatorId:this.id,question:n,title:`Which value lies between ${s.token} and ${o.token}?`,computed:f,steps:[{visual_data:lineData(h,[s.value,o.value]),animation:"bounds",caption:`${captionFrag(s)} ${captionFrag(o)}`,spoken:np(`To find a value in between, we need everything as decimals so we can compare. First we change the two ends. ${spokenFrag(s)} ${spokenFrag(o)}`)},{visual_data:lineData(h,[s.value,o.value,...l.map(b=>b.value)]),animation:"place_answer",caption:l.map(captionFrag).join(" "),spoken:np(`Now we change each option into a decimal too. ${l.map(spokenFrag).join(" ")}`)},{visual_data:lineData(h,[s.value,o.value,...l.map(b=>b.value)],{unknown:posRound(d.value),conclude_label:`${d.token} is between`}),animation:"pick",caption:`${labelWithValue(d)} sits between ${s.display} and ${o.display} — ${g.join(", ")} ${g.length>1?"do":"does"} not.`,spoken:np(`Only ${wordsOf(d.display)} lands between ${wordsOf(String(r))} and ${wordsOf(String(a))}. So the answer is ${tokenNarr(d.token)}.`)}]})}},claim={id:"FDP.P6.M06.claim.v1",microSkillId:"FDP.P6.M06",forms:["misconception_contrast_probe"],generate(n){const e=normaliseStem(n.stem).match(/says\s+(.+?)\s+is\s+(greater than|bigger than|larger than|less than|smaller than|the same as|equal to)\s+(\S+)/i);if(!e)return null;const s=extractInfo(e[1]),o=extractInfo(e[3]);if(!s||!o)return null;const r=e[2].toLowerCase();let a;if(/same|equal/.test(r)?a=Math.abs(s.value-o.value)<1e-9:/less|smaller/.test(r)?a=s.value<o.value:a=s.value>o.value,a?!isAffirmative(n.correctAnswer):!isNegative(n.correctAnswer))return null;const i=axisFor([s.value,o.value]),l={answer:a?1:0};pushComputed(l,"a",s),pushComputed(l,"b",o);const u=s.value>=o.value?s:o;return buildExample({generatorId:this.id,question:n,title:`Is ${s.token} really ${r} ${o.token}?`,computed:l,steps:[{visual_data:lineData(i,[s.value]),animation:"convert",caption:`Change both to decimals. ${captionFrag(s)}`,spoken:`Decimals are easy to compare on a number line, so to check the claim we change both values to decimals. ${np(spokenFrag(s))}`},{visual_data:lineData(i,[s.value,o.value]),animation:"place_both",caption:captionFrag(o),spoken:np(spokenFrag(o))},{visual_data:lineData(i,[s.value,o.value],{unknown:posRound(u.value),conclude_label:a?"The claim is right":"The claim is wrong"}),animation:"verdict",caption:Math.abs(s.value-o.value)<1e-9?`${labelWithValue(s)} and ${labelWithValue(o)} are equal.`:`${labelWithValue(u)} is larger.`,spoken:Math.abs(s.value-o.value)<1e-9?`Both values land on the same point, ${np(wordsOf(s.display))}, so they are equal — ${a?"yes, the claim is correct":"no, the claim is not correct"}.`:`On a number line the larger value is further right. ${cap$5(np(wordsOf(u.display)))} is further right, so it is larger — ${a?"yes, the claim is correct":"no, the claim is not correct"}.`}]})}};function tokenNarr(n){const t=/^(\d+)\/(\d+)$/.exec(n);if(t)return`${numWord(Number(t[1]))} over ${numWord(Number(t[2]))}`;const e=/^(\d+(?:\.\d+)?)%$/.exec(n);return e?`${decimalToWords(e[1])} per cent`:decimalToWords(n)}const SMALL_WORDS=["zero","one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve"];function numWord(n){return SMALL_WORDS[n]??numberToWordsLong(n)}function cap$5(n){return n.charAt(0).toUpperCase()+n.slice(1)}const FDP_P6_M06_GENERATORS=[order,which,between,claim],LINE_COUNT={equilateral_tri
71angle:3,square:4,rectangle:2,regular_pentagon:5,regular_hexagon:6,regular_octagon:8},NAMED$1=[{re:/equilateral triangle/i,shape:"equilateral_triangle"},{re:/regular pentagon/i,shape:"regular_pentagon"},{re:/regular hexagon/i,shape:"regular_hexagon"},{re:/regular octagon/i,shape:"regular_octagon"},{re:/rectangle/i,shape:"rectangle"},{re:/square/i,shape:"square"}],w$1=numberToWords,symVisual=n=>({type:"symmetry_lines",shape:n,show_lines:"all"}),eq$1=n=>({type:"equation_display",terms:n}),count={id:"GEO.P6.M04.count.v1",microSkillId:"GEO.P6.M04",forms:["visual_quantity_to_symbol"],generate(n){const t=n.visualData;if(!t||t.type!=="symmetry_lines")return null;const e=t.shape,s=LINE_COUNT[e];if(s==null||Number(n.correctAnswer)!==s)return null;const o=e!=="rectangle";return buildExample({generatorId:this.id,question:n,title:"How many lines of symmetry?",computed:{lines:s,answer:s},steps:[{visual_data:symVisual(e),animation:"fold_first",caption:"Fold the shape — the halves match.",spoken:"A line of symmetry is a fold where both halves match exactly. Here is the first fold line."},{visual_data:symVisual(e),animation:"fold_rest",caption:"Count every fold line.",spoken:"Keep finding folds that match the two halves, and count them as they appear."},{visual_data:symVisual(e),animation:"conclude",caption:`${s} lines of symmetry.`,spoken:o?`There are ${w$1(s)} — one for every side of a regular shape.`:`A rectangle has just ${w$1(s)}: its two midlines, not its diagonals.`}]})}};function verdictOf(n,t){const e=t.toLowerCase(),s=/diagonal|opposite corner/.test(e),o=/neighbouring|adjacent/.test(e),r=/opposite vert/.test(e),a=/opposite side|middle of two sides/.test(e),i=/down the middle|through the middle|straight down|midline/.test(e);return n==="rectangle"?s?!1:i?!0:null:n==="square"?s||i?!0:null:n==="equilateral_triangle"||n==="regular_pentagon"?o?!1:a?!0:r?!1:null:o?!1:r||a?!0:null}const verdict={id:"GEO.P6.M04.verdict.v1",microSkillId:"GEO.P6.M04",forms:["misconception_contrast_probe"],generate(n){const t=n.visualData;if(!t||t.type!=="symmetry_lines")return null;const e=t,s=e.shape,o=verdictOf(s,n.stem);if(o===null||(o?!isAffirmative(n.correctAnswer):!isNegative(n.correctAnswer)))return null;const r=LINE_COUNT[s],a=e.described_line,i=()=>a?{type:"symmetry_lines",shape:s,show_lines:"none",described_line:a}:symVisual(s);return buildExample({generatorId:this.id,question:n,title:"Is this a line of symmetry?",computed:{lines:r,answer:o?1:0},steps:[{visual_data:i(),animation:"fold_check_setup",caption:"Try folding along the grey line.",spoken:"A line of symmetry is a fold where both halves land exactly on top of each other. Test the grey line from the question."},{visual_data:i(),animation:"fold_check_match",caption:o?"The orange folded half covers the other half exactly.":a==="adjacent_vertices"?"This is an edge: there is no half on the other side.":"The orange folded half sticks out beyond the blue outline.",spoken:o?"Fold one half over the grey line. The orange folded half lands on the other half. Every corner and edge matches.":a==="adjacent_vertices"?"The whole shape is on one side of this edge. Folding it over leaves it outside the original shape, with no other half to match.":"The diagonal makes two equal triangles, but folding one over that diagonal does not make them match. The orange folded triangle sticks out past the blue outline."},{visual_data:i(),animation:"fold_check_verdict",caption:o?"Yes — the folded halves match exactly.":"No — the folded halves do not match.",spoken:o?"The halves match exactly when folded, so yes, this is a line of symmetry.":"The folded shape does not match the other side, so no, this is not a line of symmetry."}]})}},rule={id:"GEO.P6.M04.rule.v1",microSkillId:"GEO.P6.M04",forms:["shape_property","misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=Number(n.correctAnswer);if(!Number.isInteger(e))return null;const s=t.match(/regular shape has (\d+) lines of symmetry\. How many sides/i);if(s&&Number(s[1])===e)return buildExample({generatorId:this.id,question:n,title:"Find sides from symmetry lines",computed:{lines:e,answer:e},steps:[{visual_data:eq$1([`${e} lines`,"→","? sides"]),animation:"given_lines",caption:`Given: ${e} lines of symmetry. Sides are unknown.`,spoken:`We know this regular shape has ${w$1(e)} lines of symmetry. We need to find how many sides it has.`},{visual_data:eq$1(["Regular shape:","lines = sides"]),animation:"rule",caption:"A regular shape has as many sides as 
71symmetry lines.",spoken:"For a regular shape, the number of symmetry lines equals the number of sides. We can use that rule in either direction."},{visual_data:eq$1([`${e} lines`,"→",`${e} sides`]),animation:"conclude",caption:`${e} lines, so ${e} sides.`,spoken:`So ${w$1(e)} lines of symmetry means this regular shape has ${w$1(e)} sides.`}]});const o=NAMED$1.find(a=>a.re.test(t)&&a.shape!=="rectangle"&&LINE_COUNT[a.shape]===e);if(o)return buildExample({generatorId:this.id,question:n,title:"How many lines of symmetry?",computed:{lines:e,answer:e},steps:[{visual_data:symVisual(o.shape),animation:"fold_first",caption:"Fold the shape — the halves match.",spoken:"A line of symmetry is a fold where both halves match. Here is the first."},{visual_data:symVisual(o.shape),animation:"fold_rest",caption:"Count every fold line.",spoken:"Find every fold that matches the two halves, and count them."},{visual_data:symVisual(o.shape),animation:"conclude",caption:`${e} lines of symmetry.`,spoken:`A regular shape has one line of symmetry for every side, so this one has ${w$1(e)}.`}]});if(/regular/i.test(t)&&new RegExp(`\\b${e}\\b`).test(t))return buildExample({generatorId:this.id,question:n,title:"How many lines of symmetry?",computed:{n:e,answer:e},steps:[{visual_data:eq$1(["Regular shape:","sides = lines"]),animation:"rule",caption:"A regular shape: sides = lines.",spoken:"In a regular shape, every side gives exactly one line of symmetry, so the number of lines equals the number of sides."},{visual_data:eq$1([`${e} sides`]),animation:"count",caption:`This shape has ${e} sides.`,spoken:`This regular shape has ${w$1(e)} equal sides.`},{visual_data:eq$1([`${e} sides`,"=",`${e} lines`]),animation:"conclude",caption:`${e} sides = ${e} lines.`,spoken:`So it has ${w$1(e)} lines of symmetry.`}]});const r=NAMED$1.find(a=>a.re.test(t));if(r&&LINE_COUNT[r.shape]===e&&r.shape==="rectangle"){const a={type:"symmetry_lines",shape:"rectangle",show_lines:"none",described_line:"rect_diagonal"};return buildExample({generatorId:this.id,question:n,title:"How many lines of symmetry?",computed:{lines:e,answer:e},steps:[{visual_data:symVisual("rectangle"),animation:"fold_first",caption:"Fold the rectangle side to side.",spoken:"A rectangle folds side to side, matching the two halves — that is one fold line."},{visual_data:a,animation:"check_diagonal",caption:"Now try folding along a diagonal, like Zara says.",spoken:"Zara counts the diagonals too, like a square. Fold along a diagonal instead — the two halves do not match, so a diagonal is not a fold line."},{visual_data:symVisual("rectangle"),animation:"conclude",caption:`${e} lines of symmetry.`,spoken:`So a rectangle has just ${w$1(e)} lines of symmetry — its two midlines, not the diagonals.`}]})}return null}},compare$3={id:"GEO.P6.M04.compare.v1",microSkillId:"GEO.P6.M04",forms:["comparison_statement"],generate(n){if(!n.options||n.options.length<2)return null;const t=normaliseStem(n.stem),e=n.options.map(d=>{const m=NAMED$1.find(h=>h.re.test(d));return m?{label:d,shape:m.shape,n:LINE_COUNT[m.shape]}:null});if(e.some(d=>d===null))return null;const s=e,o=t.match(/exactly (\d+)/i);let r;if(o){const d=Number(o[1]),m=s.filter(h=>h.n===d);if(m.length!==1)return null;r=m[0]}else if(/\b(most|more|greatest)\b/i.test(t))r=s.reduce((d,m)=>m.n>d.n?m:d);else if(/\b(fewest|fewer|least)\b/i.test(t))r=s.reduce((d,m)=>m.n<d.n?m:d);else return null;if(!r||r.label!==n.correctAnswer.trim())return null;const a={answer:r.n};s.forEach((d,m)=>a[`n${m}`]=d.n);const i=s.map(d=>`${label(d.shape)} = ${d.n}`).join(", "),l=o?"Exactly":/fewe|least/i.test(t)?"Fewest":"Most",u={visual_data:symVisual(r.shape),animation:"pick",caption:`${l}: ${label(r.shape)} (${r.n}).`,spoken:o?`We want exactly ${w$1(r.n)}, and only one shape has that many, so the answer is the ${label(r.shape).toLowerCase()}, with ${w$1(r.n)} lines of symmetry.`:l==="Fewest"?`We want the fewest, and ${w$1(r.n)} is the smallest count, so the answer is the ${label(r.shape).toLowerCase()}, with ${w$1(r.n)} lines of symmetry.`:`We want the most, and ${w$1(r.n)} is the biggest count, so the answer is the ${label(r.shape).toLowerCase()}, with ${w$1(r.n)} lines of symmetry.`};if(s.length===2){const[d,m]=s;return buildExample({generatorId:this.id,question:n,title:"Compare the lines of symmetry",computed:a,steps:[{visual_data:symVisual(d.shape),animation:"count",caption:`${label(d.shape)}: ${d.n} lines of symmetry.`,spoken:`A line of symmetry is a fold where both halves match exactly, so we count the folds on each shape. The ${label(d.shape).toLowerCase()} has ${w$1(d.n)}.`},{visual_data:symVisual(m.shape),animation:"count",caption:`${label(m.shape)}: ${m.n} lines of symmetry.`,spoken:`Now we count the folds on the ${label(m.shape).toLowerCase()} the same way. It has ${w$1(m.n)}.`},u]})}return buildExample({generatorId:this.id,question:n,title:"Compare the lines of symmetry",computed:a,steps:[{visual_data:eq$1(["Count each shape's lines"]),animation:"count",caption:"Count the lines for each shape.",spoken:"First c
71ount the lines of symmetry for each shape — remember, a regular shape has one for every side."},{visual_data:eq$1(s.map(d=>`${label(d.shape)} = ${d.n}`)),animation:"list",caption:trimCap(i),spoken:`Now the counts are lined up, ready to compare: ${s.map(d=>`the ${label(d.shape).toLowerCase()} has ${w$1(d.n)}`).join(", ")}.`},u]})}},SAME_COUNT_CLAIM=/^Do (?:a |an )?(.+?) and (?:a |an )?(.+?) have the same number of lines of symmetry\?$/i,sameCount={id:"GEO.P6.M04.same-count.v1",microSkillId:"GEO.P6.M04",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(SAME_COUNT_CLAIM);if(!e)return null;const s=NAMED$1.find(f=>f.re.test(e[1])),o=NAMED$1.find(f=>f.re.test(e[2]));if(!s||!o)return null;const r=LINE_COUNT[s.shape],a=LINE_COUNT[o.shape],i=r===a,l=(n.correctAnswer??"").trim().toLowerCase(),u=r>a?s.shape:o.shape;if(i){if(!/^yes\b/.test(l))return null}else if(/^yes\b/.test(l)||!l.includes(label(u).toLowerCase()))return null;const d=i?s.shape:u,m=i?`Both have ${r} — yes, the same number.`:`${label(u)} has ${numberToWords(Math.abs(r-a))} more — no, not the same.`,h=i?`Both shapes have ${w$1(r)} lines of symmetry, so yes, they are the same.`:`${capitalised(w$1(Math.max(r,a)))} is more than ${w$1(Math.min(r,a))}, so the ${label(u).toLowerCase()} has more lines of symmetry — no, they are not the same.`;return buildExample({generatorId:this.id,question:n,title:`Do ${label(s.shape).toLowerCase()} and ${label(o.shape).toLowerCase()} match?`,computed:{nA:r,nB:a,answer:i?1:0},steps:[{visual_data:symVisual(s.shape),animation:"count",caption:`${label(s.shape)}: ${r} lines of symmetry.`,spoken:`To see if they match, we count the folds where both halves match exactly on each shape. The ${label(s.shape).toLowerCase()} has ${w$1(r)} lines of symmetry.`},{visual_data:symVisual(o.shape),animation:"count",caption:`${label(o.shape)}: ${a} lines of symmetry.`,spoken:`Now we count the folds on the ${label(o.shape).toLowerCase()} the same way. It has ${w$1(a)}.`},{visual_data:symVisual(d),animation:"pick",caption:m,spoken:h}]})}};function label(n){return{equilateral_triangle:"Triangle",square:"Square",rectangle:"Rectangle",regular_pentagon:"Pentagon",regular_hexagon:"Hexagon",regular_octagon:"Octagon"}[n]}function trimCap(n){return n.length<=70?`${n}.`:`${n.slice(0,67)}…`}const GEO_P6_M04_GENERATORS=[count,verdict,rule,compare$3,sameCount],PAIRS={parallelogram:2,rhombus:2,rectangle:2,square:2,trapezium:1,kite:0},NAME={parallelogram:"Parallelogram",rhombus:"Rhombus",trapezium:"Trapezium",rectangle:"Rectangle",square:"Square",kite:"Kite"},SUPER={square:["square","rectangle","rhombus","parallelogram"],rectangle:["rectangle","parallelogram"],rhombus:["rhombus","parallelogram"],parallelogram:["parallelogram"],trapezium:["trapezium"],kite:["kite"]},NAMED=[{re:/parallelogram/i,shape:"parallelogram"},{re:/rhombus/i,shape:"rhombus"},{re:/trapezium/i,shape:"trapezium"},{re:/rectangle/i,shape:"rectangle"},{re:/square/i,shape:"square"},{re:/kite/i,shape:"kite"}],quadVisual=n=>({type:"quadrilateral",shapes:[{shape:n}],show_parallel_marks:!0}),quadVisualTicked=(n,t)=>({type:"quadrilateral",shapes:[{shape:n,equal_side_marks:!0}],show_parallel_marks:!0});function shapesInOrder(n){const t=[];for(const{re:e,shape:s}of NAMED){const o=n.match(e);o&&o.index!=null&&t.push({idx:o.index,shape:s})}return t.sort((e,s)=>e.idx-s.idx).map(e=>e.shape)}function pairWord(n){return n===0?"no parallel pairs":n===1?"one parallel pair":"two parallel pairs"}const name={id:"GEO.P6.M05.name.v1",microSkillId:"GEO.P6.M05",forms:["shape_property"],generate(n){const t=n.visualData;if(!t||t.type!=="quadrilateral")return null;const e=t.shapes?.[0]?.shape;if(!e||NAME[e]!==n.correctAnswer.trim())return null;const s=PAIRS[e],o=s===2?"The other pair is parallel too.":s===1?"The other pair is not parallel.":"No sides are parallel.",r=e==="rhombus";return buildExample({generatorId:this.id,question:n,title:"Name this quadrilateral",computed:{pairs:s,answer:s},steps:[{visual_data:quadVisual(e),animation:"trace_pair1",caption:"Hunt for parallel sides.",spoken:"To name a quadrilateral, look for parallel sides — sides that run the same way and never meet."},{visual_data:quadVisual(e),animation:"trace_pair2",caption:o,spoken:`This shape has ${pairWord(s)}.`},{visual_data:r?quadVisualTicked(e):quadVisual(e),animation:"name",caption:r?"Two parallel pairs AND all four sides equal — rhombus, not rectangle.":`It is a ${NAME[e].toLowerCase()}.`,spoken:r?`With ${pairWord(s)} and all four sides the same length, this is a rhombus, not a rectangle — a rectangle's sides are not all equal.`:`With ${pairWord(s)} and its side and angle pattern, the most specific name is ${NAME[e].toLowerCase()}.`}]})}},odd={id:"GEO.P6.M05.odd.v1",microSkillId:"GEO.P6.M05",forms:["misconception_contrast_probe"],generate(n){if(!n.options||n.options.length<3||!/odd one out/i.test(n.stem))return null;const t=n.options.map(a=>{const i=NAMED.find(l=>l.re.test(a));return i?{label:a,shape:i.shape,p:PAIRS[i.shape]}:null});if(t.some(a=>a===null))return null;const e=t,s=new Map;for(const a of e)s.set(a.p,[...s.get(a.p)??[],a]);const o=[...s.values()].filter(a=>a.length===1);if(o.length!==1)return null;const r=o[0][0];return r.label!==n.correctAnswer.trim()?null:buildExample({generatorId:this.id,question:n,title:"Odd one out by parallel sides",computed:{pairs:r.p,answer:r.p},steps:[{visual_data:quadVisual(e.find(a=>a.p===2)?.shape??r.shape),animation:"trace_pair1",caption:"Count parallel pairs for each shape.",spoken:"Count how many pairs of parallel sides each shape has."},{visual_data:quadVisual(e.find(a=>a.p===2)?.shape??r.shape),animation:"trace_pair2",caption:"Most have two parallel pairs.",spoken:"Most of these shapes have tw
71o pairs of parallel sides."},{visual_data:quadVisual(r.shape),animation:"name",caption:`The ${NAME[r.shape].toLowerCase()} has ${pairWord(r.p)}.`,spoken:`The ${NAME[r.shape].toLowerCase()} is different — it has ${pairWord(r.p)}, so it is the odd one out.`}]})}},quadVisualRightAngle=(n,t)=>({type:"quadrilateral",shapes:[{shape:n,...t?{mark_right_angles:!0}:{}}]}),quadVisualExtended=(n,t)=>({type:"quadrilateral",shapes:[{shape:n,...t?{show_side_extensions:!0}:{}}]});function buildRightAngleIsA(n,t,e,s){const o=t==="rectangle"||t==="square",r=s==="always"?`Always a ${NAME[e].toLowerCase()}.`:s==="never"?`Never a ${NAME[e].toLowerCase()}.`:`Only sometimes a ${NAME[e].toLowerCase()}.`,a=s==="always"?`A ${NAME[t].toLowerCase()} always has four right angles, which is exactly what a ${NAME[e].toLowerCase()} needs — so it is always one.`:s==="never"?`A ${NAME[t].toLowerCase()} does not have four right angles, so it can never be a ${NAME[e].toLowerCase()}.`:`Some ${NAME[t].toLowerCase()}s have four right angles and some do not, so only sometimes.`;return buildExample({generatorId:"GEO.P6.M05.is-a.v1",question:n,title:`Is a ${NAME[t].toLowerCase()} a ${NAME[e].toLowerCase()}?`,computed:{hasRightAngles:o?1:0,answer:s==="always"?1:s==="never"?0:2},steps:[{visual_data:quadVisualRightAngle(t,!1),animation:"check_corners",caption:`A ${NAME[e].toLowerCase()} needs four right angles.`,spoken:`A ${NAME[e].toLowerCase()} is defined by its four right angles — square corners. Check the ${NAME[t].toLowerCase()}'s corners.`},{visual_data:quadVisualRightAngle(t,!0),animation:"mark_right_angles",caption:o?"Every corner is a right angle.":"Not every corner is a right angle.",spoken:o?`Every corner of the ${NAME[t].toLowerCase()} is a right angle.`:`The ${NAME[t].toLowerCase()}'s corners are not all right angles.`},{visual_data:quadVisualRightAngle(t,!0),animation:"verdict",caption:r,spoken:a}]})}function buildKiteNeverIsA(n,t,e){return buildExample({generatorId:"GEO.P6.M05.is-a.v1",question:n,title:`Is a ${NAME[t].toLowerCase()} a ${NAME[e].toLowerCase()}?`,computed:{pairsA:0,answer:0},steps:[{visual_data:quadVisualExtended(t,!1),animation:"trace_pair1",caption:`A ${NAME[e].toLowerCase()} needs two pairs of parallel sides.`,spoken:`A ${NAME[e].toLowerCase()} needs two pairs of sides that never meet, however far they go. Check the ${NAME[t].toLowerCase()}'s opposite sides.`},{visual_data:quadVisualExtended(t,!0),animation:"extend_sides",caption:"Extend the opposite sides — they cross, so they are not parallel.",spoken:`Extend each pair of opposite sides. On a ${NAME[t].toLowerCase()}, they cross instead of running side by side forever, so neither pair is parallel.`},{visual_data:quadVisualExtended(t,!0),animation:"verdict",caption:`Never a ${NAME[e].toLowerCase()}.`,spoken:`A ${NAME[t].toLowerCase()} has no parallel pairs, so it can never be a ${NAME[e].toLowerCase()}.`}]})}const isA={id:"GEO.P6.M05.is-a.v1",microSkillId:"GEO.P6.M05",forms:["misconception_contrast_probe"],generate(n){const t=normaliseStem(n.stem),e=shapesInOrder(t);if(e.length<2)return null;const[s,o]=e,r=SUPER[s].includes(o)?"always":SUPER[o].includes(s)?"sometimes":"never",a=n.correctAnswer.trim().toLowerCase();if(/always|sometimes|never/.test(t)||/always|sometimes|never/.test(a)){if(!a.startsWith(r))return null}else if(r==="sometimes"||(r==="always"?!isAffirmative(n.correctAnswer):!isNegative(n.correctAnswer)))return null;if(o==="rectangle"||o==="square")return buildRightAngleIsA(n,s,o,r);if(s==="kite"&&r==="never")return buildKiteNeverIsA(n,s,o);const l=r==="always"?`Every ${NAME[s].toLowerCase()} fits the rule for a ${NAME[o].toLowerCase()}, so it is always one.`:r==="never"?`A ${NAME[s].toLowerCase()} can never fit the rule for a ${NAME[o].toLowerCase()}.`:`Some ${NAME[s].toLowerCase()}s fit the rule for a ${NAME[o].toLowerCase()}, but not all, so only sometimes.`,u=PAIRS[s];return buildExample({generatorId:this.id,question:n,title:`Is a ${NAME[s].toLowerCase()} a ${NAME[o].toLowerCase()}?`,computed:{pairsA:u,answer:r==="always"?1:r==="never"?0:2},steps:[{visual_data:quadVisual(s),animation:"trace_pair1",caption:`Check the ${NAME[s].toLowerCase()}'s parallel sides.`,spoken:`Look at what makes a ${NAME[s].toLowerCase()}, and what a ${NAME[o].toLowerCase()} needs.`},{visual_data:quadVisual(s),animation:"trace_pair2",caption:`It has ${pairWord(u)}.`,spoken:`A ${NAME[s].toLowerCase()} has ${pairWord(u)}. A ${NAME[o].toLowerCase()} needs two pairs of parallel sides.`},{visual_data:quadVisual(s),animation:"verdict",caption:r==="always"?`Always a ${NAME[o].toLowerCase()}.`:r==="never"?`Never a ${NAME[o].toLowerCase()}.`:`Only sometimes a ${NAME[o].toLowerCase()}.`,spoken:l}]})}};function shapeMatcher(n){const t=n.toLowerCase(),e=t.match(/not a (parallelogram|rhombus|rectangle|square|trapezium|kite)/);if(e){const o=NAMED.find(r=>r.re.test(e[1])).shape;return r=>!SUPER[r].includes(o)}const s=t.match(/is a (parallelogram|rhombus|rectangle|square|trapezium|kite)\b/);if(s){const o=NAMED.find(r=>r.re.test(s[1])).shape;return r=>r===o}if(/two pairs of parallel/.test(t)){const o=/no right angle/.test(t);return r=>PAIRS[r]===2&&(!o||r!=="rectangle"&&r!=="square")}return/one pair of parallel|exactly one pair/.test(t)?o=>PAIRS[o]===1:/no parallel/.test(t)?o=>PAIRS[o]===0:null}const labelledVisual=(n,t)=>({type:"quadrilateral",shapes:[{shape:n,...t?{label:t}:{}}],show_parallel_marks:!0}
71),whichLabelled={id:"GEO.P6.M05.which-labelled.v1",microSkillId:"GEO.P6.M05",forms:["shape_property"],generate(n){const t=n.visualData;if(!t||t.type!=="quadrilateral")return null;const e=t.shapes;if(!e||e.length<2)return null;const s=shapeMatcher(n.stem);if(!s)return null;const o=e.filter(f=>s(f.shape));if(o.length!==1)return null;const r=o[0];if((r.label??"").trim()!==n.correctAnswer.trim())return null;const a=PAIRS[r.shape],i=e.filter(f=>f!==r),l=i.slice(0,2),u=i.slice(2),d=u.length>0?` ${u.map(f=>`${f.label??"another shape"} has ${pairWord(PAIRS[f.shape])}`).join("; ")}.`:"",m=l[0],h=l[1];return buildExample({generatorId:this.id,question:n,title:"Which shape matches?",computed:{pairs:a,answer:a},steps:[{visual_data:m?labelledVisual(m.shape,m.label):quadVisual(r.shape),animation:"compare_shape",caption:m?`${m.label??"This shape"} has ${pairWord(PAIRS[m.shape])} — not the answer.`:"Check each shape's parallel sides.",spoken:m?`Shape ${m.label??""} has ${pairWord(PAIRS[m.shape])}, so it does not match.`:"Look at each shape and count its pairs of parallel sides."},{visual_data:h?labelledVisual(h.shape,h.label):labelledVisual(r.shape,r.label),animation:"compare_shape",caption:h?`${h.label??"This shape"} has ${pairWord(PAIRS[h.shape])} — not the answer either.`:`${r.label??"This shape"} has ${pairWord(a)}.`,spoken:h?`Shape ${h.label??""} has ${pairWord(PAIRS[h.shape])}, so that is ruled out too.${d}`:`The matching shape has ${pairWord(a)}.${d}`},{visual_data:labelledVisual(r.shape,r.label),animation:"name",caption:`${r.label??"This shape"} has ${pairWord(a)} — that's the answer.`,spoken:`Shape ${r.label??"here"} has ${pairWord(a)}, matching what the question asks for — it is the ${NAME[r.shape].toLowerCase()}.`}]})}},howManyPairs={id:"GEO.P6.M05.how-many-pairs.v1",microSkillId:"GEO.P6.M05",forms:["shape_property"],generate(n){const t=normaliseStem(n.stem);if(!/how many pairs of parallel/i.test(t))return null;const e=NAMED.find(r=>r.re.test(t));if(!e)return null;const s=PAIRS[e.shape],o=`${s} ${s===1?"pair":"pairs"}`;return n.correctAnswer.trim()!==o?null:buildExample({generatorId:this.id,question:n,title:"Count the parallel pairs",computed:{pairs:s,answer:s},steps:[{visual_data:quadVisual(e.shape),animation:"trace_pair1",caption:`Look at a ${NAME[e.shape].toLowerCase()}.`,spoken:`Parallel sides run the same way and never meet, however far they go. To answer, we look at a ${NAME[e.shape].toLowerCase()} and find its parallel sides.`},{visual_data:quadVisual(e.shape),animation:"trace_pair2",caption:"Mark each parallel pair.",spoken:"We check the opposite sides. If a pair runs the same way, like train tracks, we mark it as a parallel pair."},{visual_data:quadVisual(e.shape),animation:"name",caption:`A ${NAME[e.shape].toLowerCase()} has ${pairWord(s)}.`,spoken:`Now we count the pairs we marked. That count is the answer: a ${NAME[e.shape].toLowerCase()} has ${pairWord(s)}.`}]})}},GEO_P6_M05_GENERATORS=[name,whichLabelled,howManyPairs,odd,isA],w=numberToWords,TOTAL=360,WORD_NUM={one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10};function pointVisual(n,t){return{type:"angle_diagram",baseline:"point",angles:[...n.map(s=>({degrees:s,label:`${s}°`})),{degrees:t,unknown:!0}]}}const eq=n=>({type:"equation_display",terms:n});function missingSteps(n,t,e){return[{animation:"show",caption:"Angles meet at a point.",spoken:"These angles meet all the way around a point. One of them is missing."},{animation:"sum_knowns",caption:"Angles at a point add to 360°.",spoken:`Angles around a point always add up to three hundred and sixty degrees. Add the known angles: they make ${w(e)}.`},{animation:"reveal",caption:`360° − ${e}° = ${t}°.`,spoken:`Take ${w(e)} away from three hundred and sixty. The missing angle is ${w(t)} degrees.`}]}const missing={id:"GEO.P6.M06.missing.v1",microSkillId:"GEO.P6.M06",forms:["symbolic_numeric_entry"],generate(n){const t=n.visualData;if(!t||t.type!=="angle_diagram")return null;const e=t.angles,s=e.filter(i=>!i.unknown).map(i=>i.degrees);if(s.length!==e.length-1)return null;const o=s.reduce((i,l)=>
71i+l,0),r=TOTAL-o;if(r<=0||Number(n.correctAnswer)!==r)return null;const a=missingSteps(s,r,o);return buildExample({generatorId:this.id,question:n,title:"Find the missing angle at a point",computed:{knownSum:o,missing:r,total:TOTAL,answer:r},steps:[{visual_data:pointVisual(s,r),...a[0]},{visual_data:pointVisual(s,r),...a[1]},{visual_data:pointVisual(s,r),...a[2]}]})}};function unresolvedPointVisual(n,t){return{type:"angle_diagram",baseline:"point",angles:Array.from({length:n},()=>({degrees:t,label:"?"}))}}const equalN={id:"GEO.P6.M06.equal-n.v1",microSkillId:"GEO.P6.M06",forms:["symbolic_numeric_entry"],generate(n){const e=normaliseStem(n.stem).match(/(\d+|one|two|three|four|five|six|seven|eight|nine|ten)\s+equal\s+(?:angles|sections)/i);if(!e)return null;const s=WORD_NUM[e[1].toLowerCase()]??Number(e[1]);if(!Number.isInteger(s)||s<2||s>12||TOTAL%s!==0)return null;const o=TOTAL/s;return Number(n.correctAnswer)!==o?null:buildExample({generatorId:this.id,question:n,title:"Equal angles at a point",computed:{n:s,each:o,total:TOTAL,answer:o},steps:[{visual_data:unresolvedPointVisual(s,o),animation:"show",caption:`${s} equal angles fill the point.`,spoken:`${cap$4(w(s))} equal angles meet all the way around the point. None of them are labelled yet.`},{visual_data:eq([TOTAL,"÷",s,"=","?"]),animation:"share",caption:`Share 360° equally between the ${s} angles.`,spoken:`The whole turn is three hundred and sixty degrees, shared equally between the ${w(s)} angles.`},{visual_data:eq([TOTAL,"÷",s,"=",o]),animation:"reveal",caption:`360° ÷ ${s} = ${o}°, so each angle is ${o}°.`,spoken:`Three hundred and sixty divided by ${w(s)} is ${w(o)}. So each angle is ${w(o)} degrees.`}]})}},twoEqual={id:"GEO.P6.M06.two-equal.v1",microSkillId:"GEO.P6.M06",forms:["symbolic_numeric_entry"],generate(n){const t=normaliseStem(n.stem);if(!/equal/i.test(t))return null;const e=t.match(/(one|two|three|four|five|six|seven|eight|nine|ten)\s+of\s+them\s+are\s+equal/i),s=t.match(/(?:is|fourth is|third is)\s+(\d+)/i)??t.match(/(\d+)\s*°/);if(!e||!s)return null;const o=WORD_NUM[e[1].toLowerCase()],r=Number(s[1]);if(!o||!Number.isInteger(r))return null;const a=TOTAL-r;if(a<=0||a%o!==0)return null;const i=a/o;return Number(n.correctAnswer)!==i?null:buildExample({generatorId:this.id,question:n,title:"Equal angles plus one given",computed:{given:r,k:o,each:i,rest:a,total:TOTAL,answer:i},steps:[{visual_data:{type:"angle_diagram",baseline:"point",angles:[{degrees:r,label:`${r}°`},...Array.from({length:o},()=>({degrees:i,unknown:!0}))]},animation:"show",caption:`One angle is ${r}°; the rest are equal.`,spoken:`One angle is ${w(r)} degrees. The other ${w(o)} are all equal — but we don't know their size yet.`},{visual_data:eq([TOTAL,"−",r,"=",a]),animation:"subtract",caption:`360° − ${r}° = ${a}°.`,spoken:`Take the ${w(r)} away from the full turn: three hundred and sixty minus ${w(r)} is ${w(a)}, shared between the equal angles.`},{visual_data:eq([a,"÷",o,"=",i]),animation:"reveal",caption:`${a}° ÷ ${o} = ${i}°, so each equal angle is ${i}°.`,spoken:`${cap$4(w(a))} divided by ${w(o)} is ${w(i)}. So each equal angle is ${w(i)} degrees.`}]})}},possible={id:"GEO.P6.M06.possible.v1",microSkillId:"GEO.P6.M06",forms:["misconception_contrast_probe"],generate(n){const e=[...normaliseStem(n.stem).matchAll(/(\d+)\s*°/g)].map(l=>Number(l[1]));if(e.length<2)return null;const s=e.reduce((l,u)=>l+u,0),o=n.correctAnswer.toLowerCase();if(!(s===TOTAL?isAffirmative(n.correctAnswer)||/exactly/.test(o):s>TOTAL?/more|too big|too large/.test(o):/less|too small/.test(o)))return null;const a=s===TOTAL?`${s}° = 360°: yes.`:s>TOTAL?`${s}° > 360°: too big.`:`${s}° < 360°: too small.`,i=[];return e.forEach((l,u)=>{u>0&&i.push("+"),i.push(`${l}°`)}),buildExample({generatorId:this.id,question:n,title:"Can these angles meet at a point?",computed:{sum:s,total:TOTAL,answer:s},steps:[{visual_data:eq(i),animation:"show",caption:"Add the angles together.",spoken:"To meet at a point, the angles must add up to exactly three hundred and sixty degrees. Add them up."},{visual_data:eq([...i,"=",`${s}°`]),animation:"sum",caption:`They total ${s}°.`,spoken:`Altogether they make ${w(s)} degrees.`},{visual_data:eq([`${s}°`,s===TOTAL?"=":s>TOTAL?">":"<","360°"]),animation:"verdict",caption:a,spoken:`Three hundred and sixty is a full turn, so ${w(s)} ${s===TOTAL?"works":"does not work"}.`}]})}},fact={id:"GEO.P6.M06.fact.v1",microSkillId:"GEO.P6.M06",forms:["fact_symbolic_retrieval"],generate(n){return n.correctAnswer.trim().replace(/\s+/g,"")!=="360°"?null:buildExample({generatorId:this.id,question:n,title:"Angles around a point",computed:{total:TOTAL,answer:TOTAL},steps:[{visual_data:unresolvedPointVisual(1,90),animation:"show",caption:"One angle meets at a point.",spoken:"Angles that meet all the way around a single point make one complete turn."},{visual_data:unresolvedPointVisual(4,90),animation:"sweep",caption:"They go all the way around — a full turn.",spoken:"Keep going all the way around the point. One complete turn is three hundred and sixty degrees."},{visual_data:eq(["angles at a point","=","360°"]),animation:"reveal",caption:"They always add up to 360°.",spoken:"So the angles around a point always add up to three hundred and sixty degrees."}]})}};function cap$4(n){return n.charAt(0).toUpperCase()+n.slice(1)}const GEO_P6_M06_GENERATORS=[missing,equalN,twoEqual,possible,fact];function tokenise(n){const t=n.replace(/−/g,"-").replace(/x/gi,"×").replace(/\*/g,"×").replace(/\//g,"÷"),e=[];let s=0;for(;s<t.length;){const o=t[s];if(o===" "){s++;continue}if(/[0-9]/.test(o)){let r=s;for(;r<t.length&&/[0-9]/.test(t[r]);)r++;e.push(Number(t.slice(s,r))),s=r;continue}if("+-×÷()".includes(o)){e.push(o),s++;continue}return null}return e.length?e:null}function termsToString(n){return n.map(String).join(" ").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").replace(/\s+/g," ").trim()}function evalTokens(n){let t=0;const e=()=>n[t],s=()=>{let i=o();if(i==null)return null;for(;e()==="+"||e()==="-";){const l=n[t++],u=o();if(u==null)return null;i=l==="+"?i+u:i-u}return i},o=()=>{let i=r();if(i==null)return null;for(;e()==="×"||e()==="÷";){const l=n[t++],u=r();if(u==null)return null;if(l==="÷"){if(u===0||i%u!==0)return null;i=i/u}else i=i*u}return i},r=()=>{const i=e();if(i==="("){t++;const l=s();return l==null||n[t]!==")"?null:(t++,l)}return typeof i=="number"?(t++,i):null},a=s();return t===n.length?a:null}function evalExpr(n){const t=tokenise(n);return t?evalTokens(t):null}function reduceOneBracket(n){let t=-1;for(let e=0;e<n.length;e++)if(n[e]==="(")t=e;else if(n[e]===")"){const s=n.slice(t+1,e),o=evalTokens(s);return o==null?null:{terms:[...n.slice(0,t),o,...n.slice(e+1)],focus:[t]}}return null}function reduceAllMulDiv(n){const t=[];let e=!1;for(let o=0;o<n.length;o++){const r=n[o];if((r==="×"||r==="÷")&&typeof t[t.length-1]=="number"&&typeof n[o+1]=="number"){const a=t.pop(),i=n[++o];if(r==="÷"){if(i===0||a%i!==0)return null;t.push(a/i)}else t.push(a*i);e=!0}else t.push(r)}if(!e)return null;const s=t.map((o,r)=>
71typeof o=="number"?r:-1).filter(o=>o>=0);return{terms:t,focus:s}}function reduceFirstMulDiv(n){for(let t=0;t<n.length;t++){const e=n[t];if((e==="×"||e==="÷")&&typeof n[t-1]=="number"&&typeof n[t+1]=="number"){const s=n[t-1],o=n[t+1];let r;if(e==="÷"){if(o===0||s%o!==0)return null;r=s/o}else r=s*o;return{terms:[...n.slice(0,t-1),r,...n.slice(t+2)],focus:[t-1]}}}return null}function hasBracket(n){return n.includes("(")}function hasMulDiv(n){return n.includes("×")||n.includes("÷")}function countMulDiv(n){return n.filter(t=>t==="×"||t==="÷").length}function bodmasStates(n){const t=tokenise(n);if(!t||evalTokens(t)==null)return null;const e=hasBracket(t),s=countMulDiv(t);let o,r,a;if(e){if(t.filter(d=>d==="(").length!==1)return null;const i=reduceOneBracket(t);if(!i)return null;const l=t.indexOf("("),u=t.indexOf(")");if(a=Array.from({length:u-l+1},(d,m)=>l+m),o=i,hasBracket(i.terms)||hasMulDiv(i.terms)&&(i.terms.includes("+")||i.terms.includes("-"))||(r=hasMulDiv(i.terms)?reduceAllMulDiv(i.terms):reduceAllAddSub(i.terms),!r))return null}else if(s>=1&&(t.includes("+")||t.includes("-"))){if(o=reduceAllMulDiv(t),!o||(a=mulDivFocus(t),r=reduceAllAddSub(o.terms),!r))return null}else if(s>=2){if(o=reduceFirstMulDiv(t),!o||(a=firstMulDivFocus(t),r=hasMulDiv(o.terms)?reduceFirstMulDiv(o.terms):null,!r))return null}else return null;return r.terms.length!==1||typeof r.terms[0]!="number"?null:{states:[t,o.terms,r.terms],firstFocus:a,midFocus:o.focus}}function reduceAllAddSub(n){const t=evalTokens(n);return t==null?null:{terms:[t],focus:[0]}}function mulDivFocus(n){const t=[];for(let e=0;e<n.length;e++)(n[e]==="×"||n[e]==="÷")&&t.push(e-1,e,e+1);return[...new Set(t)].filter(e=>e>=0&&e<n.length).sort((e,s)=>e-s)}function firstMulDivFocus(n){for(let t=0;t<n.length;t++)if(n[t]==="×"||n[t]==="÷")return[t-1,t,t+1];return[]}const OP_WORD$1={"+":"plus","-":"minus","×":"times","÷":"divided by"};function speakExpr(n){const t=[];return n.forEach(e=>{const s=String(e);typeof e=="number"?t.push(numberToWordsLong(e)):s==="("?t.push("open bracket"):s===")"?t.push("close bracket"):t.push(OP_WORD$1[s]??s)}),t.join(" ").replace(/open bracket /g,"open bracket ").replace(/ close bracket/g," close bracket")}const GEN_ID$2="MD.P5.M05.order_of_operations.v1";function eqVisual$1(n,t){return{type:"equation_display",terms:n,...t&&t.length?{highlight:t}:{}}}function candidateExprs(n){const t=normaliseStem(n),e=[],s=o=>{o&&o[1]&&e.push(o[1].trim())};return s(t.match(/\bworked out\s+(.+?)\s+and got\b/i)),s(t.match(/\bfor\s+([\d+\-×÷()\s]+?)(?:$|[^\d+\-×÷()\s])/i)),s(t.match(/(?:work out|works out)\s+(.+?)\s+(?:using|for)\b/i)),s(t.match(/\btypes\s+([\d+\-×÷()\s]+)/i)),s(t.match(/(?:work out|works out)\s+([\d+\-×÷()\s]+)/i)),e}function extractExpr(n,t){for(const e of candidateExprs(n)){if(!tokenise(e))continue;if(evalExpr(e)===t)return e}return null}function collectNums(n,t){const e={answer:t};let s=0;for(const o of n)for(const r of o)typeof r=="number"&&(e[`n${s++}`]=r);return e}function hasOp(n,t){return n.some(e=>typeof e=="string"&&t.includes(e))}function normMinus(n){return n.replace(/−/g,"-").trim()}function resolveSingleBracket(n){if(n.filter(a=>a==="(").length!==1)return null;const t=n.indexOf("("),e=n.indexOf(")");if(e<=t)return null;const s=evalExpr(termsToString(n.slice(t+1,e)));if(s==null)return null;const o=[...n.slice(0,t),s,...n.slice(e+1)],r=Array.from({length:e-t+1},(a,i)=>t+i);return{after:o,span:r}}function bracketFallback(n,t){const e=tokenise(n);if(!e)return null;const s=resolveSingleBracket(e);if(!s||evalExpr(termsToString(s.after))!==t)return null;const r=hasOp(s.after,["×","÷"])&&hasOp(s.after,["+","-"]);return{orig:e,bracketSpan:s.span,after:s.after,mixed:r}}function decidingPlaceAnim(n,t){const e=Math.max(String(Math.abs(n)).length,String(Math.abs(t)).length,1),s=String(Math.abs(n)).padStart(e,"0"),o=String(Math.abs(t)).padStart(e,"0");for(let r=0;r<e;r++)if(s[r]!==o[r]){const a=e-1-r;return a>=2?"highlight_hundreds":a===1?"highlight_tens":"highlight_ones"}return"highlight_ones"}const isWhole=n=>n!=null&&Number.isInteger(n)&&n>=0&&n<=999999;function reduceMulDivRuns(n){const t=[],e=[];let s=0;for(;s<n.length;){const o=n[s];if(typeof o!="number"){t.push(o),s++;continue}const r=[o];let a=s;for(;(n[a+1]==="×"||n[a+1]==="÷")&&typeof n[a+2]=="number";)r.push(n[a+1],n[a+2]),a+=2;if(r.length>1){const i=evalExpr(termsToString(r));if(!isWhole(i))return null;e.push({terms:r,value:i}),t.push(i)}else t.push(o);s=a+1}return{reduced:t,runs:e}}function saySums(n){return n.map(t=>`${capitalised(speakExpr(t.terms))} is ${numberToWordsLong(t.value)}.`).join(" ")}function leftToRight(n){if(typeof n[0]!="number")return null;let t=n[0];for(let e=1;e+1<n.length;e+=2){const s=n[e],o=n[e+1];if(typeof o!="number")return null;if(s==="+")t+=o;else if(s==="-")t-=o;else if(s==="×")t*=o;else if(s==="÷")t/=o;else return null}return t}function bracketPart(n){const t=n.indexOf("("),e=n.indexOf(")");if(t<0||e<=t)return null;const s=n.slice(t+1,e),o=evalExpr(termsToString(s));return isWhole(o)?{terms:s,value:o}:null}const orderOfOperations={id:GEN_ID$2,microSkillId:"MD.P5.M05",forms:["symbolic_calculation","misconception_contrast_probe"],generate(n){const t=answerInt(n);if(t==null)return null;const e=extractExpr(n.stem,t);if(!e)return null;const s=bodmasStates(e);if(s){const[d,m]=s.states,h=termsToString(m),f=d.includes("("),g=hasOp(d,["+","-"]),b=f?"Brackets first.":"× and ÷ before + and −.",x=collectNums(s.states,t);let y=null;if(f){const L=bracketPart(d);y=L?[L]:null}else if(g)y=reduceMulDivRuns(d)?.runs??null;else{const L=typeof m[0]=="number"?m[0]:null;y=isWhole(L)?[{terms:d.slice(0,3),value:L}]:null}const E=y&&y.length?f?`Inside the brackets, ${speakExpr(y[0].terms)} is ${numberToWordsLong(y[0].value)}. `:`${saySums(y)} `:"",k=y&&y.length?y.map(L=>speakExpr(L.terms)).join(" and "):"",T=y&&y.length?`${y.map(L=>`${termsToString(L.terms)} = ${L.value}`).join(", ")}, so ${h}.`:"",A=T&&T.length<=72?T:`Now: ${h}.`,N=!f&&g?leftToRight(d):null,S=N!=null&&isWhole(N)&&N!==t?` Working straight from left to right would give ${numberToWordsLong(N)}, which is wrong.`:"",M=f?`Brackets tell us which part to do first, so we work out ${k||"the brackets"}
71 before anything else.`:g?`Multiplying and dividing come before adding and subtracting, so we work out ${k||"those parts"} first.${S}`:`When a calculation only multiplies and divides, we work from left to right, so we start with ${k||"the first step"}.`;return buildExample({generatorId:GEN_ID$2,question:n,title:n.stem,computed:x,steps:[{visual_data:eqVisual$1(d,s.firstFocus),animation:"highlight_first",caption:b,spoken:M},{visual_data:eqVisual$1(m,s.midFocus),animation:"collapse",caption:A,spoken:`${E}That leaves ${speakExpr(m)}.`},{visual_data:eqVisual$1([...m,"=",t],[m.length+1]),animation:"finish",caption:`${h} = ${t}.`,spoken:`${capitalised(speakExpr(m))} is ${numberToWordsLong(t)}. So the answer is ${numberToWordsLong(t)}.`}]})}const o=bracketFallback(e,t);if(!o)return null;const r=termsToString(o.after),a=bracketPart(o.orig),i=o.mixed?reduceMulDivRuns(o.after):null,l=i&&i.runs.length?`${saySums(i.runs)} Then ${speakExpr(i.reduced)} is ${numberToWordsLong(t)}. So the answer is ${numberToWordsLong(t)}.`:`${capitalised(speakExpr(o.after))} is ${numberToWordsLong(t)}. So the answer is ${numberToWordsLong(t)}.`,u=o.mixed?"Multiplying and dividing still come before adding and subtracting.":hasOp(o.after,["×","÷"])?"Then work through the multiplying and dividing.":"Then work through the adding and subtracting.";return buildExample({generatorId:GEN_ID$2,question:n,title:n.stem,computed:collectNums([o.orig,o.after],t),steps:[{visual_data:eqVisual$1(o.orig,o.bracketSpan),animation:"highlight_bracket",caption:"Brackets first.",spoken:`Brackets tell us which part to do first, so we work out ${a?speakExpr(a.terms):"the brackets"} before anything else.`},{visual_data:eqVisual$1(o.after,o.after.map((d,m)=>m)),animation:"collapse_rest",caption:a&&`${termsToString(a.terms)} = ${a.value}, so ${r}.`.length<=72?`${termsToString(a.terms)} = ${a.value}, so ${r}.`:`Now: ${r}.`,spoken:`${a?`Inside the brackets, ${speakExpr(a.terms)} is ${numberToWordsLong(a.value)}. `:""}That leaves ${speakExpr(o.after)}. ${u}`},{visual_data:eqVisual$1([...o.after,"=",t],[o.after.length+1]),animation:"finish",caption:`${r} = ${t}.`,spoken:l}]})}},BRACKET_PLACEMENT=/where should (?:the )?brackets? go so that\s+([0-9+\-×x÷/\s]+?)\s*=\s*(\d+)\s*\??\s*$/i,bracketPlacement={id:"MD.P5.M05.bracket_placement.v1",microSkillId:"MD.P5.M05",forms:["symbolic_choice"],generate(n){const e=normaliseStem(n.stem).match(BRACKET_PLACEMENT);if(!e)return null;const s=tokenise(e[1].trim());if(!s||s.length!==5)return null;const[o,r,a,i,l]=s;if(typeof o!="number"||typeof r!="string"||typeof a!="number"||typeof i!="string"||typeof l!="number")return null;const u=Number(e[2]),d=evalExpr(`${o} ${r} ${a} ${i} ${l}`);if(d==null||d===u)return null;const m=`(${o} ${r} ${a}) ${i} ${l}`,h=`${o} ${r} (${a} ${i} ${l})`,f=evalExpr(m),g=evalExpr(h);let b,x,y;if(f===u)b=m,x=f,y=!0;else if(g===u)b=h,x=g,y=!1;else return null;if(normMinus(n.correctAnswer)!==normMinus(b))return null;const E=tokenise(b);if(!E)return null;const k=resolveSingleBracket(E)?.span??[],T=tokenise(`${o} ${r} ${a} ${i} ${l}`);return T?buildExample({generatorId:this.id,question:n,title:n.stem,computed:{n1:o,n2:a,n3:l,noBracket:d,correctVal:x,target:u,answer:x},steps:[{visual_data:eqVisual$1([...T,"=",d]),animation:"no_brackets",caption:`Without brackets: ${termsToString(T)} = ${d}.`,spoken:`Without any brackets, we follow the usual order of operations: ${speakExpr(T)} comes to ${numberToWordsLong(d)}, not ${numberToWordsLong(u)}.`},{visual_data:eqVisual$1(E,k),animation:"place_brackets",caption:`Put brackets around the ${y?"first":"last"} two: ${b}.`,spoken:`Brackets are worked out first, so putting them around the ${y?"first":"last"} two numbers changes which part happens first.`},{visual_data:eqVisual$1([...E,"=",x],[E.length+1]),animation:"finish",caption:`${b} = ${x}.`,spoken:`Now the bracket goes first: ${speakExpr(E)} is ${numberToWordsLong(x)}. That matches ${numberToWordsLong(u)}, so that is where the brackets go.`}]}):null}},EXPR_COMPARE=/which is greater:\s*(.+?)\s+or\s+(.+?),\s*or are they equal\?/i,expressionCompare={id:"MD.P5.M05.expression_compare.v1",microSkillId:"MD.P5.M05",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(EXPR_COMPARE);if(!e)return null;
71const s=e[1].trim(),o=e[2].trim(),r=tokenise(s),a=tokenise(o);if(!r||!a)return null;const i=evalExpr(s),l=evalExpr(o);if(i==null||l==null)return null;let u;i===l?u="equal":i>l?u="a":u="b";const d=normMinus(n.correctAnswer);if(!(u==="equal"&&/equal/i.test(d)||u!=="equal"&&d===normMinus(u==="a"?s:o)))return null;const h=resolveSingleBracket(r),f=resolveSingleBracket(a),g=u==="equal"?"=":u==="a"?">":"<";return buildExample({generatorId:this.id,question:n,title:`Which is greater: ${s} or ${o}?`,computed:{valA:i,valB:l,answer:u==="b"?l:i},steps:[{visual_data:eqVisual$1([...r,"=",i],h?h.span:[]),animation:"expr_a",caption:`${s} = ${i}.`,spoken:`First, ${speakExpr(r)}. ${h?"Brackets go first, so":"Following the order of operations,"} that comes to ${numberToWordsLong(i)}.`},{visual_data:eqVisual$1([...a,"=",l],f?f.span:[]),animation:"expr_b",caption:`${o} = ${l}.`,spoken:`Now, ${speakExpr(a)}. ${f?"Brackets go first, so":"There are no brackets here, so any multiplying or dividing happens first, and"} that comes to ${numberToWordsLong(l)}.`},{visual_data:{type:"comparison",left:i,right:l,symbol:g},animation:`conclude ${decidingPlaceAnim(i,l)}`,caption:u==="equal"?`Equal: both are ${i}.`:`${i} ${g} ${l}.`,spoken:u==="equal"?`Both expressions come to ${numberToWordsLong(i)}, so they are equal.`:`${capitalised(numberToWordsLong(u==="a"?i:l))} is greater than ${numberToWordsLong(u==="a"?l:i)}, so the ${u==="a"?"first":"second"} expression is greater.`}]})}},SHOP_BUNDLE=/one shop sells (\d+) [a-z]+ at £(\d+) each plus a £(\d+) [a-z]+\. another sells (\d+) [a-z]+ that each cost £(\d+) plus £(\d+)\. which comes to more\?/i,bundleCompare={id:"MD.P5.M05.bundle_compare.v1",microSkillId:"MD.P5.M05",forms:["comparison_statement"],generate(n){const e=normaliseStem(n.stem).match(SHOP_BUNDLE);if(!e)return null;const[s,o,r,a,i,l]=e.slice(1).map(Number);if([s,o,r,a,i,l].some(y=>!Number.isInteger(y)||y<1))return null;const u=s*o+r,d=a*(i+l);let m;u===d?m="equal":u>d?m="a":m="b";const h=n.correctAnswer.trim().toLowerCase();if(!(m==="equal"&&/same/.test(h)||m==="a"&&/pack/.test(h)&&/bag/.test(h)||m==="b"&&/bundle/.test(h)))return null;const g=[s,"×",o,"+",r],b=[a,"×","(",i,"+",l,")"],x=m==="equal"?"=":m==="a"?">":"<";return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{n1:s,price1:o,bag:r,exprA:u,n2:a,priceBundle:i,plusBundle:l,exprB:d,answer:m==="b"?d:u},steps:[{visual_data:eqVisual$1([...g,"=",u],[0,1,2]),animation:"expr_a",caption:`Packs + bag: ${s} × ${o} + ${r} = ${u}.`,spoken:`First, the packs and the bag: multiplying comes before adding, so ${numberToWordsLong(s)} times ${numberToWordsLong(o)} is the cost of the packs, then the ${numberToWordsLong(r)} pound bag is added once. That comes to ${numberToWordsLong(u)}.`},{visual_data:eqVisual$1([...b,"=",d],[2,3,4,5,6]),animation:"expr_b",caption:`Bundles: ${a} × (${i} + ${l}) = ${d}.`,spoken:`Now the bundles: the bracket is worked out first, ${numberToWordsLong(i)} plus ${numberToWordsLong(l)}, and that whole amount is multiplied by ${numberToWordsLong(a)}. That comes to ${numberToWordsLong(d)}.`},{visual_data:{type:"comparison",left:u,right:d,symbol:x},animation:`conclude ${decidingPlaceAnim(u,d)}`,caption:m==="equal"?`Equal: both are ${u}.`:`${u} ${x} ${d}.`,spoken:m==="equal"?`Both come to ${numberToWordsLong(u)}, so they cost the same.`:`${capitalised(numberToWordsLong(Math.max(u,d)))} is more than ${numberToWordsLong(Math.min(u,d))}, so the ${m==="a"?"packs and the bag":`${numberToWordsLong(a)} bundles`} cost more.`}]})}},MD_P5_M05_GENERATORS=[orderOfOperations,bracketPlacement,expressionCompare,bundleCompare],GEN_ID$1="MD.P5.M06.long_division.v1";function ldivVisual(n,t,e={}){return{type:"long_division",dividend:n,divisor:t,...e}}function inRangeDivisor(n){return Number.isInteger(n)&&n>=10&&n<=99}function inRangeDividend(n){return Number.isInteger(n)&&n>=100&&n<=99999}function symbolicDiv(n){const t=normaliseStem(n).match(/(\d{3,5})\s*÷\s*(\d{2})\b/);if(!t)return null;const e=Number(t[1]),s=Number(t[2]);return!inRangeDividend(e)||!inRangeDivisor(s)?null:{dividend:e,divisor:s,quotient:Math.floor(e/s),remainder:e%s}}function contextDiv(n){const t=(normaliseStem(n).match(/\d+/g)??[]).map(Number),e=t.filter(inRangeDivisor),s=t.filter(inRangeDividend);if(e.length!==1||s.length===0)return null;const o=e[0],r=Math.max(...s);return r<=o?null:{dividend:r,divisor:o,quotient:Math.floor(r/o),remainder:r%o}}function speakDiv(n){return`${numberToWordsLong(n.dividend)}
71 divided by ${numberToWordsLong(n.divisor)}`}function cyclesOf(n){const t=String(n.dividend).split("").map(Number);let e=0,s=0;for(;e<t.length&&s<n.divisor;)s=s*10+t[e++];const o=[];let r=null;for(;;){const a=Math.floor(s/n.divisor),i=a*n.divisor;if(o.push({current:s,brought:r,q:a,product:i,rem:s-i}),e>=t.length)break;r=t[e++],s=(s-i)*10+r}return o}const W=numberToWordsLong,timesWord=n=>n===1?"once":n===2?"twice":`${W(n)} times`;function saidCycle(n,t,e=!1){const s=n.brought===null?"":n.current===n.brought?`Nothing was left, so bringing down the ${W(n.brought)} makes just ${W(n.current)}. `:`Bring down the ${W(n.brought)} to make ${W(n.current)}. `;if(n.q===0)return`${s}${capitalised(W(t))} does not go into ${W(n.current)}, so we write a zero on top.`;const o=n.rem===0?"leaves nothing":`leaves ${W(n.rem)}`,r=e?" again":"";return`${s}${capitalised(W(t))} goes into ${W(n.current)} ${timesWord(n.q)}${r}. ${capitalised(W(n.q))} times ${W(t)} is ${W(n.product)}${r}, and ${W(n.current)} take away ${W(n.product)} ${o}.`}function firstStepSpoken(n,t){const e=String(n.dividend),s=String(t.current).length,o=s>1?Number(e.slice(0,s-1)):null;return`Long division works one digit at a time: divide, multiply, take away, then bring the next digit down. ${o!==null?`${capitalised(W(n.divisor))} does not go into ${W(o)}, so we start with ${W(t.current)}.`:`We start with ${W(t.current)}.`} ${saidCycle(t,n.divisor)}`}function restSpoken(n,t,e){if(e.length===0)return"There are no more digits to bring down.";const s=new Set([t.current]);return e.map(o=>{const r=s.has(o.current);return s.add(o.current),saidCycle(o,n.divisor,r)}).join(" ")}function build(n,t,e,s,o,r={},a={}){const i=cyclesOf(t),[l,...u]=i;return buildExample({generatorId:GEN_ID$1,question:n,title:n.stem,computed:{answer:e,dividend:t.dividend,divisor:t.divisor,quotient:t.quotient,remainder:t.remainder,first_part:l.current,first_digit:l.q,...r},steps:[{visual_data:ldivVisual(t.dividend,t.divisor),animation:"divide_first",caption:`${t.dividend} ÷ ${t.divisor}: start with ${l.current}, it holds ${l.q} lots of ${t.divisor}.`,spoken:firstStepSpoken(t,l)},{visual_data:ldivVisual(t.dividend,t.divisor),animation:"bring_down",caption:"Bring each digit down in turn.",spoken:restSpoken(t,l,u)},{visual_data:ldivVisual(t.dividend,t.divisor,a),animation:"finish",caption:s,spoken:o}]})}const MD_P5_M06_GENERATORS=[{id:GEN_ID$1,microSkillId:"MD.P5.M06",forms:["symbolic_calculation","symbolic_choice","context_calculation","misconception_contrast_probe"],generate(n){const t=n.mathematicalForm??"",e=normaliseStem(n.stem);if(t==="symbolic_choice"){const r=symbolicDiv(e);if(!r)return null;const a=n.correctAnswer.match(/(\d+)\s*r\s*(\d+)/i);return!a||Number(a[1])!==r.quotient||Number(a[2])!==r.remainder?null:build(n,r,r.quotient,`${r.divi
71dend} ÷ ${r.divisor} = ${r.quotient} r ${r.remainder}.`,`Reading the top gives ${numberToWordsLong(r.quotient)}, and ${numberToWordsLong(r.remainder)} is left over. So the answer is ${numberToWordsLong(r.quotient)} remainder ${numberToWordsLong(r.remainder)}.`)}if(t==="context_calculation"){const r=answerInt(n);if(r==null)return null;const a=contextDiv(e);return a?r===a.quotient?build(n,a,r,a.remainder>0?`${a.quotient} whole — ignore the ${a.remainder} left.`:`${a.dividend} ÷ ${a.divisor} = ${a.quotient}.`,a.remainder>0?`Reading the top, ${speakDiv(a)} is ${numberToWordsLong(a.quotient)} remainder ${numberToWordsLong(a.remainder)}. Only whole groups count, so the answer is ${numberToWordsLong(a.quotient)}.`:`Reading the top, ${speakDiv(a)} is exactly ${numberToWordsLong(a.quotient)}, with nothing left over. So the answer is ${numberToWordsLong(a.quotient)}.`,{},a.remainder>0?{final_badge_override:`${a.dividend} ÷ ${a.divisor} = ${a.quotient} r ${a.remainder} → ${a.quotient} full`}:{}):a.remainder>0&&r===a.quotient+1?build(n,a,r,`${a.quotient} r ${a.remainder} → round up to ${a.quotient+1}.`,`Reading the top, ${speakDiv(a)} is ${numberToWordsLong(a.quotient)} remainder ${numberToWordsLong(a.remainder)}. The leftover still needs one more, so round up to ${numberToWordsLong(a.quotient+1)}. So the answer is ${numberToWordsLong(a.quotient+1)}.`,{rounded:a.quotient+1},{final_badge_override:`${a.quotient+1} = ${a.quotient} full + 1 more for the remainder`}):null:null}const s=answerInt(n);if(s==null)return null;const o=symbolicDiv(e);return!o||o.remainder!==0||o.quotient!==s?null:build(n,o,s,`${o.dividend} ÷ ${o.divisor} = ${o.quotient}.`,`Reading the digits along the top, ${speakDiv(o)} is ${numberToWordsLong(o.quotient)}, with nothing left over.`)}}],GEN_ID="MD.P5.M07.multistep.v1";function eqVisual(n,t){return{type:"equation_display",terms:n,...t&&t.length?{highlight:t}:{}}}const OP_WORD={"-":"take away","+":"add","÷":"divided by","×":"multiplied by"},SECOND_STEP_WHY={"-":"Now some are taken away, so the second step is to take away","+":"Now more are added on, so the second step is to add on","÷":"Now the total is split into equal groups, so the second step is to divide by","×":"Now we need that total several times over, so the second step is to multiply by"};function answerNoun(n){const t=n.match(/\bhow many ((?:full|good) )?([a-z]+)/i);return t&&!/^(does|do|is|are|was|were|will|can|more|of|bundles?)$/i.test(t[2])?`${t[1]??""}${t[2].toLowerCase()}`:/£/.test(n)&&/how much money|in pounds/i.test(n)?"pounds":null}const multistepTwoOp={id:GEN_ID,microSkillId:"MD.P5.M07",forms:["context_calculation"],generate(n){const t=answerInt(n);if(t==null)return null;const e=(normaliseStem(n.stem).match(/\d+/g)??[]).map(Number);if(e.length!==3)return null;const[s,o,r]=e;if(s<1||o<1||r<1)return null;const a=s*o,i=[];if(a-r===t&&i.push({op:"-",floored:!1}),a+r===t&&i.push({op:"+",floored:!1}),r>0&&Math.floor(a/r)===t&&i.push({op:"÷",floored:a%r!==0}),a*r===t&&i.push({op:"×",floored:!1}),i.length!==1)return null;const{op:l,floored:u}=i[0],d=[a,l,r],m=l==="÷"?a%r:0,h=u?`${a} ÷ ${r} = ${t} r ${m} → ${t} full.`:`${a} ${l} ${r} = ${t}.`,f=t!==1?answerNoun(normaliseStem(n.stem)):null,g=`${numberToWordsLong(t)}${f?` ${f}`:""}`,b=u?`${capitalised(numberToWordsLong(a))} divided by ${numberToWordsLong(r)} is ${numberToWordsLong(t)} remainder ${numberToWordsLong(m)}. Only whole, full groups count, so the answer is ${g}.`:`${capitalised(numberToWordsLong(a))} ${OP_WORD[l]} ${numberToWordsLong(r)} is ${numberToWordsLong(t)}. So the answer is ${g}.`;return buildExample({generatorId:GEN_ID,question:n,title:n.stem,computed:{answer:t,a:s,b:o,c:r,product:a,remainder:m},steps:[{visual_data:eqVisual([s,"×",o,l,r],[0,1,2]),animation:"highlight_first",caption:`Multiply first: ${s} × ${o}.`,spoken:`This problem has two steps. The first two numbers make equal groups, so we multiply them to find the total: ${numberToWordsLong(s)} times ${numberToWordsLong(o)}.`},{visual_data:eqVisual(d,[0]),animation:"collapse",caption:`${s} × ${o} = ${a}.`,spoken:`${capitalised(numberToWordsLong(s))} times ${numberToWordsLong(o)} is ${numberToWordsLong(a)}. ${SECOND_STEP_WHY[l]} ${numberToWordsLong(r)}.`},{visual_data:eqVisual([...d,"=",t],[4]),animation:"finish",caption:h,spoken:b}]})}};function divisionChunks(n,t,e){const s=String(e).replace(/0/g,"").length;if(t<10||s<2)return null;
71const o=10**(String(e).length-1),r=Math.floor(e/o)*o,a=e-r,i=t*r,l=n-i,u=t*a;return{bigQ:r,bigProd:i,left:l,restQ:a,restProd:u,remainder:l-u}}function productSplit(n,t){if(n<100||t<11||t%10===0||t>99)return null;const e=Math.floor(t/10)*10,s=t-e;return{tens:e,ones:s,tensProd:n*e,onesProd:n*s}}const multistepThreeOp={id:"MD.P5.M07.multistep-three.v1",microSkillId:"MD.P5.M07",forms:["context_calculation"],generate(n){const t=answerInt(n);if(t==null)return null;const e=(normaliseStem(n.stem).match(/\d+/g)??[]).map(Number);if(e.length!==4)return null;const[s,o,r,a]=e;if(s<1||o<1||r<1||a<1)return null;const i=s*o,l=i-r,u=l>=0&&Math.floor(l/a)===t?{afterSubtract:l,remainder:l%a}:null,d=s*o*r,m=Math.floor(d/a)===t?{product3:d,remainder:d%a}:null;if([u,m].filter(Boolean).length!==1)return null;if(u){const{afterSubtract:y,remainder:E}=u,k=E>0?`${t} r ${E}`:t,T=productSplit(s,o),A=divisionChunks(y,a,t),N=T?{tens:T.tens,ones:T.ones,tensProd:T.tensProd,onesProd:T.onesProd}:{},S=A?{bigQ:A.bigQ,bigProd:A.bigProd,left:A.left,restQ:A.restQ,restProd:A.restProd}:{},M=A?`Divide by ${a}: ${a} × ${A.bigQ} = ${A.bigProd}, ${a} × ${A.restQ} = ${A.restProd} → ${t}${E>0?` r ${E}, ${t} full`:""}.`:E>0?`Finally divide: ${y} ÷ ${a} = ${t} r ${E} → ${t} full.`:`Finally divide: ${y} ÷ ${a} = ${t}.`,L=A?` Take ${numberToWordsLong(a)} lots in big chunks. ${capitalised(numberToWordsLong(a))} times ${numberToWordsLong(A.bigQ)} is ${numberToWordsLong(A.bigProd)}, which leaves ${numberToWordsLong(A.left)}. ${capitalised(numberToWordsLong(a))} times ${numberToWordsLong(A.restQ)} is ${numberToWordsLong(A.restProd)}${E>0?`, which leaves ${numberToWordsLong(E)}`:", with nothing left"}. So that is ${numberToWordsLong(A.bigQ)} and ${numberToWordsLong(A.restQ)}, which makes ${numberToWordsLong(t)}.`:"";return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{answer:t,a:s,b:o,d:r,e:a,product:i,afterSubtract:y,remainder:E,...N,...S},steps:[{visual_data:eqVisual([s,"×",o,"=",i],[0,1,2]),animation:"multiply",caption:T?`Multiply first: ${s} × ${T.tens} = ${T.tensProd}, ${s} × ${T.ones} = ${T.onesProd}, total ${i}.`:`Multiply first: ${s} × ${o} = ${i}.`,spoken:T?`This problem takes three steps. First combine the two amounts by multiplying ${numberToWordsLong(s)} by ${numberToWordsLong(o)}. Split ${numberToWordsLong(o)} into ${numberToWordsLong(T.tens)} and ${numberToWordsLong(T.ones)}: ${numberToWordsLong(s)} times ${numberToWordsLong(T.tens)} is ${numberToWordsLong(T.tensProd)}, and ${numberToWordsLong(s)} times ${numberToWordsLong(T.ones)} is ${numberToWordsLong(T.onesProd)}. Together that makes ${numberToWordsLong(i)}.`:`This problem takes three steps. First combine the two amounts by multiplying: ${numberToWordsLong(s)} times ${numberToWordsLong(o)} is ${numberToWordsLong(i)}.`},{visual_data:eqVisual([i,"-",r,"=",y],[0,1,2]),animation:"subtract",caption:`Then subtract: ${i} − ${r} = ${y}.`,spoken:`Next, take away the ${numberToWordsLong(r)} that don't count, leaving ${numberToWordsLong(y)}.`},{visual_data:eqVisual([y,"÷",a,"=",k],[0,1,2]),animation:"divide_floor",caption:M,spoken:E>0?`Finally, divide ${numberToWordsLong(y)} by ${numberToWordsLong(a)}.${L} ${capitalised(numberToWordsLong(y))} divided by ${numberToWordsLong(a)} is ${numberToWordsLong(t)} remainder ${numberToWordsLong(E)}. Only whole, full groups count, so the answer is ${numberToWordsLong(t)}.`:`Finally, divide ${numberToWordsLong(y)} by ${numberToWordsLong(a)}.${L} ${capitalised(numberToWordsLong(y))} divided by ${numberToWordsLong(a)} is ${numberToWordsLong(t)}, with nothing left over. So the answer is ${numberToWordsLong(t)}.`}]})}const{product3:f,remainder:g}=m,b=s*o,x=g>0?`${t} r ${g}`:t;return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{answer:t,a:s,b:o,c:r,d:a,firstProduct:b,product:f,remainder:g},steps:[{visual_data:eqVisual([s,"×",o],[0,1,2]),animation:"multiply_first",caption:`Multiply the first two: ${s} × ${o} = ${b}.`,spoken:`This problem takes three steps. First multiply two of the amounts: ${numberToWordsLong(s)} times ${numberToWordsLong(o)} is ${numberToWordsLong(b)}.`},{visual_data:eqVisual([b,"×",r,"=",f],[0,1,2]),animation:"multiply_third",caption:`Then multiply by ${r}: ${b} × ${r} = ${f}.`,spoken:`Then multiply by the third amount, ${numberToWordsLong(r)}, to get ${numberToWordsLong(f)} altogether.`},{visual_data:eqVisual([f,"÷",a,"=",x],[0,1,2]),animation:"divide_floor",caption:g>
710?`Finally divide: ${f} ÷ ${a} = ${t} r ${g} → ${t} full.`:`Finally divide: ${f} ÷ ${a} = ${t}.`,spoken:g>0?`Finally, divide by ${numberToWordsLong(a)}. ${capitalised(numberToWordsLong(f))} divided by ${numberToWordsLong(a)} is ${numberToWordsLong(t)} remainder ${numberToWordsLong(g)}. Only whole, full groups count, so the answer is ${numberToWordsLong(t)}.`:`Finally, divide by ${numberToWordsLong(a)}. ${capitalised(numberToWordsLong(f))} divided by ${numberToWordsLong(a)} is ${numberToWordsLong(t)}, with nothing left over. So the answer is ${numberToWordsLong(t)}.`}]})}},PACK_PATTERN_HOLDS=/holds (\d+) ([a-z]+) for £(\d+(?:\.\d+)?)/gi,PACK_PATTERN_COSTS=/a (\d+)-pack of (?:the same )?([a-z]+) costs £(\d+(?:\.\d+)?)/gi;function extractPacks(n){const t=[...n.matchAll(PACK_PATTERN_HOLDS)];if(t.length===2)return t.map((s,o)=>({count:Number(s[1]),noun:s[2],pence:Math.round(Number(s[3])*100),label:o===0?"Pack A":"Pack B",spokenLabel:o===0?"Pack A":"Pack B"}));const e=[...n.matchAll(PACK_PATTERN_COSTS)];return e.length===2?e.map(s=>({count:Number(s[1]),noun:s[2],pence:Math.round(Number(s[3])*100),label:`The ${s[1]}-pack`,spokenLabel:`The ${numberToWordsLong(Number(s[1]))}-pack`})):null}function decidingPlaceAnimP(n,t){const e=Math.max(String(Math.abs(n)).length,String(Math.abs(t)).length,1),s=String(Math.abs(n)).padStart(e,"0"),o=String(Math.abs(t)).padStart(e,"0");for(let r=0;r<e;r++)if(s[r]!==o[r]){const a=e-1-r;return a>=2?"highlight_hundreds":a===1?"highlight_tens":"highlight_ones"}return"highlight_ones"}const unitPriceCompare={id:"MD.P5.M07.unit-price.v1",microSkillId:"MD.P5.M07",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem),e=extractPacks(t);if(!e)return null;const[s,o]=e;if(s.count<1||o.count<1||s.pence%s.count!==0||o.pence%o.count!==0)return null;const r=s.pence/s.count,a=o.pence/o.count;if(r===a)return null;const i=t.match(/per ([a-z]+)\s*\??\s*$/i),u=i?i[1]:(b=>b.endsWith("s")?b.slice(0,-1):b)(s.noun),d=b=>b.endsWith("s")?b:`${b}s`,m=r<a?"a":"b",h=m==="a"?s:o;return n.correctAnswer.trim().toLowerCase()===h.label.toLowerCase()?buildExample({generatorId:this.id,question:n,title:n.stem,computed:{countA:s.count,penceA:s.pence,perA:r,countB:o.count,penceB:o.pence,perB:a,answer:m==="a"?r:a},steps:[{visual_data:eqVisual([s.pence,"÷",s.count,"=",r]),animation:"pack_a",caption:`${s.label}: ${s.pence}p ÷ ${s.count} = ${r}p each.`,spoken:`${s.spokenLabel} costs ${numberToWordsLong(s.pence)} pence for ${numberToWordsLong(s.count)} ${d(u)}. Divide to find the cost of one ${u}: ${numberToWordsLong(s.pence)} divided by ${numberToWordsLong(s.count)} is ${numberToWordsLong(r)} pence each.`},{visual_data:eqVisual([o.pence,"÷",o.count,"=",a]),animation:"pack_b",caption:`${o.label}: ${o.pence}p ÷ ${o.count} = ${a}p each.`,spoken:`${o.spokenLabel} costs ${numberToWordsLong(o.pence)} pence for ${numberToWordsLong(o.count)} ${d(u)}. Again we divide to find the cost of one: ${numberToWordsLong(o.pence)} divided by ${numberToWordsLong(o.count)} is ${numberToWordsLong(a)} pence each.`},{visual_data:{type:"comparison",left:r,right:a,symbol:r<a?"<":">"},animation:`conclude ${decidingPlaceAnimP(r,a)}`,caption:`${r}p vs ${a}p — ${h.label} is better value.`,spoken:`${capitalised(numberToWordsLong(Math.min(r,a)))} pence is less than ${numberToWordsLong(Math.max(r,a))} pence, so ${h.spokenLabel} is the better value per ${u}.`}]}):null}},FEE_THEN_DIVIDE=/costs? £(\d+) each\. a group pays £(\d+) in total, which already includes a £(\d+) (?:booking )?fee\. how many .+ did they buy\?/i,feeThenDivide={id:"MD.P5.M07.fee-then-divide.v1",microSkillId:"MD.P5.M07",forms:["context_calculation"],generate(n){const t=answerInt(n);if(t==null)return null;const s=normaliseStem(n.stem).match(FEE_THEN_DIVIDE);if(!s)return null;const[o,r,a]=s.slice(1).map(Number);if(o<1||r<1||a<0||a>=r)return null;const i=r-a;if(i<=0||i%o!==0)return null;const l=i/o;return l!==t?null:buildExample({generatorId:this.id,question:n,title:n.stem,computed:{answer:t,price:o,total:r,fee:a,remaining:i,tickets:l},steps:[{visual_data:eqVisual([r,"-",a],[0,1,2]),animation:"highlight_first",caption:`Take off the fee first: ${r} − ${a}.`,spoken:`This problem takes two steps. The booking fee is already included in the total, so take it away first: ${numberToWordsLong(r)}
71 minus ${numberToWordsLong(a)}.`},{visual_data:eqVisual([r,"-",a,"=",i],[0,1,2]),animation:"subtract",caption:`${r} − ${a} = ${i}.`,spoken:`${capitalised(numberToWordsLong(r))} minus ${numberToWordsLong(a)} is ${numberToWordsLong(i)}. Now divide what is left by the price of one ticket.`},{visual_data:eqVisual([i,"÷",o,"=",l],[0,1,2]),animation:"finish",caption:`${i} ÷ ${o} = ${l}.`,spoken:`${capitalised(numberToWordsLong(i))} divided by ${numberToWordsLong(o)} is ${numberToWordsLong(l)}. So they bought ${numberToWordsLong(l)} tickets.`}]})}},RATE_PATTERN=/(\d+) ([a-z]+) for £(\d+(?:\.\d+)?)/gi,rateCompare={id:"MD.P5.M07.rate-compare.v1",microSkillId:"MD.P5.M07",forms:["comparison_statement"],generate(n){const t=normaliseStem(n.stem);if(!/for each|per pound|more .* for/i.test(t))return null;const e=[...t.matchAll(RATE_PATTERN)];if(e.length!==2)return null;const[s,o]=e,r=Number(s[1]),a=Number(s[3]),i=Number(o[1]),l=Number(o[3]);if(r<1||i<1||!Number.isInteger(a)||!Number.isInteger(l)||a<1||l<1)return null;const u=r*l,d=i*a;if(u===d)return null;const m=u>d?"a":"b",h=`${r} ${s[2]} for £${a}`,f=`${i} ${o[2]} for £${l}`,g=n.correctAnswer.trim();if(!(m==="a"?g===h:g===f))return null;const x=a*l;return buildExample({generatorId:this.id,question:n,title:n.stem,computed:{countA:r,priceA:a,countB:i,priceB:l,commonSpend:x,scaledA:u,scaledB:d,answer:m==="a"?u:d},steps:[{visual_data:eqVisual([r,"×",l,"=",u]),animation:"deal_a",caption:`Scale to £${x}: ${h} → ${r} × ${l} = ${u}.`,spoken:`To compare fairly, scale both deals up to the same amount of money spent. From the first deal, ${numberToWordsLong(r)} times ${numberToWordsLong(l)} is ${numberToWordsLong(u)}.`},{visual_data:eqVisual([i,"×",a,"=",d]),animation:"deal_b",caption:`Scale to £${x}: ${f} → ${i} × ${a} = ${d}.`,spoken:`From the second deal, for the same money, ${numberToWordsLong(i)} times ${numberToWordsLong(a)} is ${numberToWordsLong(d)}.`},{visual_data:{type:"comparison",left:u,right:d,symbol:u>d?">":"<"},animation:`conclude ${decidingPlaceAnimP(u,d)}`,caption:`${u} vs ${d} — ${m==="a"?h:f} gives more.`,spoken:`${capitalised(numberToWordsLong(Math.max(u,d)))} is more than ${numberToWordsLong(Math.min(u,d))} for the same money, so the ${m==="a"?"first":"second"} deal gives more for each pound.`}]})}},MD_P5_M07_GENERATORS=[multistepTwoOp,multistepThreeOp,unitPriceCompare,feeThenDivide,rateCompare];function round6(n){return Math.round(n*1e6)/1e6}function decStr(n){return String(round6(n))}function signedWords(n){if(n===0)return"zero";const t=Math.abs(round6(n)),e=Number.isInteger(t)?numberToWordsLong(t):decimalToWords(decStr(t));return n<0?`negative ${e}`:e}function shiftPoint(n,t){const e=typeof n=="number"?decStr(n):n.trim(),s=e.startsWith("-"),o=s?e.slice(1):e,r=o.indexOf("."),a=r<0?o:o.slice(0,r),i=r<0?"":o.slice(r+1),l=a+i;let u=a.length+t,d;u<=0?d="0."+"0".repeat(-u)+l:u>=l.length?d=l+"0".repeat(u-l.length):d=l.slice(0,u)+"."+l.slice(u);const m=round6(Number(d));return(s&&m!==0?"-":"")+decStr(m)}function dpOf(n){const t=decStr(Math.abs(n)),e=t.indexOf(".");return e<0?0:t.length-e-1}const STEP_CHOICES=[1,2,5,10,20,25,50,100,200,500];function integerWindow(n,t=1){if(n.length===0)return null;const e=Math.min(...n)-t,s=Math.max(...n)+t;for(const o of STEP_CHOICES){const r=Math.floor(e/o)*o,a=Math.ceil(s/o)*o;if(a<=r)continue;if((a-r)/o+1<=20)return{min:r,max:a,step:o===1?void 0:o}}return null}function decimalWindow(n){const t=round6(Math.floor(10*Math.min(...n))/10),e=round6(Math.ceil(10*Math.max(...n))/10),s=round6(e-t);if(s<=0)return null;const o=s<=.2?.01:.1;return Math.round(s/o)+1>21?null:{min:t,max:e,step:o}}const words=n=>numberToWordsLong(n);function dimsFromVisual(n){const t=n.visualData;if(!t||t.type!=="volume_cuboid")return null;const e=t,s=Math.round(e.length),o=Math.round(e.width),r=Math.round(e.height);return[s,o,r].every(a=>Number.isInteger(a)&&a>=1&&a<=40)?{L:s,W:o,H:r}:null}function dimsFromStem(n){const t=n.match(/(\d+)\s*cm long,?\s*(\d+)\s*cm wide,?\s*(?:and\s*)?(\d+)\s*cm (?:high|deep|tall)/i);if(t)return{L:+t[1],W:+t[2],H:+t[3]};const e=n.match(/(\d+)\s*cm by (\d+)\s*cm by (\d+)\s*cm/i);if(e)return{L:+e[1],W:+e[2],H:+e[3]};const s=n.match(/edges? of (\d+)\s*cm|(\d+)\s*cm on every side/i);if(s){const o=Number(s[1]??s[2]);
71return{L:o,W:o,H:o}}return null}function volumeVisual(n,t={}){return{type:"volume_cuboid",length:n.L,width:n.W,height:n.H,unit:"cm",show_unit_cubes:!0,...t}}const volumeCalc={id:"MEA.P7.M05.volume.v1",microSkillId:"MEA.P7.M05",forms:["volume_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=Number(n.correctAnswer.trim());if(!Number.isInteger(t))return null;const e=dimsFromVisual(n)??dimsFromStem(normaliseStem(n.stem));if(!e)return null;const{L:s,W:o,H:r}=e,a=s*o,i=s*o*r;if(t===a&&/layer/i.test(n.stem)&&t!==i)return buildExample({generatorId:this.id,question:n,title:"How many cubes in one layer?",computed:{L:s,W:o,base:a,answer:a},steps:[{visual_data:volumeVisual(e),animation:"base_layer",caption:`The bottom layer is ${s} by ${o}.`,spoken:`The bottom layer of cubes is ${words(s)} long and ${words(o)} wide.`},{visual_data:volumeVisual(e),animation:"stack_layers",caption:"Count the cubes in that one layer.",spoken:"We count the cubes in just this one flat layer, before stacking any more on top."},{visual_data:volumeVisual(e),animation:"conclude",caption:`${s} × ${o} = ${a} cubes.`,spoken:`The layer is a rectangle of cubes, so we multiply its length by its width: ${words(s)} times ${words(o)} is ${words(a)}, so one layer holds ${words(a)} cubes.`}]});if(/identical (?:cuboid|box).*(?:on top|stacked)|stacked .*identical/i.test(n.stem)&&t===i*2){const l=i*2;return buildExample({generatorId:this.id,question:n,title:`Two identical ${s} by ${o} by ${r} cuboids stacked`,computed:{L:s,W:o,H:r,volume:i,doubled:l,two:2,answer:l},steps:[{visual_data:volumeVisual(e),animation:"base_layer",caption:`One cuboid: ${s} × ${o} × ${r} = ${i}.`,spoken:`First we find the volume of just one cuboid: ${words(s)} times ${words(o)} times ${words(r)} is ${words(i)} cubic centimetres.`},{visual_data:{type:"equation_display",terms:[i,"×",2,"=","__"]},animation:"double_cuboids",caption:`An identical cuboid sits on top: ${i} × 2.`,spoken:"An identical cuboid is placed on top, so the total volume is twice the volume of one cuboid."},{visual_data:{type:"equation_display",terms:[i,"×",2,"=",l]},animation:"conclude",caption:`${i} × 2 = ${l} cm³.`,spoken:`Twice the volume means times two: ${words(i)} times two is ${words(l)}, so the two cuboids together hold ${words(l)} cubic centimetres.`}]})}if(/half (?:its |the )?depth|filled to half/i.test(n.stem)){const l=round6(r/2),u=round6(s*o*l);return u!==t?null:buildExample({generatorId:this.id,question:n,title:`A pool filled to half its ${r} cm depth`,computed:{L:s,W:o,H:r,halfH:l,halfVolume:u,answer:u},steps:[{visual_data:{type:"equation_display",terms:[r,"÷",2,"=",l]},animation:"half_depth",caption:`Water depth: half of ${r} is ${l}.`,spoken:`The water is only half the full depth, so we halve the depth first: half of ${words(r)} is ${words(l)}.`},{visual_data:volumeVisual({L:s,W:o,H:l}),animation:"base_layer",caption:`Water fills ${s} × ${o} × ${l}.`,spoken:"The water forms its own smaller cuboid, using the water's depth, not the full depth of the pool."},{visual_data:{type:"equation_display",terms:[s,"×",o,"×",l,"=",u]},animation:"conclude",caption:`${s} × ${o} × ${l} = ${u} cm³.`,spoken:`Multiplying with the water's depth, ${words(s)} times ${words(o)} times ${words(l)} is ${words(u)}, so the water's volume is ${words(u)} cubic centimetres.`}]})}return t!==i?null:buildExample({generatorId:this.id,question:n,title:`Volume of a ${s} by ${o} by ${r} cuboid`,computed:{L:s,W:o,H:r,base:a,volume:i,answer:i},steps:[{visual_data:volumeVisual(e),animation:"base_layer",caption:`One layer: ${s} × ${o} = ${a}.`,spoken:`Fill the bottom layer with centimetre cubes. It is ${words(s)} long and ${words(o)} wide, so that layer holds ${words(s)} times ${words(o)}, which is ${words(a)} cubes.`},{visual_data:volumeVisual(e),animation:"stack_layers",caption:`Stack ${r} layers of ${a}.`,spoken:`Now stack that layer up to the full height. There are ${words(r)} identical layers, one on top of another.`},{visual_data:volumeVisual(e),animation:"conclude",caption:`${s} × ${o} × ${r} = ${i}
71 cm³.`,spoken:`So the volume is length times width times height: ${words(s)} times ${words(o)} times ${words(r)}, which is ${words(i)} cubic centimetres.`}]})}},inverse={id:"MEA.P7.M05.inverse.v1",microSkillId:"MEA.P7.M05",forms:["inverse_volume_missing_dimension"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=Number(n.correctAnswer.trim());if(!Number.isInteger(t))return null;const e=normaliseStem(n.stem),s=e.match(/volume of (\d+)\s*cm3?|volume of (\d+)\s*cubic/i),o=s?Number(s[1]??s[2]):NaN;if(!Number.isInteger(o))return null;const r=(e.match(/(\d+)\s*cm/gi)??[]).map(d=>parseInt(d,10)).filter(d=>d!==o);if(r.length<2)return null;const[a,i]=r,l=a*i;if(l===0)return null;const u=round6(o/l);return u!==t?null:buildExample({generatorId:this.id,question:n,title:`Find the missing dimension of a ${o} cm³ cuboid`,computed:{v:o,a,b:i,base:l,missing:u,answer:u},steps:[{visual_data:{type:"equation_display",terms:[a,"×",i,"=",l]},animation:"base_area",caption:`The base layer is ${a} × ${i} = ${l}.`,spoken:`To find a missing length, we start with the layer we know. The base layer is ${words(a)} times ${words(i)}, which is ${words(l)} cubes in one layer.`},{visual_data:{type:"equation_display",terms:[o,"÷",l,"=","__"]},animation:"divide",caption:`How many ${l}-cube layers make ${o}?`,spoken:`Volume is layers stacked up. So to find how many layers there are, we divide the whole volume, ${words(o)} cubic centimetres, by one layer of ${words(l)}.`},{visual_data:{type:"equation_display",terms:[o,"÷",l,"=",u]},animation:"write_answer",caption:`That is ${u} layers.`,spoken:`Working it out, ${words(o)} divided by ${words(l)} is ${words(u)}. That is ${words(u)} layers, so the missing length is ${words(u)} centimetres.`}]})}},compare$2={id:"MEA.P7.M05.compare.v1",microSkillId:"MEA.P7.M05",forms:["comparison_statement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=[...t.matchAll(/(\d+)\s*cm by (\d+)\s*cm by (\d+)\s*cm/gi)].map(h=>({L:+h[1],W:+h[2],H:+h[3]})),s=t.match(/will (\d+) .*cubes fit/i);if(s&&e.length===1){const h=Number(s[1]),f=e[0],g=f.L*f.W*f.H,b=g>=h,x=n.correctAnswer.trim().toLowerCase();if(b&&!x.startsWith("yes")||!b&&!x.startsWith("no"))return null;const y=n.correctAnswer.match(/holds (?:only )?(\d+)/i);return!y||Number(y[1])!==g?null:buildExample({generatorId:this.id,question:n,title:`Will ${h} cubes fit?`,computed:{n:h,vol:g,answer:g},steps:[{visual_data:volumeVisual(f),animation:"base_layer",caption:`The box is ${f.L} × ${f.W} × ${f.H}.`,spoken:"We work out how many centimetre cubes the box holds by multiplying its three sides."},{visual_data:{type:"equation_display",terms:[f.L,"×",f.W,"×",f.H,"=",g]},animation:"multiply",caption:`It holds ${g} cubes.`,spoken:`Multiplying the three sides, ${words(f.L)} times ${words(f.W)} times ${words(f.H)} is ${words(g)} cubes.`},{visual_data:{type:"equation_display",terms:[g,b?"≥":"<",h]},animation:"conclude",caption:b?`${g} is enough for ${h}.`:`${g} is not enough for ${h}.`,spoken:`The box holds ${words(g)} cubes, so ${words(h)} ${b?"do fit":"do not fit"}.`}]})}if(e.length!==2)return null;const[o,r]=e,a=o.L*o.W*o.H,i=r.L*r.W*r.H,l=n.correctAnswer.trim().toLowerCase();let u;if(a>i?u="a":i>a?u="b":u="equal",!(u==="a"&&/cuboid a/.test(l)||u==="b"&&/cuboid b/.test(l)||u==="equal"&&/equal/.test(l)))return null;const m=u==="equal"?"equal":u==="a"?"Cuboid A":"Cuboid B";return buildExample({generatorId:this.id,question:n,title:"Which cuboid has the greater volume?",computed:{va:a,vb:i,answer:u==="b"?i:a},steps:[{visual_data:{type:"equation_display",terms:[o.L,"×",o.W,"×",o.H,"=",a]},animation:"volume_a",caption:`Cuboid A: ${o.L} × ${o.W} × ${o.H} = ${a}.`,spoken:`Volume is length times width times height. Cuboid A works out to ${words(a)} cubic centimetres.`},{visual_data:{type:"equation_display",terms:[r.L,"×",r.W,"×",r.H,"=",i]},animation:"volume_b",caption:`Cuboid B: ${r.L} × ${r.W} × ${r.H} = ${i}.`,spoken:`Cuboid B works out to ${words(i)} cubic centimetres. A longer-looking cuboid is not always the bigger one.`},{visual_data:{type:"equation_display",terms:u==="equal"?[a,"=",i]:u==="a"?[a,">",i]:[i,">",a]},animation:"conclude",caption:u==="equal"?`Equal: both are ${a}.`:`${m} is greater.`,spoken:u==="equal"?`Both come to ${words(a)}, so the volumes are equal.`:`${m} has the greater volume.`}]})}},probe$4={id:"MEA.P7.M05.probe.v1",microSkillId:"MEA.P7.M05",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=Number((n.correctAnswer.match(/\d+/)??[])[0]);if(!Number.isInteger(e))return null;const s=dimsFromStem(t);if(!s)return null;const{L:o,W:r,H:a}=s,i=o*r*a;if(/doubl/i.test(t)){const l=i*8;return l!==e?null:buildExample({generatorId:this.id,question:n,title:"What happens to the volume when every side doubles?",computed:{volume:i,doubled:l,dL:o*2,dW:r*2,dH:a*2,answer:l},steps:[{visual_data:{type:"equation_display",terms:[o,"×",r,"×",a,"=",i]},animation:"original",caption:`Original: ${o} × ${r} × ${a} = ${i}.`,spoken:`The original volume is ${words(o)} times ${words(r)} times ${words(a)}, which is ${words(i)}.`},{visual_data:{type:"equation_display",terms:[o*2,"×",r*2,"×",a*2,"=","__"]},animation:"double_sides",caption:`Double every side: ${o*2} × ${r*2} × ${a*2}.`,spoken:"Doubling every side doubles all three lengths at once, so the volume does not just double."},{visual_data:{type:"equation_display",terms:[o*2,"×",r*2,"×",a*2,"=",l]},animation:"write_answer",caption:`That is ${l}, eight times as big.`,spoken:`The new volume is ${words(l)} — eight times the original, because two times two times two is eight.`}]})}return i!==e?null:buildExample({generatorId:this.id,question:n,title:"Volume: multiply the sides, don't add them",computed:{L:o,W:r,H:a,base:o*r,volume:i,answer:i},steps:[{visual_data:volumeVisual(s),animation:"base_layer",caption:`One layer: ${o} × ${r} = ${o*r}.`,spoken:`Adding the sides gives a length, not a volume. Instead we fill a layer: ${words(o)} times ${words(r)} is ${words(o*r)} cubes.`},{visual_data:volumeVisual(s),animation:"stack_layers",caption:`Stack ${a} layers of ${o*r}.`,spoken:`Then we stack ${words(a)} of those layers to fill the whole cuboid.`},{visual_data:{type:"equation_display",terms:[o,"×",r,"×",a,"=",i]},animation:"conclude",caption:`${o} × ${r} × ${a} = ${i}
71 cm³.`,spoken:`So the volume is ${words(o)} times ${words(r)} times ${words(a)}, which is ${words(i)} cubic centimetres.`}]})}},VOLUME_UNIT_ERROR=/works out (?:a |an )?(?:cuboid'?s?|shape'?s?) volume correctly as (\d+),?\s*but writes the unit as cm2/i,unitCorrection={id:"MEA.P7.M05.unit-correction.v1",microSkillId:"MEA.P7.M05",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const e=normaliseStem(n.stem).match(VOLUME_UNIT_ERROR);if(!e)return null;const s=Number(e[1]);return!Number.isInteger(s)||s<1||n.correctAnswer.trim()!==`${s} cm3`?null:buildExample({generatorId:this.id,question:n,title:`Freya's volume is ${s}. Which unit is right?`,computed:{v:s,one:1,answer:s},steps:[{visual_data:{type:"equation_display",terms:[1,"cm","×",1,"cm","=","1 cm²"]},animation:"area_unit",caption:"1 cm × 1 cm = 1 cm² — a flat square, for area.",spoken:"One centimetre times one centimetre makes one square centimetre, a flat square. That measures area, not volume."},{visual_data:{type:"equation_display",terms:[1,"cm","×",1,"cm","×",1,"cm","=","1 cm³"]},animation:"volume_unit",caption:"1 cm × 1 cm × 1 cm = 1 cm³ — a solid cube, for volume.",spoken:"One centimetre times one centimetre times one centimetre makes one cubic centimetre, a solid cube. Volume counts these cubes, not flat squares."},{visual_data:{type:"equation_display",terms:[s,"cm³"]},animation:"conclude",caption:`Freya's volume: ${s} cm³ (not ${s} cm²).`,spoken:`Volume counts cubes, so Freya's volume of ${words(s)} must be written in cubic centimetres: ${words(s)} cubic centimetres, not ${words(s)} square centimetres.`}]})}},MEA_P7_M05_GENERATORS=[volumeCalc,inverse,compare$2,probe$4,unitCorrection];function leadSigned(n){const t=n.trim().match(/-?\d+/);return t?Number(t[0]):null}function stripTimes(n){return n.replace(/\b\d{1,2}:\d{2}\b/g," ")}function line$1(n,t,e,s={}){return{type:"number_line",min:n,max:t,...e?{step:e}:{},marked:[],...s}}function directionOf$1(n){const t=normaliseStem(n).toLowerCase(),e=/\b(greater|greatest|larger|largest|higher|highest|warmer|most|more)\b/.test(t),s=/\b(smaller|smallest|lower|lowest|less|least|colder|coldest|deeper|deepest|best)\b/.test(t);return e&&!s?"max":s&&!e?"min":null}const compare$1={id:"NPV.P7.M03.compare.v1",microSkillId:"NPV.P7.M03",forms:["comparison"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=directionOf$1(n.stem);if(!t)return null;const e=Number(n.correctAnswer.trim());if(!Number.isInteger(e))return null;let s;const o=n.visualData;if(o&&o.type==="comparison"){const d=o;s=[d.left,d.right]}else s=(n.options??[]).map(d=>d.trim().match(/^-?\d+$/)?Number(d.trim()):NaN).filter(d=>Number.isInteger(d));if(s.length<2||s.length>4||new Set(s).size!==s.length||!s.every(d=>Number.isInteger(d)&&Math.abs(d)<=999))return null;const r=t==="max"?Math.max(...s):Math.min(...s);if(r!==e)return null;const a=integerWindow(s,1);if(!a)return null;const i=s[0],l=t==="max"?"greater":"smaller",u={winner:r,answer:r};return s.forEach((d,m)=>u[`v${m}`]=d),buildExample({generatorId:this.id,question:n,title:`Which is ${l}: ${s.join(", ")}?`,computed:u,steps:[{visual_data:line$1(a.min,a.max,a.step,{marked:[i]}),animation:"place_first",caption:`Place ${i} on the line first.`,spoken:`To compare these numbers we find each one on the number line. Let's start with ${signedWords(i)}.`},{visual_data:line$1(a.min,a.max,a.step,{marked:[...s]}),animation:"place_rest",caption:"Place them all, then look left to right.",spoken:"Now every number is on the line. A bigger-looking digit does not mean a bigger number — what matters is how far right it sits."},{visual_data:line$1(a.min,a.max,a.step,{marked:[...s],unknown:r,conclude_label:`${l}: ${r}`}),animation:"reveal",caption:`${r} is ${l}.`,spoken:`The ${l} number is ${signedWords(r)}, because it sits furthest to the ${t==="max"?"right":"left"}.`}]})}},identify$1={id:"NPV.P7.M03.identify.v1",microSkillId:"NPV.P7.M03",forms:["number_line_identify"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.visualData;if(!t||t.type!=="number_line")return null;const e=t,{min:s,max:o}=e,r=e.unknown;if(r==null||!Number.isInteger(r)||Number(n.correctAnswer.trim())!==r||s>0||o<0||o-s>24)return null;const a=Math.abs(r),i=r<0,l={from:0,to:r,label:String(a)};return buildExample({generatorId:this.id,question:n,title:"Which number does the arrow point to?",computed:{target:r,steps:a,answer:r},steps:[{visual_data:line$1(s,o,void 0,{marked:[0]}),animation:"find_zero",caption:"Start at zero.",spoken:"To read the arrow, we start at zero and count along the line to reach it."},{visual_data:line$1(s,o,void 0,{marked:[0],hops:[l]}),animation:"count_along",caption:`Count ${a} to the ${i?"left":"right"}.`,spoken:`Counting ${signedWords(a)} steps to the ${i?"left":"right"} takes us ${i?"below":"above"} zero.`},{visual_data:line$1(s,o,void 0,{marked:[0],hops:[l],unknown:r,conclude_label:`${r}`}),animation:"reveal",caption:`The arrow points to ${r}.`,spoken:`We land on ${signedWords(r)}. Points to the left of zero are negative.`}]})}},RISE=/(rises?|rose|risen|rise|up|pays? in|paid in|warmer|gains?)/,FALL=/(falls?|fell|dives?|dived|drops?|dropped|down|colder|loses?)/;function splitAtZero(n,t,e){return n<0&&t>0||n>0&&t<0?[{from:n,to:0,label:`${e}${Math.abs(0-n)}`},{from:0,to:t,label:`${e}${Math.abs(t-0)}`}]:[{from:n,to:t,label:`${e}${Math.abs(t-n)}`}]}const context$1={id:"NPV.P7.M03.context.v1",microSkillId:"NPV.P7.M03",forms:["context_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=stripTimes(normaliseStem(n.stem)),e=leadSigned(n.correctAnswer);if(e===null)return null;const s=t.match(/from (-?\d+)[^-\d]*?to (-?\d+)/i),o=t.match(/difference between (-?\d+)[^-\d]*?and (-?\d+)/i);let r=null;if((s||o)&&/how many|difference/i.test(t))r=s?[Number(s[1]),Number(s[2])]:[Number(o[1]),Number(o[2])];else if(/difference/i.test(t)){const y=[...t.matchAll(/-?\d+/g)].map(E=>Number(E[0]));y.length===2&&(r=[y[0],y[1]])}if(r){const[y,E]=r,k=Math.abs(E-y);if(k===Math.abs(e)){const T=integerWindow([y,E],1);if(!T)return null;const A=Math.min(y,E),N=Math.max(y,E),S=A<0&&N>0,M=S?[{from:A,to:0,label:`${Math.abs(A)}`},{from:0,to:N,label:`${N}`}]:[{from:A,to:N,label:String(k)}];return buildExample({generatorId:this.id,question:n,title:"How far apart are they?",computed:{x:y,y:E,diff:k,answer:k},steps:[{visual_data:line$1(T.min,T.max,T.step,{marked:[A]}),animation:"mark_start",caption:`Mark ${A} and ${N}.`,spoken:"We find both numbers on the line and measure the gap between them."},{visual_data:line$1(T.min,T.max,T.step,{marked:[A,N],hops:[{from:A,to:N,label:"?"}]}),animation:"measure_gap",caption:S?`Jump up to zero, then on to ${N}.`:"Count the gap between them.",spoken:S?`We jump from ${signedWords(A)} up to zero, then keep going up to ${signedWords(N)}.`:`Counting from ${signedWords(A)} up to ${signedWords(N)} gives the difference.`},{visual_data:line$1(T.min,T.max,T.step,{marked:[A,N],hops:M,conclude_label:S?`${Math.abs(A)} + ${N} = ${k}`:`${k}`}),animation:"reveal",caption:`The difference is ${k}.`,spoken:S?`${cap$3(signedWords(Math.abs(A)))} up to zero, then ${signedWords(N)} more, makes ${signedWords(k)} altogether.`:`The gap is ${signedWords(k)}.`}]})}}const a=leadSigned(t);if(a===null)return null;const i=[],l=/(rises?|rose|risen|up|pays? in|paid in|warmer|gains?|falls?|fell|dives?|dived|drops?|dropped|down|colder|loses?)[^.\d]*?(\d+)/gi;let u;for(;(u=l.exec(t))!==null;){const y=u[1].toLowerCase(),E=Number(u[2]),k=FALL.test(y)&&!RISE.test(y)?-1:RISE.test(y)?1:0;if(k===0)return null;i.push(k*E)}if(i.length===0||i.length>4)return null;const d=[a];for(const y of i)d.push(d[d.length-1]+y);const m=d[d.length-1];if(m!==e)return null;const h=integerWindow(d,1);if(!h)return null;const f=i.flatMap((y,E)=>splitAtZero(d[E],d[E+1],y>=0?"+":"−")),g=f.length>2?f.map((y,E)=>({...y,label_row:E%2})):f,b=i.some((y,E)=>{const k=d[E],T=d[E+1];return k<0&&T>0||k>0&&T<0}),x={start:a,result:m,answer:m};return d.forEach((y,E)=>x[`p${E}`]=y),buildExample({generatorId:this.id,question:n,title:"Count through zero on the line.",computed:x,steps:[{visual_data:line$1(h.min,h.max,h.step,{marked:[a]}),animation:"place_start",caption:`Start at ${a}.`,spoken:`We start at ${signedWords(a)} on the number line.`},{visual_data:line$1(h.min,h.max,h.step,{marked:[a],hops:g}),animation:"jump",caption:g.length===1?"Jump once along the line.":b?"Jump for each change, bridging through zero.":"Jump for each change, one after another.",spoken:`Each rise jumps to the right and each fall jumps to the left${g.length>1?", one jump at a time":""}.`},{visual_data:line$1(h.min,h.max,h.step,{marked:[a],hops:g,unknown:m,conclude_label:`${m}`}),animation:"reveal",caption:`We land on ${m}.`,spoken:`Following the jumps lands us on ${signedWords(m)}.`}]})}},statement$1={id:"NPV.P7.M03.statement.v1",microSkillId:"NPV.P7.M03",forms:["comparison_statement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=Number(n.correctAnswer.trim());if(!Number.isInteger(e))return null;const s=(n.options??[]).map(i=>i.trim().match(/^-?\d+$/)?Number(i.trim()):NaN).filter(i=>Number.isInteger(i));if(s.length<2)return null;const o=t.match(/between (-?\d+) and (-?\d+)/i),r=t.match(/greater than (-?\d+)/i),a=t.match(/less than (-?\d+)/i);if(o){const i=Number(o[1]),l=Number(o[2]),u=Math.min(i,l),d=Math.max(i,l),m=s.filter(b=>b>u&&b<d);if(m.length!==1||m[0]!==e)return null;const h=integerWindow([u,d,...s],1);if(!h)return null;const f=s.filter(b=>b!==e),g=f.map(b=>signedWords(b)).join(", ");return buildExample({generatorId:this.id,question:n,title:`Which number is between ${u} and ${d}?`,computed:{lo:u,hi:d,answer:e},steps:[{visual_data:line$1(h.min,h.max,h.step,{marked:[u,d]}),animation:"mark_bounds",caption:`Mark ${u} and ${d}.`,spoken:`A number between ${signedWords(u)} and ${signedWords(d)} must sit in the gap between them on the line.`},{visual_data:line$1(h.min,h.max,h.step,{marked:[u,d,e],pulse_steps:[1,2],crossed:f}),animation:"test_option",caption:`Plot every choice; ${f.join(", ")} fall outside the gap and are crossed out.`,spoken:`We place every choice on the line. ${cap$3(g)} sit outside the gap, so they are crossed out.`},{visual_data:line$1(h.min,h.max,h.step,{marked:[u,d],pulse_steps:[1,2],unknown:e,conclude_label:`${e}`}),animation:"reveal",caption:`${e} lies between them.`,spoken:`${cap$3(signedWords(e))} sits inside the gap, so it lies between them.`}]})}if(r||a){const i=!!r,l=Number((r??a)[1]),u=s.filter(g=>i?g>l:g<l);
71if(u.length!==1||u[0]!==e)return null;const d=integerWindow([l,...s],1);if(!d)return null;const m=i?"greater":"less",h=s.filter(g=>g!==e),f=h.map(g=>signedWords(g)).join(", ");return buildExample({generatorId:this.id,question:n,title:`Which is ${m} than ${l}?`,computed:{x:l,answer:e},steps:[{visual_data:line$1(d.min,d.max,d.step,{marked:[l]}),animation:"mark_ref",caption:`Mark ${l} on the line.`,spoken:`A number ${m} than ${signedWords(l)} must sit to its ${i?"right":"left"}.`},{visual_data:line$1(d.min,d.max,d.step,{marked:[l,e],pulse_steps:[1,2],crossed:h}),animation:"test_option",caption:`Plot every choice; the ones on the ${i?"left":"right"} are crossed out.`,spoken:`We place every choice. ${cap$3(f)} sit on the wrong side of ${signedWords(l)}, so they are crossed out.`},{visual_data:line$1(d.min,d.max,d.step,{marked:[l],pulse_steps:[1,2],unknown:e,conclude_label:`${e}`}),animation:"reveal",caption:`${e} is ${m} than ${l}.`,spoken:`${cap$3(signedWords(e))} sits to the ${i?"right":"left"}, so it is ${m} than ${signedWords(l)}.`}]})}return null}},ordering$1={id:"NPV.P7.M03.ordering.v1",microSkillId:"NPV.P7.M03",forms:["negative_number_ordering"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.visualData;if(!t||t.type!=="drag_order")return null;const e=t.numbers;if(!Array.isArray(e)||e.length<2||e.length>5)return null;const s=e.filter(g=>typeof g=="number"&&Number.isInteger(g));if(s.length!==e.length||s.some(g=>Math.abs(g)>999)||new Set(s).size!==s.length)return null;const o=t.direction;if(o!=="asc"&&o!=="desc")return null;const r=[...s].sort((g,b)=>o==="asc"?g-b:b-g);if(r.map(String).join(",")!==n.correctAnswer.trim())return null;const a=Math.min(...s)-1,i=Math.max(...s)+1;let l=integerWindow(s,1);if(l&&l.step!==void 0&&!s.every(g=>g%l.step===0)&&(l=i-a<=600?{min:a,max:i}:null),!l)return null;const u=s[0],d=r[0],m=o==="asc"?"lowest":"highest",h=r.map(signedWords).join(", "),f={answer:d};return r.forEach((g,b)=>f[`v${b}`]=g),buildExample({generatorId:this.id,question:n,title:`Put these in order: ${m} first`,computed:f,steps:[{visual_data:line$1(l.min,l.max,l.step,{marked:[u]}),animation:"place_first",caption:`Place ${u} on the line first.`,spoken:`To order these numbers we place each one on the number line, starting with ${signedWords(u)}.`},{visual_data:line$1(l.min,l.max,l.step,{marked:[...s]}),animation:"place_rest",caption:"Place them all, then read across.",spoken:"Now they are all on the line. The one furthest to the left is the lowest; the one furthest right is the highest."},{visual_data:line$1(l.min,l.max,l.step,{marked:[...s],unknown:d,conclude_label:r.map(String).join(", ")}),animation:"read_order",caption:`Read ${o==="asc"?"left to right":"right to left"}.`,spoken:o==="asc"?`Reading left to right gives them in order, lowest first: ${h}.`:`Reading right to left gives them in order, highest first: ${h}.`}]})}};function cap$3(n){return n.charAt(0).toUpperCase()+n.slice(1)}const CLAIM_RE=/says\s+(-?\d+)\s+is\s+(less than|greater than|equal to)\s+(-?\d+)/i,ORDER_CLAIM_RE=/orders? (-?\d+),\s*(-?\d+),?\s*(?:and\s*)?(-?\d+) from (smallest to largest|largest to smallest) as (-?\d+),\s*(-?\d+),\s*(-?\d+)/i,probe$3={id:"NPV.P7.M03.probe.v1",microSkillId:"NPV.P7.M03",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=t.match(ORDER_CLAIM_RE);if(e){const h=[Number(e[1]),Number(e[2]),Number(e[3])];if(!h.every(k=>Number.isInteger(k)))return null;const f=e[4].toLowerCase().startsWith("smallest")?"asc":"desc",g=[...h].sort((k,T)=>f==="asc"?k-T:T-k),b=g.join(", ");if(n.correctAnswer.trim().replace(/\s+/g,"")!==b.replace(/\s+/g,""))return null;const x=integerWindow(h,1);if(!x)return null;const y=f==="asc"?"smallest":"largest",E=g.map(signedWords).join(", ");return buildExample({generatorId:this.id,question:n,title:`Order these, ${y} first`,computed:{v0:h[0],v1:h[1],v2:h[2],answer:g[0]},steps:[{visual_data:line$1(x.min,x.max,x.step,{marked:h}),animation:"show_claim",caption:"A pupil ordered these by comparing the digits, not the positions.",spoken:"Judging by which digit looks bigger does not tell you a negative number's size — only its position on the line does."},{visual_data:line$1(x.min,x.max,x.step,{marked:h,pulse_steps:[1,2]}),animation:"place_rest",caption:"Place them all; further right is bigger.",spoken:"We place every value on the number line and compare where each one actually sits."},{visual_data:line$1(x.min,x.max,x.step,{marked:h,pulse_steps:[1,2],conclude_label:b}),animation:"reveal",caption:`Correct order, ${y}
71 first: ${b}.`,spoken:`Reading the line gives the true order: ${E}.`}]})}const s=t.match(CLAIM_RE);if(!s)return null;const o=Number(s[1]),r=s[2].toLowerCase(),a=Number(s[3]);if(!Number.isInteger(o)||!Number.isInteger(a))return null;const i=o>a?"greater than":o<a?"less than":"equal to",l=n.correctAnswer.trim().match(/^(-?\d+)\s+is\s+(less than|greater than|equal to)\s+(-?\d+)$/i);if(!l||Number(l[1])!==o||Number(l[3])!==a||l[2].toLowerCase()!==i||i===r)return null;const u=integerWindow([o,a],1);if(!u)return null;const d=i==="greater than"?o:i==="less than"?a:o,m=d===o?a:o;return buildExample({generatorId:this.id,question:n,title:`Is ${o} ${r} ${a}?`,computed:{a:o,b:a,answer:d},steps:[{visual_data:line$1(u.min,u.max,u.step,{}),animation:"show_claim",caption:`The claim: ${o} is ${r} ${a}.`,spoken:`Someone says ${signedWords(o)} is ${r} ${signedWords(a)}. That comes from comparing the digits, not the positions — and on a negative line that can be backwards.`},{visual_data:line$1(u.min,u.max,u.step,{marked:[o,a],pulse_steps:[2,3]}),animation:"place_both",caption:`Place ${o} and ${a} on the line.`,spoken:`We place ${signedWords(o)} and ${signedWords(a)} on the number line. Whichever sits further to the right is the greater number.`},{visual_data:line$1(u.min,u.max,u.step,{marked:[o,a],pulse_steps:[2,3],unknown:d,conclude_label:`${o} ${i} ${a}`}),animation:"reveal",caption:`${o} is ${i} ${a}.`,spoken:`${cap$3(signedWords(d))} sits further to the right than ${signedWords(m)}, so ${signedWords(o)} is really ${i} ${signedWords(a)} — not ${r}.`}]})}},NPV_P7_M03_GENERATORS=[compare$1,identify$1,context$1,ordering$1,statement$1,probe$3],POWERS=new Set([10,100,1e3]),noughtWords=n=>decimalToWords(n).replace(/^zero point/,"nought point"),kOf=n=>Math.round(Math.log10(n)),placeWordOf=n=>n===1?"one place":n===2?"two places":"three places",term=n=>Number(n);function acceptedNum(n){const t=n.trim().replace(/[£,]/g,"").replace(/\s*(pounds|kg|g|ml|mm|litres?|cm)\b.*/i,""),e=Number(t);return Number.isFinite(e)?round6(e):null}function shiftChain(n,t,e){const s=[n];for(let o=0;o<t;o++)s.push(term(shiftPoint(s[s.length-1],e?1:-1)));return s}function buildShiftDemo(n,t,e,s,o,r,a,i,l=!1){const u=Math.abs(o),d=o>0,m=d?"left":"right",h=d?"×10":"÷10",f=`${d?"×":"÷"}${10**u}`,g=shiftChain(e,u,d),b=[g[0]];for(let L=1;L<=u;L++)b.push(h),b.push(L===u?"__":g[L]);const x=l?noughtWords(decStr(e)):decimalToWords(decStr(e)),y=l?noughtWords(decStr(r)):decimalToWords(decStr(r)),E=placeWordOf(u),k=d?"no zeros were simply added on":"no zeros were simply taken off — every digit shifts, one place at a time",T=!!a&&a.length>1,A=T?nettingTerms(a):[...s,"=","__"],N=T?`Combine the shifts: ${a.map(L=>`${L.isMul?"×":"÷"}${L.p} = ${Math.abs(L.k)} ${L.isMul?"left":"right"}`).join(", ")}.`:`${f} moves every digit ${E} ${m}.`,S=l?T?`We want to work out a chain of steps on ${x}. Each step in the chain moves the digits its own way — some left, some right. So we combine them into one net move before we shift.`:`We want to work out ${x} ${d?"times":"divided by"} ${numberToWordsLong(10**u)}. Multiplying or dividing by a power of ten shifts every digit — ${E} to the ${m} — and the decimal point stays where it is.`:T?`${cap$2(x)}. Each step in the chain moves the digits its own way — some left, some right. We combine them into one net move before we shift.`:`${cap$2(x)}. Multiplying or dividing by a power of ten shifts every digit — ${E} to the ${m} — and the decimal point stays where it is.`,M={};return T&&a.forEach((L,P)=>M[`chainK${P}`]=Math.abs(L.k)),buildExample({generatorId:t,question:n,title:`What is ${decStr(e)} ${T?s.slice(1).join(" "):f}?`,computed:{a:e,result:r,places:u,answer:r,...M},steps:[{visual_data:{type:"equation_display",terms:A},animation:"show_problem",caption:N,spoken:S},{visual_data:{type:"equation_display",terms:b},animation:"shift_digits",caption:`Shift ${h} each time — count ${u} hop${u===1?"":"s"}.`,spoken:l?`${d?"Multiplying":"Dividing"} by ten once moves every digit one place ${m}. ${u===1?"We only need to do that once.":`We do that ${u===2?"twice":"three times"}, one hop at a time.`}`:u===1?`We shift the digits ${m}
71 by one place.`:`Each ten shifts the digits one place ${m}. We do that ${u===2?"twice":"three times"}, one hop at a time.`},{visual_data:{type:"equation_display",terms:[...s,"=",r]},animation:"write_answer",caption:i?`That gives ${decStr(r)} ${i}.`:`That gives ${decStr(r)}.`,spoken:l?`The digits moved ${E} ${m}, so the answer is ${y}${i?` ${i}`:""}. Notice that ${k}.`:i?`So the answer is ${y} ${i}. The digits moved ${E} ${m} — ${k}.`:`So the answer is ${y}. The digits moved ${E} ${m} — ${k}.`}]})}function trailingUnit(n){const t=n.match(/,?\s*in\s+([a-z]+)\s*\??\s*$/i);return t?t[1].toLowerCase():void 0}function cap$2(n){return n.charAt(0).toUpperCase()+n.slice(1)}const SYMBOLIC_RE=/^work out ([\d.]+)((?: (?:x|×|÷|\/) (?:1000|100|10))+)/i,PROSE_CHAIN_RE=/^(multiply|divide) ([\d.]+) by (1000|100|10),?\s*(?:and\s*)?then\s*(multiply|divide) the result by (1000|100|10)/i;function parseProseChain(n){const t=n.match(PROSE_CHAIN_RE);if(!t)return null;const e=Number(t[2]);if(!Number.isFinite(e))return null;const s=t[1].toLowerCase()==="multiply",o=Number(t[3]),r=t[4].toLowerCase()==="multiply",a=Number(t[5]),i=s?kOf(o):-kOf(o),l=r?kOf(a):-kOf(a);return{a:e,netK:i+l,exprTail:[s?"×":"÷",o,r?"×":"÷",a],ops:[{isMul:s,p:o,k:i},{isMul:r,p:a,k:l}]}}function parseChain(n){const t=n.trim().match(/(x|×|÷|\/) (1000|100|10)/gi);if(!t)return null;let e=0;const s=[],o=[];for(const r of t){const a=r.match(/(x|×|÷|\/) (1000|100|10)/i),i=/x|×/i.test(a[1]),l=Number(a[2]),u=i?kOf(l):-kOf(l);e+=u,s.push(i?"×":"÷",l),o.push({isMul:i,p:l,k:u})}return{netK:e,exprTail:s,ops:o}}function nettingTerms(n){const t=[];return n.forEach((e,s)=>{s===0?e.k<0&&t.push("−"):t.push(e.k>=0?"+":"−"),t.push(Math.abs(e.k))}),t.push("=","__"),t}const symbolic$1={id:"NPV.P7.M04.symbolic.v1",microSkillId:"NPV.P7.M04",forms:["symbolic_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=acceptedNum(n.correctAnswer);if(t===null)return null;const e=normaliseStem(n.stem),s=e.match(SYMBOLIC_RE);let o,r;if(s)o=Number(s[1]),r=parseChain(s[2]);else{const l=parseProseChain(e);o=l?l.a:NaN,r=l}if(!r||!Number.isFinite(o))return null;const a=r.exprTail,i=Number(shiftPoint(o,r.netK));return round6(i)!==t?null:r.netK===0&&r.ops.length>1?buildZeroNetDemo(n,this.id,o,a,r.ops):r.netK===0?null:buildShiftDemo(n,this.id,o,[o,...a],r.netK,i,r.ops,void 0,!0)}};function buildZeroNetDemo(n,t,e,s,o){const r=[e,...s];return buildExample({generatorId:t,question:n,title:`What is ${decStr(e)} ${s.join(" ")}?`,computed:{a:e,result:e,answer:e},steps:[{visual_data:{type:"equation_display",terms:[...r,"=","__"]},animation:"show_problem",caption:"Each × and ÷ shifts the digits its own way.",spoken:`We want to work out a chain of steps on ${noughtWords(decStr(e))}. Multiplying or dividing by a power of ten shifts every digit — some moves go left, some go right.`},{visual_data:{type:"equation_display",terms:nettingTerms(o)},animation:"net_shift",caption:"Combine the shifts: they cancel out.",spoken:"Before we shift anything, we combine every move. The moves to the left and the moves to the right cancel each other out exactly, so the net shift is zero."},{visual_data:{type:"equation_display",terms:[...r,"=",e]},animation:"write_answer",caption:`Net shift is zero, so it stays ${decStr(e)}.`,spoken:`With a net shift of zero, no digit moves at all. So the answer is the number we started with, ${noughtWords(decStr(e))}.`}]})}const eqCompletion={id:"NPV.P7.M04.completion.v1",microSkillId:"NPV.P7.M04",forms:["equation_completion"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=acceptedNum(n.correctAnswer);if(t===null)return null;const e=n.visualData;if(!e||e.type!=="equation_display")return null;const s=e.terms;if(s.length!==5||s[3]!=="=")return null;const o=String(s[1]),r=o==="×"||o.toLowerCase()==="x";if(!r&&!(o==="÷"||o==="/"))return null;const i=s.indexOf("?");let l,u,d;if(i===2){if(!POWERS.has(t)||(l=Number(s[0]),u=Number(s[4]),!Number.isFinite(l)||!Number.isFinite(u)||l===0)||round6(r?u/l:l/u)!==t)return null;d=kOf(t);const h=Number(r?shiftPoint(l,d):shiftPoint(l,-d));return round6(h)!==round6(u)?null:buildCompletionDemo(n,this.id,l,u,t,d,r)}if(i===0){const m=Number(s[2]),h=Number(s[4]);if(!POWERS.has(m)||!Number.isFinite(h))return null;d=kOf(m);const f=Number(r?shiftPoint(h,-d):shiftPoint(h,d));return round6(f)!==t?null:buildFirstOperandDemo(n,this.id,f,m,h,d,r)}return null}};function buildCompletionDemo(n,t,e,s,o,r,a){const i=a?"left":"right",l=placeWordOf(r),u=decimalToWords(decStr(e)),d=decimalToWords(decStr(s)),m=decimalToWords(decStr(o)),h=shiftChain(e,r,a),f=[h[0]];for(let g=1;g<h.length;g++)f.push("→"),f.push(h[g]);return buildExample({generatorId:t,question:n,title:`${decStr(e)} ${a?"×":"÷"} ? = ${decStr(s)}`,computed:{a:e,b:s,answer:o,k:r},steps:[{visual_data:{type:"equation_display",terms:[e,a?"×":"÷","__","=",s]},animation:"show_problem",caption:"Which power of ten fills the box?",spoken:`${cap$2(u)} ${a?"grows to":"shrinks to"} ${d}. We work out how far the digits moved.`},{visual_data:{type:"equation_display",terms:f},animation:"count_shift",caption:`Count the hops: ${l} ${i}.`,spoken:`We shift the digits one place at a time from ${u} to ${d}: that is ${l} to the ${i}.`},{visual_data:{type:"equation_display",terms:[e,a?"×":"÷",o,"=",s]},animation:"write_answer",caption:`So the box is ${decStr(o)}.`,spoken:`A move of ${l} to the ${i} means ${a?"multiplying":"dividing"} by ${m}.`}]})}function buildFirstOperandDemo(n,t,e,s,o,r,a){const i=a?"right":"left",l=placeWordOf(r),u=noughtWords(decStr(o)),d=noughtWords(decStr(e)),m=noughtWords(decStr(s));return buildExample({generatorId:t,question:n,title:`? ${a?"×":"÷"} ${decStr(s)} = ${decStr(o)}`,computed:{missing:e,p:s,c:o,k:r,answer:e},steps:[{visual_data:{type:"equation_display",terms:["__",a?"×":"÷",s,"=",o]},animation:"show_problem",caption:"Work backwards to the missing number.",spoken:`A number ${a?"multiplied":"divided"} by ${m} gives ${u}. To find it, we work backwards and undo that step.`},{visual_data:{type:"equation_display",terms:[o,a?"÷":"×",s,"=","__"]},animation:"reverse_shift",caption:`Undo it: shift ${l} ${i}.`,spoken:`${a?"Dividing undoes multiplying":"Multiplying undoes dividing"}, so the digits of ${u} shift ${l} back to the ${i}.`},{visual_data:{type:"equation_display",terms:[e,a?"×":"÷",s,"=",o]},animation:"write_answer",caption:`The missing number is ${decStr(e)}.`,spoken:`We can check by putting it back in: ${d} ${a?"times":"divided by"} ${m} is ${u}. So the missing number is ${d}.`}]})}const PROBE_RE=/(?:says|say) (?:to )?([\d.]+) (?:x|×|multiplied by) (1000|100|10)|(?:says|say) (?:to )?([\d.]+) (?:÷|\/|divided by) (1000|100|10)|(?:multiply|divide) ([\d.]+) by (1000|100|10)/i,probe$2={id:"NPV.P7.M04.probe.v1",microSkillId:"NPV.P7.M04",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=acceptedNum(n.correctAnswer);if(t===null)return null;const s=normaliseStem(n.stem).match(PROBE_RE);if(!s)return null;const o=Number(s[1]??s[3]??s[5]),r=Number(s[2]??s[4]??s[6]),a=!/divid|÷|\//i.test(s[0]);
71if(!Number.isFinite(o)||!POWERS.has(r))return null;const i=a?kOf(r):-kOf(r),l=Number(shiftPoint(o,i));return round6(l)!==t?null:buildShiftDemo(n,this.id,o,[o,a?"×":"÷",r],i,l,void 0,void 0,!0)}},PVA_RE=/^([\d.]+) is (multiplied|divided) by (1000|100|10)/i,NET_EFFECT_RE=/^a number is (multiplied|divided) by (1000|100|10),?\s*(?:and\s*)?then\s*(multiplied|divided) by (1000|100|10)/i,SINGLE_RULE_RE=/^how do(?:es)? the digits? move when you (multiply|divide) a number by (1000|100|10)/i,RULE_ANSWER_RE=/each digit moves (one|two|three) places? to the (left|right)/i,RULE_WORD_TO_K={one:1,two:2,three:3},adjustment$1={id:"NPV.P7.M04.adjustment.v1",microSkillId:"NPV.P7.M04",forms:["place_value_adjustment"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=t.match(SINGLE_RULE_RE);if(e){const h=e[1].toLowerCase()==="multiply",f=Number(e[2]),g=h?kOf(f):-kOf(f),b=g>0?"left":"right",x=n.correctAnswer.match(RULE_ANSWER_RE);if(!x||RULE_WORD_TO_K[x[1].toLowerCase()]!==Math.abs(g)||x[2].toLowerCase()!==b)return null;const y=5.7,E=Number(shiftPoint(y,g));return buildShiftDemo(n,this.id,y,[y,h?"×":"÷",f],g,E,void 0,void 0,!0)}const s=t.match(NET_EFFECT_RE);if(s){const h=s[1].toLowerCase()==="multiplied",f=Number(s[2]),g=s[3].toLowerCase()==="multiplied",b=Number(s[4]),x=h?kOf(f):-kOf(f),y=g?kOf(b):-kOf(b),E=x+y,k=/stays the same/i.test(n.correctAnswer),T=n.correctAnswer.match(/(\d+)\s*times\s*(smaller|bigger)/i);let A=null;if(k)A=0;else if(T&&POWERS.has(Number(T[1]))){const N=kOf(Number(T[1]));A=T[2].toLowerCase()==="smaller"?-N:N}return A===null||A!==E?null:buildNetEffectDemo(n,this.id,h,f,g,b,E)}const o=n.correctAnswer.trim().match(/^-?[\d.]+/);if(!o)return null;const r=round6(Number(o[0])),a=t.match(PVA_RE);if(!a)return null;const i=Number(a[1]),l=a[2].toLowerCase()==="multiplied",u=Number(a[3]);if(!Number.isFinite(i)||!POWERS.has(u))return null;const d=l?kOf(u):-kOf(u),m=Number(shiftPoint(i,d));return round6(m)!==r?null:buildShiftDemo(n,this.id,i,[i,l?"×":"÷",u],d,m,void 0,void 0,!0)}};function buildNetEffectDemo(n,t,e,s,o,r,a){const l=e?kOf(s):-kOf(s),u=Number(shiftPoint(5.7,l)),d=o?kOf(r):-kOf(r),m=Number(shiftPoint(u,d)),h=Math.abs(a),f=a>0?"bigger":"smaller",g=10**h,b=a===0?"the number stays the same":`it becomes ${g} times ${f}`,x=a===0?"the two moves cancel each other out, so the number stays the same":`the number becomes ${numberToWordsLong(g)} times ${f}`;return buildExample({generatorId:t,question:n,title:"What happens overall?",computed:{a:5.7,mid:u,end:m,netK:a,places:h,answer:m},steps:[{visual_data:{type:"equation_display",terms:[5.7,e?"×":"÷",s,o?"×":"÷",r,"=","__"]},animation:"show_problem",caption:`Try it with an example: ${decStr(5.7)}.`,spoken:`A number is ${e?"multiplied":"divided"} by ${numberToWordsLong(s)}, then ${o?"multiplied":"divided"} by ${numberToWordsLong(r)}. We try it with an example number to see the overall effect.`},{visual_data:{type:"equation_display",terms:[5.7,"→",u,"→","__"]},animation:"chain_shift",caption:`${decStr(5.7)} → ${decStr(u)} → ?`,spoken:"First the digits shift one way, then the other. We follow both moves in turn."},{visual_data:{type:"equation_display",terms:[5.7,"→",u,"→",m]},animation:"write_answer",caption:`Overall, ${b}.`,spoken:`The two moves combine, and ${x}.`}]})}const context={id:"NPV.P7.M04.context.v1",microSkillId:"NPV.P7.M04",forms:["context_calculation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=acceptedNum(n.correctAnswer);if(t===null||t<=0)return null;const e=normaliseStem(n.stem),s=(e.match(/\d+(?:\.\d+)?/g)??[]).map(Number);if(s.length>=3&&/per\s+\w/i.test(e)){const d=s[0],m=s.slice(1).filter(h=>POWERS.has(h));if(m.length===2&&d>0&&d!==t){const[h,f]=m,g=round6(d*h),b=round6(d*f);if(round6(g+b)===t)return buildRateSumDemo(n,this.id,d,h,f,g,b,t)}}const o=s.filter(d=>d>0&&d!==t),r=[];for(const d of new Set(o)){const m=round6(t/d),h=round6(d/t);POWERS.has(m)&&round6(Number(shiftPoint(d,kOf(m))))===t?r.push({a:d,k:kOf(m)}):POWERS.has(h)&&round6(Number(shiftPoint(d,-kOf(h))))===t&&r.push({a:d,k:-kOf(h)})}if(r.length!==1)return null;const{a,k:i}=r[0],l=i>0?"×":"÷",u=10**Math.abs(i);return buildShiftDemo(n,this.id,a,[a,l,u],i,t,void 0,trailingUnit(e),!0)}};function buildRateSumDemo(n,t,e,s,o,r,a,i){const l=noughtWords(decStr(e)),u=noughtWords(decStr(r)),d=noughtWords(decStr(a));return buildExample({generatorId:t,question:n,title:`${decStr(e)} × ${s} + ${decStr(e)} × ${o}
71`,computed:{rate:e,q1:s,q2:o,p1:r,p2:a,answer:i},steps:[{visual_data:{type:"equation_display",terms:[e,"×",s,"+",e,"×",o,"=","__"]},animation:"show_problem",caption:"Two amounts, each at the same rate.",spoken:`Both amounts use the same rate, ${l} each time, so we work out each amount separately, then add them.`},{visual_data:{type:"equation_display",terms:[e,"×",s,"=",r,"+",e,"×",o,"=",a]},animation:"shift_each",caption:`${decStr(e)} × ${s} = ${decStr(r)}; ${decStr(e)} × ${o} = ${decStr(a)}.`,spoken:`Each product shifts the digits its own way — a bigger power of ten means a bigger shift. ${cap$2(l)} times ${numberToWordsLong(s)} is ${u}, and ${l} times ${numberToWordsLong(o)} is ${d}.`},{visual_data:{type:"equation_display",terms:[r,"+",a,"=",i]},animation:"write_answer",caption:`${decStr(r)} + ${decStr(a)} = ${decStr(i)}.`,spoken:`Adding the two amounts together, ${u} add ${d} makes ${noughtWords(decStr(i))}. That is the total.`}]})}const NPV_P7_M04_GENERATORS=[symbolic$1,eqCompletion,probe$2,adjustment$1,context],PLACE_FROM_POINT={tenths:1,hundredths:2,thousandths:3},PLACE_WORD=["ones","tenths","hundredths","thousandths"];function decimalStr(n){const t=n.match(/\d+\.\d+/);return t?t[0]:null}function expansion(n,t){const[e,s=""]=n.split("."),o=Number(e),r=[];o>0&&r.push({value:o,isTarget:t===0,place:0});let a=0;for(let h=1;h<=s.length;h++)s[h-1]!=="0"&&(a=h);const i=Math.max(a,t,0);for(let h=1;h<=i;h++){const f=Number(s[h-1]??0),g=round6(f/10**h);r.push({value:g,isTarget:h===t,place:h})}const l=[],u=[];r.forEach((h,f)=>{f>0&&(l.push("+"),u.push("+")),l.push(h.value),u.push(h.isTarget?"__":h.value)});const d=r.find(h=>h.isTarget),m=r.filter(h=>h.value===0&&!h.isTarget&&h.place>0).map(h=>PLACE_WORD[h.place]);return{full:l,blanked:u,targetValue:d?d.value:0,zeroPlaceNames:m}}function digitAtPlace(n,t){const e=n.split(".")[1]??"";return t<1||t>e.length?null:Number(e[t-1])}const cap$1=n=>n.charAt(0).toUpperCase()+n.slice(1);function buildPartitionDemo(n,t,e,s,o,r,a,i={}){const{full:l,blanked:u,targetValue:d,zeroPlaceNames:m}=expansion(e,s),h=Number(e),f=m.length>0?`Split by place; the ${m.join(" and ")} holds a zero.`:`Split by place; find the ${PLACE_WORD[s]}.`,g=m.length>0?`We split the number into its place-value parts. The ${m.join(" and ")} column has nothing in it, so a zero holds that place — it still counts, it just has no value here.`:`We split the number into its place-value parts and look at the ${PLACE_WORD[s]} column.`;return buildExample({generatorId:t,question:n,title:o,computed:{num:h,targetValue:d,answer:a,...i},steps:[{visual_data:{type:"equation_display",terms:[h]},animation:"show_number",caption:"After the point: tenths, hundredths, thousandths.",spoken:`${cap$1(decimalToWords(e))}. Each column after the decimal point is ten times smaller than the one before it.`},{visual_data:{type:"equation_display",terms:u},animation:"split_columns",caption:f,spoken:g},{visual_data:{type:"equation_display",terms:l},animation:"write_answer",caption:r,spoken:`The ${PLACE_WORD[s]} column tells us the answer. Position decides worth.`}]})}const digitValue={id:"NPV.P7.M05.digit-value.v1",microSkillId:"NPV.P7.M05",forms:["digit_value_entry"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=decimalStr(t);if(!e)return null;const s=t.match(/digit is in the (tenths|hundredths|thousandths) place|in the (tenths|hundredths|thousandths) place/i);if(s&&/which digit/i.test(t)){const r=(s[1]??s[2]).toLowerCase(),a=PLACE_FROM_POINT[r],i=digitAtPlace(e,a);return i===null||Number(n.correctAnswer.trim())!==i?null:buildPartitionDemo(n,this.id,e,a,`Which digit is in the ${r} place of ${e}?`,`The ${r} digit is ${i}.`,i,{digit:i})}const o=t.match(/value of the digit (\d) in/i);if(o){const r=Number(o[1]),a=e.split(".")[1]??"";if(a.split("").filter(d=>d===String(r)).length!==1)return null;const i=a.indexOf(String(r))+1,l=round6(r/10**i),u=n.correctAnswer.match(/\d\.\d+/);return!u||round6(Number(u[0]))!==l?null:buildPartitionDemo(n,this.id,e,i,`What is the ${r} worth in ${e}?`,`The ${r} is worth ${decStr(l)}.`,l,{digit:r})}return null}},UNIT={zero:0,one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12,thirteen:13,fourteen:14,fifteen:15,sixteen:16,seventeen:17,eighteen:18,nineteen:19},TENS={twenty:20,thirty:30,forty:40,fifty:50,sixty:60,seventy:70,eighty:80,ninety:90};function wordsToInt(n){const t=n.toLowerCase().replace(/-/g," ").replace(/\band\b/g," ").split(/\s+/).filter(
71Boolean);if(t.length===0)return null;let e=0;for(const s of t)if(UNIT[s]!=null)e+=UNIT[s];else if(TENS[s]!=null)e+=TENS[s];else if(s==="hundred")e*=100;else return null;return e}const NUM_TOKENS=new Set([...Object.keys(UNIT),...Object.keys(TENS),"hundred","and"]);function parseThousandthsPhrase(n){const t=n.match(/([a-z\s-]+?) thousandths?/i);if(!t)return null;let e=t[1].toLowerCase().replace(/-/g," ").split(/\s+/).filter(Boolean);for(;e.length&&!NUM_TOKENS.has(e[0]);)e.shift();for(;e.length&&(e[0]==="and"||!NUM_TOKENS.has(e[0]));)e.shift();if(e.length===0)return null;const s=e.join(" ");if(/hundred/.test(s)){const a=wordsToInt(s);return a===null?null:round6(a/1e3)}const o=s.split(/\band\b/);if(o.length===2){const a=wordsToInt(o[0]),i=wordsToInt(o[1]);return a===null||i===null?null:round6(a+i/1e3)}const r=wordsToInt(s);return r===null?null:round6(r/1e3)}const wordToNumeral={id:"NPV.P7.M05.word-to-numeral.v1",microSkillId:"NPV.P7.M05",forms:["word_to_numeral"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=parseThousandthsPhrase(normaliseStem(n.stem));if(t===null)return null;const e=n.correctAnswer.match(/\d+\.\d+/);if(!e||round6(Number(e[0]))!==t)return null;const s=e[0],o=Math.max(1,s.split(".")[1]?.length??1),{full:r,blanked:a,zeroPlaceNames:i}=expansion(s,o),l=i.length>0?`Fill each column from the words; the ${i.join(" and ")} holds a zero.`:"Fill each column from the words, one place at a time.";return buildExample({generatorId:this.id,question:n,title:"Write this in digits.",computed:{num:t,answer:t},steps:[{visual_data:{type:"equation_display",terms:["tenths","hundredths","thousandths"]},animation:"show_columns",caption:"Set out tenths, hundredths, thousandths.",spoken:"We write a number in words by building it one place-value column at a time, in the order the words give them."},{visual_data:{type:"equation_display",terms:a},animation:"fill_columns",caption:l,spoken:"We put each part of the words into its own column. Any column the words don't mention still needs a zero to hold its place."},{visual_data:{type:"equation_display",terms:r},animation:"write_answer",caption:`That is ${s}.`,spoken:`So it is written as ${decimalToWords(s)}.`}]})}},symbolic={id:"NPV.P7.M05.symbolic.v1",microSkillId:"NPV.P7.M05",forms:["symbolic_numeric_entry"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=decimalStr(t);if(!e)return null;const s=Number(e),o=Number(n.correctAnswer.trim());if(!Number.isInteger(o))return null;const r=/hundredths/i.test(t)?100:/thousandths/i.test(t)?1e3:0;if(!r||Math.round(round6(s*r))!==o)return null;const a=r===100?"hundredths":"thousandths",i=r===1e3?3:2,l=[s];for(let d=0;d<i;d++)l.push(Number(shiftPoint(l[l.length-1],1)));const u=[l[0]];for(let d=1;d<=i;d++)u.push("×10"),u.push(d===i?"__":l[d]);return buildExample({generatorId:this.id,question:n,title:`How many ${a} are in ${e}?`,computed:{num:s,answer:o,places:i},steps:[{visual_data:{type:"equation_display",terms:[s]},animation:"show_number",caption:`Read ${e} in ${a}.`,spoken:`We want to know how many ${a} make ${decimalToWords(e).replace(/^zero point/,"nought point")}. To count them, we turn it into a whole number of ${a}.`},{visual_data:{type:"equation_display",terms:u},animation:"count_units",caption:`Shift ×10 each time — count ${i} hops.`,spoken:`${cap$1(a)} sit ${numberToWordsLong(i)} places after the point, so we multiply by ten ${i===2?"twice":"three times"}. Each hop moves every digit one place left.`},{visual_data:{type:"equation_display",terms:[s,"=",o,a]},animation:"write_answer",caption:`That is ${o} ${a}.`,spoken:`The digits moved ${numberToWordsLong(i)} places left, so ${decimalToWords(e).replace(/^zero point/,"nought point")} is ${numberToWordsLong(o)} ${a}.`}]})}},partition={id:"NPV.P7.M05.partition.v1",microSkillId:"NPV.P7.M05",forms:["partition_equation"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=n.correctAnswer.match(/\d+\.\d+/);if(/partitioned correctly|correctly partitioned/i.test(t)){const r=decimalStr(t),a=(n.correctAnswer.match(/\d+\.\d+/g)??[]).map(Number);if(r===null||a.length<2)return null;const i=Number(r),l=round6(a.reduce((u,d)=>u+d,0));return round6(i)!==l?null:buildSumDemo(n,this.id,a,l,decStr(l),!1)}const s=t.match(/(\d+) ones?,?\s*(?:and\s*)?(\d+) tenths?,?\s*(?:and\s*)?(\d+) hundredths?(?:,?\s*(?:and\s*)?(\d+) thousandths?)?/i);if(s&&e){const r=Number(s[1]),a=Number(s[2]),i=Number(s[3]),l=s[4]?Number(s[4]):0,u=round6(r+a/10+i/100+l/1e3);if(round6(Number(e[0]))!==u)return null;const d=[r,round6(a/10),round6(i/100),round6(l/1e3)].filter(m=>m>0);return buildSumDemo(n,this.id,d,u,e[0])}const o=n.stem.match(/(\d*\.\d+)\s*=\s*([\d.\s+?]+)/);if(o&&e&&/\?/.test(o[2])){const r=round6(Number(o[1])),a=(o[2].match(/\d*\.\d+/g)??[]).map(Number),i=round6(r-a.reduce((l,u)=>l+u,0));return round6(Number(e[0]))!==i?null:buildMissingPartDemo(n,this.id,r,a,i,e[0])}if(/equals/i.test(t)&&e){const a=((t.split(/equals/i)[1]??"").match(/\d+(?:\.\d+)?/g)??[]).map(Number).filter(l=>l<10);if(a.length<2)return null;const i=round6(a.reduce((l,u)=>l+u,0));return round6(Number(e[0]))!==i?null:buildSumDemo(n,this.id,a,i,e[0])}return null}};function placeOf(n){return Number.isInteger(n)?0:dpOf(n)}function withPlaceholders(n){const t=n.filter(r=>Number.isInteger(r)&&r>0),e=n.filter(r=>!Number.isInteger(r)),s=[...t];if(e.length===0)return s;const o=Math.max(...e.map(placeOf));for(let r=1;r<=o;r++){const a=e.find(i=>placeOf(i)===r);s.length&&s.push("+"),s.push(a??0)}return s}function buildSumDemo(n,t,e,s,o,r=!0){const a=[];e.forEach((m,h)=>{h>0&&a.push("+"),a.push(m)});const i=r?withPlaceholders(e):a,l=i.length>a.length,u=l?"Fill every column;
71 an empty one gets a zero.":"Slot each digit into its own column.",d=l?"We line the parts up by column. Any column with nothing given still needs a zero to hold its place, or the digits after it would shift.":"We slot each digit straight into its column and read across, keeping every place lined up.";return buildExample({generatorId:t,question:n,title:"Combine the place-value parts",computed:{total:s,answer:s},steps:[{visual_data:{type:"equation_display",terms:[...a]},animation:"show_parts",caption:"Each part is one place-value column.",spoken:"Each part is a single place-value column — ones, tenths, hundredths or thousandths."},{visual_data:{type:"equation_display",terms:[...i,"=","__"]},animation:"combine",caption:u,spoken:d},{visual_data:{type:"equation_display",terms:[...i,"=",s]},animation:"write_answer",caption:`That makes ${o}.`,spoken:`Put together, the parts make ${decimalToWords(o)}.`}]})}function buildMissingPartDemo(n,t,e,s,o,r){const a=[];s.forEach((d,m)=>{m>0&&a.push("+"),a.push(d)});const i=placeOf(o),l=PLACE_WORD[i]??"next",u=s.map(d=>PLACE_WORD[placeOf(d)]).join(" and ");return buildExample({generatorId:t,question:n,title:`Find the missing part of ${decStr(e)}`,computed:{total:e,missing:o,answer:o},steps:[{visual_data:{type:"equation_display",terms:[e]},animation:"show_number",caption:`${decStr(e)} splits into place-value columns.`,spoken:`${cap$1(decimalToWords(decStr(e)))}
71. We split it into its place-value columns.`},{visual_data:{type:"equation_display",terms:[e,"=",...a,"+","__"],highlight:s.map((d,m)=>2+2*m)},animation:"find_gap",caption:`The ${l} column is still blank.`,spoken:`We line up the parts we have — the ${u} — with the columns of the whole number, and the ${l} column has nothing yet.`},{visual_data:{type:"equation_display",terms:[e,"=",...a,"+",o]},animation:"write_answer",caption:`The missing part is ${r}.`,spoken:`The empty column is filled by ${decimalToWords(r)}.`}]})}const probe$1={id:"NPV.P7.M05.probe.v1",microSkillId:"NPV.P7.M05",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=t.match(/the 0 in (\d+\.\d+) does nothing,?\s*so\s*\d+\.\d+ is (?:the )?same as (\d+\.\d+)/i);if(e){const i=e[1],l=e[2],u=i.split(".")[1]??"",d=u.indexOf("0");if(d<0)return null;const m=d+1,h=PLACE_WORD[m],f=n.correctAnswer.match(/holds the (tenths|hundredths|thousandths) place/i);if(!f||f[1].toLowerCase()!==h)return null;const g=Math.max(1,u.length),{full:b,blanked:x}=expansion(i,g),y=Number(i);return buildExample({generatorId:this.id,question:n,title:`Is ${i} the same as ${l}?`,computed:{num:y,claimed:Number(l),answer:y},steps:[{visual_data:{type:"equation_display",terms:[y,"=",Number(l),"?"]},animation:"show_claim",caption:`Is ${i} really the same as ${l}?`,spoken:`A pupil says the zero does nothing, so ${decimalToWords(i)} is the same as ${decimalToWords(l)}. Let's check by splitting into columns.`},{visual_data:{type:"equation_display",terms:x},animation:"split_columns",caption:`Split by place; the ${h} holds a zero.`,spoken:`We split the number into its columns. The ${h} column has a zero in it — removing it would shift every digit after it into the wrong place.`},{visual_data:{type:"equation_display",terms:b},animation:"write_answer",caption:`${i} is not ${l}.`,spoken:`${cap$1(decimalToWords(i))} keeps its zero, so it is not the same as ${decimalToWords(l)}.`}]})}const s=/reads (\d+\.\d+) as ([a-z\s-]+?)\.\s*how should \1 be read/i,o=t.match(s);if(o){const i=o[1],l=o[2].trim(),u=Number(i),d=i.split(".")[1]??"",m=d.length,h=PLACE_WORD[m];if(!h)return null;const f=n.correctAnswer.trim().match(/^([a-z\s-]+?)\s+(tenths|hundredths|thousandths)$/i);if(!f||f[2].toLowerCase()!==h)return null;const g=Number(d),b=wordsToInt(f[1]);if(b===null||b!==g)return null;const{full:x,blanked:y,zeroPlaceNames:E}=expansion(i,m),k=f[1].trim();return buildExample({generatorId:this.id,question:n,title:`How should ${i} be read?`,computed:{num:u,answer:u},steps:[{visual_data:{type:"equation_display",terms:[u,"?"]},animation:"show_claim",caption:`Is ${i} read as ${l}?`,spoken:`Someone reads it as ${l}. Let's count the digits after the point to check.`},{visual_data:{type:"equation_display",terms:y},animation:"split_columns",caption:E.length>0?`Split by place; the ${E.join(" and ")} holds a zero.`:`Split by place; find the ${h}.`,spoken:E.length>0?`We split the number into its place-value columns. The ${E.join(" and ")} column has a zero in it, but it still counts as one of the columns.`:"We split the number into its place-value columns."},{visual_data:{type:"equation_display",terms:x},animation:"write_answer",caption:`${i} is ${k} ${h}.`,spoken:`So it is read as ${k} ${h}, because there ${m===1?"is":"are"} ${numberToWordsLong(m)} digit${m===1?"":"s"} after the point.`}]})}const r=t.match(/digit (\d) in (\d+\.\d+)/i);if(r&&/worth|value/i.test(t)){const i=Number(r[1]),l=r[2],u=l.split(".")[1]??"";if(u.split("").filter(f=>f===String(i)).length!==1)return null;const d=u.indexOf(String(i))+1,m=round6(i/10**d),h=n.correctAnswer.match(/\d\.\d+/);return!h||round6(Number(h[0]))!==m?null:buildPartitionDemo(n,this.id,l,d,`What is the ${i} worth in ${l}?`,`The ${i} is worth ${decStr(m)}.`,m,{digit:i})}const a=parseThousandthsPhrase(t);if(a!==null){const i=n.correctAnswer.match(/\d+\.\d+/),l=t.match(/as (\d+\.\d+)/i);if(i&&round6(Number(i[0]))===a){const u=i[0],d=Math.max(1,u.split(".")[1]?.length??1),{full:m}=expansion(u,d),h=l?l[1]:null,f=h?round6(Number(h)):null,g=f!==null&&f!==a,b=Math.round(a*1e3),x=String(b).padStart(3,"0"),y=Number(x[0]),E=Number(x[1]),k=Number(x[2]),T=[...y>0?[`${y} tenths`]:[],`${E}
71 hundredths`,`${k} thousandths`],A=[...y>0?[`${numberToWordsLong(y)} tenths`]:[],`${numberToWordsLong(E)} hundredths`,`${numberToWordsLong(k)} thousandths`];let N="Line up the decimal columns.",S=`${cap$1(decimalToWords(u))}. The last word tells us the smallest column that must be filled.`,M;if(g&&h){const L=h.split(".")[1]??"",P=L.length;M=Number(L);const F=PLACE_WORD[P];N=F?`${h} is ${M} ${F}, not ${b} thousandths.`:`Is ${h} the same value?`,S=F?`A pupil writes it as ${decimalToWords(h)} — but that is ${numberToWordsLong(M)} ${F}, not ${numberToWordsLong(b)} thousandths. Let's build the correct number in columns.`:`A pupil writes it as ${decimalToWords(h)}. That puts the digits in the wrong columns — we check each place to see why.`}return buildExample({generatorId:this.id,question:n,title:`Write ${decimalToWords(u)} in digits.`,computed:{num:a,answer:a,countThousandths:b,...g?{wrongNum:f,wrongCount:M}:{}},steps:[{visual_data:g?{type:"equation_display",terms:[f,"?"]}:{type:"equation_display",terms:["tenths","hundredths","thousandths"]},animation:"show_claim",caption:N,spoken:S},{visual_data:{type:"equation_display",terms:[b,"thousandths","=",...T.flatMap((L,P)=>P===0?[L]:["+",L])]},animation:"fill_columns",caption:`${b} thousandths = ${T.join(" + ")}.`,spoken:`${cap$1(numberToWordsLong(b))} thousandths splits into ${A.join(", then ")} — every column still needs its own digit, even the ones worth zero.`},{visual_data:{type:"equation_display",terms:m},animation:"write_answer",caption:`That is ${u}.`,spoken:`So it should be written as ${decimalToWords(u)}.`}]})}}return null}},adjustment={id:"NPV.P7.M05.adjustment.v1",microSkillId:"NPV.P7.M05",forms:["place_value_adjustment"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=t.match(/^in the number (\d+\.\d+),\s*which statement is true/i);if(e){const T=e[1],A=n.correctAnswer.match(/the 0 is in the (tenths|hundredths|thousandths) place/i);if(!A)return null;const S=(T.split(".")[1]??"").indexOf("0");if(S<0)return null;const M=S+1;if(PLACE_WORD[M]!==A[1].toLowerCase())return null;const L=Number(T);return buildPartitionDemo(n,this.id,T,M,`Which statement about ${T} is true?`,`The 0 is in the ${A[1].toLowerCase()} place.`,L)}const s=[...t.matchAll(/(\d) in the (tenths|hundredths|thousandths) place/gi)];if(s.length<2)return null;const o=n.correctAnswer.match(/\d*\.\d+/);if(!o)return null;const r=round6(Number(o[0])),a=[0,0,0];for(const T of s){const A=Number(T[1]),N=PLACE_FROM_POINT[T[2].toLowerCase()];a[N-1]=A}const i=round6(a[0]/10+a[1]/100+a[2]/1e3);if(i!==r)return null;const l=[...new Set(s.map(T=>PLACE_FROM_POINT[T[2].toLowerCase()]))].sort(),u=l[0],d=l[l.length-1],m=[...a];u!==d&&([m[u-1],m[d-1]]=[m[d-1],m[u-1]]);const h=round6(m[0]/10+m[1]/100+m[2]/1e3),f=Math.max(...s.map(T=>PLACE_FROM_POINT[T[2].toLowerCase()]));if(u===d||h===i)return buildPartitionDemo(n,this.id,i.toFixed(3),f,"Build the number that fits both clues.",`That number is ${o[0]}.`,r);const g=`0.${m.join("")}`,b=u,x=PLACE_WORD[b],y=a[b-1],E=m[b-1],k=s.map(T=>`${numberToWordsLong(Number(T[1]))} in the ${T[2].toLowerCase()}`).join(" and ");return buildExample({generatorId:this.id,question:n,title:"Build the number that fits both clues.",computed:{built:i,target:r,answer:r,wantDigit:y,gotDigit:E,swapValue:h},steps:[{visual_data:{type:"equation_display",terms:[h,"?"]},animation:"test_option",caption:`Test ${g}: does it fit both clues?`,spoken:`A common mistake is putting the digits in the wrong columns. Let's test ${decimalToWords(g)} against the clues: ${k}.`},{visual_data:{type:"equation_display",terms:[h,"→",x,"=",E]},animation:"reject_option",caption:`Check the ${x}: it shows ${E}, not ${y}.`,spoken:`In that number the ${x} digit doesn't match the clue, so it fails — that number is rejected.`},{visual_data:{type:"equation_display",terms:[r]},animation:"write_answer",caption:`The digits in place, it is ${o[0]}.`,spoken:`Putting each digit in its own named column instead gives ${decimalToWords(o[0])}.`}]})}},NPV_P7_M05_GENERATORS=[digitValue,wordToNumeral,symbolic,partition,probe$1,adjustment],nw=n=>decimalToWords(n).replace(/^zero point/,"nought point");function line(n,t={}){return{type:"number_line",min:n.min,max:n.max,step:n.step,marked:[],...t}}function numsIn(n){return(n.match(/-?\d+(?:\.\d+)?/g)??[]).map(Number).filter(t=>Number.isFinite(t))}function firstNum(n){const t=n.match(/-?\d+(?:\.\d+)?/);return t?Number(t[0]):null}const cap=n=>n.charAt(0).toUpperCase()+n.slice(1);function buildCompareDemo(n,t,e,s,o,r){if(s.some(b=>dpOf(b)>3||b<0||b>=100))return null;const a=resolveWindow(s);if(!a)return null;const{win:i,padded:l}=a,u=s[0],d={winner:o,answer:o};
71s.forEach((b,x)=>d[`v${x}`]=b);const m=s.filter(b=>b!==o),h=m.map(b=>l?b.toFixed(3):decStr(b)).join(", "),f=m.map(b=>decimalToWords(decStr(b))).join(", "),g=m.length>0?`Plot every value; ${h} ${m.length>1?"are":"is"} crossed out.`:"Place them all; further right is bigger.";return buildExample({generatorId:t,question:n,title:e,computed:d,steps:[{visual_data:line(i,{marked:[u]}),animation:"place_first",caption:`Place ${decStr(u)} on the line.`,spoken:`We compare by placing each decimal on a number line, starting with ${decimalToWords(decStr(u))}.`},{visual_data:line(i,{marked:[o],crossed:m}),animation:"place_rest",caption:g,spoken:m.length>0?`Now every decimal is on the line. Having more digits does not make a decimal bigger — ${cap(f)} ${m.length>1?"sit":"sits"} further to the ${r==="greater"?"left":"right"}, so ${m.length>1?"they are":"it is"} crossed out.`:"Now every decimal is on the line. Having more digits does not make a decimal bigger — what counts is how far right it sits."},{visual_data:line(i,{marked:[o],crossed:m,unknown:o,conclude_label:`${r}: ${decStr(o)}`}),animation:"reveal",caption:`${decStr(o)} is ${r}.`,spoken:`The ${r} decimal is ${decimalToWords(decStr(o))}, sitting furthest to the ${r==="greater"?"right":"left"}.`}]})}function thousandthsWindow(n){const t=round6(Math.floor(1e3*Math.min(...n))/1e3),e=round6(Math.ceil(1e3*Math.max(...n))/1e3),s=round6(e-t);if(s<=0)return null;const o=.001;return Math.round(s/o)+1>21?null:{min:t,max:e,step:o}}function resolveWindow(n){const t=decimalWindow(n);if(!t)return null;const e=o=>n.some((r,a)=>n.some((i,l)=>a!==l&&Math.abs(round6(r-i))>0&&Math.abs(round6(r-i))<o.step));if(!e(t))return{win:t,padded:!1};const s=thousandthsWindow(n);return s&&!e(s)?{win:s,padded:!1}:{win:t,padded:!0}}function directionOf(n){const t=normaliseStem(n).toLowerCase(),e=/\b(greater|larger|largest|bigger|thicker|heavier|longer|higher|deeper)\b/.test(t),s=/\b(smaller|smallest|thinner|lighter|shorter|faster|lower)\b/.test(t);return e&&!s?"max":s&&!e?"min":null}const compare={id:"NPV.P7.M06.compare.v1",microSkillId:"NPV.P7.M06",forms:["comparison"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=firstNum(n.correctAnswer);if(e===null)return null;const s=t.match(/between (\d+(?:\.\d+)?) and (\d+(?:\.\d+)?)/i);if(s&&!/other two/i.test(t)){const u=Number(s[1]),d=Number(s[2]),m=Math.min(u,d),h=Math.max(u,d),f=(n.options??[]).map(firstNum).filter(N=>N!==null),g=f.filter(N=>N>m&&N<h);if(g.length!==1||round6(g[0])!==round6(e))return null;const b=[m,h,...f];if(b.some(N=>dpOf(N)>3||N<0||N>=100))return null;const x=resolveWindow(b);if(!x)return null;const{win:y,padded:E}=x,k=f.filter(N=>N!==e),T=k.map(N=>E?N.toFixed(3):decStr(N)).join(", "),A=k.map(N=>decimalToWords(decStr(N))).join(", ");return buildExample({generatorId:this.id,question:n,title:`Which decimal is between ${decStr(m)} and ${decStr(h)}?`,computed:{lo:m,hi:h,answer:e},steps:[{visual_data:line(y,{marked:[m,h]}),animation:"mark_bounds",caption:`Mark ${decStr(m)} and ${decStr(h)}.`,spoken:`A decimal between ${decimalToWords(decStr(m))} and ${decimalToWords(decStr(h))} must sit in the gap between them.`},{visual_data:line(y,{marked:[m,h,e],crossed:k}),animation:"test_option",caption:k.length>0?`Plot every choice; ${T} fall outside and are crossed out.`:"Plot every choice.",spoken:k.length>0?`We place every choice on the line. ${cap(A)} sit outside the gap, so they are crossed out.`:"We place every choice on the line and keep the one that lands inside the gap."},{visual_data:line(y,{marked:[m,h],crossed:k,unknown:e,conclude_label:`${decStr(e)}`}),animation:"reveal",caption:`${decStr(e)} lies between them.`,spoken:`${cap(decimalToWords(decStr(e)))} sits inside the gap, so it lies between them.`}]})}if(/other two|in between/i.test(t)){const u=numsIn(t).filter(h=>dpOf(h)>=1),d=[...new Set(u.map(round6))].sort((h,f)=>h-f);if(d.length!==3)return null;const m=d[1];return round6(m)!==round6(e)?null:buildMiddleDemo(n,this.id,d,m)}const o=directionOf(t);if(!o)return null;let r;const a=n.visualData;if(a&&a.type==="comparison"){const u=a;r=[u.left,u.right]}else r=(n.options??[]).map(firstNum).filter(u=>u!==null);if(r.length<2||r.length>4||new Set(r.map(round6)).size!==r.length)return null;const i=o==="max"?Math.max(...r):Math.min(...r);if(round6(i)!==round6(e))return null;const l=o==="max"?"greater":"smaller";return buildCompareDemo(n,this.id,`Which is ${l}: ${r.map(decStr).join(", ")}?`,r,i,l)}};function buildMiddleDemo(n,t,e,s){if(e.some(a=>dpOf(a)>3||a<0||a>=100))return null;const o=decimalWindow(e);if(!o)return null;const r={answer:s};return e.forEach((a,i)=>r[`v${i}`]=a),buildExample({generatorId:t,question:n,title:"Which value is in the middle?",computed:r,steps:[{visual_data:line(o,{marked:[e[0]]}),animation:"place_first",caption:`Place ${decStr(e[0])} on the line.`,spoken:"We line the values up smallest to largest by placing them on a number line."},{visual_data:line(o,{marked:[...e]}),animation:"place_rest",caption:"Place them all in order.",spoken:"With all of them on the line, the smallest is on the left and the largest on the right."},{visual_data:line(o,{marked:[...e],unknown:s,conclude_label:`middle: ${decStr(s)}`}),animation:"reveal",caption:`${decStr(s)} is in the middle.`,spoken:`${cap(decimalToWords(decStr(s)))} sits between the other two, so it is the one in the middle.`}]})}const ordering={id:"NPV.P7.M06.ordering.v1",microSkillId:"NPV.P7.M06",forms:["decimal_ordering"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=/lightest to heaviest|smallest to (?:the )?largest|least to (?:the )?most/i.test(t)?"asc":/heaviest to lightest|largest to (?:the )?smallest|most to (?:the )?least/i.test(t)?"desc":null;if(e&&(!n.visualData||n.visualData.type!=="drag_order")){const k=(n.options??[]).map(T=>numsIn(T));if(k.length>=2&&k.every(T=>T.length===k[0].length&&T.length>=2)){const T=k[0],A=[...T].sort((P,F)=>e==="asc"?P-F:F-P),N=A.map(decStr).join(", ");
71if(n.correctAnswer.trim().replace(/\s+/g," ")!==N.replace(/\s+/g," ")||T.some(P=>dpOf(P)>3||P<0||P>=100)||new Set(T.map(round6)).size!==T.length)return null;const S=decimalWindow(T);if(!S)return null;const M=e==="asc"?"lightest":"heaviest",L=A.map(P=>decimalToWords(decStr(P))).join(", ");return buildExample({generatorId:this.id,question:n,title:`Order these, ${M} first`,computed:Object.fromEntries([...T.map((P,F)=>[`v${F}`,P]),["answer",A[0]]]),steps:[{visual_data:line(S,{marked:[T[0]]}),animation:"place_first",caption:`Place ${decStr(T[0])} on the line first.`,spoken:`We put these values in order by placing each one on a number line, starting with ${decimalToWords(decStr(T[0]))}.`},{visual_data:line(S,{marked:[...T]}),animation:"place_rest",caption:"Place every value on the line.",spoken:"Now they are all on the line. Where each one sits shows its size — further right means larger."},{visual_data:line(S,{marked:[...T],conclude_label:N}),animation:"read_order",caption:`Read ${e==="asc"?"left to right":"right to left"}: ${N}.`,spoken:e==="asc"?`Reading left to right puts them in order, lightest first: ${L}.`:`Reading right to left puts them in order, heaviest first: ${L}.`}]})}}const s=n.visualData;if(!s||s.type!=="drag_order")return null;const o=s.numbers;if(!Array.isArray(o)||o.length<2||o.length>5)return null;const r=o.filter(k=>typeof k=="number"&&Number.isFinite(k));if(r.length!==o.length||r.some(k=>dpOf(k)>3||k<0||k>=100)||new Set(r.map(round6)).size!==r.length)return null;const a=s.direction;if(a!=="asc"&&a!=="desc")return null;const i=[...r].sort((k,T)=>a==="asc"?k-T:T-k);if(i.map(decStr).join(",")!==n.correctAnswer.trim())return null;const l=decimalWindow(r);if(!l)return null;const u=k=>r.some((T,A)=>r.some((N,S)=>A!==S&&Math.abs(round6(T-N))>0&&Math.abs(round6(T-N))<k.step));let d=l,m=!1;if(u(l)){const k=thousandthsWindow(r);k&&!u(k)?d=k:m=!0}const h=i[0],f=r[0],g=a==="asc"?"smallest":"largest",b=i.map(decStr),x=i.map(k=>decimalToWords(decStr(k))).join(", "),y=i.map(k=>k.toFixed(3)).join(", "),E={answer:h};return i.forEach((k,T)=>E[`v${T}`]=k),buildExample({generatorId:this.id,question:n,title:`Order these decimals: ${g} first`,computed:E,steps:[{visual_data:line(d,{marked:[f]}),animation:"place_first",caption:`Place ${decStr(f)} on the line first.`,spoken:`To put these decimals in order we place them on a number line, one at a time, starting with ${decimalToWords(decStr(f))}.`},{visual_data:line(d,{marked:[...r]}),animation:"place_rest",caption:m?`Padded to thousandths: ${y}.`:"Place every decimal on the line.",spoken:m?`A few of these are close together on the line, so we line them up to thousandths as well: ${x}.`:"Now they are all on the line. Where each one sits shows its size — further right means larger."},{visual_data:line(d,{marked:[...r],unknown:h,conclude_label:b.join(", ")}),animation:"read_order",caption:m?`Order, ${g} first: ${y}.`:`Read ${a==="asc"?"left to right":"right to left"}: ${b.join(", ")}.`,spoken:a==="asc"?`Reading left to right puts them in order, smallest first: ${x}.`:`Reading right to left puts them in order, largest first: ${x}.`}]})}},statement={id:"NPV.P7.M06.statement.v1",microSkillId:"NPV.P7.M06",forms:["comparison_statement"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.correctAnswer.trim(),e=t.match(/^(\d+(?:\.\d+)?)\s*([<>])\s*(\d+(?:\.\d+)?)\s*\2\s*(\d+(?:\.\d+)?)$/);if(e){const[u,d,m]=[Number(e[1]),Number(e[3]),Number(e[4])],h=e[2];if(!(h==="<"?u<d&&d<m:u>d&&d>m)||[u,d,m].some(y=>dpOf(y)>3||y<0||y>=100))return null;const g=decimalWindow([u,d,m]);if(!g)return null;const b=h==="<"?"ascending":"descending",x=[u,d,m].map(y=>decimalToWords(decStr(y))).join(", then ");return buildExample({generatorId:this.id,question:n,title:`Order ${decStr(u)}, ${decStr(d)}, ${decStr(m)}`,computed:{x:u,y:d,z:m,answer:m},steps:[{visual_data:line(g,{marked:[u]}),animation:"place_first",caption:`Place ${decStr(u)} on the line.`,spoken:`We place each value on a number line, starting with ${decimalToWords(decStr(u))}.`},{visual_data:line(g,{marked:[u,d,m]}),animation:"place_rest",caption:"Place them all; further right is bigger.",spoken:"Now every value is on the line. Having more digits does not make a decimal bigger — position does."},{visual_data:line(g,{marked:[u,d,m],conclude_label:t}),animation:"reveal",caption:`${b==="ascending"?"Left to right":"Right to left"}: ${t}.`,spoken:`Reading ${b==="ascending"?"left to right":"right to left"} gives ${x}.`}]})}const s=t.match(/^(\d+(?:\.\d+)?)\s*([<>])\s*(\d+(?:\.\d+)?)$/);if(!s)return null;const o=Number(s[1]),r=Number(s[3]);if(!(s[2]===">"?o>r:o<r))return null;const l=Math.max(o,r);return buildCompareDemo(n,this.id,`Compare ${decStr(o)} and ${decStr(r)}`,[o,r],l,"greater")}},identify={id:"NPV.P7.M06.identify.v1",microSkillId:"NPV.P7.M06",forms:["number_line_identify"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=n.visualData;if(!t||t.type!=="number_line")return null;const e=t,s=e.unknown;if(s==null||firstNum(n.correctAnswer)===null||round6(firstNum(n.correctAnswer))!==round6(s))return null;const o=e.step??.01,r=Math.round((e.max-e.min)/o)+1;if(r>21||r<2)return null;const a={min:e.min,max:e.max,step:o},i=o<.01?.01:.1,l=round6(Math.floor(s/i)*i),u=Math.round(round6(s-l)/o),d=u>0&&u<=9?Array.from({length:u},(f,g)=>
71{const b=round6(l+g*o),x=round6(l+(g+1)*o);return{from:b,to:x,label:decStr(x)}}):[],m=[];if(o<.01)for(let f=round6(a.min+.01);f<round6(a.max-.005);f=round6(f+.01))m.push(f);const h=f=>[...new Set([...m,...f])];return buildExample({generatorId:this.id,question:n,title:"Which decimal does the arrow point to?",computed:d.length>0?{target:s,answer:s,coarse:l,hopCount:u,step:o}:{target:s,answer:s,step:o},steps:[{visual_data:line(a,{marked:m}),animation:"show_line",caption:`Each small step is ${decStr(o)}.`,spoken:`To read the arrow, we first need the size of each small step. The line is split into equal steps, and each one is ${nw(decStr(o))}.`},{visual_data:d.length>0?line(a,{marked:h([l]),hops:d}):line(a,{marked:h([s])}),animation:"count_along",caption:d.length>0?`Count on from ${decStr(l)}: ${u} step${u===1?"":"s"}.`:"Count the steps to the arrow.",spoken:d.length>0?`We start from ${nw(decStr(l))}, the labelled mark just before the arrow, and count on one small step at a time until we reach the arrow. That is ${numberToWords(u)} step${u===1?"":"s"}.`:"Counting the steps from the start lands us on the marked point."},{visual_data:d.length>0?line(a,{marked:h([l]),hops:d,unknown:s,conclude_label:`${decStr(s)}`}):line(a,{marked:h([s]),unknown:s,conclude_label:`${decStr(s)}`}),animation:"reveal",caption:`The arrow points to ${decStr(s)}.`,spoken:`Counting on lands exactly on ${nw(decStr(s))}, so that is the decimal the arrow points to.`}]})}},probe={id:"NPV.P7.M06.probe.v1",microSkillId:"NPV.P7.M06",forms:["misconception_contrast_probe"],generate(n){if(n.microSkillId!==this.microSkillId)return null;const t=normaliseStem(n.stem),e=t.match(/orders? ([\d.]+),\s*([\d.]+) and ([\d.]+) from (largest to smallest|smallest to largest) as ([\d.,\s]+?),?\s*judging by length/i);if(e){const u=[Number(e[1]),Number(e[2]),Number(e[3])],d=e[4].toLowerCase().startsWith("largest")?"desc":"asc",m=[...u].sort((x,y)=>d==="desc"?y-x:x-y),h=m.map(decStr).join(", ");if(n.correctAnswer.trim()!==h||u.some(x=>dpOf(x)>3||x<0||x>=100))return null;const f=decimalWindow(u);if(!f)return null;const g=d==="desc"?"largest":"smallest",b=m.map(x=>decimalToWords(decStr(x))).join(", ");return buildExample({generatorId:this.id,question:n,title:`Order these, ${g} first`,computed:{v0:u[0],v1:u[1],v2:u[2],answer:m[0]},steps:[{visual_data:line(f,{marked:u}),animation:"show_claim",caption:"A pupil ordered these by counting digits, not position.",spoken:"Judging by how many digits a decimal has does not tell you its size — only its position on the line does."},{visual_data:line(f,{marked:u}),animation:"place_rest",caption:"Place them all; further right is bigger.",spoken:"We place every value on the number line and compare where each one actually sits."},{visual_data:line(f,{marked:u,conclude_label:h}),animation:"reveal",caption:`Correct order, ${g} first: ${h}.`,spoken:`Reading the line gives the true order: ${b}.`}]})}if(!/which .*(greater|actually greater)/i.test(t))return null;const s=firstNum(n.correctAnswer);if(s===null)return null;const r=numsIn(t).filter(u=>Number.isFinite(u)).filter(u=>dpOf(u)>=1);if(r.length<2)return null;const[a,i]=r,l=Math.max(a,i);return round6(l)!==round6(s)?null:buildCompareDemo(n,this.id,`Which is greater: ${decStr(a)}, ${decStr(i)}?`,[a,i],l,"greater")}},NPV_P7_M06_GENERATORS=[compare,ordering,statement,identify,probe];function extractUnitNums(n){const t=[],e=/£\s?(\d+(?:\.\d+)?)|(\d+(?:\.\d+)?)p\b|(\d+(?:\.\d+)?)(?![.A-Za-z])/g;let s;for(;(s=e.exec(n))!==null;)s[1]!=null?t.push({v:Number(s[1]),kind:"pound"}):s[2]!=null?t.push({v:Number(s[2]),kind:"pence"}):t.push({v:Number(s[3]),kind:"plain"});return t}function stripWrongWorking(n){return n.split(new RegExp("(?<=[.?])\\s+")).filter(t=>!/[×÷=]|working out|by working/i.test(t)).join(" ").trim()}function convUnit(n,t,e){const s=t==="pound"||t==="pence",o=e==="pound"||e==="pence";return!s&&!o?n:s&&o?t===e?n:t==="pound"?n*100:n/100:null}function outUnitKind(n){return/\bin pence\b/i.test(n)?"pence":/\bin pounds?\b/i.test(n)?"pound":"plain"}function fmtVal(n,t){return t==="pound"?"£"+(Number.isInteger(n)?String(n):n.toFixed(2)):t==="pence"?`${n}p`:String(n)}function moneyWords(n,t,e){if(t==="pound"){const s=Math.floor(n+1e-9),o=Math.round((n-s)*100),r=`${numberToWordsLong(s)} pound${s===1?"":"s"}`;return o===0?r:`${r} ${numberToWordsLong(o)}`}return t==="pence"?`${numberToWordsLong(n)} pence`:e?`${numberToWordsLong(n)} ${e}`:numberToWordsLong(n)}function approxEq(n,t){return Math.abs(n-t)<1e-6}function parseMoneyOrNumber(n){const t=n.match(/\d+(?:\.\d+)?/);return t?Number(t[0]):null}const MICRO$3="RP.P3.M01",FORMS$3=["context_calculation","context_reasoning","misconception_contrast_probe","varied_symbolic_calculation"],INVERSE_MARKER=/\b(buy for|are needed|make with|can \w+ make|can you buy)\b/i;function cleanPer(n,t){return n<=0?!1:t?n>=.05&&approxEq(n*100,Math.round(n*100)):approxEq(n*10,Math.round(n*10))}const NOUN_STOP=new Set(["of","cost","costs","pack","packs","cover","covers","mix","mixes","use","uses","need","needs","make","makes","is","are","come","comes","takes","holds","buy","buys","get","gets","find","finds","how","many","much","does","do","did","same","rate","hour","hours","identical","matching","the","a","an","in","for","at","with","every","each","working","out","correct","actually","should"]);function headNoun(n,t,e=!1){return n&&UNIT_SPOKEN[n]!==void 0?n:t&&!e&&!NOUN_STOP.has(t)?t:n&&!NOUN_STOP.has(n)?n:""}function parseNounNums(n){const t=normaliseStem(n),e=[],s=/£\s?(\d+(?:\.\d+)?)|(\d+(?:\.\d+)?)p\b|(\d+(?:\.\d+)?)(?![.A-Za-z])/g;let o;for(;(o=s.exec(t))!==null;){let r,a;o[1]!=null?(r=Number(o[1]),a="pound"):o[2]!=null?(r=Number(o[2]),a="pence"):(r=Number(o[3]),a="plain");const l=t.slice(o.index+o[0].length).trimStart().split(/\s+/).slice(0,2),u=l.map(d=>d.toLowerCase().replace(/[^a-z²]/g,""));e.push({v:r,kind:a,noun:a==="plain"?headNoun(u[0],u[1],/[.?!]$/.test(l[0]??"")):""})}return e}const UNIT_SPOKEN={"m²":"square metres",m2:"square metres","cm²":"square centimetres",ml:"millilitres",l:"litres",g:"grams",kg:"kilograms",cm:"centimetres",km:"kilometres",mm:"millimetres"};function spokenNoun(n){return UNIT_SPOKEN[n.toLowerCase()]??n}
71function qty(n){if(n.kind==="pound"&&n.v>0&&n.v<1)return`${numberToWordsLong(Math.round(n.v*100))} pence`;if(n.kind==="pound"||n.kind==="pence")return moneyWords(n.v,n.kind);const t=Number.isInteger(n.v)?numberToWordsLong(n.v):decimalToWords(Math.round(n.v*100)/100);return n.noun?`${t} ${spokenNoun(n.noun)}`:t}function fmtQty(n,t,e){const s=fmtVal(n,t);return t==="plain"&&e?`${s} ${e}`:s}function areIs(n){return n===1?"is":"are"}function singular(n){return/ies$/.test(n)?n.replace(/ies$/,"y"):/(ches|shes|xes|sses)$/.test(n)?n.replace(/es$/,""):n.replace(/s$/,"")}function oneOf(n){return n?`one ${singular(spokenNoun(n))}`:"one"}function linkAmount(n,t,e,s,o,r){return t==="pound"||t==="pence"?`${n} ${o?"cost":"costs"} ${s}`:/^(seconds?|minutes?|hours?|days?)$/i.test(e)?`${n} ${o?"take":"takes"} ${s}`:`for ${n} there ${areIs(r)} ${s}`}function knowWant(n,t,e,s){return`${s&&n.noun?`${capitalised(qty(n))} cost ${qty(t)}.`:`For every ${qty(n)}, there ${areIs(n.v)} ${qty(t)}.`} ${e}`}const BAR_MAX_BOXES=12;function boxesFit(n,t){return!Number.isInteger(n)||n<2||n>BAR_MAX_BOXES?!1:t.length*12*.62<=306/n-7}function bar(n,t,e={}){return{type:"percent_bar",parts:n,whole_label:t,...e}}function oneLabel(n,t){return n?`1 ${singular(n)} = ${t}`:`1 = ${t}`}const RP_P3_M01_GENERATORS=[{id:`${MICRO$3}.unitary.v1`,microSkillId:MICRO$3,forms:FORMS$3,generate(n){const t=parseMoneyOrNumber(n.correctAnswer);if(t==null)return null;const e=stripWrongWorking(n.stem),s=extractUnitNums(e);if(s.length<2)return null;const o=parseNounNums(e),r=outUnitKind(n.stem),a=[[0,1],[1,0]],i=n.mathematicalForm==="misconception_contrast_probe"?n.stem.match(/\b([A-Z][a-z]+) finds? the cost of \d+ [a-z]+ by working out/i):null;if(/(?:how much does|how long does) (?:1|one) /i.test(e)){for(const[f,g]of a){const b=s[f],x=s[g],y=convUnit(x.v,x.kind,r);if(y==null||b.v<=0)continue;const E=y/b.v;if(!approxEq(E,t))continue;const k=r==="pound"||r==="pence";if(!cleanPer(t,k))return null;const T=o[f]?.noun??"",A={v:b.v,kind:"plain",noun:T},N=r==="plain"?o[g]?.noun??"":"",S={v:y,kind:r,noun:N},M={v:t,kind:r,noun:N},L=T?T.replace(/s$/,""):"item",P=fmtVal(y,r),F=fmtVal(t,r),D=boxesFit(b.v,F),U=fmtQty(y,r,N);return buildExample({generatorId:`${MICRO$3}.unitary.v1`,question:n,title:`How much does one ${L} cost?`,computed:{count:b.v,amount:y,answer:E},steps:[{visual_data:D?bar(b.v,U,{part_value:"?"}):{type:"equation_display",terms:[fmtQty(b.v,"plain",T),"→",P]},animation:D?"pb_split":"show_ratio",caption:`${fmtQty(b.v,"plain",T)} → ${P}`.slice(0,72),spoken:knowWant(A,S,k?`We want to know how much ${oneOf(T)} costs.`:`We want the amount for ${oneOf(T)}.`,k)},{visual_data:D?bar(b.v,U,{part_value:F}):{type:"equation_display",terms:[P,"÷",`${b.v}`]},animation:D?"pb_share":"divide_both",caption:`Share equally: ${P} ÷ ${b.v}`.slice(0,72),spoken:`Each one ${k?"costs":"is"} the same, so we share the total equally between the ${numberToWordsLong(b.v)}: ${qty(S)} divided by ${numberToWordsLong(b.v)}.`},{visual_data:D?bar(b.v,U,{part_value:F,lit:1,lit_label:`1 ${L} = ${F}`}):{type:"equation_display",terms:[`1 ${L}`,"=",F]},animation:D?"pb_shade":"write_answer",caption:`So one ${L} = ${F}`.slice(0,72),spoken:k?`Sharing gives ${qty(M)} each. So ${oneOf(T)} costs ${qty(M)}.`:`Sharing gives ${qty(M)} each. So ${linkAmount(oneOf(T),r,N,qty(M),!1,t)}.`}]})}return null}if(s.length<3)return null;const u=INVERSE_MARKER.test(n.stem),d=s[2],m=o[2]?.noun??"",h={v:d.v,kind:d.kind,noun:m};for(const[f,g]of a){const b=s[f],x=s[g];if(b.v<=0||x.v<=0)continue;const y=o[f]?.noun??"",E=o[g]?.noun??"",k={v:b.v,kind:"plain",noun:y},T=x.kind==="pound"||x.kind==="pence",A=x.v/b.v;if(!cleanPer(A,T))continue;const N=T?Math.round(A*100)/100:Math.round(A*10)/10;if(u){const K=convUnit(d.v,d.kind,x.kind);if(K==null)continue;const Z=K/N;if(!approxEq(Z,t))continue;const Q={v:x.v,kind:x.kind,noun:E},B={v:N,kind:x.kind,noun:E},G=fmtQty(x.v,x.kind,E),X=fmtQty(N,x.kind,E),ne=fmtQty(d.v,d.kind,m);return buildExample({generatorId:`${MICRO$3}.unitary.v1`,question:n,title:"How many can we get?",computed:{count:b.v,amount:x.v,per:N,spend:d.v,answer:t},steps:[{visual_data:{type:"equation_display",terms:[`${b.v}`,"→",G]},animation:"show_ratio",caption:`${fmtQty(b.v,"plain",y)} → ${G}`.slice(0,72),spoken:knowWant(k,Q,`We want to know how many ${y||""} we get for ${qty(h)}.`,T).replace(/\s+/g," ")},{visual_data:{type:"equation_display",terms:[G,"÷",`${b.v}`,"=",X]},animation:"divide_both",caption:`One: ${G} ÷ ${b.v} = ${X}`.slice(0,72),spoken:`First find the amount for ${oneOf(y)}, because then we can count how many fit. ${capitalised(qty(Q))}
71 divided by ${numberToWordsLong(b.v)} is ${qty(B)}, so ${linkAmount(oneOf(y),x.kind,E,qty(B),!1,N)}.`},{visual_data:{type:"equation_display",terms:[ne,"÷",X,"=",`${t}`]},animation:"write_answer",caption:`${ne} ÷ ${X} = ${t}`.slice(0,72),spoken:`Now count how many lots of ${qty(B)} fit into ${qty(h)}. ${capitalised(qty(h))} divided by ${qty(B)} is ${numberToWordsLong(t)}. So the answer is ${numberToWordsLong(t)}${y&&t!==1?` ${spokenNoun(y)}`:""}.`}]})}const S=N*d.v;if(!approxEq(S,t))continue;const M={v:x.v,kind:x.kind,noun:E},L={v:t,kind:x.kind,noun:E},P={v:N,kind:x.kind,noun:E},F=fmtQty(x.v,x.kind,E),D=fmtQty(N,x.kind,E),U=fmtQty(t,x.kind,E),q=`${capitalised(qty(M))} divided by ${numberToWordsLong(b.v)} is ${qty(P)}, so ${linkAmount(oneOf(y),x.kind,E,qty(P),!1,N)}.`,V=qty({...h,noun:m||(d.kind==="plain"?y:"")}),O=boxesFit(b.v,D)&&boxesFit(d.v,D),z=i?{caption:`${i[1]} multiplied the price of ${b.v} ${y||""} by ${d.v} — find one first: ${F}÷${b.v}=${D}`.replace(/\s+/g," ").slice(0,72),spoken:`${i[1]} multiplied the price of ${numberToWordsLong(b.v)} ${y||""} by ${numberToWordsLong(d.v)}, but that price is for ${numberToWordsLong(b.v)}, not for one. We need the price of ${oneOf(y)} first. ${q}`.replace(/\s+/g," ")}:{caption:`One first: ${F} ÷ ${b.v} = ${D}`.slice(0,72),spoken:`First find the amount for ${oneOf(y)}, because once we know one, we can make any number. ${q}`};return buildExample({generatorId:`${MICRO$3}.unitary.v1`,question:n,title:`How do we scale up to ${d.v} ${m||y||""}?`.replace(/\s+/g," ").trim(),computed:{count:b.v,amount:x.v,per:N,target:d.v,answer:t},steps:[{visual_data:O?bar(b.v,F,{part_value:"?"}):{type:"equation_display",terms:[`${b.v}`,"→",F]},animation:O?"pb_split":"show_ratio",caption:`${fmtQty(b.v,"plain",y)} → ${F}`.slice(0,72),spoken:knowWant(k,M,`We want the amount for ${V}.`,T)},{visual_data:O?bar(b.v,F,{part_value:D,lit:1,lit_label:oneLabel(y,D)}):{type:"equation_display",terms:[F,"÷",`${b.v}`,"=",D]},animation:O?"pb_share":"divide_both",caption:z.caption,spoken:z.spoken},{visual_data:O?bar(d.v,`${fmtQty(d.v,"plain",m||y)} = ${U}`,{part_value:D}):{type:"equation_display",terms:[D,"×",`${d.v}`,"=",U]},animation:O?"pb_build":"write_answer",caption:`${d.v} of them: ${D} × ${d.v} = ${U}`.slice(0,72),spoken:`Now multiply the amount for one by ${numberToWordsLong(d.v)}: ${qty(P)} times ${numberToWordsLong(d.v)} is ${qty(L)}. So ${linkAmount(V,x.kind,E,qty(L),!0,t)}.`}]})}if(!u)for(const[f,g]of a){const b=s[f],x=s[g];if(b.v<=0||x.v<=0)continue;const y=convUnit(d.v,d.kind,b.kind);if(y==null)continue;const E=y/b.v,k=Math.round(E*100)/100;if(!approxEq(E,k)||k<=0||approxEq(k,1))continue;const T=x.v*k;if(!approxEq(T,t))continue;const A=o[f]?.noun??"",N=o[g]?.noun??"",S={v:b.v,kind:"plain",noun:A},M={v:x.v,kind:x.kind,noun:N},L={v:t,kind:x.kind,noun:N},P=fmtQty(b.v,"plain",A),F=fmtQty(x.v,x.kind,N),D=fmtQty(d.v,d.kind,m),U=Number.isInteger(k)?String(k):k.toFixed(2).replace(/0$/,""),q=decimalToWords(k);return buildExample({generatorId:`${MICRO$3}.unitary.v1`,question:n,title:"What is the scale factor?",computed:{count:b.v,amount:x.v,target:d.v,factor:k,answer:t},steps:[{visual_data:{type:"equation_display",terms:[P,"→",F]},animation:"show_ratio",caption:`${P} → ${F}; want ${D}`.slice(0,72),spoken:knowWant(S,M,`We want the amount for ${qty(h)}.`,x.kind==="pound"||x.kind==="pence")},{visual_data:{type:"equation_display",terms:[`${d.v}`,"÷",`${b.v}`,"=",U]},animation:"divide_both",caption:`Scale factor: ${d.v} ÷ ${b.v} = ${U}`.slice(0,72),spoken:`The amount for one is not a neat number here, so instead we find how many times bigger ${qty(h)} is than ${qty(S)}. ${capitalised(numberToWordsLong(d.v))} divided by ${numberToWordsLong(b.v)} is ${q}. That is the scale factor.`},{visual_data:{type:"equation_display",terms:[F,"×",U,"=",fmtQty(t,x.kind,N)]},animation:"write_answer",caption:`${F} × ${U} = ${fmtQty(t,x.kind,N)}`.slice(0,72),spoken:`The amount grows by the same scale factor, so multiply ${qty(M)} by ${q}. That gives ${qty(L)}, so the answer is ${qty(L)}.`}]})}return null}}],MICRO$2="RP.P3.M02",FORMS$2=["context_calculation","context_reasoning","comparison_statement","misconception_contrast_probe","varied_symbolic_calculation"];function ratioOf(n){const t=n.match(/(\d+)\s*:\s*(\d+)(?:\s*:\s*(\d+))?/);if(!t)return null;const e=[Number(t[1]),Number(t[2])];return t[3]!=null&&e.push(Number(t[3])),e}function totalOf(n){const t=[/\bshares?\s+£?(\d+)/i,/\bsplits?\s+£?(\d+)/i,/£?(\d+)\b[^.]*?\bis\s+(?:shared|split)/i,/£?(\d+)\s+\w+\s+are\s+shared/i,/\btotal\s+is\s+£?(\d+)/i];for(const e of t){const s=n.match(e);if(s)return Number(s[1])}return null}function groupLabels(n,t){const e=n.visualData;if(e&&e.type==="ratio_bar")return[e.left_label,e.right_label];let s=t.match(/\b([A-Z][a-z]+) and ([A-Z][a-z]+)\b/);return s?[s[1],s[2]]:(s=t.match(/between ([\w -]+?) and ([\w -]+?)(?: in\b| the\b|\.)/i),s?[s[1].trim(),s[2].trim()]:["Part A","Part B"])}function fmtM(n,t){return t?`£${n}`:`${n}`}function wordsM(n,t){return t?`${numberToWordsLong(n)} pound${n===1?"":"s"}`:numberToWordsLong(n)}function partWord$1(n){return n===1?"part":"parts"}function claimRebuttal(n,t,e,s){let o=n.match(/\b([A-Z][a-z]+) says they each get £?(\d+(?:\.\d+)?)/i);if(o){const r=o[1],a=Number(o[2]);return{short:`${r}'s ${fmtM(a,s)} each`,spoken:`${r} says they would each get ${wordsM(a,s)}. But a ratio of ${numberToWordsLong(t)} to ${numberToWordsLong(e)} is not an equal split, so that is wrong.`}}
71if(o=n.match(/\b([A-Z][a-z]+) says the (larger|smaller) share is £?(\d+(?:\.\d+)?), which is half of (\d+(?:\.\d+)?)/i),o){const r=o[1],a=o[2],i=Number(o[3]),l=Number(o[4]);return{short:`${r} halves ${fmtM(l,s)}`,spoken:`${r} says the ${a} share is ${wordsM(i,s)}, half of ${wordsM(l,s)}. But halving only works for an equal one-to-one split — this ratio is ${numberToWordsLong(t)} to ${numberToWordsLong(e)}, so that is wrong.`}}return null}const buildBar=(n,t,e,s,o,r,a,i,l,u,d,m=null)=>{const h=t+e,f=s/h,g=()=>({type:"ratio_bar",left_label:o,right_label:r,left_parts:t,right_parts:e,total:s,show_part_values:!1,fit_bars:!0,named_colors:!0});return buildExample({generatorId:`${MICRO$2}.share.v1`,question:n,title:i,computed:{...d,parts:h,per:f},steps:[{visual_data:g(),animation:"rb_build",caption:m?`${m.short} is wrong: ${t} + ${e} = ${h} parts.`.slice(0,72):`${t} + ${e} = ${h} equal parts`.slice(0,72),spoken:m?`${m.spoken} A ratio shares things out in equal-sized parts, so we split the whole into ${numberToWordsLong(t)} plus ${numberToWordsLong(e)}, which is ${numberToWordsLong(h)} equal parts.`:`A ratio shares things out in equal-sized parts. So we split the whole into ${numberToWordsLong(t)} plus ${numberToWordsLong(e)}, which is ${numberToWordsLong(h)} equal parts.`},{visual_data:g(),animation:"rb_distribute",caption:`Each part = ${fmtM(s,l)} ÷ ${h} = ${fmtM(f,l)}`.slice(0,72),spoken:`We share the total equally across every part: ${wordsM(s,l)} divided by ${numberToWordsLong(h)} is ${wordsM(f,l)} in each part.`},{visual_data:g(),animation:`rb_share:${a}`,caption:u.caption.slice(0,72),spoken:u.spoken}]})};function acceptedNums(n){return(n.match(/\d+(?:\.\d+)?/g)??[]).map(Number)}function threePartShare(n,t,e){const[s,o,r]=e;if(s<1||o<1||r<1)return null;const a=totalOf(t);if(a==null)return null;const i=s+o+r;if(a%i!==0)return null;const l=a/i,u=/£/.test(n.stem),d=acceptedNums(n.correctAnswer);if(d.length<1)return null;const m=[s,o,r],h=/\bone part\b/i.test(t),f=/\blargest\b/i.test(t),g=/\bsmallest\b/i.test(t);let b=null,x;if(h)x="one";else if(f)b=m.indexOf(Math.max(...m)),x=["left","right","third"][b];else if(g)b=m.indexOf(Math.min(...m)),x=["left","right","third"][b];else return null;const y=b==null?l:l*m[b];if(!approxEq(d[0],y))return null;const[E,k,T]=["Part A","Part B","Part C"],A=()=>({type:"ratio_bar",left_label:E,right_label:k,third_label:T,left_parts:s,right_parts:o,third_parts:r,total:a,show_part_values:!1,fit_bars:!0,named_colors:!0}),N=x==="one"?"one part":f?"the largest share":"the smallest share",S=x==="one"||b==null?1:m[b],M=x==="one"?{caption:`One part = ${fmtM(y,u)}`.slice(0,72),spoken:`Every part is the same size, so one part is ${wordsM(y,u)}.`}:{caption:`${N}: ${S} × ${fmtM(l,u)} = ${fmtM(y,u)}`.slice(0,72),spoken:`${capitalise(N)} is ${numberToWordsLong(S)} ${partWord$1(S)}, and each part is ${wordsM(l,u)}. So ${N} is ${wordsM(y,u)}.`};return buildExample({generatorId:`${MICRO$2}.share.v1`,question:n,title:`How do we share ${fmtM(a,u)} in three parts?`,computed:{first:s,second:o,third:r,parts:i,per:l,answer:y},steps:[{visual_data:A(),animation:"rb_build",caption:`${s} + ${o} + ${r} = ${i} equal parts`.slice(0,72),spoken:`A ratio shares things out in equal-sized parts. So we split the whole into ${numberToWordsLong(s)}, ${numberToWordsLong(o)} and ${numberToWordsLong(r)}, which is ${numberToWordsLong(i)} equal parts.`},{visual_data:A(),animation:"rb_distribute",caption:`Each part = ${fmtM(a,u)} ÷ ${i} = ${fmtM(l,u)}`.slice(0,72),spoken:`We share the total equally across every part: ${wordsM(a,u)} divided by ${numberToWordsLong(i)} is ${wordsM(l,u)} in each part.`},{visual_data:A(),animation:`rb_share:${x}`,caption:M.caption,spoken:M.spoken}]})}function capitalise(n){return n.charAt(0).toUpperCase()+n.slice(1)}const RP_P3_M02_GENERATORS=[{id:`${MICRO$2}.share.v1`,microSkillId:MICRO$2,forms:FORMS$2,generate(n){const t=normaliseStem(n.stem),e=ratioOf(t);if(e&&e.length===3)return threePartShare(n,t,[e[0],e[1],e[2]]);if(!e||e.length!==2)return null;const[s,o]=e;if(s<1||o<1)return null;const[r,a]=groupLabels(n,t),i=totalOf(t),l=/£/.test(n.stem);if(i!=null){const T=s+o;if(i%T!==0)return null;const A=i/T,N=acceptedNums(n.correctAnswer),S=/\bone part\b/i.test(t),M=/\btwo (?:shares|amounts)\b/i.test(t),L=/\bhow (?:many|much) more\b|\bmore\b.*\bthan\b/i.test(t),P=/\blarger|\bbigger/i.test(t),F=/\bsmaller/i.test(t);let D=null;if(S)D="one";else if(M)D="both";else if(L)D="diff";else if(P)D="larger";else if(F)D="smaller";else{const z=t.slice(t.search(/how (?:many|much)|what|should/i));r!=="Part A"&&z.toLowerCase().includes(r.toLowerCase())?D="left":a!=="Part B"&&z.toLowerCase().includes(a.toLowerCase())&&(D="right")}if(D==null)return null;const U={one:A,left:A*s,right:A*o,larger:A*Math.max(s,o),smaller:A*Math.min(s,o),diff:A*Math.abs(s-o),both:0},q=claimRebuttal(t,s,o,l);if(D==="both"){const z=A*s,K=A*o;return N.length<2||N[0]!==z||N[1]!==K?null:buildBar(n,s,o,i,r,a,"both",`How do we share ${fmtM(i,l)}?`,l,{caption:`${capitalise(r)}: ${s} × ${fmtM(A,l)} = ${fmtM(z,l)}, ${a}: ${o} × ${fmtM(A,l)} = ${fmtM(K,l)}`.slice(0,72),spoken:`${capitalise(r)} has ${numberToWordsLong(s)} ${partWord$1(s)}, worth ${wordsM(z,l)}. ${capitalise(a)} has ${numberToWordsLong(o)} ${partWord$1(o)}, worth ${wordsM(K,l)}. So the two shares are ${wordsM(z,l)} and ${wordsM(K,l)}.`},{first:s,second:o,per:A,answer:z,right_value:K},q)}const V=U[D];if(N.length<1||!approxEq(N[0],V))return null;const O={one:{caption:`One part = ${fmtM(V,l)}`.slice(0,72),spoken:`Every part is the same size, so one part is ${wordsM(V,l)}.`},left:{caption:`${capitalise(r)}: ${s} × ${fmtM(A,l)} = ${fmtM(V,l)}`.slice(0,72),spoken:`${capitalise(r)} has ${numberToWordsLong(s)} ${partWord$1(s)}, and each part is ${wordsM(A,l)}. So ${r} gets ${wordsM(V,l)}.`},right:{caption:`${capitalise(a)}: ${o} × ${fmtM(A,l)} = ${fmtM(V,l)}`.slice(0,72),spoken:`${capitalise(a)} has ${numberToWordsLong(o)} ${partWord$1(o)}, and each part is ${wordsM(A,l)}. So ${a} gets ${wordsM(V,l)}.`},larger:{caption:`Larger: ${Math.max(s,o)} × ${fmtM(A,l)} = ${fmtM(V,l)}`.slice(0,72),spoken:`The larger share has ${numberToWordsLong(Math.max(s,o))} ${partWord$1(Math.max(s,o))}, and each part is ${wordsM(A,l)}. So the larger share is ${wordsM(V,l)}.`},smaller:{caption:`Smaller: ${Math.min(s,o)} × ${fmtM(A,l)} = ${fmtM(V,l)}`.slice(0,72),spoken:`The smaller share has ${numberToWordsLong(Math.min(s,o))} ${partWord$1(Math.min(s,o))}, and each part is ${wordsM(A,l)}. So the smaller share is ${wordsM(V,l)}.`},diff:{caption:`${Math.max(s,o)}−${Math.min(s,o)}=${Math.abs(s-o)}; ×${fmtM(A,l)}=${fmtM(V,l)}`.slice(0,72),spoken:`The bigger share has ${numberToWordsLong(Math.max(s,o))} parts and the smaller has ${numberToWordsLong(Math.min(s,o))}, so the difference is ${numberToWordsLong(Math.max(s,o))} take away ${numberToWordsLong(Math.min(s,o))}, which is ${numberToWordsLong(Math.abs(s-o))} more ${partWord$1(Math.abs(s-o))}, worth ${wordsM(V,l)}.`}};return buildBar(n,s,o,i,r,a,D,`How do we share ${fmtM(i,l)}?`,l,O[D],{first:s,second:o,diff:Math.abs(s-o),per:A,answer:V},q)}const u=parseMoneyOrNumber(n.correctAnswer);if(u==null)return null;let d=null,m=0,h=0,f="the total",g="named",b;if(b=t.match(/(?:receives|gets|is)\s+£?(\d+)\s+more|£?(\d+)\s+more\b/i))d=Number(b[1]??b[2]),m=Math.abs(s-o),h=s+o,g="diff";else if(b=t.match(/smaller[^.\d]*?(\d+)/i))d=Number(b[1]),m=Math.min(s,o),h=s+o,g="smaller";else if(b=t.match(/larger[^.\d]*?(\d+)/i))d=Number(b[1]),m=Math.max(s,o),h=s+o,g="larger";else{const T=t.match(/(\d+)\s+([a-z]+)/i);T&&(d=Number(T[1]),m=s,h=o,f="the other share")}if(d==null||m<1||d%m!==0)return null;const x=d/m,y=x*h;if(!approxEq(y,u))return null;const E=g==="diff"?{caption:`${Math.max(s,o)} − ${Math.min(s,o)} = ${m} ${partWord$1(m)} = ${fmtM(d,l)}`.slice(0,72),spoken:`The difference between the shares is ${numberToWordsLong(Math.max(s,o))} take away ${numberToWordsLong(Math.min(s,o))}, which is ${numberToWordsLong(m)} ${partWord$1(m)}, worth ${wordsM(d,l)}.`}:g==="smaller"?{caption:`Smaller = ${m} ${partWord$1(m)} = ${fmtM(d,l)}`.slice(0,72),spoken:`The smaller share is ${numberToWordsLong(m)} ${partWord$1(m)} of the ratio, worth ${wordsM(d,l)}.`}:g==="larger"?{caption:`Larger = ${m} ${partWord$1(m)} = ${fmtM(d,l)}`.slice(0,72),spoken:`The larger share is ${numberToWordsLong(m)} ${partWord$1(m)} of the ratio, worth ${wordsM(d,l)}.`}:{caption:`${m} ${partWord$1(m)} = ${fmtM(d,l)}`.slice(0,72),spoken:`The amount we are given, ${wordsM(d,l)}, is worth ${numberToWordsLong(m)} ${partWord$1(m)} of the ratio.`},k=f==="the total"?{caption:`total = ${s} + ${o} = ${h} ${partWord$1(h)}; ${fmtM(x,l)} × ${h} = ${fmtM(y,l)}`.slice(0,72),spoken:`The total is ${numberToWordsLong(s)} plus ${numberToWordsLong(o)}, which is ${numberToWordsLong(h)} ${partWord$1(h)}. ${capitalise(wordsM(x,l))} times ${numberToWordsLong(h)} is ${wordsM(y,l)}, so the total is ${wordsM(y,l)}.`}:{caption:`${f}: ${fmtM(x,l)} × ${h} = ${fmtM(y,l)}`.slice(0,72),spoken:`Then ${f} is ${numberToWordsLong(h)} ${partWord$1(h)}. ${capitalise(wordsM(x,l))} times ${numberToWordsLong(h)} is ${wordsM(y,l)}, so ${f} is ${wordsM(y,l)}.`};return buildExample({generatorId:`${MICRO$2}.share.v1`,question:n,title:"Working back from one share",computed:{given:d,givenParts:m,onePart:x,targetParts:h,first:s,second:o,answer:u},steps:[{visual_data:{type:"equation_display",terms:[`${m} parts`,"=",fmtM(d,l)]},animation:"show_ratio",caption:E.caption,spoken:E.spoken},{visual_data:{type:"equation_display",terms:[fmtM(d,l),"÷",`${m}`,"=",fmtM(x,l)]},animation:"divide_both",caption:`One part = ${fmtM(d,l)} ÷ ${m} = ${fmtM(x,l)}`.slice(0,72),spoken:`To find one part, we share ${wordsM(d,l)} equally between ${numberToWordsLong(m)} ${partWord$1(m)}. One part is ${wordsM(x,l)}.`},{visual_data:{type:"equation_display",terms:[fmtM(x,l),"×",`${h}`,"=",fmtM(y,l)]},animation:"write_answer",caption:k.caption,spoken:k.spoken}]})}}],MICRO$1="RP.P3.M03",FORMS$1=["ratio_interpretation","context_calculation","ratio_notation","misconception_contrast_probe","varied_symbolic_calculation"];function gcd(n,t){for(;t;)[n,t]=[t,n%t];return n}function partWord(n){return n===1?"part":"parts"}function partsOf(n){const t=n.match(/(\d+)\s*:\s*(\d+)(?:\s*:\s*(\d+))?/);if(t)return t[3]!=null?null:{a:Number(t[1]),b:Number(t[2]),source:"colon"};const e=n.match(/for every (\d+) [a-z]+[^.]*?(\d+) [a-z]+/i);if(e)return{a:Number(e[1]),b:Number(e[2]),source:"forEvery"};const s=n.match(/(\d+)\/(\d+)/);if(s){const r=Number(s[1]),a=Number(s[2]);if(a>r&&r>0)return{a:r,b:a-r,source:"fraction",whole:a}}const o=n.match(/(\d+)%/);if(o){const r=Number(o[1]);if(r>0&&r<100)return{a:r,b:100-r,source:"percentage",whole:100}}return null}function names(n){let t=n.match(/ratio of ([a-z]+) to ([a-z]+)/i);return t?[t[1],t[2]]:(t=n.match(/\b([a-z]+) and ([a-z]+)\b[^.]*?\bratio\b/i),t?[t[1],t[2]]:(t=n.match(/for every \d+ ([a-z]+)[^.]*?\d+ ([a-z]+)/i),t?[t[1],t[2]]:(t=n.match(/\d+\/\d+ of (?:the |a )?[a-z]+ ([a-z]+)[^.]*?\brest (?:
71are |is )?([a-z]+)/i),t?[t[1],t[2]]:(t=n.match(/(\d+)% of (?:the |a )?[a-z]+ is ([a-z]+)[^.]*?\brest is ([a-z]+)/i),t?[t[2],t[3]]:(t=n.match(/\b(?:wore|are|is|were)\s+([a-z]+)\b/i),t?[t[1],`not ${t[1]}`]:["Group A","Group B"])))))}const GENERIC_NOUNS=new Set(["team","club","crowd","fans","people","animals","pupils","visitors","tiles","counters","marbles","beads","sweets","cars","vehicles","matches","games","fruit","drink","school","team's","the"]);function askNoun(n){const t=n.search(/\bwhat |\bwrite /i),s=(t>=0?n.slice(t):n).replace(/[^a-z ]/gi," ").toLowerCase().split(/\s+/).filter(Boolean);for(let o=s.length-1;o>=0;o--)if(!GENERIC_NOUNS.has(s[o])&&s[o].length>2&&!/^(are|is|the|of|percentage|percent|fraction|ratio|what|write|actually|preferred|support)$/.test(s[o]))return s[o];return null}const barVisual=(n,t,e,s)=>({type:"ratio_bar",left_label:e,right_label:s,left_parts:n,right_parts:t,show_part_values:!1,fit_bars:!0,named_colors:!0}),barVisual3=(n,t,e,s,o,r)=>({type:"ratio_bar",left_label:s,right_label:o,third_label:r,left_parts:n,right_parts:t,third_parts:e,show_part_values:!1,fit_bars:!0,named_colors:!0});function threeNames(n){const t=n.match(/\b(?:in|among|between)\s+([a-z]+),\s*([a-z]+)\s+and\s+([a-z]+)\b/i);return t?[t[1],t[2],t[3]]:null}function threePartFraction(n,t,e,s,o){const[r,a,i]=o;if(r<1||a<1||i<1||r+a+i>120)return null;const l=threeNames(t);if(!l)return null;const[u,d,m]=l,h=r+a+i,f=[r,a,i],g=[u,d,m],b=askNoun(t);if(!b)return null;const x=g.findIndex(L=>L.toLowerCase()===b.toLowerCase());if(x<0)return null;const y=f[x];if((s==="fraction"?`${y}/${h}`:`${Math.round(y/h*100)}%`)!==e)return null;const k=["left","right","third"][x],T=g[x].toLowerCase(),A=Math.round(y/h*100),N=s==="fraction"?`rb_frac:${k}`:`rb_pct:${k}`,S=s==="fraction"?`${T}: ${y} out of ${h} = ${y}/${h}`.slice(0,72):`${y} out of ${h} = ${A}%`.slice(0,72),M=s==="fraction"?`So the fraction is ${numberToWordsLong(y)} out of ${numberToWordsLong(h)} — remember it is out of the WHOLE, not just the other groups.`:`So that is ${numberToWordsLong(y)} out of ${numberToWordsLong(h)}, which is ${numberToWordsLong(A)} percent.`;return buildExample({generatorId:`${MICRO$1}.part-whole.v1`,question:n,title:s==="fraction"?`What fraction are ${T}?`:`What percentage are ${T}?`,computed:{first:r,second:a,third:i,whole:h,part:y,percent:A,answer:y},steps:[{visual_data:barVisual3(r,a,i,u,d,m),animation:"rb_build",caption:`${r} + ${a} + ${i} = ${h} equal parts`.slice(0,72),spoken:`The ratio has ${numberToWordsLong(r)} ${partWord(r)}, ${numberToWordsLong(a)} ${partWord(a)} and ${numberToWordsLong(i)} ${partWord(i)}, which is ${numberToWordsLong(h)} equal parts altogether.`},{visual_data:barVisual3(r,a,i,u,d,m),animation:`rb_shade:${k}`,caption:`${y} of the ${h} parts are ${T}`.slice(0,72),spoken:`We shade the ${T} group: ${numberToWordsLong(y)} of the ${numberToWordsLong(h)} parts.`},{visual_data:barVisual3(r,a,i,u,d,m),animation:N,caption:S,spoken:M}]})}const RP_P3_M03_GENERATORS=[{id:`${MICRO$1}.part-whole.v1`,microSkillId:MICRO$1,forms:FORMS$1,generate(n){const t=normaliseStem(n.stem),e=n.correctAnswer.replace(/\s+/g,""),s=askedRep(t);if(s&&s!=="ratio"){const N=t.match(/(\d+)\s*:\s*(\d+)\s*:\s*(\d+)/);if(N){const S=threePartFraction(n,t,e,s,[Number(N[1]),Number(N[2]),Number(N[3])]);return S||null}}if(!s)return null;const o=partsOf(t);if(!o)return null;const{a:r,b:a,source:i,whole:l}=o;if(r<1||a<1||r+a>120)return null;const[u,d]=names(t),m=r+a;if(s==="ratio"){const N=gcd(r,a),S=r/N,M=a/N;if(`${S}:${M}`!==e)return null;const L=i==="fraction"||i==="percentage",P=N>1;if(L||P){const F=L?{visual_data:{type:"equation_display",terms:[`${l}`,"−",`${r}`,"=",`${a}`]},animation:"derive_part",caption:`${d} = ${l} − ${r} = ${a}`.slice(0,72),spoken:`A ratio needs both parts, but we are only told one, so we work out the other part: ${numberToWordsLong(l)} take away ${numberToWordsLong(r)} leaves ${numberToWordsLong(a)}.`}:{visual_data:{type:"equation_display",terms:[`${r}`,":",`${a}`]},animation:"show_ratio",caption:`${u} to ${d} = ${r} : ${a}`.slice(0,72),spoken:`The ratio is given as ${numberToWordsLong(r)} to ${numberToWordsLong(a)}.`},D=P?{visual_data:{type:"equation_display",terms:[`${r}:${a}`,"÷",`${N}`,"=",`${S}:${M}`]},animation:"simplify",caption:`÷${N} both sides: ${r}:${a} = ${S}:${M}`.slice(0,72),spoken:`Dividing both parts by ${numberToWordsLong(N)} simplifies the ratio to ${numberToWordsLong(S)} to ${numberToWordsLong(M)}.`}:{visual_data:barVisual(S,M,u,d),animation:"rb_build",caption:`${S} + ${M} = ${S+M} equal parts`.slice(0,72),spoken:`Now we build the bar to see the ratio: ${numberToWordsLong(S)} equal parts and ${numberToWordsLong(M)} equal parts, which makes ${numberToWordsLong(S+M)} equal parts in all.`},U={visual_data:barVisual(S,M,u,d),animation:"rb_ratio",caption:`The ratio is ${S} : ${M}
71`.slice(0,72),spoken:`So, in simplest form, the ratio is ${numberToWordsLong(S)} to ${numberToWordsLong(M)}.`};return buildExample({generatorId:`${MICRO$1}.part-whole.v1`,question:n,title:"Writing it as a ratio",computed:{first:r,second:a,whole:l??r+a,gcd:N,simpFirst:S,simpSecond:M,answer:S},steps:[F,D,U]})}return buildExample({generatorId:`${MICRO$1}.part-whole.v1`,question:n,title:"Writing it as a ratio",computed:{first:S,second:M,whole:S+M,answer:S},steps:[{visual_data:barVisual(S,M,u,d),animation:"rb_build",caption:`${S} + ${M} = ${S+M} equal parts`.slice(0,72),spoken:`We split the whole into ${numberToWordsLong(S+M)} equal parts.`},{visual_data:barVisual(S,M,u,d),animation:"rb_shade:left",caption:`${S} parts, then the other ${M}`.slice(0,72),spoken:`One group takes ${numberToWordsLong(S)} of the parts, the other takes ${numberToWordsLong(M)}.`},{visual_data:barVisual(S,M,u,d),animation:"rb_ratio",caption:`The ratio is ${S} : ${M}`.slice(0,72),spoken:`So, comparing the two groups part to part, the ratio is ${numberToWordsLong(S)} to ${numberToWordsLong(M)}.`}]})}const h=N=>{const S=N==="left"?r:a;return s==="fraction"?`${S}/${m}`:`${Math.round(S/m*100)}%`};let f=null;if(h("left")===e?f="left":h("right")===e&&(f="right"),!f)return null;const g=f==="left"?r:a;let b=u,x=d;if(u==="Group A"){const N=askNoun(t)??"this group";f==="left"?(b=N,x="the rest"):(x=N,b="the rest")}const y=(f==="left"?b:x).toLowerCase(),E=Math.round(g/m*100),k=s==="fraction"?`rb_frac:${f}`:`rb_pct:${f}`,T=s==="fraction"?`${y}: ${g} out of ${m} = ${g}/${m}`.slice(0,72):`${g} out of ${m} = ${E}%`.slice(0,72),A=s==="fraction"?`So the fraction is ${numberToWordsLong(g)} out of ${numberToWordsLong(m)} — remember it is out of the whole, not the other group.`:`So that is ${numberToWordsLong(g)} out of ${numberToWordsLong(m)}, which is ${numberToWordsLong(E)} percent.`;return buildExample({generatorId:`${MICRO$1}.part-whole.v1`,question:n,title:s==="fraction"?`What fraction are ${y}?`:`What percentage are ${y}?`,computed:{first:r,second:a,whole:m,part:g,percent:E,answer:g},steps:[{visual_data:barVisual(r,a,b,x),animation:"rb_build",caption:`${r} + ${a} = ${m} equal parts`.slice(0,72),spoken:`The ratio has ${numberToWordsLong(r)} ${partWord(r)} to ${numberToWordsLong(a)} ${partWord(a)}, which is ${numberToWordsLong(m)} equal parts altogether.`},{visual_data:barVisual(r,a,b,x),animation:`rb_shade:${f}`,caption:`${g} of the ${m} parts are ${y}`.slice(0,72),spoken:`We shade the ${y} group: ${numberToWordsLong(g)} of the ${numberToWordsLong(m)} parts.`},{visual_data:barVisual(r,a,b,x),animation:k,caption:T,spoken:A}]})}}];function askedRep(n){const t=n.search(/\bwhat |\bwrite /i),e=t>=0?n.slice(t):n;return/fraction/i.test(e)?"fraction":/percentage|percent|%/i.test(e)?"percentage":/ratio/i.test(e)?"ratio":null}const MICRO="RP.P3.M04",FORMS=["context_calculation","context_reasoning","ratio_interpretation","equation_completion","misconception_contrast_probe","varied_symbolic_calculation"],CM_PER={cm:1,m:100,km:1e5};function convLen(n,t,e){return n*CM_PER[t]/CM_PER[e]}function unitWord(n){return n==="km"?"kilometres":n==="m"?"metres":"centimetres"}function normUnit(n){const t=n.toLowerCase();return t==="km"?"km":t==="cm"||t==="centimetres"||t==="centimetre"?"cm":"m"}function lengthTokens(n){const t=[],e=/(\d+(?:\.\d+)?)\s*(km|centimetres|centimetre|cm|metres|metre|m)\b/gi;let s;for(;(s=e.exec(n))!==null;)t.push({v:Number(s[1]),unit:normUnit(s[2])});return t}function askedUnit(n){return/\bin (?:km|kilometres)\b/i.test(n)?"km":/\bin (?:cm|centimetres)\b/i.test(n)?"cm":/\bin (?:m|metres)\b/i.test(n)?"m":null}function unitOf(n){const t=n.match(/\b(km|kilometres|centimetres|cm|metres|m)\b/i);return t?normUnit(t[1]):null}function smallWord(n,t){return n==="map"?"the map":/\bmodel\b/i.test(t)?"the model":/\bmini-map\b/i.test(t)?"the mini-map":/\bmap\b/i.test(t)?"the map":"the drawing"}function largeWord(n){return/\bgame world\b/i.test(n)?"the game world":"real life"}function inLargeTail(n,t){return n==="sf"?"after enlarging":t==="real life"?"in real life":`in ${t}`}function onSmallTail(n,t){return n==="sf"?"before enlarging":`on ${t}`}function scaleMisconceptionNote(n){let t=n.match(/\b([A-Z][a-z]+) says[^.]*?because (\d+(?:\.\d+)?)\s*÷\s*(\d+(?:\.\d+)?)\s*=\s*(\d+(?:\.\d+)?)/i);if(t){const[,e,s,o,r]=t;return{subject:e,spoken:`${e} divided ${numberToWordsLong(Number(s))} by ${numberToWordsLong(Number(o))} to get ${numberToWordsLong(Number(r))}. But scale problems are solved by multiplying by the scale factor, not dividing, so that is wrong.`}}if(t=n.match(/\b([A-Z][a-z]+) says[^.]*?because (\d+(?:\.\d+)?)\s*\+\s*(\d+(?:\.\d+)?)\s*=\s*(\d+(?:\.\d+)?)/i),t){const[,e,s,o,r]=t;return{subject:e,spoken:`${e} added ${numberToWordsLong(Number(s))} and ${numberToWordsLong(Number(o))} to get ${numberToWordsLong(Number(r))}. But scale problems are solved by multiplying by the scale factor, not adding, so that is wrong.`}}return null}function doubleLineFits(n,t){return!Number.isInteger(n)||n<2||n>8||!Number.isInteger(t)?!1:(String(n*t).length+1)*n<=28}function doubleLine(n,t,e,s){const o=Array.from({length:n+1},(r,a)=>a);return{type:"number_line",min:0,max:n,step:1,hops:o.slice(1,s.hops+1).map(r=>r===1?{from:0,to:1,label:"1 cm"}:{from:r-1,to:r,label:""}),tick_labels:Object.fromEntries(o.map(r=>[r,r===n?s.topLast:String(r)])),under_labels:Object.fromEntries(o.filter(r=>r<=s.underUpTo||r===n).map(r=>[r,r===n?s.underLast:r===0?`0 ${e}`:String(r*t)]))}}function lineIntro(n,t,e,s){return n==="sf"?"On the top of the line we count centimetres before enlarging, and underneath the centimetres after enlarging.":`On the top of the line we count centimetres on ${t}, and underneath the ${s} ${e==="real life"?"in real life":`in ${e}`}.`}const RP_P3_M04_GENERATORS=[{id:`${MICRO}.scale.v1`,microSkillId:MICRO,forms:FORMS,generate(n){const t=parseMoneyOrNumber(n.correctAnswer);if(t==null)return null;const e=t,s=normaliseStem(n.stem),o=askedUnit(s)??unitOf(n.correctAnswer)??"cm",r=unitWord(o);let a=null,i="cm",l=null,u;if((u=s.match(/1\s*cm\b[^.\d]*?(?:represents|=|to)\s+(\d+)\s*(km|metres|metre|m)\b/i))?(a=Number(u[1]),i=normUnit(u[2]),l="map"):(u=s.match(/1\s*:\s*(\d+)/))?(a=Number(u[1]),i="cm",l="ratio"):(u=s.match(/scale factor (\d+)/i))&&(a=Number(u[1]),i="cm",l="sf"),a==null||a<=0||l==null)return null;const d=a,m=l,h=smallWord(m,s),f=largeWord(s),g=s.match(/(\d+(?:\.\d+)?)\s*cm by (\d+(?:\.\d+)?)\s*cm/i);if(g&&/perimeter/i.test(s)){const E=Number(g[1]),k=Number(g[2]),T=2*(E+k),A=T*a;return approxEq(A,t)?buildExample({generatorId:`${MICRO}.scale.v1`,question:n,title:"The perimeter after enlarging",computed:{width:E,height:k,perimeter:T,factor:a,two:2,answer:t},steps:[{visual_data:{type:"equation_display",terms:[`2 × (${E} + ${k})`,"=",`${T}`]},animation:"show_ratio",caption:`Perimeter now = 2 × (${E} + ${k}) = ${T} cm`.slice(0,72),spoken:`First we find the perimeter now: two lots of the width plus the height, which is ${numberToWordsLong(T)} centimetres.`},{visual_data:{type:"equation_display",terms:[`${T}`,"×",`${a}`]},animation:"divide_both",caption:`Enlarge every length: × ${a}`.slice(0,72),spoken:`A scale factor of ${numberToWordsLong(a)} multiplies every length, so the perimeter is multiplied by ${numberToWordsLong(a)} too.`},{visual_data:{type:"equation_display",terms:[`${T} × ${a}`,"=",`${t}`]},animation:"write_answer",caption:`${T} × ${a} = ${t}
71 cm`.slice(0,72),spoken:`So the enlarged perimeter is ${numberToWordsLong(t)} centimetres.`}]}):null}const b=lengthTokens(s).filter(E=>!(E.v===1&&E.unit==="cm")&&E.v!==a),x=unitWord(i);for(const E of b){const k=y(E);if(k)return k}return null;function y(E){const k=E.v*d,T=convLen(k,i,o),A=convLen(E.v,E.unit,i),N=A/d,S=convLen(N,"cm",o),M=m==="map"?`1 cm stands for ${d} ${i}`:m==="ratio"?`1 : ${d} — real is ${d}× as big`:`Scale factor ${d}: every length × ${d}`,L=m==="map"?`On this map, one centimetre stands for ${numberToWordsLong(d)} ${x}.`:m==="ratio"?`A scale of one to ${numberToWordsLong(d)} means the real thing is ${numberToWordsLong(d)} times as big as ${h}.`:`A scale factor of ${numberToWordsLong(d)} multiplies every length by ${numberToWordsLong(d)}.`,P=scaleMisconceptionNote(s),F=P?`${P.subject} is wrong: ${M}`.slice(0,72):M.slice(0,72),D=P?`${P.spoken} ${L}`:L;if(E.unit==="cm"&&approxEq(T,e)){const U=i!==o;if(!U&&doubleLineFits(E.v,d)){const z=E.v;return buildExample({generatorId:`${MICRO}.scale.v1`,question:n,title:`How big is it ${inLargeTail(m,f)}?`,computed:{factor:d,given:E.v,real:k,answer:e},steps:[{visual_data:doubleLine(z,d,i,{hops:1,topLast:`${z} cm`,underUpTo:1,underLast:""}),animation:"show_ratio",caption:F.slice(0,72),spoken:`${D} ${lineIntro(m,h,f,x)}`},{visual_data:doubleLine(z,d,i,{hops:z,topLast:`${z} cm`,underUpTo:z-1,underLast:"?"}),animation:"count_jumps",caption:`${E.v} cm × ${d}`.slice(0,72),spoken:`${m==="sf"?`The side is ${numberToWordsLong(E.v)} centimetres.`:`The length on ${h} is ${numberToWordsLong(E.v)} centimetres.`} Every jump of one centimetre adds another ${numberToWordsLong(d)} ${x} underneath, so we multiply ${numberToWordsLong(E.v)} by ${numberToWordsLong(d)}.`},{visual_data:doubleLine(z,d,i,{hops:z,topLast:`${z} cm`,underUpTo:z-1,underLast:`${e} ${o}`}),animation:"write_answer",caption:`${E.v} × ${d} = ${e} ${o}`.slice(0,72),spoken:`Multiplying gives ${numberToWordsLong(e)} ${r} ${inLargeTail(m,f)}.`}]})}const q=m==="sf"?`The side is ${numberToWordsLong(E.v)} centimetres; we multiply by the scale factor, ${numberToWordsLong(d)}.`:`The length on ${h} is ${numberToWordsLong(E.v)} centimetres, so we multiply it by ${numberToWordsLong(d)}.`,V=U?{visual_data:{type:"equation_display",terms:[`${E.v} cm`,"×",`${d}`,"=",`${k} ${i}`]},caption:`${E.v} × ${d} = ${k} ${i}`.slice(0,72),spoken:`${q} That gives ${numberToWordsLong(k)} ${x}.`}:{visual_data:{type:"equation_display",terms:[`${E.v} cm`,"×",`${d}`]},caption:`${E.v} cm × ${d}`.slice(0,72),spoken:q},O=U?{visual_data:{type:"equation_display",terms:[`${k} ${i}`,"=",`${e} ${o}`]},caption:`${k} ${i} = ${e} ${o}`.slice(0,72),spoken:`The answer must be in ${r}, so we change the units: ${numberToWordsLong(k)} ${x} is ${numberToWordsLong(e)} ${r}.`}:{visual_data:{type:"equation_display",terms:[`${E.v} × ${d}`,"=",`${e} ${o}`]},caption:`${E.v} × ${d} = ${e} ${o}`.slice(0,72),spoken:`Multiplying gives ${numberToWordsLong(e)} ${r} ${inLargeTail(m,f)}.`};return buildExample({generatorId:`${MICRO}.scale.v1`,question:n,title:`How big is it ${inLargeTail(m,f)}?`,computed:{factor:d,given:E.v,real:k,answer:e},steps:[{visual_data:{type:"equation_display",terms:["1 cm","→",`${d} ${i}`]},animation:"show_ratio",caption:F.slice(0,72),spoken:D},{visual_data:V.visual_data,animation:"divide_both",caption:V.caption,spoken:V.spoken},{visual_data:O.visual_data,animation:"write_answer",caption:O.caption,spoken:O.spoken}]})}if(approxEq(S,e)){const U=E.unit!==i,q=m==="sf"?"The length before enlarging":"The real length",V=U?`${E.v} ${E.unit} = ${A} ${i}, ÷ ${d}`.slice(0,72):`${A} ${i} ÷ ${d}`.slice(0,72),O=U?`${q} is ${numberToWordsLong(E.v)} ${unitWord(E.unit)}, which is ${numberToWordsLong(A)} ${x}; we divide that by ${numberToWordsLong(d)}.`:`${q} is ${numberToWordsLong(A)} ${x}, so we divide by ${numberToWordsLong(d)} to get back ${onSmallTail(m,h)}.`;return buildExample({generatorId:`${MICRO}.scale.v1`,question:n,title:`How big is it ${onSmallTail(m,h)}?`,computed:{factor:d,real:A,answer:e},steps:[{visual_data:{type:"equation_display",terms:["1 cm","→",`${d} ${i}`]},animation:"show_ratio",caption:F.slice(0,72),spoken:D},{visual_data:{type:"equation_display",terms:[`${A} ${i}`,"÷",`${d}`]},animation:"divide_both",caption:V,spoken:O},{visual_data:{type:"equation_display",terms:[`${A} ÷ ${d}`,"=",`${e} cm`]},animation:"write_answer",caption:`${A} ÷ ${d} = ${e}
71 cm`.slice(0,72),spoken:`So ${onSmallTail(m,h)} it is ${numberToWordsLong(e)} centimetres.`}]})}return null}}}],Y6_GENERATORS=[...AS_P6_M07_GENERATORS,...AS_P6_M08_GENERATORS,...AS_P6_M09_GENERATORS,...AS_P6_M10_GENERATORS,...DS_P5_M01_GENERATORS,...DS_P5_M02_GENERATORS,...DS_P5_M03_GENERATORS,...FDP_P6_M04_GENERATORS,...FDP_P6_M05_GENERATORS,...FDP_P6_M06_GENERATORS,...GEO_P6_M04_GENERATORS,...GEO_P6_M05_GENERATORS,...GEO_P6_M06_GENERATORS,...MD_P5_M05_GENERATORS,...MD_P5_M06_GENERATORS,...MD_P5_M07_GENERATORS,...MEA_P7_M05_GENERATORS,...NPV_P7_M03_GENERATORS,...NPV_P7_M04_GENERATORS,...NPV_P7_M05_GENERATORS,...NPV_P7_M06_GENERATORS,...RP_P3_M01_GENERATORS,...RP_P3_M02_GENERATORS,...RP_P3_M03_GENERATORS,...RP_P3_M04_GENERATORS],MD_P4_M05_Q102_PRE_FIXTURE_ID="WEXA.MD.P4.M05.Q102.REVIEWED.01",MD_P4_M05_Q102_REVIEWED_GENERATOR_ID="MD.P4.M05.reviewed-q102.v1",REVIEWED_SPECS=[{questionId:MD_P4_M05_Q102_PRE_FIXTURE_ID,stem:"7455 ÷ 5 = __",dividend:7455,divisor:5,answer:1491,example:preQ102Raw},{questionId:"MD.P4.M05.Q102",stem:"9368 ÷ 4 = __",dividend:9368,divisor:4,answer:2342,example:postQ102Raw}];function normaliseQuestionId$2(n){return n.trim().replace(/^cb-/,"")}function reviewedSpec(n){const t=normaliseQuestionId$2(n);return REVIEWED_SPECS.find(e=>e.questionId===t)??null}function hasReviewedQuestionWexOverride(n){return reviewedSpec(n)!==null}function divideByColumns(n,t){const e=[];let s=0;for(const o of String(n).split("").map(Number)){const r=s*10+o,a=Math.floor(r/t),i=r-a*t;e.push({digit:o,carryIn:s,working:r,quotientDigit:a,carryOut:i}),s=i}return e}function reviewedComputed(n){if(n.dividend%n.divisor!==0)return null;const t=n.dividend/n.divisor;if(t!==n.answer)return null;const e={dividend:n.dividend,divisor:n.divisor,quotient:t,answer:t};return divideByColumns(n.dividend,n.divisor).forEach((s,o)=>{e[`col${o}Digit`]=s.digit,e[`col${o}CarryIn`]=s.carryIn,e[`col${o}Working`]=s.working,e[`col${o}QuotientDigit`]=s.quotientDigit,e[`col${o}CarryOut`]=s.carryOut,e[`col${o}Exchange`]=s.carryOut*10}),e}function exactInputMatches(n,t){if(normaliseQuestionId$2(n.questionId)!==t.questionId||n.microSkillId!=="MD.P4.M05"||n.mathematicalForm!=="symbolic_calculation"||n.stem.trim().replace(/\s+/g," ")!==t.stem||n.correctAnswer.trim()!==String(t.answer))return!1;const e=n.visualData;return!!e&&e.type==="short_division"&&e.dividend===t.dividend&&e.divisor===t.divisor}const reviewedQ102={id:MD_P4_M05_Q102_REVIEWED_GENERATOR_ID,microSkillId:"MD.P4.M05",forms:["symbolic_calculation"],generate(n){const t=reviewedSpec(n.questionId);if(!t||!exactInputMatches(n,t))return null;const e=reviewedComputed(t);if(!e)return null;const[s,o,r]=t.example.steps;return!s||!o||!r||t.example.steps.length!==3?null:buildExample({generatorId:this.id,question:n,title:t.example.title,computed:e,steps:[s,o,r]})}},REVIEWED_QUESTION_WEX_GENERATORS=[reviewedQ102],ADD_EQ=/^(\d+) \+ (\d+) = __$/,ADD_WHAT=/^What is (\d+) \+ (\d+)\?$/,SUB_EQ=/^(\d+) - (\d+) = __$/,MISS_ADDEND_RIGHT=/^(\d+) \+ __ = (\d+)$/,MISS_ADDEND_LEFT=/^__ \+ (\d+) = (\d+)$/,MISS_SUBTRAHEND=/^(\d+) - __ = (\d+)$/,MISS_MINUEND=/^__ - (\d+) = (\d+)$/,DOUBLE_PLAIN=/^Double (\d+) = __$/,DOUBLE_EXPANDED=/^Double (\d+) = (\d+) \+ (\d+) = __$/,BOND_AND_RIGHT=/^(\d+) and __ make (\d+)$/,BOND_AND_LEFT=/^__ and (\d+) make (\d+)$/,WHOLE_PART=/^The whole is (\d+)\. One part is (\d+)\. What is the (?:other|missing) part\?$/,STORY_PART_WHOLE=/^([A-Z][a-z]+) has (\d+) ([a-z]+)\. (\d+) (?:are|is) ([a-z]+)\. How many (?:are|is) ([a-z]+)\?$/;function partAndWholeFromCounter(n){const t=n.visualData;return t?t.type==="ten_frame"&&Number.isInteger(t.filled)&&Number.isInteger(t.total)?{p:t.filled,whole:t.total}:t.type==="filled_slots"&&Number.isInteger(t.filled)&&Number.isInteger(t.total)?{p:t.filled,whole:t.total}:null:null}function takeAwayFromStory(n){const t=n.visualData;if(!t||t.type!=="story")return null;const e=t.count??t.count_total??t.total,s=t.count_removed;return!Number.isInteger(e)||!Number.isInteger(s)?null:{start:e,removed:s}}function comparisonFromMeasureBars(n){const t=n.visualData;if(!t||t.type!=="measure_bars"||!Array.isArray(t.bars)||t.bars.length!==2)return null;const[e,s]=t.bars;if(!Number.isInteger(e?.length)||!Number.isInteger(s?.length)||e.length===s.length)return null;const[o,r]=e.length>s.length?[e,s]:[s,e],a=cleanBarLabel(o.label),i=cleanBarLabel(r.label);return!a||!i?null:{moreLabel:a,longer:o.length,fewerLabel:i,shorter:r.length}}
71function cleanBarLabel(n){if(typeof n!="string")return null;const t=n.replace(/\s+/g," ").trim();return t.length===0||t.length>14||/\d/.test(t)?null:capitalised(t)}const COMPARISON_RE=/how (?:many|much) .*(?:more|fewer|less|older|younger|longer|shorter|taller|bigger|smaller|heavier)\b/i;function countingRunDown(n,t){const e=[];for(let s=n;s>=t;s--)e.push(numberWord(s));return e.join(", ")}function missingStemFromEquationDisplay(n){const t=n.visualData;if(!t||t.type!=="equation_display"||!Array.isArray(t.terms))return null;const e=t.terms.map(d=>d==="?"?"__":d);if(e.length!==5||e[3]!=="=")return null;const[s,o,r,,a]=e,i=o==="−"?"-":o,l=d=>typeof d=="number"&&Number.isInteger(d),u=d=>d==="__";return l(s)&&i==="+"&&u(r)&&l(a)?`${s} + __ = ${a}`:u(s)&&i==="+"&&l(r)&&l(a)?`__ + ${r} = ${a}`:l(s)&&i==="-"&&u(r)&&l(a)?`${s} - __ = ${a}`:u(s)&&i==="-"&&l(r)&&l(a)?`__ - ${r} = ${a}`:null}const bridgingAddition={id:"AS.P3.M03.bridging.v1",microSkillId:"AS.P3.M03",forms:["equation_completion","addition","bridging_through_10_addition","misconception_contrast_probe"],generate(n){let t=matchInts(n.stem,ADD_EQ)??matchInts(n.stem,ADD_WHAT);if(!t){const i=n.visualData;i&&i.type==="ten_frame_overflow"&&Number.isInteger(i.start)&&Number.isInteger(i.adding)&&(t=[i.start,i.adding])}if(!t)return null;const[e,s]=t;if(e<5||e>9||s<2||s>9)return null;const o=e+s;if(o<11||o>18)return null;const r=10-e,a=s-r;return o!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} + ${s}?`,computed:{a:e,b:s,gap:r,leftover:a,total:o,answer:o},steps:[{visual_data:{type:"ten_frame_overflow",start:e,adding:s},animation:"highlight_gap",caption:`${e} needs ${r} more to make 10.`,spoken:`${capitalised(numberWord(e))} plus ${numberWord(s)}. Here's one way. ${capitalised(numberWord(e))} needs ${numberWord(r)} more to make ten.`},{visual_data:{type:"ten_frame_overflow",start:e,adding:s},animation:"dots_fly_in",caption:`${r} of the ${s} fly in to fill the frame.`,spoken:`We're adding ${numberWord(s)}. ${capitalised(numberWord(r))} of them fly in to fill the frame and make ten.`},{visual_data:{type:"ten_frame_overflow",start:e,adding:s},animation:"write_total",caption:`10 and ${a} more — that's ${o}.`,spoken:`Now we have ten, plus ${numberWord(a)} more. That's ${numberWord(o)}. ${capitalised(numberWord(e))} plus ${numberWord(s)} equals ${numberWord(o)}.`}]})}};function subtractionOnLine(n,t,e,s,o){const r=e-s,a=e<=10?{min:0,max:10}:{min:0,max:20},i={a:e,b:s,result:r,answer:r},l=capitalised(numberWord(e)),u=x=>x===1?"jump":"jumps";if(o==="back"){const x=[];for(let k=e;k>r;k--)x.push({from:k,to:k-1,label:"-1"});const y=e>10?onesWindow(r,e):a,E=(k,T)=>({type:"number_line",min:y.min,max:y.max,marked:[e],hops:k,unknown:T});return buildExample({generatorId:n,question:t,title:`How do we work out ${e} - ${s}?`,computed:i,steps:[{visual_data:E([],null),animation:"highlight_start",caption:`${e} - ${s}: start at ${e}.`,spoken:s<=5?`${l} take away ${numberWord(s)}. We are taking away a small number, so start at ${numberWord(e)} and count back ${numberWord(s)}, one jump at a time.`:`${l} take away ${numberWord(s)}. The answer stays above ten, so start at ${numberWord(e)} and count back ${numberWord(s)}, one jump at a time.`},{visual_data:E(x,null),animation:"count_back",caption:`Count back ${s}.`,spoken:`Count back: ${countingRunDown(e-1,r)}. That's ${numberWord(s)} ${u(s)} back.`},{visual_data:E(x,r),animation:"land",caption:`We land on ${r}. ${e} - ${s} = ${r}.`,spoken:`The jumps stop on ${numberWord(r)}. ${capitalised(numberWord(s))} back from ${numberWord(e)} lands on ${numberWord(r)}, so ${numberWord(e)} take away ${numberWord(s)} leaves ${numberWord(r)}.`}]})}const d=countOnPlan(s,e);d.kind==="bridge"&&(i.toTen=d.toTen,i.rest=d.rest);const m=e>10&&d.hops.length>=2?onesWindow(s,e):a,h=(x,y)=>({type:"number_line",min:m.min,max:m.max,marked:[s,e],hops:x,unknown:null,...y?{conclude_label:y}:{}});let f,g;const b=`So ${numberWord(e)} take away ${numberWord(s)} is ${numberWord(r)}.`;return d.kind==="bridge"?(f={hops:[d.hops[0]],caption:`${s} and ${d.toTen} more makes 10.`,spoken:`Start at ${numberWord(s)} and jump to ten first. That's ${numberWord(d.toTen)}.`},g={caption:`${d.toTen} + ${d.rest} = ${r}. So ${e} - ${s} = ${r}.`,spoken:`Then ${numberWord(d.rest)} more makes ${numberWord(e)}. ${capitalised(numberWord(d.toTen))} and ${numberWord(d.rest)} make ${numberWord(r)}. ${b}`,conclude:`${d.toTen} + ${d.rest} = ${r}`}):d.kind==="ones"?(f={hops:d.hops,caption:`Count on from ${s} to ${e}.`,spoken:`Count on, one jump at a time: ${countingRun(s+1,e)}.`},g={caption:`${r} ${u(r)}. So ${e} - ${s} = ${r}.`,spoken:`That's ${numberWord(r)} ${u(r)}. ${b}`,conclude:`${r} ${u(r)}`}):(f={hops:d.hops,caption:`Count on from ${s} to ${e}.`,spoken:`From ${numberWord(s)} to ${numberWord(e)} is ${numberWord(r)} more.`},g={caption:`${r} more. So ${e} - ${s} = ${r}.`,spoken:`The gap from ${numberWord(s)} to ${numberWord(e)} is ${numberWord(r)}. ${b}`,conclude:`${r} more`}),buildExample({generatorId:n,question:t,title:`How do we work out ${e} - ${s}?`,computed:i,steps:[{visual_data:h([]),animation:"highlight_start",caption:`${e} - ${s}: find the gap from ${s} to ${e}.`,spoken:`${l} take away ${numberWord(s)}. ${capitalised(numberWord(s))} is a big number to take away, so find the gap instead: start at ${numberWord(s)} and count on to ${numberWord(e)}.`},{visual_data:h(f.hops),animation:"count_on",caption:f.caption,spoken:f.spoken},{visual_data:h(d.hops,g.conclude),animation:"show_difference",caption:g.caption,spoken:g.spoken}]})}const subtractWithinTenOnLine={id:"AS.P3.M02.number-line.v1",microSkillId:"AS.P3.M02",forms:["equation_completion","misconception_contrast_probe"],generate(n){if(n.visualData?.type==="take_away")return null;const t=matchInts(n.stem,SUB_EQ);if(!t)return null;const[e,s]=t;return e<2||e>10||s<1||s>=e||e-s!==answerInt(n)?null:subtractionOnLine(this.id,n,e,s,s>e-s?"on":"back")}},subtractWithinTwentyChooseMove={id:"AS.P3.M04.choose-move.v1",microSkillId:"AS.P3.M04",forms:["equation_completion","misconception_contrast_probe"],generate(n){const t=matchInts(n.stem,SUB_EQ);if(!t)return null;const[e,s]=t;return e<11||e>20||s<1||s>=e||e-s!==answerInt(n)?null:subtractionOnLine(this.id,n,e,s,s<=5||e-s>=10?"back":"on")}},takeAwayWithinTen={id:"AS.P3.M02.take-away.v1",microSkillId:"AS.P3.M02",forms:["equation_completion","misconception_contrast_probe"],generate(n){const t=matchInts(n.stem,SUB_EQ);if(!t)return null;const[e,s]=t;if(e<2||e>10||s<1||s>=e)return null;const o=e-s;return o!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} - ${s}?`,computed:{a:e,b:s,result:o,answer:o},steps:[{visual_data:{type:"take_away",total:e,take_away:0},animation:"show_start",caption:`We have ${e} counters.`,spoken:`Let's work out ${numberWord(e)} minus ${numberWord(s)}. Minus means take away, so we start with ${numberWord(e)} counters and we need to take ${numberWord(s)} of them away.`},{visual_data:{type:"take_away",total:e,take_away:s},animation:"fade_away",caption:`Take ${s} away.`,spoken:`We count them as we take them, so we don't take too many or too few: ${countingRun(1,s)}. That's ${numberWord(s)} gone.`},{visual_data:{type:"take_away",total:e,take_away:s,result_visible:!0},animation:"show_remaining",caption:`${o} left. ${e} - ${s} = ${o}.`,spoken:`The counters that are left are the answer, so now we count them: ${countingRun(1,o)}. ${capitalised(numberWord(o))} left, so ${numberWord(e)} minus ${numberWord(s)} is ${numberWord(o)}.`}]})}};function subtractionOperands(n){const t=matchInts(n.stem,SUB_EQ);if(t)return[t[0],t[1]];const e=takeAwayFromStory(n);return e?[e.start,e.removed]:null}const SUB_VISUAL_FORMS=["equation_completion","misconception_contrast_probe","subtraction_story_context"],bridgeBackSubtraction={id:"AS.P3.M04.bridge-back.v1",microSkillId:"AS.P3.M04",forms:SUB_VISUAL_FORMS,generate(n){const t=subtractionOperands(n);if(!t)return null;const[e,s]=t;if(e<11||e>19||s<2||s>9)return null;const o=e-10,r=s-o;if(r<1)return null;const a=10-r;return a!==answerInt(n)?null:buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} - ${s}?`,computed:{a:e,b:s,hop1:o,hop2:r,result:a,answer:a},steps:[{visual_data:{type:"number_line",min:0,max:20,marked:[e]},animation:"highlight_start",caption:`Start at ${e}.`,spoken:`${capitalised(numberWord(e))} minus ${numberWord(s)}. We start at ${numberWord(e)} on the number line, and we need to jump back ${numberWord(s)}.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[{from:e,to:10,label:`-${o}`}]},animation:"first_hop",caption:`Jump back ${o} to land on 10.`,spoken:`Take ${numberWord(o)} away first. That lands us exactly on ten.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[{from:e,to:10,label:`-${o}`},{from:10,to:a,label:`-${r}`}],unknown:a},animation:"second_hop",caption:`Jump back ${r} more. ${e} - ${s} = ${a}.`,spoken:`${capitalised(numberWord(o))} and ${numberWord(r)} more makes ${numberWord(s)}. So jump back ${numberWord(r)} from ten — that's ${numberWord(a)}.`}]})}},subtractCountBack={id:"AS.P3.M04.count-back.v1",microSkillId:"AS.P3.M04",forms:SUB_VISUAL_FORMS,generate(n){const t=subtractionOperands(n);if(!t)return null;const[e,s]=t;if(e<11||e>20||s<1||s>=e)return null;const o=e-s;if(o<0||o>20||o!==answerInt(n))return null;const r=s<=1?s:Math.ceil(s/2),a=s-r,i=e-r,l={from:e,to:i,label:`-${r}`},u={from:i,to:o,label:`-${a}`},d=a>0?[l,u]:[l];return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} - ${s}?`,computed:{a:e,b:s,result:o,answer:o,hop1Size:r,hop2Size:a,wayp
71oint:i},steps:[{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[],unknown:null},animation:"highlight_start",caption:`Start at ${e}.`,spoken:`${capitalised(numberWord(e))} minus ${numberWord(s)}. We start at ${numberWord(e)} on the number line, and count back ${numberWord(s)}.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:[l],unknown:null},animation:"first_hop",caption:`Count back ${r}, landing on ${i}.`,spoken:`First, count back ${numberWord(r)}: ${countingRunDown(e-1,i)}.`},{visual_data:{type:"number_line",min:0,max:20,marked:[e],hops:d,unknown:o},animation:"land",caption:`That lands on ${o}. ${e} - ${s} = ${o}.`,spoken:a>0?`Then count back ${numberWord(a)} more: ${countingRunDown(i-1,o)}. So ${numberWord(e)} minus ${numberWord(s)} is ${numberWord(o)}.`:`That lands on ${numberWord(o)}. So ${numberWord(e)} minus ${numberWord(s)} is ${numberWord(o)}.`}]})}},differenceCountUp={id:"AS.P3.M04.difference-count-up.v1",microSkillId:"AS.P3.M04",forms:SUB_VISUAL_FORMS,generate(n){const t=comparisonFromMeasureBars(n);if(!t)return null;const{moreLabel:e,longer:s,fewerLabel:o,shorter:r}=t;if(!COMPARISON_RE.test(n.stem)||r<0||s>20)return null;const a=s-r;if(a<1||a!==answerInt(n))return null;const i=countOnPlan(r,s),l=i.hops.length>=2?onesWindow(r,s):{min:0,max:20},u=i.kind==="bridge"?{toTen:i.toTen,rest:i.rest}:{},d=i.kind==="bridge"?`${i.toTen} + ${i.rest} = ${a}. So ${s} − ${r} = ${a}.`:`${a} ${a===1?"hop":"hops"}. So ${s} − ${r} = ${a}.`,m=i.kind==="bridge"?`${capitalised(numberWord(i.toTen))} to get to ten, then ${numberWord(i.rest)} more. ${capitalised(numberWord(i.toTen))} and ${numberWord(i.rest)} make ${numberWord(a)}, so ${e} has ${numberWord(a)} more. The difference is ${numberWord(a)}.`:`That took ${numberWord(a)} ${a===1?"hop":"hops"}, so ${e} has ${numberWord(a)} more. The difference is ${numberWord(a)}.`;return buildExample({generatorId:this.id,question:n,title:`How many more? Count up from ${r} to ${s}.`,computed:{longer:s,shorter:r,diff:a,answer:a,...u},steps:[{visual_data:{type:"number_line",min:l.min,max:l.max,marked:[r,s],hops:[],unknown:null},animation:"highlight_start",caption:`${e} ${s}, ${o} ${r} — how many more?`,spoken:`How many more does ${e} have than ${o}? We can count up from ${o}'s number to ${e}'s.`},{visual_data:{type:"number_line",min:l.min,max:l.max,marked:[r,s],hops:i.hops,unknown:null},animation:"count_up",caption:`Count up from ${r} to ${s}.`,spoken:`Start at ${numberWord(r)}. Count up to ${numberWord(s)} — ${countingRun(r+1,s)}.`},{visual_data:{type:"number_line",min:l.min,max:l.max,marked:[r,s],hops:i.hops,unknown:null,conclude_label:`${a} more`},animation:"show_difference",caption:d,spoken:m}]})}};function missingShapeFor(n){const t=(...s)=>s.every(o=>o>=0&&o<=20);let e=matchInts(n.stem,MISS_ADDEND_RIGHT);if(e){const[s,o]=e,r=o-s;return!t(s,o,r)||r<1?null:{shown:[s,"+","__","=",o],reframed:[o,"−",s,"=","__"],completed:[s,"+",r,"=",o],missing:r,caption2:`Start at ${s}. Count on to ${o}.`,spoken1:`${capitalised(numberWord(s))} plus something equals ${numberWord(o)}. The missing number is how many we add to ${numberWord(s)}.`,spoken2:`Start at ${numberWord(s)} and count on to ${numberWord(o)}. We count the jumps.`,spoken3:`${capitalised(numberWord(r))}. So ${numberWord(s)} plus ${numberWord(r)} equals ${numberWord(o)}. The missing number is ${numberWord(r)}.`}}if(e=matchInts(n.stem,MISS_ADDEND_LEFT),e){const[s,o]=e,r=o-s;return!t(s,o,r)||r<1?null:{shown:["__","+",s,"=",o],reframed:[o,"−",s,"=","__"],completed:[r,"+",s,"=",o],missing:r,caption2:`Start at ${s}. Count on to ${o}.`,spoken1:`Something plus ${numberWord(s)} equals ${numberWord(o)}. The missing number is how many go with ${numberWord(s)} to make ${numberWord(o)}.`,spoken2:`Start at ${numberWord(s)}, the part we know, and count on to ${numberWord(o)}. We count the jumps.`,spoken3:`${capitalised(numberWord(r))}. So ${numberWord(r)} plus ${numberWord(s)} equals ${numberWord(o)}. The missing number is ${numberWord(r)}.`}}if(e=matchInts(n.stem,MISS_SUBTRAHEND),e){const[s,o]=e,r=s-o;return!t(s,o,r)||r<1?null:{shown:[s,"−","__","=",o],reframed:[s,"−",o,"=","__"],completed:[s,"−",r,"=",o],missing:r,caption2:`Start at ${o}. Count on to ${s}.`,spoken1:`${capitalised(numberWord(s))} take away something leaves ${numberWord(o)}. The missing number is how many we take away.`,spoken2:`How many do we add to ${numberWord(o)} to get back to ${numberWord(s)}? Start at ${numberWord(o)} and count on to ${numberWord(s)}. We count the jumps.`,spoken3:`${capitalised(numberWord(r))}. So ${numberWord(s)} minus ${numberWord(r)} equals ${numberWord(o)}.`}}if(e=matchInts(n.stem,MISS_MINUEND),e){const[s,o]=e,r=o+s;return!t(s,o,r)||r<1?null:{shown:["__","−",s,"=",o],reframed:[o,"+",s,"=","__"],completed:[r,"−",s,"=",o],missing:r,caption2:`${o} + ${s} = __`,spoken1:`Something minus ${numberWord(s)} equals ${numberWord(o)}. The missing number is what we started with.`,spoken2:`To find the starting number, add back 
71what was taken. ${capitalised(numberWord(o))} plus ${numberWord(s)}.`,spoken3:`${capitalised(numberWord(r))}. So ${numberWord(r)} minus ${numberWord(s)} equals ${numberWord(o)}.`}}return null}function effectiveMissingStem(n){if(matchInts(n.stem,MISS_ADDEND_RIGHT)||matchInts(n.stem,MISS_ADDEND_LEFT)||matchInts(n.stem,MISS_SUBTRAHEND)||matchInts(n.stem,MISS_MINUEND))return n.stem;const t=missingStemFromEquationDisplay(n);if(t)return t;const e=partWholeFromVisual(n);if(e)return`__ + ${e.knownPart} = ${e.whole}`;const s=partAndWholeFromCounter(n);return s?`${s.p} + __ = ${s.whole}`:n.stem}const missingNumber={id:"AS.P3.M05.missing-number.v1",microSkillId:"AS.P3.M05",forms:["equation_completion","missing_addend","missing_number_bridging"],generate(n){const t=missingShapeFor({...n,stem:effectiveMissingStem(n)});if(!t||t.missing!==answerInt(n))return null;const e=S=>S.join(" "),s={missing:t.missing,answer:t.missing};[...t.shown,...t.reframed,...t.completed].forEach((S,M)=>{typeof S=="number"&&(s[`t${M}`]=S)});const o=t.reframed[0],r=t.reframed[1],a=t.reframed[2],i=e(t.completed),l=t.missing,u=t.spoken3.replace(/^[A-Z][a-z-]*\. /,""),d=S=>S===1?"jump":"jumps";if(r==="+"){const S=countOnPlan(o,l),M=t.reframed.map(P=>P==="__"?l:P),L=S.kind==="bridge"?`${capitalised(numberWord(o))} and ${numberWord(S.toTen)} more makes ten, then ${numberWord(S.rest)} more makes ${numberWord(l)}.`:S.kind==="ones"?`Count on ${numberWord(a)} from ${numberWord(o)}: ${countingRun(o+1,l)}.`:`${capitalised(numberWord(o))} and ${numberWord(a)} more makes ${numberWord(l)}.`;return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e(t.shown)}?`,computed:s,steps:[{visual_data:{type:"equation_display",terms:t.shown},animation:"pulse_unknown",caption:e(t.shown),spoken:t.spoken1},{visual_data:{type:"equation_display",terms:t.reframed},animation:"reframe",caption:t.caption2,spoken:t.spoken2},{visual_data:{type:"number_line",min:0,max:20,marked:[o],hops:S.hops,unknown:l},animation:"bridge_compute",caption:`${e(M)}. So ${i}`,spoken:`${L} ${u}`}]})}if(t.shown.includes("+")&&l>=10&&a>=1&&a<=5){const S=o,M=a,L=[];for(let D=S;D>l;D--)L.push({from:D,to:D-1,label:"-1"});const P=onesWindow(l,S),F=(D,U)=>({type:"number_line",min:P.min,max:P.max,marked:[S],hops:D,unknown:U});return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e(t.shown)}?`,computed:s,steps:[{visual_data:{type:"equation_display",terms:t.shown},animation:"pulse_unknown",caption:e(t.shown),spoken:t.spoken1},{visual_data:F(L,null),animation:"count_back",caption:`Take ${M} away from ${S}.`,spoken:`${capitalised(numberWord(M))} is a small part, so take it away from ${numberWord(S)}. Count back ${numberWord(M)}: ${countingRunDown(S-1,l)}.`},{visual_data:F(L,l),animation:"land",caption:`${S} − ${M} = ${l}. So ${i}`,spoken:`We landed on ${numberWord(l)}. ${u}`}]})}const h=a,f=o,g=countOnPlan(h,f);g.kind==="bridge"&&(s.toTen=g.toTen,s.rest=g.rest);const b=g.hops.length>=2?onesWindow(h,f):{min:0,max:20},x=(S,M)=>({type:"number_line",min:b.min,max:b.max,marked:[h,f],hops:S,unknown:null,...M?{conclude_label:M}:{}});let y=x([]),E=t.caption2,k=t.spoken2,T,A,N;return g.kind==="bridge"?(y=x([g.hops[0]]),E=`Start at ${h}. ${g.toTen} more makes 10.`,k=`${t.spoken2} First jump to ten: ${numberWord(h)} and ${numberWord(g.toTen)} more makes ten.`,T=`${g.toTen} + ${g.rest} = ${l}. So ${i}`,A=`Then ${numberWord(g.rest)} more from ten to ${numberWord(f)}. ${capitalised(numberWord(g.toTen))} and ${numberWord(g.rest)} make ${numberWord(l)}. ${u}`,N=`${g.toTen} + ${g.rest} = ${l}`):g.kind==="ones"?(T=`${l} ${d(l)}. So ${i}`,A=`Count on: ${countingRun(h+1,f)}. That's ${numberWord(l)} ${d(l)}. ${u}`,N=`${l} ${d(l)}`):(T=`${l} more. So ${i}`,A=`From ${numberWord(h)} to ${numberWord(f)} is ${numberWord(l)} more. ${u}`,N=`${l} more`),buildExample({generatorId:this.id,question:n,title:`How do we work out ${e(t.shown)}?`,computed:s,steps:[{visual_data:{type:"equation_display",terms:t.shown},animation:"pulse_unknown",caption:e(t.shown),spoken:t.spoken1},{visual_data:y,animation:"count_on",caption:E,spoken:k},{visual_data:x(g.hops,N),animation:"count_on_compute",caption:T,spoken:A}]})}};function doublesGen(n,t,e,s){return{id:n,microSkillId:t,forms:s,generate(o){let r=null;const a=matchInts(o.stem,DOUBLE_PLAIN),i=matchInts(o.stem,DOUBLE_EXPANDED),l=matchInts(o.stem,ADD_EQ);if(a)r=a[0];else if(i&&i[0]===i[1]&&i[1]===i[2])r=i[0];else if(l&&l[0]===l[1])r=l[0];else{const h=o.visualData;h&&h.type==="mirror"&&Number.isInteger(h.each_side)&&(r=h.each_side)}if(r===null||r<1||r>e)return null;const u=2*r;if(u!==answerInt(o))return null;const d=h=>h===1?"dot":"dots",m=r<=6?`Add ${r} more: ${countingRun(r+1,u)}.`:`Add ${r} more and count on to ${u}.`;return buildExample({generatorId:n,question:o,title:`How do we work out ${r} + ${r}?`,computed:{n:r,total:u,answer:u},steps:[{visual_data:{type:"mirror",each_side:r,emoji:"🔵",right_hidden:!0},animation:"highlight_one_side",caption:`${r} ${d(r)} on this side.`,spoken:`We have ${numberWord(r)} ${d(r)}. A double means we add the same number again, on the other side.`},{visual_data:{type:"mirror",each_side:r,emoji:"🔵",right_hidden:!1},animation:"mirror_and_count",caption:m,spoken:`Now count ${numberWord(r)} more ${d(r)}. ${capitalised(countingRun(r+1,u))}.`},{visual_data:{type:"mirror",each_side:r,emoji:"🔵",right_hidden:!1},animation:"write_total",caption:`${r} + ${r} = ${u}
71.`,spoken:`${capitalised(numberWord(r))} and ${numberWord(r)} make ${numberWord(u)}.`}]})}}}const doubles=doublesGen("AS.P3.M01.doubles.v1","AS.P3.M01",9,["doubling","near_double","equation_completion"]),doublesTeen=doublesGen("AS.P3.M03.doubles.v1","AS.P3.M03",10,["doubling","equation_completion"]),nearDouble={id:"AS.P3.M01.near-double.v1",microSkillId:"AS.P3.M01",forms:["near_double","equation_completion"],generate(n){const t=matchInts(n.stem,ADD_EQ);if(!t)return null;const[e,s]=t;if(Math.abs(e-s)!==1)return null;const o=Math.min(e,s),r=2*o,a=r+1;if(o<1||a>10||a!==answerInt(n))return null;const i={from:o,to:r,label:`+${o}`},l={from:r,to:a,label:"+1"};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${e} + ${s}?`,computed:{x:e,y:s,s:o,dbl:r,total:a,answer:a},steps:[{visual_data:{type:"number_line",min:0,max:10,marked:[o],hops:[],unknown:null},animation:"highlight_start",caption:`${e} + ${s} is close to a double.`,spoken:`${capitalised(numberWord(e))} plus ${numberWord(s)}. These are next-door numbers, so we can use a double. Start at ${numberWord(o)}.`},{visual_data:{type:"number_line",min:0,max:10,marked:[o],hops:[i],unknown:null},animation:"first_hop",caption:`Double ${o} is ${r}.`,spoken:`Double ${numberWord(o)} is ${numberWord(r)}. Jump on ${numberWord(o)} to ${numberWord(r)}.`},{visual_data:{type:"number_line",min:0,max:10,marked:[o],hops:[i,l],unknown:a},animation:"second_hop",caption:`One more makes ${a}.`,spoken:`Then one more. ${capitalised(numberWord(r))} and one more is ${numberWord(a)}. So ${numberWord(e)} plus ${numberWord(s)} is ${numberWord(a)}.`}]})}};function bondCountUp(n,t,e){return{id:n,microSkillId:t,forms:e,generate(s){let o=matchInts(s.stem,BOND_AND_RIGHT)??matchInts(s.stem,BOND_AND_LEFT)??matchInts(s.stem,MISS_ADDEND_RIGHT);if(!o){const u=partWholeFromVisual(s);u&&(o=[u.knownPart,u.whole])}if(!o){const u=partAndWholeFromCounter(s);u&&(o=[u.p,u.whole])}if(!o)return null;const[r,a]=o;if(a<2||a>10||r<1||r>=a)return null;const i=a-r;if(i!==answerInt(s))return null;const l=s.visualData;if(l&&(l.type==="ten_frame"||l.type==="filled_slots")&&l.total===a&&l.filled===r){const u=l.type==="filled_slots"?l.emoji:void 0,d=l.type==="filled_slots"?l.context_label:void 0,m=h=>l.type==="filled_slots"?{type:"filled_slots",total:a,filled:h,emoji:u,context_label:d}:{type:"ten_frame",total:a,filled:h};return buildExample({generatorId:n,question:s,title:`How do we work out ${r} and what make ${a}?`,computed:{p:r,whole:a,missing:i,answer:i},steps:[{visual_data:m(r),animation:"highlight_empty",caption:`${r} filled. ${i} empty.`,spoken:`We have ${numberWord(r)} so far, out of ${numberWord(a)}. How many more do we need?`},{visual_data:m(a),animation:"count_fill",caption:`Count on: ${countingRun(r+1,a)}.`,spoken:`Fill the rest one at a time, counting on from ${numberWord(r)}: ${countingRun(r+1,a)}.`},{visual_data:m(a),animation:"conclude",caption:`${r} and ${i} make ${a}.`,spoken:`The other part is ${numberWord(i)}. ${capitalised(numberWord(r))} and ${numberWord(i)} make ${numberWord(a)}.`}]})}return buildExample({generatorId:n,question:s,title:`How do we work out ${r} and what make ${a}?`,computed:{p:r,whole:a,missing:i,answer:i},steps:[{visual_data:{type:"part_whole",whole:a,parts:[r,null]},animation:"highlight_unknown",caption:`${a} is made of ${r} and something.`,spoken:`We have ${numberWord(a)} in total. One part is ${numberWord(r)}. What's the other part?`},{visual_data:{type:"part_whole",whole:a,parts:[r,null]},animation:"count_up",caption:`Count up from ${r} to ${a}.`,spoken:`Count up from ${numberWord(r)}: ${countingRun(r+1,a)}. That's ${numberWord(i)} ${i===1?"count":"counts"}.`},{visual_data:{type:"part_whole",whole:a,parts:[r,i]},animation:"fill_part",caption:`${r} and ${i} make ${a}.`,spoken:`The other part is ${numberWord(i)}. ${capitalised(numberWord(r))} and ${numberWord(i)} make ${numberWord(a)}.`}]})}}}const BOND_FORMS=["bond_equation","visual_quantity_to_symbol","part_whole_structure","unknown_part_within_whole","bond_complement","bond_story_context"],bondsRecognise=bondCountUp("AS.P2.M02.bond-count-up.v1","AS.P2.M02",BOND_FORMS),bondsToTen=bondCountUp("AS.P2.M03.bond-count-up.v1","AS.P2.M03",BOND_FORMS),findWhole={id:"AS.P2.M02.find-whole.v1",microSkillId:"AS.P2.M02",forms:["visual_quantity_to_symbol","part_whole_structure"],generate(n){const t=n.visualData;if(!t||t.type!=="part_whole"||!Array.isArray(t.parts)||t.parts.length!==2||t.whole!==null)return null;const e=t.parts.filter(i=>Number.isInteger(i));if(e.length!==2)return null;const[s,o]=e[0]>=e[1]?e:[e[1],e[0]];if(s<1||o<1)return null;const r=s+o;if(r<2||r>10||r!==answerInt(n))return null;const a={from:s,to:r,label:`+${o}`};return buildExample({generatorId:this.id,question:n,title:`How do we work out ${s} + ${o}?`,computed:{a:s,b:o,whole:r,answer:r},steps:[{visual_data:{type:"number_line",min:0,max:10,marked:[s],hops:[],unknown:null},animation:"highlight_start",caption:`Start at ${s}.`,spoken:`The two parts are ${numberWord(s)} and ${numberWord(o)}. Start at ${numberWord(s)} on the number line.`},{visual_data:{type:"number_line",min:0,max:10,marked:[s],hops:[a],unknown:null},animation:"count_on",caption:`Count on ${o}: ${countingRun(s+1,r)}.`,spoken:`To put the parts together, we count on ${numberWord(o)} more, one at a time: ${countingRun(s+1,r)}.`},{visual_data:{type:"number_line",min:0,max:10,marked:[s],hops:[a],unknown:r},animation:"land",caption:`${s} and ${o} make ${r}.`,spoken:`That lands on ${numberWord(r)}. ${capitalised(numberWord(s))} and ${numberWord(o)} make ${numberWord(r)}.`}]})}},missingPart={id:"AS.P2.M04.missing-part.v1",microSkillId:"AS.P2.M04",forms:["visual_quantity_to_symbol","equation_completion","part_whole_structure","unknown_part_within_whole"],generate(n){let t=null,e=null;const s=matchInts(n.stem,WHOLE_PART),o=matchInts(n.stem,MISS_ADDEND_RIGHT),r=matchInts(n.stem,MISS_ADDEND_LEFT),a=matchInts(n.stem,MISS_SUBTRAHEND);if(s)[t,e]=s;else if(o)[e,t]=o;else if(r)[e,t]=r;else if(a)[t,e]=a;else{const b=partWholeFromVisual(n);if(b)t=b.whole,e=b.knownPart;else{const x=partAndWholeFromCounter(n);x&&(t=x.whole,e=x.p)}}if(t===null||e===null||t<2||t>10||e<1||e>=t)return null;const i=t-e;if(i!==answerInt(n))return null;const l=`Count up from ${e} to ${t} — that's ${i} ${i===1?"count":"counts"}.`,u=`To find the missing part, we count on from the part we know. Start at ${numberWord(e)} and count up to ${numberWord(t)}: ${countingRun(e+1,t)}. That's ${numberWord(i)} more.`,m=a?(()=>{const b=[];for(let x=t;x>e;x--)b.push({from:x,to:x-1,label:"-1"});return{visual_data:{type:"number_line",min:0,max:10,marked:[t,e],hops:b,unknown:null,conclude_label:`${i} ${i===1?"jump":"jumps"}`},animation:"count_back",caption:`Count back from ${t} to ${e}: ${i} ${i===1?"jump":"jumps"}.`,spoken:`Start at ${numberWord(t)} and count back to ${numberWord(e)}: ${countingRunDown(t-1,e)}. That's ${numberWord(i)} ${i===1?"jump":"jumps"}, so we took away ${numberWord(i)}.`}})():!!!(o||r)&&e<=3&&i>=5?(()=>{const b=[];for(let x=t;x>i;x--)b.push({from:x,to:x-1,label:"-1"});return{visual_data:{type:"number_line",min:0,max:10,marked:[t],hops:b,unknown:i},animation:"count_back",caption:`Take ${e} away: count back from ${t}.`,spoken:`${capitalised(numberWord(e))} is a small part, so take it away from ${numberWord(t)}. Count back ${numberWord(e)}: ${countingRunDown(t-1,i)}. We land on ${numberWord(i)}.`}})():null,h=a?`${t} - ${i} = ${e}. The missing number is ${i}.`:`The missing part is ${i}. ${e} and ${i} make ${t}.`,f=n.visualData;if(f&&(f.type==="ten_frame"||f.type==="filled_slots")&&f.total===t&&f.filled===e){const b=f.type==="filled_slots"?f.emoji:void 0,x=f.type==="filled_slots"?f.context_label:void 0,y=E=>f.type==="filled_slots"?{type:"filled_slots",total:t,filled:E,emoji:b,context_label:x}:{type:"ten_frame",total:t,filled:E};return buildExample({generatorId:this.id,question:n,title:"How do we find the missing part?",computed:{whole:t,part:e,missing:i,answer:i},steps:[{visual_data:y(e),animation:"show_whole_and_known_part",caption:`${e} filled. ${i} empty.`,spoken:`There are ${numberWord(t)} spaces altogether, and ${numberWord(e)} are already filled. The empty spaces are the missing part — how many are there?`},{visual_data:y(t),animation:"count_fill",caption:l,spoken:u},{visual_data:y(t),animation:"conclude",caption:`${e} and ${i} make ${t}
71.`,spoken:`So ${numberWord(i)} are missing. ${capitalised(numberWord(e))} and ${numberWord(i)} make ${numberWord(t)}, all the spaces full.`}]})}const g=STORY_PART_WHOLE.exec(n.stem);if(g){const[,b,x,y,E,k,T]=g;if(Number(x)===t&&Number(E)===e)return buildExample({generatorId:this.id,question:n,title:`How many are ${T}?`,computed:{whole:t,part:e,missing:i,answer:i},steps:[{visual_data:{type:"part_whole",whole:t,parts:[e,null]},animation:"show_whole_and_known_part",caption:`${b} has ${t} ${y}. ${e} are ${k}.`,spoken:`${b} has ${numberWord(t)} ${y}. ${capitalised(numberWord(e))} are ${k}. How many are ${T}?`},m??{visual_data:{type:"part_whole",whole:t,parts:[e,null]},animation:"show_subtraction_strategy",caption:l,spoken:u},{visual_data:{type:"part_whole",whole:t,parts:[e,i]},animation:"write_missing_part",caption:`${i} are ${T}.`,spoken:`So ${numberWord(i)} are ${T}. ${capitalised(numberWord(e))} and ${numberWord(i)} make ${numberWord(t)}, all of ${b}'s ${y}.`}]})}return buildExample({generatorId:this.id,question:n,title:"How do we find the missing part?",computed:{whole:t,part:e,missing:i,answer:i},steps:[{visual_data:{type:"part_whole",whole:t,parts:[e,null]},animation:"show_whole_and_known_part",caption:`The whole is ${t}. One part is ${e}. What is the other part?`,spoken:`The long bar on top is the whole: ${numberWord(t)}. Underneath, one part is ${numberWord(e)}. The other part is hiding. How many is it?`},m??{visual_data:{type:"part_whole",whole:t,parts:[e,null]},animation:"show_subtraction_strategy",caption:`Count up from ${e} to ${t}. That's ${i} more.`,spoken:`To find the hiding part, we count on from the part we know. We start at ${numberWord(e)} and count up to ${numberWord(t)}, one square at a time: ${countingRun(e+1,t)}. That's ${numberWord(i)} ${i===1?"square":"squares"}.`},{visual_data:{type:"part_whole",whole:t,parts:[e,i]},animation:"write_missing_part",caption:h,spoken:`So the missing part is ${numberWord(i)}. ${capitalised(numberWord(e))} and ${numberWord(i)} make ${numberWord(t)}, the whole bar.`}]})}},WEX_GENERATORS=[...REVIEWED_QUESTION_WEX_GENERATORS,bondsRecognise,bondsToTen,findWhole,missingPart,doubles,nearDouble,doublesTeen,subtractWithinTenOnLine,takeAwayWithinTen,bridgingAddition,differenceCountUp,subtractWithinTwentyChooseMove,bridgeBackSubtraction,subtractCountBack,missingNumber,...NPV_MD_GENERATORS,...Y2_GENERATORS,...Y3_GENERATORS,...Y3_GAP_GENERATORS,...Y4_GENERATORS,...Y5_GENERATORS,...Y6_GENERATORS,...STATEMENT_CHECK_GENERATORS,...Y1_SUPPLEMENTAL_GENERATORS];function getWorkedExampleForQuestion(n){if(hasReviewedQuestionWexOverride(n.questionId)){const t=REVIEWED_QUESTION_WEX_GENERATORS[0];if(!t)return null;try{return t.generate(n)}catch{return null}}for(const t of WEX_GENERATORS)if(t.microSkillId===n.microSkillId&&t.forms.includes(n.mathematicalForm??""))try{const e=t.generate(n);if(e)return e}catch{return null}return null}const NUMBER_TOKEN=/[−-]?(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?/g,CONFIRMED_DIFFERENT_TASK_FORMS=new Set(["MD.P5.M02::common_multiple_find"]);function numberKey(n){return Object.is(n,-0)?"0":String(n)}function numbersInText(n){const t=new Set;for(const e of n.matchAll(NUMBER_TOKEN)){const s=Number(e[0].replace(/−/g,"-").replace(/,/g,""));Number.isFinite(s)&&t.add(numberKey(s))}return t}function numbersInJson(n,t){const e=new Set,s=(o,r)=>{if(typeof o=="number"){Number.isFinite(o)&&e.add(numberKey(o));return}if(typeof o=="string"){if(r!=="type"&&r!=="color"&&r!=="animation")for(const a of numbersInText(o))e.add(a);return}if(Array.isArray(o)){for(const a of o)s(a,r);return}if(!(!o||typeof o!="object"))for(const[a,i]of Object.entries(o))s(i,a)};return s(n,t),e}function union(...n){const t=new Set;for(const e of n)for(const s of e)t.add(s);return t}function isSubset(n,t){if(n.size>t.size)return!1;for(const e of n)if(!t.has(e))return!1;return!0}function normaliseQuestionId$1(n){return n.trim().replace(/^cb-/,"")}function normaliseComparableText(n){return n.normalize("NFKC").toLocaleLowerCase("en-GB").replace(/[−–—]/g,"-").replace(/[×]/g,"x").replace(/\s+/g," ").trim().replace(/[.!?]+$/g,"")}function escapeRegExp(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function containsStandalonePhrase(n,t){if(!t)return!1;const e=escapeRegExp(t).replace(/\\ /g,"\\s+");return new RegExp(`(?:^|[^a-z0-9])${e}(?:$|[^a-z0-9])`,"i").test(n)}function wordAliasesForNumber(n){const t=Number(n);if(!Number.isFinite(t))return[];const e=Math.abs(t);let s;try{s=Number.isInteger(e)?numberToWordsLong(e):decimalToWords(String(e))}catch{return[]}const o=new Set([s,s.replace(/-/g," ")]);return t<0?[...o].flatMap(r=>[`minus ${r}`,`negative ${r}`]):[...o]}function exampleContainsNumber(n,t,e){return t.has(n)?!0:wordAliasesForNumber(n).some(s=>containsStandalonePhrase(e,s))}function objectsGridDrawnNumbers(n){const t=new Set,e=n;if(e?.type!=="objects_grid"||!e.group_size||e.group_size<=0||!e.count||e.numbered!=null||e.dealt!=null||e.groups_formed!=null||e.regroup_into!=null||e.compare_group_size!=null)return t;const s=e.group_size;
71if(e.chosen!=null)return e.total_label||e.chosen.forEach((r,a)=>t.add(numberKey((a+1)*s))),t.add(numberKey(e.chosen.length*s)),t;if(e.scatter||e.hide_subtotals||e.mode==="share")return t;const o=Math.ceil(e.count/s);for(let r=0;r<o;r+=1)t.add(numberKey((r+1)*s));return t}function lineGraphTickNumbers(n){if(n.type!=="line_graph")return new Set;const t=Math.min(n.x_labels.length,n.values.length,8);if(t===0)return new Set;const e=Math.max(n.axis_max??0,...n.values.slice(0,t),1),s=[1,2,5,10,20,25,50,100,200,250,500,1e3],o=n.axis_step??s.find(a=>Math.ceil(e/a)<=12)??s[s.length-1],r=Math.ceil(e/o)*o;return new Set(Array.from({length:r/o+1},(a,i)=>numberKey(i*o)))}function exampleRevealsAnswer(n,t){const e=normaliseComparableText(t),s=[n.title,...n.steps.flatMap(a=>[a.caption,a.spoken,JSON.stringify(a.visual_data)])].map(normaliseComparableText),o=s.join(" "),r=numbersInText(t);if(r.size>0){const a=union(numbersInText(n.title),...n.steps.flatMap(i=>[numbersInText(i.caption),numbersInText(i.spoken),numbersInJson(i.visual_data),objectsGridDrawnNumbers(i.visual_data),lineGraphTickNumbers(i.visual_data)]));if([...r].every(i=>exampleContainsNumber(i,a,o)))return!0}return s.some(a=>containsStandalonePhrase(a,e))}function orderingLineTickNumbers(n){const t=new Set;if(n.type!=="number_line")return t;const{min:e,max:s,step:o,marked:r=[],hops:a=[],unknown:i,tick_labels:l}=n,u=s-e;if(o==null||!(o>0&&o<u)||Number.isInteger(o))return t;const d=[e,s,...r,...a.flatMap(m=>[m.from,m.to])];i!=null&&d.push(i);for(const m of d){const h=Math.round((m-e)/o);if(h<0||h>Math.round(u/o))continue;const f=Math.round((e+h*o)*1e4)/1e4;if(!(Math.abs(f-m)>=o/100))for(const g of numbersInText(l?.[f]??String(f)))t.add(g)}return t}function exampleRevealsOrderingMember(n,t,e){if(n.visualData?.type!=="drag_order")return!1;const s=union(numbersInText(t.title),...t.steps.flatMap(r=>[numbersInText(r.caption),numbersInText(r.spoken),numbersInJson(r.visual_data),objectsGridDrawnNumbers(r.visual_data),lineGraphTickNumbers(r.visual_data),orderingLineTickNumbers(r.visual_data)])),o=normaliseComparableText([t.title,...t.steps.flatMap(r=>[r.caption,r.spoken,JSON.stringify(r.visual_data)])].join(" ")).replace(/\bnought point\b/g,"zero point");return e.some(r=>{const a=r.split(",").map(i=>i.trim());return a.length<2||a.some(i=>!/^[−-]?\d+(?:\.\d+)?$/.test(i))?!1:a.some(i=>exampleContainsNumber(numberKey(Number(i.replace("−","-"))),s,o))})}const VALUE_TOKEN=/[−-]?\d+(?:,\d{3})*(?:\.\d+)?(?:\s*[/:]\s*\d+(?:\.\d+)?)?/g;function valueTokens(n){return[...n.matchAll(VALUE_TOKEN)].map(t=>t[0].replace(/−/g,"-").replace(/,/g,"").replace(/\s+/g,"").replace(/^(-?)0+(?=\d)/,"$1").replace(/(\.\d*?)0+$/,"$1").replace(/\.$/,""))}const UNIT_ONLY=/^\s*£?\s*(?:p|%|°|degrees?|mm|cm|m|km|ml|l|g|kg|minutes?|hours?|days?)?\s*$/i;function phraseKey(n){return normaliseComparableText(n).replace(/-/g," ").replace(/\s+/g," ")}function exampleEndsOnWrongOption(n,t,e=acceptedAnswers(n)){const s=n.options??[];if(s.length===0)return!1;const o=new Set(e.map(normaliseComparableText)),r=t.steps[t.steps.length-1];if(!r)return!1;const a=valueTokens(r.caption);if(a.length===0)return!1;const i=a[a.length-1],l=phraseKey(r.caption);return s.some(u=>{if(o.has(normaliseComparableText(u)))return!1;const d=valueTokens(u);if(d.length!==1)return!1;const m=u.replace(VALUE_TOKEN,"");return UNIT_ONLY.test(m)?d[0]===i:containsStandalonePhrase(l,phraseKey(u))})}
71function acceptedAnswers(n){const t=n.acceptedAnswers?.length?n.acceptedAnswers:[n.correctAnswer];return[...new Set(t.map(e=>e.trim()).filter(Boolean))]}function repeatsWholeVisibleNumberSet(n,t){const e=union(numbersInText(n.stem),numbersInJson(n.visualData)),s=union(...(n.options??[]).map(numbersInText)),o=e.size>=2?[numbersInText(n.stem),numbersInJson(n.visualData),e]:[s],r=t.steps[0];if(!r)return!1;const a=numbersInText(t.title),i=numbersInJson(r.visual_data),l=[a,i,union(a,i)];return o.some(u=>u.size>=2&&l.some(d=>isSubset(u,d)))}function drawsSamePicture(n,t){const e=n.visualData;if(!e||e.type!=="fraction_shape")return!1;const s=e.equal!==!1,o=Number(e.total_parts),r=Number(e.shaded_parts??0),a=s&&r>0&&r<o;return t.steps.some(i=>{const l=i.visual_data;return!l||l.type!=="fraction_shape"||l.total_parts!==e.total_parts||l.equal!==!1!==s?!1:a?Number(l.shaded_parts??0)===r:l.shape===e.shape})}const SUBJECT_SHAPES=["quadrilateral","semicircle","rectangle","triangle","pentagon","hexagon","octagon","cylinder","pyramid","sphere","square","circle","cuboid","oval","cube","cone"];function shapeWordsIn(n){const t=new Set,e=normaliseComparableText(n);for(const s of SUBJECT_SHAPES)(containsStandalonePhrase(e,s)||containsStandalonePhrase(e,`${s}s`))&&t.add(s);return t.has("semicircle")&&t.delete("circle"),t}function talksAboutSameShape(n,t){if(!n.microSkillId.startsWith("GEO.P3."))return!1;const e=shapeWordsIn(n.stem??"");if(e.size===0)return!1;const s=[t.title,...t.steps.flatMap(r=>[r.caption,r.spoken])].join(" "),o=shapeWordsIn(s);return[...e].some(r=>o.has(r))}const CURVED_SUBJECT=/\b(circle|circles|curved|curve|oval|semicircle|semi-circle)\b/i;function curvedEdgeNotShown(n,t){if(!n.microSkillId.startsWith("GEO.P3."))return!1;const e=n.visualData,s=`${n.stem??""} ${e?.type==="shape_svg"?e.shape??"":""}`;if(!CURVED_SUBJECT.test(s))return!1;const o=[t.title,...t.steps.flatMap(r=>[r.caption,r.spoken,JSON.stringify(r.visual_data??null)])].join(" ");return!/\bcurve/i.test(o)&&!CURVED_SUBJECT.test(o)}function tallyNotShown(n,t){return n.visualData?.type!=="tally"?!1:!t.steps.some(s=>s.visual_data?.type==="tally")}function offsetStartNotShown(n,t){const e=n.visualData;return!e||e.type!=="measure_bars"||!Array.isArray(e.bars)||!e.bars.some(s=>Number(s.start??0)>0)?!1:!t.steps.some(s=>{const o=s.visual_data;return o?.type==="measure_bars"&&Array.isArray(o.bars)&&o.bars.some(r=>Number(r.start??0)>0)})}function evaluatesTheClaim(n,t,e){if(!e.every(l=>/^(true|false|yes|no)$/i.test(l.trim())))return!1;const s=n.visualData;if(!s||s.type!=="equation_display"||!Array.isArray(s.terms))return!1;const o=s.terms.indexOf("=");if(o<1)return!1;const r=s.terms.slice(0,o).filter(l=>typeof l=="number");if(r.length<2)return!1;const a=union(numbersInText(t.title),...t.steps.flatMap(l=>[numbersInText(l.caption),numbersInText(l.spoken),numbersInJson(l.visual_data)])),i=[t.title,...t.steps.flatMap(l=>[l.caption,l.spoken])].map(normaliseComparableText).join(" ");return r.every(l=>exampleContainsNumber(numberKey(l),a,i))}const ADD_SUB="[+-]",LIVE_UNKNOWN=[[new RegExp(`^\\d+ ${ADD_SUB} \\d+ = (?:__|\\?)$`),"result"],[new RegExp(`^(?:__|\\?) = \\d+ ${ADD_SUB} \\d+$`),"result"],[new RegExp(`^Which equals \\d+ ${ADD_SUB} \\d+\\?$`),"result"],[new RegExp(`^__ ${ADD_SUB} \\d+ = \\d+$`),"start"],[new RegExp(`^\\d+ ${ADD_SUB} __ = \\d+(?:\\. What is missing\\?)?$`),"change"],[new RegExp(`^\\d+ ${ADD_SUB} \\d+ = \\d+ ${ADD_SUB} __$`),"balance"],[/\bbefore\?$/i,"start"]];function stemForm(n){return n.replace(/−/g,"-").replace(/_{2,}/g,"__").replace(/\s+/g," ").trim().replace(/\.$/,"")}function liveUnknownPosition(n){const t=stemForm(n);for(const[e,s]of LIVE_UNKNOWN)if(e.test(t))return s;return null}function exampleUnknownPosition(n){const t=stemForm(n);if(new RegExp(`^How do we work out \\d+ ${ADD_SUB} \\d+\\?$`).test(t))return"result";if(!t.includes("__"))return null;const e=t.replace(/^(?:How do we work out|Missing number:)\s*/i,"").replace(/\?$/,"");return liveUnknownPosition(e)}function findsDifferentUnknown(n,t){if(!n.microSkillId.startsWith("AS.P4."))return!1;const e=liveUnknownPosition(n.stem??"");if(!e||e==="result")return!1;const s=exampleUnknownPosition(t.title);return s!==null&&s!==e}function usesDifferentScaffold(n,t){return!n.microSkillId.startsWith("AS.P4.")||n.visualData?.type!=="two_step"?!1:!t.steps.some(s=>s.visual_data?.type==="two_step")}function columnExchanges(n,t,e){let s=0,o=0;for(let r=1;r<=Math.max(n,t);r*=10){const a=Math.floor(n/r)%10,i=Math.floor(t/r)%10;e==="add"?o=a+i+o>=10?1:0:o=a-o<i?1:0,s+=o}return s}function exchangesDiffer(n,t){if(!n.microSkillId.startsWith("AS.P5."))return!1;const e=n.visualData;if(e?.type!=="column_method"||!Number.isInteger(e.top)||!Number.isInteger(e.bottom))return!1;const s=t.steps.map(a=>a.visual_data).find(a=>a?.type==="column_method"&&a.operation===e.operation);if(!s||!Number.isInteger(s.top)||!Number.isInteger(s.bottom))return!1;const o=columnExchanges(e.top,e.bottom,e.operation??""),r=columnExchanges(s.top,s.bottom,s.operation??"");return o===0!=(r===0)?!0:e.operation==="subtract"?exchangesThroughZero(e.top,e.bottom)!==exchangesThroughZero(s.top,s.bottom):!1}function exchangesThroughZero(n,t){const e=Math.floor(n/10)%10;return n>=100&&e===0&&n%10<t%10}const ASKS_TO_ORDER=/\border\b|\b(?:shortest|longest|smallest|largest|lightest|heaviest|least|most) first\b/i,SHOWS_ORDERING=/\bf
71irst\b|\border\b|\bthen\b.*\bthen\b/i;function doesNotOrder(n,t){if(!n.microSkillId.startsWith("MEA.")||!ASKS_TO_ORDER.test(n.stem??""))return!1;const e=[t.title,...t.steps.flatMap(s=>[s.caption,s.spoken])].join(" ");return!SHOWS_ORDERING.test(e)}function readsArrayOtherWay(n,t){if(!n.microSkillId.startsWith("MD."))return!1;const e=n.visualData;return e?.type!=="array"||!Number.isInteger(e.rows)||!Number.isInteger(e.cols)||e.rows===e.cols||Number(n.correctAnswer)!==e.cols?!1:t.steps.some(o=>o.animation.startsWith("group_count"))}function yearOneTakeAwayMove(n,t){return n<=10?t>n-t?"on":"back":t<=5||n-t>=10?"back":"on"}function exampleMove(n){const t=[n.title,...n.steps.flatMap(o=>[o.caption,o.spoken])].join(" ").toLowerCase(),e=/count(?:ing)? (?:on|up)\b|find the gap/.test(t),s=/count(?:ing)? back|jump back|take (?:the )?\d+ away|\bback\b/.test(t);return e&&!s?"on":s&&!e?"back":null}function teachesDifferentYearOneMethod(n,t){if(!/^AS\.P[23]\./.test(n.microSkillId))return!1;const e=stemForm(n.stem??"");[t.title,...t.steps.flatMap(o=>[o.caption,o.spoken])].join(" ");const s=/^(\d+) - (\d+) = __$/.exec(e);if(s){const o=n.visualData?.type==="take_away"?"back":yearOneTakeAwayMove(Number(s[1]),Number(s[2])),r=exampleMove(t);return r!==null&&r!==o}if(/^(?:\d+ - __|__ - \d+) = \d+$/.test(e)){const o=[t.title,...t.steps.map(r=>r.caption)].join(" ");return!/[-−]|take away/i.test(o)}return/^How many more to \d+\?$/i.test(e)?/__/.test(stemForm(t.title)):!1}function isNearCopy(n,t){if(!n.microSkillId.startsWith("AS."))return!1;const e=/^(\d+) ([+-]) (\d+) = __$/.exec(stemForm(n.stem??"")),s=/^How do we work out (\d+) ([+-]) (\d+)\?$/.exec(stemForm(t.title));if(!e||!s||e[2]!==s[2])return!1;const[o,r,a,i]=[e[1],e[3],s[1],s[3]].map(Number);return o===a&&Math.abs(r-i)===1||r===i&&Math.abs(o-a)===1}function drawsSameBars(n,t){const e=r=>(r.bars??[]).map(a=>`${a.start??0}:${a.length}`).sort().join("|"),s=n.visualData;if(s?.type!=="measure_bars"||!s.bars?.length)return!1;const o=e(s);return t.steps.some(r=>{const a=r.visual_data;return a?.type==="measure_bars"&&e(a)===o})}function coinCountEqualsValue(n,t){const e=n.visualData;if(!e||e.type!=="coin_set"||!Array.isArray(e.coins)||e.coins.every(o=>o==="1p"))return!1;const s=t.steps.map(o=>o.visual_data).filter(o=>o?.type==="coin_set"&&Array.isArray(o.coins)&&o.coins.length>0);return s.length>0&&s.every(o=>o.coins.every(r=>r==="1p"))}const UNIT_SPELLINGS={mm:["length","mm"],millimetre:["length","mm"],millimetres:["length","mm"],cm:["length","cm"],centimetre:["length","cm"],centimetres:["length","cm"],m:["length","m"],metre:["length","m"],metres:["length","m"],km:["length","km"],kilometre:["length","km"],kilometres:["length","km"],g:["mass","g"],gram:["mass","g"],grams:["mass","g"],kg:["mass","kg"],kilogram:["mass","kg"],kilograms:["mass","kg"],ml:["capacity","ml"],millilitre:["capacity","ml"],millilitres:["capacity","ml"],l:["capacity","l"],litre:["capacity","l"],litres:["capacity","l"]},UNIT_SYMBOL_AFTER_NUMBER=/(?:\d|_|\?|\bmany|\bin|\binto)\s*(mm|cm|km|kg|ml|m|g|l)\b/gi,UNIT_WORD_AFTER_NUMBER=new RegExp("\\b(?:\\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety|hundred|thousand|half|quarter|quarters|many|in|into)(?:\\s+of\\s+a)?\\s+(millimetres?|centimetres?|kilometres?|metres?|kilograms?|grams?|millilitres?|litres?)\\b","gi");function unitsIn(n,t){const e=new Map,s=a=>{const i=UNIT_SPELLINGS[a.toLowerCase()];if(!i)return;const[l,u]=i;e.has(l)||e.set(l,new Set),e.get(l).add(u)},o=a=>{for(const i of a.matchAll(UNIT_SYMBOL_AFTER_NUMBER))s(i[1]);for(const i of a.matchAll(UNIT_WORD_AFTER_NUMBER))s(i[1])};n.forEach(o);const r=(a,i)=>{if(typeof a=="string")i==="unit"||UNIT_SPELLINGS[a.trim().toLowerCase()]?s(a.trim()):i!=="type"&&i!=="color"&&i!=="animation"&&o(a);else if(Array.isArray(a))a.forEach(l=>r(l,i));else if(a&&typeof a=="object")for(const[l,u]of Object.entries(a))r(u,l)};return r(t),e}function unitConversionMismatch(n,t){if(!n.microSkillId.startsWith("MEA."))return!1;const e=unitsIn([n.stem??"",...n.options??[]],n.visualData),s=unitsIn([t.title,...t.steps.flatMap(o=>[o.caption,o.spoken])],t.steps.map(o=>o.visual_data));for(const[o,r]of e){const a=s.get(o);if(a&&r.size>1!=a.size>1)return!0}return!1}const ASKS_WHAT_IS_LEFT=/\bhow (?:many|much)\b.*\b(?:left|remaining|remain)\b/i,SHOWS_TAKING_AWAY=/take away|takes away|subtract|−|\bminus\b|\bleft\b|fewer|count(?:ing)? back|remain/i;function neverTakesAway(n,t){if(!n.microSkillId.startsWith("MEA.")||!ASKS_WHAT_IS_LEFT.test(n.stem??""))return!1;const e=[t.title,...t.steps.flatMap(s=>[s.caption,s.spoken])].join(" ");return!SHOWS_TAKING_AWAY.test(e)}function numberLineNotShown(n,t){if(!n.microSkillId.startsWith("MD."))return!1;const e=n.visualData;if(!e||e.type!=="number_line")return!1;const s=t.steps.map(o=>o.visual_data?.type);return!s.includes("number_line")&&s.includes("array")}
71function assessPreAnswerWexSafety(n,t){const e=acceptedAnswers(n);if(!n.questionId.trim()||!n.microSkillId.trim()||e.length===0||!t.title.trim()||t.steps.length!==3||t.steps.some(o=>!o.caption.trim()||!o.spoken.trim()))return{safe:!1,reasons:["invalid-input"]};const s=[];return CONFIRMED_DIFFERENT_TASK_FORMS.has(`${n.microSkillId}::${n.mathematicalForm??""}`)&&s.push("known-different-task"),t.provenance&&normaliseQuestionId$1(t.provenance.source_question_id)===normaliseQuestionId$1(n.questionId)&&s.push("same-source-question"),(e.some(o=>exampleRevealsAnswer(t,o))||exampleRevealsOrderingMember(n,t,e))&&s.push("live-answer-present"),repeatsWholeVisibleNumberSet(n,t)&&s.push("same-visible-number-set"),exampleEndsOnWrongOption(n,t,e)&&s.push("ends-on-wrong-option"),drawsSamePicture(n,t)&&s.push("same-picture"),evaluatesTheClaim(n,t,e)&&s.push("evaluates-the-claim"),offsetStartNotShown(n,t)&&s.push("offset-start"),curvedEdgeNotShown(n,t)&&s.push("curved-edge-not-shown"),tallyNotShown(n,t)&&s.push("tally-not-shown"),findsDifferentUnknown(n,t)&&s.push("different-unknown"),usesDifferentScaffold(n,t)&&s.push("different-scaffold"),exchangesDiffer(n,t)&&s.push("different-exchange"),doesNotOrder(n,t)&&s.push("does-not-order"),readsArrayOtherWay(n,t)&&s.push("different-array-reading"),drawsSameBars(n,t)&&s.push("near-copy"),coinCountEqualsValue(n,t)&&s.push("count-equals-value"),numberLineNotShown(n,t)&&s.push("number-line-not-shown"),teachesDifferentYearOneMethod(n,t)&&s.push("different-method"),isNearCopy(n,t)&&s.push("near-copy"),!t.provenance&&talksAboutSameShape(n,t)&&s.push("same-shape-subject"),!t.provenance&&unitConversionMismatch(n,t)&&s.push("unit-conversion-mismatch"),!t.provenance&&neverTakesAway(n,t)&&s.push("never-takes-away"),{safe:s.length===0,reasons:s}}function guardPreAnswerWorkedExample(n,t){if(!t)return null;try{return assessPreAnswerWexSafety(n,t).safe?t:null}catch{return null}}const PRE_ANSWER_POLICY=preAnswerPolicyRaw,GUARDED_CANNED_IDS=new Set(PRE_ANSWER_POLICY.guarded_canned_question_ids);function fnv1a32(n,t){let e=t;for(let s=0;s<n.length;s+=1)e^=n.charCodeAt(s),e=Math.imul(e,16777619);return e>>>0}function preAnswerQuestionFingerprint(n){const t=JSON.stringify({id:n.id,micro_skill_id:n.micro_skill_id,mathematical_form:n.mathematical_form??"",representation:n.representation??"",interaction_type:n.interaction_type??"",prompt:n.prompt,answer_spec:n.answer_spec,visual_data:n.visual_data??null}),e=fnv1a32(t,2166136261).toString(16).padStart(8,"0"),s=fnv1a32(t,3339675911).toString(16).padStart(8,"0");return`fnv1a64x2:${e}${s}`}function fingerprintMatches(n){return PRE_ANSWER_POLICY.target_fingerprints[n.id]===preAnswerQuestionFingerprint(n)}function normaliseQuestionId(n){return n.trim().replace(/^cb-/,"")}function visualType(n){return n.visual_data?.type??"none"}function contractMatches(n,t){return n.micro_skill_id===t.micro_skill_id&&(n.mathematical_form??"")===t.mathematical_form&&(n.representation??"")===t.representation&&(n.interaction_type??"")===t.interaction_type&&visualType(n)===t.visual_type&&n.answer_spec.kind===t.answer_kind&&n.status===t.status}
71function acceptedAnswersForBankQuestion(n){const t=n.answer_spec;if(t.kind==="select"){const e=new Set(t.correct_option_ids);return t.options.filter(s=>e.has(s.id)).map(s=>s.label)}return t.kind==="free_response"||t.kind==="number_line"?t.accepted_values.map(String):[]}function bankQuestionToInput(n){const t=acceptedAnswersForBankQuestion(n);return{questionId:n.id,microSkillId:n.micro_skill_id,mathematicalForm:n.mathematical_form,stem:wexStemForQuestion(n.prompt.stem,n.prompt.spoken_prompt,n.visual_data),correctAnswer:t[0]??"",acceptedAnswers:t,visualData:n.visual_data??null,options:n.answer_spec.kind==="select"?n.answer_spec.options.map(e=>e.label):void 0}}function fixtureToInput(n){return{questionId:n.question_id,microSkillId:n.micro_skill_id,mathematicalForm:n.mathematical_form,stem:n.stem,correctAnswer:n.correct_answer,acceptedAnswers:[n.correct_answer],visualData:n.visual_data,options:n.options}}function suppressed(n){return{example:null,kind:"suppressed",reason:n,donorQuestionId:null,expectedBehaviorSha256:null}}function resolvePreAnswerWorkedExample(n,t){try{const e=normaliseQuestionId(n.questionId),s=getQuestionBankIndex().byId.get(e);if(!s||s.status!=="approved")return suppressed("unknown-question");if(!fingerprintMatches(s))return suppressed("stale-policy");const o={...n,questionId:e,acceptedAnswers:acceptedAnswersForBankQuestion(s)},r=PRE_ANSWER_POLICY.routes[e];if(!r){if(!GUARDED_CANNED_IDS.has(e))return suppressed("unknown-question");const u=guardPreAnswerWorkedExample(o,t);return u?{example:u,kind:"guarded-canned",reason:null,donorQuestionId:null,expectedBehaviorSha256:null}:suppressed(t?"safety-guard":"missing-example")}if(r.action==="suppress")return suppressed(r.reason);if(!contractMatches(s,r.target_contract))return suppressed("stale-policy");let a=null,i;if(r.action==="analogue-donor"){const u=getQuestionBankIndex().byId.get(r.donor_question_id);if(!u||u.id===e||u.status!=="approved"||!fingerprintMatches(u)||!contractMatches(u,r.donor_contract))return suppressed("stale-policy");a=getWorkedExampleForQuestion(bankQuestionToInput(u)),i=u.id}else{const u=PRE_ANSWER_POLICY.fixtures[r.fixture_id];if(!u)return suppressed("stale-policy");a=getWorkedExampleForQuestion(fixtureToInput(u)),i=u.question_id}if(!a?.provenance||a.provenance.generator_id!==r.expected_generator_id||normaliseQuestionId(a.provenance.source_question_id)!==normaliseQuestionId(i))return suppressed("stale-policy");const l=guardPreAnswerWorkedExample(o,a);return l?{example:l,kind:r.action,reason:null,donorQuestionId:i,expectedBehaviorSha256:r.expected_behavior_sha256}:suppressed("safety-guard")}catch{return suppressed("stale-policy")}}function buildWexEvent(n){return{childId:n.childId,sessionId:n.sessionId,microSkillId:n.microSkillId,trigger:n.trigger,content:n.provenance?"parameterized":"canned",generatorId:n.provenance?.generator_id??null,sourceQuestionId:n.provenance?.source_question_id??null,stepsReached:Math.max(1,n.dismiss.stepsReached),replays:Math.max(0,n.dismiss.replays),outcome:n.dismiss.outcome}}const MAX_AUTO_FIRST_SEEN_INTROS_PER_SESSION=0;function canAutoShowWorkedExample(n){if(n.isCalibrating)return!1;const t=n.maxPerSession??MAX_AUTO_FIRST_SEEN_INTROS_PER_SESSION;return n.autoShownThisSession<t}const wexLocalStorageKey=n=>`wex.${n}.seen`,SAMPLE_DEMO_EYEBROW="Mia shows how",SAMPLE_EXAMPLE_EYEBROW="Mia shows one like it",SAMPLE_EXAMPLE_DELAY_MS=1400,MAX_EXPLAIN_RETAPS=4,MIA_LOOK_AT_QUESTION={x:-.75,y:.15};function SessionScreen({sessionConfig:n,pupilId:t,yearGroupLabel:e,progressSavingRequired:s=!1,onStartBackendSession:o,onCancelBeforeStart:r,onRecordAttempt:a,onLoadSkillStates:i,onLoadSkillRecency:l,onLoadSkillAccuracy:u,onLoadDifficultyCarry:d,onHasAttemptedMicroSkill:m,onRecordWexEvent:h,onCompleteBackendSession:f,onSessionComplete:g,onAbortSession:b,onHelpUsed:x,onSampleDemo:y,onOpenSampleSignUp:E}){const k=(n.curatedQuestionIds?.length??0)>0,T=reactExports.useRef({clues:0,readAloud:0,examples:0}),A=reactExports.useRef({questionId:null,counts:{}}),N=reactExports.useCallback(ve=>g({...ve,miaHelps:{...T.current}}),[g]),{questions:S,currentIndex:M,currentQuestion:L,selectedAnswer:P,lastCheckedAnswer:F,status:D,attempts:U,shownHintCount:q,hintDisabled:V,canAdvance:O,isLoading:z,sessionSaveStatus:K,retrySessionStart:Z,isLoadingNext:Q,isChecking:B,isSpeakingQuestion:G,isSpeakingHint:X,canSaveForLater:ne,onChangeAnswer:se,handleHint:te,handleCheck:oe,handleNext:le,handleSaveForLater:ae,handleSkipBroken:ie,handleExitToMenu:we,setIsSpeakingQuestion:ke,setIsSpeakingHint:Te,backendSessionId:Se,isCalibrating:$e}=useSessionEngine(n,t,e,{progressSavingRequired:s,onStartBackendSession:o,onRecordAttempt:a,onLoadSkillStates:i,onLoadSkillRecency:l,onLoadSkillAccuracy:u,onLoadDifficultyCarry:d,onCompleteBackendSession:f,getHelpUsed:ve=>{const Ee=A.current;return Ee.questionId===ve&&Object.keys(Ee.counts).length>0?{...Ee.counts}:null}},N,b),[Re,ye]=reactExports.useState(!1),[Ne,Ye]=reactExports.useState(!1),Ge=k?sampleHelpModeAt(M):"none",Fe=reactExports.useRef(null),Xe=reactExports.useRef(null),Rt=reactExports.useRef(null),{tutorMessage:ge,tutorMood:Ie,tutorKind:ft,fireTutorEvent:wt}=useTutor(e,(ve,Ee)=>{if(Ee==="reveal"&&!readsExplanationsAloud(e)||Ee==="reveal"&&(kt||sn))return;const ze=toSpeechFriendlyText(ve);Ee==="hint"&&L&&(Rt.current={questionId:L.id,text:ze}),Fe.current?.(ze,"hint",Ee==="reveal"?()=>Xe.current?.():void 0)},t),[mt,et]=reactExports.useState(null),Ke=reactExports.useCallback(ve=>{const Ee=gestureForTutorEvent(ve.type);Ee&&et({gesture:Ee,at:Date.now()}
71),wt(ve)},[wt]),Pt=reactExports.useRef(void 0),ht=reactExports.useRef("idle"),lt=reactExports.useRef(0),ct=reactExports.useRef(!1),$t=reactExports.useRef(null);reactExports.useEffect(()=>{if(!canUseSpeechSynthesis())return;const ve=window.speechSynthesis,Ee=()=>{ve.getVoices()};return Ee(),ve.onvoiceschanged=Ee,()=>{ve.onvoiceschanged===Ee&&(ve.onvoiceschanged=null)}},[]),reactExports.useEffect(()=>()=>{$t.current?.cancel(),$t.current=null,canUseSpeechSynthesis()&&window.speechSynthesis.cancel(),ke(!1),Te(!1)},[]),reactExports.useEffect(()=>{const ve=S[M+1];if(!ve)return;const Ee=ve.spokenPrompt??ve.stem;Ee&&prefetchSpeech(toSpeechFriendlyText(Ee))},[M,S]),reactExports.useEffect(()=>{if(!L||L.type!=="equation")return;const ve=Ee=>{if(!L||L.type!=="equation")return;const ze=Ee.target;if(!(ze&&["INPUT","TEXTAREA","SELECT"].includes(ze.tagName))){if(/^\d$/.test(Ee.key)){Ee.preventDefault(),se(`${P??""}${Ee.key}`.slice(0,7));return}if(Ee.key==="Backspace"){Ee.preventDefault(),se((P??"").slice(0,-1));return}if(Ee.key==="Delete"){Ee.preventDefault(),se("");return}if(Ee.key==="Enter"){if(Ee.preventDefault(),!O&&P&&!B&&U<getMaxAttempts(L)){oe();return}O&&!Q&&(Ke({type:"question_advance"}),le())}}};return window.addEventListener("keydown",ve),()=>{window.removeEventListener("keydown",ve)}},[U,O,L,Ke,B,Q,P]);const ot=reactExports.useRef(0);reactExports.useEffect(()=>{if(!P||D!=="idle")return;const ve=Date.now();ve-ot.current<5e3||(ot.current=ve,et({gesture:"glance",at:ve}))},[P,D]),reactExports.useEffect(()=>{if(z){ct.current=!1;return}L&&!ct.current&&(ct.current=!0,Ke({type:"session_start",strand:n.strands[0]??"Number & Place Value"}))},[z,L?.id,Ke]),reactExports.useEffect(()=>{if(!L)return;const ve=L.id;if(ve!==Pt.current){Pt.current=ve,ht.current=D,lt.current=U;return}const Ee=ht.current,ze=lt.current;D==="correct"&&Ee!=="correct"?Ke({type:ze===0?"correct_first":"correct_retry"}):D==="incorrect"&&Ee!=="incorrect"&&U>=getMaxAttempts(L)?Ke({type:"wrong_exhausted",questionStem:L.stem,wrongAnswer:P??"",correctAnswer:L.correctAnswer,questionId:L.debugInfo?.questionId}):D==="incorrect"&&Ee!=="incorrect"&&Ke({type:"wrong_first"}),ht.current=D,lt.current=U},[D,U,L?.id,Ke,P]);const nn=(ve,Ee,ze)=>{ve.trim()&&($t.current?.cancel(),ke(!1),Te(!1),$t.current=speak(ve,{rate:Ee==="hint"?.92:.9,pitch:1.02,onStart:()=>{Ee==="question"?ke(!0):Te(!0)},onEnd:()=>{ke(!1),Te(!1),ze?.()},onStop:()=>{ke(!1),Te(!1)},onError:()=>{ke(!1),Te(!1)}}))};Fe.current=nn;const It=ve=>{if(!L)return;A.current.questionId!==L.id&&(A.current={questionId:L.id,counts:{}});const ze=A.current.counts;ze[ve]=(ze[ve]??0)+1;const Mt=T.current;ve==="clue"?Mt.clues+=1:ve==="read_aloud"?Mt.readAloud+=1:(ve==="example_before"||ve==="example_after"||ve==="trick")&&(Mt.examples+=1),x?.(ve),ls()},ln=()=>
71{if(!L)return;const ve=L.spokenPrompt??L.stem;It("read_aloud"),nn(toSpeechFriendlyText(ve),"question")},Et=()=>{if(!(!L||V))if(q<2){const ve=L.hints[q];if(te(),It("clue"),ve){const Ee=`${SPOKEN_CLUE_PREFIX} ${toSpeechFriendlyText(ve)}`;Rt.current={questionId:L.id,text:Ee},nn(Ee,"hint"),Ke({type:"hint",hintText:ve})}q===1&&Ke({type:"prefetch_tier_model",questionStem:L.stem,correctAnswer:L.correctAnswer,questionId:L.debugInfo?.questionId,wrongAnswer:P??void 0})}else te(),It("clue"),Ke({type:"hint_tier_model",questionStem:L.stem,correctAnswer:L.correctAnswer,questionId:L.debugInfo?.questionId,wrongAnswer:P??void 0})},[Qe,Ut]=reactExports.useState(null),[Xt,Ce]=reactExports.useState(null),kt=Qe!==null,[sn,En]=reactExports.useState(null),[wn,Zt]=reactExports.useState(void 0),[$n,cn]=reactExports.useState(!1),[kn,Tn]=reactExports.useState(!1),zn=reactExports.useRef(void 0);zn.current=L?.id,reactExports.useEffect(()=>{Tn(!1),cn(!1),Gt(!1)},[L?.id]);const[Sn,Rn]=reactExports.useState([]),[Yn,Gt]=reactExports.useState(!1),[rt,At]=reactExports.useState(null),un=reactExports.useCallback(async()=>{if(rt){jt.current.add(rt),on.current.add(rt),Ce(rt),Ut("after-exhausted");return}Ke({type:"question_advance"}),await le()},[Ke,le,rt]),On=reactExports.useRef(un);On.current=un;const[as,xe]=reactExports.useState(null),[Ve,tt]=reactExports.useState(null),[Le,He]=reactExports.useState(!1),gt=reactExports.useRef(!1),at=Ge==="demo"&&!!L&&O&&D==="correct";reactExports.useEffect(()=>{const ve=M===effectiveQuestionCount(n)-1;if(D!=="correct"||ve||gt.current||Ve===L?.id){xe(null);return}xe(2);const Ee=setInterval(()=>{xe(Mt=>Mt!==null&&Mt>1?Mt-1:Mt)},1e3),ze=setTimeout(()=>{On.current()},2e3);return()=>{clearTimeout(ze),clearInterval(Ee),xe(null)}},[D,L?.id,M,n.questionCount,n.targetQuestionId,Ve]);const be=L?.debugInfo?.microSkillId,We=!!be&&listWorkedExampleMicroSkills().includes(be),De=isForceWorkedExample(),An=reactExports.useMemo(()=>{if(!L||!be||!(O&&(D==="incorrect"||at)))return null;const ve=getWorkedExampleForQuestion({questionId:L.debugInfo?.questionId??L.id,microSkillId:be,mathematicalForm:L.mathematicalForm,stem:wexStemForQuestion(L.stem,L.spokenPrompt,L.visual_data),correctAnswer:L.correctAnswer,visualData:L.visual_data,options:L.options});return ve?.provenance&&console.debug("[wex] parameterized example",ve.provenance),ve},[L,be,O,D,at]),Jt=D==="incorrect"?An:null,Ct=reactExports.useMemo(()=>be?getWorkedExampleForMicroSkillForm(be,L?.mathematicalForm):null,[be,L?.mathematicalForm]),xn=reactExports.useMemo(()=>!L||!be?null:resolvePreAnswerWorkedExample({questionId:L.debugInfo?.questionId??L.id,microSkillId:be,mathematicalForm:L.mathematicalForm,stem:wexStemForQuestion(L.stem,L.spokenPrompt,L.visual_data),correctAnswer:L.correctAnswer,acceptedAnswers:L.acceptedAnswers,visualData:L.visual_data,options:L.options},Ct),[L,be,Ct])?.example??null,Kn=O?D==="incorrect"?Jt??Ct:null:xn,Tt=at?An??Ct:null,qt=!!L&&!!(Ge!=="demo"||!O?null:D==="correct"?Tt:D==="incorrect"&&We?Jt??Ct:null)&&Ve!==L.id;gt.current=qt;const jt=reactExports.useRef(new Set),on=reactExports.useRef(new Set),Us=reactExports.useRef(null),xo=reactExports.useRef(0);reactExports.useEffect(()=>{if(z||!L||!We||!be||kt||jt.current.has(be)||!De&&!xn||!De&&!canAutoShowWorkedExample({isCalibrating:$e,autoShownThisSession:xo.current})||Us.current===L.id)return;if(Us.current=L.id,De){jt.current.add(be),xo.current+=1,Ce(be),Ut("first-seen");return}let ve=!1;return(async()=>{let Ee=!1;if(m)try{Ee=await m(t,be)}catch(ze){console.warn("[wex] first-seen check failed, falling back to localStorage",ze)}!Ee&&typeof window<"u"&&(Ee=window.localStorage.getItem(wexLocalStorageKey(be))==="true"),!ve&&(Ee||(jt.current.add(be),xo.current+=1,Ce(be),Ut("first-seen")))})(),()=>{ve=!0}},[z,L?.id,be,We,t,m,kt,$e,De,xn]);const Gs=3,yn=reactExports.useRef(new Map),ps=reactExports.useRef(null);reactExports.useEffect(()=>{if(!L||!We||!be||!O||ps.current===L.id)return;if(ps.current=L.id,D==="correct"){yn.current.set(be,0);return}const ve=(yn.current.get(be)??0)+1;yn.current.set(be,ve),!$e&&ve>=Gs&&!on.current.has(be)&&(At(be),yn.current.set(be,0))},[O,D,L,We,be,$e]);const wr=reactExports.useCallback(async()=>{$t.current?.cancel(),$t.current=null,canUseSpeechSynthesis()&&window.speechSynthesis.cancel(),ke(!1),Te(!1),Ke({type:"question_saved"}),await ae()},[Ke,ae,Te,ke]),gn=reactExports.useCallback(()=>{!be||!Kn||(jt.current.add(be),O&&D==="incorrect"&&(yn.current.set(be,0),At(null)),It(O?"example_after":"example_before"),Ce(be),Ut("on-demand"))},[be,Kn,O,D]),yo=reactExports.useCallback(()=>{!be||!We||(jt.current.add(be),yn.current.set(be,0),At(null),Ce(be),It("example_after"),Ut("reveal-offer"))},[be,We]);reactExports.useEffect(()=>{if(!qt||kt||!L||!be)return;const ve=L.id,Ee=be,ze=D==="correct"?"trick":"reveal-offer",Mt=setTimeout(()=>{tt(ve),dn(ve),ze==="reveal-offer"&&(jt.current.add(Ee),yn.current.set(Ee,0),At(null)),T.current.examples+=1,He(!0),Ce(Ee),Ut
71(ze)},1200);return()=>clearTimeout(Mt)},[qt,kt,L,be,D]);const[qs,is]=reactExports.useState(null);reactExports.useEffect(()=>{if(Ge!=="example"||z||!L||O||kt||!be||!xn||qs===L.id)return;if(Ne){is(L.id);return}const ve=L.id,Ee=be,ze=setTimeout(()=>{is(ve),jt.current.add(Ee),T.current.examples+=1,He(!0),Ce(Ee),Ut("on-demand")},SAMPLE_EXAMPLE_DELAY_MS);return()=>clearTimeout(ze)},[Ge,z,L,O,kt,be,xn,qs,Ne]);const Qn=reactExports.useCallback(async(ve=!1)=>{if(!L||(ve?Yn:$n))return;ve||Tn(!1);const Ee=ve?Gt:cn;Ee(!0);const ze=L.id;try{const Mt=ve?Sn:[],Nn=await getAlternateExplanation(L.debugInfo?.questionId??L.id,{wrongAnswer:P||F||void 0,alreadyShown:Mt});if(zn.current!==ze)return;Nn?(En(Nn.steps),Zt(Nn.representation),Rn([...Mt,Nn.representation])):ve||Tn(!0)}finally{zn.current===ze&&Ee(!1)}},[L,$n,Yn,Sn,P,F,e,be]),ys=reactExports.useMemo(()=>{if(!kt||!L||!(O&&D==="incorrect"))return null;const ve=["drag_order","bar_builder"].includes(L.visual_data?.type??""),Ee=String(L.correctAnswer??"").trim(),ze=ve?Ee.replace(/,(?=\S)/g,", "):Ee;if(!ze)return null;const Mt=L.type==="multiple_choice"?(L.options??[]).indexOf(L.correctAnswer):-1,Nn=L.options??[];return{stem:L.stem,answer:ze,optionLetter:Mt>=0?optionBadgeLabel(Nn,Mt):null,options:Mt>=0?Nn.map((kr,us)=>({letter:optionBadgeLabel(Nn,us),label:kr,correct:us===Mt,picked:us!==Mt&&kr===P})):null}},[kt,L,O,D,P]),vs=reactExports.useMemo(()=>!Qe||!Xt||Xt!==be?null:Qe==="first-seen"?De?Ct:xn:Qe==="on-demand"?Kn:Qe==="trick"?Tt:Jt??Ct,[Qe,Xt,be,De,Ct,xn,Kn,Jt,Tt]),$s=reactExports.useCallback(async ve=>{const Ee=Qe,ze=Xt,Mt=Le;Ut(null),He(!1),k&&Ee&&y?.({position:M+1,forced:Mt,outcome:ve.outcome}),Ee&&ze&&h?.(buildWexEvent({childId:t,sessionId:Se,microSkillId:ze,trigger:Ee==="trick"?"on-demand":Ee,provenance:vs?.provenance??null,dismiss:ve})),Ee==="first-seen"&&ze&&typeof window<"u"&&window.localStorage.setItem(wexLocalStorageKey(ze),"true"),Ee==="after-exhausted"&&(At(null),Ke({type:"question_advance"}),await le())},[Qe,Xt,Le,vs,t,Se,h,Ke,le,k,y,M]),[vo,Fo]=reactExports.useState(()=>!hasSeenHelpIntro()),Vs=reactExports.useRef(vo);Vs.current=vo;function ls(){Vs.current&&(Vs.current=!1,markHelpIntroSeen(),Fo(!1))}reactExports.useEffect(()=>{(P||M>0)&&ls()},[P,M]);const[jn,_e]=reactExports.useState(null);reactExports.useEffect(()=>{!L||ft!=="clue"||!ge||_e({questionId:L.id,message:ge,thinking:Ie==="thinking"})},[ft,ge,Ie,L?.id]),reactExports.useEffect(()=>{if(!jn||jn.thinking||O)return;const ve=setTimeout(()=>{const Ee=document.querySelector(".sm-dock");if(!Ee||Ee.getBoundingClientRect().height===0)return;const ze=[".keypad",".option-grid","[aria-label='Ordered slots']",".equation-wrap",".text-input-wrap"].map(Tr=>document.querySelector(`.question-card ${Tr}`)).find(Tr=>!!Tr);if(!ze)return;const Mt=Ee.getBoundingClientRect().top,Nn=ze.getBoundingClientRect();let us=Nn.top+Math.min(Nn.height,140)-(Mt-12);if(us<=0)return;const ma=document.querySelector(".sm-clue-note");ma&&(us=Math.min(us,ma.getBoundingClientRect().top-16)),us>0&&window.scrollBy({top:us,behavior:"smooth"})},400);return()=>clearTimeout(ve)},[jn?.message,jn?.thinking,O]);const[Me,Be]=reactExports.useState(!1);reactExports.useEffect(()=>{if(Be(!1),Ye(!1),!L||O)return;const ve=setTimeout(()=>Be(!0),2e4);return()=>clearTimeout(ve)},[L?.id,O]);const[Ft,dn]=reactExports.useState(null);if(K==="starting"||K==="failed")return jsxRuntimeExports.jsx(SessionStartGate,{status:K,onRetry:()=>{Z()},onBack:r??we});if(z||!L)return jsxRuntimeExports.jsxs("div",{className:"sm-app",children:[jsxRuntimeExports.jsx("div",{className:"sm-grain","aria-hidden":"true"}),jsxRuntimeExports.jsx("div",{className:"sm-screen sm-session-wrap",children:jsxRuntimeExports.jsx("div",{className:"sm-session-body",children:jsxRuntimeExports.jsx("div",{className:"sm-q-card",style:{minHeight:240,alignItems:"center",justifyItems:"center"},children:jsxRuntimeExports.jsx("p",{className:"sm-eyebrow",children:"Loading today's questions…"})})})})]});
71const en=effectiveQuestionCount(n),bn=M===en-1,Ho=getMaxAttempts(L),Es=!P||B||U>=Ho||Re,Mn=buildSessionLabel(n.strands),_n=canUseSpokenAudio(),Vt=!O,xr=Vt&&!!Kn&&!kt,zs=vo&&M===0&&Vt,cs=Q?"Loading…":bn?"Finish round ✓":"Next question →",Ys=Q||Re,Ks=()=>{un()},yr=O&&D==="incorrect",ks=yr&&!sn&&isExplainAltEnabled()&&($n||kn),Xn=ks?"another-way":!yr||Ft===L.id||qt?null:We?"lesson":isExplainAltEnabled()&&!sn?"another-way":null,ua=()=>{dn(L.id),Xn==="lesson"?yo():(ks||It("another_way"),Qn())},rn=Xn==="another-way"&&$n?HELP_COPY.thinking:Xn==="another-way"&&kn?HELP_COPY.tryAgain:HELP_COPY.yesShowMe,Ts=ks?$n?HELP_COPY.thinkingAnotherWay:HELP_COPY.anotherWayFailed:Xn==="lesson"?HELP_COPY.askAfterMiss:HELP_COPY.askAnotherWayAfterMiss,Bo=bn?"No thanks, finish ✓":HELP_COPY.noThanksNext,Dn=kt||sn?null:Xn?{question:Ts,yesLabel:rn,noLabel:Bo,onYes:ua,onNo:Ks,noDisabled:Ys,dockYesLabel:rn===HELP_COPY.yesShowMe?"Show me":rn,dockNoLabel:bn?"Finish ✓":"Next →"}:null;Xe.current=Xn&&_n&&!kt&&!sn&&!ks?()=>nn(Xn==="lesson"?"Want me to show you how?":"Want me to explain it another way?","hint"):null;const $i=Qe==="reveal-offer"&&isExplainAltEnabled()?()=>{It("another_way"),Qn()}:void 0,Eo=()=>{Ge==="example"&&Vt&&is(L.id)},Uo=()=>{Eo(),Et()},Qs=()=>{Eo(),ln()},Go=()=>{Eo(),gn()},vr=()=>{Be(!1),ls(),Ye(!0)},da={shownHintCount:q,hintDisabled:V,canWatch:xr,canRead:_n,isReading:G,onClue:Uo,onWatch:Go,onRead:Qs},gi=Vt&&jn?.questionId===L.id,bi=ft==="explanation"&&!!ge,Er=Vt&&!V&&q<3,_i=Vt?{kind:"answer",checkDisabled:Es,checkLabel:B?"Checking…":"Check my answer",onHelp:vr,onCheck:()=>{oe()},helpPulse:Me&&!P&&q===0}:Dn?{kind:"ask",question:Dn.question,yesLabel:Dn.dockYesLabel,onYes:Dn.onYes,nextLabel:Dn.dockNoLabel,onNext:Dn.onNo,nextDisabled:Ys}:{kind:"next",nextLabel:cs,onNext:Ks,nextDisabled:Ys};return jsxRuntimeExports.jsxs("div",{className:"sm-app",children:[jsxRuntimeExports.jsx("div",{className:"sm-grain","aria-hidden":"true"}),kt&&Xt&&vs&&jsxRuntimeExports.jsx(WorkedExample,{microSkillId:Xt,example:vs,onDismiss:$s,tieBack:ys,eyebrow:Le?Qe==="on-demand"?SAMPLE_EXAMPLE_EYEBROW:SAMPLE_DEMO_EYEBROW:ys?void 0:Qe==="on-demand"?"Here's one like yours":Qe==="trick"?"Here's a quick trick":void 0,onTryAnotherWay:$i,yourTurn:Qe==="on-demand"&&Vt,autoPlay:Le}),sn&&jsxRuntimeExports.jsx(ExplainAltOverlay,{steps:sn,representation:wn,onAnother:()=>{Qn(!0)},canAnother:Sn.length<MAX_EXPLAIN_RETAPS,loadingAnother:Yn,onDismiss:()=>{En(null),Zt(void 0),Rn([]),Gt(!1)}}),Ne&&Vt&&jsxRuntimeExports.jsx(HelpSheet,{...da,onClose:()=>Ye(!1)}),jsxRuntimeExports.jsxs("div",{className:"sm-screen sm-session-wrap",children:[jsxRuntimeExports.jsxs("header",{className:"sm-session-bar",children:[jsxRuntimeExports.jsx("button",{className:"sm-back-link",type:"button",onClick:we,children:s?"← Save & exit":"← Exit"}),jsxRuntimeExports.jsx("div",{className:"sm-progress","aria-label":`Question ${M+1} of ${en}`,children:Array.from({length:en},(ve,Ee)=>{const ze=Ee<M?"done":Ee===M?"current":"";return jsxRuntimeExports.jsx("span",{className:`sm-seg${ze?` ${ze}`:""}`,"aria-hidden":"true"},Ee)})}),jsxRuntimeExports.jsxs("span",{className:"sm-session-count",children:[M+1," / ",en]})]}),jsxRuntimeExports.jsxs("div",{className:"sm-session-body",children:[jsxRuntimeExports.jsxs("div",{className:"sm-q-card",children:[jsxRuntimeExports.jsxs("div",{className:"sm-q-main",children:[jsxRuntimeExports.jsx("span",{className:"sm-q-tag",children:Mn}),k&&M===0&&Vt&&jsxRuntimeExports.jsxs("p",{className:"sm-sample-reassure",children:["Question 1 of ",en," · free, no sign-up needed"]}),zs&&jsxRuntimeExports.jsx("div",{className:"sm-help-intro-inline",children:jsxRuntimeExports.jsx(HelpIntro,{onDismiss:ls})}),jsxRuntimeExports.jsx(QuestionErrorBoundary,{questionId:L.debugInfo?.questionId??L.stem,onSkip:ie,onTrip:ye,children:jsxRuntimeExports.jsx(QuestionCard,{question:L,selectedAnswer:P,status:D,attemptsExhausted:U>=Ho,onChangeAnswer:se,onSpeakQuestion:Qs,canSpeak:_n,isSpeaking:G,hideDebugBar:!0,helpNote:bi?jsxRuntimeExports.jsx(ClueNote,{kind:"explanation",message:ge,thinking:Ie==="thinking",isSpeaking:X,onReplay:_
71n?()=>nn(toSpeechFriendlyText(ge),"hint"):void 0,replayLabel:readsExplanationsAloud(e)?void 0:"Hear it"}):gi&&jn?jsxRuntimeExports.jsx(ClueNote,{kind:"clue",message:jn.message,thinking:jn.thinking,isSpeaking:X,onReplay:_n?()=>{const ve=Rt.current;nn(ve?.questionId===L.id?ve.text:toSpeechFriendlyText(jn.message),"hint")}:void 0,onAnotherClue:Er?Uo:void 0,anotherLabel:clueLabel(q)}):null})},L.debugInfo?.questionId??L.stem)]}),jsxRuntimeExports.jsx(SessionSidePanel,{answering:Vt,chatMessage:ft==="chat"?ge:"",chatMood:Ie,isSpeaking:X,cue:mt,lookAt:MIA_LOOK_AT_QUESTION,onMiaTap:vr,showIntro:zs,onDismissIntro:ls,choices:da,ask:Dn,check:{label:B?"Checking…":"Check my answer",disabled:Es,onCheck:()=>{oe()}},next:{label:cs,disabled:Ys,onNext:Ks},onComeBack:ne&&!Re?()=>{wr()}:null,autoNextIn:O&&!Q?as:null})]}),k&&E&&jsxRuntimeExports.jsx(SampleSignUpBar,{variant:"inline",onSignUp:E}),L.debugInfo?.questionId&&!(k&&!isAdminMode())&&jsxRuntimeExports.jsx("p",{className:"sm-qid",children:L.debugInfo.questionId})]}),jsxRuntimeExports.jsx(SessionDock,{mode:_i})]})]})}const OUTCOME_LABEL={first:{text:"Right first try ✓",cls:"correct"},retry:{text:"Right on the retry ✓",cls:"correct"},miss:{text:"One to look at again",cls:"wrong"},skipped:{text:"Not answered",cls:"skipped"}};function QuestionRecapModal({recap:n,questionNumber:t,onClose:e}){reactExports.useEffect(()=>{const a=i=>{i.key==="Escape"&&e()};return window.addEventListener("keydown",a),()=>window.removeEventListener("keydown",a)},[e]);const s=OUTCOME_LABEL[n.outcome],o=n.visualData&&!(n.visualData.type==="sequence"&&stemEmbedsSequence(n.stem,n.visualData)),r=n.outcome==="miss"&&!!n.yourAnswer&&n.yourAnswer!==n.correctAnswer;return jsxRuntimeExports.jsx("div",{className:"sm-recap-backdrop",onClick:e,role:"presentation",children:jsxRuntimeExports.jsxs("div",{className:"sm-recap-card",role:"dialog","aria-modal":"true","aria-label":`Question ${t} — look back`,onClick:a=>a.stopPropagation(),children:[jsxRuntimeExports.jsxs("header",{className:"sm-recap-head",children:[jsxRuntimeExports.jsxs("span",{className:"sm-recap-num",children:["Question ",t]}),jsxRuntimeExports.jsx("span",{className:`sm-recap-outcome ${s.cls}`,children:s.text}),jsxRuntimeExports.jsx("button",{type:"button",className:"sm-recap-close",onClick:e,"aria-label":"Close",children:"✕"})]}),jsxRuntimeExports.jsx("p",{className:"sm-recap-stem",children:n.stem}),o&&n.visualData&&jsxRuntimeExports.jsx("div",{className:"sm-recap-visual",children:jsxRuntimeExports.jsx(VisualDataRenderer,{data:n.visualData,mode:"pupil",stem:n.stem})}),jsxRuntimeExports.jsxs("div",{className:"sm-recap-answers",children:[r&&jsxRuntimeExports.jsxs("p",{className:"sm-recap-answer yours",children:["Your answer ",jsxRuntimeExports.jsx("b",{children:n.yourAnswer})]}),jsxRuntimeExports.jsxs("p",{className:"sm-recap-answer right",children:["Right answer ",jsxRuntimeExports.jsx("b",{children:n.correctAnswer})]})]}),n.outcome==="miss"&&n.tip&&jsxRuntimeExports.jsxs("p",{className:"sm-recap-tip",children:["Remember: ",n.tip]})]})})}const topics="_topics_x57p5_1",topic="_topic_x57p5_1",styles$1={topics,topic};function SummaryTopics({isAuthenticated:n,strands:t,strandDelta:e}){if(!n)return jsxRuntimeExports.jsxs("section",{className:"sm-progress-strip","aria-label":"What you practised",children:[jsxRuntimeExports.jsx("h4",{children:"What you practised"}),jsxRuntimeExports.jsx("div",{className:styles$1.topics,children:t.map(o=>jsxRuntimeExports.jsx("span",{className:`sm-chip ${styles$1.topic}`,children:o},o))})]});const s=(e??[]).filter(o=>o.after>o.before);return s.length===0?null:jsxRuntimeExports.jsxs("div",{className:"sm-progress-strip",children:[jsxRuntimeExports.jsx("h4",{children:"What got stronger"}),s.map(o=>jsxRuntimeExports.jsxs("div",{className:"sm-progress-strip-row",children:[jsxRuntimeExports.jsxs("span",{className:`sm-chip ${styles$1.topic}`,children:["● ",o.strand]}),jsxRuntimeExports.jsxs("b",{children:[o.before,"% → ",o.after,"%"]}),jsxRuntimeExports.jsxs("span",{className:"sm-bonus",children:["+",o.after-o.before]})]},o.strand))]})}function plural(n,t,e){return`${n} ${n===1?t:e}`}function miaHelpedLine(n){const t=n?.clues??0,e=n?.readAloud??0,s=n?.examples??0,o=t+e+s;if(o===0)return"Thanks for practising with me. Whenever you're stuck, I can give a clue, read a question out loud or show you how.";const r=[t>0?plural(t,"clue","clues"):null,s>0?plural(s,"worked example","worked examples"):null,e>0?plural(e,"read-aloud","read-alouds"):null].filter(i=>i!==null),a=r.length>1?`${r.slice(0,-1).join(", ")} and ${r[r.length-1]}`:r[0];return`I helped ${plural(o,"time","times")} this round: ${a}. That's what I'm here for!`}const EXAMPLE_SKILL_BY_YEAR={"Year 1":"Number bonds to 10","Year 2":"Adding two-digit numbers","Year 3":"Column addition","Year 4":"Times tables to 12 × 12","Year 5":"Short division","Year 6":"Sharing in a ratio"};function SampleUpdatePreview({yearGroupLabel:n}){const t=EXAMPLE_SKILL_BY_YEAR[n]??"Column addition";return jsxRuntimeExports.jsxs("section",{className:"sm-update-preview","aria-label":"Example weekly update for parents",children:[jsxRuntimeExports.jsx("p",{className:"sm-update-preview-label",children:"Example · what you’d get each week"}),jsxRuntimeExports.jsxs("div",{className:"sm-update-preview-card",children:[jsxRuntimeExports.jsxs("div",{className:"sm-update-preview-head",children:[jsxRuntimeExports.jsx("span",{className:"sm-update-preview-avatar","aria-hidden":"true",children:"S"}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsxs("h3",{children:["This week with Sam ",jsxRuntimeExports.jsxs("span",{className:"sm-nowrap",children:["· ",n]})]}),jsxRuntimeExports.jsx("p",{children:"Weekly update for grown-ups"})]})]}),jsxRuntimeExports.jsx("p",{className:"sm-update-preview-line",children:"Sam had a steady week and is getting more confident. One skill moved up, and Mia will keep practising the tricky bits."}),jsxRuntimeExports.jsxs("p",{className:"sm-update-preview-move",children:[jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"▲ "}),t," → Knows it"]}),jsxRuntimeExports.jsxs("ul",{className:"sm-update-preview-pills","aria-label":"Example skills summary",children:[jsxRuntimeExports.jsx("li",{children:"Knows it · 2"}),jsxRuntimeExports.jsx("li",{children:"Building · 4"}),jsxRuntimeExports.jsx("li",{children:"Learning · 3"})]})]})]})}function headlineFor(n){return n>=80?"Strong round":n>=50?"Nice round":"Good effort"}function captionFor(n,t){if(n<50)return"Some of those were tough. Another short round tomorrow and they'll start to feel easier.";if(t===null)return n>=80?"Most of those went in first try — the patterns are settling in nicely.":"Some on the first go and a few that took two tries. That's how it sticks.";const e=t===1?"answer":"answers";return n>=80?`You got ${t} ${e} on the first try — the patterns are settling in nicely.`:`${t} ${e} on the first try and a few that took two goes. That's how it sticks.`}const TILE_BY_OUTCOME={first:{cls:"correct",sym:"✓"},retry:{cls:"correct",sym:"✓"},miss:{cls:"wrong",sym:"·"},skipped:{cls:"skipped",sym:"—"}};function tutorLineFor(n,t){return n>=80?`That was a really strong round on ${t}. You really got going — tomorrow I'll bring something a touch trickier.`:n>=50?`That was a good round on ${t}. A few took two goes — that's the part that makes them stick.`:`Tough one today. We'll come back to ${t} tomorrow with a gentler start.`}function SummaryScreen({profile:n,result:t,onPlayAgain:e,onBackHome:s,nextRecommendation:o,isAuthenticated:r,onOpenSignUp:a}){reactExports.useEffect(()=>{window.scrollTo({top:0,left:0,behavior:"auto"})},[]);const i=t.total>0?Math.round(t.correct/t.total*100):0,l=t.perQuestion,u=l?l.filter(M=>M==="first").length:null,d=Math.min(t.total,Math.round(t.correct)),m=Math.max(0,t.total-d),h=l?l.map(M=>TILE_BY_OUTCOME[M]):[...Array.from({length:d},()=>({cls:"correct",sym:"✓"})),...Array.from({length:m},()=>({cls:"wrong",sym:"·"}))],f=l?l.filter(M=>M==="first"||M==="retry").length:d,g=r?i:t.total>0?Math.round(f/t.total*100):0,b=l?.filter(M=>M==="retry").length??0,x=f===0?"Some questions were tricky. Thanks for giving them a try.":u===null?"Your sample roun
71d is complete.":`${u} on the first try${b>0?` · ${b} after another try`:""}.`,y=t.questionRecaps,E=!!y&&y.some(M=>M!==null),[k,T]=reactExports.useState(null),A=k!==null?y?.[k]??null:null,N=reactExports.useRef(null),S=reactExports.useRef(null);return reactExports.useEffect(()=>{const M=N.current;if(!M)return;const L=window.setTimeout(()=>{M.style.setProperty("--p",`${g}%`)},80);return()=>window.clearTimeout(L)},[g]),jsxRuntimeExports.jsxs("div",{className:"sm-app",children:[jsxRuntimeExports.jsx("div",{className:"sm-grain","aria-hidden":"true"}),A&&k!==null&&jsxRuntimeExports.jsx(QuestionRecapModal,{recap:A,questionNumber:k+1,onClose:()=>T(null)}),jsxRuntimeExports.jsxs("div",{className:"sm-screen",children:[jsxRuntimeExports.jsxs("header",{className:"sm-topbar",children:[jsxRuntimeExports.jsxs("div",{className:"sm-logo",children:[jsxRuntimeExports.jsx("span",{className:"sm-logo-smart",children:"Smart"}),jsxRuntimeExports.jsx("span",{className:"sm-logo-maths",children:"Maths"})]}),jsxRuntimeExports.jsx("div",{className:"sm-topbar-right",children:jsxRuntimeExports.jsxs("span",{className:"sm-chip",children:[n.firstName&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("strong",{children:n.firstName}),jsxRuntimeExports.jsx("span",{children:"·"})]}),jsxRuntimeExports.jsx("span",{children:n.yearGroupLabel})]})})]}),jsxRuntimeExports.jsx("div",{className:"sm-summary-wrap",children:jsxRuntimeExports.jsxs("div",{className:"sm-summary-card",children:[jsxRuntimeExports.jsxs("section",{className:"sm-summary-left",children:[jsxRuntimeExports.jsx("span",{className:"sm-summary-eyebrow",children:r?"Round complete":"Sample complete"}),jsxRuntimeExports.jsx("h1",{children:n.firstName?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[headlineFor(g),",",jsxRuntimeExports.jsx("br",{}),jsxRuntimeExports.jsxs("span",{className:"accent",children:[n.firstName,"."]})]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[headlineFor(g),"."]})}),jsxRuntimeExports.jsxs("div",{className:"sm-summary-score",children:[jsxRuntimeExports.jsxs("div",{className:"sm-big-num",children:[f,"/",t.total,jsxRuntimeExports.jsx("small",{children:"questions correct"})]}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("div",{ref:N,className:"sm-score-bar",style:{"--p":"0%"}}),jsxRuntimeExports.jsx("p",{className:"sm-summary-note",children:r?captionFor(g,u):x})]})]}),jsxRuntimeExports.jsxs("div",{className:"sm-summary-track","aria-label":"Per-question outcomes",children:[h.map((M,L)=>y?.[L]?jsxRuntimeExports.jsx("button",{type:"button",className:`sm-tile ${M.cls}`,onClick:()=>T(L),"aria-label":`Look back at question ${L+1}`,children:M.sym},L):jsxRuntimeExports.jsx("span",{className:`sm-tile ${M.cls}`,"aria-hidden":"true",children:M.sym},L)),h.length===0&&jsxRuntimeExports.jsx("span",{className:"sm-tile skipped","aria-hidden":"true",children:"—"})]}),E&&jsxRuntimeExports.jsx("p",{className:"sm-recap-hint",children:"Tap a question to see it again."}),r&&jsxRuntimeExports.jsx(SummaryTopics,{isAuthenticated:!0,strands:t.strands,strandDelta:t.strandDelta}),!r&&a&&jsxRuntimeExports.jsxs("div",{className:"sm-summary-signup",children:[jsxRuntimeExports.jsxs("p",{className:"sm-summary-signup-lead",children:["Keep going in ",n.yearGroupLabel,". Mia picks every question at your child’s level, and helps each time they get stuck."]}),jsxRuntimeExports.jsxs("button",{ref:S,className:"sm-btn sm-btn-primary sm-btn-large sm-summary-trial",type:"button",onClick:a,children:["Start your free trial ",jsxRuntimeExports.jsx("span",{className:"arrow",children:"→"})]}),jsxRuntimeExports.jsxs("p",{className:"sm-summary-offer",children:["Welcome offer — ",FOUNDING_OFFER.stripeCouponPercent,"% off"]}),jsxRuntimeExports.jsxs("p",{className:"sm-summary-terms",children:[SMARTMATHS_TRIAL_DAYS," days free, then ",formatGbp(FOUNDING_OFFER.pricePence.monthly)," a month or ",formatGbp(FOUNDING_OFFER.pricePence.annual)," a year (standard"," ",formatGbp(SMARTMATHS_PLANS.monthly.pricePence)," / ",formatGbp(SMARTMATHS_PLANS.annual.pricePence),"), for every child at home. Card needed · cancel anytime. Welcome price subject to availability, confirmed at checkout."]})]}),r&&jsxRuntimeExports.jsxs("div",{className:"sm-summary-actions",children:[jsxRuntimeExports.jsx("button",{className:"sm-btn sm-btn-accent",type:"button",onClick:e,children:"Another round →"}),jsxRuntimeExports.jsx("button",{className:"sm-btn sm-btn-soft",type:"button",onClick:s,children:"Done"})]})]}),jsxRuntimeExports.jsxs("aside",{className:"sm-summary-right",children:[jsxRuntimeExports.jsxs("div",{className:"sm-summary-tutor",children:[jsxRuntimeExports.jsx(Mia,{size:88,mood:"happy"}),jsxRuntimeExports.jsx("div",{className:"sm-summary-tutor-msg",children:r?tutorLineFor(g,t.sessionLabel):miaHelpedLine(t.miaHelps)})]}),!r&&jsxRuntimeExports.jsx(SampleUpdatePreview,{yearGroupLabel:n.yearGroupLabel}),!r&&jsxRuntimeExports.jsxs("div",{className:"sm-summary-links",children:[jsxRuntimeExports.jsx("button",{className:"sm-link",type:"button",onClick:e,children:"Play the sample again"}),jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"·"}),jsxRuntimeExports.jsx("button",{className:"sm-link",type:"button",onClick:s,children:"Back to home"})]}),r&&jsxRuntimeExports.jsxs("div",{className:"sm-up",children:[jsxRuntimeExports.jsxs("div",{className:"sm-up-head",children:[jsxRuntimeExports.jsx("h3",{children:"Next time we'll try"}),jsxRuntimeExports.jsx("span",{className:"sm-eyebrow",children:"Planned"})]}),jsxRuntimeExports.jsxs("div",{className:"sm-up-row",children:[jsxRuntimeExports.jsx("span",{className:"sm-up-glyph",style:{background:"var(--sm-accent-soft)",color:"var(--sm-accent)"},"aria-hidden":"true",children:"N"}),jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("b",{children:t.sessionLabel}),jsxRuntimeExports.jsx("div",{className:"sub",children:o})]}),jsxRuntimeExports.jsx("span",{className:"sm-up-pill",children:"Up next"})]})]})]})]})}),!r&&a&&jsxRuntimeExports.jsx(SampleSignUpBar,{onSignUp:a,hideWhileVisible:S})]})]})}const AVATAR_HUES=[210,162,28,340,270,50,110,195];function hueForName(n){if(!n)return AVATAR_HUES[0];const t=n.charCodeAt(0);return AVATAR_HUES[t%AVATAR_HUES.length]}function ChildSwitcher({onAdd:n}){const{children:t,selectedChild:e,selectChild:s}=useAuth();return t.length===0?null:jsxRuntimeExports.jsxs("div",{className:"child-switcher",role:"tablist","aria-label":"Switch active child",children:[t.map(o=>{const r=e?.id===o.id,a=o.firstName.charAt(0).toUpperCase(),i=hueForName(o.firstName);return jsxRuntimeExports.jsxs("button",{type:"button",role:"tab","aria-selected":r,className:`child-tab ${r?"is-active":""}`,onClick:()=>s(o.id),children:[jsxRuntimeExports.jsx("span",{className:"child-avatar",style:{background:`hsl(${i} 65% 92%)`,color:`hsl(${i} 45% 30%)`},"aria-hidden":"true",children:a}),jsxRuntimeExports.jsxs("span",{className:"child-tab-text",children:[jsxRuntimeExports.jsx("span",{className:"child-tab-name",children:o.firstName}),jsxRuntimeExports.jsx("span",{className:"child-tab-year",children:o.yearGroupLabel})]})]},o.id)}),n&&jsxRuntimeExports.jsxs("button",{type:"button",className:"child-tab child-tab-add",onClick:n,"aria-label":"Add another child",children:[jsxRuntimeExports.jsx("span",{className:"child-avatar child-avatar-add","aria-hidden":"true",children:"+"}),jsxRuntimeExports.jsx("span",{className:"child-tab-text",children:jsxRuntimeExports.jsx("span",{className:"child-tab-name",children:"Add child"})})]})]})}function formatUpdatedDate(n){return new Date(`${n}T12:00:00Z`).toLocaleDateString("en-GB",{weekday:"long",day:"numeric",month:"short"})}function formatNextSunday(n){const t=new Date(`${n}T12:00:00Z`),e=(7-t.getUTCDay())%7||7;return t.setUTCDate(t.getUTCDate()+e),t.toLocaleDateString("en-GB",{weekday:"long",day:"numeric",month:"short"})}function HeroNarrative({childName:n,yearGroupLabel:t,snapshot:e,isLoading:s,isAuthenticated:o,hasError:r=!1,onRetry:a}){if(r&&!s&&!e)return jsxRuntimeExports.jsxs("div",{className:"card hero-narrative",children:[jsxRuntimeExports.jsxs("p",{className:"hero-eyebrow",children:[n," · ",t," · Weekly update"]}),jsxRuntimeExports.jsxs("h1",{className:"hero-summary",children:["We couldn’t load ",n,"’s update just now — check your connection and try again."]}),jsxRuntimeExports.jsx("button",{type:"button",className:"btn btn-soft hero-retry",onClick:a,children:"Try again"})]});let i,l="Weekly update";return e?.settling.active?(l=e.settling.sessionsCompleted===0?"Getting started":"Settling in",i=e.settling.sessionsCompleted===0?`This page will build a calm picture of how ${n} is getting on.`:`SmartMaths is getting to know how ${n} learns.`):e?i=e.narrative.text:s?i="Putting the latest update together…":o?i=`Once ${n}
71 finishes a first round, this becomes a weekly update — what got stronger, what we're working on next, and one thing to try at home.`:i=`Sign in to get ${n}'s weekly update — what got stronger, what we're working on, and one thing to try at home.`,jsxRuntimeExports.jsxs("div",{className:"card hero-narrative",children:[jsxRuntimeExports.jsxs("p",{className:"hero-eyebrow",children:[n," · ",t," · ",l]}),jsxRuntimeExports.jsx("h1",{className:"hero-summary",children:i}),e&&!e.settling.active&&jsxRuntimeExports.jsxs("p",{className:"hero-cadence",children:[jsxRuntimeExports.jsxs("span",{children:["Updated ",formatUpdatedDate(e.periodEnd)]}),jsxRuntimeExports.jsx("span",{"aria-hidden":"true",className:"hero-cadence-dot",children:"·"}),jsxRuntimeExports.jsxs("span",{children:["Next update ",formatNextSunday(e.periodEnd)]})]})]})}function StatTile({label:n,value:t,tone:e="neutral"}){return jsxRuntimeExports.jsxs("div",{className:`stat-tile stat-tile-${e}`,children:[jsxRuntimeExports.jsx("span",{className:"stat-tile-value",children:t}),jsxRuntimeExports.jsx("span",{className:"stat-tile-label",children:n})]})}function WeekStats({rhythm:n}){return jsxRuntimeExports.jsxs("section",{className:"pd-week-facts","aria-labelledby":"pd-week-facts-title",children:[jsxRuntimeExports.jsxs("div",{className:"pd-section-heading",children:[jsxRuntimeExports.jsx("h2",{id:"pd-week-facts-title",children:"Update at a glance"}),jsxRuntimeExports.jsx("p",{children:"Simple activity facts — no scores or comparisons."})]}),jsxRuntimeExports.jsxs("div",{className:"stat-grid",children:[jsxRuntimeExports.jsx(StatTile,{label:"Active days",value:n?`${n.activeDays}`:"—",tone:n?"neutral":"muted"}),jsxRuntimeExports.jsx(StatTile,{label:"Questions answered",value:n?`${n.questionsAnswered}`:"—",tone:n?"neutral":"muted"})]}),!n&&jsxRuntimeExports.jsx("p",{className:"subtle stat-empty-note",children:"These fill in after the first practice."})]})}const GERUNDS={add:"adding",build:"building",calculate:"calculating",check:"checking",choose:"choosing",classify:"classifying",combine:"combining",compare:"comparing",compose:"composing",connect:"connecting",construct:"constructing",convert:"converting",count:"counting",derive:"deriving",describe:"describing",divide:"dividing",estimate:"estimating",find:"finding",identify:"identifying",interpret:"interpreting",know:"knowing",locate:"locating",match:"matching",measure:"measuring",multiply:"multiplying",name:"naming",order:"ordering",partition:"partitioning",read:"reading",recall:"recalling",recognise:"recognising",record:"recording",reflect:"reflecting",remove:"removing",represent:"representing",round:"rounding",scale:"scaling",share:"sharing",simplify:"simplifying",solve:"solving",sort:"sorting",subitise:"subitising",subtract:"subtracting",tell:"telling",translate:"translating",understand:"understanding",use:"using",weigh:"weighing",write:"writing"},ADVERB=/ly$/;function verbIndexOf(n){return n.length>1&&!GERUNDS[n[0].toLowerCase()]&&ADVERB.test(n[0].toLowerCase())?1:0}function skillPhrase(n){const t=n.trim().split(/\s+/);if(t.length===0||!t[0])return n;const e=verbIndexOf(t),s=t[e].toLowerCase(),o=GERUNDS[s];if(!o)return n.length>1&&/[A-Z]/.test(n[1])?n:n.length>0?n[0].toLowerCase()+n.slice(1):n;const r=[...t];return r[e]=o,e===1&&(r[0]=r[0].toLowerCase()),r.join(" ")}function skillPhraseSentence(n){const t=skillPhrase(n);return t.length>0?t[0].toUpperCase()+t.slice(1):t}new Set(Object.keys(GERUNDS));function publicLabel(n){switch(n){case"knows_it":return"Knows it";case"building":return"Building";case"refresh_due":return"Refresh due";default:return"Learning"}}const CHIP_ORDER=[{key:"knowsIt",state:"knows_it",label:"Knows it"},{key:"building",state:"building",label:"Building"},{key:"learning",state:"learning",label:"Learning"},{key:"refreshDue",state:"refresh_due",label:"Refresh due"},{key:"notTried",state:"not_tried",label:"Not tried"}];function skillStateLabel(n){return n==="not_tried"?"Not tried":publicLabel(n)}function evidenceCaption(n){const{attempts:t,distinctSessions:e}=n.evidence;if(t===0)return null;const s=`${t} question${t===1?"":"s"}`,o=`${e} session${e===1?"":"s"}`;return`based on ${s} across ${o}`}function TopicProgress({topics:n}){const[t,e]=reactExports.useState(null),[s,o]=reactExports.useState(!1),r=s?n:n.slice(0,3);return n.length===0?jsxRuntimeExports.jsxs("div",{className:"card topic-groups",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"All topics"}),jsxRuntimeExports.jsx("h2",{children:"Topic detail"}),jsxRuntimeExports.jsx("p",{className:"subtle",children:"Topic detail appears after the first few saved rounds."})]}):jsxRuntimeExports.jsxs("div",{className:"card topic-groups",children:[jsxRuntimeExports.jsxs("div",{className:"topic-groups-header",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"All topics"}),jsxRuntimeExports.jsx("h2",{children:"Topic detail"}),jsxRuntimeExports.jsx("p",{className:"subtle topic-legend",children:"Open a topic to see the evidence for each label. Labels change only after enough saved practice."})]}),jsxRuntimeExports.jsx("ul",{className:"topic-list",children:r.map(a=>{const i=t===a.strandId;return jsxRuntimeExports.jsxs("li",{className:"pd-topic",children:[jsxRuntimeExports.jsxs("button",{type:"button",className:"pd-topic-row","aria-expanded":i,onClick:()=>e(i?null:a.strandId),children:[jsxRuntimeExports.jsx("span",{className:"pd-topic-label",children:a.strandLabel}),jsxRuntimeExports.jsx("span",{className:"pd-topic-chips",children:CHIP_ORDER.map(({key:l,state:u,label:d})=>{const m=a.counts[l];return m===0?null:jsxRuntimeExports.jsxs("span",{className:`pd-chip pd-chip-${u}`,children:[d," ",m]},u)})}),jsxRuntimeExports.jsx("span",{className:"pd-topic-caret","aria-hidden":"true",children:i?"▴":"▾"})]}),i&&jsxRuntimeExports.jsx("ul",{className:"pd-skill-list",children:a.skills.map(l
71=>{const u=evidenceCaption(l);return jsxRuntimeExports.jsxs("li",{className:"pd-skill-row",children:[jsxRuntimeExports.jsx("span",{className:`pd-chip pd-chip-${l.publicState}`,children:skillStateLabel(l.publicState)}),jsxRuntimeExports.jsxs("span",{className:"pd-skill-label",children:[l.label,u&&jsxRuntimeExports.jsx("span",{className:"subtle pd-skill-evidence",children:u})]})]},l.skillId)})})]},a.strandId)})}),n.length>3&&jsxRuntimeExports.jsx("button",{type:"button",className:"btn btn-soft pd-topics-toggle",onClick:()=>o(a=>!a),children:s?"Show fewer topics":`Show all ${n.length} topics`})]})}function ThisWeekFocus({workingOn:n,childName:t}){return n.length===0?jsxRuntimeExports.jsxs("div",{className:"card week-focus",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"Working on"}),jsxRuntimeExports.jsx("h2",{children:"What comes next"}),jsxRuntimeExports.jsxs("p",{className:"subtle",children:["After the first few rounds, ",t,"'s focus will show here."]})]}):jsxRuntimeExports.jsxs("div",{className:"card week-focus",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"Working on"}),jsxRuntimeExports.jsx("h2",{children:"What comes next"}),jsxRuntimeExports.jsx("ul",{className:"week-focus-list",children:n.slice(0,2).map(e=>jsxRuntimeExports.jsx("li",{className:"week-focus-item",children:jsxRuntimeExports.jsx("span",{className:"week-focus-label",children:e.label})},e.skillId))}),jsxRuntimeExports.jsxs("p",{className:"subtle week-focus-cadence",children:[skillPhraseSentence(n[0].label)," is the focus next. SmartMaths chooses the questions in each round."]})]})}function MovementCard({snapshot:n,childName:t}){const{movement:e,trigger:s}=n,o=n.rhythm.questionsAnswered,r=n.workingOn[0]?.label;return jsxRuntimeExports.jsxs("div",{className:"card pd-movement",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"Since the last update"}),jsxRuntimeExports.jsx("h2",{children:"What changed"}),e.length>0?jsxRuntimeExports.jsx("ul",{className:"pd-movement-list",children:e.map(a=>jsxRuntimeExports.jsxs("li",{className:"pd-movement-row",children:[jsxRuntimeExports.jsx("span",{className:"pd-movement-label",children:a.label}),jsxRuntimeExports.jsx("span",{className:"pd-movement-arrow","aria-hidden":"true",children:"→"}),jsxRuntimeExports.jsx("span",{className:`pd-chip pd-chip-${a.to}`,children:publicLabel(a.to)})]},a.skillId))}):s==="day3_unlock"?jsxRuntimeExports.jsxs("p",{children:["This is ",t,"'s first baseline. From next Sunday, this section will show only genuine changes."]}):jsxRuntimeExports.jsxs("p",{children:["No topic label changed this week. SmartMaths recorded ",o," answered question",o===1?"":"s",". ",r?`${skillPhraseSentence(r)} is the focus next.`:"The next focus will appear here when there is enough evidence."]})]})}function RefreshDueCard({refreshDue:n}){return n.length===0?null:jsxRuntimeExports.jsxs("div",{className:"card pd-refresh-due",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"Automatic revisit"}),jsxRuntimeExports.jsx("h2",{children:"Refresh due"}),jsxRuntimeExports.jsxs("p",{className:"pd-refresh-skills",children:["Worth a quick revisit: ",n.map(t=>t.label).join(" · ")]}),jsxRuntimeExports.jsx("p",{className:"subtle",children:"Nothing for you to do — we'll weave these into upcoming rounds automatically."})]})}function nextBandLabel(n){if(n==="Reception")return"Year 1";const t=n.match(/^Year (\d)$/);if(!t)return null;const e=Number(t[1])+1;return e<=6?`Year ${e}`:null}function BandReadinessCard({snapshot:n,yearGroupLabel:t,childName:e}){const s=n.bandReadiness,o=nextBandLabel(t);return!s||!s.ready||!o?null:jsxRuntimeExports.jsxs("div",{className:"card pd-band-readiness",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"Looking ahead"}),jsxRuntimeExports.jsx("h2",{children:"Ready for more"}),jsxRuntimeExports.jsxs("p",{className:"pd-band-readiness-text",children:[e," is comfortable across most of their ",t," topics and has some really solid ones — a strong sign they're ready to start seeing ",o," work."]}),jsxRuntimeExports.jsxs("p",{className:"subtle",children:["Practice stays at ",t," for now. If you'd like to move them up, you can change their year group in account settings."]})]})}function TryAtHomeCard({tryAtHome:n,childName:t}){if(n.length===0)return null;const e=n[0];return jsxRuntimeExports.jsxs("div",{className:"card pd-try-home",children:[jsxRuntimeExports.jsx("p",{className:"pd-card-eyebrow",children:"Optional · no homework needed"}),jsxRuntimeExports.jsx("h2",{children:"One thing to try at home"}),jsxRuntimeExports.jsxs("p",{className:"pd-try-home-intro",children:["If it comes up naturally, ask ",t,":"]}),jsxRuntimeExports.jsxs("blockquote",{className:"pd-try-home-question",children:["“",e.stem,"”"]}),jsxRuntimeExports.jsx("p",{className:"subtle pd-try-home-skill",children:e.skillLabel})]})}function SettlingCard({snapshot:n,childName:t,onBackHome:e}){const s=Math.min(n.settling.sessionsCompleted,3),o=s>0;return jsxRuntimeExports.jsxs("div",{className:"card pd-settling",children:[jsxRuntimeExports.jsx("span",{className:"pd-chip pd-chip-settling",children:"Settling in"}),jsxRuntimeExports.jsx("h2",{children:o?`Building ${t}’s starting picture`:"Nothing has gone missing"}),jsxRuntimeExports.jsx("p",{className:"pd-settling-text",children:o?`SmartMaths has seen ${s} of ${t}’s first 3 rounds. After the third, this page will show what is changing and what to focus on next.`:`No practice has been recorded for ${t} yet. After SmartMaths sees ${t}’s first 3 rounds, this page will show a calm, evidence-based starting picture.`}),jsxRuntimeExports.jsxs("div",{className:"pd-settling-dots","aria-label":`${s} of ${t}'s first 3 rounds`,children:[[0,1,2].map(r=>jsxRuntimeExports.jsx("span",{className:`pd-settling-dot${r<s?" pd-settling-dot-d
71one":""}`},r)),jsxRuntimeExports.jsxs("span",{className:"subtle pd-settling-count",children:[s," of ",t,"'s first 3 rounds"]})]}),!o&&jsxRuntimeExports.jsxs("button",{type:"button",className:"btn btn-primary pd-settling-action",onClick:e,children:["Go to ",t,"'s practice"]})]})}function ParentFeedbackForm({childId:n,childName:t,onSubmit:e}){const s=!!n,[o,r]=reactExports.useState("general"),[a,i]=reactExports.useState(""),[l,u]=reactExports.useState(""),[d,m]=reactExports.useState(s),[h,f]=reactExports.useState(!1),[g,b]=reactExports.useState("idle"),[x,y]=reactExports.useState(null),E=async k=>{if(k.preventDefault(),!(!e||!a.trim())){f(!0),b("idle"),y(null);try{await e({childId:d&&n?n:null,category:o,message:a.trim(),contactEmail:l.trim()||null}),b("success"),i(""),u("")}catch(T){console.error("Feedback submission failed:",T),y(T instanceof Error?T.message:"Something went wrong. Please try again."),b("error")}finally{f(!1)}}};return jsxRuntimeExports.jsxs("div",{className:"card pd-section",children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"Send feedback"}),jsxRuntimeExports.jsxs("form",{onSubmit:k=>{E(k)},className:"pd-feedback-form",children:[jsxRuntimeExports.jsxs("div",{className:"pd-form-group",children:[jsxRuntimeExports.jsx("label",{htmlFor:"feedback-category",className:"subtle",children:"Category"}),jsxRuntimeExports.jsxs("select",{id:"feedback-category",value:o,onChange:k=>r(k.target.value),disabled:h,className:"pd-select",children:[jsxRuntimeExports.jsx("option",{value:"general",children:"General feedback"}),jsxRuntimeExports.jsx("option",{value:"bug",children:"Report a bug"}),jsxRuntimeExports.jsx("option",{value:"content_gap",children:"Missing topics/content"}),jsxRuntimeExports.jsx("option",{value:"feature_request",children:"Suggest a feature"}),jsxRuntimeExports.jsx("option",{value:"billing",children:"Billing query"})]})]}),jsxRuntimeExports.jsxs("div",{className:"pd-form-group",children:[jsxRuntimeExports.jsx("label",{htmlFor:"feedback-message",className:"subtle",children:"Message"}),jsxRuntimeExports.jsx("textarea",{id:"feedback-message",value:a,onChange:k=>i(k.target.value),disabled:h,placeholder:"Got something to share? We're all ears.",required:!0,className:"pd-textarea",rows:3})]}),jsxRuntimeExports.jsxs("div",{className:"pd-form-group",children:[jsxRuntimeExports.jsx("label",{htmlFor:"feedback-email",className:"subtle",children:"Contact email (optional)"}),jsxRuntimeExports.jsx("input",{id:"feedback-email",type:"email",value:l,onChange:k=>u(k.target.value),disabled:h,placeholder:"If you'd like a reply",className:"pd-input"})]}),s&&jsxRuntimeExports.jsxs("label",{className:"pd-feedback-checkbox",children:[jsxRuntimeExports.jsx("input",{type:"checkbox",checked:d,onChange:k=>m(k.target.checked),disabled:h}),jsxRuntimeExports.jsxs("span",{children:["Attach this feedback to ",t,"'s dashboard context"]})]}),jsxRuntimeExports.jsx("button",{type:"submit",disabled:h||!a.trim(),className:"btn btn-primary btn-full",children:h?"Sending...":"Submit feedback"}),g==="success"&&jsxRuntimeExports.jsx("p",{className:"pd-success-msg",children:"Thank you! Your feedback has been sent."}),g==="error"&&jsxRuntimeExports.jsx("p",{className:"pd-error-msg",children:x??"Something went wrong. Please try again."})]})]})}function DashboardFeedbackCard({childId:n,childName:t,onSubmit:e}){const[s,o]=reactExports.useState(!1);return e?jsxRuntimeExports.jsxs("section",{className:"card pd-dashboard-feedback",children:[jsxRuntimeExports.jsxs("button",{type:"button",className:"pd-dashboard-feedback-toggle","aria-expanded":s,onClick:()=>o(r=>!r),children:[jsxRuntimeExports.jsxs("span",{children:[jsxRuntimeExports.jsx("span",{className:"pd-card-eyebrow",children:"Help us improve SmartMaths"}),jsxRuntimeExports.jsx("span",{className:"pd-dashboard-feedback-title",children:"Share feedback"})]}),jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:s?"−":"+"})]}),s&&jsxRuntimeExports.jsx("div",{className:"pd-dashboard-feedback-form",children:jsxRuntimeExports.jsx(ParentFeedbackForm,{childId:n,childName:t,onSubmit:e})})]}):null}function formatDate(n){return n?new Date(n).toLocaleDateString("en-GB",{day:"numeric",month:"short"}):""}function humanizeFlagReason(n){switch(n){case"wrong_answer":return"Wrong answer";case"too_hard":return"Too hard";case"too_easy":return"Too easy";case"confusing":return"Confusing";default:return"Other"}}function ParentDashboard({chrome:n,profile:t,recentSessions:e,snapshot:s,questionFlags:o,isDashboardLoading:r,isSupabaseConfigured:a,isAuthenticated:i,snapshotError:l,onRetrySnapsh
71ot:u,onOpenLogin:d,onOpenSignUp:m,onBackHome:h,onOpenAccount:f,onSubmitFeedback:g}){const b=isAdminMode(),x=!i&&a&&!b,y=s?.settling.active??!1,E=!!(s&&(s.movement.length>0||s.rhythm.questionsAnswered>0||s.trigger==="day3_unlock")),k=i?jsxRuntimeExports.jsx(DashboardFeedbackCard,{childId:t.id,childName:t.firstName,onSubmit:g}):null,T=e.filter(A=>{const N=Date.now()-6048e5;return new Date(A.playedAtIso).getTime()>=N}).length;return jsxRuntimeExports.jsxs("section",{className:`${adultStyles.surface} screen screen-enter parent-screen`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"progress"}),jsxRuntimeExports.jsxs("div",{className:"parent-topbar",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft parent-back-btn",type:"button",onClick:h,"aria-label":"Back to home",children:"←"}),jsxRuntimeExports.jsx(ChildSwitcher,{onAdd:f}),jsxRuntimeExports.jsxs("button",{className:"btn btn-soft parent-gear-btn",type:"button",onClick:f,"aria-label":"Account settings",children:[jsxRuntimeExports.jsx("span",{"aria-hidden":"true",children:"⚙"}),jsxRuntimeExports.jsx("span",{className:"parent-gear-label",children:"Account"})]})]}),x&&jsxRuntimeExports.jsxs("div",{className:"card pd-auth-callout",children:[jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"Parent account"}),jsxRuntimeExports.jsxs("h3",{children:["Sign in to keep ",t.firstName||"your child","'s progress safe, see it from any device, and get a clear picture of what to practise next."]}),T>0&&jsxRuntimeExports.jsxs("p",{className:"subtle pd-local-note",children:[T," practice round",T===1?"":"s"," on this device in the last 7 days."]})]}),jsxRuntimeExports.jsxs("div",{className:"pd-auth-actions",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-primary",type:"button",onClick:d,children:"Parent sign in"}),jsxRuntimeExports.jsx("button",{className:"btn btn-soft",type:"button",onClick:m,children:"Create account"})]})]}),!a&&b&&jsxRuntimeExports.jsx("div",{className:"card pd-auth-callout",children:jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("p",{className:"subtle",children:"Local testing mode"}),jsxRuntimeExports.jsx("h3",{children:"Cloud auth is not configured — showing local progress only."})]})}),jsxRuntimeExports.jsx(HeroNarrative,{childName:t.firstName,yearGroupLabel:t.yearGroupLabel,snapshot:s,isLoading:r,isAuthenticated:i,hasError:l,onRetry:u}),y&&s?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(SettlingCard,{snapshot:s,childName:t.firstName,onBackHome:h}),jsxRuntimeExports.jsx(WeekStats,{rhythm:s.rhythm}),k]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[s&&jsxRuntimeExports.jsx(BandReadinessCard,{snapshot:s,yearGroupLabel:t.yearGroupLabel,childName:t.firstName}),jsxRuntimeExports.jsx(WeekStats,{rhythm:s?.rhythm??null}),jsxRuntimeExports.jsxs("div",{className:"pd-action-grid",children:[s&&E&&jsxRuntimeExports.jsx(MovementCard,{snapshot:s,childName:t.firstName}),jsxRuntimeExports.jsx(ThisWeekFocus,{workingOn:s?.workingOn??[],childName:t.firstName}),s&&jsxRuntimeExports.jsx(TryAtHomeCard,{tryAtHome:s.tryAtHome,childName:t.firstName}),s&&jsxRuntimeExports.jsx(RefreshDueCard,{refreshDue:s.refreshDue})]}),k,jsxRuntimeExports.jsx(TopicProgress,{topics:s?.topics??[]}),o.length>0&&jsxRuntimeExports.jsxs("div",{className:"card recent-sessions",children:[jsxRuntimeExports.jsxs("h3",{children:["Questions ",t.firstName," flagged"]}),jsxRuntimeExports.jsx("ul",{className:"recent-session-list",children:o.slice(0,3).map(A=>jsxRuntimeExports.jsxs("li",{className:"recent-session-row",children:[jsxRuntimeExports.jsx("span",{className:"recent-session-date",children:formatDate(A.createdAt)}),jsxRuntimeExports.jsx("span",{className:"recent-session-label",children:humanizeFlagReason(A.reason)}),A.questionRef&&jsxRuntimeExports.jsx("span",{className:"subtle",children:A.questionRef})]},A.id))}),jsxRuntimeExports.jsx("p",{className:"subtle pd-flag-note",children:"We review flagged questions every week and fix or replace the ones that aren't working."})]})]}),jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}function ChildRow({child:n,isActive:t,isArchived:e,onSelectActive:s,onRename:o,onUp
71dateYear:r,onArchive:a,onRestore:i,onRequireFreshParentAuth:l}){const[u,d]=reactExports.useState(!1),[m,h]=reactExports.useState({firstName:n.firstName,yearGroupLabel:n.yearGroupLabel}),[f,g]=reactExports.useState(!1),[b,x]=reactExports.useState(null),[y,E]=reactExports.useState(!1),k=()=>{h({firstName:n.firstName,yearGroupLabel:n.yearGroupLabel}),x(null),d(!0)},T=()=>{d(!1),x(null)},A=async()=>{g(!0),x(null);const M=m.firstName.trim();if(!M){x("Name can't be empty."),g(!1);return}try{const L=[];M!==n.firstName&&L.push(await o(n.id,M)),m.yearGroupLabel!==n.yearGroupLabel&&L.push(await r(n.id,m.yearGroupLabel));const P=L.find(F=>F.error)?.error;if(P){x(P);return}d(!1)}catch(L){console.error("ChildrenManager save failed:",L),x(L instanceof Error?L.message:"Something went wrong. Try again.")}finally{g(!1)}},N=async()=>{if(await l({kind:"archive",childName:n.firstName})){g(!0),x(null);try{const{error:L}=await a(n.id);L?x(L):E(!1)}catch(L){console.error("ChildrenManager archive failed:",L),x(L instanceof Error?L.message:"Something went wrong. Try again.")}finally{g(!1)}}},S=async()=>{g(!0);const{error:M}=await i(n.id);g(!1),M&&x(M)};return u?jsxRuntimeExports.jsxs("li",{className:"cm-row cm-row-editing",children:[jsxRuntimeExports.jsxs("div",{className:"cm-fields",children:[jsxRuntimeExports.jsxs("label",{className:"cm-field",children:[jsxRuntimeExports.jsx("span",{className:"cm-field-label",children:"Name"}),jsxRuntimeExports.jsx("input",{type:"text",className:"auth-input",value:m.firstName,onChange:M=>h({...m,firstName:M.target.value}),disabled:f,autoFocus:!0})]}),jsxRuntimeExports.jsxs("label",{className:"cm-field",children:[jsxRuntimeExports.jsx("span",{className:"cm-field-label",children:"Year group"}),jsxRuntimeExports.jsx("select",{className:"auth-input",value:m.yearGroupLabel,onChange:M=>h({...m,yearGroupLabel:M.target.value}),disabled:f,children:yearGroupOptionsFor(n.yearGroupLabel).map(M=>jsxRuntimeExports.jsx("option",{children:M},M))})]})]}),b&&jsxRuntimeExports.jsx("p",{className:"cm-error",children:b}),jsxRuntimeExports.jsxs("div",{className:"cm-actions",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:T,disabled:f,children:"Cancel"}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-compact",type:"button",onClick:()=>{A()},disabled:f,children:f?"Saving…":"Save"})]})]}):jsxRuntimeExports.jsxs("li",{className:"cm-row",children:[jsxRuntimeExports.jsxs("div",{className:"cm-row-main",children:[jsxRuntimeExports.jsx("span",{className:"cm-name",children:n.firstName}),jsxRuntimeExports.jsx("span",{className:"cm-year",children:n.yearGroupLabel}),t&&!e&&jsxRuntimeExports.jsx("span",{className:"cm-active-pill",children:"Active"})]}),jsxRuntimeExports.jsx("div",{className:"cm-actions",children:e?jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:()=>{S()},disabled:f,children:"Restore"}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[!t&&jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:s,children:"Switch to"}),jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:k,children:"Edit"}),y?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:()=>E(!1),disabled:f,children:"Cancel"}),jsxRuntimeExports.jsx("button",{className:"btn btn-danger btn-compact",type:"button",onClick:()=>{N()},disabled:f,children:f?"Archiving…":"Confirm archive"})]}):jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:()=>E(!0),children:"Archive"})]})}),b&&jsxRuntimeExports.jsx("p",{className:"cm-error",children:b})]})}function ChildrenManager({onRequireFreshParentAuth:n}){const{children:t,archivedChildren:e,selectedChild:s,selectChild:o,addChild:r,renameChild:a,updateChildYearGroup:i,archiveChild:l,restoreChild:u}=useAuth(),[d,m]=reactExports.useState(!1),[h,f]=reactExports.useState(""),[g,b]=reactExports.useState("Year 1"),[x,y]=reactExports.useState(null),[E,k]=reactExports.useState(!1),T=async A=>{A.preventDefault(),k(!0),y(null);const{error:N}=await r({firstName:h,yearGroupLabel:g});if(k(!1),N){y(N);return}f(""),b("Year 1"),m(!1)};return jsxRuntimeExports.jsxs("div",{className:"card account-section",children:[jsxRuntimeExports.jsx("h3",{children:"Children"}),jsxRuntimeExports.jsxs("ul",{className:"cm-list",children:[t.map(A=>jsxRuntimeExports.jsx(ChildRow,{child:A,isActive:s?.id===A.id,onSelectActive:()=>
71o(A.id),onRename:a,onUpdateYear:i,onArchive:l,onRestore:u,onRequireFreshParentAuth:n},A.id)),t.length===0&&jsxRuntimeExports.jsx("li",{className:"cm-empty",children:jsxRuntimeExports.jsx("span",{className:"subtle",children:"No active children — add one to start practising."})})]}),d?jsxRuntimeExports.jsxs("form",{className:"cm-add-form",onSubmit:A=>{T(A)},children:[jsxRuntimeExports.jsxs("label",{className:"cm-field",children:[jsxRuntimeExports.jsx("span",{className:"cm-field-label",children:"Child's first name"}),jsxRuntimeExports.jsx("input",{className:"auth-input",type:"text",value:h,onChange:A=>f(A.target.value),required:!0,autoFocus:!0})]}),jsxRuntimeExports.jsxs("label",{className:"cm-field",children:[jsxRuntimeExports.jsx("span",{className:"cm-field-label",children:"Year group"}),jsxRuntimeExports.jsx("select",{className:"auth-input",value:g,onChange:A=>b(A.target.value),children:YEAR_GROUP_OPTIONS.map(A=>jsxRuntimeExports.jsx("option",{children:A},A))})]}),x&&jsxRuntimeExports.jsx("p",{className:"cm-error",children:x}),jsxRuntimeExports.jsxs("div",{className:"cm-actions",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact",type:"button",onClick:()=>m(!1),children:"Cancel"}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-compact",type:"submit",disabled:E,children:E?"Adding…":"Add child"})]})]}):jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-compact cm-add-btn",type:"button",onClick:()=>m(!0),children:"+ Add another child"}),e.length>0&&jsxRuntimeExports.jsxs("details",{className:"cm-archived",children:[jsxRuntimeExports.jsxs("summary",{className:"cm-archived-summary",children:["Archived (",e.length,")"]}),jsxRuntimeExports.jsx("ul",{className:"cm-list cm-list-archived",children:e.map(A=>jsxRuntimeExports.jsx(ChildRow,{child:A,isActive:!1,isArchived:!0,onSelectActive:()=>{},onRename:a,onUpdateYear:i,onArchive:l,onRestore:u,onRequireFreshParentAuth:n},A.id))})]})]})}const SECTIONS=[{id:"profile",label:"Profile",description:"Your name, email, password"},{id:"children",label:"Children",description:"Add, rename, set year group"},{id:"subscription",label:"Plan & billing",description:"Manage your plan and payment details"},{id:"privacy",label:"Data & privacy",description:"Export or delete your data"},{id:"help",label:"Help & support",description:"Contact us, send feedback"}];function Account({chrome:n,isAuthenticated:t,isSupabaseConfigured:e,initialSection:s="profile",onBack:o,onOpenLogin:r,onSignOut:a,onSubmitFeedback:i,onOpenPricing:l,onOpenLegal:u,onOpenFaq:d,onRequireFreshParentAuth:m}){const{session:h,parentFirstName:f,selectedChild:g}=useAuth(),[b,x]=reactExports.useState(s),y=h?.user?.email??null,{status:E,entitled:k,loading:T,foundingMember:A,trialEnd:N,currentPeriodEnd:S,cancelAtPeriodEnd:M}=useSubscriptionStatus(),[L,P]=reactExports.useState(!1),[F,D]=reactExports.useState(null),[U,q]=reactExports.useState(null),[V,O]=reactExports.useState(!1);reactExports.useEffect(()=>{if(b!=="subscription"||!k||U)return;let G=!1;return fetchReferralInfo().then(X=>{!G&&X&&q(X)}),()=>{G=!0}},[b,k,U]);const z=U?`We use SmartMaths for maths practice and the kids actually ask to do it. Use my code ${U.code} and your first month is free: ${window.location.origin}/?ref=${U.code}`:"",K=async()=>{if(U)try{await navigator.clipboard.writeText(z),O(!0),setTimeout(()=>O(!1),2e3)}catch{}},Z=async()=>{if(!await m({kind:"billing"}))return;P(!0),D(null);const X=await createPortalSession();if(X.url){window.location.assign(X.url);return}D("Couldn't open the billing portal — please try again."),P(!1)},Q=G=>{x(G)};if(!t&&e)return jsxRuntimeExports.jsxs("section",{className:`${adultStyles.surface} screen screen-enter account-screen`,children:[jsxRuntimeExports.jsxs("div",{className:"account-header",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft account-back-btn",type:"button",onClick:o,children:"←"}),jsxRuntimeExports.jsx("h2",{children:"Account"})]}),jsxRuntimeExports.jsxs("div",{className:"card account-empty",children:[jsxRuntimeExports.jsx("p",{children:"Sign in to manage your account."}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary",type:"button",onClick:r,children:"Parent sign in"})]})]});const B=(G,X)=>jsxRuntimeExports.jsx("a",{href:`/legal#${G}`,onClick:ne=>{ne.preventDefault(),u(G)},children:X});return jsxRuntimeExports.jsxs("section",{className:`${adultStyles.surface} screen screen-enter account-screen`,children:[jsxRuntimeExports.jsx(SiteChrome,{chrome:n,active:"account"}),jsxRuntimeExports.jsxs("div",{className:"account-header",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft account-back-btn",type:"button",onClick:o,children:"←"}),jsxRuntimeExports.jsx("h2",{children:"Account"})]}),jsxRuntimeExports.jsxs("div",{className:"account-layout",children:[jsxRuntimeExports.jsxs("nav",{className:"account-nav","aria-label":"Account sections",children:[SECTIONS.map(G=>jsxRuntimeExports.jsxs("button",{type:"button",className:`account-nav-item ${b===G.id?"is-active":""}`,onClick:()=>{Q(G.id)},children:[jsxRuntimeExports.jsx("span",{className:"account-nav-label",children:G.label}),jsxRuntimeExports.jsx("span",{className:"account-nav-desc",children:G.description})]},G.id)),jsxRuntimeExports.jsx("button",{type:"button",className:"account-nav-item account-nav-signout",onClick:a,children:jsxRuntimeExports.jsx("span",{className:"account-nav-label",children:"Sign out"})})]}),jsxRuntimeExports.jsxs("div",{className:"account-content",children:[b==="profile"&&jsxRuntimeExports.jsxs("div",{className:"card account-section",children:[jsxRuntimeExports.jsx("h3",{children:"Profile"}),jsxRuntimeExports.jsxs("dl",{className:"account-fields",children:[jsxRuntimeExports.jsxs("div",{className:"account-field-row",children:[jsxRuntimeExports.jsx("dt",{children:"Name"}),jsxRuntimeExports.jsx("dd",{children:f??"—"})]}),jsxRuntimeExports.jsxs("div",{className:"account-field-row",children:[jsxRuntimeExports.jsx("dt",{children:"Email"}),jsxRuntimeExports.jsxs("dd",{children:[y??"—",jsxRuntimeExports.jsxs("span",{className:"subtle account-field-note",children:[" ","· to change it, email ",jsxRuntimeExports.jsx("a",{href:"mailto:[email protected]",children:"[email protected]"})]})]})]}),jsxRuntimeExports.jsxs("div",{className:"account-field-row",children:[jsxRuntimeExports.jsx("dt",{children:"Password"}),jsxRuntimeExports.jsx("dd",{className:"subtle",children:"Change via the password-reset link"})]})]})]}),b==="children"&&jsxRuntimeExports.jsx(ChildrenManager,{onRequireFreshParentAuth:m}),b==="subscription"&&jsxRuntimeExports.jsxs("div",{className:"card account-section",children:[jsxRuntimeExports.jsx("h3",{children:"Plan & billing"}),T?jsxRuntimeExports.jsx("p",{className:"subtle",children:"Loading your plan…"}):!E||E==="canceled"&&!k?jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("p",{children:"No active subscription."}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary",type:"button",onClick:l,children:"See plans"})]}):jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[A&&jsxRuntimeExports.jsx("p",{className:"plan-badge plan-badge-popular",children:"Founding family — 50% off forever"}),(()=>{const G=derivePlanCopy(E,N,S,M);return G.tone==="warning"?jsxRuntimeExports.jsx("p",{className:"acct-billing-warning",children:G.line}):jsxRuntimeExports.jsx("p",{children:G.line})})(),jsxRuntimeExports.jsx("button",{className:"btn btn-soft",type:"button",disabled:L,onClick:()=>{Z()},children:L?"Opening…":"Manage subscription"}),F&&jsxRuntimeExports.jsx("p",{className:"pd-error-msg",children:F})]}),jsxRuntimeExports.jsxs("p",{className:"subtle account-legal-links",children:[B("plans","Plan facts")," · ",B("cancellation","How to cancel or ask for a refund")]})]}),b==="subscription"&&U&&jsxRuntimeExports.jsxs("div",{className:"card account-section account-referral",children:[jsxRuntimeExports.jsx("h3",{children:"Give a month, get a month"}
71),jsxRuntimeExports.jsx("p",{children:"Share your code with a friend: they get their first month free, and once their first payment goes through, a month's credit comes off your next bill."}),jsxRuntimeExports.jsx("p",{className:"account-referral-code","aria-label":"Your referral code",children:U.code}),jsxRuntimeExports.jsxs("div",{className:"account-referral-actions",children:[jsxRuntimeExports.jsx("button",{className:"btn btn-soft",type:"button",onClick:()=>{K()},children:V?"Copied!":"Copy invite"}),jsxRuntimeExports.jsx("a",{className:"btn btn-primary",href:`https://wa.me/?text=${encodeURIComponent(z)}`,target:"_blank",rel:"noreferrer",children:"Share on WhatsApp"})]}),U.creditPence>0&&jsxRuntimeExports.jsxs("p",{children:[jsxRuntimeExports.jsxs("strong",{children:["£",(U.creditPence/100).toFixed(2)," credit banked"]})," — it comes off your next bill automatically."]}),jsxRuntimeExports.jsxs("p",{className:"subtle",children:[U.rewardsUsedYear," of ",U.rewardsCapYear," referral rewards used in the last year."]})]}),b==="privacy"&&jsxRuntimeExports.jsxs("div",{className:"card account-section",children:[jsxRuntimeExports.jsx("h3",{children:"Data & privacy"}),jsxRuntimeExports.jsxs("p",{children:["You can ask for a copy of ",g?.firstName??"your child","'s practice history, remove a child's records, or delete your whole account — email"," ",jsxRuntimeExports.jsx("a",{href:"mailto:[email protected]",children:"[email protected]"})," and we'll sort it within 7 days."]}),jsxRuntimeExports.jsxs("p",{className:"subtle account-legal-links",children:["What we hold and why: ",B("privacy","Privacy for parents")," · ",B("child","Privacy for children")]})]}),b==="help"&&jsxRuntimeExports.jsxs("div",{className:"account-section-stack",children:[jsxRuntimeExports.jsxs("div",{className:"card account-section",children:[jsxRuntimeExports.jsx("h3",{children:"Help & support"}),jsxRuntimeExports.jsxs("p",{children:["For anything urgent, email ",jsxRuntimeExports.jsx("a",{href:"mailto:[email protected]",children:"[email protected]"}),"."]}),jsxRuntimeExports.jsxs("p",{className:"subtle account-legal-links",children:["Common questions are answered in the"," ",jsxRuntimeExports.jsx("a",{href:"/faq",onClick:G=>{G.preventDefault(),d()},children:"FAQ"}),", and the"," ",jsxRuntimeExports.jsx("a",{href:"/blog",children:"maths guides"})," cover the mix-ups children make most."]})]}),i&&jsxRuntimeExports.jsx(ParentFeedbackForm,{childId:g?.id??null,childName:g?.firstName??null,onSubmit:i})]})]})]}),jsxRuntimeExports.jsx(SiteFooter,{chrome:n})]})}const backdrop="_backdrop_pj7kn_1 _surface_2pd64_19",dialog="_dialog_pj7kn_14",lockIcon="_lockIcon_pj7kn_31",eyebrow="_eyebrow_pj7kn_42",description="_description_pj7kn_51",email="_email_pj7kn_58",form="_form_pj7kn_65",forgot="_forgot_pj7kn_113",styles={backdrop,dialog,lockIcon,eyebrow,description,email,form,forgot};function reasonCopy(n){return n.kind==="paywall"?"Ask a grown-up to help you continue. They can enter the parent account password here.":n.kind==="billing"?"Enter the parent account password again before opening billing.":n.kind==="archive"?`Enter the parent account password again before archiving ${n.childName}.`:"Enter the parent account password to open the grown-up area."}function ParentGateDialog({requestId:n,reason:t,accountEmail:e,onVerify:s,onForgotPassword:o,onCancel:r}){const a=reactExports.useRef(null),[i,l]=reactExports.useState(""),[u,d]=reactExports.useState(null),[m,h]=reactExports.useState(null),[f,g]=reactExports.useState(!1),[b,x]=reactExports.useState(!1);reactExports.useEffect(()=>{l(""),d(null),h(null),g(!1),x(!1)},[n]),reactExports.useEffect(()=>{const k=T=>{if(T.key==="Escape"&&!f&&!b){r();return}if(T.key!=="Tab")return;const A=Array.from(a.current?.querySelectorAll("button:not(:disabled), input:not(:disabled), [href], [tabindex]:not([tabindex='-1'])")??[]);if(A.length===0)return;const N=A[0],S=A[A.length-1];T.shiftKey&&document.activeElement===N?(T.preventDefault(),S.focus()):!T.shiftKey&&document.activeElement===S?(T.preventDefault(),N.focus()):a.current?.contains(document.activeElement)||(T.preventDefault(),N.focus())};return window.addEventListener("keydown",k),()=>window.removeEventListener("keydown",k)},[b,f,r]);const y=async k=>{k.preventDefault(),d(null),h(null),g(!0);const T=await s(i);T.ok||d(T.error),g(!1)},E=async()=>{d(null),h(null),x(!0);const k=await o();k.ok?h("Password-reset email sent. Check your inbox and spam folder."):d(k.error),x(!1)};return jsxRuntimeExports.jsx("div",{className:styles.backdrop,children:jsxRuntimeExports.jsxs("section",{ref:a,className:styles.dialog,role:"dialog","aria-modal":"true","aria-labelledby":"parent-gate-title","aria-describedby":"parent-gate-description",children:[jsxRuntimeExports.jsx("div",{className:styles.lockIcon,"aria-hidden":"true",children:"🔒"}),jsxRuntimeExports.jsx("p",{className:styles.eyebrow,children:"Parent check"}),jsxRuntimeExports.jsx("h1",{id:"parent-gate-title",children:"Grown-ups only"}),jsxRuntimeExports.jsx("p",{id:"parent-gate-description",className:styles.description,children:reasonCopy(t)}),e&&jsxRuntimeExports.jsx("p",{className:styles.email,children:e}),jsxRuntimeExports.jsxs("form",{className:styles.form,onSubmit:k=>{y(k)},children:[jsxRuntimeExports.jsx("label",{htmlFor:"parent-gate-password",children:"Password"}),jsxRuntimeExports.jsx("input",{id:"parent-gate-password",className:"auth-input",type:"password",autoComplete:"off",value:i,onChange:k=>l(k.target.value),disabled:f,autoFocus:!0,required:!0}),u&&jsxRuntimeExports.jsx("p",{className:"auth-error",role:"alert",children:u}),m&&jsxRuntimeExports.jsx("p",{className:"auth-info",role:"status",children:m}),jsxRuntimeExports.jsx("button",{className:"btn btn-primary btn-full",type:"submit",disabled:f||b,children:f?"Checking…":"Continue"})]}),jsxRuntimeExports.jsx("button",{className:styles.forgot,type:"button",onClick:()=>{E()},disabled:f||b,children:b?"Sending reset email…":"Forgot password?"}),jsxRuntimeExports.jsx("button",{className:"btn btn-soft btn-full",type:"button",onClick:r,disabled:f||b,children:"Back to children’s maths"})]})})}const PATH_BY_SCREEN={home:"/",pricing:"/pricing",faq:"/faq",about:"/about",login:"/sign-in",signup:"/sign-up"},SCREEN_BY_PATH={"/pricing":"pricing","/faq":"faq","/about":"about","/sign-in":"login","/sign-up":"signup","/try":"practice"};function screenFromPath(n){const t=n.length>1&&n.endsWith("/")?n.slice(0,-1):n;return SCREEN_BY_PATH[t]??null}function urlTargetForScreen(n,t){if(n==="legal"||n==="year-landing"||n==="authoring"||n==="coverage"||n==="qa-dashboard"||n==="question-preview"||n==="calibrate"||n==="wex-review"||n==="mia-lab"||n==="account"||n==="reset-password")return null;if(n==="practice"||n==="session"||n==="summary")return t?{path:"/try",mode:"push"}:{path:"/",mode:"replace"};const e=PATH_BY_SCREEN[n];return e?e==="/"?{path:e,mode:t?"push":"replace"}:{path:e,mode:"push"}:{path:"/",mode:"replace"}}function BankGate({children:n}){const[t,e]=React.useState("loading");return React.useEffect(()=>{if(t!=="loading")return;let s=!1,o=null;const r=new Promise((a,i)=>{o=setTimeout(()=>i(new Error("bank load timed out")),2e4)});return Promise.race([loadQuestionBankIndex(),r]).then(()=>{s||e("ready")},a=>{logClientError("error",a,{tag:"bank_load"}),s||e("failed")}).finally(()=>{o&&clearTimeout(o)}),()=>{s=!0,o&&clearTimeout(o)}},[t]),t==="ready"?jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:n}):t==="loading"?jsxRuntimeExports.jsxs("div",{className:"sm-app",children:[jsxRuntimeExports.jsx("div",{className:"sm-grain","aria-hidden":"true"}),jsxRuntimeExports.jsx("div",{className:"sm-screen sm-session-wrap",children:jsxRuntimeExports.jsx("div",{className:"sm-session-body",children:jsxRuntimeExports.jsx("div",{className:"sm-q-card",style:{minHeight:240,alignItems:"center",justifyItems:"center"},children:jsxRuntimeExports.jsx("p",{className:"sm-eyebrow",children:"Loading today's questions…"})})})})]}):jsxRuntimeExports.jsxs("div",{role:"alert",style:{minHeight:"100vh",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",textAlign:"center",padding:"2rem",background:"#FAF7F2",color:"#2A2622",fontFamily:'"Plus Jakarta Sans", "Helvetica Neue", system-ui, sans-serif'},children:[jsxRuntimeExports.jsx("div",{style:{fontSize:"3rem",marginBottom:"0.5rem",lineHeight:1},children:"📶"}),jsxRuntimeExports.jsx("h1",{style:{fontSize:"1.4rem",fontWeight:800,margin:"0 0 0.5rem"},children:"Having trouble loading"}),jsxRuntimeExports.jsx("p",{style:{color:"#5f5950",maxWidth:"22rem",lineHeight:1.5,marginBottom:"1.5rem"},children:"Check your internet connection, then try again."}),jsxRuntimeExports.jsx("button",{type:"button",onClick:()=>e("loading"),style:{background:"#C7634B",color:"#fff",border:"none",borderRadius:"999px",padding:"0.8rem 1.6rem",fontSize:"1rem",fontWeight:700,cursor:"pointer",fontFamily:"inherit"},children:"Try again"})]})}function isBrowser(){return typeof window<"u"}function detectEnvironment(){try{const n="production"}catch{}return"production"}function getMode(){return(isBrowser()?window.vam:detectEnvironment())||"production"}function isProduction(){return getMode()==="production"}function isDevelopment(){return getMode()==="development"}function removeKey(n,{[n]:t,...e}){return e}function parseProperties(n,t){if(!n)return;let e=n;const s=[];for(const[o,r]of Object.entries(n))typeof r=="object"&&r!==null&&(t.strip?e=removeKey(o,e):s.push(o));if(s.length>0&&!t.strip)throw Error(`The following properties are not valid: ${s.join(", ")}. Only strings, numbers, booleans, and null are allowed.`);return e}function track(n,t,e){var s,o;if(!isBrowser()){const r="[Vercel Web Analytics] Please import `track` from `@vercel/analytics/server` when using this function in a server environment";if(isProduction())console.warn(r);else throw new Error(r);return}if(!t){(s=window.va)==null||s.call(window,"event",{name:n,options:e});return}
71try{const r=parseProperties(t,{strip:isProduction()});(o=window.va)==null||o.call(window,"event",{name:n,data:r,options:e})}catch(r){r instanceof Error&&isDevelopment()&&console.error(r)}}function shouldTrackSample(n){return n?n.webdriver!==!0:!1}function currentNavigator(){return typeof navigator>"u"?null:navigator}function yearToken(n){const t=/(\d+)/.exec(n);return t?`y${t[1]}`:n.trim().toLowerCase().replace(/\s+/g,"_")||"unknown"}function trackSampleEvent(n,t,e=track,s=currentNavigator()){if(!shouldTrackSample(s))return!1;try{return e(n,t),!0}catch{return!1}}const AuthoringReviewScreen=reactExports.lazy(()=>__vitePreload(()=>import("./AuthoringReviewScreen-LXcL6Gua.js"),__vite__mapDeps([0,1,2])).then(n=>({default:n.AuthoringReviewScreen}))),CoverageDashboard=reactExports.lazy(()=>Promise.all([__vitePreload(()=>import("./CoverageDashboard-BL4576C7.js"),__vite__mapDeps([3,1,2])),loadQuestionBankIndex()]).then(([n])=>({default:n.CoverageDashboard}))),QaDashboard=reactExports.lazy(()=>Promise.all([__vitePreload(()=>import("./QaDashboard-UC3iQJlS.js"),__vite__mapDeps([4,1,5,2,6])),loadQuestionBankIndex()]).then(([n])=>({default:n.QaDashboard}))),CalibrationPage=reactExports.lazy(()=>Promise.all([__vitePreload(()=>import("./CalibrationPage-Ds5-qKgG.js"),__vite__mapDeps([7,1,2,8])),loadQuestionBankIndex()]).then(([n])=>({default:n.CalibrationPage}))),QuestionPreview=reactExports.lazy(()=>Promise.all([__vitePreload(()=>import("./QuestionPreview-DNfxQ1g7.js"),__vite__mapDeps([9,5,1,2,10])),loadQuestionBankIndex()]).then(([n])=>({default:n.QuestionPreview}))),WexReviewScreen=reactExports.lazy(()=>Promise.all([__vitePreload(()=>import("./WexReviewScreen-CCGVrCOJ.js"),__vite__mapDeps([11,1,2,12])),loadQuestionBankIndex()]).then(([n])=>({default:n.WexReviewScreen}))),MiaLabScreen=reactExports.lazy(()=>__vitePreload(()=>import("./MiaLabScreen-soHhDFtK.js"),__vite__mapDeps([13,1,2,14])).then(n=>({default:n.MiaLabScreen})));function loadStrandProgressForChild(n){if(n===DEFAULT_CHILD_PROFILE.id||typeof window>"u")return getBlankStrandProgress();const t=parseStrandProgress(readStorageJson(strandProgressKey(n)));if(t)return t;const e=parseStrandProgress(readStorageJson(STORAGE_KEYS.strandProgress));return e?(window.localStorage.setItem(strandProgressKey(n),JSON.stringify(e)),window.localStorage.removeItem(STORAGE_KEYS.strandProgress),e):getBlankStrandProgress()}function loadSessionHistoryForChild(n){if(n===DEFAULT_CHILD_PROFILE.id)return[];if(typeof window>"u")return[];const t=o=>Array.isArray(o)?o.map(r=>{const a=parseSessionResult(r),i=r,l=typeof i.playedAtIso=="string"?i.playedAtIso:"";return!a||!l?null:{...a,playedAtIso:l}}).filter(r=>!!r).slice(0,5):[],e=t(readStorageJson(sessionHistoryKey(n)));if(e.length>0)return e;const s=t(readStorageJson(STORAGE_KEYS.sessionHistory));return s.length>0?(window.localStorage.setItem(sessionHistoryKey(n),JSON.stringify(s)),window.localStorage.removeItem(STORAGE_KEYS.sessionHistory),s):[]}const PUBLIC_LEGAL_VIEWS=["privacy","terms","child","plans","cancellation"];function isPublicLegalPath(){return typeof window>"u"?!1:window.location.pathname==="/legal"||window.location.pathname==="/legal/"}function legalViewFromLocation(){if(typeof window>"u")return"privacy";const n=window.location.hash.slice(1);return PUBLIC_LEGAL_VIEWS.includes(n)?n:"privacy"}function publicLegalUrl(n){return`/legal${window.location.search}#${n}`}const HASH_ROUTED_HASH=/^#(account|authoring|qa-dashboard|calibrate|coverage|question-preview|wex-review|mia-lab)/;function initialScreenFromHash(){if(typeof window<"u"){if(yearLandingFromPath(window.location.pathname)!==null)return"year-landing";const n=window.location.hash;if(n.startsWith("#reset-password"))return"reset-password";if(isPublicLegalPath())return"legal";const t=screenFromPath(window.location.pathname);if(t)return t;if(n==="#account")return"account";if(isAdminMode()){if(n==="#authoring")return"authoring";if(n==="#qa-dashboard")return"qa-dashboard";if(n==="#calibrate")return"calibrate";if(n==="#coverage")return"coverage";if(n.startsWith("#question-preview"))return"question-preview";if(n==="#wex-review")return"wex-review";if(n==="#mia-lab")return"mia-lab"}}return"home"}function App(){const{session:n,selectedChild:t,children:e,childrenError:s,parentFirstName:o,isLoading:r,isAdmin:a,signOut:i,selectChild:l,refresh:u}=useAuth(),d=supabase!==null,m=n?.user?.id??null,h=t??DEFAULT_CHILD_PROFILE,[f,g]=reactExports.useState(()=>{const _e=new URLSearchParams(window.location.search).get("year");return _e?_e==="Reception"?"Reception":`Year ${_e}`:null}),b=reactExports.useMemo(()=>!f||n?h:{...h,yearGroupLabel:f},[h,f,n]),{loadInitialData:x,getParentSnapshot:y,getQuestionFlags:E,loadSkillStates:k,loadSkillRecency:T,loadSkillAccuracy:A,hasAttemptedMicroSkill:N,startBackendSession:S,recordAttemptEvent:M,recordWexEvent:L,flagQuestion:P,submitParentFeedback:F,c
71ompleteBackendSession:D,syncSessionResult:U,syncStrandProgress:q,loadDifficultyCarry:V,bumpSessionsCompleted:O}=useCloudSync({userId:m}),{status:z,entitled:K,loading:Z,foundingMember:Q,foundingAvailable:B,trialEnd:G,currentPeriodEnd:X,cancelAtPeriodEnd:ne,refresh:se}=useSubscriptionStatus(),te=m&&t?t.id:null,oe=reactExports.useCallback(async _e=>m?M(_e):(trackSampleEvent("sample_answered",{year:yearToken(b.yearGroupLabel),position:_e.questionPosition??0,correct:_e.isCorrect,strand:_e.strand}),null),[M,m,b.yearGroupLabel]),le=reactExports.useCallback(async()=>te?V(te):loadLocalDifficultyCarry(b.id),[te,b.id,V]),[ae,ie]=reactExports.useState(()=>initialScreenFromHash()),we=reactExports.useRef(ae);we.current=ae;const{isUnlocked:ke,prompt:Te,accountEmail:Se,requestAccess:$e,verifyPassword:Re,sendPasswordReset:ye,cancelRequest:Ne,lock:Ye,unlock:Ge}=useParentGate(),Fe=!!(n&&isProtectedParentScreen(ae)&&!ke),Xe=reactExports.useCallback((_e,Me={kind:"parent-area"},Be={})=>{$e(Me,Be).then(Ft=>{Ft&&_e()})},[$e]),Rt=!!(m&&!t),ge=ae==="practice"||ae==="session"||ae==="summary",Ie=Rt&&ge?"home":ae;reactExports.useEffect(()=>{r||!Rt||!ge||ie("home")},[r,Rt,ge]),reactExports.useEffect(()=>{if(r)return;const _e=["authoring","coverage","qa-dashboard","calibrate","question-preview","wex-review","mia-lab"];!isAdminMode()&&_e.includes(ae)&&ie("home")},[r,a,ae]);const[ft,wt]=reactExports.useState("home"),[mt,et]=reactExports.useState("home"),[Ke,Pt]=reactExports.useState("profile"),ht=reactExports.useCallback(_e=>{const Me=we.current;Xe(()=>{et(Me),Pt(_e),ie("account")})},[Xe]),lt=reactExports.useCallback(()=>ht("profile"),[ht]),ct=reactExports.useCallback(()=>{Xe(()=>ie("parent"))},[Xe]),$t=reactExports.useCallback(()=>{Xe(()=>ie("home"))},[Xe]),ot=reactExports.useCallback(()=>{Xe(()=>ie("pricing"),{kind:"billing"})},[Xe]),nn=reactExports.useCallback(()=>{const _e=we.current;Xe(()=>{et(_e),Pt("subscription"),ie("account")},{kind:"billing"})},[Xe]),It=reactExports.useCallback(_e=>$e(_e,{fresh:!0}),[$e]),ln=reactExports.useCallback(_e=>{Ye(),l(_e),ie("practice")},[Ye,l]),Et=reactExports.useCallback(()=>{Ye(),i()},[Ye,i]),Qe=reactExports.useCallback(()=>{},[]),[Ut,Xt]=reactExports.useState("home"),[Ce,kt]=reactExports.useState(()=>isPublicLegalPath()?legalViewFromLocation():"privacy"),sn=reactExports.useCallback(_e=>{Xt(we.current==="legal"?"home":we.current),kt(_e),window.location.pathname==="/legal"?window.history.replaceState(null,"",publicLegalUrl(_e)):window.history.pushState(null,"",publicLegalUrl(_e)),ie("legal")},[]),En=()=>{window.history.replaceState(null,"",`/${window.location.search}`),ie(Ut)},[wn,Zt]=reactExports.useState(()=>typeof window<"u"&&window.location.pathname==="/faq"&&window.location.hash.length>1?decodeURIComponent(window.location.hash.slice(1)):null),$n=reactExports.useCallback(_e=>{Zt(typeof _e=="string"?_e:null),Qe(),ie("faq")},[Qe]),cn=reactExports.useCallback(()=>{Qe(),ie("about")},[Qe]),kn=reactExports.useCallback(()=>{Qe(),ie("home")},[Qe]),Tn=reactExports.useCallback(()=>{Qe(),ie("login")},[Qe]),zn=reactExports.useRef(null),Sn=reactExports.useRef(!1),Rn=reactExports.useCallback(()=>{Qe(),zn.current=we.current,Sn.current=we.current==="summary",ie("signup")},[Qe]),Yn=reactExports.useCallback(()=>{Qe(),new URLSearchParams(window.location.search).has("year")&&yn(!0),ie("practice")},[Qe]),Gt=reactExports.useCallback(()=>{Qe(),ot()},[Qe,ot]),rt=reactExports.useMemo(()=>n?ke?{mode:"parent",onOpenHome:kn,onOpenParentDashboard:ct,onOpenAccount:lt,onOpenFaq:$n,onOpenAbout:cn,onSignOut:Et,onOpenLegal:sn}:{mode:"locked",onOpenHome:kn,onOpenPricing:Gt,onOpenFaq:$n,onOpenAbout:cn,onUnlock:$t,onOpenLegal:sn}:{mode:"public",onOpenHome:kn,onTrySample:Yn,onOpenPricing:Gt,onOpenFaq:$n,onOpenAbout:cn,onOpenLogin:Tn,onOpenSignUp:Rn,onOpenLegal:sn},[n,ke,kn,Yn,Gt,$n,cn,Tn,Rn,sn,$t,ct,lt,Et]),At=_e=>{kt(_e),window.history.replaceState(null,"",publicLegalUrl(_e))},[un]=reactExports.useState(()=>yearLandingFromPath(window.location.pathname)),[On,as]=reactExports.useState(()=>readStorageString(STORAGE_KEYS.selectedStrand)),[xe,Ve]=reactExports.useState(()=>parseSessionResult(readStorageJson(STORAGE_KEYS.lastSessionResult))),[tt,Le]=reactExports.useState(()=>loadStrandProgressForChild(b.id)),[He,gt]=reactExports.useState(()=>loadSessionHistoryForChild(b.id)),at=reactExports.useRef(b.id);reactExports.useEffect(()=>{at.current!==b.id&&(at.current=b.id,Le(loadStrandProgressForChild(b.id)),gt(loadSessionHistoryForChild(b.id)))},[b.id]);const[be,We]=reactExports.useState(()=>{const _e=readStorageString(STORAGE_KEYS.selectedStrand)??"Number & Place Value";return{questionCount:questionCountForYearGroup(b.yearGroupLabel),strands:[_e],targetQuestionId:getForcedQuestionId()??void 0}});reactExports.useEffect(()=>{We(_e=>({..._e,questionCount:questionCountForYearGroup(b.yearGroupLabel)}))},[b.yearGroupLabel]);const[De,An]=reactExports.useState(()=>parseAuthoringReviewBatch(readStorageJson(STORAGE_KEYS.authoringReviewBatch))),[Jt,Ct]=reactExports.useState(null),[Bs,xn]=reactExports.useState(!1),[Kn,Tt]=reactExports.useState(0),[xs,qt]=reactExports.useState([]),[jt,on]=reactExports.useState(!1);reactExports.useEffect(()=>{m&&x().then(({strandProgress:_e,sessionHistory:Me})=>{_e&&Le(_e),Me&&gt(Me)})},[m]),reactExports.useEffect(()=>{if(!(typeof window>"u")){if(!On){window.localStorage.removeItem(STORAGE_KEYS.selectedStrand);return}window.localStorage.setItem(STORAGE_KEYS.selectedStrand,On)}},[On]),reactExports.useEffect(()=>{if(!(typeof window>"u")){if(!xe){window.localStorage.removeItem(STORAGE_KEYS.lastSessionResult);return}window.localStorage.setItem(STORAGE_KEYS.lastSessionResult,JSON.stringify(xe))}},[xe]),reactExports.useEffect(()=>{typeof window>"u"||at.current!==DEFAULT_CHILD_PROFILE.id&&window.localStorage.setItem(strandProgressKey(at.current),JSON.stringify(tt))},[tt]),reactExports.useEffect(()=>{typeof window>"u"||at.current!==DEFAULT_CHILD_PROFILE.id&&window.localStorage.setItem(sessionHistoryKey(at.current),JSON.stringify(He))},[He]),reactExports.useEffect(()=>{if(!(typeof window>"u")){if(!De){window.localStorage.removeItem(STORAGE_KEYS.authoringReviewBatch);return}window.localStorage.setItem(STORAGE_KEYS.authoringReviewBatch,JSON.stringify(De))}},[De]),reactExports.useEffect(()=>{if(typeof window>"u")return;const _e=()=>{const Me=window.location.hash;if(Me.startsWith("#reset-password")){ie("reset-password");return}if(Me==="#account"){et(we.current==="account"?"home":we.current),ie("account");return}if(isAdminMode()){if(Me==="#authoring"){ie("authoring");return}if(Me==="#qa-dashboard"){ie("qa-dashboard");return}if(Me==="#calibrate"){ie("calibrate");return}if(Me==="#coverage"){ie("coverage");return}if(Me.startsWith("#question-preview")){we.current!=="question-preview"&&wt(we.current),ie("question-preview");return}if(Me==="#mia-lab"){ie("mia-lab");return}}ie(Be=>Be==="authoring"||Be==="qa-dashboard"||Be==="question-preview"||Be==="reset-password"||Be==="account"||Be==="coverage"||Be==="mia-lab"?"home":Be)};return window.addEventListener("hashchange",_e),()=>window.removeEventListener("hashchange",_e)},[]),reactExports.useEffect(()=>{if(typeof window>"u")return;if(["authoring","qa-dashboard","calibrate","question-preview","account","coverage","wex-review","mia-lab"].includes(ae)){window.location.hash.startsWith(`#${ae}`)||(window.location.hash=ae);return}(window.location.hash==="#authoring"||window.location.hash==="#qa-dashboard"||window.location.hash==="#calibrate"||window.location.hash==="#question-preview"||window.location.hash==="#account"||window.location.hash==="#coverage")&&window.history.replaceState(null,"",`${window.location.pathname}${window.location.search}`)},[ae]),reactExports.useLayoutEffect(()=>{window.scrollTo({top:0,left:0,behavior:"auto"})},[ae]),reactExports.useEffect(()=>{if(typeof window>"u"||r)return;const _e=urlTargetForScreen(ae,!n);if(!_e||window.location.pathname===_e.path)return;const Me=HASH_ROUTED_HASH.test(window.location.hash)?window.location.hash:"",Be=`${_e.path}${window.location.search}${Me}`;_e.mode==="push"?window.history.pushState(null,"",Be):window.history.replaceState(null,"",Be)},[ae,n,r]);const[Us,xo]=reactExports.useState(()=>typeof window<"u"&&screenFromPath(window.location.pathname)==="practice"),[Gs,yn]=reactExports.useState(()=>typeof window<"u"&&screenFromPath(window.location.pathname)==="practice"&&new URLSearchParams(window.location.search).has("year"));reactExports.useEffect(()=>{if(r||!n||!Us)return;xo(!1),g(null);const _e=new URLSearchParams(window.location.search);_e.delete("year");const Me=_e.toString();window.history.replaceState(null,"",`/${Me?`?${Me}`:""}`),ie("home")},[r,n,Us]),reactExports.useEffect(()=>{if(typeof window>"u")return;const _e=()=>{if(window.location.hash.startsWith("#reset-password"))return;if(isPublicLegalPath()){kt(legalViewFromLocation()),ie("legal");return}if(HASH_ROUTED_HASH.test(window.location.hash))return;if(yearLandingFromPath(window.location.pathname)!==null){window.location.reload();return}const Me=screenFromPath(window.location.pathname)??"home";if(Me==="practice"&&Sn.current){Sn.current=!1,ie("summary");return}Sn.current=!1,ie(Me)};return window.addEventListener("popstate",_e),()=>window.removeEventListener("popstate",_e)},[]),reactExports.useEffect(()=>{if(r||!Fe||Te)return;const _e=ae;$e(_e==="pricing"?{kind:"billing"}:{kind:"parent-area"}).then(Be=>{Be||we.current!==_e||(window.location.hash==="#account"&&window.history.replaceState(null,"",`${window.location.pathname}${window.location.search}`),ie(t?"practice":"home"))})},[r,Te,Fe,$e,ae,t]),reactExports.useEffect(()=>{ae!=="parent"||!m||!ke||(Ct(null),qt([]),xn(!1),on(!0),Promise.all([y(b.id),E(b.
71id)]).then(([_e,Me])=>{Ct(_e),qt(Me),xn(_e===null)}).finally(()=>{on(!1)}))},[ae,m,b.id,Kn,ke]),reactExports.useEffect(()=>{n&&(ae==="login"||ae==="signup")&&ie("home")},[ae,n]);const[ps,wr]=reactExports.useState(()=>isUpdateReady());reactExports.useEffect(()=>subscribeToSwUpdate(()=>wr(isUpdateReady())),[]),reactExports.useEffect(()=>{ps&&ae!=="session"&&ae!=="summary"&&applyWaitingUpdate()},[ps,ae]);const[gn,yo]=reactExports.useState(!1);reactExports.useEffect(()=>{const _e=new URLSearchParams(window.location.search),Me=_e.get("checkout");if(Me!=="success"&&Me!=="cancel")return;const Be=_e.get("session_id");_e.delete("checkout"),_e.delete("session_id");const Ft=_e.toString();window.history.replaceState(null,"",`${window.location.pathname}${Ft?`?${Ft}`:""}${window.location.hash}`),ie("pricing"),Me==="success"&&(yo(!0),se(Be??void 0))},[]);const qs=_e=>{if(m&&!t){ie("home");return}if(n&&!Z&&!K){Xe(()=>ie("pricing"),{kind:"paywall"},{fresh:!0});return}const Me=_e.strands.filter(en=>STRANDS.some(bn=>bn.name===en)),Be=Me.length>0?Me:["Number & Place Value"],Ft=m?void 0:sampleSessionIdsFor(b.yearGroupLabel),dn=(Ft?sampleSessionStrandsFor(b.yearGroupLabel):void 0)??[];m||trackSampleEvent("sample_started",{year:yearToken(b.yearGroupLabel)}),We({..._e,questionCount:questionCountForYearGroup(b.yearGroupLabel),curatedQuestionIds:Ft,strands:dn.length>0?dn:Be}),as(Be[0]),ie("session")};reactExports.useEffect(()=>{!Gs||r||(yn(!1),!(n||ae!=="practice")&&qs({...be,targetQuestionId:void 0}))},[Gs,r,n,ae,b.yearGroupLabel]);const[is,Qn]=reactExports.useState(null),ys=_e=>{const Me=_e==="picker"?null:b.yearGroupLabel;Qn(Me),trackSampleEvent("sample_signup_click",{year:Me?yearToken(Me):"none",from:_e,..._e==="summary"&&xe?{correct:xe.correct,total:xe.total}:{}}),Rn()},vs=reactExports.useCallback(_e=>{const Me=/(\d+)/.exec(_e)?.[1];if(Me){const Be=new URLSearchParams(window.location.search);Be.set("year",Me),window.history.replaceState(null,"",`${window.location.pathname}?${Be.toString()}${window.location.hash}`)}g(_e),yn(!0)},[]),$s=_e=>{if(!m){trackSampleEvent("sample_completed",{year:yearToken(b.yearGroupLabel),correct:_e.correct,total:_e.total});const{strandDelta:Mn,..._n}=_e;Ve(_n),as(_n.strands[0]??"Number & Place Value"),ie("summary");return}const Me=_e.total>0?_e.correct/_e.total:0,Be=Math.max(3,Math.round(Me*10)),Ft=tt,dn={...Ft};for(const Mn of _e.strands){const _n=dn[Mn]??0;dn[Mn]=clampProgress(_n+Be)}const en=_e.strands.map(Mn=>({strand:Mn,before:Ft[Mn]??0,after:dn[Mn]})),bn={..._e,strandDelta:en},{questionRecaps:Ho,...Es}=bn;Ve(bn),as(bn.strands[0]??"Number & Place Value"),Le(dn),q(dn),gt(Mn=>[{...Es,playedAtIso:new Date().toISOString()},...Mn].slice(0,5)),U(Es),te?O(te,_e.endDifficultyStep):bumpLocalDifficultyCarry(b.id,_e.endDifficultyStep),ie("summary")},vo=()=>{if(m&&!t){ie("home");return}xe?.strands&&xe.strands.length>0&&(We(_e=>({..._e,strands:xe.strands})),as(xe.strands[0])),ie("session")},Fo=_e=>{if(m||trackSampleEvent("sample_exited",{year:yearToken(b.yearGroupLabel),answered:_e.answeredQuestions}),typeof window<"u"){const Me=readStorageJson(STORAGE_KEYS.abandonedSessions),Ft=[...Array.isArray(Me)?Me:[],_e].slice(-25);window.localStorage.setItem(STORAGE_KEYS.abandonedSessions,JSON.stringify(Ft))}ie("practice")},Vs=reactExports.useMemo(()=>STRANDS.map(_e=>{const Me=[];let Be=0;for(const Ft of He){Be+=Ft.wrongByTheme[_e.name]??0;const dn=Ft.wrongExamplesByTheme[_e.name]??[];for(const en of dn)if(Me.includes(en)||Me.push(en),Me.length>=5)break;if(Me.length>=5)break}return{theme:_e.name,progress:tt[_e.name]??_e.defaultProgress,wrongCount:Be,wrongExamples:Me}}),[He,tt]),ls=reactExports.useMemo(()=>{if(!xe)return"Pick topics and start a custom session.";const _e=xe.total>0?Math.round(xe.correct/xe.total*100):0;return buildRecommendation(xe.sessionLabel,_e)},[xe]),jn=ae==="authoring"?"app-main app-main-authoring":ge&&!Rt?`app-main ${publicFlowStyles.sample}${n?` ${publicFlowStyles.member}`:""}`:"app-main";return ae==="reset-password"?jsxRuntimeExports.jsx("div",{className:"app-shell",children:jsxRuntimeExports.jsx("main",{className:"app-main",children:jsxRuntimeExports.jsx(ResetPasswordScreen,{onDone:_e=>{typeof window<"u"&&window.history.replaceState(null,"",`${window.location.pathname}${window.location.search}`),_e?(Ge(),ie("parent")):ie("login")}})})}):ae==="legal"&&isPublicLegalPath()?jsxRuntimeExports.jsx("div",{className:"app-shell",children:jsxRuntimeExports.jsxs("main",{className:"app-main",children:[jsxRuntimeExports.jsx(LegalScreen,{chrome:rt,initialView:Ce,onViewChange:At,onBack:En}),Te&&jsxRuntimeExports.jsx(ParentGateDialog,{requestId:Te.id,reason:Te.reason,accountEmail:Se,onVerify:Re,onForgotPassword:ye,onCancel:Ne})]})}):r?jsxRuntimeExports.jsx("div",{className:"app-shell",children:jsxRuntimeExports.jsx("main",{className:jn,children:jsxRuntimeExports.jsx("section",{className:"screen screen-enter",children:jsxRuntimeExports.jsx("div",{className:"card loading-card",children:jsxRuntimeExports.jsx("h2",{children:"Loading..."})})})})}):jsxRuntimeExports.jsx("div",{className:"app-shell",children:jsxRuntimeExports.jsxs("main",{className:jn,children:[Ie==="home"&&jsxRuntimeExports.jsx(ProductHomeScreen,{chrome:rt,isAuthenticated:!!n,parentFirstName:o,childProfiles:e,isParentUnlocked:ke,onUnlockParent:$t,childrenError:s,onRetryChildren:()=>{u()},onSelectChild:ln,onAddChild:()=>ht("children"),onOpenParentDashboard:ct,onOpenAccount:lt,onOpenFeedback:()=>ht("help"),onOpenLogin:Tn,onOpenSignUp:Rn,onTrySample:Yn}),Ie==="year-landing"&&un!==null&&jsxRuntimeExports.jsx(YearLandingScreen,{year:un,chrome:rt,onTrySample:()=>{window.history.pushState(null,"",`/try?year=${un}`),g(`Year ${un}`),yn(!0),ie("practice")},onOpenPricing:Gt}),Ie==="practice"&&jsxRuntimeExports.jsx(HomeScreen,{profile:b,onStart
71Session:qs,onOpenGrownUps:$t,onSelectStrand:as,selectedStrand:On,strandProgress:tt,recentSessions:He,parentThemeSummaries:Vs,sessionConfig:be,onUpdateSessionConfig:We,onChangeYearGroup:g,onStartSampleYear:n?void 0:vs,onOpenSampleSignUp:n?void 0:()=>ys("picker"),onOpenQaDashboard:()=>ie("qa-dashboard"),onOpenCalibration:()=>ie("calibrate"),isAuthenticated:!!n,onOpenLogin:()=>ie("login"),onBackHome:()=>ie("home")}),Ie==="session"&&jsxRuntimeExports.jsx(BankGate,{children:jsxRuntimeExports.jsx(SessionScreen,{sessionConfig:be,pupilId:b.id,yearGroupLabel:b.yearGroupLabel,progressSavingRequired:!!(m&&t),onStartBackendSession:S,onCancelBeforeStart:()=>ie("practice"),onRecordAttempt:oe,onLoadSkillStates:k,onLoadSkillRecency:T,onLoadSkillAccuracy:A,onLoadDifficultyCarry:le,onHasAttemptedMicroSkill:N,onRecordWexEvent:L,onFlagQuestion:P,onCompleteBackendSession:D,onSessionComplete:$s,onAbortSession:Fo,onHelpUsed:m?void 0:_e=>trackSampleEvent("sample_help",{year:yearToken(b.yearGroupLabel),kind:_e}),onOpenSampleSignUp:m?void 0:()=>ys("question"),onSampleDemo:m?void 0:({position:_e,forced:Me,outcome:Be})=>trackSampleEvent("sample_demo",{year:yearToken(b.yearGroupLabel),position:_e,forced:Me,outcome:Be})})}),Ie==="summary"&&xe&&jsxRuntimeExports.jsx(SummaryScreen,{profile:b,result:xe,onPlayAgain:vo,onBackHome:()=>ie(n?"practice":"home"),nextRecommendation:ls,isAuthenticated:!!n,onOpenSignUp:()=>{m?Rn():ys("summary")}}),ae==="parent"&&!Fe&&jsxRuntimeExports.jsx(ParentDashboard,{chrome:rt,profile:b,recentSessions:He,snapshot:Jt,snapshotError:Bs,onRetrySnapshot:()=>Tt(_e=>_e+1),questionFlags:xs,isDashboardLoading:jt,isSupabaseConfigured:d,isAuthenticated:!!n,onOpenLogin:()=>ie("login"),onOpenSignUp:()=>ie("signup"),onSignOut:Et,onBackHome:()=>ie("home"),onOpenAccount:lt,onSubmitFeedback:F}),ae==="account"&&!Fe&&jsxRuntimeExports.jsx(Account,{chrome:rt,isAuthenticated:!!n,isSupabaseConfigured:d,initialSection:Ke,onBack:()=>{mt==="practice"&&Ye(),ie(mt)},onOpenLogin:()=>ie("login"),onSignOut:Et,onSubmitFeedback:F,onOpenPricing:ot,onOpenLegal:sn,onOpenFaq:$n,onRequireFreshParentAuth:It}),ae==="login"&&jsxRuntimeExports.jsx(LoginScreen,{onSwitchToSignUp:()=>ie("signup"),onBack:()=>ie("home"),onSignedIn:Ge}),ae==="signup"&&jsxRuntimeExports.jsx(SignUpScreen,{onSwitchToLogin:()=>ie("login"),onBack:()=>{zn.current?window.history.back():ie("home")},onSignedUp:Ge,initialYearGroup:is??void 0,onAccountCreated:is?()=>trackSampleEvent("sample_signup_done",{year:yearToken(is)}):void 0}),ae==="pricing"&&!Fe&&jsxRuntimeExports.jsx(PricingScreen,{chrome:rt,onBackHome:kn,onOpenFaq:$n,onOpenLegal:sn,onOpenSignUp:Rn,onOpenAccount:nn,isAuthenticated:!!n,entitled:K,loading:Z,status:z,trialEnd:G,currentPeriodEnd:X,cancelAtPeriodEnd:ne,foundingMember:Q,foundingAvailable:B,checkoutJustSucceeded:gn,onRefreshStatus:se,onRequireFreshParentAuth:It}),Fe&&!Te&&jsxRuntimeExports.jsx("section",{className:"screen screen-enter",role:"status",children:jsxRuntimeExports.jsx("div",{className:"card loading-card",children:jsxRuntimeExports.jsx("h2",{children:"Opening the grown-up area…"})})}),ae==="faq"&&jsxRuntimeExports.jsx(FaqScreen,{chrome:rt,initialAnchor:wn}),ae==="about"&&jsxRuntimeExports.jsx(AboutScreen,{chrome:rt}),ae==="legal"&&jsxRuntimeExports.jsx(LegalScreen,{chrome:rt,initialView:Ce,onViewChange:At,onBack:En}),jsxRuntimeExports.jsxs(reactExports.Suspense,{fallback:jsxRuntimeExports.jsx("section",{className:"screen screen-enter",children:jsxRuntimeExports.jsx("div",{className:"card loading-card",children:jsxRuntimeExports.jsx("h2",{children:"Loading..."})})}),children:[ae==="authoring"&&isAdminMode()&&jsxRuntimeExports.jsx(AuthoringReviewScreen,{storedBatch:De,onBatchChange:An,onBackHome:()=>ie("practice")}),ae==="coverage"&&isAdminMode()&&jsxRuntimeExports.jsx(CoverageDashboard,{onBackHome:()=>ie("qa-dashboard")}),ae==="qa-dashboard"&&isAdminMode()&&jsxRuntimeExports.jsx(QaDashboard,{onBackHome:()=>ie("practice"),onOpenWexReview:()=>ie("wex-review")}),ae==="calibrate"&&isAdminMode()&&jsxRuntimeExports.jsx(CalibrationPage,{onBackHome:()=>ie("practice")}),ae==="question-preview"&&isAdminMode()&&jsxRuntimeExports.jsx(QuestionPreview,{onBackHome:()=>ie(ft==="qa-dashboard"?"qa-dashboard":"practice")}),ae==="wex-review"&&isAdminMode()&&jsxRuntimeExports.jsx(WexReviewScreen,{onBackHome:()=>ie("practice")}),ae==="mia-lab"&&isAdminMode()&&jsxRuntimeExports.jsx(MiaLabScreen,{onBackHome:()=>ie("practice")})]}),Te&&jsxRuntimeExports.jsx(ParentGateDialog,{requestId:Te.id,reason:Te.reason,accountEmail:Se,onVerify:Re,onForgotPassword:ye,onCancel:Ne})]})})}function scrubUrl(n){try{const t=new URL(n);return`${t.origin}${t.pathname}`}catch{return n.split(/[?#]/)[0]??n}}function scrubAnalyticsEvent(n){return{...n,url:scrubUrl(n.url)}}function installApiAuthProvider(){registerApiAuthProvider(async()=>
71{if(supabase)try{const{data:n}=await supabase.auth.getSession(),t=n.session?.access_token;if(t)return{Authorization:`Bearer ${t}`}}catch{}return{"x-dev-pin":DEV_PIN}})}installApiAuthProvider();registerServiceWorker();loadQuestionBankIndex().catch(()=>{});typeof window<"u"&&(window.addEventListener("error",n=>logClientError("error",n.error??n.message)),window.addEventListener("unhandledrejection",n=>logClientError("unhandledrejection",n.reason)),window.addEventListener("smartmaths:tts-silent",()=>logClientError("tts_silent",new Error("no_audio_path"))));class ErrorBoundary extends React.Component{constructor(t){super(t),this.state={error:null}}static getDerivedStateFromError(t){return{error:t}}componentDidCatch(t){logClientError("react_render",t)}render(){if(this.state.error){const t=()=>{typeof window<"u"&&window.location.assign(window.location.pathname+window.location.search)};return jsxRuntimeExports.jsxs("div",{role:"alert",style:{minHeight:"100vh",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",textAlign:"center",padding:"2rem",background:"#FAF7F2",color:"#2A2622",fontFamily:'"Plus Jakarta Sans", "Helvetica Neue", system-ui, sans-serif'},children:[jsxRuntimeExports.jsx("div",{style:{fontSize:"3rem",marginBottom:"0.5rem",lineHeight:1},children:"🌱"}),jsxRuntimeExports.jsx("h1",{style:{fontSize:"1.4rem",fontWeight:800,margin:"0 0 0.5rem"},children:"Oops — something went wrong"}),jsxRuntimeExports.jsx("p",{style:{color:"#5f5950",maxWidth:"22rem",lineHeight:1.5,marginBottom:"1.5rem"},children:"That wasn't your fault. Let's go back and try again."}),jsxRuntimeExports.jsx("button",{type:"button",onClick:t,style:{background:"#C7634B",color:"#fff",border:"none",borderRadius:"999px",padding:"0.8rem 1.6rem",fontSize:"1rem",fontWeight:700,cursor:"pointer",fontFamily:"inherit"},children:"Back to home"})]})}return this.props.children}}ReactDOM.createRoot(document.getElementById("root")).render(jsxRuntimeExports.jsxs(React.StrictMode,{children:[jsxRuntimeExports.jsx(ErrorBoundary,{children:jsxRuntimeExports.jsx(AuthProvider,{children:jsxRuntimeExports.jsx(App,{})})}),jsxRuntimeExports.jsx(Analytics,{mode:"production",beforeSend:scrubAnalyticsEvent})]}));export{CORRECT_FIRST_ATTEMPT as C,EXPRESSION_POSES as E,Frame as F,GESTURES as G,HINT_INTRO as H,Mia as M,QuestionCard as Q,React as R,THINKING_MESSAGE as T,WRONG_FIRST as W,applyDraftReviewDecision as a,buildApprovedExportBundle as b,supabase as c,deriveBatchStatus as d,adaptToSessionQuestion as e,findEditedFields as f,getQuestionBankIndex as g,speak as h,listWorkedExampleMicroSkillForms as i,jsxRuntimeExports as j,buildApiAuthHeaders as k,listWorkedExampleMicroSkills as l,markBatchExported as m,getWorkedExampleForMicroSkill as n,getWorkedExampleForMicroSkillForm as o,QUESTION_SAVED as p,getSessionStartMessage as q,reactExports as r,summarizeReviewBatch as s,getSessionCompleteMessage as t,useAuth as u,validateReviewBatchShape as v,canUseSpokenAudio as w};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.