Compare commits
2 Commits
9621d0ca63
...
99bc387244
| Author | SHA1 | Date |
|---|---|---|
|
|
99bc387244 | |
|
|
d8f18c0d9d |
|
|
@ -44,7 +44,9 @@
|
||||||
"Bash(pnpm pdf:*)",
|
"Bash(pnpm pdf:*)",
|
||||||
"Bash(mv freighter-trade1.png freighter1.png)",
|
"Bash(mv freighter-trade1.png freighter1.png)",
|
||||||
"Bash(mv freighter-mining1.png freighter11.png)",
|
"Bash(mv freighter-mining1.png freighter11.png)",
|
||||||
"Bash(mv freighter-science1.png freighter12.png)"
|
"Bash(mv freighter-science1.png freighter12.png)",
|
||||||
|
"Bash(sort -t'\\(' -k2 -n)",
|
||||||
|
"Bash(grep -o 'scale\\(3.50\\).\\\\{0,200\\\\}')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
server: {
|
server: {
|
||||||
baseDir: "output",
|
baseDir: "output",
|
||||||
|
|
@ -13,5 +16,30 @@ module.exports = {
|
||||||
port: 3300,
|
port: 3300,
|
||||||
open: false,
|
open: false,
|
||||||
notify: false,
|
notify: false,
|
||||||
ui: false
|
ui: false,
|
||||||
|
middleware: [
|
||||||
|
{
|
||||||
|
route: "/api/save-editor",
|
||||||
|
handle: function (req, res, next) {
|
||||||
|
if (req.method !== 'POST') return next();
|
||||||
|
let body = '';
|
||||||
|
req.on('data', chunk => body += chunk);
|
||||||
|
req.on('end', () => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(body);
|
||||||
|
const name = path.basename(data.file || 'unknown', '.html');
|
||||||
|
const dir = path.join(__dirname, 'output', 'editor-saves');
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
const savePath = path.join(dir, name + '.json');
|
||||||
|
fs.writeFileSync(savePath, JSON.stringify(data, null, 2));
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ ok: true, path: savePath }));
|
||||||
|
} catch (e) {
|
||||||
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: e.message }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Чеклист: Данила — Математика и логика</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300..700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
:root,[data-theme="light"]{
|
||||||
|
--bg:#f7f6f2;--surf:#f9f8f5;--surf2:#fff;--off:#f0ede8;
|
||||||
|
--div:#dcd9d5;--brd:#d4d1ca;
|
||||||
|
--txt:#28251d;--muted:#7a7974;--faint:#bab9b4;
|
||||||
|
--pri:#01696f;--pri-hl:#cedcd8;
|
||||||
|
--suc:#437a22;--suc-hl:#d4dfcc;
|
||||||
|
--org:#da7101;--org-hl:#e7d7c4;
|
||||||
|
--pur:#7a39bb;--pur-hl:#dacfde;
|
||||||
|
--blu:#006494;--blu-hl:#c6d8e4;
|
||||||
|
--gld:#c98a00;--gld-hl:#e9e0c6;
|
||||||
|
--err:#a12c7b;--err-hl:#e0ced7;
|
||||||
|
--sh-sm:0 1px 3px rgba(40,37,29,.07);--sh-md:0 4px 14px rgba(40,37,29,.09);
|
||||||
|
--r-md:.5rem;--r-lg:.75rem;--r-xl:1rem;--r-full:9999px;
|
||||||
|
--t:180ms cubic-bezier(.16,1,.3,1);
|
||||||
|
}
|
||||||
|
[data-theme="dark"]{
|
||||||
|
--bg:#171614;--surf:#1c1b19;--surf2:#242320;--off:#222120;
|
||||||
|
--div:#262523;--brd:#3a3937;
|
||||||
|
--txt:#cdccca;--muted:#797876;--faint:#5a5957;
|
||||||
|
--pri:#4f98a3;--pri-hl:#2b3b3c;
|
||||||
|
--suc:#6daa45;--suc-hl:#2e3d2a;
|
||||||
|
--org:#fdab43;--org-hl:#3d3020;
|
||||||
|
--pur:#a86fdf;--pur-hl:#352a42;
|
||||||
|
--blu:#5591c7;--blu-hl:#243040;
|
||||||
|
--gld:#e8af34;--gld-hl:#36300f;
|
||||||
|
--err:#d163a7;--err-hl:#3d2535;
|
||||||
|
--sh-sm:0 1px 3px rgba(0,0,0,.25);--sh-md:0 4px 14px rgba(0,0,0,.35);
|
||||||
|
}
|
||||||
|
html{-webkit-font-smoothing:antialiased;scroll-behavior:smooth}
|
||||||
|
body{min-height:100dvh;font-family:'Inter',sans-serif;font-size:16px;color:var(--txt);background:var(--bg);transition:background var(--t),color var(--t)}
|
||||||
|
button{cursor:pointer;background:none;border:none;font:inherit;color:inherit}
|
||||||
|
:focus-visible{outline:2px solid var(--pri);outline-offset:3px;border-radius:4px}
|
||||||
|
|
||||||
|
.hdr{position:sticky;top:0;z-index:50;background:var(--surf);border-bottom:1px solid var(--div);box-shadow:var(--sh-sm);padding:.7rem 1.5rem;display:flex;align-items:center;gap:.75rem}
|
||||||
|
.logo-ic{width:32px;height:32px;border-radius:7px;background:var(--pri);display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.1rem;flex-shrink:0;font-weight:bold}
|
||||||
|
.logo-t{font-weight:700;font-size:.9rem;line-height:1.2}
|
||||||
|
.logo-s{font-size:.7rem;color:var(--muted);margin-top:1px}
|
||||||
|
.hdr-r{margin-left:auto;display:flex;align-items:center;gap:.4rem}
|
||||||
|
.btn-rst{padding:.28rem .8rem;border-radius:var(--r-full);font-size:.7rem;font-weight:600;background:var(--off);color:var(--muted);border:1px solid var(--brd);transition:all var(--t)}
|
||||||
|
.btn-rst:hover{background:var(--div);color:var(--txt)}
|
||||||
|
.btn-ic{width:32px;height:32px;border-radius:var(--r-md);display:flex;align-items:center;justify-content:center;color:var(--muted);transition:all var(--t)}
|
||||||
|
.btn-ic:hover{background:var(--off);color:var(--txt)}
|
||||||
|
|
||||||
|
.wrap{max-width:800px;margin:0 auto;padding:1.75rem 1.5rem 5rem}
|
||||||
|
|
||||||
|
.hero{background:var(--surf);border:1px solid var(--brd);border-radius:var(--r-xl);padding:1.4rem 1.75rem;margin-bottom:1.75rem;box-shadow:var(--sh-sm);display:grid;grid-template-columns:1fr auto;gap:1.25rem;align-items:center}
|
||||||
|
.h-lbl{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-bottom:.35rem}
|
||||||
|
.h-n{font-size:2rem;font-weight:800;color:var(--pri);line-height:1;font-variant-numeric:tabular-nums}
|
||||||
|
.h-of{font-size:.8rem;color:var(--muted);margin-top:.2rem}
|
||||||
|
.bw{margin-top:.9rem;height:7px;background:var(--off);border-radius:var(--r-full);overflow:hidden}
|
||||||
|
.bf{height:100%;background:var(--pri);border-radius:var(--r-full);transition:width .5s cubic-bezier(.16,1,.3,1)}
|
||||||
|
.dnt{position:relative;width:76px;height:76px;flex-shrink:0}
|
||||||
|
.dnt svg{width:76px;height:76px}
|
||||||
|
.dp{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:.9rem;color:var(--pri)}
|
||||||
|
|
||||||
|
.pills{display:flex;gap:.35rem;flex-wrap:wrap;margin-bottom:1.1rem}
|
||||||
|
.pill{padding:.22rem .75rem;border-radius:var(--r-full);font-size:.7rem;font-weight:600;border:1.5px solid var(--brd);background:var(--surf);color:var(--muted);cursor:pointer;transition:all var(--t)}
|
||||||
|
.pill:hover,.pill.on{background:var(--pri);border-color:var(--pri);color:#fff}
|
||||||
|
|
||||||
|
.card{background:var(--surf);border:1px solid var(--brd);border-radius:var(--r-xl);margin-bottom:.75rem;overflow:hidden;box-shadow:var(--sh-sm);transition:box-shadow var(--t)}
|
||||||
|
.card:hover{box-shadow:var(--sh-md)}
|
||||||
|
.ch{display:flex;align-items:center;gap:.65rem;padding:.8rem 1.1rem;cursor:pointer;user-select:none;border-bottom:1px solid transparent;transition:border-color var(--t)}
|
||||||
|
.card.open .ch{border-bottom-color:var(--div)}
|
||||||
|
.sico{width:32px;height:32px;border-radius:var(--r-md);display:flex;align-items:center;justify-content:center;font-size:.95rem;flex-shrink:0}
|
||||||
|
.stw{flex:1;min-width:0}
|
||||||
|
.st{font-weight:700;font-size:.9rem;line-height:1.2}
|
||||||
|
.sst{font-size:.68rem;color:var(--muted);margin-top:2px}
|
||||||
|
.sm{display:flex;align-items:center;gap:.55rem;flex-shrink:0}
|
||||||
|
.sc{font-size:.68rem;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums;min-width:2.8rem;text-align:right}
|
||||||
|
.mb{width:48px;height:4px;background:var(--off);border-radius:var(--r-full);overflow:hidden}
|
||||||
|
.mf{height:100%;border-radius:var(--r-full);transition:width .4s cubic-bezier(.16,1,.3,1)}
|
||||||
|
.chev{color:var(--faint);transition:transform var(--t)}
|
||||||
|
.card.open .chev{transform:rotate(180deg)}
|
||||||
|
.cb{display:none;padding:.4rem 1.1rem .9rem}
|
||||||
|
.card.open .cb{display:block}
|
||||||
|
|
||||||
|
.itm{display:flex;align-items:flex-start;gap:.6rem;padding:.5rem .3rem;border-radius:var(--r-md);cursor:pointer;transition:background var(--t)}
|
||||||
|
.itm:hover{background:var(--off)}
|
||||||
|
.itm.ok{opacity:.55}
|
||||||
|
.itm.ok .it{text-decoration:line-through;color:var(--muted)}
|
||||||
|
.ck{width:21px;height:21px;border-radius:5px;border:2px solid var(--brd);background:var(--surf2);flex-shrink:0;margin-top:2px;display:flex;align-items:center;justify-content:center;transition:all var(--t)}
|
||||||
|
.itm.ok .ck{background:var(--pri);border-color:var(--pri)}
|
||||||
|
.cki{display:none;color:#fff}
|
||||||
|
.itm.ok .cki{display:block}
|
||||||
|
.it{font-size:.875rem;line-height:1.55;color:var(--txt)}
|
||||||
|
|
||||||
|
.done-b{display:none;background:linear-gradient(120deg,var(--suc-hl),var(--pri-hl));border:1.5px solid var(--suc);border-radius:var(--r-xl);padding:1.4rem 1.75rem;text-align:center;margin-bottom:1.4rem;animation:bIn .6s cubic-bezier(.16,1,.3,1) both}
|
||||||
|
@keyframes bIn{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:none}}
|
||||||
|
.done-b.show{display:block}
|
||||||
|
.dem{font-size:2rem;margin-bottom:.35rem}
|
||||||
|
.dt{font-size:1.1rem;font-weight:800;color:var(--suc);margin-bottom:.25rem}
|
||||||
|
.ds{font-size:.82rem;color:var(--muted)}
|
||||||
|
|
||||||
|
@media(max-width:520px){
|
||||||
|
.wrap{padding:1rem 1rem 4rem}
|
||||||
|
.hero{grid-template-columns:1fr}
|
||||||
|
.dnt{display:none}
|
||||||
|
.hdr{padding:.6rem 1rem}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="hdr">
|
||||||
|
<div class="logo-ic">∑</div>
|
||||||
|
<div>
|
||||||
|
<div class="logo-t">Данила — подготовка к школе</div>
|
||||||
|
<div class="logo-s">Математика и логика · 7 лет · сентябрь 2026</div>
|
||||||
|
</div>
|
||||||
|
<div class="hdr-r">
|
||||||
|
<button class="btn-rst" onclick="resetAll()">Сбросить</button>
|
||||||
|
<button class="btn-ic" id="themeBtn" onclick="toggleTheme()" aria-label="Переключить тему">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="wrap">
|
||||||
|
<div class="done-b" id="doneBanner">
|
||||||
|
<div class="dem">🎉</div>
|
||||||
|
<div class="dt">Все пункты пройдены!</div>
|
||||||
|
<div class="ds">Данила готов к первому классу. Отличная работа!</div>
|
||||||
|
</div>
|
||||||
|
<div class="hero">
|
||||||
|
<div>
|
||||||
|
<div class="h-lbl">Общий прогресс</div>
|
||||||
|
<div class="h-n" id="pN">0</div>
|
||||||
|
<div class="h-of" id="pO">из 36 пунктов выполнено</div>
|
||||||
|
<div class="bw"><div class="bf" id="pB" style="width:0%"></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="dnt" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 76 76">
|
||||||
|
<circle cx="38" cy="38" r="30" fill="none" stroke="var(--off)" stroke-width="8"/>
|
||||||
|
<circle id="dc" cx="38" cy="38" r="30" fill="none" stroke="var(--pri)" stroke-width="8"
|
||||||
|
stroke-linecap="round" stroke-dasharray="188.5" stroke-dashoffset="188.5"
|
||||||
|
transform="rotate(-90 38 38)" style="transition:stroke-dashoffset .5s cubic-bezier(.16,1,.3,1)"/>
|
||||||
|
</svg>
|
||||||
|
<div class="dp" id="dp">0%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav class="pills">
|
||||||
|
<button class="pill on" onclick="flt('all',this)">Все</button>
|
||||||
|
<button class="pill" onclick="flt('s1',this)">🔢 Числа</button>
|
||||||
|
<button class="pill" onclick="flt('s2',this)">➕ Арифметика</button>
|
||||||
|
<button class="pill" onclick="flt('s3',this)">📏 Величины</button>
|
||||||
|
<button class="pill" onclick="flt('s4',this)">🔷 Геометрия</button>
|
||||||
|
<button class="pill" onclick="flt('s5',this)">🧩 Логика</button>
|
||||||
|
<button class="pill" onclick="flt('s6',this)">🧠 Внимание</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- S1: ЧИСЛА — 6 пунктов из списка -->
|
||||||
|
<div class="card open" id="s1" data-s="s1">
|
||||||
|
<div class="ch" onclick="tog('s1')">
|
||||||
|
<div class="sico" style="background:var(--pri-hl);color:var(--pri)">🔢</div>
|
||||||
|
<div class="stw"><div class="st">Числа и счёт</div><div class="sst">6 пунктов</div></div>
|
||||||
|
<div class="sm"><span class="sc" id="ct-s1">0/6</span><div class="mb"><div class="mf" id="mf-s1" style="background:var(--pri);width:0%"></div></div><svg class="chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg></div>
|
||||||
|
</div>
|
||||||
|
<div class="cb">
|
||||||
|
<div class="itm" data-id="1_1" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Понимает разницу между количественным (сколько?) и порядковым (который по счёту?) счётом</div></div>
|
||||||
|
<div class="itm" data-id="1_2" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Называет предыдущее и следующее числа к любому числу в пределах 10</div></div>
|
||||||
|
<div class="itm" data-id="1_3" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Восстанавливает числовой ряд с пропущенными числами (1, __, 3, __, 5)</div></div>
|
||||||
|
<div class="itm" data-id="1_4" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Знает чётные и нечётные числа в пределах 10</div></div>
|
||||||
|
<div class="itm" data-id="1_5" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Пишет цифры от 0 до 9</div></div>
|
||||||
|
<div class="itm" data-id="1_6" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Сравнивает числа, использует знаки >, <, = (6 > 4)</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- S2: АРИФМЕТИКА — 3 пункта из списка -->
|
||||||
|
<div class="card" id="s2" data-s="s2">
|
||||||
|
<div class="ch" onclick="tog('s2')">
|
||||||
|
<div class="sico" style="background:var(--org-hl);color:var(--org)">➕</div>
|
||||||
|
<div class="stw"><div class="st">Арифметика (текстовые задачи)</div><div class="sst">3 пункта</div></div>
|
||||||
|
<div class="sm"><span class="sc" id="ct-s2">0/3</span><div class="mb"><div class="mf" id="mf-s2" style="background:var(--org);width:0%"></div></div><svg class="chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg></div>
|
||||||
|
</div>
|
||||||
|
<div class="cb">
|
||||||
|
<div class="itm" data-id="2_1" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Самостоятельно составляет задачу по картинке или условию</div></div>
|
||||||
|
<div class="itm" data-id="2_2" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Уравнивает неравное число предметов: «В одной группе 3 кружка, в другой 5. Сколько надо добавить, чтобы стало поровну?»</div></div>
|
||||||
|
<div class="itm" data-id="2_3" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Понимает понятия «больше», «меньше», «столько же», «поровну»</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- S3: ВЕЛИЧИНЫ — 2 пункта из списка -->
|
||||||
|
<div class="card" id="s3" data-s="s3">
|
||||||
|
<div class="ch" onclick="tog('s3')">
|
||||||
|
<div class="sico" style="background:var(--blu-hl);color:var(--blu)">📏</div>
|
||||||
|
<div class="stw"><div class="st">Величины и измерения</div><div class="sst">2 пункта</div></div>
|
||||||
|
<div class="sm"><span class="sc" id="ct-s3">0/2</span><div class="mb"><div class="mf" id="mf-s3" style="background:var(--blu);width:0%"></div></div><svg class="chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg></div>
|
||||||
|
</div>
|
||||||
|
<div class="cb">
|
||||||
|
<div class="itm" data-id="3_1" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Делит предмет на 2 и 4 равные части (половина, четверть)</div></div>
|
||||||
|
<div class="itm" data-id="3_2" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Измеряет условной меркой (сколько ладошек длиной стол?)</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- S4: ГЕОМЕТРИЯ — 8 пунктов из списка -->
|
||||||
|
<div class="card" id="s4" data-s="s4">
|
||||||
|
<div class="ch" onclick="tog('s4')">
|
||||||
|
<div class="sico" style="background:var(--gld-hl);color:var(--gld)">🔷</div>
|
||||||
|
<div class="stw"><div class="st">Геометрия и пространство</div><div class="sst">8 пунктов</div></div>
|
||||||
|
<div class="sm"><span class="sc" id="ct-s4">0/8</span><div class="mb"><div class="mf" id="mf-s4" style="background:var(--gld);width:0%"></div></div><svg class="chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg></div>
|
||||||
|
</div>
|
||||||
|
<div class="cb">
|
||||||
|
<div class="itm" data-id="4_1" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Называет и показывает: круг, квадрат, прямоугольник, треугольник, овал</div></div>
|
||||||
|
<div class="itm" data-id="4_2" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Называет и показывает объёмные тела: шар, куб, цилиндр, конус</div></div>
|
||||||
|
<div class="itm" data-id="4_3" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Находит предметы похожей формы в окружении (мяч — шар, окно — прямоугольник)</div></div>
|
||||||
|
<div class="itm" data-id="4_4" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Ориентируется на листе бумаги: вверху / внизу, слева / справа, в центре, по углам</div></div>
|
||||||
|
<div class="itm" data-id="4_5" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Составляет фигуры из частей (квадрат из 2 треугольников, прямоугольник из 2 квадратов)</div></div>
|
||||||
|
<div class="itm" data-id="4_6" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Рисует фигуры по клеточкам</div></div>
|
||||||
|
<div class="itm" data-id="4_7" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Знает «план» или «схему» как обозначение реального пространства</div></div>
|
||||||
|
<div class="itm" data-id="4_8" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Знает времена года и их последовательность</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- S5: ЛОГИКА — 9 пунктов из списка -->
|
||||||
|
<div class="card" id="s5" data-s="s5">
|
||||||
|
<div class="ch" onclick="tog('s5')">
|
||||||
|
<div class="sico" style="background:var(--pur-hl);color:var(--pur)">🧩</div>
|
||||||
|
<div class="stw"><div class="st">Логика и мышление</div><div class="sst">9 пунктов</div></div>
|
||||||
|
<div class="sm"><span class="sc" id="ct-s5">0/9</span><div class="mb"><div class="mf" id="mf-s5" style="background:var(--pur);width:0%"></div></div><svg class="chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg></div>
|
||||||
|
</div>
|
||||||
|
<div class="cb">
|
||||||
|
<div class="itm" data-id="5_1" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Объединяет предметы в группы по одному признаку (цвет, форма, размер)</div></div>
|
||||||
|
<div class="itm" data-id="5_2" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Объединяет по двум признакам одновременно (маленькие красные фигуры)</div></div>
|
||||||
|
<div class="itm" data-id="5_3" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Выделяет лишний предмет и объясняет почему («лишний — помидор, потому что это овощ, остальные фрукты»)</div></div>
|
||||||
|
<div class="itm" data-id="5_4" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Называет обобщающее слово для группы (стол, стул, шкаф → мебель)</div></div>
|
||||||
|
<div class="itm" data-id="5_5" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Решает простые аналогии: «Птица — гнездо, человек — ?»</div></div>
|
||||||
|
<div class="itm" data-id="5_6" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Продолжает простой паттерн (○ △ ○ △ ○ …)</div></div>
|
||||||
|
<div class="itm" data-id="5_7" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Продолжает числовой ряд с закономерностью (2, 4, 6, …)</div></div>
|
||||||
|
<div class="itm" data-id="5_8" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Выстраивает последовательность событий по картинкам (4–5 картинок) и составляет рассказ</div></div>
|
||||||
|
<div class="itm" data-id="5_9" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Отвечает на вопросы «почему?» и «что будет, если…?»</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- S6: ВНИМАНИЕ — 6 пунктов из списка -->
|
||||||
|
<div class="card" id="s6" data-s="s6">
|
||||||
|
<div class="ch" onclick="tog('s6')">
|
||||||
|
<div class="sico" style="background:var(--err-hl);color:var(--err)">🧠</div>
|
||||||
|
<div class="stw"><div class="st">Внимание, память, регуляция</div><div class="sst">6 пунктов</div></div>
|
||||||
|
<div class="sm"><span class="sc" id="ct-s6">0/6</span><div class="mb"><div class="mf" id="mf-s6" style="background:var(--err);width:0%"></div></div><svg class="chev" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg></div>
|
||||||
|
</div>
|
||||||
|
<div class="cb">
|
||||||
|
<div class="itm" data-id="6_1" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Выполняет задание, не отвлекаясь, около 15–20 минут</div></div>
|
||||||
|
<div class="itm" data-id="6_2" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Удерживает в поле зрения не менее 8–10 предметов</div></div>
|
||||||
|
<div class="itm" data-id="6_3" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Копирует узор точно по образцу</div></div>
|
||||||
|
<div class="itm" data-id="6_4" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Повторяет ряд цифр из 5–7 элементов (3, 1, 7, 4, 2…)</div></div>
|
||||||
|
<div class="itm" data-id="6_5" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Запоминает и выполняет инструкцию из 3 шагов</div></div>
|
||||||
|
<div class="itm" data-id="6_6" onclick="ti(this)"><div class="ck"><svg class="cki" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div><div class="it">Доводит задание до конца, не бросая на полпути</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ЗАМЕТКА из списка: физические объекты -->
|
||||||
|
<div style="background:var(--off);border:1px solid var(--brd);border-radius:var(--r-lg);padding:.9rem 1.1rem;margin-bottom:.75rem;font-size:.8rem;color:var(--muted);line-height:1.6">
|
||||||
|
📌 <b style="color:var(--txt)">Счётные палочки и физические объекты</b> — работа с бусами, палочками, монетами формирует понимание числа глубже, чем тетрадные упражнения (Монтессори, российские методисты)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
const S={};
|
||||||
|
const SECS=['s1','s2','s3','s4','s5','s6'];
|
||||||
|
function ga(){return document.querySelectorAll('.itm[data-id]');}
|
||||||
|
function cd(){let n=0;ga().forEach(e=>{if(S[e.dataset.id])n++;});return n;}
|
||||||
|
function cs(s){const el=document.querySelectorAll('#'+s+' .itm[data-id]');let d=0;el.forEach(e=>{if(S[e.dataset.id])d++;});return{d,t:el.length};}
|
||||||
|
function upd(){
|
||||||
|
const tot=ga().length,done=cd(),pct=tot?Math.round(done/tot*100):0;
|
||||||
|
document.getElementById('pN').textContent=done;
|
||||||
|
document.getElementById('pO').textContent='из '+tot+' пунктов выполнено';
|
||||||
|
document.getElementById('pB').style.width=pct+'%';
|
||||||
|
const circ=188.5;
|
||||||
|
document.getElementById('dc').style.strokeDashoffset=circ-(circ*pct/100);
|
||||||
|
document.getElementById('dp').textContent=pct+'%';
|
||||||
|
SECS.forEach(s=>{
|
||||||
|
const{d,t}=cs(s);
|
||||||
|
const ct=document.getElementById('ct-'+s);
|
||||||
|
const mf=document.getElementById('mf-'+s);
|
||||||
|
if(ct)ct.textContent=d+'/'+t;
|
||||||
|
if(mf)mf.style.width=(t?Math.round(d/t*100):0)+'%';
|
||||||
|
});
|
||||||
|
const b=document.getElementById('doneBanner');
|
||||||
|
if(done===tot&&tot>0)b.classList.add('show');else b.classList.remove('show');
|
||||||
|
}
|
||||||
|
function ti(el){S[el.dataset.id]=!S[el.dataset.id];el.classList.toggle('ok',S[el.dataset.id]);upd();}
|
||||||
|
function tog(id){document.getElementById(id).classList.toggle('open');}
|
||||||
|
function flt(t,btn){
|
||||||
|
document.querySelectorAll('.pill').forEach(p=>p.classList.remove('on'));
|
||||||
|
btn.classList.add('on');
|
||||||
|
document.querySelectorAll('.card').forEach(c=>{c.style.display=(t==='all'||c.dataset.s===t)?'':'none';});
|
||||||
|
}
|
||||||
|
function resetAll(){
|
||||||
|
if(!confirm('Сбросить весь прогресс?'))return;
|
||||||
|
Object.keys(S).forEach(k=>delete S[k]);
|
||||||
|
document.querySelectorAll('.itm.ok').forEach(e=>e.classList.remove('ok'));
|
||||||
|
upd();
|
||||||
|
}
|
||||||
|
let dark=matchMedia('(prefers-color-scheme:dark)').matches;
|
||||||
|
function setThemeIcon(){
|
||||||
|
document.getElementById('themeBtn').innerHTML=dark
|
||||||
|
?'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>'
|
||||||
|
:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
||||||
|
}
|
||||||
|
function toggleTheme(){dark=!dark;document.documentElement.setAttribute('data-theme',dark?'dark':'light');setThemeIcon();}
|
||||||
|
document.documentElement.setAttribute('data-theme',dark?'dark':'light');
|
||||||
|
setThemeIcon();
|
||||||
|
upd();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,311 @@
|
||||||
|
/**
|
||||||
|
* Generate space-worksheet3.html — 9-page static HTML worksheet
|
||||||
|
* combining task patterns from worksheet1 and worksheet2.
|
||||||
|
*
|
||||||
|
* Usage: node src/scripts/generate-worksheet3.mjs
|
||||||
|
* Output: output/html/space-worksheet3.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const ROOT = process.cwd();
|
||||||
|
|
||||||
|
// ── Icon pools ──────────────────────────────────────────────────────────────
|
||||||
|
const pack1Icons = fs.readdirSync(path.join(ROOT, 'assets/icons/pack1'))
|
||||||
|
.filter(f => f.endsWith('.png'))
|
||||||
|
.map(f => `assets/icons/pack1/${f}`);
|
||||||
|
|
||||||
|
const pack2Icons = fs.readdirSync(path.join(ROOT, 'assets/icons/pack2'))
|
||||||
|
.filter(f => f.endsWith('.png'))
|
||||||
|
.map(f => `assets/icons/pack2/${f}`);
|
||||||
|
|
||||||
|
function shuffle(arr) {
|
||||||
|
const a = [...arr];
|
||||||
|
for (let i = a.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[a[i], a[j]] = [a[j], a[i]];
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
function randSign() {
|
||||||
|
return Math.random() < 0.5 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickAlignment() {
|
||||||
|
const opts = ['justify-start', 'justify-center', 'justify-end'];
|
||||||
|
return opts[Math.floor(Math.random() * opts.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Problem generators ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Pattern 1: A + B ± C (A:8-16, B:4-8, C:1-3)
|
||||||
|
function genPattern1() {
|
||||||
|
const problems = [];
|
||||||
|
const seen = new Set();
|
||||||
|
while (problems.length < 20) {
|
||||||
|
const A = randInt(8, 16);
|
||||||
|
const B = randInt(4, 8);
|
||||||
|
const C = randInt(1, 3);
|
||||||
|
const sign = randSign();
|
||||||
|
const expr = sign > 0 ? `${A} + ${B} + ${C}` : `${A} + ${B} − ${C}`;
|
||||||
|
const result = A + B + sign * C;
|
||||||
|
if (result >= 0 && !seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 2: 5 × N ± C (N:1-5, C:1-6)
|
||||||
|
function genPattern2() {
|
||||||
|
const problems = [];
|
||||||
|
const seen = new Set();
|
||||||
|
while (problems.length < 20) {
|
||||||
|
const N = randInt(1, 5);
|
||||||
|
const C = randInt(1, 6);
|
||||||
|
const sign = randSign();
|
||||||
|
const expr = sign > 0 ? `5 × ${N} + ${C}` : `5 × ${N} − ${C}`;
|
||||||
|
const result = 5 * N + sign * C;
|
||||||
|
if (result >= 0 && !seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 3: A × B ± C (A,B:1-4, C:1-8)
|
||||||
|
function genPattern3() {
|
||||||
|
const problems = [];
|
||||||
|
const seen = new Set();
|
||||||
|
while (problems.length < 20) {
|
||||||
|
const A = randInt(1, 4);
|
||||||
|
const B = randInt(1, 4);
|
||||||
|
const C = randInt(1, 8);
|
||||||
|
const sign = randSign();
|
||||||
|
const expr = sign > 0 ? `${A} × ${B} + ${C}` : `${A} × ${B} − ${C}`;
|
||||||
|
const result = A * B + sign * C;
|
||||||
|
if (result >= 0 && !seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 4: A + B + C (A:12-24, B:±6-10, C:±2-5, result 0-40)
|
||||||
|
function genPattern4() {
|
||||||
|
const problems = [];
|
||||||
|
const seen = new Set();
|
||||||
|
while (problems.length < 20) {
|
||||||
|
const A = randInt(12, 24);
|
||||||
|
const signB = randSign();
|
||||||
|
const B = randInt(6, 10);
|
||||||
|
const signC = randSign();
|
||||||
|
const C = randInt(2, 5);
|
||||||
|
const result = A + signB * B + signC * C;
|
||||||
|
if (result >= 0 && result <= 40) {
|
||||||
|
const bPart = signB > 0 ? `+ ${B}` : `− ${B}`;
|
||||||
|
const cPart = signC > 0 ? `+ ${C}` : `− ${C}`;
|
||||||
|
const expr = `${A} ${bPart} ${cPart}`;
|
||||||
|
if (!seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 5: mixed fives (5+5+5, 5×N, 5×N±5)
|
||||||
|
function genPattern5() {
|
||||||
|
const problems = [];
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
// Type A: 5+5+5 (2,3,4 fives)
|
||||||
|
for (const count of [2, 3, 4]) {
|
||||||
|
const expr = Array(count).fill('5').join(' + ');
|
||||||
|
problems.push(expr);
|
||||||
|
seen.add(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type B: 5×N (N:1-12)
|
||||||
|
const ns = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
||||||
|
for (const n of ns) {
|
||||||
|
if (problems.length >= 20) break;
|
||||||
|
const expr = `5 × ${n}`;
|
||||||
|
if (!seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type C: 5×N±5 (fill remaining)
|
||||||
|
while (problems.length < 20) {
|
||||||
|
const N = randInt(2, 12);
|
||||||
|
const sign = randSign();
|
||||||
|
const expr = sign > 0 ? `5 × ${N} + 5` : `5 × ${N} − 5`;
|
||||||
|
if (!seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shuffle(problems);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 6: A×B + C×D (A:2-4, B:1-3, C:2-3, D:1-2)
|
||||||
|
function genPattern6() {
|
||||||
|
const problems = [];
|
||||||
|
const seen = new Set();
|
||||||
|
while (problems.length < 20) {
|
||||||
|
const A = randInt(2, 4);
|
||||||
|
const B = randInt(1, 3);
|
||||||
|
const C = randInt(2, 3);
|
||||||
|
const D = randInt(1, 2);
|
||||||
|
const expr = `${A} × ${B} + ${C} × ${D}`;
|
||||||
|
if (!seen.has(expr)) {
|
||||||
|
seen.add(expr);
|
||||||
|
problems.push(expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return problems;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generators = [genPattern1, genPattern2, genPattern3, genPattern4, genPattern5, genPattern6, genPattern1, genPattern2, genPattern3];
|
||||||
|
|
||||||
|
// ── Page config ─────────────────────────────────────────────────────────────
|
||||||
|
const pages = [
|
||||||
|
{ hero: 'assets/hero-images/spaceship1.jpeg', footer: 'assets/footers/planet1.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 0 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship2.jpeg', footer: 'assets/footers/planet2.jpeg', dir: '', pack: 'pack1', gen: 1 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship3.png', footer: 'assets/footers/planet3.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 2 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship4.jpeg', footer: 'assets/footers/planet4.jpeg', dir: '', pack: 'pack2', gen: 3 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship5.jpeg', footer: 'assets/footers/planet5.jpeg', dir: 'flex-row-reverse', pack: 'pack2', gen: 4, fivesHint: true },
|
||||||
|
{ hero: 'assets/hero-images/spaceship6.jpeg', footer: 'assets/footers/planet6.jpeg', dir: '', pack: 'pack2', gen: 5 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship7.jpeg', footer: 'assets/footers/planet7.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 6 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship8.jpeg', footer: 'assets/footers/planet8.jpeg', dir: '', pack: 'pack1', gen: 7 },
|
||||||
|
{ hero: 'assets/hero-images/spaceship9.jpeg', footer: 'assets/footers/planet9.jpeg', dir: 'flex-row-reverse', pack: 'pack1', gen: 8 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Assign icons per section (3 pages share a pool) ─────────────────────────
|
||||||
|
// Section 1 (pages 0-2): pack1 shuffled
|
||||||
|
// Section 2 (pages 3-5): pack2 shuffled
|
||||||
|
// Section 3 (pages 6-8): pack1 re-shuffled
|
||||||
|
const iconSections = [
|
||||||
|
shuffle(pack1Icons), // pages 0-2: 60 icons
|
||||||
|
shuffle(pack2Icons), // pages 3-5: 60 icons
|
||||||
|
shuffle(pack1Icons), // pages 6-8: 60 icons (reuse pack1, different shuffle)
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── Build HTML ──────────────────────────────────────────────────────────────
|
||||||
|
function buildProblemCard(icon, expression, alignment, iconSize) {
|
||||||
|
return ` <div class="flex ${alignment}">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<img src="../../${icon}" class="w-[${iconSize}px] h-[${iconSize}px] shrink-0 object-contain" alt="">
|
||||||
|
<div class="pl-3 pr-4 py-1 border-[1.5px] border-indigo-100 rounded-full bg-gradient-to-br from-white to-indigo-50/40 shadow-sm shadow-indigo-100/30">
|
||||||
|
<span class="text-[1.2rem] font-semibold text-indigo-950 whitespace-nowrap">${expression} = <span class="inline-block w-16 border-b-[2px] border-indigo-300"> </span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPage(pageIdx, isLast) {
|
||||||
|
const pg = pages[pageIdx];
|
||||||
|
const sectionIdx = Math.floor(pageIdx / 3);
|
||||||
|
const posInSection = pageIdx % 3;
|
||||||
|
const sectionIcons = iconSections[sectionIdx];
|
||||||
|
const pageIcons = sectionIcons.slice(posInSection * 20, posInSection * 20 + 20);
|
||||||
|
const iconSize = pg.pack === 'pack2' ? 58 : 58;
|
||||||
|
|
||||||
|
const problems = generators[pg.gen]();
|
||||||
|
const dirClass = pg.dir ? ` ${pg.dir}` : '';
|
||||||
|
const breakStyle = isLast ? '' : ' style="break-after: page;"';
|
||||||
|
|
||||||
|
const fivesHint = pg.fivesHint
|
||||||
|
? `\n <div class="font-semibold text-indigo-950 mb-3 text-center" style="font-size: 0.8rem; margin-top: -10px;">Посчитай пятёрками: <span style="font-size: 1rem; word-spacing: 0.25em;">5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60</span> для зарядки расщепителя</div>\n`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const cards = problems.map((expr, i) => {
|
||||||
|
return buildProblemCard(pageIcons[i], expr, pickAlignment(), iconSize);
|
||||||
|
}).join('\n\n');
|
||||||
|
|
||||||
|
return ` <!-- PAGE ${pageIdx + 1} -->
|
||||||
|
<div class="w-[210mm] h-[297mm] relative overflow-hidden mx-auto bg-white"${breakStyle}>
|
||||||
|
|
||||||
|
<div class="absolute bottom-0 left-0 right-0 h-[80mm] z-0">
|
||||||
|
<div class="absolute top-0 left-0 right-0 h-full z-10" style="background: linear-gradient(to bottom, white 0%, rgba(255,255,255,0.6) 25%, transparent 50%);"></div>
|
||||||
|
<img src="../../${pg.footer}" class="w-full h-full object-cover object-top" alt="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute bottom-[12mm] left-0 right-0 z-20 flex justify-center">
|
||||||
|
<div style="background: rgba(255,255,255,0.85);" class="rounded-full px-6 py-1.5 border-[1.5px] border-indigo-200 shadow-md">
|
||||||
|
<span class="text-[0.95rem] font-bold text-indigo-950">Итого собрано на планете: <span class="inline-block w-14 border-b-2 border-indigo-400 text-center ml-1"> </span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative z-10 w-full h-full px-[12mm] pt-[4mm] pb-[65mm] flex flex-col">
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4 mb-4${dirClass}">
|
||||||
|
<img src="../../${pg.hero}" class="w-[48%] shrink-0 object-contain" alt="">
|
||||||
|
<div class="flex-1 flex flex-col items-center justify-center text-center">
|
||||||
|
<h1 class="text-2xl font-extrabold leading-tight tracking-tight text-indigo-950">Исследуй Планету</h1>
|
||||||
|
<p class="text-sm font-medium text-indigo-400 mt-1">Собери ресурсы, решая примеры!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${fivesHint}
|
||||||
|
<div class="grid grid-cols-2 gap-x-3 gap-y-[3px] flex-1 content-start">
|
||||||
|
|
||||||
|
${cards}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlPages = pages.map((_, i) => buildPage(i, i === pages.length - 1)).join('\n\n');
|
||||||
|
|
||||||
|
const html = `<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&display=swap" rel="stylesheet">
|
||||||
|
<title>Исследуй Планету</title>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: { nunito: ['Nunito', 'sans-serif'] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
@page { size: A4; margin: 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-200 font-nunito">
|
||||||
|
|
||||||
|
${htmlPages}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// ── Write output ────────────────────────────────────────────────────────────
|
||||||
|
const outDir = path.join(ROOT, 'output/html');
|
||||||
|
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||||
|
|
||||||
|
const outPath = path.join(outDir, 'space-worksheet3.html');
|
||||||
|
fs.writeFileSync(outPath, html, 'utf-8');
|
||||||
|
console.log(`Generated: ${outPath} (${pages.length} pages, ${pages.length * 20} problems)`);
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform collecting-asteroids-2.html into collecting-asteroids-3.html
|
||||||
|
* Output goes to stdout for review before saving.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
const root = resolve(import.meta.dirname, '../..');
|
||||||
|
const v2Html = readFileSync(resolve(root, 'output/html/collecting-asteroids-2.html'), 'utf-8');
|
||||||
|
|
||||||
|
// Page configs for v3
|
||||||
|
const pages = [
|
||||||
|
{
|
||||||
|
comment: 'PAGE 1: ships 11(2), 9(2), 13(6) — R-L-R',
|
||||||
|
hero: 'splitter4', footer: 'cabin3',
|
||||||
|
cargoBays: [7, 8, 9],
|
||||||
|
ships: [11, 9, 13],
|
||||||
|
oldAsteroidTypes: ['asteroid7', 'asteroid14'],
|
||||||
|
newAsteroidTypes: ['asteroid3', 'asteroid10'],
|
||||||
|
asteroidValues: [3, 2, 1, 4, 5, 2, 3, 6, 5, 4, 1, 6],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 2: ships 12(3), 12(3), 18(4) — L-R-L',
|
||||||
|
hero: 'splitter7', footer: 'cabin6',
|
||||||
|
cargoBays: [1, 2, 3],
|
||||||
|
ships: [12, 12, 18],
|
||||||
|
oldAsteroidTypes: ['asteroid4', 'asteroid11'],
|
||||||
|
newAsteroidTypes: ['asteroid6', 'asteroid13'],
|
||||||
|
asteroidValues: [4, 5, 3, 6, 2, 7, 4, 3, 5, 2, 6, 4],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 3: ships 9(5), 11(3), 11(2) — R-L-R',
|
||||||
|
hero: 'splitter1', footer: 'cabin9',
|
||||||
|
cargoBays: [4, 5, 6],
|
||||||
|
ships: [9, 11, 11],
|
||||||
|
oldAsteroidTypes: ['asteroid10', 'asteroid3'],
|
||||||
|
newAsteroidTypes: ['asteroid2', 'asteroid8'],
|
||||||
|
asteroidValues: [2, 4, 1, 3, 5, 2, 1, 3, 4, 6, 5, 2],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 4: ships 15(4), 17(5), 11(2) — L-R-L',
|
||||||
|
hero: 'splitter6', footer: 'cabin2',
|
||||||
|
cargoBays: [7, 8, 9],
|
||||||
|
ships: [15, 17, 11],
|
||||||
|
oldAsteroidTypes: ['asteroid16', 'asteroid5'],
|
||||||
|
newAsteroidTypes: ['asteroid12', 'asteroid15'],
|
||||||
|
asteroidValues: [4, 3, 5, 2, 6, 1, 3, 4, 6, 2, 5, 4],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 5: ships 7(2), 9(4), 7(2) — R-L-R',
|
||||||
|
hero: 'splitter2', footer: 'cabin5',
|
||||||
|
cargoBays: [1, 2, 3],
|
||||||
|
ships: [7, 9, 7],
|
||||||
|
oldAsteroidTypes: ['asteroid2', 'asteroid13'],
|
||||||
|
newAsteroidTypes: ['asteroid1', 'asteroid9'],
|
||||||
|
asteroidValues: [3, 1, 4, 2, 5, 1, 6, 2, 3, 4, 5],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 6: ships 18(4), 18(5), 14(3) — L-R-L',
|
||||||
|
hero: 'splitter8', footer: 'cabin7',
|
||||||
|
cargoBays: [4, 5, 6],
|
||||||
|
ships: [18, 18, 14],
|
||||||
|
oldAsteroidTypes: ['asteroid8', 'asteroid12'],
|
||||||
|
newAsteroidTypes: ['asteroid5', 'asteroid16'],
|
||||||
|
asteroidValues: [3, 4, 5, 6, 2, 4, 7, 3, 2, 5, 6, 4, 3, 7],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 7: ships 10(2), 16(3), 22(6) — R-L-R',
|
||||||
|
hero: 'splitter9', footer: 'cabin4',
|
||||||
|
cargoBays: [7, 8, 9],
|
||||||
|
ships: [10, 16, 22],
|
||||||
|
oldAsteroidTypes: ['asteroid15', 'asteroid6'],
|
||||||
|
newAsteroidTypes: ['asteroid11', 'asteroid7'],
|
||||||
|
asteroidValues: [4, 3, 2, 6, 5, 3, 7, 4, 5, 2, 6, 4],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 8: ships 26(0), 18(0), 30(0) — UNSOLVABLE — L-R-L',
|
||||||
|
hero: 'splitter5', footer: 'cabin1',
|
||||||
|
cargoBays: [1, 2, 3],
|
||||||
|
ships: [26, 18, 30],
|
||||||
|
oldAsteroidTypes: ['asteroid9', 'asteroid1'],
|
||||||
|
newAsteroidTypes: ['asteroid14', 'asteroid4'],
|
||||||
|
asteroidValues: [6, 4, 5, 3, 7, 2, 4, 6, 5, 7, 3, 4, 6],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: 'PAGE 9: ships 8(2), 18(3), 28(8) — R-L-R',
|
||||||
|
hero: 'splitter3', footer: 'cabin8',
|
||||||
|
cargoBays: [4, 5, 6],
|
||||||
|
ships: [8, 18, 28],
|
||||||
|
oldAsteroidTypes: ['asteroid11', 'asteroid4'],
|
||||||
|
newAsteroidTypes: ['asteroid10', 'asteroid6'],
|
||||||
|
asteroidValues: [4, 3, 2, 5, 6, 3, 4, 7, 2, 5, 4, 6, 3, 7, 5],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Split HTML into pages
|
||||||
|
// Pages are delimited by <!-- PAGE N comments
|
||||||
|
const pageRegex = /<!-- PAGE \d+/g;
|
||||||
|
const matches = [...v2Html.matchAll(pageRegex)];
|
||||||
|
|
||||||
|
// Extract page blocks
|
||||||
|
const pageBlocks = [];
|
||||||
|
for (let i = 0; i < matches.length; i++) {
|
||||||
|
const start = matches[i].index;
|
||||||
|
const end = i + 1 < matches.length ? matches[i + 1].index : v2Html.lastIndexOf('</body>');
|
||||||
|
pageBlocks.push(v2Html.substring(start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract header (before first page) and footer (after last page)
|
||||||
|
const header = v2Html.substring(0, matches[0].index);
|
||||||
|
const footer = v2Html.substring(v2Html.lastIndexOf('</body>'));
|
||||||
|
|
||||||
|
function transformPage(block, config, pageIdx) {
|
||||||
|
let html = block;
|
||||||
|
|
||||||
|
// 1. Replace page comment
|
||||||
|
html = html.replace(/<!-- PAGE \d+:.*?-->/, `<!-- ${config.comment} -->`);
|
||||||
|
|
||||||
|
// 2. Replace hero image
|
||||||
|
html = html.replace(
|
||||||
|
/splitters\/splitter\d+\.png/,
|
||||||
|
`splitters/${config.hero}.png`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Replace footer image
|
||||||
|
html = html.replace(
|
||||||
|
/footers\/cabin\d+\.jpeg/,
|
||||||
|
`footers/${config.footer}.jpeg`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Replace cargo bay images (3 per page, in order)
|
||||||
|
let cbIdx = 0;
|
||||||
|
html = html.replace(
|
||||||
|
/pack4-cargobay\/cargo-bay\d+\.png/g,
|
||||||
|
(match) => `pack4-cargobay/cargo-bay${config.cargoBays[cbIdx++]}.png`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Replace ship capacity numbers (text-xl spans inside blue circles)
|
||||||
|
let shipIdx = 0;
|
||||||
|
html = html.replace(
|
||||||
|
/(<span class="text-white font-extrabold text-xl">)(\d+)(<\/span>)/g,
|
||||||
|
(match, pre, val, post) => `${pre}${config.ships[shipIdx++]}${post}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Replace asteroid type images
|
||||||
|
for (let i = 0; i < config.oldAsteroidTypes.length; i++) {
|
||||||
|
const oldType = config.oldAsteroidTypes[i];
|
||||||
|
const newType = config.newAsteroidTypes[i];
|
||||||
|
html = html.replaceAll(
|
||||||
|
`pack3-asteroids/${oldType}.png`,
|
||||||
|
`pack3-asteroids/${newType}.png`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Replace asteroid values (text-2xl spans, sequential)
|
||||||
|
let avIdx = 0;
|
||||||
|
html = html.replace(
|
||||||
|
/(<span class="text-white font-extrabold text-2xl"[^>]*>)(\d+)(<\/span>)/g,
|
||||||
|
(match, pre, val, post) => {
|
||||||
|
if (avIdx < config.asteroidValues.length) {
|
||||||
|
return `${pre}${config.asteroidValues[avIdx++]}${post}`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if (shipIdx !== 3) {
|
||||||
|
console.error(`WARNING: Page ${pageIdx + 1} — replaced ${shipIdx} ship values (expected 3)`);
|
||||||
|
}
|
||||||
|
if (avIdx !== config.asteroidValues.length) {
|
||||||
|
console.error(`WARNING: Page ${pageIdx + 1} — replaced ${avIdx} asteroid values (expected ${config.asteroidValues.length})`);
|
||||||
|
}
|
||||||
|
if (cbIdx !== 3) {
|
||||||
|
console.error(`WARNING: Page ${pageIdx + 1} — replaced ${cbIdx} cargo bay refs (expected 3)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform all pages
|
||||||
|
const newPages = pageBlocks.map((block, i) => transformPage(block, pages[i], i));
|
||||||
|
|
||||||
|
// Assemble
|
||||||
|
const result = header + newPages.join('') + footer;
|
||||||
|
|
||||||
|
// Write to temp file
|
||||||
|
import { writeFileSync } from 'fs';
|
||||||
|
const tmpPath = resolve(root, 'output/html/.collecting-asteroids-3-tmp.html');
|
||||||
|
writeFileSync(tmpPath, result);
|
||||||
|
console.log(`Output written to: ${tmpPath}`);
|
||||||
|
console.log(`Total lines: ${result.split('\n').length}`);
|
||||||
|
|
||||||
|
// Print summary of changes per page
|
||||||
|
for (let i = 0; i < pages.length; i++) {
|
||||||
|
const p = pages[i];
|
||||||
|
console.log(`\nPage ${i + 1}: hero=${p.hero} footer=${p.footer} cargoBays=[${p.cargoBays}]`);
|
||||||
|
console.log(` Ships: [${p.ships}] Asteroids: [${p.asteroidValues}]`);
|
||||||
|
console.log(` Types: ${p.oldAsteroidTypes} → ${p.newAsteroidTypes}`);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asteroid partition solver.
|
||||||
|
* Verifies that disjoint subsets of asteroid values can sum to each ship capacity.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node verify-asteroids.mjs --ships 10,10,14 --asteroids 3,2,1,4,5,2,3,6,5,4,1,6
|
||||||
|
*
|
||||||
|
* Output: prints whether a valid partition exists and one example solution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
function parseArg(flag) {
|
||||||
|
const idx = args.indexOf(flag);
|
||||||
|
if (idx === -1 || idx + 1 >= args.length) return null;
|
||||||
|
return args[idx + 1].split(',').map(Number);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ships = parseArg('--ships');
|
||||||
|
const asteroids = parseArg('--asteroids');
|
||||||
|
|
||||||
|
if (!ships || !asteroids) {
|
||||||
|
console.log('Usage: node verify-asteroids.mjs --ships 10,10,14 --asteroids 3,2,1,4,5,2,3,6,5,4,1,6');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Ships: [${ships.join(', ')}] (sum=${ships.reduce((a, b) => a + b, 0)})`);
|
||||||
|
console.log(`Asteroids: [${asteroids.join(', ')}] (count=${asteroids.length}, sum=${asteroids.reduce((a, b) => a + b, 0)})`);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find disjoint subsets of `available` (by index) that sum to each capacity in `capacities`.
|
||||||
|
* Returns array of index arrays, or null if no solution.
|
||||||
|
*/
|
||||||
|
function findPartition(available, capacities, usedSet = new Set()) {
|
||||||
|
if (capacities.length === 0) return [];
|
||||||
|
|
||||||
|
const target = capacities[0];
|
||||||
|
const remaining = capacities.slice(1);
|
||||||
|
const remainingSum = remaining.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
// Available sum check
|
||||||
|
let availSum = 0;
|
||||||
|
for (let i = 0; i < available.length; i++) {
|
||||||
|
if (!usedSet.has(i)) availSum += available[i];
|
||||||
|
}
|
||||||
|
if (availSum < target + remainingSum) return null;
|
||||||
|
|
||||||
|
// Find all subsets summing to target using DFS
|
||||||
|
const subset = [];
|
||||||
|
|
||||||
|
function dfs(startIdx, currentSum) {
|
||||||
|
if (currentSum === target) {
|
||||||
|
// Try solving remaining capacities
|
||||||
|
const newUsed = new Set(usedSet);
|
||||||
|
for (const idx of subset) newUsed.add(idx);
|
||||||
|
const result = findPartition(available, remaining, newUsed);
|
||||||
|
if (result !== null) {
|
||||||
|
return [subset.slice(), ...result];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentSum > target) return null;
|
||||||
|
|
||||||
|
for (let i = startIdx; i < available.length; i++) {
|
||||||
|
if (usedSet.has(i)) continue;
|
||||||
|
if (currentSum + available[i] > target) continue;
|
||||||
|
|
||||||
|
// Pruning: check if remaining available sum can reach target
|
||||||
|
let futureSum = 0;
|
||||||
|
for (let j = i; j < available.length; j++) {
|
||||||
|
if (!usedSet.has(j) && !subset.includes(j)) futureSum += available[j];
|
||||||
|
}
|
||||||
|
if (currentSum + futureSum < target) return null;
|
||||||
|
|
||||||
|
subset.push(i);
|
||||||
|
const result = dfs(i + 1, currentSum + available[i]);
|
||||||
|
if (result) return result;
|
||||||
|
subset.pop();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dfs(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort ships descending for better pruning (hardest to fill first)
|
||||||
|
const sortedShips = [...ships].sort((a, b) => b - a);
|
||||||
|
|
||||||
|
console.log(`\nSolving for ships (sorted desc): [${sortedShips.join(', ')}]`);
|
||||||
|
|
||||||
|
const solution = findPartition(asteroids, sortedShips);
|
||||||
|
|
||||||
|
if (solution) {
|
||||||
|
console.log('\n✓ SOLVABLE! Solution found:');
|
||||||
|
// Map back to original ship order
|
||||||
|
const shipOrder = ships.map((cap, idx) => ({ cap, idx }))
|
||||||
|
.sort((a, b) => b.cap - a.cap);
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedShips.length; i++) {
|
||||||
|
const indices = solution[i];
|
||||||
|
const values = indices.map(j => asteroids[j]);
|
||||||
|
const origShipIdx = shipOrder[i].idx;
|
||||||
|
console.log(` Ship ${origShipIdx + 1} (capacity ${sortedShips[i]}): asteroids [${values.join(' + ')}] = ${values.reduce((a, b) => a + b, 0)} (indices: ${indices.join(',')})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const usedIndices = new Set(solution.flat());
|
||||||
|
const unused = asteroids.map((v, i) => ({ v, i })).filter(x => !usedIndices.has(x.i));
|
||||||
|
if (unused.length > 0) {
|
||||||
|
console.log(` Unused asteroids: [${unused.map(x => `${x.v}(idx ${x.i})`).join(', ')}]`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('\n✗ NO SOLUTION found.');
|
||||||
|
console.log(` Ship sum: ${ships.reduce((a, b) => a + b, 0)}, Asteroid sum: ${asteroids.reduce((a, b) => a + b, 0)}`);
|
||||||
|
if (asteroids.reduce((a, b) => a + b, 0) < ships.reduce((a, b) => a + b, 0)) {
|
||||||
|
console.log(' Reason: asteroid sum < ship sum (mathematically impossible)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
{
|
||||||
|
"id": "collecting-asteroids-3",
|
||||||
|
"title": "Собери Астероиды",
|
||||||
|
"description": "9-page asteroid-matching worksheet v3: reshuffled assets, halved asteroid values",
|
||||||
|
"labels": {
|
||||||
|
"title": "Собери Астероиды",
|
||||||
|
"subtitle": "Загрузи трюмы кораблей!",
|
||||||
|
"footerBubble": "Итого загружено:"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"style": "space-asteroids",
|
||||||
|
"template": "space-base",
|
||||||
|
"asteroids": "assets/icons/pack3-asteroids/"
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"type": "asteroid-matching",
|
||||||
|
"shipsPerPage": 3,
|
||||||
|
"asteroidTypesPerPage": 2,
|
||||||
|
"asteroidTypeRatio": "70:30"
|
||||||
|
},
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 11, "fillCount": 2 },
|
||||||
|
{ "capacity": 9, "fillCount": 2 },
|
||||||
|
{ "capacity": 13, "fillCount": 6 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter4.png",
|
||||||
|
"footerImage": "assets/footers/cabin3.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"],
|
||||||
|
"asteroidTypes": ["asteroid3", "asteroid10"],
|
||||||
|
"asteroidValues": [3, 2, 1, 4, 5, 2, 3, 6, 5, 4, 1, 6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 12, "fillCount": 3 },
|
||||||
|
{ "capacity": 12, "fillCount": 3 },
|
||||||
|
{ "capacity": 18, "fillCount": 4 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter7.png",
|
||||||
|
"footerImage": "assets/footers/cabin6.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"],
|
||||||
|
"asteroidTypes": ["asteroid6", "asteroid13"],
|
||||||
|
"asteroidValues": [4, 5, 3, 6, 2, 7, 4, 3, 5, 2, 6, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 9, "fillCount": 5 },
|
||||||
|
{ "capacity": 11, "fillCount": 3 },
|
||||||
|
{ "capacity": 11, "fillCount": 2 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter1.png",
|
||||||
|
"footerImage": "assets/footers/cabin9.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"],
|
||||||
|
"asteroidTypes": ["asteroid2", "asteroid8"],
|
||||||
|
"asteroidValues": [2, 4, 1, 3, 5, 2, 1, 3, 4, 6, 5, 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 15, "fillCount": 4 },
|
||||||
|
{ "capacity": 17, "fillCount": 5 },
|
||||||
|
{ "capacity": 11, "fillCount": 2 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter6.png",
|
||||||
|
"footerImage": "assets/footers/cabin2.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"],
|
||||||
|
"asteroidTypes": ["asteroid12", "asteroid15"],
|
||||||
|
"asteroidValues": [4, 3, 5, 2, 6, 1, 3, 4, 6, 2, 5, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 7, "fillCount": 2 },
|
||||||
|
{ "capacity": 9, "fillCount": 4 },
|
||||||
|
{ "capacity": 7, "fillCount": 2 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter2.png",
|
||||||
|
"footerImage": "assets/footers/cabin5.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"],
|
||||||
|
"asteroidTypes": ["asteroid1", "asteroid9"],
|
||||||
|
"asteroidValues": [3, 1, 4, 2, 5, 1, 6, 2, 3, 4, 5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 18, "fillCount": 4 },
|
||||||
|
{ "capacity": 18, "fillCount": 5 },
|
||||||
|
{ "capacity": 14, "fillCount": 3 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter8.png",
|
||||||
|
"footerImage": "assets/footers/cabin7.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"],
|
||||||
|
"asteroidTypes": ["asteroid5", "asteroid16"],
|
||||||
|
"asteroidValues": [3, 4, 5, 6, 2, 4, 7, 3, 2, 5, 6, 4, 3, 7]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 10, "fillCount": 2 },
|
||||||
|
{ "capacity": 16, "fillCount": 3 },
|
||||||
|
{ "capacity": 22, "fillCount": 6 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter9.png",
|
||||||
|
"footerImage": "assets/footers/cabin4.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"cargoBays": ["cargo-bay7", "cargo-bay8", "cargo-bay9"],
|
||||||
|
"asteroidTypes": ["asteroid11", "asteroid7"],
|
||||||
|
"asteroidValues": [4, 3, 2, 6, 5, 3, 7, 4, 5, 2, 6, 4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 26, "fillCount": 0 },
|
||||||
|
{ "capacity": 18, "fillCount": 0 },
|
||||||
|
{ "capacity": 30, "fillCount": 0 }
|
||||||
|
],
|
||||||
|
"_unsolvable": true,
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter5.png",
|
||||||
|
"footerImage": "assets/footers/cabin1.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"cargoBays": ["cargo-bay1", "cargo-bay2", "cargo-bay3"],
|
||||||
|
"asteroidTypes": ["asteroid14", "asteroid4"],
|
||||||
|
"asteroidValues": [6, 4, 5, 3, 7, 2, 4, 6, 5, 7, 3, 4, 6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ships": [
|
||||||
|
{ "capacity": 8, "fillCount": 2 },
|
||||||
|
{ "capacity": 18, "fillCount": 3 },
|
||||||
|
{ "capacity": 28, "fillCount": 8 }
|
||||||
|
],
|
||||||
|
"heroImage": "assets/hero-images/splitters/splitter3.png",
|
||||||
|
"footerImage": "assets/footers/cabin8.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"cargoBays": ["cargo-bay4", "cargo-bay5", "cargo-bay6"],
|
||||||
|
"asteroidTypes": ["asteroid10", "asteroid6"],
|
||||||
|
"asteroidValues": [4, 3, 2, 5, 6, 3, 4, 7, 2, 5, 4, 6, 3, 7, 5]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"id": "space-worksheet3",
|
||||||
|
"title": "Исследуй Планету",
|
||||||
|
"description": "9-page space worksheet combining patterns from worksheet1 and worksheet2: addition, multiply by 5, multiplication table, constrained addition, mixed fives, compound multiplication",
|
||||||
|
"labels": {
|
||||||
|
"title": "Исследуй Планету",
|
||||||
|
"subtitle": "Собери ресурсы, решая примеры!",
|
||||||
|
"footerBubble": "Итого собрано на планете:"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"style": "space",
|
||||||
|
"template": "space-base",
|
||||||
|
"icons": ["assets/icons/pack1/", "assets/icons/pack2/"]
|
||||||
|
},
|
||||||
|
"layout": {
|
||||||
|
"columns": 2,
|
||||||
|
"problemsPerPage": 20
|
||||||
|
},
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"task": "A + B ± C, где A от 8 до 16, B от 4 до 8, C от 1 до 3. Знак ± выбирается случайно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship1.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet1.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"iconPack": "pack1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "5 × N ± C, где N от 1 до 5, C от 1 до 6. Знак ± выбирается случайно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship2.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet2.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"iconPack": "pack1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "A × B ± C, где A и B от 1 до 4, C от 1 до 8. Знак ± выбирается случайно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship3.png",
|
||||||
|
"footerImage": "assets/footers/planet3.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"iconPack": "pack1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "A + B + C, где A от 12 до 24, B ±(от 6 до 10), C ±(от 2 до 5). Результат от 0 до 40 включительно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship4.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet4.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"iconPack": "pack2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "5 + 5 + 5 (сложение пятерок: 2, 3 или 4 штуки) ИЛИ 5 × N (N от 1 до 12) ИЛИ 5 × N ± 5",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship5.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet5.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"iconPack": "pack2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "A × B + C × D, где A от 2 до 4, B от 1 до 3, C от 2 до 3, D от 1 до 2",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship6.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet6.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"iconPack": "pack2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "A + B ± C, где A от 8 до 16, B от 4 до 8, C от 1 до 3. Знак ± выбирается случайно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship7.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet7.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"iconPack": "pack1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "5 × N ± C, где N от 1 до 5, C от 1 до 6. Знак ± выбирается случайно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship8.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet8.jpeg",
|
||||||
|
"heroDirection": "row",
|
||||||
|
"iconPack": "pack1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"task": "A × B ± C, где A и B от 1 до 4, C от 1 до 8. Знак ± выбирается случайно",
|
||||||
|
"problemCount": 20,
|
||||||
|
"heroImage": "assets/hero-images/spaceship9.jpeg",
|
||||||
|
"footerImage": "assets/footers/planet9.jpeg",
|
||||||
|
"heroDirection": "row-reverse",
|
||||||
|
"iconPack": "pack1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue