{"id":1671,"date":"2023-03-08T14:53:56","date_gmt":"2023-03-08T21:53:56","guid":{"rendered":"https:\/\/www.washk12.org\/cte\/?page_id=1671"},"modified":"2026-10-05T15:50:50","modified_gmt":"2026-10-05T21:50:50","slug":"teacher-resources","status":"publish","type":"page","link":"https:\/\/www.washk12.org\/cte\/teacher-resources\/","title":{"rendered":"Teacher Resources"},"content":{"rendered":"<h1>Teacher Resources<\/h1>\n<h3>Teacher Forms<\/h3>\n<ul>\n<li>\n<p><a href=\"https:\/\/washk12.instructure.com\/enroll\/8CK3FA\"><\/p>\n<p>Canvas Safety Course<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.washk12.org\/cte\/wp-content\/uploads\/sites\/11\/2023\/03\/cte_lab_safety_checklist.pdf\"><\/p>\n<p>CTE Lab Safety Forms<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/1nEPkzIqRb6Pg5dhi3OtUl1yRSlKOLKvs\/view?usp=sharing\">CTSO Advisor Form<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/help.washk12.org\/\">Equipment Repair Form<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.washk12.org\/cte\/wp-content\/uploads\/sites\/11\/2023\/03\/proposal_concurrent_enrollment_form.pdf\"><\/p>\n<p>Proposal for Concurrent Enrollment Course Form<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/1Mm2VNpDySDSVEU2jO4sIxazypBcdl7tZ\/view?usp=sharing\"><\/p>\n<p>Utah ACTE Membership Form 2024-25<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/10fz4GV7WdmE2pn7OJjgzCy9HY5uYIoiL\/view?usp=sharing\">WCSD Cosmetology\/Esthetician Form<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>Information<\/h3>\n<ul>\n<li>\n<p><a href=\"https:\/\/docs.google.com\/document\/d\/1hlmjvZg2FNg1INR45WTQix8TUMhNXTGVt9deANyNk5Q\/edit?usp=sharing\"><\/p>\n<p>Approved Fee Schedule 2024-25<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/docs.google.com\/document\/d\/1rK5gpVCfm79iRQmZ5KRKbM2XBbDNixnI\/edit?usp=sharing&#038;ouid=102649544415761951444&#038;rtpof=true&#038;sd=true\"><\/p>\n<p>CTE Guidebook<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.washk12.org\/cte\/wp-content\/uploads\/sites\/11\/2023\/03\/2016_cte_funding_in_wcsd.pdf\"><\/p>\n<p>Overview of CTE Funding in WCSD<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.washk12.org\/cte\/wp-content\/uploads\/sites\/11\/2023\/03\/StandardsTimeEffortReporting.pdf\"><\/p>\n<p>Time and Effort Reporting 2 CFR 200.430 (i)<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/sites.google.com\/washk12.org\/cteteachinglearningresources\/career-technical-education\">CTE: Teaching and Learning Resources<\/a><\/p>\n<\/li>\n<\/ul>\n<hr>\n<h3>Teacher Directory<\/h3>\n<ul>\n<li>\n<p><a href=\"https:\/\/docs.google.com\/presentation\/d\/1rhrXoIyU5JdY_IDxBHAWFtywntC2MF0x3HO7VUqXz14\/edit?usp=sharing\">CTE Team Leads 2025-26<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/docs.google.com\/spreadsheets\/d\/1fZgg5wpQPur1D_zTBwSWCNUDVqT1eK5Y\/edit?usp=sharing&#038;ouid=102649544415761951444&#038;rtpof=true&#038;sd=true\">CTE Teams 2025-26<\/a><\/p>\n<\/li>\n<\/ul>\n<h3>CTE AT-A-Glance<\/h3>\n<ul>\n<li>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/1iKEHX0XIK1UJtDvpO_6e9-tEClG4ICse\/view?usp=share_link\"><\/p>\n<p>CTE WCSD<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/1lQq0TjRo45oxMsC4H5QWgxKqtxhER3V8\/view?usp=share_link\"><\/p>\n<p>CTE SW Region<\/p>\n<p><\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"\/cte\/wp-content\/uploads\/sites\/11\/2025\/01\/Utah-CTE-At-A-Glance.pdf\"><\/p>\n<p>Utah CTE<\/p>\n<p><\/a><\/p>\n<\/li>\n<\/ul>\n<pre><code>&lt;!doctype html&gt;\n&lt;html lang=&quot;en&quot;&gt;\n&lt;head&gt;\n  &lt;meta charset=&quot;utf-8&quot;&gt;\n  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no&quot;&gt;\n  &lt;meta name=&quot;theme-color&quot; content=&quot;#12304a&quot;&gt;\n  &lt;meta name=&quot;description&quot; content=&quot;An ad-free woodshop measurement game for learning to read an inch ruler and solve practical cut-list problems.&quot;&gt;\n  &lt;title&gt;Ruler Lab \u2014 Woodshop Measurement Practice&lt;\/title&gt;\n  &lt;link rel=&quot;icon&quot; type=&quot;image\/svg+xml&quot; href=&quot;data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 64 64'%3E%3Crect x='5' y='13' width='54' height='38' rx='7' fill='%23f6c453'\/%3E%3Cpath d='M13 13v15m8-15v9m8-9v15m8-15v9m8-9v15m8-15v9' stroke='%2312304a' stroke-width='4'\/%3E%3C\/svg%3E&quot;&gt;\n  &lt;style&gt;\n    :root{--ink:#102a43;--muted:#62758a;--navy:#12304a;--blue:#1675a9;--sky:#e8f6fb;--paper:#fffdfa;--yellow:#f6c453;--green:#16866f;--red:#d44949;--line:#d6e1e8;--shadow:0 18px 50px rgba(18,48,74,.14)}\n    *{box-sizing:border-box}\n    html,body{min-height:100%;margin:0}\n    body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,&quot;Segoe UI&quot;,sans-serif;color:var(--ink);background:linear-gradient(180deg,#e9f7fb 0,#f7fbfc 52%,#edf4f7 100%)}\n    button,select,input{font:inherit}\n    button{cursor:pointer}\n    .app{min-height:100vh;display:flex;flex-direction:column}\n    header{height:72px;display:flex;align-items:center;justify-content:space-between;padding:0 clamp(18px,4vw,64px);background:var(--navy);color:white}\n    .brand{display:flex;align-items:center;gap:12px;font-weight:850;font-size:1.25rem;letter-spacing:.01em}\n    .brand-icon{width:42px;height:30px;border-radius:7px;background:var(--yellow);position:relative;box-shadow:inset 0 -3px rgba(0,0,0,.08)}\n    .brand-icon:after{content:&quot;&quot;;position:absolute;inset:0;background:repeating-linear-gradient(90deg,transparent 0 7px,var(--navy) 7px 9px,transparent 9px 14px);clip-path:polygon(0 0,100% 0,100% 58%,0 58%)}\n    .header-actions{display:flex;gap:8px}\n    .icon-btn,.settings-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.08);color:#fff;border-radius:10px;min-height:42px;padding:0 14px;font-weight:750}\n    .icon-btn:hover,.settings-btn:hover{background:rgba(255,255,255,.16)}\n    main{width:min(1500px,100%);margin:auto;padding:clamp(18px,3vw,40px) clamp(14px,4vw,58px) 36px;flex:1;display:flex;flex-direction:column;justify-content:center;gap:18px}\n    .session-picker{align-self:center;display:grid;grid-template-columns:repeat(3,1fr);gap:5px;background:#dce9ee;border:1px solid #c8d9e1;padding:5px;border-radius:15px}\n    .session-btn{border:0;background:transparent;color:#50687a;border-radius:10px;padding:11px 18px;font-weight:850;min-width:150px}\n    .session-btn[aria-selected=&quot;true&quot;]{background:white;color:var(--navy);box-shadow:0 3px 12px rgba(18,48,74,.12)}\n    .session-btn small{display:block;font-size:.7rem;font-weight:700;color:#718495;margin-top:2px}\n    .challenge-levels{align-self:center;display:flex;align-items:center;gap:8px;padding:6px;background:#fff;border:1px solid var(--line);border-radius:13px;box-shadow:0 4px 16px rgba(18,48,74,.07)}\n    .challenge-levels[hidden]{display:none}.challenge-label{font-size:.76rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:900;padding:0 7px}.level-btn{border:1px solid transparent;background:transparent;color:#536c7e;border-radius:9px;padding:8px 13px;font-weight:850}.level-btn[aria-selected=&quot;true&quot;]{background:var(--blue);color:white}.difficulty-note{font-size:.78rem;color:var(--muted);font-weight:700;padding:0 8px;max-width:280px}\n    .topline{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:18px}\n    .mode-pill{justify-self:start;display:inline-flex;align-items:center;gap:8px;background:#d9f3ed;color:#116653;padding:8px 12px;border-radius:999px;font-weight:800;font-size:.85rem;text-transform:uppercase;letter-spacing:.06em}\n    .mode-pill:before{content:&quot;&quot;;width:8px;height:8px;border-radius:50%;background:var(--green)}\n    .prompt{text-align:center;min-width:min(430px,60vw)}\n    .eyebrow{font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;font-size:.82rem;margin:0 0 4px}\n    #target{font-size:clamp(2.1rem,5vw,4.8rem);line-height:1;font-weight:900;letter-spacing:-.045em;margin:0;color:var(--navy);font-variant-numeric:tabular-nums}\n    #target small{font-size:.45em;letter-spacing:0;color:var(--muted);font-weight:800}\n    .best{justify-self:end;text-align:right;color:var(--muted);font-weight:700}\n    .best strong{display:block;color:var(--ink);font-size:1.15rem}\n    .game-card{background:var(--paper);border:1px solid rgba(18,48,74,.12);border-radius:22px;box-shadow:var(--shadow);overflow:hidden}\n    .ruler-legend{display:flex;align-items:center;justify-content:center;gap:18px;flex-wrap:wrap;margin:0 0 10px;color:#526b7d;font-size:.8rem;font-weight:800}\n    .legend-item{display:inline-flex;align-items:center;gap:6px}.legend-line{display:inline-block;width:24px;border-top:4px solid;border-radius:2px}.legend-quarter{border-color:#2463eb}.legend-eighth{border-color:#0f8a72}.legend-sixteenth{border-color:#d97706}\n    .ruler-shell{padding:34px 20px 18px;position:relative;overflow:hidden}\n    #ruler{width:100%;height:260px;display:block;touch-action:none;user-select:none}\n    .hint{text-align:center;color:var(--muted);font-weight:650;margin:0 0 18px;min-height:24px;padding:0 14px}\n    .learn-note{display:none;width:min(660px,calc(100% - 28px));margin:-4px auto 18px;padding:12px 16px;border-radius:12px;background:#e8f6fb;border:1px solid #b9ddea;color:#24526c;text-align:center;font-weight:750}\n    .learn-note.show{display:block}\n    .type-row{display:none;align-items:center;justify-content:center;gap:10px;margin:0 auto 22px;padding:0 16px}\n    .type-row.show{display:flex}\n    #answer{width:min(320px,70vw);height:52px;border:2px solid var(--line);border-radius:12px;padding:0 16px;font-size:1.2rem;font-weight:750;color:var(--ink);background:white}\n    #answer:focus{outline:3px solid rgba(22,117,169,.2);border-color:var(--blue)}\n    .primary{height:52px;border:0;border-radius:12px;background:var(--blue);color:white;padding:0 22px;font-weight:850;box-shadow:0 6px 16px rgba(22,117,169,.22)}\n    .primary:hover{background:#0e668f}\n    .stats{display:grid;grid-template-columns:repeat(4,1fr);background:#f7fafb;border-top:1px solid var(--line)}\n    .stat{padding:18px 22px;text-align:center;border-right:1px solid var(--line)}\n    .stat:last-child{border:0}\n    .stat-label{display:block;color:var(--muted);font-size:.75rem;text-transform:uppercase;letter-spacing:.1em;font-weight:850;margin-bottom:5px}\n    .stat-value{font-size:1.45rem;font-weight:900;font-variant-numeric:tabular-nums}\n    .timer-track{height:7px;background:#dfe8ec;border-radius:5px;overflow:hidden;margin:7px auto 0;max-width:120px}\n    #timerBar{height:100%;width:100%;background:var(--green);transition:width .08s linear,background .2s}\n    .strikes{display:flex;justify-content:center;gap:7px;margin-top:5px}\n    .strike{width:12px;height:12px;border:2px solid #c6d2d9;border-radius:50%}\n    .strike.on{border-color:var(--red);background:var(--red)}\n    .controls{display:flex;justify-content:center;gap:10px}\n    .start{min-width:190px;height:54px;border:0;border-radius:13px;background:var(--yellow);color:#352808;font-weight:900;font-size:1.05rem;box-shadow:0 7px 0 #dca62e}\n    .start:active{transform:translateY(4px);box-shadow:0 3px 0 #dca62e}\n    .secondary{height:54px;border:1px solid #bfcfd7;border-radius:13px;background:#fff;color:var(--navy);padding:0 18px;font-weight:800}\n    .footer-note{text-align:center;color:#718495;font-size:.84rem;margin-top:4px}\n    dialog{border:0;border-radius:20px;padding:0;width:min(620px,calc(100vw - 28px));color:var(--ink);box-shadow:0 28px 90px rgba(4,25,44,.32)}\n    dialog::backdrop{background:rgba(6,28,45,.62);backdrop-filter:blur(3px)}\n    .modal-head{display:flex;align-items:center;justify-content:space-between;padding:22px 24px 16px;border-bottom:1px solid var(--line)}\n    .modal-head h2{margin:0;font-size:1.4rem}\n    .close{border:0;background:#edf3f6;color:var(--ink);width:38px;height:38px;border-radius:10px;font-size:1.4rem}\n    .settings-grid{padding:22px 24px;display:grid;grid-template-columns:1fr 1fr;gap:18px}\n    label{display:grid;gap:7px;font-weight:800;font-size:.88rem;color:#334e68}\n    select,input[type=&quot;text&quot;]{height:46px;border:1px solid #bfcfd7;border-radius:10px;background:white;padding:0 12px;color:var(--ink)}\n    .span-2{grid-column:1\/-1}.teacher-tools{grid-column:1\/-1;background:#f1f7f9;border:1px solid var(--line);border-radius:13px;padding:14px;display:flex;align-items:center;justify-content:space-between;gap:14px}.teacher-tools p{margin:0;color:var(--muted);font-size:.84rem;line-height:1.35}.compact{height:42px;white-space:nowrap}\n    .modal-foot{display:flex;justify-content:flex-end;gap:10px;padding:16px 24px 24px}\n    .result-overlay{position:fixed;inset:0;z-index:20;display:none;place-items:center;background:rgba(7,28,45,.62);padding:16px}\n    .result-overlay.show{display:grid}\n    .result-card{background:#fff;border-radius:22px;padding:30px;width:min(520px,100%);text-align:center;box-shadow:var(--shadow)}\n    .result-card h2{font-size:2rem;margin:0 0 8px}.result-card p{color:var(--muted);font-size:1.05rem;margin:0 0 18px}.result-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.result-actions .primary{width:100%}.mastery-badge{display:inline-block;border-radius:999px;padding:8px 14px;background:#dff5ed;color:#126b57;font-weight:900;margin:4px 0 16px}.mastery-badge.not-yet{background:#fff1d2;color:#855d08}.result-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:13px;overflow:hidden;margin:0 0 20px}.result-item{background:#f8fbfc;padding:12px}.result-item span{display:block;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:850}.result-item strong{display:block;margin-top:3px;font-size:1.15rem}.result-name{font-weight:850!important;color:var(--ink)!important;margin-bottom:6px!important}\n    .toast{position:fixed;left:50%;top:90px;transform:translate(-50%,-20px);opacity:0;pointer-events:none;padding:12px 18px;border-radius:999px;color:#fff;font-weight:850;z-index:30;transition:.22s;background:var(--green);box-shadow:0 10px 30px rgba(0,0,0,.18)}\n    .toast.show{opacity:1;transform:translate(-50%,0)}.toast.bad{background:var(--red)}\n    @media(max-width:760px){header{height:62px}.settings-btn span{display:none}.session-picker{width:100%}.session-btn{min-width:0;padding:10px 5px;font-size:.83rem}.session-btn small{display:none}.challenge-levels{width:100%;justify-content:center;flex-wrap:wrap}.challenge-label{width:100%;text-align:center}.difficulty-note{width:100%;max-width:none;text-align:center}.topline{grid-template-columns:1fr auto}.prompt{grid-column:1\/-1;grid-row:1}.mode-pill{grid-row:2}.best{grid-row:2}.ruler-shell{padding:24px 6px 12px}#ruler{height:220px}.stats{grid-template-columns:1fr 1fr}.stat:nth-child(2){border-right:0}.stat:nth-child(-n+2){border-bottom:1px solid var(--line)}.settings-grid{grid-template-columns:1fr}.span-2,.teacher-tools{grid-column:auto}.teacher-tools{align-items:stretch;flex-direction:column}.type-row{flex-direction:column}.type-row .primary,#answer{width:100%}.result-actions{grid-template-columns:1fr}}\n    @media print{body{background:white}.app,.toast{display:none!important}.result-overlay{position:static;display:block!important;background:white;padding:0}.result-card{box-shadow:none;border:2px solid #12304a;width:100%;max-width:720px;margin:20px auto}.result-actions{display:none}.result-card:after{content:&quot;Ruler Lab \u2022 CTE Makes School Matter&quot;;display:block;margin-top:24px;color:#62758a;font-size:.82rem}}\n    @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=&quot;app&quot;&gt;\n    &lt;header&gt;\n      &lt;div class=&quot;brand&quot;&gt;&lt;span class=&quot;brand-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;\/span&gt;Ruler Lab &lt;span style=&quot;font-weight:600;opacity:.65&quot;&gt;| Woodshop&lt;\/span&gt;&lt;\/div&gt;\n      &lt;div class=&quot;header-actions&quot;&gt;\n        &lt;button class=&quot;icon-btn&quot; id=&quot;fullscreen&quot; aria-label=&quot;Enter full screen&quot; title=&quot;Full screen&quot;&gt;\u26f6&lt;\/button&gt;\n        &lt;button class=&quot;settings-btn&quot; id=&quot;openSettings&quot; aria-haspopup=&quot;dialog&quot;&gt;\u2699 &lt;span&gt;Settings&lt;\/span&gt;&lt;\/button&gt;\n      &lt;\/div&gt;\n    &lt;\/header&gt;\n    &lt;main&gt;\n      &lt;nav class=&quot;session-picker&quot; aria-label=&quot;Learning mode&quot;&gt;\n        &lt;button class=&quot;session-btn&quot; data-session=&quot;learn&quot; aria-selected=&quot;false&quot;&gt;Learn&lt;small&gt;Guided help&lt;\/small&gt;&lt;\/button&gt;\n        &lt;button class=&quot;session-btn&quot; data-session=&quot;practice&quot; aria-selected=&quot;true&quot;&gt;Practice&lt;small&gt;Build accuracy&lt;\/small&gt;&lt;\/button&gt;\n        &lt;button class=&quot;session-btn&quot; data-session=&quot;challenge&quot; aria-selected=&quot;false&quot;&gt;Shop Challenge&lt;small&gt;20 questions&lt;\/small&gt;&lt;\/button&gt;\n      &lt;\/nav&gt;\n      &lt;div class=&quot;challenge-levels&quot; id=&quot;challengeLevels&quot; hidden&gt;\n        &lt;span class=&quot;challenge-label&quot;&gt;Difficulty&lt;\/span&gt;\n        &lt;button class=&quot;level-btn&quot; data-level=&quot;easy&quot; aria-selected=&quot;false&quot;&gt;Easy&lt;\/button&gt;\n        &lt;button class=&quot;level-btn&quot; data-level=&quot;medium&quot; aria-selected=&quot;true&quot;&gt;Medium&lt;\/button&gt;\n        &lt;button class=&quot;level-btn&quot; data-level=&quot;hard&quot; aria-selected=&quot;false&quot;&gt;Hard&lt;\/button&gt;\n        &lt;span class=&quot;difficulty-note&quot; id=&quot;difficultyNote&quot;&gt;Sixteenths and one-step shop math&lt;\/span&gt;\n      &lt;\/div&gt;\n      &lt;div class=&quot;topline&quot;&gt;\n        &lt;div class=&quot;mode-pill&quot; id=&quot;modeLabel&quot;&gt;Find the mark&lt;\/div&gt;\n        &lt;div class=&quot;prompt&quot; aria-live=&quot;polite&quot;&gt;\n          &lt;p class=&quot;eyebrow&quot; id=&quot;promptLabel&quot;&gt;Ready to measure?&lt;\/p&gt;\n          &lt;p id=&quot;target&quot;&gt;Press Start&lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;best&quot;&gt;Session best&lt;strong id=&quot;best&quot;&gt;0&lt;\/strong&gt;&lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;div class=&quot;controls&quot;&gt;\n        &lt;button class=&quot;start&quot; id=&quot;start&quot;&gt;Start game&lt;\/button&gt;\n        &lt;button class=&quot;secondary&quot; id=&quot;newQuestion&quot; hidden&gt;Skip&lt;\/button&gt;\n      &lt;\/div&gt;\n      &lt;section class=&quot;game-card&quot; aria-label=&quot;Ruler game&quot;&gt;\n        &lt;div class=&quot;ruler-shell&quot;&gt;\n          &lt;div class=&quot;ruler-legend&quot; id=&quot;rulerLegend&quot; aria-label=&quot;Ruler mark color key&quot;&gt;&lt;\/div&gt;\n          &lt;canvas id=&quot;ruler&quot; tabindex=&quot;0&quot; role=&quot;slider&quot; aria-label=&quot;Interactive inch ruler. Use arrow keys and Enter, or move and click or tap, to choose a measurement.&quot; aria-valuemin=&quot;0&quot; aria-valuemax=&quot;64&quot; aria-valuenow=&quot;0&quot;&gt;&lt;\/canvas&gt;\n        &lt;\/div&gt;\n        &lt;p class=&quot;hint&quot; id=&quot;hint&quot;&gt;Choose your settings, then press Start.&lt;\/p&gt;\n        &lt;div class=&quot;learn-note&quot; id=&quot;learnNote&quot;&gt;&lt;\/div&gt;\n        &lt;div class=&quot;type-row&quot; id=&quot;typeRow&quot;&gt;\n          &lt;input id=&quot;answer&quot; type=&quot;text&quot; inputmode=&quot;text&quot; autocomplete=&quot;off&quot; placeholder='Example: 2 3\/8 or 2.375' aria-label=&quot;Measurement answer&quot;&gt;\n          &lt;button class=&quot;primary&quot; id=&quot;submit&quot;&gt;Check answer&lt;\/button&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;stats&quot;&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot;&gt;Score&lt;\/span&gt;&lt;span class=&quot;stat-value&quot; id=&quot;score&quot;&gt;0&lt;\/span&gt;&lt;\/div&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot;&gt;Time&lt;\/span&gt;&lt;span class=&quot;stat-value&quot; id=&quot;time&quot;&gt;\u2014&lt;\/span&gt;&lt;div class=&quot;timer-track&quot;&gt;&lt;div id=&quot;timerBar&quot;&gt;&lt;\/div&gt;&lt;\/div&gt;&lt;\/div&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot; id=&quot;levelLabel&quot;&gt;Level&lt;\/span&gt;&lt;span class=&quot;stat-value&quot; id=&quot;level&quot;&gt;1&lt;\/span&gt;&lt;\/div&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot; id=&quot;strikeLabel&quot;&gt;Strikes&lt;\/span&gt;&lt;div class=&quot;strikes&quot; id=&quot;strikes&quot;&gt;&lt;i class=&quot;strike&quot;&gt;&lt;\/i&gt;&lt;i class=&quot;strike&quot;&gt;&lt;\/i&gt;&lt;i class=&quot;strike&quot;&gt;&lt;\/i&gt;&lt;\/div&gt;&lt;span class=&quot;stat-value&quot; id=&quot;mistakeCount&quot; hidden&gt;0&lt;\/span&gt;&lt;\/div&gt;\n        &lt;\/div&gt;\n      &lt;\/section&gt;\n      &lt;p class=&quot;footer-note&quot;&gt;Ad-free \u2022 No sign-in \u2022 CTE Makes School Matter&lt;\/p&gt;\n    &lt;\/main&gt;\n  &lt;\/div&gt;\n\n  &lt;dialog id=&quot;settingsDialog&quot;&gt;\n    &lt;form method=&quot;dialog&quot; id=&quot;settingsForm&quot;&gt;\n      &lt;div class=&quot;modal-head&quot;&gt;&lt;h2&gt;Game settings&lt;\/h2&gt;&lt;button class=&quot;close&quot; value=&quot;cancel&quot; aria-label=&quot;Close settings&quot;&gt;\u00d7&lt;\/button&gt;&lt;\/div&gt;\n      &lt;div class=&quot;settings-grid&quot;&gt;\n        &lt;label class=&quot;span-2&quot;&gt;Student name &lt;input id=&quot;studentName&quot; type=&quot;text&quot; maxlength=&quot;40&quot; placeholder=&quot;Optional \u2014 appears on results&quot;&gt;&lt;\/label&gt;\n        &lt;label&gt;Game mode&lt;select id=&quot;mode&quot;&gt;&lt;option value=&quot;find&quot;&gt;Find the mark&lt;\/option&gt;&lt;option value=&quot;type&quot;&gt;Read and type&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Question precision&lt;select id=&quot;questions&quot;&gt;&lt;option value=&quot;1&quot;&gt;Whole inches&lt;\/option&gt;&lt;option value=&quot;2&quot;&gt;Halves&lt;\/option&gt;&lt;option value=&quot;4&quot;&gt;Quarters&lt;\/option&gt;&lt;option value=&quot;8&quot;&gt;Eighths&lt;\/option&gt;&lt;option value=&quot;16&quot; selected&gt;Sixteenths&lt;\/option&gt;&lt;option value=&quot;32&quot;&gt;Thirty-seconds&lt;\/option&gt;&lt;option value=&quot;64&quot;&gt;Sixty-fourths&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Ruler marks&lt;select id=&quot;marks&quot;&gt;&lt;option value=&quot;2&quot;&gt;Halves&lt;\/option&gt;&lt;option value=&quot;4&quot;&gt;Quarters&lt;\/option&gt;&lt;option value=&quot;8&quot;&gt;Eighths&lt;\/option&gt;&lt;option value=&quot;16&quot; selected&gt;Sixteenths&lt;\/option&gt;&lt;option value=&quot;32&quot;&gt;Thirty-seconds&lt;\/option&gt;&lt;option value=&quot;64&quot;&gt;Sixty-fourths&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Ruler length&lt;select id=&quot;length&quot;&gt;&lt;option value=&quot;auto&quot;&gt;Automatic&lt;\/option&gt;&lt;option value=&quot;1&quot;&gt;1 inch&lt;\/option&gt;&lt;option value=&quot;2&quot;&gt;2 inches&lt;\/option&gt;&lt;option value=&quot;3&quot;&gt;3 inches&lt;\/option&gt;&lt;option value=&quot;4&quot; selected&gt;4 inches&lt;\/option&gt;&lt;option value=&quot;6&quot;&gt;6 inches&lt;\/option&gt;&lt;option value=&quot;8&quot;&gt;8 inches&lt;\/option&gt;&lt;option value=&quot;10&quot;&gt;10 inches&lt;\/option&gt;&lt;option value=&quot;12&quot;&gt;12 inches&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Answer notation&lt;select id=&quot;notation&quot;&gt;&lt;option value=&quot;fraction&quot;&gt;Fractions&lt;\/option&gt;&lt;option value=&quot;decimal&quot;&gt;Decimals&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Fraction style&lt;select id=&quot;fractionStyle&quot;&gt;&lt;option value=&quot;both&quot;&gt;Mixed practice&lt;\/option&gt;&lt;option value=&quot;simplified&quot;&gt;Simplified&lt;\/option&gt;&lt;option value=&quot;unsimplified&quot;&gt;Unsimplified&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Timer&lt;select id=&quot;timer&quot;&gt;&lt;option value=&quot;on&quot;&gt;On&lt;\/option&gt;&lt;option value=&quot;off&quot;&gt;Off&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Sounds&lt;select id=&quot;sound&quot;&gt;&lt;option value=&quot;on&quot;&gt;On&lt;\/option&gt;&lt;option value=&quot;off&quot;&gt;Off&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Measure twice&lt;select id=&quot;measureTwice&quot;&gt;&lt;option value=&quot;off&quot;&gt;Off&lt;\/option&gt;&lt;option value=&quot;on&quot;&gt;On&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;div class=&quot;teacher-tools&quot;&gt;&lt;p&gt;&lt;strong&gt;Teacher preset link&lt;\/strong&gt;&lt;br&gt;Opens with these settings already selected.&lt;\/p&gt;&lt;button type=&quot;button&quot; class=&quot;secondary compact&quot; id=&quot;copyPreset&quot;&gt;Copy link&lt;\/button&gt;&lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;div class=&quot;modal-foot&quot;&gt;&lt;button class=&quot;secondary&quot; value=&quot;cancel&quot;&gt;Cancel&lt;\/button&gt;&lt;button class=&quot;primary&quot; id=&quot;saveSettings&quot; value=&quot;default&quot;&gt;Save settings&lt;\/button&gt;&lt;\/div&gt;\n    &lt;\/form&gt;\n  &lt;\/dialog&gt;\n  &lt;div class=&quot;result-overlay&quot; id=&quot;gameOver&quot;&gt;&lt;div class=&quot;result-card&quot;&gt;\n    &lt;p class=&quot;eyebrow&quot; id=&quot;resultEyebrow&quot;&gt;Session complete&lt;\/p&gt;&lt;h2 id=&quot;finalScore&quot;&gt;Score: 0&lt;\/h2&gt;&lt;p class=&quot;result-name&quot; id=&quot;resultName&quot;&gt;&lt;\/p&gt;&lt;div class=&quot;mastery-badge&quot; id=&quot;masteryBadge&quot;&gt;Keep practicing&lt;\/div&gt;\n    &lt;div class=&quot;result-grid&quot;&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Accuracy&lt;\/span&gt;&lt;strong id=&quot;resultAccuracy&quot;&gt;0%&lt;\/strong&gt;&lt;\/div&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Correct&lt;\/span&gt;&lt;strong id=&quot;resultCorrect&quot;&gt;0&lt;\/strong&gt;&lt;\/div&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Precision&lt;\/span&gt;&lt;strong id=&quot;resultPrecision&quot;&gt;1\/16\u2033&lt;\/strong&gt;&lt;\/div&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Date&lt;\/span&gt;&lt;strong id=&quot;resultDate&quot;&gt;\u2014&lt;\/strong&gt;&lt;\/div&gt;&lt;\/div&gt;\n    &lt;p id=&quot;finalMessage&quot;&gt;Nice work. Every mark gets easier with practice.&lt;\/p&gt;&lt;div class=&quot;result-actions&quot;&gt;&lt;button class=&quot;secondary&quot; id=&quot;printResults&quot;&gt;Print \/ Save PDF&lt;\/button&gt;&lt;button class=&quot;primary&quot; id=&quot;playAgain&quot;&gt;Try again&lt;\/button&gt;&lt;\/div&gt;\n  &lt;\/div&gt;&lt;\/div&gt;\n  &lt;div class=&quot;toast&quot; id=&quot;toast&quot; role=&quot;status&quot; aria-live=&quot;polite&quot;&gt;&lt;\/div&gt;\n\n  &lt;script&gt;\n  (()=&gt;{\n    const $=s=&gt;document.querySelector(s), canvas=$('#ruler'), ctx=canvas.getContext('2d');\n    const els={target:$('#target'),prompt:$('#promptLabel'),hint:$('#hint'),note:$('#learnNote'),score:$('#score'),level:$('#level'),levelLabel:$('#levelLabel'),time:$('#time'),timerBar:$('#timerBar'),strikes:$('#strikes'),mistakes:$('#mistakeCount'),strikeLabel:$('#strikeLabel'),start:$('#start'),skip:$('#newQuestion'),typeRow:$('#typeRow'),answer:$('#answer'),modeLabel:$('#modeLabel'),toast:$('#toast'),best:$('#best')};\n    const inputs=['mode','questions','marks','length','notation','fractionStyle','timer','sound','measureTwice'];\n    const defaults={session:'practice',challengeLevel:'medium',mode:'find',questions:'16',marks:'16',length:'4',notation:'fraction',fractionStyle:'both',timer:'on',sound:'on',measureTwice:'off',studentName:''};\n    let settings={...defaults,...JSON.parse(localStorage.getItem('rulerLabSettings')||'{}')};\n    const preset=new URLSearchParams(location.search);['session','challengeLevel',...inputs].forEach(k=&gt;{if(preset.has(k))settings[k]=preset.get(k)});if(+settings.questions&gt;+settings.marks)settings.marks=settings.questions;\n    let state={playing:false,score:0,correct:0,answered:0,strikes:0,level:1,answerUnits:0,answerDen:+settings.questions,hoverUnits:null,shownUnits:null,firstMeasure:null,questionKind:'find',expectsFind:true,startTime:0,duration:10,raf:0,locked:false};\n    let geom={x:0,y:0,w:0,h:0,inch:0,length:4};\n    inputs.forEach(id=&gt;$('#'+id).value=settings[id]??defaults[id]);$('#studentName').value=settings.studentName||'';\n    function gcd(a,b){while(b)[a,b]=[b,a%b];return a||1}\n    function autoLength(){const width=canvas.clientWidth;return width&lt;520?2:width&lt;820?3:width&lt;1120?4:6}\n    function rulerLength(){return settings.length==='auto'?autoLength():+settings.length}\n    function challengeDen(){return settings.challengeLevel==='easy'?8:16}\n    function rulerMarks(){return settings.session==='challenge'?16:+settings.marks}\n    function format(units,denom=+settings.questions,forceStyle=settings.fractionStyle){\n      const value=units\/denom;if(settings.notation==='decimal')return value.toFixed(4).replace(\/0+$\/,'').replace(\/\\.$\/,'')+'\u2033';\n      const whole=Math.floor(units\/denom),n=units%denom;if(!n)return whole+'\u2033';let num=n,d=denom;\n      const style=forceStyle==='both'?(Math.random()&lt;.5?'simplified':'unsimplified'):forceStyle;if(style==='simplified'){const g=gcd(num,d);num\/=g;d\/=g}\n      return(whole?whole+' ':'')+num+'\/'+d+'\u2033';\n    }\n    function precisionName(){if(settings.session==='challenge')return settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1)+' \u00b7 1\/'+challengeDen()+'\u2033';return({1:'Whole',2:'1\/2\u2033',4:'1\/4\u2033',8:'1\/8\u2033',16:'1\/16\u2033',32:'1\/32\u2033',64:'1\/64\u2033'})[settings.questions]}\n    function unitsAtMarks(units,den=state.answerDen){return Math.round(units*rulerMarks()\/den)}\n    function tickFamily(frac,marks){if(frac===0||frac===marks\/2)return'major';const pos=frac\/marks;if(Number.isInteger(pos*4))return'quarter';if(Number.isInteger(pos*8))return'eighth';if(Number.isInteger(pos*16))return'sixteenth';return'fine'}\n    function tickColor(family){const colors={major:'#12304a',quarter:'#2463eb',eighth:'#0f8a72',sixteenth:'#d97706',fine:'#8797a4'};return settings.session==='learn'?colors[family]:'#12304a'}\n    function updateLegend(){const marks=rulerMarks(),items=[];if(settings.session==='learn'&amp;&amp;marks&gt;=4)items.push(['quarter','\u00bc-inch']);if(settings.session==='learn'&amp;&amp;marks&gt;=8)items.push(['eighth','\u215b-inch']);if(settings.session==='learn'&amp;&amp;marks&gt;=16)items.push(['sixteenth','\u00b9\u2044\u2081\u2086-inch']);const legend=$('#rulerLegend');legend.hidden=!items.length;legend.innerHTML=items.map(([c,t])=&gt;'&lt;span class=&quot;legend-item&quot;&gt;&lt;i class=&quot;legend-line legend-'+c+'&quot;&gt;&lt;\/i&gt;'+t+'&lt;\/span&gt;').join('')}\n    function draw(){\n      const dpr=Math.min(devicePixelRatio||1,2),rect=canvas.getBoundingClientRect();\n      if(canvas.width!==Math.round(rect.width*dpr)||canvas.height!==Math.round(rect.height*dpr)){canvas.width=Math.round(rect.width*dpr);canvas.height=Math.round(rect.height*dpr)}\n      ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,rect.width,rect.height);const len=rulerLength(),pad=Math.max(20,rect.width*.025),top=26,base=rect.height-35,usable=rect.width-pad*2;geom={x:pad,y:top,w:usable,h:base-top,inch:usable\/len,length:len};\n      ctx.fillStyle='#fff9df';ctx.strokeStyle='#12304a';ctx.lineWidth=2.5;roundRect(ctx,pad,top,usable,base-top,8);ctx.fill();ctx.stroke();const marks=rulerMarks(),total=len*marks;\n      for(let i=0;i&lt;=total;i++){const x=pad+i\/total*usable,frac=i%marks,family=tickFamily(frac,marks);let tick;if(i===0||i===total||frac===0)tick=78;else{let pow=0,n=frac;while(n%2===0){pow++;n\/=2}tick=Math.min(20+pow*12,64)}ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,top+tick);ctx.strokeStyle=tickColor(family);ctx.lineWidth=family==='major'?2.8:2.2;ctx.stroke();if(frac===0&amp;&amp;i&gt;0){ctx.fillStyle='#12304a';ctx.font='800 25px Inter,system-ui';ctx.textAlign='center';ctx.fillText(String(i\/marks),x,top+104)}}\n      const active=state.playing&amp;&amp;!state.locked&amp;&amp;state.expectsFind?state.hoverUnits:null;if(active!==null){const x=pad+active\/(marks*len)*usable;ctx.fillStyle='rgba(22,134,111,.22)';ctx.fillRect(Math.max(pad,x-geom.inch\/marks\/2),top+2,Math.max(4,geom.inch\/marks),base-top-4);ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#16866f';ctx.lineWidth=4;ctx.stroke();canvas.setAttribute('aria-valuenow',String(active));canvas.setAttribute('aria-valuetext',format(active,marks,'simplified'))}\n      canvas.setAttribute('aria-valuemax',String(marks*len));if(state.shownUnits!==null){const x=pad+state.shownUnits\/(marks*len)*usable;ctx.beginPath();ctx.moveTo(x,top-5);ctx.lineTo(x,base+2);ctx.strokeStyle='#d44949';ctx.lineWidth=4;ctx.stroke();ctx.fillStyle='#d44949';ctx.beginPath();ctx.moveTo(x-8,top-10);ctx.lineTo(x+8,top-10);ctx.lineTo(x,top+1);ctx.fill()}\n      if(state.questionKind==='read'&amp;&amp;state.playing){const m=unitsAtMarks(state.answerUnits),x=pad+m\/(marks*len)*usable;ctx.fillStyle='#a11d48';ctx.beginPath();ctx.moveTo(x-11,top-15);ctx.lineTo(x+11,top-15);ctx.lineTo(x,top+1);ctx.fill();ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#a11d48';ctx.lineWidth=3;ctx.stroke()}updateLegend()\n    }\n    function roundRect(c,x,y,w,h,r){c.beginPath();c.roundRect?c.roundRect(x,y,w,h,r):c.rect(x,y,w,h)}\n    function randomUnits(max,avoid=0){let u;do{u=Math.max(1,Math.floor(Math.random()*max))}while(u===avoid);return u}\n    function learningHelp(units,den){const whole=Math.floor(units\/den),n=units%den;if(!n)return'Find the long line for '+whole+' whole inch'+(whole===1?'':'es')+'.';const g=gcd(n,den),sn=n\/g,sd=den\/g;return'Break it down: '+(whole?whole+' whole inch'+(whole===1?'':'es')+' plus ':'')+sn+' of '+sd+' equal parts.'}\n    function showType(on){els.typeRow.classList.toggle('show',on);if(on)setTimeout(()=&gt;els.answer.focus(),60)}\n    function newQuestion(){\n      state.locked=false;state.shownUnits=null;state.hoverUnits=null;state.firstMeasure=null;els.answer.value='';els.note.classList.remove('show');const den=settings.session==='challenge'?challengeDen():+settings.questions,len=rulerLength();state.answerDen=den;\n      let kind=settings.mode==='find'?'find':'read';if(settings.session==='challenge'){const sets={easy:['find','read','cutlist','find','midpoint'],medium:['find','read','cutlist','midpoint','remaining'],hard:['read','cutlist','remaining','midpoint','multiCut','difference']};const kinds=sets[settings.challengeLevel];kind=kinds[state.answered%kinds.length]}\n      state.questionKind=kind;state.expectsFind=kind==='find'||kind==='cutlist';\n      if(kind==='find'||kind==='read'||kind==='cutlist'){state.answerUnits=randomUnits(len*den,state.answerUnits)}\n      if(kind==='find'){els.prompt.textContent='Click the measurement';els.target.textContent=format(state.answerUnits,den);els.hint.textContent=settings.measureTwice==='on'?'Measure it twice. Both selections must match.':'Move over the ruler, then click or tap the correct mark.';showType(false)}\n      if(kind==='read'){els.prompt.textContent='Read the marked line';els.target.innerHTML='&lt;small&gt;Type your answer below&lt;\/small&gt;';els.hint.textContent='Enter a fraction or decimal measurement.';showType(true)}\n      if(kind==='cutlist'){const parts=['Side rail','Shelf support','Face-frame piece','Drawer spacer'];els.prompt.textContent='Cut list: '+parts[state.answered%parts.length];els.target.textContent=format(state.answerUnits,den,'simplified');els.hint.textContent='Select this cut length on the ruler.';showType(false)}\n      if(kind==='midpoint'){state.answerUnits=randomUnits(Math.max(2,Math.floor(6*den)));const boardUnits=state.answerUnits*2;els.prompt.textContent='Find the midpoint';els.target.textContent=format(boardUnits,den,'simplified')+' board';els.hint.textContent='What measurement marks the exact center?';showType(true)}\n      if(kind==='remaining'){const totalInches=6+Math.floor(Math.random()*7),totalUnits=totalInches*den,cutUnits=randomUnits(totalUnits-1);state.answerUnits=totalUnits-cutUnits;els.prompt.textContent='Board remaining';els.target.style.fontSize='clamp(1.55rem,3.4vw,3.2rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \u2212 '+format(cutUnits,den,'simplified');els.hint.textContent='Type the board length left after the cut.';showType(true)}else els.target.style.fontSize='';\n      if(kind==='multiCut'){const totalInches=10+Math.floor(Math.random()*3),totalUnits=totalInches*den,maxPiece=Math.floor((totalUnits-den)\/3),pieceUnits=den+Math.floor(Math.random()*Math.max(1,maxPiece-den));state.answerUnits=totalUnits-pieceUnits*2;els.prompt.textContent='Two pieces from one board';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \u2212 2 \u00d7 '+format(pieceUnits,den,'simplified');els.hint.textContent='Ignore saw kerf. How much stock remains?';showType(true)}\n      if(kind==='difference'){const planned=2*den+randomUnits(4*den),gap=randomUnits(den),actual=planned-gap;state.answerUnits=gap;els.prompt.textContent='Measurement difference';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(planned,den,'simplified')+' planned \u00b7 '+format(actual,den,'simplified')+' actual';els.hint.textContent='How much shorter is the actual piece?';showType(true)}\n      if(settings.session==='learn'){els.note.textContent=learningHelp(state.answerUnits,den);els.note.classList.add('show')}\n      setStatMode();startClock();draw();\n    }\n    function startGame(){cancelAnimationFrame(state.raf);state={...state,playing:true,score:0,correct:0,answered:0,strikes:0,level:1,locked:false,shownUnits:null,firstMeasure:null};els.score.textContent='0';els.start.textContent='Restart';els.skip.hidden=settings.session==='learn';$('#gameOver').classList.remove('show');updateStrikes();newQuestion()}\n    function startClock(){cancelAnimationFrame(state.raf);state.duration=state.expectsFind?Math.max(3,11-state.level):Math.max(6,20-(state.level-1)*2);if(settings.session==='challenge')state.duration+=settings.challengeLevel==='easy'?6:settings.challengeLevel==='hard'?-2:0;state.startTime=performance.now();if(settings.timer==='off'||settings.session==='learn'){els.time.textContent='\u221e';els.timerBar.style.width='100%';els.timerBar.style.background='#16866f';return}const tick=now=&gt;{if(!state.playing||state.locked)return;const left=Math.max(0,state.duration-(now-state.startTime)\/1000),pct=left\/state.duration*100;els.time.textContent=Math.ceil(left)+'s';els.timerBar.style.width=pct+'%';els.timerBar.style.background=pct&lt;25?'#d44949':pct&lt;55?'#e7ad29':'#16866f';if(left&lt;=0){check(null);return}state.raf=requestAnimationFrame(tick)};state.raf=requestAnimationFrame(tick)}\n    function parseAnswer(v){v=v.trim().replace(\/[\u2033&quot;]\/g,'');if(!v)return null;if(\/^\\d*\\.?\\d+$\/.test(v))return+v;const m=v.match(\/^(?:(\\d+)\\s+)?(\\d+)\\\/(\\d+)$\/);if(!m)return null;return+(m[1]||0)+(+m[2]\/+m[3])}\n    function check(selectedMark){\n      if(!state.playing||state.locked)return;if(state.expectsFind&amp;&amp;selectedMark!==null&amp;&amp;settings.measureTwice==='on'){if(state.firstMeasure===null){state.firstMeasure=selectedMark;els.hint.textContent='First measurement saved. Move away, then measure it again.';toast('First measurement saved',false);return}if(state.firstMeasure!==selectedMark){state.firstMeasure=null;els.hint.textContent='Those measurements did not match. Start again.';toast('Measurements did not match',true);return}}\n      state.locked=true;cancelAnimationFrame(state.raf);let ok=false;if(state.expectsFind)ok=selectedMark!==null&amp;&amp;selectedMark===unitsAtMarks(state.answerUnits);else{const v=parseAnswer(els.answer.value);ok=v!==null&amp;&amp;Math.abs(v-state.answerUnits\/state.answerDen)&lt;1e-7}\n      state.answered++;if(ok){state.correct++;state.score+=state.level*10;els.score.textContent=state.score;toast('Correct! +'+state.level*10,false);sound(true)}else{state.strikes++;const correct=format(state.answerUnits,state.answerDen,'simplified');toast(selectedMark===null?'Time\u2019s up \u2014 '+correct:'Correct answer: '+correct,true);sound(false);if(state.expectsFind)state.shownUnits=unitsAtMarks(state.answerUnits);draw()}\n      if(settings.session!=='challenge'&amp;&amp;state.correct&gt;0&amp;&amp;state.correct%10===0){state.level=Math.min(10,Math.floor(state.correct\/10)+1);els.level.textContent=state.level}\n      updateStrikes();if(settings.session==='learn'&amp;&amp;!ok)els.note.textContent='The correct mark is highlighted. '+learningHelp(state.answerUnits,state.answerDen);\n      const done=settings.session==='challenge'&amp;&amp;state.answered&gt;=20||settings.session==='practice'&amp;&amp;state.strikes&gt;=3;if(done)setTimeout(endGame,1100);else setTimeout(newQuestion,ok?700:1500)\n    }\n    function setStatMode(){const challenge=settings.session==='challenge';els.levelLabel.textContent=challenge?'Question':'Level';els.level.textContent=challenge?(Math.min(20,state.answered+1)+'\/20'):state.level;els.strikeLabel.textContent=challenge?'Missed':'Strikes';els.strikes.hidden=challenge;els.mistakes.hidden=!challenge;if(challenge)els.mistakes.textContent=state.strikes}\n    function updateStrikes(){[...els.strikes.children].forEach((x,i)=&gt;x.classList.toggle('on',i&lt;state.strikes));els.mistakes.textContent=state.strikes;setStatMode()}\n    function endGame(){state.playing=false;cancelAnimationFrame(state.raf);const total=Math.max(1,state.answered),accuracy=Math.round(state.correct\/total*100),mastered=settings.session==='challenge'&amp;&amp;accuracy&gt;=90,best=Math.max(state.score,+localStorage.getItem('rulerLabBest')||0);localStorage.setItem('rulerLabBest',best);els.best.textContent=best;$('#resultEyebrow').textContent=settings.session==='challenge'?'Shop challenge complete':'Practice complete';$('#finalScore').textContent=settings.session==='challenge'?(mastered?'Measurement mastered':'Keep building accuracy'):'Score: '+state.score;$('#resultName').textContent=settings.studentName||'Ruler Lab Student';$('#masteryBadge').textContent=settings.session==='challenge'?(mastered?'Mastered \u2022 90%+':'Not yet mastered \u2022 Goal: 90%'):'Practice session';$('#masteryBadge').classList.toggle('not-yet',!mastered);$('#resultAccuracy').textContent=accuracy+'%';$('#resultCorrect').textContent=state.correct+' \/ '+state.answered;$('#resultPrecision').textContent=precisionName();$('#resultDate').textContent=new Date().toLocaleDateString();$('#finalMessage').textContent=mastered?'Ready to apply this skill in the shop.':accuracy&gt;=75?'Very close\u2014review the marks you missed and try again.':'Slow down, count the spaces, and measure twice.';$('#gameOver').classList.add('show');els.start.textContent='Start '+sessionName();els.skip.hidden=true}\n    function sessionName(){return settings.session==='challenge'?'challenge':settings.session}\n    function updateSessionUI(){document.querySelectorAll('.session-btn').forEach(b=&gt;b.setAttribute('aria-selected',String(b.dataset.session===settings.session)));const challenge=settings.session==='challenge',notes={easy:'Eighth-inch questions on a full sixteenth-inch ruler',medium:'Sixteenths, midpoints, and one-step remaining length',hard:'Sixteenths, multi-cut stock, differences, and mixed calculations'};$('#challengeLevels').hidden=!challenge;document.querySelectorAll('.level-btn').forEach(b=&gt;b.setAttribute('aria-selected',String(b.dataset.level===settings.challengeLevel)));$('#difficultyNote').textContent=notes[settings.challengeLevel];els.modeLabel.textContent=settings.session==='learn'?'Color-guided learning':challenge?'Shop Challenge \u00b7 '+settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1):'Independent practice';els.start.textContent='Start '+sessionName();setStatMode();if(!state.playing){els.prompt.textContent=challenge?'20-question '+settings.challengeLevel+' challenge':settings.session==='learn'?'Learn each ruler mark':'Build speed and accuracy';els.target.textContent='Press Start';els.hint.textContent=challenge?notes[settings.challengeLevel]+'.':settings.session==='learn'?'Colors identify quarters, eighths, and sixteenths on the ruler.':'Practice on a standard ruler without color clues.';showType(false);draw()}}\n    let toastTimer;function toast(msg,bad){els.toast.textContent=msg;els.toast.className='toast show'+(bad?' bad':'');clearTimeout(toastTimer);toastTimer=setTimeout(()=&gt;els.toast.className='toast',1100)}\n    function sound(good){if(settings.sound!=='on')return;try{const ac=new(window.AudioContext||window.webkitAudioContext)(),o=ac.createOscillator(),g=ac.createGain();o.frequency.value=good?660:190;o.type=good?'sine':'triangle';g.gain.setValueAtTime(.08,ac.currentTime);g.gain.exponentialRampToValueAtTime(.001,ac.currentTime+.16);o.connect(g);g.connect(ac.destination);o.start();o.stop(ac.currentTime+.17)}catch(e){}}\n    function pointer(e){const rect=canvas.getBoundingClientRect(),x=Math.max(geom.x,Math.min(geom.x+geom.w,e.clientX-rect.left)),marks=rulerMarks();return Math.round((x-geom.x)\/geom.w*(marks*geom.length))}\n    canvas.addEventListener('pointermove',e=&gt;{if(state.playing&amp;&amp;!state.locked&amp;&amp;state.expectsFind){state.hoverUnits=pointer(e);draw()}});canvas.addEventListener('pointerleave',()=&gt;{if(state.expectsFind){state.hoverUnits=null;draw()}});canvas.addEventListener('pointerdown',e=&gt;{if(state.playing&amp;&amp;!state.locked&amp;&amp;state.expectsFind){e.preventDefault();const u=pointer(e);state.hoverUnits=u;draw();check(u)}});\n    canvas.addEventListener('keydown',e=&gt;{if(!state.playing||state.locked||!state.expectsFind)return;const max=rulerMarks()*rulerLength();if(e.key==='ArrowRight'||e.key==='ArrowUp'){e.preventDefault();state.hoverUnits=Math.min(max,(state.hoverUnits??0)+1);draw()}else if(e.key==='ArrowLeft'||e.key==='ArrowDown'){e.preventDefault();state.hoverUnits=Math.max(0,(state.hoverUnits??0)-1);draw()}else if(e.key==='Enter'||e.key===' '){e.preventDefault();check(state.hoverUnits??0)}});\n    $('#submit').onclick=()=&gt;check(0);els.answer.addEventListener('keydown',e=&gt;{if(e.key==='Enter')check(0)});els.start.onclick=startGame;$('#playAgain').onclick=startGame;$('#printResults').onclick=()=&gt;window.print();els.skip.onclick=()=&gt;check(null);\n    document.querySelectorAll('.session-btn').forEach(b=&gt;b.onclick=()=&gt;{settings.session=b.dataset.session;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\n    document.querySelectorAll('.level-btn').forEach(b=&gt;b.onclick=()=&gt;{settings.challengeLevel=b.dataset.level;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\n    $('#openSettings').onclick=()=&gt;$('#settingsDialog').showModal();$('#saveSettings').onclick=e=&gt;{e.preventDefault();inputs.forEach(id=&gt;settings[id]=$('#'+id).value);settings.studentName=$('#studentName').value.trim();if(+settings.questions&gt;+settings.marks){settings.marks=settings.questions;$('#marks').value=settings.marks;toast('Ruler marks adjusted to match questions',false)}localStorage.setItem('rulerLabSettings',JSON.stringify(settings));$('#settingsDialog').close();if(state.playing)startGame();else updateSessionUI()};\n    $('#copyPreset').onclick=async()=&gt;{inputs.forEach(id=&gt;settings[id]=$('#'+id).value);const url=new URL(location.href);url.search='';url.searchParams.set('session',settings.session);url.searchParams.set('challengeLevel',settings.challengeLevel);inputs.forEach(k=&gt;url.searchParams.set(k,settings[k]));try{await navigator.clipboard.writeText(url.href);toast('Teacher preset link copied',false)}catch(e){prompt('Copy this teacher preset link:',url.href)}};\n    $('#questions').onchange=()=&gt;{if(+$('#questions').value&gt;+$('#marks').value)$('#marks').value=$('#questions').value};$('#marks').onchange=()=&gt;{if(+$('#marks').value&lt;+$('#questions').value)$('#questions').value=$('#marks').value};$('#fullscreen').onclick=()=&gt;{if(!document.fullscreenElement)document.documentElement.requestFullscreen?.();else document.exitFullscreen?.()};window.addEventListener('resize',draw);\n    els.best.textContent=localStorage.getItem('rulerLabBest')||'0';updateSessionUI();\n    if(document.modelContext?.registerTool){const register=t=&gt;Promise.resolve(document.modelContext.registerTool(t)).catch(()=&gt;{});register({name:'start_ruler_game',title:'Start ruler game',description:'Start or restart the visible ruler learning, practice, or shop challenge session.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=&gt;{if(v?.session)settings.session=v.session;if(v?.challengeLevel)settings.challengeLevel=v.challengeLevel;updateSessionUI();startGame();return{status:'started',session:settings.session,challengeLevel:settings.challengeLevel,precision:settings.session==='challenge'?challengeDen():settings.questions}}});register({name:'configure_ruler_game',title:'Configure ruler game',description:'Change the visible ruler game settings.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']},mode:{enum:['find','type']},questions:{enum:['1','2','4','8','16','32','64']},marks:{enum:['2','4','8','16','32','64']},length:{enum:['auto','1','2','3','4','6','8','10','12']},notation:{enum:['fraction','decimal']},timer:{enum:['on','off']},measureTwice:{enum:['on','off']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=&gt;{settings={...settings,...v};if(+settings.questions&gt;+settings.marks)settings.marks=settings.questions;inputs.forEach(id=&gt;$('#'+id).value=settings[id]);localStorage.setItem('rulerLabSettings',JSON.stringify(settings));updateSessionUI();return{status:'configured',settings}}})}\n  })();\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;&lt;!doctype html&gt;\n&lt;html lang=&quot;en&quot;&gt;\n&lt;head&gt;\n  &lt;meta charset=&quot;utf-8&quot;&gt;\n  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no&quot;&gt;\n  &lt;meta name=&quot;theme-color&quot; content=&quot;#12304a&quot;&gt;\n  &lt;meta name=&quot;description&quot; content=&quot;An ad-free woodshop measurement game for learning to read an inch ruler and solve practical cut-list problems.&quot;&gt;\n  &lt;title&gt;Ruler Lab \u2014 Woodshop Measurement Practice&lt;\/title&gt;\n  &lt;link rel=&quot;icon&quot; type=&quot;image\/svg+xml&quot; href=&quot;data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' viewBox='0 0 64 64'%3E%3Crect x='5' y='13' width='54' height='38' rx='7' fill='%23f6c453'\/%3E%3Cpath d='M13 13v15m8-15v9m8-9v15m8-15v9m8-9v15m8-15v9' stroke='%2312304a' stroke-width='4'\/%3E%3C\/svg%3E&quot;&gt;\n  &lt;style&gt;\n    :root{--ink:#102a43;--muted:#62758a;--navy:#12304a;--blue:#1675a9;--sky:#e8f6fb;--paper:#fffdfa;--yellow:#f6c453;--green:#16866f;--red:#d44949;--line:#d6e1e8;--shadow:0 18px 50px rgba(18,48,74,.14)}\n    *{box-sizing:border-box}\n    html,body{min-height:100%;margin:0}\n    body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,&quot;Segoe UI&quot;,sans-serif;color:var(--ink);background:linear-gradient(180deg,#e9f7fb 0,#f7fbfc 52%,#edf4f7 100%)}\n    button,select,input{font:inherit}\n    button{cursor:pointer}\n    .app{min-height:100vh;display:flex;flex-direction:column}\n    header{height:72px;display:flex;align-items:center;justify-content:space-between;padding:0 clamp(18px,4vw,64px);background:var(--navy);color:white}\n    .brand{display:flex;align-items:center;gap:12px;font-weight:850;font-size:1.25rem;letter-spacing:.01em}\n    .brand-icon{width:42px;height:30px;border-radius:7px;background:var(--yellow);position:relative;box-shadow:inset 0 -3px rgba(0,0,0,.08)}\n    .brand-icon:after{content:&quot;&quot;;position:absolute;inset:0;background:repeating-linear-gradient(90deg,transparent 0 7px,var(--navy) 7px 9px,transparent 9px 14px);clip-path:polygon(0 0,100% 0,100% 58%,0 58%)}\n    .header-actions{display:flex;gap:8px}\n    .icon-btn,.settings-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.08);color:#fff;border-radius:10px;min-height:42px;padding:0 14px;font-weight:750}\n    .icon-btn:hover,.settings-btn:hover{background:rgba(255,255,255,.16)}\n    main{width:min(1500px,100%);margin:auto;padding:clamp(18px,3vw,40px) clamp(14px,4vw,58px) 36px;flex:1;display:flex;flex-direction:column;justify-content:center;gap:18px}\n    .session-picker{align-self:center;display:grid;grid-template-columns:repeat(3,1fr);gap:5px;background:#dce9ee;border:1px solid #c8d9e1;padding:5px;border-radius:15px}\n    .session-btn{border:0;background:transparent;color:#50687a;border-radius:10px;padding:11px 18px;font-weight:850;min-width:150px}\n    .session-btn[aria-selected=&quot;true&quot;]{background:white;color:var(--navy);box-shadow:0 3px 12px rgba(18,48,74,.12)}\n    .session-btn small{display:block;font-size:.7rem;font-weight:700;color:#718495;margin-top:2px}\n    .challenge-levels{align-self:center;display:flex;align-items:center;gap:8px;padding:6px;background:#fff;border:1px solid var(--line);border-radius:13px;box-shadow:0 4px 16px rgba(18,48,74,.07)}\n    .challenge-levels[hidden]{display:none}.challenge-label{font-size:.76rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:900;padding:0 7px}.level-btn{border:1px solid transparent;background:transparent;color:#536c7e;border-radius:9px;padding:8px 13px;font-weight:850}.level-btn[aria-selected=&quot;true&quot;]{background:var(--blue);color:white}.difficulty-note{font-size:.78rem;color:var(--muted);font-weight:700;padding:0 8px;max-width:280px}\n    .topline{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:18px}\n    .mode-pill{justify-self:start;display:inline-flex;align-items:center;gap:8px;background:#d9f3ed;color:#116653;padding:8px 12px;border-radius:999px;font-weight:800;font-size:.85rem;text-transform:uppercase;letter-spacing:.06em}\n    .mode-pill:before{content:&quot;&quot;;width:8px;height:8px;border-radius:50%;background:var(--green)}\n    .prompt{text-align:center;min-width:min(430px,60vw)}\n    .eyebrow{font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;font-size:.82rem;margin:0 0 4px}\n    #target{font-size:clamp(2.1rem,5vw,4.8rem);line-height:1;font-weight:900;letter-spacing:-.045em;margin:0;color:var(--navy);font-variant-numeric:tabular-nums}\n    #target small{font-size:.45em;letter-spacing:0;color:var(--muted);font-weight:800}\n    .best{justify-self:end;text-align:right;color:var(--muted);font-weight:700}\n    .best strong{display:block;color:var(--ink);font-size:1.15rem}\n    .game-card{background:var(--paper);border:1px solid rgba(18,48,74,.12);border-radius:22px;box-shadow:var(--shadow);overflow:hidden}\n    .ruler-legend{display:flex;align-items:center;justify-content:center;gap:18px;flex-wrap:wrap;margin:0 0 10px;color:#526b7d;font-size:.8rem;font-weight:800}\n    .legend-item{display:inline-flex;align-items:center;gap:6px}.legend-line{display:inline-block;width:24px;border-top:4px solid;border-radius:2px}.legend-quarter{border-color:#2463eb}.legend-eighth{border-color:#0f8a72}.legend-sixteenth{border-color:#d97706}\n    .ruler-shell{padding:34px 20px 18px;position:relative;overflow:hidden}\n    #ruler{width:100%;height:260px;display:block;touch-action:none;user-select:none}\n    .hint{text-align:center;color:var(--muted);font-weight:650;margin:0 0 18px;min-height:24px;padding:0 14px}\n    .learn-note{display:none;width:min(660px,calc(100% - 28px));margin:-4px auto 18px;padding:12px 16px;border-radius:12px;background:#e8f6fb;border:1px solid #b9ddea;color:#24526c;text-align:center;font-weight:750}\n    .learn-note.show{display:block}\n    .type-row{display:none;align-items:center;justify-content:center;gap:10px;margin:0 auto 22px;padding:0 16px}\n    .type-row.show{display:flex}\n    #answer{width:min(320px,70vw);height:52px;border:2px solid var(--line);border-radius:12px;padding:0 16px;font-size:1.2rem;font-weight:750;color:var(--ink);background:white}\n    #answer:focus{outline:3px solid rgba(22,117,169,.2);border-color:var(--blue)}\n    .primary{height:52px;border:0;border-radius:12px;background:var(--blue);color:white;padding:0 22px;font-weight:850;box-shadow:0 6px 16px rgba(22,117,169,.22)}\n    .primary:hover{background:#0e668f}\n    .stats{display:grid;grid-template-columns:repeat(4,1fr);background:#f7fafb;border-top:1px solid var(--line)}\n    .stat{padding:18px 22px;text-align:center;border-right:1px solid var(--line)}\n    .stat:last-child{border:0}\n    .stat-label{display:block;color:var(--muted);font-size:.75rem;text-transform:uppercase;letter-spacing:.1em;font-weight:850;margin-bottom:5px}\n    .stat-value{font-size:1.45rem;font-weight:900;font-variant-numeric:tabular-nums}\n    .timer-track{height:7px;background:#dfe8ec;border-radius:5px;overflow:hidden;margin:7px auto 0;max-width:120px}\n    #timerBar{height:100%;width:100%;background:var(--green);transition:width .08s linear,background .2s}\n    .strikes{display:flex;justify-content:center;gap:7px;margin-top:5px}\n    .strike{width:12px;height:12px;border:2px solid #c6d2d9;border-radius:50%}\n    .strike.on{border-color:var(--red);background:var(--red)}\n    .controls{display:flex;justify-content:center;gap:10px}\n    .start{min-width:190px;height:54px;border:0;border-radius:13px;background:var(--yellow);color:#352808;font-weight:900;font-size:1.05rem;box-shadow:0 7px 0 #dca62e}\n    .start:active{transform:translateY(4px);box-shadow:0 3px 0 #dca62e}\n    .secondary{height:54px;border:1px solid #bfcfd7;border-radius:13px;background:#fff;color:var(--navy);padding:0 18px;font-weight:800}\n    .footer-note{text-align:center;color:#718495;font-size:.84rem;margin-top:4px}\n    dialog{border:0;border-radius:20px;padding:0;width:min(620px,calc(100vw - 28px));color:var(--ink);box-shadow:0 28px 90px rgba(4,25,44,.32)}\n    dialog::backdrop{background:rgba(6,28,45,.62);backdrop-filter:blur(3px)}\n    .modal-head{display:flex;align-items:center;justify-content:space-between;padding:22px 24px 16px;border-bottom:1px solid var(--line)}\n    .modal-head h2{margin:0;font-size:1.4rem}\n    .close{border:0;background:#edf3f6;color:var(--ink);width:38px;height:38px;border-radius:10px;font-size:1.4rem}\n    .settings-grid{padding:22px 24px;display:grid;grid-template-columns:1fr 1fr;gap:18px}\n    label{display:grid;gap:7px;font-weight:800;font-size:.88rem;color:#334e68}\n    select,input[type=&quot;text&quot;]{height:46px;border:1px solid #bfcfd7;border-radius:10px;background:white;padding:0 12px;color:var(--ink)}\n    .span-2{grid-column:1\/-1}.teacher-tools{grid-column:1\/-1;background:#f1f7f9;border:1px solid var(--line);border-radius:13px;padding:14px;display:flex;align-items:center;justify-content:space-between;gap:14px}.teacher-tools p{margin:0;color:var(--muted);font-size:.84rem;line-height:1.35}.compact{height:42px;white-space:nowrap}\n    .modal-foot{display:flex;justify-content:flex-end;gap:10px;padding:16px 24px 24px}\n    .result-overlay{position:fixed;inset:0;z-index:20;display:none;place-items:center;background:rgba(7,28,45,.62);padding:16px}\n    .result-overlay.show{display:grid}\n    .result-card{background:#fff;border-radius:22px;padding:30px;width:min(520px,100%);text-align:center;box-shadow:var(--shadow)}\n    .result-card h2{font-size:2rem;margin:0 0 8px}.result-card p{color:var(--muted);font-size:1.05rem;margin:0 0 18px}.result-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.result-actions .primary{width:100%}.mastery-badge{display:inline-block;border-radius:999px;padding:8px 14px;background:#dff5ed;color:#126b57;font-weight:900;margin:4px 0 16px}.mastery-badge.not-yet{background:#fff1d2;color:#855d08}.result-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:13px;overflow:hidden;margin:0 0 20px}.result-item{background:#f8fbfc;padding:12px}.result-item span{display:block;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:850}.result-item strong{display:block;margin-top:3px;font-size:1.15rem}.result-name{font-weight:850!important;color:var(--ink)!important;margin-bottom:6px!important}\n    .toast{position:fixed;left:50%;top:90px;transform:translate(-50%,-20px);opacity:0;pointer-events:none;padding:12px 18px;border-radius:999px;color:#fff;font-weight:850;z-index:30;transition:.22s;background:var(--green);box-shadow:0 10px 30px rgba(0,0,0,.18)}\n    .toast.show{opacity:1;transform:translate(-50%,0)}.toast.bad{background:var(--red)}\n    @media(max-width:760px){header{height:62px}.settings-btn span{display:none}.session-picker{width:100%}.session-btn{min-width:0;padding:10px 5px;font-size:.83rem}.session-btn small{display:none}.challenge-levels{width:100%;justify-content:center;flex-wrap:wrap}.challenge-label{width:100%;text-align:center}.difficulty-note{width:100%;max-width:none;text-align:center}.topline{grid-template-columns:1fr auto}.prompt{grid-column:1\/-1;grid-row:1}.mode-pill{grid-row:2}.best{grid-row:2}.ruler-shell{padding:24px 6px 12px}#ruler{height:220px}.stats{grid-template-columns:1fr 1fr}.stat:nth-child(2){border-right:0}.stat:nth-child(-n+2){border-bottom:1px solid var(--line)}.settings-grid{grid-template-columns:1fr}.span-2,.teacher-tools{grid-column:auto}.teacher-tools{align-items:stretch;flex-direction:column}.type-row{flex-direction:column}.type-row .primary,#answer{width:100%}.result-actions{grid-template-columns:1fr}}\n    @media print{body{background:white}.app,.toast{display:none!important}.result-overlay{position:static;display:block!important;background:white;padding:0}.result-card{box-shadow:none;border:2px solid #12304a;width:100%;max-width:720px;margin:20px auto}.result-actions{display:none}.result-card:after{content:&quot;Ruler Lab \u2022 CTE Makes School Matter&quot;;display:block;margin-top:24px;color:#62758a;font-size:.82rem}}\n    @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=&quot;app&quot;&gt;\n    &lt;header&gt;\n      &lt;div class=&quot;brand&quot;&gt;&lt;span class=&quot;brand-icon&quot; aria-hidden=&quot;true&quot;&gt;&lt;\/span&gt;Ruler Lab &lt;span style=&quot;font-weight:600;opacity:.65&quot;&gt;| Woodshop&lt;\/span&gt;&lt;\/div&gt;\n      &lt;div class=&quot;header-actions&quot;&gt;\n        &lt;button class=&quot;icon-btn&quot; id=&quot;fullscreen&quot; aria-label=&quot;Enter full screen&quot; title=&quot;Full screen&quot;&gt;\u26f6&lt;\/button&gt;\n        &lt;button class=&quot;settings-btn&quot; id=&quot;openSettings&quot; aria-haspopup=&quot;dialog&quot;&gt;\u2699 &lt;span&gt;Settings&lt;\/span&gt;&lt;\/button&gt;\n      &lt;\/div&gt;\n    &lt;\/header&gt;\n    &lt;main&gt;\n      &lt;nav class=&quot;session-picker&quot; aria-label=&quot;Learning mode&quot;&gt;\n        &lt;button class=&quot;session-btn&quot; data-session=&quot;learn&quot; aria-selected=&quot;false&quot;&gt;Learn&lt;small&gt;Guided help&lt;\/small&gt;&lt;\/button&gt;\n        &lt;button class=&quot;session-btn&quot; data-session=&quot;practice&quot; aria-selected=&quot;true&quot;&gt;Practice&lt;small&gt;Build accuracy&lt;\/small&gt;&lt;\/button&gt;\n        &lt;button class=&quot;session-btn&quot; data-session=&quot;challenge&quot; aria-selected=&quot;false&quot;&gt;Shop Challenge&lt;small&gt;20 questions&lt;\/small&gt;&lt;\/button&gt;\n      &lt;\/nav&gt;\n      &lt;div class=&quot;challenge-levels&quot; id=&quot;challengeLevels&quot; hidden&gt;\n        &lt;span class=&quot;challenge-label&quot;&gt;Difficulty&lt;\/span&gt;\n        &lt;button class=&quot;level-btn&quot; data-level=&quot;easy&quot; aria-selected=&quot;false&quot;&gt;Easy&lt;\/button&gt;\n        &lt;button class=&quot;level-btn&quot; data-level=&quot;medium&quot; aria-selected=&quot;true&quot;&gt;Medium&lt;\/button&gt;\n        &lt;button class=&quot;level-btn&quot; data-level=&quot;hard&quot; aria-selected=&quot;false&quot;&gt;Hard&lt;\/button&gt;\n        &lt;span class=&quot;difficulty-note&quot; id=&quot;difficultyNote&quot;&gt;Sixteenths and one-step shop math&lt;\/span&gt;\n      &lt;\/div&gt;\n      &lt;div class=&quot;topline&quot;&gt;\n        &lt;div class=&quot;mode-pill&quot; id=&quot;modeLabel&quot;&gt;Find the mark&lt;\/div&gt;\n        &lt;div class=&quot;prompt&quot; aria-live=&quot;polite&quot;&gt;\n          &lt;p class=&quot;eyebrow&quot; id=&quot;promptLabel&quot;&gt;Ready to measure?&lt;\/p&gt;\n          &lt;p id=&quot;target&quot;&gt;Press Start&lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;best&quot;&gt;Session best&lt;strong id=&quot;best&quot;&gt;0&lt;\/strong&gt;&lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;div class=&quot;controls&quot;&gt;\n        &lt;button class=&quot;start&quot; id=&quot;start&quot;&gt;Start game&lt;\/button&gt;\n        &lt;button class=&quot;secondary&quot; id=&quot;newQuestion&quot; hidden&gt;Skip&lt;\/button&gt;\n      &lt;\/div&gt;\n      &lt;section class=&quot;game-card&quot; aria-label=&quot;Ruler game&quot;&gt;\n        &lt;div class=&quot;ruler-shell&quot;&gt;\n          &lt;div class=&quot;ruler-legend&quot; id=&quot;rulerLegend&quot; aria-label=&quot;Ruler mark color key&quot;&gt;&lt;\/div&gt;\n          &lt;canvas id=&quot;ruler&quot; tabindex=&quot;0&quot; role=&quot;slider&quot; aria-label=&quot;Interactive inch ruler. Use arrow keys and Enter, or move and click or tap, to choose a measurement.&quot; aria-valuemin=&quot;0&quot; aria-valuemax=&quot;64&quot; aria-valuenow=&quot;0&quot;&gt;&lt;\/canvas&gt;\n        &lt;\/div&gt;\n        &lt;p class=&quot;hint&quot; id=&quot;hint&quot;&gt;Choose your settings, then press Start.&lt;\/p&gt;\n        &lt;div class=&quot;learn-note&quot; id=&quot;learnNote&quot;&gt;&lt;\/div&gt;\n        &lt;div class=&quot;type-row&quot; id=&quot;typeRow&quot;&gt;\n          &lt;input id=&quot;answer&quot; type=&quot;text&quot; inputmode=&quot;text&quot; autocomplete=&quot;off&quot; placeholder='Example: 2 3\/8 or 2.375' aria-label=&quot;Measurement answer&quot;&gt;\n          &lt;button class=&quot;primary&quot; id=&quot;submit&quot;&gt;Check answer&lt;\/button&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;stats&quot;&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot;&gt;Score&lt;\/span&gt;&lt;span class=&quot;stat-value&quot; id=&quot;score&quot;&gt;0&lt;\/span&gt;&lt;\/div&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot;&gt;Time&lt;\/span&gt;&lt;span class=&quot;stat-value&quot; id=&quot;time&quot;&gt;\u2014&lt;\/span&gt;&lt;div class=&quot;timer-track&quot;&gt;&lt;div id=&quot;timerBar&quot;&gt;&lt;\/div&gt;&lt;\/div&gt;&lt;\/div&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot; id=&quot;levelLabel&quot;&gt;Level&lt;\/span&gt;&lt;span class=&quot;stat-value&quot; id=&quot;level&quot;&gt;1&lt;\/span&gt;&lt;\/div&gt;\n          &lt;div class=&quot;stat&quot;&gt;&lt;span class=&quot;stat-label&quot; id=&quot;strikeLabel&quot;&gt;Strikes&lt;\/span&gt;&lt;div class=&quot;strikes&quot; id=&quot;strikes&quot;&gt;&lt;i class=&quot;strike&quot;&gt;&lt;\/i&gt;&lt;i class=&quot;strike&quot;&gt;&lt;\/i&gt;&lt;i class=&quot;strike&quot;&gt;&lt;\/i&gt;&lt;\/div&gt;&lt;span class=&quot;stat-value&quot; id=&quot;mistakeCount&quot; hidden&gt;0&lt;\/span&gt;&lt;\/div&gt;\n        &lt;\/div&gt;\n      &lt;\/section&gt;\n      &lt;p class=&quot;footer-note&quot;&gt;Ad-free \u2022 No sign-in \u2022 CTE Makes School Matter&lt;\/p&gt;\n    &lt;\/main&gt;\n  &lt;\/div&gt;\n\n  &lt;dialog id=&quot;settingsDialog&quot;&gt;\n    &lt;form method=&quot;dialog&quot; id=&quot;settingsForm&quot;&gt;\n      &lt;div class=&quot;modal-head&quot;&gt;&lt;h2&gt;Game settings&lt;\/h2&gt;&lt;button class=&quot;close&quot; value=&quot;cancel&quot; aria-label=&quot;Close settings&quot;&gt;\u00d7&lt;\/button&gt;&lt;\/div&gt;\n      &lt;div class=&quot;settings-grid&quot;&gt;\n        &lt;label class=&quot;span-2&quot;&gt;Student name &lt;input id=&quot;studentName&quot; type=&quot;text&quot; maxlength=&quot;40&quot; placeholder=&quot;Optional \u2014 appears on results&quot;&gt;&lt;\/label&gt;\n        &lt;label&gt;Game mode&lt;select id=&quot;mode&quot;&gt;&lt;option value=&quot;find&quot;&gt;Find the mark&lt;\/option&gt;&lt;option value=&quot;type&quot;&gt;Read and type&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Question precision&lt;select id=&quot;questions&quot;&gt;&lt;option value=&quot;1&quot;&gt;Whole inches&lt;\/option&gt;&lt;option value=&quot;2&quot;&gt;Halves&lt;\/option&gt;&lt;option value=&quot;4&quot;&gt;Quarters&lt;\/option&gt;&lt;option value=&quot;8&quot;&gt;Eighths&lt;\/option&gt;&lt;option value=&quot;16&quot; selected&gt;Sixteenths&lt;\/option&gt;&lt;option value=&quot;32&quot;&gt;Thirty-seconds&lt;\/option&gt;&lt;option value=&quot;64&quot;&gt;Sixty-fourths&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Ruler marks&lt;select id=&quot;marks&quot;&gt;&lt;option value=&quot;2&quot;&gt;Halves&lt;\/option&gt;&lt;option value=&quot;4&quot;&gt;Quarters&lt;\/option&gt;&lt;option value=&quot;8&quot;&gt;Eighths&lt;\/option&gt;&lt;option value=&quot;16&quot; selected&gt;Sixteenths&lt;\/option&gt;&lt;option value=&quot;32&quot;&gt;Thirty-seconds&lt;\/option&gt;&lt;option value=&quot;64&quot;&gt;Sixty-fourths&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Ruler length&lt;select id=&quot;length&quot;&gt;&lt;option value=&quot;auto&quot;&gt;Automatic&lt;\/option&gt;&lt;option value=&quot;1&quot;&gt;1 inch&lt;\/option&gt;&lt;option value=&quot;2&quot;&gt;2 inches&lt;\/option&gt;&lt;option value=&quot;3&quot;&gt;3 inches&lt;\/option&gt;&lt;option value=&quot;4&quot; selected&gt;4 inches&lt;\/option&gt;&lt;option value=&quot;6&quot;&gt;6 inches&lt;\/option&gt;&lt;option value=&quot;8&quot;&gt;8 inches&lt;\/option&gt;&lt;option value=&quot;10&quot;&gt;10 inches&lt;\/option&gt;&lt;option value=&quot;12&quot;&gt;12 inches&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Answer notation&lt;select id=&quot;notation&quot;&gt;&lt;option value=&quot;fraction&quot;&gt;Fractions&lt;\/option&gt;&lt;option value=&quot;decimal&quot;&gt;Decimals&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Fraction style&lt;select id=&quot;fractionStyle&quot;&gt;&lt;option value=&quot;both&quot;&gt;Mixed practice&lt;\/option&gt;&lt;option value=&quot;simplified&quot;&gt;Simplified&lt;\/option&gt;&lt;option value=&quot;unsimplified&quot;&gt;Unsimplified&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Timer&lt;select id=&quot;timer&quot;&gt;&lt;option value=&quot;on&quot;&gt;On&lt;\/option&gt;&lt;option value=&quot;off&quot;&gt;Off&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Sounds&lt;select id=&quot;sound&quot;&gt;&lt;option value=&quot;on&quot;&gt;On&lt;\/option&gt;&lt;option value=&quot;off&quot;&gt;Off&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;label&gt;Measure twice&lt;select id=&quot;measureTwice&quot;&gt;&lt;option value=&quot;off&quot;&gt;Off&lt;\/option&gt;&lt;option value=&quot;on&quot;&gt;On&lt;\/option&gt;&lt;\/select&gt;&lt;\/label&gt;\n        &lt;div class=&quot;teacher-tools&quot;&gt;&lt;p&gt;&lt;strong&gt;Teacher preset link&lt;\/strong&gt;&lt;br&gt;Opens with these settings already selected.&lt;\/p&gt;&lt;button type=&quot;button&quot; class=&quot;secondary compact&quot; id=&quot;copyPreset&quot;&gt;Copy link&lt;\/button&gt;&lt;\/div&gt;\n      &lt;\/div&gt;\n      &lt;div class=&quot;modal-foot&quot;&gt;&lt;button class=&quot;secondary&quot; value=&quot;cancel&quot;&gt;Cancel&lt;\/button&gt;&lt;button class=&quot;primary&quot; id=&quot;saveSettings&quot; value=&quot;default&quot;&gt;Save settings&lt;\/button&gt;&lt;\/div&gt;\n    &lt;\/form&gt;\n  &lt;\/dialog&gt;\n  &lt;div class=&quot;result-overlay&quot; id=&quot;gameOver&quot;&gt;&lt;div class=&quot;result-card&quot;&gt;\n    &lt;p class=&quot;eyebrow&quot; id=&quot;resultEyebrow&quot;&gt;Session complete&lt;\/p&gt;&lt;h2 id=&quot;finalScore&quot;&gt;Score: 0&lt;\/h2&gt;&lt;p class=&quot;result-name&quot; id=&quot;resultName&quot;&gt;&lt;\/p&gt;&lt;div class=&quot;mastery-badge&quot; id=&quot;masteryBadge&quot;&gt;Keep practicing&lt;\/div&gt;\n    &lt;div class=&quot;result-grid&quot;&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Accuracy&lt;\/span&gt;&lt;strong id=&quot;resultAccuracy&quot;&gt;0%&lt;\/strong&gt;&lt;\/div&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Correct&lt;\/span&gt;&lt;strong id=&quot;resultCorrect&quot;&gt;0&lt;\/strong&gt;&lt;\/div&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Precision&lt;\/span&gt;&lt;strong id=&quot;resultPrecision&quot;&gt;1\/16\u2033&lt;\/strong&gt;&lt;\/div&gt;&lt;div class=&quot;result-item&quot;&gt;&lt;span&gt;Date&lt;\/span&gt;&lt;strong id=&quot;resultDate&quot;&gt;\u2014&lt;\/strong&gt;&lt;\/div&gt;&lt;\/div&gt;\n    &lt;p id=&quot;finalMessage&quot;&gt;Nice work. Every mark gets easier with practice.&lt;\/p&gt;&lt;div class=&quot;result-actions&quot;&gt;&lt;button class=&quot;secondary&quot; id=&quot;printResults&quot;&gt;Print \/ Save PDF&lt;\/button&gt;&lt;button class=&quot;primary&quot; id=&quot;playAgain&quot;&gt;Try again&lt;\/button&gt;&lt;\/div&gt;\n  &lt;\/div&gt;&lt;\/div&gt;\n  &lt;div class=&quot;toast&quot; id=&quot;toast&quot; role=&quot;status&quot; aria-live=&quot;polite&quot;&gt;&lt;\/div&gt;\n\n  &lt;script&gt;\n  (()=&gt;{\n    const $=s=&gt;document.querySelector(s), canvas=$('#ruler'), ctx=canvas.getContext('2d');\n    const els={target:$('#target'),prompt:$('#promptLabel'),hint:$('#hint'),note:$('#learnNote'),score:$('#score'),level:$('#level'),levelLabel:$('#levelLabel'),time:$('#time'),timerBar:$('#timerBar'),strikes:$('#strikes'),mistakes:$('#mistakeCount'),strikeLabel:$('#strikeLabel'),start:$('#start'),skip:$('#newQuestion'),typeRow:$('#typeRow'),answer:$('#answer'),modeLabel:$('#modeLabel'),toast:$('#toast'),best:$('#best')};\n    const inputs=['mode','questions','marks','length','notation','fractionStyle','timer','sound','measureTwice'];\n    const defaults={session:'practice',challengeLevel:'medium',mode:'find',questions:'16',marks:'16',length:'4',notation:'fraction',fractionStyle:'both',timer:'on',sound:'on',measureTwice:'off',studentName:''};\n    let settings={...defaults,...JSON.parse(localStorage.getItem('rulerLabSettings')||'{}')};\n    const preset=new URLSearchParams(location.search);['session','challengeLevel',...inputs].forEach(k=&gt;{if(preset.has(k))settings[k]=preset.get(k)});if(+settings.questions&gt;+settings.marks)settings.marks=settings.questions;\n    let state={playing:false,score:0,correct:0,answered:0,strikes:0,level:1,answerUnits:0,answerDen:+settings.questions,hoverUnits:null,shownUnits:null,firstMeasure:null,questionKind:'find',expectsFind:true,startTime:0,duration:10,raf:0,locked:false};\n    let geom={x:0,y:0,w:0,h:0,inch:0,length:4};\n    inputs.forEach(id=&gt;$('#'+id).value=settings[id]??defaults[id]);$('#studentName').value=settings.studentName||'';\n    function gcd(a,b){while(b)[a,b]=[b,a%b];return a||1}\n    function autoLength(){const width=canvas.clientWidth;return width&lt;520?2:width&lt;820?3:width&lt;1120?4:6}\n    function rulerLength(){return settings.length==='auto'?autoLength():+settings.length}\n    function challengeDen(){return settings.challengeLevel==='easy'?8:16}\n    function rulerMarks(){return settings.session==='challenge'?16:+settings.marks}\n    function format(units,denom=+settings.questions,forceStyle=settings.fractionStyle){\n      const value=units\/denom;if(settings.notation==='decimal')return value.toFixed(4).replace(\/0+$\/,'').replace(\/\\.$\/,'')+'\u2033';\n      const whole=Math.floor(units\/denom),n=units%denom;if(!n)return whole+'\u2033';let num=n,d=denom;\n      const style=forceStyle==='both'?(Math.random()&lt;.5?'simplified':'unsimplified'):forceStyle;if(style==='simplified'){const g=gcd(num,d);num\/=g;d\/=g}\n      return(whole?whole+' ':'')+num+'\/'+d+'\u2033';\n    }\n    function precisionName(){if(settings.session==='challenge')return settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1)+' \u00b7 1\/'+challengeDen()+'\u2033';return({1:'Whole',2:'1\/2\u2033',4:'1\/4\u2033',8:'1\/8\u2033',16:'1\/16\u2033',32:'1\/32\u2033',64:'1\/64\u2033'})[settings.questions]}\n    function unitsAtMarks(units,den=state.answerDen){return Math.round(units*rulerMarks()\/den)}\n    function tickFamily(frac,marks){if(frac===0||frac===marks\/2)return'major';const pos=frac\/marks;if(Number.isInteger(pos*4))return'quarter';if(Number.isInteger(pos*8))return'eighth';if(Number.isInteger(pos*16))return'sixteenth';return'fine'}\n    function tickColor(family){const colors={major:'#12304a',quarter:'#2463eb',eighth:'#0f8a72',sixteenth:'#d97706',fine:'#8797a4'};return settings.session==='learn'?colors[family]:'#12304a'}\n    function updateLegend(){const marks=rulerMarks(),items=[];if(settings.session==='learn'&amp;&amp;marks&gt;=4)items.push(['quarter','\u00bc-inch']);if(settings.session==='learn'&amp;&amp;marks&gt;=8)items.push(['eighth','\u215b-inch']);if(settings.session==='learn'&amp;&amp;marks&gt;=16)items.push(['sixteenth','\u00b9\u2044\u2081\u2086-inch']);const legend=$('#rulerLegend');legend.hidden=!items.length;legend.innerHTML=items.map(([c,t])=&gt;'&lt;span class=&quot;legend-item&quot;&gt;&lt;i class=&quot;legend-line legend-'+c+'&quot;&gt;&lt;\/i&gt;'+t+'&lt;\/span&gt;').join('')}\n    function draw(){\n      const dpr=Math.min(devicePixelRatio||1,2),rect=canvas.getBoundingClientRect();\n      if(canvas.width!==Math.round(rect.width*dpr)||canvas.height!==Math.round(rect.height*dpr)){canvas.width=Math.round(rect.width*dpr);canvas.height=Math.round(rect.height*dpr)}\n      ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,rect.width,rect.height);const len=rulerLength(),pad=Math.max(20,rect.width*.025),top=26,base=rect.height-35,usable=rect.width-pad*2;geom={x:pad,y:top,w:usable,h:base-top,inch:usable\/len,length:len};\n      ctx.fillStyle='#fff9df';ctx.strokeStyle='#12304a';ctx.lineWidth=2.5;roundRect(ctx,pad,top,usable,base-top,8);ctx.fill();ctx.stroke();const marks=rulerMarks(),total=len*marks;\n      for(let i=0;i&lt;=total;i++){const x=pad+i\/total*usable,frac=i%marks,family=tickFamily(frac,marks);let tick;if(i===0||i===total||frac===0)tick=78;else{let pow=0,n=frac;while(n%2===0){pow++;n\/=2}tick=Math.min(20+pow*12,64)}ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,top+tick);ctx.strokeStyle=tickColor(family);ctx.lineWidth=family==='major'?2.8:2.2;ctx.stroke();if(frac===0&amp;&amp;i&gt;0){ctx.fillStyle='#12304a';ctx.font='800 25px Inter,system-ui';ctx.textAlign='center';ctx.fillText(String(i\/marks),x,top+104)}}\n      const active=state.playing&amp;&amp;!state.locked&amp;&amp;state.expectsFind?state.hoverUnits:null;if(active!==null){const x=pad+active\/(marks*len)*usable;ctx.fillStyle='rgba(22,134,111,.22)';ctx.fillRect(Math.max(pad,x-geom.inch\/marks\/2),top+2,Math.max(4,geom.inch\/marks),base-top-4);ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#16866f';ctx.lineWidth=4;ctx.stroke();canvas.setAttribute('aria-valuenow',String(active));canvas.setAttribute('aria-valuetext',format(active,marks,'simplified'))}\n      canvas.setAttribute('aria-valuemax',String(marks*len));if(state.shownUnits!==null){const x=pad+state.shownUnits\/(marks*len)*usable;ctx.beginPath();ctx.moveTo(x,top-5);ctx.lineTo(x,base+2);ctx.strokeStyle='#d44949';ctx.lineWidth=4;ctx.stroke();ctx.fillStyle='#d44949';ctx.beginPath();ctx.moveTo(x-8,top-10);ctx.lineTo(x+8,top-10);ctx.lineTo(x,top+1);ctx.fill()}\n      if(state.questionKind==='read'&amp;&amp;state.playing){const m=unitsAtMarks(state.answerUnits),x=pad+m\/(marks*len)*usable;ctx.fillStyle='#a11d48';ctx.beginPath();ctx.moveTo(x-11,top-15);ctx.lineTo(x+11,top-15);ctx.lineTo(x,top+1);ctx.fill();ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#a11d48';ctx.lineWidth=3;ctx.stroke()}updateLegend()\n    }\n    function roundRect(c,x,y,w,h,r){c.beginPath();c.roundRect?c.roundRect(x,y,w,h,r):c.rect(x,y,w,h)}\n    function randomUnits(max,avoid=0){let u;do{u=Math.max(1,Math.floor(Math.random()*max))}while(u===avoid);return u}\n    function learningHelp(units,den){const whole=Math.floor(units\/den),n=units%den;if(!n)return'Find the long line for '+whole+' whole inch'+(whole===1?'':'es')+'.';const g=gcd(n,den),sn=n\/g,sd=den\/g;return'Break it down: '+(whole?whole+' whole inch'+(whole===1?'':'es')+' plus ':'')+sn+' of '+sd+' equal parts.'}\n    function showType(on){els.typeRow.classList.toggle('show',on);if(on)setTimeout(()=&gt;els.answer.focus(),60)}\n    function newQuestion(){\n      state.locked=false;state.shownUnits=null;state.hoverUnits=null;state.firstMeasure=null;els.answer.value='';els.note.classList.remove('show');const den=settings.session==='challenge'?challengeDen():+settings.questions,len=rulerLength();state.answerDen=den;\n      let kind=settings.mode==='find'?'find':'read';if(settings.session==='challenge'){const sets={easy:['find','read','cutlist','find','midpoint'],medium:['find','read','cutlist','midpoint','remaining'],hard:['read','cutlist','remaining','midpoint','multiCut','difference']};const kinds=sets[settings.challengeLevel];kind=kinds[state.answered%kinds.length]}\n      state.questionKind=kind;state.expectsFind=kind==='find'||kind==='cutlist';\n      if(kind==='find'||kind==='read'||kind==='cutlist'){state.answerUnits=randomUnits(len*den,state.answerUnits)}\n      if(kind==='find'){els.prompt.textContent='Click the measurement';els.target.textContent=format(state.answerUnits,den);els.hint.textContent=settings.measureTwice==='on'?'Measure it twice. Both selections must match.':'Move over the ruler, then click or tap the correct mark.';showType(false)}\n      if(kind==='read'){els.prompt.textContent='Read the marked line';els.target.innerHTML='&lt;small&gt;Type your answer below&lt;\/small&gt;';els.hint.textContent='Enter a fraction or decimal measurement.';showType(true)}\n      if(kind==='cutlist'){const parts=['Side rail','Shelf support','Face-frame piece','Drawer spacer'];els.prompt.textContent='Cut list: '+parts[state.answered%parts.length];els.target.textContent=format(state.answerUnits,den,'simplified');els.hint.textContent='Select this cut length on the ruler.';showType(false)}\n      if(kind==='midpoint'){state.answerUnits=randomUnits(Math.max(2,Math.floor(6*den)));const boardUnits=state.answerUnits*2;els.prompt.textContent='Find the midpoint';els.target.textContent=format(boardUnits,den,'simplified')+' board';els.hint.textContent='What measurement marks the exact center?';showType(true)}\n      if(kind==='remaining'){const totalInches=6+Math.floor(Math.random()*7),totalUnits=totalInches*den,cutUnits=randomUnits(totalUnits-1);state.answerUnits=totalUnits-cutUnits;els.prompt.textContent='Board remaining';els.target.style.fontSize='clamp(1.55rem,3.4vw,3.2rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \u2212 '+format(cutUnits,den,'simplified');els.hint.textContent='Type the board length left after the cut.';showType(true)}else els.target.style.fontSize='';\n      if(kind==='multiCut'){const totalInches=10+Math.floor(Math.random()*3),totalUnits=totalInches*den,maxPiece=Math.floor((totalUnits-den)\/3),pieceUnits=den+Math.floor(Math.random()*Math.max(1,maxPiece-den));state.answerUnits=totalUnits-pieceUnits*2;els.prompt.textContent='Two pieces from one board';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \u2212 2 \u00d7 '+format(pieceUnits,den,'simplified');els.hint.textContent='Ignore saw kerf. How much stock remains?';showType(true)}\n      if(kind==='difference'){const planned=2*den+randomUnits(4*den),gap=randomUnits(den),actual=planned-gap;state.answerUnits=gap;els.prompt.textContent='Measurement difference';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(planned,den,'simplified')+' planned \u00b7 '+format(actual,den,'simplified')+' actual';els.hint.textContent='How much shorter is the actual piece?';showType(true)}\n      if(settings.session==='learn'){els.note.textContent=learningHelp(state.answerUnits,den);els.note.classList.add('show')}\n      setStatMode();startClock();draw();\n    }\n    function startGame(){cancelAnimationFrame(state.raf);state={...state,playing:true,score:0,correct:0,answered:0,strikes:0,level:1,locked:false,shownUnits:null,firstMeasure:null};els.score.textContent='0';els.start.textContent='Restart';els.skip.hidden=settings.session==='learn';$('#gameOver').classList.remove('show');updateStrikes();newQuestion()}\n    function startClock(){cancelAnimationFrame(state.raf);state.duration=state.expectsFind?Math.max(3,11-state.level):Math.max(6,20-(state.level-1)*2);if(settings.session==='challenge')state.duration+=settings.challengeLevel==='easy'?6:settings.challengeLevel==='hard'?-2:0;state.startTime=performance.now();if(settings.timer==='off'||settings.session==='learn'){els.time.textContent='\u221e';els.timerBar.style.width='100%';els.timerBar.style.background='#16866f';return}const tick=now=&gt;{if(!state.playing||state.locked)return;const left=Math.max(0,state.duration-(now-state.startTime)\/1000),pct=left\/state.duration*100;els.time.textContent=Math.ceil(left)+'s';els.timerBar.style.width=pct+'%';els.timerBar.style.background=pct&lt;25?'#d44949':pct&lt;55?'#e7ad29':'#16866f';if(left&lt;=0){check(null);return}state.raf=requestAnimationFrame(tick)};state.raf=requestAnimationFrame(tick)}\n    function parseAnswer(v){v=v.trim().replace(\/[\u2033&quot;]\/g,'');if(!v)return null;if(\/^\\d*\\.?\\d+$\/.test(v))return+v;const m=v.match(\/^(?:(\\d+)\\s+)?(\\d+)\\\/(\\d+)$\/);if(!m)return null;return+(m[1]||0)+(+m[2]\/+m[3])}\n    function check(selectedMark){\n      if(!state.playing||state.locked)return;if(state.expectsFind&amp;&amp;selectedMark!==null&amp;&amp;settings.measureTwice==='on'){if(state.firstMeasure===null){state.firstMeasure=selectedMark;els.hint.textContent='First measurement saved. Move away, then measure it again.';toast('First measurement saved',false);return}if(state.firstMeasure!==selectedMark){state.firstMeasure=null;els.hint.textContent='Those measurements did not match. Start again.';toast('Measurements did not match',true);return}}\n      state.locked=true;cancelAnimationFrame(state.raf);let ok=false;if(state.expectsFind)ok=selectedMark!==null&amp;&amp;selectedMark===unitsAtMarks(state.answerUnits);else{const v=parseAnswer(els.answer.value);ok=v!==null&amp;&amp;Math.abs(v-state.answerUnits\/state.answerDen)&lt;1e-7}\n      state.answered++;if(ok){state.correct++;state.score+=state.level*10;els.score.textContent=state.score;toast('Correct! +'+state.level*10,false);sound(true)}else{state.strikes++;const correct=format(state.answerUnits,state.answerDen,'simplified');toast(selectedMark===null?'Time\u2019s up \u2014 '+correct:'Correct answer: '+correct,true);sound(false);if(state.expectsFind)state.shownUnits=unitsAtMarks(state.answerUnits);draw()}\n      if(settings.session!=='challenge'&amp;&amp;state.correct&gt;0&amp;&amp;state.correct%10===0){state.level=Math.min(10,Math.floor(state.correct\/10)+1);els.level.textContent=state.level}\n      updateStrikes();if(settings.session==='learn'&amp;&amp;!ok)els.note.textContent='The correct mark is highlighted. '+learningHelp(state.answerUnits,state.answerDen);\n      const done=settings.session==='challenge'&amp;&amp;state.answered&gt;=20||settings.session==='practice'&amp;&amp;state.strikes&gt;=3;if(done)setTimeout(endGame,1100);else setTimeout(newQuestion,ok?700:1500)\n    }\n    function setStatMode(){const challenge=settings.session==='challenge';els.levelLabel.textContent=challenge?'Question':'Level';els.level.textContent=challenge?(Math.min(20,state.answered+1)+'\/20'):state.level;els.strikeLabel.textContent=challenge?'Missed':'Strikes';els.strikes.hidden=challenge;els.mistakes.hidden=!challenge;if(challenge)els.mistakes.textContent=state.strikes}\n    function updateStrikes(){[...els.strikes.children].forEach((x,i)=&gt;x.classList.toggle('on',i&lt;state.strikes));els.mistakes.textContent=state.strikes;setStatMode()}\n    function endGame(){state.playing=false;cancelAnimationFrame(state.raf);const total=Math.max(1,state.answered),accuracy=Math.round(state.correct\/total*100),mastered=settings.session==='challenge'&amp;&amp;accuracy&gt;=90,best=Math.max(state.score,+localStorage.getItem('rulerLabBest')||0);localStorage.setItem('rulerLabBest',best);els.best.textContent=best;$('#resultEyebrow').textContent=settings.session==='challenge'?'Shop challenge complete':'Practice complete';$('#finalScore').textContent=settings.session==='challenge'?(mastered?'Measurement mastered':'Keep building accuracy'):'Score: '+state.score;$('#resultName').textContent=settings.studentName||'Ruler Lab Student';$('#masteryBadge').textContent=settings.session==='challenge'?(mastered?'Mastered \u2022 90%+':'Not yet mastered \u2022 Goal: 90%'):'Practice session';$('#masteryBadge').classList.toggle('not-yet',!mastered);$('#resultAccuracy').textContent=accuracy+'%';$('#resultCorrect').textContent=state.correct+' \/ '+state.answered;$('#resultPrecision').textContent=precisionName();$('#resultDate').textContent=new Date().toLocaleDateString();$('#finalMessage').textContent=mastered?'Ready to apply this skill in the shop.':accuracy&gt;=75?'Very close\u2014review the marks you missed and try again.':'Slow down, count the spaces, and measure twice.';$('#gameOver').classList.add('show');els.start.textContent='Start '+sessionName();els.skip.hidden=true}\n    function sessionName(){return settings.session==='challenge'?'challenge':settings.session}\n    function updateSessionUI(){document.querySelectorAll('.session-btn').forEach(b=&gt;b.setAttribute('aria-selected',String(b.dataset.session===settings.session)));const challenge=settings.session==='challenge',notes={easy:'Eighth-inch questions on a full sixteenth-inch ruler',medium:'Sixteenths, midpoints, and one-step remaining length',hard:'Sixteenths, multi-cut stock, differences, and mixed calculations'};$('#challengeLevels').hidden=!challenge;document.querySelectorAll('.level-btn').forEach(b=&gt;b.setAttribute('aria-selected',String(b.dataset.level===settings.challengeLevel)));$('#difficultyNote').textContent=notes[settings.challengeLevel];els.modeLabel.textContent=settings.session==='learn'?'Color-guided learning':challenge?'Shop Challenge \u00b7 '+settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1):'Independent practice';els.start.textContent='Start '+sessionName();setStatMode();if(!state.playing){els.prompt.textContent=challenge?'20-question '+settings.challengeLevel+' challenge':settings.session==='learn'?'Learn each ruler mark':'Build speed and accuracy';els.target.textContent='Press Start';els.hint.textContent=challenge?notes[settings.challengeLevel]+'.':settings.session==='learn'?'Colors identify quarters, eighths, and sixteenths on the ruler.':'Practice on a standard ruler without color clues.';showType(false);draw()}}\n    let toastTimer;function toast(msg,bad){els.toast.textContent=msg;els.toast.className='toast show'+(bad?' bad':'');clearTimeout(toastTimer);toastTimer=setTimeout(()=&gt;els.toast.className='toast',1100)}\n    function sound(good){if(settings.sound!=='on')return;try{const ac=new(window.AudioContext||window.webkitAudioContext)(),o=ac.createOscillator(),g=ac.createGain();o.frequency.value=good?660:190;o.type=good?'sine':'triangle';g.gain.setValueAtTime(.08,ac.currentTime);g.gain.exponentialRampToValueAtTime(.001,ac.currentTime+.16);o.connect(g);g.connect(ac.destination);o.start();o.stop(ac.currentTime+.17)}catch(e){}}\n    function pointer(e){const rect=canvas.getBoundingClientRect(),x=Math.max(geom.x,Math.min(geom.x+geom.w,e.clientX-rect.left)),marks=rulerMarks();return Math.round((x-geom.x)\/geom.w*(marks*geom.length))}\n    canvas.addEventListener('pointermove',e=&gt;{if(state.playing&amp;&amp;!state.locked&amp;&amp;state.expectsFind){state.hoverUnits=pointer(e);draw()}});canvas.addEventListener('pointerleave',()=&gt;{if(state.expectsFind){state.hoverUnits=null;draw()}});canvas.addEventListener('pointerdown',e=&gt;{if(state.playing&amp;&amp;!state.locked&amp;&amp;state.expectsFind){e.preventDefault();const u=pointer(e);state.hoverUnits=u;draw();check(u)}});\n    canvas.addEventListener('keydown',e=&gt;{if(!state.playing||state.locked||!state.expectsFind)return;const max=rulerMarks()*rulerLength();if(e.key==='ArrowRight'||e.key==='ArrowUp'){e.preventDefault();state.hoverUnits=Math.min(max,(state.hoverUnits??0)+1);draw()}else if(e.key==='ArrowLeft'||e.key==='ArrowDown'){e.preventDefault();state.hoverUnits=Math.max(0,(state.hoverUnits??0)-1);draw()}else if(e.key==='Enter'||e.key===' '){e.preventDefault();check(state.hoverUnits??0)}});\n    $('#submit').onclick=()=&gt;check(0);els.answer.addEventListener('keydown',e=&gt;{if(e.key==='Enter')check(0)});els.start.onclick=startGame;$('#playAgain').onclick=startGame;$('#printResults').onclick=()=&gt;window.print();els.skip.onclick=()=&gt;check(null);\n    document.querySelectorAll('.session-btn').forEach(b=&gt;b.onclick=()=&gt;{settings.session=b.dataset.session;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\n    document.querySelectorAll('.level-btn').forEach(b=&gt;b.onclick=()=&gt;{settings.challengeLevel=b.dataset.level;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\n    $('#openSettings').onclick=()=&gt;$('#settingsDialog').showModal();$('#saveSettings').onclick=e=&gt;{e.preventDefault();inputs.forEach(id=&gt;settings[id]=$('#'+id).value);settings.studentName=$('#studentName').value.trim();if(+settings.questions&gt;+settings.marks){settings.marks=settings.questions;$('#marks').value=settings.marks;toast('Ruler marks adjusted to match questions',false)}localStorage.setItem('rulerLabSettings',JSON.stringify(settings));$('#settingsDialog').close();if(state.playing)startGame();else updateSessionUI()};\n    $('#copyPreset').onclick=async()=&gt;{inputs.forEach(id=&gt;settings[id]=$('#'+id).value);const url=new URL(location.href);url.search='';url.searchParams.set('session',settings.session);url.searchParams.set('challengeLevel',settings.challengeLevel);inputs.forEach(k=&gt;url.searchParams.set(k,settings[k]));try{await navigator.clipboard.writeText(url.href);toast('Teacher preset link copied',false)}catch(e){prompt('Copy this teacher preset link:',url.href)}};\n    $('#questions').onchange=()=&gt;{if(+$('#questions').value&gt;+$('#marks').value)$('#marks').value=$('#questions').value};$('#marks').onchange=()=&gt;{if(+$('#marks').value&lt;+$('#questions').value)$('#questions').value=$('#marks').value};$('#fullscreen').onclick=()=&gt;{if(!document.fullscreenElement)document.documentElement.requestFullscreen?.();else document.exitFullscreen?.()};window.addEventListener('resize',draw);\n    els.best.textContent=localStorage.getItem('rulerLabBest')||'0';updateSessionUI();\n    if(document.modelContext?.registerTool){const register=t=&gt;Promise.resolve(document.modelContext.registerTool(t)).catch(()=&gt;{});register({name:'start_ruler_game',title:'Start ruler game',description:'Start or restart the visible ruler learning, practice, or shop challenge session.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=&gt;{if(v?.session)settings.session=v.session;if(v?.challengeLevel)settings.challengeLevel=v.challengeLevel;updateSessionUI();startGame();return{status:'started',session:settings.session,challengeLevel:settings.challengeLevel,precision:settings.session==='challenge'?challengeDen():settings.questions}}});register({name:'configure_ruler_game',title:'Configure ruler game',description:'Change the visible ruler game settings.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']},mode:{enum:['find','type']},questions:{enum:['1','2','4','8','16','32','64']},marks:{enum:['2','4','8','16','32','64']},length:{enum:['auto','1','2','3','4','6','8','10','12']},notation:{enum:['fraction','decimal']},timer:{enum:['on','off']},measureTwice:{enum:['on','off']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=&gt;{settings={...settings,...v};if(+settings.questions&gt;+settings.marks)settings.marks=settings.questions;inputs.forEach(id=&gt;$('#'+id).value=settings[id]);localStorage.setItem('rulerLabSettings',JSON.stringify(settings));updateSessionUI();return{status:'configured',settings}}})}\n  })();\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<p><!--more--><br \/>\n<!-- {\"type\":\"layout\",\"children\":[{\"type\":\"section\",\"props\":{\"image_position\":\"center-center\",\"style\":\"default\",\"title_breakpoint\":\"xl\",\"title_position\":\"top-left\",\"title_rotation\":\"left\",\"vertical_align\":\"\",\"width\":\"default\"},\"children\":[{\"type\":\"row\",\"children\":[{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\"},\"children\":[{\"type\":\"headline\",\"props\":{\"content\":\"Teacher Resources\",\"title_element\":\"h1\"}}]}]},{\"type\":\"row\",\"children\":[{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\",\"width_medium\":\"1-2\"},\"children\":[{\"type\":\"headline\",\"props\":{\"content\":\"Teacher Forms\",\"title_element\":\"h3\"}},{\"type\":\"list\",\"props\":{\"column_breakpoint\":\"m\",\"image_align\":\"left\",\"image_svg_color\":\"emphasis\",\"image_vertical_align\":true,\"list_element\":\"ul\",\"list_horizontal_separator\":\", \",\"list_marker\":\"disc\",\"list_type\":\"vertical\",\"show_image\":true,\"show_link\":true},\"children\":[{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Canvas Safety Course<\\\/p>\",\"link\":\"https:\\\/\\\/washk12.instructure.com\\\/enroll\\\/8CK3FA\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>CTE Lab Safety Forms<\\\/p>\",\"link\":\"https:\\\/\\\/www.washk12.org\\\/cte\\\/wp-content\\\/uploads\\\/sites\\\/11\\\/2023\\\/03\\\/cte_lab_safety_checklist.pdf\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"CTSO Advisor Form\",\"link\":\"https:\\\/\\\/drive.google.com\\\/file\\\/d\\\/1nEPkzIqRb6Pg5dhi3OtUl1yRSlKOLKvs\\\/view?usp=sharing\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"Equipment Repair Form\",\"link\":\"https:\\\/\\\/help.washk12.org\\\/\"}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Proposal for Concurrent Enrollment Course Form<\\\/p>\",\"link\":\"https:\\\/\\\/www.washk12.org\\\/cte\\\/wp-content\\\/uploads\\\/sites\\\/11\\\/2023\\\/03\\\/proposal_concurrent_enrollment_form.pdf\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Utah ACTE Membership Form 2024-25<\\\/p>\",\"link\":\"https:\\\/\\\/drive.google.com\\\/file\\\/d\\\/1Mm2VNpDySDSVEU2jO4sIxazypBcdl7tZ\\\/view?usp=sharing\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"WCSD Cosmetology\\\/Esthetician Form\",\"link\":\"https:\\\/\\\/drive.google.com\\\/file\\\/d\\\/10fz4GV7WdmE2pn7OJjgzCy9HY5uYIoiL\\\/view?usp=sharing\"}}]}]},{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\",\"width_medium\":\"1-2\"},\"children\":[{\"type\":\"headline\",\"props\":{\"content\":\"Information\",\"title_element\":\"h3\"}},{\"type\":\"list\",\"props\":{\"column_breakpoint\":\"m\",\"image_align\":\"left\",\"image_svg_color\":\"emphasis\",\"image_vertical_align\":true,\"list_element\":\"ul\",\"list_horizontal_separator\":\", \",\"list_marker\":\"disc\",\"list_type\":\"vertical\",\"show_image\":true,\"show_link\":true},\"children\":[{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Approved Fee Schedule 2024-25<\\\/p>\",\"link\":\"https:\\\/\\\/docs.google.com\\\/document\\\/d\\\/1hlmjvZg2FNg1INR45WTQix8TUMhNXTGVt9deANyNk5Q\\\/edit?usp=sharing\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>CTE Guidebook<\\\/p>\",\"link\":\"https:\\\/\\\/docs.google.com\\\/document\\\/d\\\/1rK5gpVCfm79iRQmZ5KRKbM2XBbDNixnI\\\/edit?usp=sharing&ouid=102649544415761951444&rtpof=true&sd=true\"}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Overview of CTE Funding in WCSD<\\\/p>\",\"link\":\"https:\\\/\\\/www.washk12.org\\\/cte\\\/wp-content\\\/uploads\\\/sites\\\/11\\\/2023\\\/03\\\/2016_cte_funding_in_wcsd.pdf\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Time and Effort Reporting 2 CFR 200.430 (i)<\\\/p>\",\"link\":\"https:\\\/\\\/www.washk12.org\\\/cte\\\/wp-content\\\/uploads\\\/sites\\\/11\\\/2023\\\/03\\\/StandardsTimeEffortReporting.pdf\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"CTE: Teaching and Learning Resources\",\"link\":\"https:\\\/\\\/sites.google.com\\\/washk12.org\\\/cteteachinglearningresources\\\/career-technical-education\",\"link_target\":true}}]}]}],\"props\":{\"divider\":true,\"layout\":\"1-2,1-2\"}},{\"type\":\"row\",\"children\":[{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\"},\"children\":[{\"type\":\"divider\",\"props\":{\"divider_element\":\"hr\"}}]}]},{\"type\":\"row\",\"props\":{\"divider\":true,\"layout\":\"1-2,1-2\"},\"children\":[{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\",\"width_medium\":\"1-2\"},\"children\":[{\"type\":\"headline\",\"props\":{\"content\":\"Teacher Directory\",\"title_element\":\"h3\"}},{\"type\":\"list\",\"props\":{\"column_breakpoint\":\"m\",\"image_align\":\"left\",\"image_svg_color\":\"emphasis\",\"image_vertical_align\":true,\"list_element\":\"ul\",\"list_horizontal_separator\":\", \",\"list_marker\":\"disc\",\"list_type\":\"vertical\",\"show_image\":true,\"show_link\":true},\"children\":[{\"type\":\"list_item\",\"props\":{\"content\":\"CTE Team Leads 2025-26\",\"link\":\"https:\\\/\\\/docs.google.com\\\/presentation\\\/d\\\/1rhrXoIyU5JdY_IDxBHAWFtywntC2MF0x3HO7VUqXz14\\\/edit?usp=sharing\"}},{\"type\":\"list_item\",\"props\":{\"content\":\"CTE Teams 2025-26\",\"link\":\"https:\\\/\\\/docs.google.com\\\/spreadsheets\\\/d\\\/1fZgg5wpQPur1D_zTBwSWCNUDVqT1eK5Y\\\/edit?usp=sharing&ouid=102649544415761951444&rtpof=true&sd=true\"}}]}]},{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\",\"width_medium\":\"1-2\"},\"children\":[{\"type\":\"headline\",\"props\":{\"content\":\"CTE AT-A-Glance\",\"title_element\":\"h3\"}},{\"type\":\"list\",\"props\":{\"column_breakpoint\":\"m\",\"image_align\":\"left\",\"image_svg_color\":\"emphasis\",\"image_vertical_align\":true,\"list_element\":\"ul\",\"list_horizontal_separator\":\", \",\"list_marker\":\"disc\",\"list_type\":\"vertical\",\"show_image\":true,\"show_link\":true},\"children\":[{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>CTE WCSD<\\\/p>\",\"link\":\"https:\\\/\\\/drive.google.com\\\/file\\\/d\\\/1iKEHX0XIK1UJtDvpO_6e9-tEClG4ICse\\\/view?usp=share_link\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>CTE SW Region<\\\/p>\",\"link\":\"https:\\\/\\\/drive.google.com\\\/file\\\/d\\\/1lQq0TjRo45oxMsC4H5QWgxKqtxhER3V8\\\/view?usp=share_link\",\"link_target\":true}},{\"type\":\"list_item\",\"props\":{\"content\":\"\n\n<p>Utah CTE<\\\/p>\",\"link\":\"wp-content\\\/uploads\\\/sites\\\/11\\\/2025\\\/01\\\/Utah-CTE-At-A-Glance.pdf\"}}]}]}]}]},{\"type\":\"section\",\"props\":{\"image_position\":\"center-center\",\"style\":\"default\",\"title_breakpoint\":\"xl\",\"title_position\":\"top-left\",\"title_rotation\":\"left\",\"vertical_align\":\"middle\",\"width\":\"default\"},\"children\":[{\"type\":\"row\",\"children\":[{\"type\":\"column\",\"props\":{\"image_position\":\"center-center\",\"position_sticky_breakpoint\":\"m\"},\"children\":[{\"type\":\"code\",\"props\":{\"content\":\"<!doctype html>\\n<html lang=\\\"en\\\">\\n<head>\\n  <meta charset=\\\"utf-8\\\">\\n  <meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\\n  <meta name=\\\"theme-color\\\" content=\\\"#12304a\\\">\\n  <meta name=\\\"description\\\" content=\\\"An ad-free woodshop measurement game for learning to read an inch ruler and solve practical cut-list problems.\\\">\\n  <title>Ruler Lab \\u2014 Woodshop Measurement Practice<\\\/title>\\n  <link rel=\\\"icon\\\" type=\\\"image\\\/svg+xml\\\" href=\\\"data:image\\\/svg+xml,%3Csvg xmlns='http:\\\/\\\/www.w3.org\\\/2000\\\/svg' viewBox='0 0 64 64'%3E%3Crect x='5' y='13' width='54' height='38' rx='7' fill='%23f6c453'\\\/%3E%3Cpath d='M13 13v15m8-15v9m8-9v15m8-15v9m8-9v15m8-15v9' stroke='%2312304a' stroke-width='4'\\\/%3E%3C\\\/svg%3E\\\">\\n  \n\n<style>\\n    :root{--ink:#102a43;--muted:#62758a;--navy:#12304a;--blue:#1675a9;--sky:#e8f6fb;--paper:#fffdfa;--yellow:#f6c453;--green:#16866f;--red:#d44949;--line:#d6e1e8;--shadow:0 18px 50px rgba(18,48,74,.14)}\\n    *{box-sizing:border-box}\\n    html,body{min-height:100%;margin:0}\\n    body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,\\\"Segoe UI\\\",sans-serif;color:var(--ink);background:linear-gradient(180deg,#e9f7fb 0,#f7fbfc 52%,#edf4f7 100%)}\\n    button,select,input{font:inherit}\\n    button{cursor:pointer}\\n    .app{min-height:100vh;display:flex;flex-direction:column}\\n    header{height:72px;display:flex;align-items:center;justify-content:space-between;padding:0 clamp(18px,4vw,64px);background:var(--navy);color:white}\\n    .brand{display:flex;align-items:center;gap:12px;font-weight:850;font-size:1.25rem;letter-spacing:.01em}\\n    .brand-icon{width:42px;height:30px;border-radius:7px;background:var(--yellow);position:relative;box-shadow:inset 0 -3px rgba(0,0,0,.08)}\\n    .brand-icon:after{content:\\\"\\\";position:absolute;inset:0;background:repeating-linear-gradient(90deg,transparent 0 7px,var(--navy) 7px 9px,transparent 9px 14px);clip-path:polygon(0 0,100% 0,100% 58%,0 58%)}\\n    .header-actions{display:flex;gap:8px}\\n    .icon-btn,.settings-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.08);color:#fff;border-radius:10px;min-height:42px;padding:0 14px;font-weight:750}\\n    .icon-btn:hover,.settings-btn:hover{background:rgba(255,255,255,.16)}\\n    main{width:min(1500px,100%);margin:auto;padding:clamp(18px,3vw,40px) clamp(14px,4vw,58px) 36px;flex:1;display:flex;flex-direction:column;justify-content:center;gap:18px}\\n    .session-picker{align-self:center;display:grid;grid-template-columns:repeat(3,1fr);gap:5px;background:#dce9ee;border:1px solid #c8d9e1;padding:5px;border-radius:15px}\\n    .session-btn{border:0;background:transparent;color:#50687a;border-radius:10px;padding:11px 18px;font-weight:850;min-width:150px}\\n    .session-btn[aria-selected=\\\"true\\\"]{background:white;color:var(--navy);box-shadow:0 3px 12px rgba(18,48,74,.12)}\\n    .session-btn small{display:block;font-size:.7rem;font-weight:700;color:#718495;margin-top:2px}\\n    .challenge-levels{align-self:center;display:flex;align-items:center;gap:8px;padding:6px;background:#fff;border:1px solid var(--line);border-radius:13px;box-shadow:0 4px 16px rgba(18,48,74,.07)}\\n    .challenge-levels[hidden]{display:none}.challenge-label{font-size:.76rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:900;padding:0 7px}.level-btn{border:1px solid transparent;background:transparent;color:#536c7e;border-radius:9px;padding:8px 13px;font-weight:850}.level-btn[aria-selected=\\\"true\\\"]{background:var(--blue);color:white}.difficulty-note{font-size:.78rem;color:var(--muted);font-weight:700;padding:0 8px;max-width:280px}\\n    .topline{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:18px}\\n    .mode-pill{justify-self:start;display:inline-flex;align-items:center;gap:8px;background:#d9f3ed;color:#116653;padding:8px 12px;border-radius:999px;font-weight:800;font-size:.85rem;text-transform:uppercase;letter-spacing:.06em}\\n    .mode-pill:before{content:\\\"\\\";width:8px;height:8px;border-radius:50%;background:var(--green)}\\n    .prompt{text-align:center;min-width:min(430px,60vw)}\\n    .eyebrow{font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;font-size:.82rem;margin:0 0 4px}\\n    #target{font-size:clamp(2.1rem,5vw,4.8rem);line-height:1;font-weight:900;letter-spacing:-.045em;margin:0;color:var(--navy);font-variant-numeric:tabular-nums}\\n    #target small{font-size:.45em;letter-spacing:0;color:var(--muted);font-weight:800}\\n    .best{justify-self:end;text-align:right;color:var(--muted);font-weight:700}\\n    .best strong{display:block;color:var(--ink);font-size:1.15rem}\\n    .game-card{background:var(--paper);border:1px solid rgba(18,48,74,.12);border-radius:22px;box-shadow:var(--shadow);overflow:hidden}\\n    .ruler-legend{display:flex;align-items:center;justify-content:center;gap:18px;flex-wrap:wrap;margin:0 0 10px;color:#526b7d;font-size:.8rem;font-weight:800}\\n    .legend-item{display:inline-flex;align-items:center;gap:6px}.legend-line{display:inline-block;width:24px;border-top:4px solid;border-radius:2px}.legend-quarter{border-color:#2463eb}.legend-eighth{border-color:#0f8a72}.legend-sixteenth{border-color:#d97706}\\n    .ruler-shell{padding:34px 20px 18px;position:relative;overflow:hidden}\\n    #ruler{width:100%;height:260px;display:block;touch-action:none;user-select:none}\\n    .hint{text-align:center;color:var(--muted);font-weight:650;margin:0 0 18px;min-height:24px;padding:0 14px}\\n    .learn-note{display:none;width:min(660px,calc(100% - 28px));margin:-4px auto 18px;padding:12px 16px;border-radius:12px;background:#e8f6fb;border:1px solid #b9ddea;color:#24526c;text-align:center;font-weight:750}\\n    .learn-note.show{display:block}\\n    .type-row{display:none;align-items:center;justify-content:center;gap:10px;margin:0 auto 22px;padding:0 16px}\\n    .type-row.show{display:flex}\\n    #answer{width:min(320px,70vw);height:52px;border:2px solid var(--line);border-radius:12px;padding:0 16px;font-size:1.2rem;font-weight:750;color:var(--ink);background:white}\\n    #answer:focus{outline:3px solid rgba(22,117,169,.2);border-color:var(--blue)}\\n    .primary{height:52px;border:0;border-radius:12px;background:var(--blue);color:white;padding:0 22px;font-weight:850;box-shadow:0 6px 16px rgba(22,117,169,.22)}\\n    .primary:hover{background:#0e668f}\\n    .stats{display:grid;grid-template-columns:repeat(4,1fr);background:#f7fafb;border-top:1px solid var(--line)}\\n    .stat{padding:18px 22px;text-align:center;border-right:1px solid var(--line)}\\n    .stat:last-child{border:0}\\n    .stat-label{display:block;color:var(--muted);font-size:.75rem;text-transform:uppercase;letter-spacing:.1em;font-weight:850;margin-bottom:5px}\\n    .stat-value{font-size:1.45rem;font-weight:900;font-variant-numeric:tabular-nums}\\n    .timer-track{height:7px;background:#dfe8ec;border-radius:5px;overflow:hidden;margin:7px auto 0;max-width:120px}\\n    #timerBar{height:100%;width:100%;background:var(--green);transition:width .08s linear,background .2s}\\n    .strikes{display:flex;justify-content:center;gap:7px;margin-top:5px}\\n    .strike{width:12px;height:12px;border:2px solid #c6d2d9;border-radius:50%}\\n    .strike.on{border-color:var(--red);background:var(--red)}\\n    .controls{display:flex;justify-content:center;gap:10px}\\n    .start{min-width:190px;height:54px;border:0;border-radius:13px;background:var(--yellow);color:#352808;font-weight:900;font-size:1.05rem;box-shadow:0 7px 0 #dca62e}\\n    .start:active{transform:translateY(4px);box-shadow:0 3px 0 #dca62e}\\n    .secondary{height:54px;border:1px solid #bfcfd7;border-radius:13px;background:#fff;color:var(--navy);padding:0 18px;font-weight:800}\\n    .footer-note{text-align:center;color:#718495;font-size:.84rem;margin-top:4px}\\n    dialog{border:0;border-radius:20px;padding:0;width:min(620px,calc(100vw - 28px));color:var(--ink);box-shadow:0 28px 90px rgba(4,25,44,.32)}\\n    dialog::backdrop{background:rgba(6,28,45,.62);backdrop-filter:blur(3px)}\\n    .modal-head{display:flex;align-items:center;justify-content:space-between;padding:22px 24px 16px;border-bottom:1px solid var(--line)}\\n    .modal-head h2{margin:0;font-size:1.4rem}\\n    .close{border:0;background:#edf3f6;color:var(--ink);width:38px;height:38px;border-radius:10px;font-size:1.4rem}\\n    .settings-grid{padding:22px 24px;display:grid;grid-template-columns:1fr 1fr;gap:18px}\\n    label{display:grid;gap:7px;font-weight:800;font-size:.88rem;color:#334e68}\\n    select,input[type=\\\"text\\\"]{height:46px;border:1px solid #bfcfd7;border-radius:10px;background:white;padding:0 12px;color:var(--ink)}\\n    .span-2{grid-column:1\\\/-1}.teacher-tools{grid-column:1\\\/-1;background:#f1f7f9;border:1px solid var(--line);border-radius:13px;padding:14px;display:flex;align-items:center;justify-content:space-between;gap:14px}.teacher-tools p{margin:0;color:var(--muted);font-size:.84rem;line-height:1.35}.compact{height:42px;white-space:nowrap}\\n    .modal-foot{display:flex;justify-content:flex-end;gap:10px;padding:16px 24px 24px}\\n    .result-overlay{position:fixed;inset:0;z-index:20;display:none;place-items:center;background:rgba(7,28,45,.62);padding:16px}\\n    .result-overlay.show{display:grid}\\n    .result-card{background:#fff;border-radius:22px;padding:30px;width:min(520px,100%);text-align:center;box-shadow:var(--shadow)}\\n    .result-card h2{font-size:2rem;margin:0 0 8px}.result-card p{color:var(--muted);font-size:1.05rem;margin:0 0 18px}.result-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.result-actions .primary{width:100%}.mastery-badge{display:inline-block;border-radius:999px;padding:8px 14px;background:#dff5ed;color:#126b57;font-weight:900;margin:4px 0 16px}.mastery-badge.not-yet{background:#fff1d2;color:#855d08}.result-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:13px;overflow:hidden;margin:0 0 20px}.result-item{background:#f8fbfc;padding:12px}.result-item span{display:block;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:850}.result-item strong{display:block;margin-top:3px;font-size:1.15rem}.result-name{font-weight:850!important;color:var(--ink)!important;margin-bottom:6px!important}\\n    .toast{position:fixed;left:50%;top:90px;transform:translate(-50%,-20px);opacity:0;pointer-events:none;padding:12px 18px;border-radius:999px;color:#fff;font-weight:850;z-index:30;transition:.22s;background:var(--green);box-shadow:0 10px 30px rgba(0,0,0,.18)}\\n    .toast.show{opacity:1;transform:translate(-50%,0)}.toast.bad{background:var(--red)}\\n    @media(max-width:760px){header{height:62px}.settings-btn span{display:none}.session-picker{width:100%}.session-btn{min-width:0;padding:10px 5px;font-size:.83rem}.session-btn small{display:none}.challenge-levels{width:100%;justify-content:center;flex-wrap:wrap}.challenge-label{width:100%;text-align:center}.difficulty-note{width:100%;max-width:none;text-align:center}.topline{grid-template-columns:1fr auto}.prompt{grid-column:1\\\/-1;grid-row:1}.mode-pill{grid-row:2}.best{grid-row:2}.ruler-shell{padding:24px 6px 12px}#ruler{height:220px}.stats{grid-template-columns:1fr 1fr}.stat:nth-child(2){border-right:0}.stat:nth-child(-n+2){border-bottom:1px solid var(--line)}.settings-grid{grid-template-columns:1fr}.span-2,.teacher-tools{grid-column:auto}.teacher-tools{align-items:stretch;flex-direction:column}.type-row{flex-direction:column}.type-row .primary,#answer{width:100%}.result-actions{grid-template-columns:1fr}}\\n    @media print{body{background:white}.app,.toast{display:none!important}.result-overlay{position:static;display:block!important;background:white;padding:0}.result-card{box-shadow:none;border:2px solid #12304a;width:100%;max-width:720px;margin:20px auto}.result-actions{display:none}.result-card:after{content:\\\"Ruler Lab \\u2022 CTE Makes School Matter\\\";display:block;margin-top:24px;color:#62758a;font-size:.82rem}}\\n    @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\\n  <\\\/style>\\n<\\\/head>\\n<body>\\n  \n\n<div class=\\\"app\\\">\\n    \n\n<header>\\n      \n\n<div class=\\\"brand\\\"><span class=\\\"brand-icon\\\" aria-hidden=\\\"true\\\"><\\\/span>Ruler Lab <span style=\\\"font-weight:600;opacity:.65\\\">| Woodshop<\\\/span><\\\/div>\\n      \n\n<div class=\\\"header-actions\\\">\\n        <button class=\\\"icon-btn\\\" id=\\\"fullscreen\\\" aria-label=\\\"Enter full screen\\\" title=\\\"Full screen\\\">\\u26f6<\\\/button>\\n        <button class=\\\"settings-btn\\\" id=\\\"openSettings\\\" aria-haspopup=\\\"dialog\\\">\\u2699 <span>Settings<\\\/span><\\\/button>\\n      <\\\/div>\\n    <\\\/header>\\n    <main>\\n      \n\n<nav class=\\\"session-picker\\\" aria-label=\\\"Learning mode\\\">\\n        <button class=\\\"session-btn\\\" data-session=\\\"learn\\\" aria-selected=\\\"false\\\">Learn<small>Guided help<\\\/small><\\\/button>\\n        <button class=\\\"session-btn\\\" data-session=\\\"practice\\\" aria-selected=\\\"true\\\">Practice<small>Build accuracy<\\\/small><\\\/button>\\n        <button class=\\\"session-btn\\\" data-session=\\\"challenge\\\" aria-selected=\\\"false\\\">Shop Challenge<small>20 questions<\\\/small><\\\/button>\\n      <\\\/nav>\\n      \n\n<div class=\\\"challenge-levels\\\" id=\\\"challengeLevels\\\" hidden>\\n        <span class=\\\"challenge-label\\\">Difficulty<\\\/span>\\n        <button class=\\\"level-btn\\\" data-level=\\\"easy\\\" aria-selected=\\\"false\\\">Easy<\\\/button>\\n        <button class=\\\"level-btn\\\" data-level=\\\"medium\\\" aria-selected=\\\"true\\\">Medium<\\\/button>\\n        <button class=\\\"level-btn\\\" data-level=\\\"hard\\\" aria-selected=\\\"false\\\">Hard<\\\/button>\\n        <span class=\\\"difficulty-note\\\" id=\\\"difficultyNote\\\">Sixteenths and one-step shop math<\\\/span>\\n      <\\\/div>\\n      \n\n<div class=\\\"topline\\\">\\n        \n\n<div class=\\\"mode-pill\\\" id=\\\"modeLabel\\\">Find the mark<\\\/div>\\n        \n\n<div class=\\\"prompt\\\" aria-live=\\\"polite\\\">\\n          \n\n<p class=\\\"eyebrow\\\" id=\\\"promptLabel\\\">Ready to measure?<\\\/p>\\n          \n\n<p id=\\\"target\\\">Press Start<\\\/p>\\n        <\\\/div>\\n        \n\n<div class=\\\"best\\\">Session best<strong id=\\\"best\\\">0<\\\/strong><\\\/div>\\n      <\\\/div>\\n      \n\n<div class=\\\"controls\\\">\\n        <button class=\\\"start\\\" id=\\\"start\\\">Start game<\\\/button>\\n        <button class=\\\"secondary\\\" id=\\\"newQuestion\\\" hidden>Skip<\\\/button>\\n      <\\\/div>\\n      \n\n<section class=\\\"game-card\\\" aria-label=\\\"Ruler game\\\">\\n        \n\n<div class=\\\"ruler-shell\\\">\\n          \n\n<div class=\\\"ruler-legend\\\" id=\\\"rulerLegend\\\" aria-label=\\\"Ruler mark color key\\\"><\\\/div>\\n          <canvas id=\\\"ruler\\\" tabindex=\\\"0\\\" role=\\\"slider\\\" aria-label=\\\"Interactive inch ruler. Use arrow keys and Enter, or move and click or tap, to choose a measurement.\\\" aria-valuemin=\\\"0\\\" aria-valuemax=\\\"64\\\" aria-valuenow=\\\"0\\\"><\\\/canvas>\\n        <\\\/div>\\n        \n\n<p class=\\\"hint\\\" id=\\\"hint\\\">Choose your settings, then press Start.<\\\/p>\\n        \n\n<div class=\\\"learn-note\\\" id=\\\"learnNote\\\"><\\\/div>\\n        \n\n<div class=\\\"type-row\\\" id=\\\"typeRow\\\">\\n          <input id=\\\"answer\\\" type=\\\"text\\\" inputmode=\\\"text\\\" autocomplete=\\\"off\\\" placeholder='Example: 2 3\\\/8 or 2.375' aria-label=\\\"Measurement answer\\\">\\n          <button class=\\\"primary\\\" id=\\\"submit\\\">Check answer<\\\/button>\\n        <\\\/div>\\n        \n\n<div class=\\\"stats\\\">\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\">Score<\\\/span><span class=\\\"stat-value\\\" id=\\\"score\\\">0<\\\/span><\\\/div>\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\">Time<\\\/span><span class=\\\"stat-value\\\" id=\\\"time\\\">\\u2014<\\\/span>\n\n<div class=\\\"timer-track\\\">\n\n<div id=\\\"timerBar\\\"><\\\/div><\\\/div><\\\/div>\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\" id=\\\"levelLabel\\\">Level<\\\/span><span class=\\\"stat-value\\\" id=\\\"level\\\">1<\\\/span><\\\/div>\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\" id=\\\"strikeLabel\\\">Strikes<\\\/span>\n\n<div class=\\\"strikes\\\" id=\\\"strikes\\\"><i class=\\\"strike\\\"><\\\/i><i class=\\\"strike\\\"><\\\/i><i class=\\\"strike\\\"><\\\/i><\\\/div><span class=\\\"stat-value\\\" id=\\\"mistakeCount\\\" hidden>0<\\\/span><\\\/div>\\n        <\\\/div>\\n      <\\\/section>\\n      \n\n<p class=\\\"footer-note\\\">Ad-free \\u2022 No sign-in \\u2022 CTE Makes School Matter<\\\/p>\\n    <\\\/main>\\n  <\\\/div>\\n\\n  <dialog id=\\\"settingsDialog\\\">\\n    \n\n<form method=\\\"dialog\\\" id=\\\"settingsForm\\\">\\n      \n\n<div class=\\\"modal-head\\\">\n\n<h2>Game settings<\\\/h2><button class=\\\"close\\\" value=\\\"cancel\\\" aria-label=\\\"Close settings\\\">\\u00d7<\\\/button><\\\/div>\\n      \n\n<div class=\\\"settings-grid\\\">\\n        <label class=\\\"span-2\\\">Student name <input id=\\\"studentName\\\" type=\\\"text\\\" maxlength=\\\"40\\\" placeholder=\\\"Optional \\u2014 appears on results\\\"><\\\/label>\\n        <label>Game mode<select id=\\\"mode\\\"><option value=\\\"find\\\">Find the mark<\\\/option><option value=\\\"type\\\">Read and type<\\\/option><\\\/select><\\\/label>\\n        <label>Question precision<select id=\\\"questions\\\"><option value=\\\"1\\\">Whole inches<\\\/option><option value=\\\"2\\\">Halves<\\\/option><option value=\\\"4\\\">Quarters<\\\/option><option value=\\\"8\\\">Eighths<\\\/option><option value=\\\"16\\\" selected>Sixteenths<\\\/option><option value=\\\"32\\\">Thirty-seconds<\\\/option><option value=\\\"64\\\">Sixty-fourths<\\\/option><\\\/select><\\\/label>\\n        <label>Ruler marks<select id=\\\"marks\\\"><option value=\\\"2\\\">Halves<\\\/option><option value=\\\"4\\\">Quarters<\\\/option><option value=\\\"8\\\">Eighths<\\\/option><option value=\\\"16\\\" selected>Sixteenths<\\\/option><option value=\\\"32\\\">Thirty-seconds<\\\/option><option value=\\\"64\\\">Sixty-fourths<\\\/option><\\\/select><\\\/label>\\n        <label>Ruler length<select id=\\\"length\\\"><option value=\\\"auto\\\">Automatic<\\\/option><option value=\\\"1\\\">1 inch<\\\/option><option value=\\\"2\\\">2 inches<\\\/option><option value=\\\"3\\\">3 inches<\\\/option><option value=\\\"4\\\" selected>4 inches<\\\/option><option value=\\\"6\\\">6 inches<\\\/option><option value=\\\"8\\\">8 inches<\\\/option><option value=\\\"10\\\">10 inches<\\\/option><option value=\\\"12\\\">12 inches<\\\/option><\\\/select><\\\/label>\\n        <label>Answer notation<select id=\\\"notation\\\"><option value=\\\"fraction\\\">Fractions<\\\/option><option value=\\\"decimal\\\">Decimals<\\\/option><\\\/select><\\\/label>\\n        <label>Fraction style<select id=\\\"fractionStyle\\\"><option value=\\\"both\\\">Mixed practice<\\\/option><option value=\\\"simplified\\\">Simplified<\\\/option><option value=\\\"unsimplified\\\">Unsimplified<\\\/option><\\\/select><\\\/label>\\n        <label>Timer<select id=\\\"timer\\\"><option value=\\\"on\\\">On<\\\/option><option value=\\\"off\\\">Off<\\\/option><\\\/select><\\\/label>\\n        <label>Sounds<select id=\\\"sound\\\"><option value=\\\"on\\\">On<\\\/option><option value=\\\"off\\\">Off<\\\/option><\\\/select><\\\/label>\\n        <label>Measure twice<select id=\\\"measureTwice\\\"><option value=\\\"off\\\">Off<\\\/option><option value=\\\"on\\\">On<\\\/option><\\\/select><\\\/label>\\n        \n\n<div class=\\\"teacher-tools\\\">\n\n<p><strong>Teacher preset link<\\\/strong><br \/>Opens with these settings already selected.<\\\/p><button type=\\\"button\\\" class=\\\"secondary compact\\\" id=\\\"copyPreset\\\">Copy link<\\\/button><\\\/div>\\n      <\\\/div>\\n      \n\n<div class=\\\"modal-foot\\\"><button class=\\\"secondary\\\" value=\\\"cancel\\\">Cancel<\\\/button><button class=\\\"primary\\\" id=\\\"saveSettings\\\" value=\\\"default\\\">Save settings<\\\/button><\\\/div>\\n    <\\\/form>\\n  <\\\/dialog>\\n  \n\n<div class=\\\"result-overlay\\\" id=\\\"gameOver\\\">\n\n<div class=\\\"result-card\\\">\\n    \n\n<p class=\\\"eyebrow\\\" id=\\\"resultEyebrow\\\">Session complete<\\\/p>\n\n<h2 id=\\\"finalScore\\\">Score: 0<\\\/h2>\n\n<p class=\\\"result-name\\\" id=\\\"resultName\\\"><\\\/p>\n\n<div class=\\\"mastery-badge\\\" id=\\\"masteryBadge\\\">Keep practicing<\\\/div>\\n    \n\n<div class=\\\"result-grid\\\">\n\n<div class=\\\"result-item\\\"><span>Accuracy<\\\/span><strong id=\\\"resultAccuracy\\\">0%<\\\/strong><\\\/div>\n\n<div class=\\\"result-item\\\"><span>Correct<\\\/span><strong id=\\\"resultCorrect\\\">0<\\\/strong><\\\/div>\n\n<div class=\\\"result-item\\\"><span>Precision<\\\/span><strong id=\\\"resultPrecision\\\">1\\\/16\\u2033<\\\/strong><\\\/div>\n\n<div class=\\\"result-item\\\"><span>Date<\\\/span><strong id=\\\"resultDate\\\">\\u2014<\\\/strong><\\\/div><\\\/div>\\n    \n\n<p id=\\\"finalMessage\\\">Nice work. Every mark gets easier with practice.<\\\/p>\n\n<div class=\\\"result-actions\\\"><button class=\\\"secondary\\\" id=\\\"printResults\\\">Print \\\/ Save PDF<\\\/button><button class=\\\"primary\\\" id=\\\"playAgain\\\">Try again<\\\/button><\\\/div>\\n  <\\\/div><\\\/div>\\n  \n\n<div class=\\\"toast\\\" id=\\\"toast\\\" role=\\\"status\\\" aria-live=\\\"polite\\\"><\\\/div>\\n\\n  <script>\\n  (()=>{\\n    const $=s=>document.querySelector(s), canvas=$('#ruler'), ctx=canvas.getContext('2d');\\n    const els={target:$('#target'),prompt:$('#promptLabel'),hint:$('#hint'),note:$('#learnNote'),score:$('#score'),level:$('#level'),levelLabel:$('#levelLabel'),time:$('#time'),timerBar:$('#timerBar'),strikes:$('#strikes'),mistakes:$('#mistakeCount'),strikeLabel:$('#strikeLabel'),start:$('#start'),skip:$('#newQuestion'),typeRow:$('#typeRow'),answer:$('#answer'),modeLabel:$('#modeLabel'),toast:$('#toast'),best:$('#best')};\\n    const inputs=['mode','questions','marks','length','notation','fractionStyle','timer','sound','measureTwice'];\\n    const defaults={session:'practice',challengeLevel:'medium',mode:'find',questions:'16',marks:'16',length:'4',notation:'fraction',fractionStyle:'both',timer:'on',sound:'on',measureTwice:'off',studentName:''};\\n    let settings={...defaults,...JSON.parse(localStorage.getItem('rulerLabSettings')||'{}')};\\n    const preset=new URLSearchParams(location.search);['session','challengeLevel',...inputs].forEach(k=>{if(preset.has(k))settings[k]=preset.get(k)});if(+settings.questions>+settings.marks)settings.marks=settings.questions;\\n    let state={playing:false,score:0,correct:0,answered:0,strikes:0,level:1,answerUnits:0,answerDen:+settings.questions,hoverUnits:null,shownUnits:null,firstMeasure:null,questionKind:'find',expectsFind:true,startTime:0,duration:10,raf:0,locked:false};\\n    let geom={x:0,y:0,w:0,h:0,inch:0,length:4};\\n    inputs.forEach(id=>$('#'+id).value=settings[id]??defaults[id]);$('#studentName').value=settings.studentName||'';\\n    function gcd(a,b){while(b)[a,b]=[b,a%b];return a||1}\\n    function autoLength(){const width=canvas.clientWidth;return width<520?2:width<820?3:width<1120?4:6}\\n    function rulerLength(){return settings.length==='auto'?autoLength():+settings.length}\\n    function challengeDen(){return settings.challengeLevel==='easy'?8:16}\\n    function rulerMarks(){return settings.session==='challenge'?16:+settings.marks}\\n    function format(units,denom=+settings.questions,forceStyle=settings.fractionStyle){\\n      const value=units\\\/denom;if(settings.notation==='decimal')return value.toFixed(4).replace(\\\/0+$\\\/,'').replace(\\\/\\\\.$\\\/,'')+'\\u2033';\\n      const whole=Math.floor(units\\\/denom),n=units%denom;if(!n)return whole+'\\u2033';let num=n,d=denom;\\n      const style=forceStyle==='both'?(Math.random()<.5?'simplified':'unsimplified'):forceStyle;if(style==='simplified'){const g=gcd(num,d);num\\\/=g;d\\\/=g}\\n      return(whole?whole+' ':'')+num+'\\\/'+d+'\\u2033';\\n    }\\n    function precisionName(){if(settings.session==='challenge')return settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1)+' \\u00b7 1\\\/'+challengeDen()+'\\u2033';return({1:'Whole',2:'1\\\/2\\u2033',4:'1\\\/4\\u2033',8:'1\\\/8\\u2033',16:'1\\\/16\\u2033',32:'1\\\/32\\u2033',64:'1\\\/64\\u2033'})[settings.questions]}\\n    function unitsAtMarks(units,den=state.answerDen){return Math.round(units*rulerMarks()\\\/den)}\\n    function tickFamily(frac,marks){if(frac===0||frac===marks\\\/2)return'major';const pos=frac\\\/marks;if(Number.isInteger(pos*4))return'quarter';if(Number.isInteger(pos*8))return'eighth';if(Number.isInteger(pos*16))return'sixteenth';return'fine'}\\n    function tickColor(family){const colors={major:'#12304a',quarter:'#2463eb',eighth:'#0f8a72',sixteenth:'#d97706',fine:'#8797a4'};return settings.session==='learn'?colors[family]:'#12304a'}\\n    function updateLegend(){const marks=rulerMarks(),items=[];if(settings.session==='learn'&&marks>=4)items.push(['quarter','\\u00bc-inch']);if(settings.session==='learn'&&marks>=8)items.push(['eighth','\\u215b-inch']);if(settings.session==='learn'&&marks>=16)items.push(['sixteenth','\\u00b9\\u2044\\u2081\\u2086-inch']);const legend=$('#rulerLegend');legend.hidden=!items.length;legend.innerHTML=items.map(([c,t])=>'<span class=\\\"legend-item\\\"><i class=\\\"legend-line legend-'+c+'\\\"><\\\/i>'+t+'<\\\/span>').join('')}\\n    function draw(){\\n      const dpr=Math.min(devicePixelRatio||1,2),rect=canvas.getBoundingClientRect();\\n      if(canvas.width!==Math.round(rect.width*dpr)||canvas.height!==Math.round(rect.height*dpr)){canvas.width=Math.round(rect.width*dpr);canvas.height=Math.round(rect.height*dpr)}\\n      ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,rect.width,rect.height);const len=rulerLength(),pad=Math.max(20,rect.width*.025),top=26,base=rect.height-35,usable=rect.width-pad*2;geom={x:pad,y:top,w:usable,h:base-top,inch:usable\\\/len,length:len};\\n      ctx.fillStyle='#fff9df';ctx.strokeStyle='#12304a';ctx.lineWidth=2.5;roundRect(ctx,pad,top,usable,base-top,8);ctx.fill();ctx.stroke();const marks=rulerMarks(),total=len*marks;\\n      for(let i=0;i<=total;i++){const x=pad+i\\\/total*usable,frac=i%marks,family=tickFamily(frac,marks);let tick;if(i===0||i===total||frac===0)tick=78;else{let pow=0,n=frac;while(n%2===0){pow++;n\\\/=2}tick=Math.min(20+pow*12,64)}ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,top+tick);ctx.strokeStyle=tickColor(family);ctx.lineWidth=family==='major'?2.8:2.2;ctx.stroke();if(frac===0&&i>0){ctx.fillStyle='#12304a';ctx.font='800 25px Inter,system-ui';ctx.textAlign='center';ctx.fillText(String(i\\\/marks),x,top+104)}}\\n      const active=state.playing&&!state.locked&&state.expectsFind?state.hoverUnits:null;if(active!==null){const x=pad+active\\\/(marks*len)*usable;ctx.fillStyle='rgba(22,134,111,.22)';ctx.fillRect(Math.max(pad,x-geom.inch\\\/marks\\\/2),top+2,Math.max(4,geom.inch\\\/marks),base-top-4);ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#16866f';ctx.lineWidth=4;ctx.stroke();canvas.setAttribute('aria-valuenow',String(active));canvas.setAttribute('aria-valuetext',format(active,marks,'simplified'))}\\n      canvas.setAttribute('aria-valuemax',String(marks*len));if(state.shownUnits!==null){const x=pad+state.shownUnits\\\/(marks*len)*usable;ctx.beginPath();ctx.moveTo(x,top-5);ctx.lineTo(x,base+2);ctx.strokeStyle='#d44949';ctx.lineWidth=4;ctx.stroke();ctx.fillStyle='#d44949';ctx.beginPath();ctx.moveTo(x-8,top-10);ctx.lineTo(x+8,top-10);ctx.lineTo(x,top+1);ctx.fill()}\\n      if(state.questionKind==='read'&&state.playing){const m=unitsAtMarks(state.answerUnits),x=pad+m\\\/(marks*len)*usable;ctx.fillStyle='#a11d48';ctx.beginPath();ctx.moveTo(x-11,top-15);ctx.lineTo(x+11,top-15);ctx.lineTo(x,top+1);ctx.fill();ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#a11d48';ctx.lineWidth=3;ctx.stroke()}updateLegend()\\n    }\\n    function roundRect(c,x,y,w,h,r){c.beginPath();c.roundRect?c.roundRect(x,y,w,h,r):c.rect(x,y,w,h)}\\n    function randomUnits(max,avoid=0){let u;do{u=Math.max(1,Math.floor(Math.random()*max))}while(u===avoid);return u}\\n    function learningHelp(units,den){const whole=Math.floor(units\\\/den),n=units%den;if(!n)return'Find the long line for '+whole+' whole inch'+(whole===1?'':'es')+'.';const g=gcd(n,den),sn=n\\\/g,sd=den\\\/g;return'Break it down: '+(whole?whole+' whole inch'+(whole===1?'':'es')+' plus ':'')+sn+' of '+sd+' equal parts.'}\\n    function showType(on){els.typeRow.classList.toggle('show',on);if(on)setTimeout(()=>els.answer.focus(),60)}\\n    function newQuestion(){\\n      state.locked=false;state.shownUnits=null;state.hoverUnits=null;state.firstMeasure=null;els.answer.value='';els.note.classList.remove('show');const den=settings.session==='challenge'?challengeDen():+settings.questions,len=rulerLength();state.answerDen=den;\\n      let kind=settings.mode==='find'?'find':'read';if(settings.session==='challenge'){const sets={easy:['find','read','cutlist','find','midpoint'],medium:['find','read','cutlist','midpoint','remaining'],hard:['read','cutlist','remaining','midpoint','multiCut','difference']};const kinds=sets[settings.challengeLevel];kind=kinds[state.answered%kinds.length]}\\n      state.questionKind=kind;state.expectsFind=kind==='find'||kind==='cutlist';\\n      if(kind==='find'||kind==='read'||kind==='cutlist'){state.answerUnits=randomUnits(len*den,state.answerUnits)}\\n      if(kind==='find'){els.prompt.textContent='Click the measurement';els.target.textContent=format(state.answerUnits,den);els.hint.textContent=settings.measureTwice==='on'?'Measure it twice. Both selections must match.':'Move over the ruler, then click or tap the correct mark.';showType(false)}\\n      if(kind==='read'){els.prompt.textContent='Read the marked line';els.target.innerHTML='<small>Type your answer below<\\\/small>';els.hint.textContent='Enter a fraction or decimal measurement.';showType(true)}\\n      if(kind==='cutlist'){const parts=['Side rail','Shelf support','Face-frame piece','Drawer spacer'];els.prompt.textContent='Cut list: '+parts[state.answered%parts.length];els.target.textContent=format(state.answerUnits,den,'simplified');els.hint.textContent='Select this cut length on the ruler.';showType(false)}\\n      if(kind==='midpoint'){state.answerUnits=randomUnits(Math.max(2,Math.floor(6*den)));const boardUnits=state.answerUnits*2;els.prompt.textContent='Find the midpoint';els.target.textContent=format(boardUnits,den,'simplified')+' board';els.hint.textContent='What measurement marks the exact center?';showType(true)}\\n      if(kind==='remaining'){const totalInches=6+Math.floor(Math.random()*7),totalUnits=totalInches*den,cutUnits=randomUnits(totalUnits-1);state.answerUnits=totalUnits-cutUnits;els.prompt.textContent='Board remaining';els.target.style.fontSize='clamp(1.55rem,3.4vw,3.2rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \\u2212 '+format(cutUnits,den,'simplified');els.hint.textContent='Type the board length left after the cut.';showType(true)}else els.target.style.fontSize='';\\n      if(kind==='multiCut'){const totalInches=10+Math.floor(Math.random()*3),totalUnits=totalInches*den,maxPiece=Math.floor((totalUnits-den)\\\/3),pieceUnits=den+Math.floor(Math.random()*Math.max(1,maxPiece-den));state.answerUnits=totalUnits-pieceUnits*2;els.prompt.textContent='Two pieces from one board';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \\u2212 2 \\u00d7 '+format(pieceUnits,den,'simplified');els.hint.textContent='Ignore saw kerf. How much stock remains?';showType(true)}\\n      if(kind==='difference'){const planned=2*den+randomUnits(4*den),gap=randomUnits(den),actual=planned-gap;state.answerUnits=gap;els.prompt.textContent='Measurement difference';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(planned,den,'simplified')+' planned \\u00b7 '+format(actual,den,'simplified')+' actual';els.hint.textContent='How much shorter is the actual piece?';showType(true)}\\n      if(settings.session==='learn'){els.note.textContent=learningHelp(state.answerUnits,den);els.note.classList.add('show')}\\n      setStatMode();startClock();draw();\\n    }\\n    function startGame(){cancelAnimationFrame(state.raf);state={...state,playing:true,score:0,correct:0,answered:0,strikes:0,level:1,locked:false,shownUnits:null,firstMeasure:null};els.score.textContent='0';els.start.textContent='Restart';els.skip.hidden=settings.session==='learn';$('#gameOver').classList.remove('show');updateStrikes();newQuestion()}\\n    function startClock(){cancelAnimationFrame(state.raf);state.duration=state.expectsFind?Math.max(3,11-state.level):Math.max(6,20-(state.level-1)*2);if(settings.session==='challenge')state.duration+=settings.challengeLevel==='easy'?6:settings.challengeLevel==='hard'?-2:0;state.startTime=performance.now();if(settings.timer==='off'||settings.session==='learn'){els.time.textContent='\\u221e';els.timerBar.style.width='100%';els.timerBar.style.background='#16866f';return}const tick=now=>{if(!state.playing||state.locked)return;const left=Math.max(0,state.duration-(now-state.startTime)\\\/1000),pct=left\\\/state.duration*100;els.time.textContent=Math.ceil(left)+'s';els.timerBar.style.width=pct+'%';els.timerBar.style.background=pct<25?'#d44949':pct<55?'#e7ad29':'#16866f';if(left<=0){check(null);return}state.raf=requestAnimationFrame(tick)};state.raf=requestAnimationFrame(tick)}\\n    function parseAnswer(v){v=v.trim().replace(\\\/[\\u2033\\\"]\\\/g,'');if(!v)return null;if(\\\/^\\\\d*\\\\.?\\\\d+$\\\/.test(v))return+v;const m=v.match(\\\/^(?:(\\\\d+)\\\\s+)?(\\\\d+)\\\\\\\/(\\\\d+)$\\\/);if(!m)return null;return+(m[1]||0)+(+m[2]\\\/+m[3])}\\n    function check(selectedMark){\\n      if(!state.playing||state.locked)return;if(state.expectsFind&&selectedMark!==null&&settings.measureTwice==='on'){if(state.firstMeasure===null){state.firstMeasure=selectedMark;els.hint.textContent='First measurement saved. Move away, then measure it again.';toast('First measurement saved',false);return}if(state.firstMeasure!==selectedMark){state.firstMeasure=null;els.hint.textContent='Those measurements did not match. Start again.';toast('Measurements did not match',true);return}}\\n      state.locked=true;cancelAnimationFrame(state.raf);let ok=false;if(state.expectsFind)ok=selectedMark!==null&&selectedMark===unitsAtMarks(state.answerUnits);else{const v=parseAnswer(els.answer.value);ok=v!==null&&Math.abs(v-state.answerUnits\\\/state.answerDen)<1e-7}\\n      state.answered++;if(ok){state.correct++;state.score+=state.level*10;els.score.textContent=state.score;toast('Correct! +'+state.level*10,false);sound(true)}else{state.strikes++;const correct=format(state.answerUnits,state.answerDen,'simplified');toast(selectedMark===null?'Time\\u2019s up \\u2014 '+correct:'Correct answer: '+correct,true);sound(false);if(state.expectsFind)state.shownUnits=unitsAtMarks(state.answerUnits);draw()}\\n      if(settings.session!=='challenge'&&state.correct>0&&state.correct%10===0){state.level=Math.min(10,Math.floor(state.correct\\\/10)+1);els.level.textContent=state.level}\\n      updateStrikes();if(settings.session==='learn'&&!ok)els.note.textContent='The correct mark is highlighted. '+learningHelp(state.answerUnits,state.answerDen);\\n      const done=settings.session==='challenge'&&state.answered>=20||settings.session==='practice'&&state.strikes>=3;if(done)setTimeout(endGame,1100);else setTimeout(newQuestion,ok?700:1500)\\n    }\\n    function setStatMode(){const challenge=settings.session==='challenge';els.levelLabel.textContent=challenge?'Question':'Level';els.level.textContent=challenge?(Math.min(20,state.answered+1)+'\\\/20'):state.level;els.strikeLabel.textContent=challenge?'Missed':'Strikes';els.strikes.hidden=challenge;els.mistakes.hidden=!challenge;if(challenge)els.mistakes.textContent=state.strikes}\\n    function updateStrikes(){[...els.strikes.children].forEach((x,i)=>x.classList.toggle('on',i<state.strikes));els.mistakes.textContent=state.strikes;setStatMode()}\\n    function endGame(){state.playing=false;cancelAnimationFrame(state.raf);const total=Math.max(1,state.answered),accuracy=Math.round(state.correct\\\/total*100),mastered=settings.session==='challenge'&&accuracy>=90,best=Math.max(state.score,+localStorage.getItem('rulerLabBest')||0);localStorage.setItem('rulerLabBest',best);els.best.textContent=best;$('#resultEyebrow').textContent=settings.session==='challenge'?'Shop challenge complete':'Practice complete';$('#finalScore').textContent=settings.session==='challenge'?(mastered?'Measurement mastered':'Keep building accuracy'):'Score: '+state.score;$('#resultName').textContent=settings.studentName||'Ruler Lab Student';$('#masteryBadge').textContent=settings.session==='challenge'?(mastered?'Mastered \\u2022 90%+':'Not yet mastered \\u2022 Goal: 90%'):'Practice session';$('#masteryBadge').classList.toggle('not-yet',!mastered);$('#resultAccuracy').textContent=accuracy+'%';$('#resultCorrect').textContent=state.correct+' \\\/ '+state.answered;$('#resultPrecision').textContent=precisionName();$('#resultDate').textContent=new Date().toLocaleDateString();$('#finalMessage').textContent=mastered?'Ready to apply this skill in the shop.':accuracy>=75?'Very close\\u2014review the marks you missed and try again.':'Slow down, count the spaces, and measure twice.';$('#gameOver').classList.add('show');els.start.textContent='Start '+sessionName();els.skip.hidden=true}\\n    function sessionName(){return settings.session==='challenge'?'challenge':settings.session}\\n    function updateSessionUI(){document.querySelectorAll('.session-btn').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.session===settings.session)));const challenge=settings.session==='challenge',notes={easy:'Eighth-inch questions on a full sixteenth-inch ruler',medium:'Sixteenths, midpoints, and one-step remaining length',hard:'Sixteenths, multi-cut stock, differences, and mixed calculations'};$('#challengeLevels').hidden=!challenge;document.querySelectorAll('.level-btn').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.level===settings.challengeLevel)));$('#difficultyNote').textContent=notes[settings.challengeLevel];els.modeLabel.textContent=settings.session==='learn'?'Color-guided learning':challenge?'Shop Challenge \\u00b7 '+settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1):'Independent practice';els.start.textContent='Start '+sessionName();setStatMode();if(!state.playing){els.prompt.textContent=challenge?'20-question '+settings.challengeLevel+' challenge':settings.session==='learn'?'Learn each ruler mark':'Build speed and accuracy';els.target.textContent='Press Start';els.hint.textContent=challenge?notes[settings.challengeLevel]+'.':settings.session==='learn'?'Colors identify quarters, eighths, and sixteenths on the ruler.':'Practice on a standard ruler without color clues.';showType(false);draw()}}\\n    let toastTimer;function toast(msg,bad){els.toast.textContent=msg;els.toast.className='toast show'+(bad?' bad':'');clearTimeout(toastTimer);toastTimer=setTimeout(()=>els.toast.className='toast',1100)}\\n    function sound(good){if(settings.sound!=='on')return;try{const ac=new(window.AudioContext||window.webkitAudioContext)(),o=ac.createOscillator(),g=ac.createGain();o.frequency.value=good?660:190;o.type=good?'sine':'triangle';g.gain.setValueAtTime(.08,ac.currentTime);g.gain.exponentialRampToValueAtTime(.001,ac.currentTime+.16);o.connect(g);g.connect(ac.destination);o.start();o.stop(ac.currentTime+.17)}catch(e){}}\\n    function pointer(e){const rect=canvas.getBoundingClientRect(),x=Math.max(geom.x,Math.min(geom.x+geom.w,e.clientX-rect.left)),marks=rulerMarks();return Math.round((x-geom.x)\\\/geom.w*(marks*geom.length))}\\n    canvas.addEventListener('pointermove',e=>{if(state.playing&&!state.locked&&state.expectsFind){state.hoverUnits=pointer(e);draw()}});canvas.addEventListener('pointerleave',()=>{if(state.expectsFind){state.hoverUnits=null;draw()}});canvas.addEventListener('pointerdown',e=>{if(state.playing&&!state.locked&&state.expectsFind){e.preventDefault();const u=pointer(e);state.hoverUnits=u;draw();check(u)}});\\n    canvas.addEventListener('keydown',e=>{if(!state.playing||state.locked||!state.expectsFind)return;const max=rulerMarks()*rulerLength();if(e.key==='ArrowRight'||e.key==='ArrowUp'){e.preventDefault();state.hoverUnits=Math.min(max,(state.hoverUnits??0)+1);draw()}else if(e.key==='ArrowLeft'||e.key==='ArrowDown'){e.preventDefault();state.hoverUnits=Math.max(0,(state.hoverUnits??0)-1);draw()}else if(e.key==='Enter'||e.key===' '){e.preventDefault();check(state.hoverUnits??0)}});\\n    $('#submit').onclick=()=>check(0);els.answer.addEventListener('keydown',e=>{if(e.key==='Enter')check(0)});els.start.onclick=startGame;$('#playAgain').onclick=startGame;$('#printResults').onclick=()=>window.print();els.skip.onclick=()=>check(null);\\n    document.querySelectorAll('.session-btn').forEach(b=>b.onclick=()=>{settings.session=b.dataset.session;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\\n    document.querySelectorAll('.level-btn').forEach(b=>b.onclick=()=>{settings.challengeLevel=b.dataset.level;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\\n    $('#openSettings').onclick=()=>$('#settingsDialog').showModal();$('#saveSettings').onclick=e=>{e.preventDefault();inputs.forEach(id=>settings[id]=$('#'+id).value);settings.studentName=$('#studentName').value.trim();if(+settings.questions>+settings.marks){settings.marks=settings.questions;$('#marks').value=settings.marks;toast('Ruler marks adjusted to match questions',false)}localStorage.setItem('rulerLabSettings',JSON.stringify(settings));$('#settingsDialog').close();if(state.playing)startGame();else updateSessionUI()};\\n    $('#copyPreset').onclick=async()=>{inputs.forEach(id=>settings[id]=$('#'+id).value);const url=new URL(location.href);url.search='';url.searchParams.set('session',settings.session);url.searchParams.set('challengeLevel',settings.challengeLevel);inputs.forEach(k=>url.searchParams.set(k,settings[k]));try{await navigator.clipboard.writeText(url.href);toast('Teacher preset link copied',false)}catch(e){prompt('Copy this teacher preset link:',url.href)}};\\n    $('#questions').onchange=()=>{if(+$('#questions').value>+$('#marks').value)$('#marks').value=$('#questions').value};$('#marks').onchange=()=>{if(+$('#marks').value<+$('#questions').value)$('#questions').value=$('#marks').value};$('#fullscreen').onclick=()=>{if(!document.fullscreenElement)document.documentElement.requestFullscreen?.();else document.exitFullscreen?.()};window.addEventListener('resize',draw);\\n    els.best.textContent=localStorage.getItem('rulerLabBest')||'0';updateSessionUI();\\n    if(document.modelContext?.registerTool){const register=t=>Promise.resolve(document.modelContext.registerTool(t)).catch(()=>{});register({name:'start_ruler_game',title:'Start ruler game',description:'Start or restart the visible ruler learning, practice, or shop challenge session.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=>{if(v?.session)settings.session=v.session;if(v?.challengeLevel)settings.challengeLevel=v.challengeLevel;updateSessionUI();startGame();return{status:'started',session:settings.session,challengeLevel:settings.challengeLevel,precision:settings.session==='challenge'?challengeDen():settings.questions}}});register({name:'configure_ruler_game',title:'Configure ruler game',description:'Change the visible ruler game settings.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']},mode:{enum:['find','type']},questions:{enum:['1','2','4','8','16','32','64']},marks:{enum:['2','4','8','16','32','64']},length:{enum:['auto','1','2','3','4','6','8','10','12']},notation:{enum:['fraction','decimal']},timer:{enum:['on','off']},measureTwice:{enum:['on','off']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=>{settings={...settings,...v};if(+settings.questions>+settings.marks)settings.marks=settings.questions;inputs.forEach(id=>$('#'+id).value=settings[id]);localStorage.setItem('rulerLabSettings',JSON.stringify(settings));updateSessionUI();return{status:'configured',settings}}})}\\n  })();\\n  <\\\/script>\\n<\\\/body>\\n<\\\/html><!doctype html>\\n<html lang=\\\"en\\\">\\n<head>\\n  <meta charset=\\\"utf-8\\\">\\n  <meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\\\">\\n  <meta name=\\\"theme-color\\\" content=\\\"#12304a\\\">\\n  <meta name=\\\"description\\\" content=\\\"An ad-free woodshop measurement game for learning to read an inch ruler and solve practical cut-list problems.\\\">\\n  <title>Ruler Lab \\u2014 Woodshop Measurement Practice<\\\/title>\\n  <link rel=\\\"icon\\\" type=\\\"image\\\/svg+xml\\\" href=\\\"data:image\\\/svg+xml,%3Csvg xmlns='http:\\\/\\\/www.w3.org\\\/2000\\\/svg' viewBox='0 0 64 64'%3E%3Crect x='5' y='13' width='54' height='38' rx='7' fill='%23f6c453'\\\/%3E%3Cpath d='M13 13v15m8-15v9m8-9v15m8-15v9m8-9v15m8-15v9' stroke='%2312304a' stroke-width='4'\\\/%3E%3C\\\/svg%3E\\\">\\n  \n\n<style>\\n    :root{--ink:#102a43;--muted:#62758a;--navy:#12304a;--blue:#1675a9;--sky:#e8f6fb;--paper:#fffdfa;--yellow:#f6c453;--green:#16866f;--red:#d44949;--line:#d6e1e8;--shadow:0 18px 50px rgba(18,48,74,.14)}\\n    *{box-sizing:border-box}\\n    html,body{min-height:100%;margin:0}\\n    body{font-family:Inter,ui-sans-serif,system-ui,-apple-system,\\\"Segoe UI\\\",sans-serif;color:var(--ink);background:linear-gradient(180deg,#e9f7fb 0,#f7fbfc 52%,#edf4f7 100%)}\\n    button,select,input{font:inherit}\\n    button{cursor:pointer}\\n    .app{min-height:100vh;display:flex;flex-direction:column}\\n    header{height:72px;display:flex;align-items:center;justify-content:space-between;padding:0 clamp(18px,4vw,64px);background:var(--navy);color:white}\\n    .brand{display:flex;align-items:center;gap:12px;font-weight:850;font-size:1.25rem;letter-spacing:.01em}\\n    .brand-icon{width:42px;height:30px;border-radius:7px;background:var(--yellow);position:relative;box-shadow:inset 0 -3px rgba(0,0,0,.08)}\\n    .brand-icon:after{content:\\\"\\\";position:absolute;inset:0;background:repeating-linear-gradient(90deg,transparent 0 7px,var(--navy) 7px 9px,transparent 9px 14px);clip-path:polygon(0 0,100% 0,100% 58%,0 58%)}\\n    .header-actions{display:flex;gap:8px}\\n    .icon-btn,.settings-btn{border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.08);color:#fff;border-radius:10px;min-height:42px;padding:0 14px;font-weight:750}\\n    .icon-btn:hover,.settings-btn:hover{background:rgba(255,255,255,.16)}\\n    main{width:min(1500px,100%);margin:auto;padding:clamp(18px,3vw,40px) clamp(14px,4vw,58px) 36px;flex:1;display:flex;flex-direction:column;justify-content:center;gap:18px}\\n    .session-picker{align-self:center;display:grid;grid-template-columns:repeat(3,1fr);gap:5px;background:#dce9ee;border:1px solid #c8d9e1;padding:5px;border-radius:15px}\\n    .session-btn{border:0;background:transparent;color:#50687a;border-radius:10px;padding:11px 18px;font-weight:850;min-width:150px}\\n    .session-btn[aria-selected=\\\"true\\\"]{background:white;color:var(--navy);box-shadow:0 3px 12px rgba(18,48,74,.12)}\\n    .session-btn small{display:block;font-size:.7rem;font-weight:700;color:#718495;margin-top:2px}\\n    .challenge-levels{align-self:center;display:flex;align-items:center;gap:8px;padding:6px;background:#fff;border:1px solid var(--line);border-radius:13px;box-shadow:0 4px 16px rgba(18,48,74,.07)}\\n    .challenge-levels[hidden]{display:none}.challenge-label{font-size:.76rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:900;padding:0 7px}.level-btn{border:1px solid transparent;background:transparent;color:#536c7e;border-radius:9px;padding:8px 13px;font-weight:850}.level-btn[aria-selected=\\\"true\\\"]{background:var(--blue);color:white}.difficulty-note{font-size:.78rem;color:var(--muted);font-weight:700;padding:0 8px;max-width:280px}\\n    .topline{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:18px}\\n    .mode-pill{justify-self:start;display:inline-flex;align-items:center;gap:8px;background:#d9f3ed;color:#116653;padding:8px 12px;border-radius:999px;font-weight:800;font-size:.85rem;text-transform:uppercase;letter-spacing:.06em}\\n    .mode-pill:before{content:\\\"\\\";width:8px;height:8px;border-radius:50%;background:var(--green)}\\n    .prompt{text-align:center;min-width:min(430px,60vw)}\\n    .eyebrow{font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;font-size:.82rem;margin:0 0 4px}\\n    #target{font-size:clamp(2.1rem,5vw,4.8rem);line-height:1;font-weight:900;letter-spacing:-.045em;margin:0;color:var(--navy);font-variant-numeric:tabular-nums}\\n    #target small{font-size:.45em;letter-spacing:0;color:var(--muted);font-weight:800}\\n    .best{justify-self:end;text-align:right;color:var(--muted);font-weight:700}\\n    .best strong{display:block;color:var(--ink);font-size:1.15rem}\\n    .game-card{background:var(--paper);border:1px solid rgba(18,48,74,.12);border-radius:22px;box-shadow:var(--shadow);overflow:hidden}\\n    .ruler-legend{display:flex;align-items:center;justify-content:center;gap:18px;flex-wrap:wrap;margin:0 0 10px;color:#526b7d;font-size:.8rem;font-weight:800}\\n    .legend-item{display:inline-flex;align-items:center;gap:6px}.legend-line{display:inline-block;width:24px;border-top:4px solid;border-radius:2px}.legend-quarter{border-color:#2463eb}.legend-eighth{border-color:#0f8a72}.legend-sixteenth{border-color:#d97706}\\n    .ruler-shell{padding:34px 20px 18px;position:relative;overflow:hidden}\\n    #ruler{width:100%;height:260px;display:block;touch-action:none;user-select:none}\\n    .hint{text-align:center;color:var(--muted);font-weight:650;margin:0 0 18px;min-height:24px;padding:0 14px}\\n    .learn-note{display:none;width:min(660px,calc(100% - 28px));margin:-4px auto 18px;padding:12px 16px;border-radius:12px;background:#e8f6fb;border:1px solid #b9ddea;color:#24526c;text-align:center;font-weight:750}\\n    .learn-note.show{display:block}\\n    .type-row{display:none;align-items:center;justify-content:center;gap:10px;margin:0 auto 22px;padding:0 16px}\\n    .type-row.show{display:flex}\\n    #answer{width:min(320px,70vw);height:52px;border:2px solid var(--line);border-radius:12px;padding:0 16px;font-size:1.2rem;font-weight:750;color:var(--ink);background:white}\\n    #answer:focus{outline:3px solid rgba(22,117,169,.2);border-color:var(--blue)}\\n    .primary{height:52px;border:0;border-radius:12px;background:var(--blue);color:white;padding:0 22px;font-weight:850;box-shadow:0 6px 16px rgba(22,117,169,.22)}\\n    .primary:hover{background:#0e668f}\\n    .stats{display:grid;grid-template-columns:repeat(4,1fr);background:#f7fafb;border-top:1px solid var(--line)}\\n    .stat{padding:18px 22px;text-align:center;border-right:1px solid var(--line)}\\n    .stat:last-child{border:0}\\n    .stat-label{display:block;color:var(--muted);font-size:.75rem;text-transform:uppercase;letter-spacing:.1em;font-weight:850;margin-bottom:5px}\\n    .stat-value{font-size:1.45rem;font-weight:900;font-variant-numeric:tabular-nums}\\n    .timer-track{height:7px;background:#dfe8ec;border-radius:5px;overflow:hidden;margin:7px auto 0;max-width:120px}\\n    #timerBar{height:100%;width:100%;background:var(--green);transition:width .08s linear,background .2s}\\n    .strikes{display:flex;justify-content:center;gap:7px;margin-top:5px}\\n    .strike{width:12px;height:12px;border:2px solid #c6d2d9;border-radius:50%}\\n    .strike.on{border-color:var(--red);background:var(--red)}\\n    .controls{display:flex;justify-content:center;gap:10px}\\n    .start{min-width:190px;height:54px;border:0;border-radius:13px;background:var(--yellow);color:#352808;font-weight:900;font-size:1.05rem;box-shadow:0 7px 0 #dca62e}\\n    .start:active{transform:translateY(4px);box-shadow:0 3px 0 #dca62e}\\n    .secondary{height:54px;border:1px solid #bfcfd7;border-radius:13px;background:#fff;color:var(--navy);padding:0 18px;font-weight:800}\\n    .footer-note{text-align:center;color:#718495;font-size:.84rem;margin-top:4px}\\n    dialog{border:0;border-radius:20px;padding:0;width:min(620px,calc(100vw - 28px));color:var(--ink);box-shadow:0 28px 90px rgba(4,25,44,.32)}\\n    dialog::backdrop{background:rgba(6,28,45,.62);backdrop-filter:blur(3px)}\\n    .modal-head{display:flex;align-items:center;justify-content:space-between;padding:22px 24px 16px;border-bottom:1px solid var(--line)}\\n    .modal-head h2{margin:0;font-size:1.4rem}\\n    .close{border:0;background:#edf3f6;color:var(--ink);width:38px;height:38px;border-radius:10px;font-size:1.4rem}\\n    .settings-grid{padding:22px 24px;display:grid;grid-template-columns:1fr 1fr;gap:18px}\\n    label{display:grid;gap:7px;font-weight:800;font-size:.88rem;color:#334e68}\\n    select,input[type=\\\"text\\\"]{height:46px;border:1px solid #bfcfd7;border-radius:10px;background:white;padding:0 12px;color:var(--ink)}\\n    .span-2{grid-column:1\\\/-1}.teacher-tools{grid-column:1\\\/-1;background:#f1f7f9;border:1px solid var(--line);border-radius:13px;padding:14px;display:flex;align-items:center;justify-content:space-between;gap:14px}.teacher-tools p{margin:0;color:var(--muted);font-size:.84rem;line-height:1.35}.compact{height:42px;white-space:nowrap}\\n    .modal-foot{display:flex;justify-content:flex-end;gap:10px;padding:16px 24px 24px}\\n    .result-overlay{position:fixed;inset:0;z-index:20;display:none;place-items:center;background:rgba(7,28,45,.62);padding:16px}\\n    .result-overlay.show{display:grid}\\n    .result-card{background:#fff;border-radius:22px;padding:30px;width:min(520px,100%);text-align:center;box-shadow:var(--shadow)}\\n    .result-card h2{font-size:2rem;margin:0 0 8px}.result-card p{color:var(--muted);font-size:1.05rem;margin:0 0 18px}.result-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}.result-actions .primary{width:100%}.mastery-badge{display:inline-block;border-radius:999px;padding:8px 14px;background:#dff5ed;color:#126b57;font-weight:900;margin:4px 0 16px}.mastery-badge.not-yet{background:#fff1d2;color:#855d08}.result-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:13px;overflow:hidden;margin:0 0 20px}.result-item{background:#f8fbfc;padding:12px}.result-item span{display:block;font-size:.72rem;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;font-weight:850}.result-item strong{display:block;margin-top:3px;font-size:1.15rem}.result-name{font-weight:850!important;color:var(--ink)!important;margin-bottom:6px!important}\\n    .toast{position:fixed;left:50%;top:90px;transform:translate(-50%,-20px);opacity:0;pointer-events:none;padding:12px 18px;border-radius:999px;color:#fff;font-weight:850;z-index:30;transition:.22s;background:var(--green);box-shadow:0 10px 30px rgba(0,0,0,.18)}\\n    .toast.show{opacity:1;transform:translate(-50%,0)}.toast.bad{background:var(--red)}\\n    @media(max-width:760px){header{height:62px}.settings-btn span{display:none}.session-picker{width:100%}.session-btn{min-width:0;padding:10px 5px;font-size:.83rem}.session-btn small{display:none}.challenge-levels{width:100%;justify-content:center;flex-wrap:wrap}.challenge-label{width:100%;text-align:center}.difficulty-note{width:100%;max-width:none;text-align:center}.topline{grid-template-columns:1fr auto}.prompt{grid-column:1\\\/-1;grid-row:1}.mode-pill{grid-row:2}.best{grid-row:2}.ruler-shell{padding:24px 6px 12px}#ruler{height:220px}.stats{grid-template-columns:1fr 1fr}.stat:nth-child(2){border-right:0}.stat:nth-child(-n+2){border-bottom:1px solid var(--line)}.settings-grid{grid-template-columns:1fr}.span-2,.teacher-tools{grid-column:auto}.teacher-tools{align-items:stretch;flex-direction:column}.type-row{flex-direction:column}.type-row .primary,#answer{width:100%}.result-actions{grid-template-columns:1fr}}\\n    @media print{body{background:white}.app,.toast{display:none!important}.result-overlay{position:static;display:block!important;background:white;padding:0}.result-card{box-shadow:none;border:2px solid #12304a;width:100%;max-width:720px;margin:20px auto}.result-actions{display:none}.result-card:after{content:\\\"Ruler Lab \\u2022 CTE Makes School Matter\\\";display:block;margin-top:24px;color:#62758a;font-size:.82rem}}\\n    @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\\n  <\\\/style>\\n<\\\/head>\\n<body>\\n  \n\n<div class=\\\"app\\\">\\n    \n\n<header>\\n      \n\n<div class=\\\"brand\\\"><span class=\\\"brand-icon\\\" aria-hidden=\\\"true\\\"><\\\/span>Ruler Lab <span style=\\\"font-weight:600;opacity:.65\\\">| Woodshop<\\\/span><\\\/div>\\n      \n\n<div class=\\\"header-actions\\\">\\n        <button class=\\\"icon-btn\\\" id=\\\"fullscreen\\\" aria-label=\\\"Enter full screen\\\" title=\\\"Full screen\\\">\\u26f6<\\\/button>\\n        <button class=\\\"settings-btn\\\" id=\\\"openSettings\\\" aria-haspopup=\\\"dialog\\\">\\u2699 <span>Settings<\\\/span><\\\/button>\\n      <\\\/div>\\n    <\\\/header>\\n    <main>\\n      \n\n<nav class=\\\"session-picker\\\" aria-label=\\\"Learning mode\\\">\\n        <button class=\\\"session-btn\\\" data-session=\\\"learn\\\" aria-selected=\\\"false\\\">Learn<small>Guided help<\\\/small><\\\/button>\\n        <button class=\\\"session-btn\\\" data-session=\\\"practice\\\" aria-selected=\\\"true\\\">Practice<small>Build accuracy<\\\/small><\\\/button>\\n        <button class=\\\"session-btn\\\" data-session=\\\"challenge\\\" aria-selected=\\\"false\\\">Shop Challenge<small>20 questions<\\\/small><\\\/button>\\n      <\\\/nav>\\n      \n\n<div class=\\\"challenge-levels\\\" id=\\\"challengeLevels\\\" hidden>\\n        <span class=\\\"challenge-label\\\">Difficulty<\\\/span>\\n        <button class=\\\"level-btn\\\" data-level=\\\"easy\\\" aria-selected=\\\"false\\\">Easy<\\\/button>\\n        <button class=\\\"level-btn\\\" data-level=\\\"medium\\\" aria-selected=\\\"true\\\">Medium<\\\/button>\\n        <button class=\\\"level-btn\\\" data-level=\\\"hard\\\" aria-selected=\\\"false\\\">Hard<\\\/button>\\n        <span class=\\\"difficulty-note\\\" id=\\\"difficultyNote\\\">Sixteenths and one-step shop math<\\\/span>\\n      <\\\/div>\\n      \n\n<div class=\\\"topline\\\">\\n        \n\n<div class=\\\"mode-pill\\\" id=\\\"modeLabel\\\">Find the mark<\\\/div>\\n        \n\n<div class=\\\"prompt\\\" aria-live=\\\"polite\\\">\\n          \n\n<p class=\\\"eyebrow\\\" id=\\\"promptLabel\\\">Ready to measure?<\\\/p>\\n          \n\n<p id=\\\"target\\\">Press Start<\\\/p>\\n        <\\\/div>\\n        \n\n<div class=\\\"best\\\">Session best<strong id=\\\"best\\\">0<\\\/strong><\\\/div>\\n      <\\\/div>\\n      \n\n<div class=\\\"controls\\\">\\n        <button class=\\\"start\\\" id=\\\"start\\\">Start game<\\\/button>\\n        <button class=\\\"secondary\\\" id=\\\"newQuestion\\\" hidden>Skip<\\\/button>\\n      <\\\/div>\\n      \n\n<section class=\\\"game-card\\\" aria-label=\\\"Ruler game\\\">\\n        \n\n<div class=\\\"ruler-shell\\\">\\n          \n\n<div class=\\\"ruler-legend\\\" id=\\\"rulerLegend\\\" aria-label=\\\"Ruler mark color key\\\"><\\\/div>\\n          <canvas id=\\\"ruler\\\" tabindex=\\\"0\\\" role=\\\"slider\\\" aria-label=\\\"Interactive inch ruler. Use arrow keys and Enter, or move and click or tap, to choose a measurement.\\\" aria-valuemin=\\\"0\\\" aria-valuemax=\\\"64\\\" aria-valuenow=\\\"0\\\"><\\\/canvas>\\n        <\\\/div>\\n        \n\n<p class=\\\"hint\\\" id=\\\"hint\\\">Choose your settings, then press Start.<\\\/p>\\n        \n\n<div class=\\\"learn-note\\\" id=\\\"learnNote\\\"><\\\/div>\\n        \n\n<div class=\\\"type-row\\\" id=\\\"typeRow\\\">\\n          <input id=\\\"answer\\\" type=\\\"text\\\" inputmode=\\\"text\\\" autocomplete=\\\"off\\\" placeholder='Example: 2 3\\\/8 or 2.375' aria-label=\\\"Measurement answer\\\">\\n          <button class=\\\"primary\\\" id=\\\"submit\\\">Check answer<\\\/button>\\n        <\\\/div>\\n        \n\n<div class=\\\"stats\\\">\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\">Score<\\\/span><span class=\\\"stat-value\\\" id=\\\"score\\\">0<\\\/span><\\\/div>\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\">Time<\\\/span><span class=\\\"stat-value\\\" id=\\\"time\\\">\\u2014<\\\/span>\n\n<div class=\\\"timer-track\\\">\n\n<div id=\\\"timerBar\\\"><\\\/div><\\\/div><\\\/div>\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\" id=\\\"levelLabel\\\">Level<\\\/span><span class=\\\"stat-value\\\" id=\\\"level\\\">1<\\\/span><\\\/div>\\n          \n\n<div class=\\\"stat\\\"><span class=\\\"stat-label\\\" id=\\\"strikeLabel\\\">Strikes<\\\/span>\n\n<div class=\\\"strikes\\\" id=\\\"strikes\\\"><i class=\\\"strike\\\"><\\\/i><i class=\\\"strike\\\"><\\\/i><i class=\\\"strike\\\"><\\\/i><\\\/div><span class=\\\"stat-value\\\" id=\\\"mistakeCount\\\" hidden>0<\\\/span><\\\/div>\\n        <\\\/div>\\n      <\\\/section>\\n      \n\n<p class=\\\"footer-note\\\">Ad-free \\u2022 No sign-in \\u2022 CTE Makes School Matter<\\\/p>\\n    <\\\/main>\\n  <\\\/div>\\n\\n  <dialog id=\\\"settingsDialog\\\">\\n    \n\n<form method=\\\"dialog\\\" id=\\\"settingsForm\\\">\\n      \n\n<div class=\\\"modal-head\\\">\n\n<h2>Game settings<\\\/h2><button class=\\\"close\\\" value=\\\"cancel\\\" aria-label=\\\"Close settings\\\">\\u00d7<\\\/button><\\\/div>\\n      \n\n<div class=\\\"settings-grid\\\">\\n        <label class=\\\"span-2\\\">Student name <input id=\\\"studentName\\\" type=\\\"text\\\" maxlength=\\\"40\\\" placeholder=\\\"Optional \\u2014 appears on results\\\"><\\\/label>\\n        <label>Game mode<select id=\\\"mode\\\"><option value=\\\"find\\\">Find the mark<\\\/option><option value=\\\"type\\\">Read and type<\\\/option><\\\/select><\\\/label>\\n        <label>Question precision<select id=\\\"questions\\\"><option value=\\\"1\\\">Whole inches<\\\/option><option value=\\\"2\\\">Halves<\\\/option><option value=\\\"4\\\">Quarters<\\\/option><option value=\\\"8\\\">Eighths<\\\/option><option value=\\\"16\\\" selected>Sixteenths<\\\/option><option value=\\\"32\\\">Thirty-seconds<\\\/option><option value=\\\"64\\\">Sixty-fourths<\\\/option><\\\/select><\\\/label>\\n        <label>Ruler marks<select id=\\\"marks\\\"><option value=\\\"2\\\">Halves<\\\/option><option value=\\\"4\\\">Quarters<\\\/option><option value=\\\"8\\\">Eighths<\\\/option><option value=\\\"16\\\" selected>Sixteenths<\\\/option><option value=\\\"32\\\">Thirty-seconds<\\\/option><option value=\\\"64\\\">Sixty-fourths<\\\/option><\\\/select><\\\/label>\\n        <label>Ruler length<select id=\\\"length\\\"><option value=\\\"auto\\\">Automatic<\\\/option><option value=\\\"1\\\">1 inch<\\\/option><option value=\\\"2\\\">2 inches<\\\/option><option value=\\\"3\\\">3 inches<\\\/option><option value=\\\"4\\\" selected>4 inches<\\\/option><option value=\\\"6\\\">6 inches<\\\/option><option value=\\\"8\\\">8 inches<\\\/option><option value=\\\"10\\\">10 inches<\\\/option><option value=\\\"12\\\">12 inches<\\\/option><\\\/select><\\\/label>\\n        <label>Answer notation<select id=\\\"notation\\\"><option value=\\\"fraction\\\">Fractions<\\\/option><option value=\\\"decimal\\\">Decimals<\\\/option><\\\/select><\\\/label>\\n        <label>Fraction style<select id=\\\"fractionStyle\\\"><option value=\\\"both\\\">Mixed practice<\\\/option><option value=\\\"simplified\\\">Simplified<\\\/option><option value=\\\"unsimplified\\\">Unsimplified<\\\/option><\\\/select><\\\/label>\\n        <label>Timer<select id=\\\"timer\\\"><option value=\\\"on\\\">On<\\\/option><option value=\\\"off\\\">Off<\\\/option><\\\/select><\\\/label>\\n        <label>Sounds<select id=\\\"sound\\\"><option value=\\\"on\\\">On<\\\/option><option value=\\\"off\\\">Off<\\\/option><\\\/select><\\\/label>\\n        <label>Measure twice<select id=\\\"measureTwice\\\"><option value=\\\"off\\\">Off<\\\/option><option value=\\\"on\\\">On<\\\/option><\\\/select><\\\/label>\\n        \n\n<div class=\\\"teacher-tools\\\">\n\n<p><strong>Teacher preset link<\\\/strong><br \/>Opens with these settings already selected.<\\\/p><button type=\\\"button\\\" class=\\\"secondary compact\\\" id=\\\"copyPreset\\\">Copy link<\\\/button><\\\/div>\\n      <\\\/div>\\n      \n\n<div class=\\\"modal-foot\\\"><button class=\\\"secondary\\\" value=\\\"cancel\\\">Cancel<\\\/button><button class=\\\"primary\\\" id=\\\"saveSettings\\\" value=\\\"default\\\">Save settings<\\\/button><\\\/div>\\n    <\\\/form>\\n  <\\\/dialog>\\n  \n\n<div class=\\\"result-overlay\\\" id=\\\"gameOver\\\">\n\n<div class=\\\"result-card\\\">\\n    \n\n<p class=\\\"eyebrow\\\" id=\\\"resultEyebrow\\\">Session complete<\\\/p>\n\n<h2 id=\\\"finalScore\\\">Score: 0<\\\/h2>\n\n<p class=\\\"result-name\\\" id=\\\"resultName\\\"><\\\/p>\n\n<div class=\\\"mastery-badge\\\" id=\\\"masteryBadge\\\">Keep practicing<\\\/div>\\n    \n\n<div class=\\\"result-grid\\\">\n\n<div class=\\\"result-item\\\"><span>Accuracy<\\\/span><strong id=\\\"resultAccuracy\\\">0%<\\\/strong><\\\/div>\n\n<div class=\\\"result-item\\\"><span>Correct<\\\/span><strong id=\\\"resultCorrect\\\">0<\\\/strong><\\\/div>\n\n<div class=\\\"result-item\\\"><span>Precision<\\\/span><strong id=\\\"resultPrecision\\\">1\\\/16\\u2033<\\\/strong><\\\/div>\n\n<div class=\\\"result-item\\\"><span>Date<\\\/span><strong id=\\\"resultDate\\\">\\u2014<\\\/strong><\\\/div><\\\/div>\\n    \n\n<p id=\\\"finalMessage\\\">Nice work. Every mark gets easier with practice.<\\\/p>\n\n<div class=\\\"result-actions\\\"><button class=\\\"secondary\\\" id=\\\"printResults\\\">Print \\\/ Save PDF<\\\/button><button class=\\\"primary\\\" id=\\\"playAgain\\\">Try again<\\\/button><\\\/div>\\n  <\\\/div><\\\/div>\\n  \n\n<div class=\\\"toast\\\" id=\\\"toast\\\" role=\\\"status\\\" aria-live=\\\"polite\\\"><\\\/div>\\n\\n  <script>\\n  (()=>{\\n    const $=s=>document.querySelector(s), canvas=$('#ruler'), ctx=canvas.getContext('2d');\\n    const els={target:$('#target'),prompt:$('#promptLabel'),hint:$('#hint'),note:$('#learnNote'),score:$('#score'),level:$('#level'),levelLabel:$('#levelLabel'),time:$('#time'),timerBar:$('#timerBar'),strikes:$('#strikes'),mistakes:$('#mistakeCount'),strikeLabel:$('#strikeLabel'),start:$('#start'),skip:$('#newQuestion'),typeRow:$('#typeRow'),answer:$('#answer'),modeLabel:$('#modeLabel'),toast:$('#toast'),best:$('#best')};\\n    const inputs=['mode','questions','marks','length','notation','fractionStyle','timer','sound','measureTwice'];\\n    const defaults={session:'practice',challengeLevel:'medium',mode:'find',questions:'16',marks:'16',length:'4',notation:'fraction',fractionStyle:'both',timer:'on',sound:'on',measureTwice:'off',studentName:''};\\n    let settings={...defaults,...JSON.parse(localStorage.getItem('rulerLabSettings')||'{}')};\\n    const preset=new URLSearchParams(location.search);['session','challengeLevel',...inputs].forEach(k=>{if(preset.has(k))settings[k]=preset.get(k)});if(+settings.questions>+settings.marks)settings.marks=settings.questions;\\n    let state={playing:false,score:0,correct:0,answered:0,strikes:0,level:1,answerUnits:0,answerDen:+settings.questions,hoverUnits:null,shownUnits:null,firstMeasure:null,questionKind:'find',expectsFind:true,startTime:0,duration:10,raf:0,locked:false};\\n    let geom={x:0,y:0,w:0,h:0,inch:0,length:4};\\n    inputs.forEach(id=>$('#'+id).value=settings[id]??defaults[id]);$('#studentName').value=settings.studentName||'';\\n    function gcd(a,b){while(b)[a,b]=[b,a%b];return a||1}\\n    function autoLength(){const width=canvas.clientWidth;return width<520?2:width<820?3:width<1120?4:6}\\n    function rulerLength(){return settings.length==='auto'?autoLength():+settings.length}\\n    function challengeDen(){return settings.challengeLevel==='easy'?8:16}\\n    function rulerMarks(){return settings.session==='challenge'?16:+settings.marks}\\n    function format(units,denom=+settings.questions,forceStyle=settings.fractionStyle){\\n      const value=units\\\/denom;if(settings.notation==='decimal')return value.toFixed(4).replace(\\\/0+$\\\/,'').replace(\\\/\\\\.$\\\/,'')+'\\u2033';\\n      const whole=Math.floor(units\\\/denom),n=units%denom;if(!n)return whole+'\\u2033';let num=n,d=denom;\\n      const style=forceStyle==='both'?(Math.random()<.5?'simplified':'unsimplified'):forceStyle;if(style==='simplified'){const g=gcd(num,d);num\\\/=g;d\\\/=g}\\n      return(whole?whole+' ':'')+num+'\\\/'+d+'\\u2033';\\n    }\\n    function precisionName(){if(settings.session==='challenge')return settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1)+' \\u00b7 1\\\/'+challengeDen()+'\\u2033';return({1:'Whole',2:'1\\\/2\\u2033',4:'1\\\/4\\u2033',8:'1\\\/8\\u2033',16:'1\\\/16\\u2033',32:'1\\\/32\\u2033',64:'1\\\/64\\u2033'})[settings.questions]}\\n    function unitsAtMarks(units,den=state.answerDen){return Math.round(units*rulerMarks()\\\/den)}\\n    function tickFamily(frac,marks){if(frac===0||frac===marks\\\/2)return'major';const pos=frac\\\/marks;if(Number.isInteger(pos*4))return'quarter';if(Number.isInteger(pos*8))return'eighth';if(Number.isInteger(pos*16))return'sixteenth';return'fine'}\\n    function tickColor(family){const colors={major:'#12304a',quarter:'#2463eb',eighth:'#0f8a72',sixteenth:'#d97706',fine:'#8797a4'};return settings.session==='learn'?colors[family]:'#12304a'}\\n    function updateLegend(){const marks=rulerMarks(),items=[];if(settings.session==='learn'&&marks>=4)items.push(['quarter','\\u00bc-inch']);if(settings.session==='learn'&&marks>=8)items.push(['eighth','\\u215b-inch']);if(settings.session==='learn'&&marks>=16)items.push(['sixteenth','\\u00b9\\u2044\\u2081\\u2086-inch']);const legend=$('#rulerLegend');legend.hidden=!items.length;legend.innerHTML=items.map(([c,t])=>'<span class=\\\"legend-item\\\"><i class=\\\"legend-line legend-'+c+'\\\"><\\\/i>'+t+'<\\\/span>').join('')}\\n    function draw(){\\n      const dpr=Math.min(devicePixelRatio||1,2),rect=canvas.getBoundingClientRect();\\n      if(canvas.width!==Math.round(rect.width*dpr)||canvas.height!==Math.round(rect.height*dpr)){canvas.width=Math.round(rect.width*dpr);canvas.height=Math.round(rect.height*dpr)}\\n      ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,rect.width,rect.height);const len=rulerLength(),pad=Math.max(20,rect.width*.025),top=26,base=rect.height-35,usable=rect.width-pad*2;geom={x:pad,y:top,w:usable,h:base-top,inch:usable\\\/len,length:len};\\n      ctx.fillStyle='#fff9df';ctx.strokeStyle='#12304a';ctx.lineWidth=2.5;roundRect(ctx,pad,top,usable,base-top,8);ctx.fill();ctx.stroke();const marks=rulerMarks(),total=len*marks;\\n      for(let i=0;i<=total;i++){const x=pad+i\\\/total*usable,frac=i%marks,family=tickFamily(frac,marks);let tick;if(i===0||i===total||frac===0)tick=78;else{let pow=0,n=frac;while(n%2===0){pow++;n\\\/=2}tick=Math.min(20+pow*12,64)}ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,top+tick);ctx.strokeStyle=tickColor(family);ctx.lineWidth=family==='major'?2.8:2.2;ctx.stroke();if(frac===0&&i>0){ctx.fillStyle='#12304a';ctx.font='800 25px Inter,system-ui';ctx.textAlign='center';ctx.fillText(String(i\\\/marks),x,top+104)}}\\n      const active=state.playing&&!state.locked&&state.expectsFind?state.hoverUnits:null;if(active!==null){const x=pad+active\\\/(marks*len)*usable;ctx.fillStyle='rgba(22,134,111,.22)';ctx.fillRect(Math.max(pad,x-geom.inch\\\/marks\\\/2),top+2,Math.max(4,geom.inch\\\/marks),base-top-4);ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#16866f';ctx.lineWidth=4;ctx.stroke();canvas.setAttribute('aria-valuenow',String(active));canvas.setAttribute('aria-valuetext',format(active,marks,'simplified'))}\\n      canvas.setAttribute('aria-valuemax',String(marks*len));if(state.shownUnits!==null){const x=pad+state.shownUnits\\\/(marks*len)*usable;ctx.beginPath();ctx.moveTo(x,top-5);ctx.lineTo(x,base+2);ctx.strokeStyle='#d44949';ctx.lineWidth=4;ctx.stroke();ctx.fillStyle='#d44949';ctx.beginPath();ctx.moveTo(x-8,top-10);ctx.lineTo(x+8,top-10);ctx.lineTo(x,top+1);ctx.fill()}\\n      if(state.questionKind==='read'&&state.playing){const m=unitsAtMarks(state.answerUnits),x=pad+m\\\/(marks*len)*usable;ctx.fillStyle='#a11d48';ctx.beginPath();ctx.moveTo(x-11,top-15);ctx.lineTo(x+11,top-15);ctx.lineTo(x,top+1);ctx.fill();ctx.beginPath();ctx.moveTo(x,top);ctx.lineTo(x,base);ctx.strokeStyle='#a11d48';ctx.lineWidth=3;ctx.stroke()}updateLegend()\\n    }\\n    function roundRect(c,x,y,w,h,r){c.beginPath();c.roundRect?c.roundRect(x,y,w,h,r):c.rect(x,y,w,h)}\\n    function randomUnits(max,avoid=0){let u;do{u=Math.max(1,Math.floor(Math.random()*max))}while(u===avoid);return u}\\n    function learningHelp(units,den){const whole=Math.floor(units\\\/den),n=units%den;if(!n)return'Find the long line for '+whole+' whole inch'+(whole===1?'':'es')+'.';const g=gcd(n,den),sn=n\\\/g,sd=den\\\/g;return'Break it down: '+(whole?whole+' whole inch'+(whole===1?'':'es')+' plus ':'')+sn+' of '+sd+' equal parts.'}\\n    function showType(on){els.typeRow.classList.toggle('show',on);if(on)setTimeout(()=>els.answer.focus(),60)}\\n    function newQuestion(){\\n      state.locked=false;state.shownUnits=null;state.hoverUnits=null;state.firstMeasure=null;els.answer.value='';els.note.classList.remove('show');const den=settings.session==='challenge'?challengeDen():+settings.questions,len=rulerLength();state.answerDen=den;\\n      let kind=settings.mode==='find'?'find':'read';if(settings.session==='challenge'){const sets={easy:['find','read','cutlist','find','midpoint'],medium:['find','read','cutlist','midpoint','remaining'],hard:['read','cutlist','remaining','midpoint','multiCut','difference']};const kinds=sets[settings.challengeLevel];kind=kinds[state.answered%kinds.length]}\\n      state.questionKind=kind;state.expectsFind=kind==='find'||kind==='cutlist';\\n      if(kind==='find'||kind==='read'||kind==='cutlist'){state.answerUnits=randomUnits(len*den,state.answerUnits)}\\n      if(kind==='find'){els.prompt.textContent='Click the measurement';els.target.textContent=format(state.answerUnits,den);els.hint.textContent=settings.measureTwice==='on'?'Measure it twice. Both selections must match.':'Move over the ruler, then click or tap the correct mark.';showType(false)}\\n      if(kind==='read'){els.prompt.textContent='Read the marked line';els.target.innerHTML='<small>Type your answer below<\\\/small>';els.hint.textContent='Enter a fraction or decimal measurement.';showType(true)}\\n      if(kind==='cutlist'){const parts=['Side rail','Shelf support','Face-frame piece','Drawer spacer'];els.prompt.textContent='Cut list: '+parts[state.answered%parts.length];els.target.textContent=format(state.answerUnits,den,'simplified');els.hint.textContent='Select this cut length on the ruler.';showType(false)}\\n      if(kind==='midpoint'){state.answerUnits=randomUnits(Math.max(2,Math.floor(6*den)));const boardUnits=state.answerUnits*2;els.prompt.textContent='Find the midpoint';els.target.textContent=format(boardUnits,den,'simplified')+' board';els.hint.textContent='What measurement marks the exact center?';showType(true)}\\n      if(kind==='remaining'){const totalInches=6+Math.floor(Math.random()*7),totalUnits=totalInches*den,cutUnits=randomUnits(totalUnits-1);state.answerUnits=totalUnits-cutUnits;els.prompt.textContent='Board remaining';els.target.style.fontSize='clamp(1.55rem,3.4vw,3.2rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \\u2212 '+format(cutUnits,den,'simplified');els.hint.textContent='Type the board length left after the cut.';showType(true)}else els.target.style.fontSize='';\\n      if(kind==='multiCut'){const totalInches=10+Math.floor(Math.random()*3),totalUnits=totalInches*den,maxPiece=Math.floor((totalUnits-den)\\\/3),pieceUnits=den+Math.floor(Math.random()*Math.max(1,maxPiece-den));state.answerUnits=totalUnits-pieceUnits*2;els.prompt.textContent='Two pieces from one board';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(totalUnits,den,'simplified')+' \\u2212 2 \\u00d7 '+format(pieceUnits,den,'simplified');els.hint.textContent='Ignore saw kerf. How much stock remains?';showType(true)}\\n      if(kind==='difference'){const planned=2*den+randomUnits(4*den),gap=randomUnits(den),actual=planned-gap;state.answerUnits=gap;els.prompt.textContent='Measurement difference';els.target.style.fontSize='clamp(1.45rem,3vw,2.8rem)';els.target.textContent=format(planned,den,'simplified')+' planned \\u00b7 '+format(actual,den,'simplified')+' actual';els.hint.textContent='How much shorter is the actual piece?';showType(true)}\\n      if(settings.session==='learn'){els.note.textContent=learningHelp(state.answerUnits,den);els.note.classList.add('show')}\\n      setStatMode();startClock();draw();\\n    }\\n    function startGame(){cancelAnimationFrame(state.raf);state={...state,playing:true,score:0,correct:0,answered:0,strikes:0,level:1,locked:false,shownUnits:null,firstMeasure:null};els.score.textContent='0';els.start.textContent='Restart';els.skip.hidden=settings.session==='learn';$('#gameOver').classList.remove('show');updateStrikes();newQuestion()}\\n    function startClock(){cancelAnimationFrame(state.raf);state.duration=state.expectsFind?Math.max(3,11-state.level):Math.max(6,20-(state.level-1)*2);if(settings.session==='challenge')state.duration+=settings.challengeLevel==='easy'?6:settings.challengeLevel==='hard'?-2:0;state.startTime=performance.now();if(settings.timer==='off'||settings.session==='learn'){els.time.textContent='\\u221e';els.timerBar.style.width='100%';els.timerBar.style.background='#16866f';return}const tick=now=>{if(!state.playing||state.locked)return;const left=Math.max(0,state.duration-(now-state.startTime)\\\/1000),pct=left\\\/state.duration*100;els.time.textContent=Math.ceil(left)+'s';els.timerBar.style.width=pct+'%';els.timerBar.style.background=pct<25?'#d44949':pct<55?'#e7ad29':'#16866f';if(left<=0){check(null);return}state.raf=requestAnimationFrame(tick)};state.raf=requestAnimationFrame(tick)}\\n    function parseAnswer(v){v=v.trim().replace(\\\/[\\u2033\\\"]\\\/g,'');if(!v)return null;if(\\\/^\\\\d*\\\\.?\\\\d+$\\\/.test(v))return+v;const m=v.match(\\\/^(?:(\\\\d+)\\\\s+)?(\\\\d+)\\\\\\\/(\\\\d+)$\\\/);if(!m)return null;return+(m[1]||0)+(+m[2]\\\/+m[3])}\\n    function check(selectedMark){\\n      if(!state.playing||state.locked)return;if(state.expectsFind&&selectedMark!==null&&settings.measureTwice==='on'){if(state.firstMeasure===null){state.firstMeasure=selectedMark;els.hint.textContent='First measurement saved. Move away, then measure it again.';toast('First measurement saved',false);return}if(state.firstMeasure!==selectedMark){state.firstMeasure=null;els.hint.textContent='Those measurements did not match. Start again.';toast('Measurements did not match',true);return}}\\n      state.locked=true;cancelAnimationFrame(state.raf);let ok=false;if(state.expectsFind)ok=selectedMark!==null&&selectedMark===unitsAtMarks(state.answerUnits);else{const v=parseAnswer(els.answer.value);ok=v!==null&&Math.abs(v-state.answerUnits\\\/state.answerDen)<1e-7}\\n      state.answered++;if(ok){state.correct++;state.score+=state.level*10;els.score.textContent=state.score;toast('Correct! +'+state.level*10,false);sound(true)}else{state.strikes++;const correct=format(state.answerUnits,state.answerDen,'simplified');toast(selectedMark===null?'Time\\u2019s up \\u2014 '+correct:'Correct answer: '+correct,true);sound(false);if(state.expectsFind)state.shownUnits=unitsAtMarks(state.answerUnits);draw()}\\n      if(settings.session!=='challenge'&&state.correct>0&&state.correct%10===0){state.level=Math.min(10,Math.floor(state.correct\\\/10)+1);els.level.textContent=state.level}\\n      updateStrikes();if(settings.session==='learn'&&!ok)els.note.textContent='The correct mark is highlighted. '+learningHelp(state.answerUnits,state.answerDen);\\n      const done=settings.session==='challenge'&&state.answered>=20||settings.session==='practice'&&state.strikes>=3;if(done)setTimeout(endGame,1100);else setTimeout(newQuestion,ok?700:1500)\\n    }\\n    function setStatMode(){const challenge=settings.session==='challenge';els.levelLabel.textContent=challenge?'Question':'Level';els.level.textContent=challenge?(Math.min(20,state.answered+1)+'\\\/20'):state.level;els.strikeLabel.textContent=challenge?'Missed':'Strikes';els.strikes.hidden=challenge;els.mistakes.hidden=!challenge;if(challenge)els.mistakes.textContent=state.strikes}\\n    function updateStrikes(){[...els.strikes.children].forEach((x,i)=>x.classList.toggle('on',i<state.strikes));els.mistakes.textContent=state.strikes;setStatMode()}\\n    function endGame(){state.playing=false;cancelAnimationFrame(state.raf);const total=Math.max(1,state.answered),accuracy=Math.round(state.correct\\\/total*100),mastered=settings.session==='challenge'&&accuracy>=90,best=Math.max(state.score,+localStorage.getItem('rulerLabBest')||0);localStorage.setItem('rulerLabBest',best);els.best.textContent=best;$('#resultEyebrow').textContent=settings.session==='challenge'?'Shop challenge complete':'Practice complete';$('#finalScore').textContent=settings.session==='challenge'?(mastered?'Measurement mastered':'Keep building accuracy'):'Score: '+state.score;$('#resultName').textContent=settings.studentName||'Ruler Lab Student';$('#masteryBadge').textContent=settings.session==='challenge'?(mastered?'Mastered \\u2022 90%+':'Not yet mastered \\u2022 Goal: 90%'):'Practice session';$('#masteryBadge').classList.toggle('not-yet',!mastered);$('#resultAccuracy').textContent=accuracy+'%';$('#resultCorrect').textContent=state.correct+' \\\/ '+state.answered;$('#resultPrecision').textContent=precisionName();$('#resultDate').textContent=new Date().toLocaleDateString();$('#finalMessage').textContent=mastered?'Ready to apply this skill in the shop.':accuracy>=75?'Very close\\u2014review the marks you missed and try again.':'Slow down, count the spaces, and measure twice.';$('#gameOver').classList.add('show');els.start.textContent='Start '+sessionName();els.skip.hidden=true}\\n    function sessionName(){return settings.session==='challenge'?'challenge':settings.session}\\n    function updateSessionUI(){document.querySelectorAll('.session-btn').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.session===settings.session)));const challenge=settings.session==='challenge',notes={easy:'Eighth-inch questions on a full sixteenth-inch ruler',medium:'Sixteenths, midpoints, and one-step remaining length',hard:'Sixteenths, multi-cut stock, differences, and mixed calculations'};$('#challengeLevels').hidden=!challenge;document.querySelectorAll('.level-btn').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.level===settings.challengeLevel)));$('#difficultyNote').textContent=notes[settings.challengeLevel];els.modeLabel.textContent=settings.session==='learn'?'Color-guided learning':challenge?'Shop Challenge \\u00b7 '+settings.challengeLevel[0].toUpperCase()+settings.challengeLevel.slice(1):'Independent practice';els.start.textContent='Start '+sessionName();setStatMode();if(!state.playing){els.prompt.textContent=challenge?'20-question '+settings.challengeLevel+' challenge':settings.session==='learn'?'Learn each ruler mark':'Build speed and accuracy';els.target.textContent='Press Start';els.hint.textContent=challenge?notes[settings.challengeLevel]+'.':settings.session==='learn'?'Colors identify quarters, eighths, and sixteenths on the ruler.':'Practice on a standard ruler without color clues.';showType(false);draw()}}\\n    let toastTimer;function toast(msg,bad){els.toast.textContent=msg;els.toast.className='toast show'+(bad?' bad':'');clearTimeout(toastTimer);toastTimer=setTimeout(()=>els.toast.className='toast',1100)}\\n    function sound(good){if(settings.sound!=='on')return;try{const ac=new(window.AudioContext||window.webkitAudioContext)(),o=ac.createOscillator(),g=ac.createGain();o.frequency.value=good?660:190;o.type=good?'sine':'triangle';g.gain.setValueAtTime(.08,ac.currentTime);g.gain.exponentialRampToValueAtTime(.001,ac.currentTime+.16);o.connect(g);g.connect(ac.destination);o.start();o.stop(ac.currentTime+.17)}catch(e){}}\\n    function pointer(e){const rect=canvas.getBoundingClientRect(),x=Math.max(geom.x,Math.min(geom.x+geom.w,e.clientX-rect.left)),marks=rulerMarks();return Math.round((x-geom.x)\\\/geom.w*(marks*geom.length))}\\n    canvas.addEventListener('pointermove',e=>{if(state.playing&&!state.locked&&state.expectsFind){state.hoverUnits=pointer(e);draw()}});canvas.addEventListener('pointerleave',()=>{if(state.expectsFind){state.hoverUnits=null;draw()}});canvas.addEventListener('pointerdown',e=>{if(state.playing&&!state.locked&&state.expectsFind){e.preventDefault();const u=pointer(e);state.hoverUnits=u;draw();check(u)}});\\n    canvas.addEventListener('keydown',e=>{if(!state.playing||state.locked||!state.expectsFind)return;const max=rulerMarks()*rulerLength();if(e.key==='ArrowRight'||e.key==='ArrowUp'){e.preventDefault();state.hoverUnits=Math.min(max,(state.hoverUnits??0)+1);draw()}else if(e.key==='ArrowLeft'||e.key==='ArrowDown'){e.preventDefault();state.hoverUnits=Math.max(0,(state.hoverUnits??0)-1);draw()}else if(e.key==='Enter'||e.key===' '){e.preventDefault();check(state.hoverUnits??0)}});\\n    $('#submit').onclick=()=>check(0);els.answer.addEventListener('keydown',e=>{if(e.key==='Enter')check(0)});els.start.onclick=startGame;$('#playAgain').onclick=startGame;$('#printResults').onclick=()=>window.print();els.skip.onclick=()=>check(null);\\n    document.querySelectorAll('.session-btn').forEach(b=>b.onclick=()=>{settings.session=b.dataset.session;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\\n    document.querySelectorAll('.level-btn').forEach(b=>b.onclick=()=>{settings.challengeLevel=b.dataset.level;localStorage.setItem('rulerLabSettings',JSON.stringify(settings));if(state.playing)startGame();else updateSessionUI()});\\n    $('#openSettings').onclick=()=>$('#settingsDialog').showModal();$('#saveSettings').onclick=e=>{e.preventDefault();inputs.forEach(id=>settings[id]=$('#'+id).value);settings.studentName=$('#studentName').value.trim();if(+settings.questions>+settings.marks){settings.marks=settings.questions;$('#marks').value=settings.marks;toast('Ruler marks adjusted to match questions',false)}localStorage.setItem('rulerLabSettings',JSON.stringify(settings));$('#settingsDialog').close();if(state.playing)startGame();else updateSessionUI()};\\n    $('#copyPreset').onclick=async()=>{inputs.forEach(id=>settings[id]=$('#'+id).value);const url=new URL(location.href);url.search='';url.searchParams.set('session',settings.session);url.searchParams.set('challengeLevel',settings.challengeLevel);inputs.forEach(k=>url.searchParams.set(k,settings[k]));try{await navigator.clipboard.writeText(url.href);toast('Teacher preset link copied',false)}catch(e){prompt('Copy this teacher preset link:',url.href)}};\\n    $('#questions').onchange=()=>{if(+$('#questions').value>+$('#marks').value)$('#marks').value=$('#questions').value};$('#marks').onchange=()=>{if(+$('#marks').value<+$('#questions').value)$('#questions').value=$('#marks').value};$('#fullscreen').onclick=()=>{if(!document.fullscreenElement)document.documentElement.requestFullscreen?.();else document.exitFullscreen?.()};window.addEventListener('resize',draw);\\n    els.best.textContent=localStorage.getItem('rulerLabBest')||'0';updateSessionUI();\\n    if(document.modelContext?.registerTool){const register=t=>Promise.resolve(document.modelContext.registerTool(t)).catch(()=>{});register({name:'start_ruler_game',title:'Start ruler game',description:'Start or restart the visible ruler learning, practice, or shop challenge session.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=>{if(v?.session)settings.session=v.session;if(v?.challengeLevel)settings.challengeLevel=v.challengeLevel;updateSessionUI();startGame();return{status:'started',session:settings.session,challengeLevel:settings.challengeLevel,precision:settings.session==='challenge'?challengeDen():settings.questions}}});register({name:'configure_ruler_game',title:'Configure ruler game',description:'Change the visible ruler game settings.',inputSchema:{type:'object',properties:{session:{enum:['learn','practice','challenge']},challengeLevel:{enum:['easy','medium','hard']},mode:{enum:['find','type']},questions:{enum:['1','2','4','8','16','32','64']},marks:{enum:['2','4','8','16','32','64']},length:{enum:['auto','1','2','3','4','6','8','10','12']},notation:{enum:['fraction','decimal']},timer:{enum:['on','off']},measureTwice:{enum:['on','off']}},additionalProperties:false},annotations:{readOnlyHint:false,untrustedContentHint:false},execute:v=>{settings={...settings,...v};if(+settings.questions>+settings.marks)settings.marks=settings.questions;inputs.forEach(id=>$('#'+id).value=settings[id]);localStorage.setItem('rulerLabSettings',JSON.stringify(settings));updateSessionUI();return{status:'configured',settings}}})}\\n  })();\\n  <\\\/script>\\n<\\\/body>\\n<\\\/html>\"}}]}]}]}],\"version\":\"4.5.21\",\"yooessentialsVersion\":\"2.2.25\"} --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Teacher Resources Teacher Forms Canvas Safety Course CTE Lab Safety Forms CTSO Advisor Form Equipment Repair Form Proposal for Concurrent Enrollment Course Form Utah ACTE Membership Form 2024-25 WCSD Cosmetology\/Esthetician Form Information Approved Fee Schedule 2024-25 CTE Guidebook Overview of CTE Funding in WCSD Time and Effort Reporting 2 CFR 200.430 (i) CTE: Teaching and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1671","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/pages\/1671","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/comments?post=1671"}],"version-history":[{"count":83,"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/pages\/1671\/revisions"}],"predecessor-version":[{"id":2014,"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/pages\/1671\/revisions\/2014"}],"wp:attachment":[{"href":"https:\/\/www.washk12.org\/cte\/wp-json\/wp\/v2\/media?parent=1671"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}