naider 1.24.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -83
- package/SPEC.naide +8 -6
- package/bin/naide.js +42 -103
- package/bin/postinstall.js +5 -15
- package/examples/model.naide +1 -1
- package/lsp/server.js +1 -2
- package/mcp/server.js +2 -2
- package/package.json +2 -4
- package/src/gen.js +975 -273
- package/src/generator.js +5 -1
- package/src/index.js +34 -14
- package/src/parser.js +15 -14
- package/src/typechecker.js +60 -13
- package/vscode-naide/README.md +4 -6
- package/vscode-naide/extension.js +1 -2
- package/vscode-naide/package-lock.json +2 -2
- package/vscode-naide/package.json +3 -15
- package/SPEC-X.nx +0 -276
- package/assets/nx.ico +0 -0
- package/assets/nx_check.png +0 -0
- package/examples/ai-app.nx +0 -21
- package/examples/async.nx +0 -40
- package/examples/crud.nx +0 -64
- package/examples/fullapp.nx +0 -74
- package/examples/hello.nx +0 -29
- package/examples/server.nx +0 -27
- package/examples/sql-app.nx +0 -23
- package/examples/todo-api.nx +0 -31
- package/src/preprocess.js +0 -308
- package/vscode-naide/language-configuration-nx.json +0 -32
- package/vscode-naide/naide-lang-1.1.0.vsix +0 -0
- package/vscode-naide/syntaxes/naidex.tmLanguage.json +0 -287
package/src/gen.js
CHANGED
|
@@ -100,6 +100,20 @@ const GAME_TYPES = {
|
|
|
100
100
|
reaction: ['reaction', 'reflex', 'リアクション', '反射', '反応'],
|
|
101
101
|
snake: ['snake', 'スネーク', 'ヘビ'],
|
|
102
102
|
breakout: ['breakout', 'brick', 'block', 'ブロック崩し', 'ブレイクアウト'],
|
|
103
|
+
dodge: ['dodge', 'avoid', 'escape', '避け', 'ドッジ', '回避'],
|
|
104
|
+
color: ['color', 'colour', 'stroop', 'カラー', '色'],
|
|
105
|
+
pong: ['pong', 'paddle', 'ポン', 'パドル', 'ピンポン'],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const APP_TYPES = {
|
|
109
|
+
pomodoro: ['pomodoro', 'ポモドーロ'],
|
|
110
|
+
calculator: ['calculator', 'calc', '計算', '計算機', '電卓'],
|
|
111
|
+
drawing: ['drawing', 'draw', 'paint', 'sketch', 'お絵描き', 'ペイント', '落書き'],
|
|
112
|
+
timer: ['timer', 'stopwatch', 'タイマー', 'ストップウォッチ'],
|
|
113
|
+
counter: ['counter', 'カウンター', 'カウント'],
|
|
114
|
+
todo: ['todo', 'todos', 'タスク', 'やること', 'やることリスト'],
|
|
115
|
+
clock: ['clock', 'watch', '時計', 'クロック'],
|
|
116
|
+
notes: ['notes', 'note', 'memo', 'メモ', 'ノート'],
|
|
103
117
|
};
|
|
104
118
|
|
|
105
119
|
const NOISE = new Set([
|
|
@@ -111,6 +125,9 @@ const NOISE = new Set([
|
|
|
111
125
|
'game', 'play', 'player', 'score', 'level', 'arcade', 'highscore',
|
|
112
126
|
'tapping', 'tap', 'clicker', 'click', 'quiz', 'trivia', 'memory', 'matching', 'typing',
|
|
113
127
|
'reaction', 'reflex', 'snake', 'breakout', 'brick', 'block', 'pong', 'puzzle',
|
|
128
|
+
'dodge', 'avoid', 'escape', 'color', 'colour', 'stroop', 'paddle',
|
|
129
|
+
'calculator', 'calc', 'timer', 'stopwatch', 'counter', 'pomodoro',
|
|
130
|
+
'clock', 'watch', 'drawing', 'draw', 'paint', 'sketch', 'notes', 'note', 'memo', 'app',
|
|
114
131
|
'の', 'を', 'に', 'で', 'は', 'が', 'と', 'も', 'から', 'まで', 'より', 'って',
|
|
115
132
|
'な', 'ような', 'みたいな', 'っぽい', 'ような感じ',
|
|
116
133
|
'作って', '作る', '作成', '生成', '追加', '実装', 'ください', 'して', 'つけて',
|
|
@@ -129,6 +146,16 @@ function detectGameType(words, raw) {
|
|
|
129
146
|
return 'tapping';
|
|
130
147
|
}
|
|
131
148
|
|
|
149
|
+
function detectAppType(words, raw) {
|
|
150
|
+
const lower = raw.toLowerCase();
|
|
151
|
+
for (const [type, kws] of Object.entries(APP_TYPES)) {
|
|
152
|
+
for (const kw of kws) {
|
|
153
|
+
if (lower.includes(kw)) return type;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
132
159
|
// ── Field Normalization ─────────────────────────────────────
|
|
133
160
|
|
|
134
161
|
const FIELD_SYNONYMS = {
|
|
@@ -524,6 +551,21 @@ function classify(words, raw) {
|
|
|
524
551
|
mods.isGame = true;
|
|
525
552
|
}
|
|
526
553
|
|
|
554
|
+
// App intent: standalone frontend apps
|
|
555
|
+
if (!mods.isGame) {
|
|
556
|
+
const appType = detectAppType(words, raw);
|
|
557
|
+
if (appType) {
|
|
558
|
+
const strongServerKw = ['server', 'api', 'rest', 'restful', 'endpoint', 'backend', 'service', 'microservice', 'http', 'web'];
|
|
559
|
+
const hasStrongServer = strongServerKw.some(k => lower.includes(k));
|
|
560
|
+
if (!hasStrongServer && !intents.has('crud') && !intents.has('database') && !intents.has('schema') && !intents.has('auth')) {
|
|
561
|
+
intents.delete('server');
|
|
562
|
+
mods.appType = appType;
|
|
563
|
+
mods.isApp = true;
|
|
564
|
+
intents.add('app');
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
527
569
|
// Default fallback: if nothing detected, server + schema
|
|
528
570
|
if (intents.size === 0) {
|
|
529
571
|
intents.add('server'); intents.add('schema'); intents.add('crud'); intents.add('database');
|
|
@@ -544,6 +586,12 @@ function extractParams(words, raw, intents, mods) {
|
|
|
544
586
|
return params;
|
|
545
587
|
}
|
|
546
588
|
|
|
589
|
+
if (mods.isApp) {
|
|
590
|
+
params.appType = mods.appType;
|
|
591
|
+
params.appTitle = raw.replace(/\b(make|create|build|generate|simple|basic|new|a|an|the)\b/gi, '').trim();
|
|
592
|
+
return params;
|
|
593
|
+
}
|
|
594
|
+
|
|
547
595
|
// Extract port number
|
|
548
596
|
for (let i = 0; i < words.length; i++) {
|
|
549
597
|
const n = parseInt(words[i]);
|
|
@@ -1781,19 +1829,30 @@ function migrationFile(entities, relationships, m2mRels) {
|
|
|
1781
1829
|
|
|
1782
1830
|
// ── Code Composer ───────────────────────────────────────────
|
|
1783
1831
|
|
|
1784
|
-
// ──
|
|
1785
|
-
|
|
1786
|
-
const
|
|
1787
|
-
tapping:
|
|
1788
|
-
quiz:
|
|
1789
|
-
memory:
|
|
1790
|
-
snake:
|
|
1791
|
-
typing:
|
|
1792
|
-
reaction:
|
|
1793
|
-
breakout:
|
|
1832
|
+
// ── Frontend Code Engine (module-based) ──────────────────
|
|
1833
|
+
|
|
1834
|
+
const THEMES = {
|
|
1835
|
+
tapping: { bg:'#1a0a2e', sf:'#2d1a42', pr:'#FF6B35', sc:'#FF3864', ac:'#FFD700', tx:'#fff', mt:'#a89cc8' },
|
|
1836
|
+
quiz: { bg:'#0d1b2a', sf:'#1b2838', pr:'#3A86FF', sc:'#8338EC', ac:'#06D6A0', tx:'#e8ecf1', mt:'#7b8ca3' },
|
|
1837
|
+
memory: { bg:'#1a0f25', sf:'#2d1f3d', pr:'#C77DFF', sc:'#E05297', ac:'#FF6B9D', tx:'#f5f0ff', mt:'#9b8bb4' },
|
|
1838
|
+
snake: { bg:'#071a0e', sf:'#0f2918', pr:'#4ADE80', sc:'#22D3EE', ac:'#FCD34D', tx:'#e8f5e9', mt:'#6b8a7a' },
|
|
1839
|
+
typing: { bg:'#0a192f', sf:'#112240', pr:'#64FFDA', sc:'#00B4D8', ac:'#CCD6F6', tx:'#CCD6F6', mt:'#5c7a8a' },
|
|
1840
|
+
reaction: { bg:'#18181b', sf:'#27272a', pr:'#F87171', sc:'#EF4444', ac:'#4ADE80', tx:'#fafafa', mt:'#71717a' },
|
|
1841
|
+
breakout: { bg:'#0c0a1d', sf:'#1c1833', pr:'#A78BFA', sc:'#8B5CF6', ac:'#F59E0B', tx:'#f0f0ff', mt:'#8f85a3' },
|
|
1842
|
+
dodge: { bg:'#1a0005', sf:'#2d0a12', pr:'#FF4757', sc:'#FF6B81', ac:'#2ED573', tx:'#fff', mt:'#a88a8f' },
|
|
1843
|
+
color: { bg:'#0a1628', sf:'#132240', pr:'#FF6348', sc:'#FFA502', ac:'#7BED9F', tx:'#f5f6fa', mt:'#778ca3' },
|
|
1844
|
+
pong: { bg:'#0a0a0a', sf:'#1a1a2e', pr:'#00FF87', sc:'#60EFFF', ac:'#FFE66D', tx:'#f0f0f0', mt:'#666680' },
|
|
1845
|
+
calculator: { bg:'#1a1a2e', sf:'#25254a', pr:'#667eea', sc:'#764ba2', ac:'#f093fb', tx:'#e8e8ff', mt:'#8888aa' },
|
|
1846
|
+
timer: { bg:'#0f172a', sf:'#1e293b', pr:'#38bdf8', sc:'#818cf8', ac:'#34d399', tx:'#f1f5f9', mt:'#64748b' },
|
|
1847
|
+
counter: { bg:'#18181b', sf:'#27272a', pr:'#a78bfa', sc:'#c084fc', ac:'#fbbf24', tx:'#fafafa', mt:'#71717a' },
|
|
1848
|
+
todo: { bg:'#fafaf9', sf:'#f5f5f4', pr:'#4f46e5', sc:'#7c3aed', ac:'#10b981', tx:'#1c1917', mt:'#78716c' },
|
|
1849
|
+
pomodoro: { bg:'#1a0000', sf:'#2d0a0a', pr:'#ef4444', sc:'#f97316', ac:'#22c55e', tx:'#fff', mt:'#a89c8c' },
|
|
1850
|
+
clock: { bg:'#020617', sf:'#0f172a', pr:'#e2e8f0', sc:'#94a3b8', ac:'#38bdf8', tx:'#f8fafc', mt:'#475569' },
|
|
1851
|
+
drawing: { bg:'#ffffff', sf:'#f3f4f6', pr:'#3b82f6', sc:'#8b5cf6', ac:'#ef4444', tx:'#111827', mt:'#9ca3af' },
|
|
1852
|
+
notes: { bg:'#fffbeb', sf:'#fef3c7', pr:'#f59e0b', sc:'#d97706', ac:'#059669', tx:'#1c1917', mt:'#92400e' },
|
|
1794
1853
|
};
|
|
1795
1854
|
|
|
1796
|
-
function
|
|
1855
|
+
function extractBuildOpts(raw) {
|
|
1797
1856
|
const o = {};
|
|
1798
1857
|
const d = raw.match(/(\d+)\s*(?:sec|seconds|秒|s\b)/i);
|
|
1799
1858
|
if (d) o.duration = Math.min(120, Math.max(3, parseInt(d[1])));
|
|
@@ -1802,42 +1861,177 @@ function extractGameOpts(raw) {
|
|
|
1802
1861
|
if (/fast|高速|はやい/i.test(raw)) o.speed = 'fast';
|
|
1803
1862
|
if (/hard|難しい|ハード/i.test(raw)) o.difficulty = 'hard';
|
|
1804
1863
|
if (/easy|簡単|イージー/i.test(raw)) o.difficulty = 'easy';
|
|
1864
|
+
if (/dark|ダーク|暗い/i.test(raw)) o.dark = true;
|
|
1865
|
+
if (/sound|sfx|音|サウンド/i.test(raw)) o.sfx = true;
|
|
1866
|
+
if (/history|履歴|ヒストリー/i.test(raw)) o.history = true;
|
|
1867
|
+
if (/category|カテゴリ|分類/i.test(raw)) o.categories = true;
|
|
1805
1868
|
return o;
|
|
1806
1869
|
}
|
|
1807
1870
|
|
|
1808
|
-
|
|
1809
|
-
return `:root{--bg:${p.bg};--sf:${p.sf};--pr:${p.pr};--sc:${p.sc};--ac:${p.ac};--tx:${p.tx};--mt:${p.mt}}`;
|
|
1810
|
-
}
|
|
1871
|
+
// ── CSS Modules ──
|
|
1811
1872
|
|
|
1812
|
-
function cssBase(
|
|
1873
|
+
function cssBase(t) {
|
|
1813
1874
|
return `*{margin:0;padding:0;box-sizing:border-box}
|
|
1814
|
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:${
|
|
1815
|
-
.ct{width:min(420px,92vw);text-align:center}
|
|
1816
|
-
h1{font-size:1.8rem;margin-bottom:.5rem;background:linear-gradient(135deg,${
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
.
|
|
1822
|
-
|
|
1875
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:${t.bg};color:${t.tx};min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow-x:hidden;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent}
|
|
1876
|
+
.ct{width:min(420px,92vw);text-align:center;position:relative}
|
|
1877
|
+
h1{font-size:1.8rem;margin-bottom:.5rem;background:linear-gradient(135deg,${t.pr},${t.sc});-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}`;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
function cssBtn(t) {
|
|
1881
|
+
return `.btn{display:inline-block;padding:.75rem 2rem;border:none;border-radius:12px;font-size:1.1rem;font-weight:600;cursor:pointer;transition:transform .1s,box-shadow .2s;color:#fff;background:linear-gradient(135deg,${t.pr},${t.sc})}
|
|
1882
|
+
.btn:active{transform:scale(.95)}.btn:hover{box-shadow:0 0 24px ${t.pr}66}`;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
function cssScr() {
|
|
1886
|
+
return `.scr{display:none}.scr.on{display:block}`;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function cssDsp() {
|
|
1890
|
+
return `.dsp{font-size:3rem;font-weight:800;margin:.5rem 0;font-variant-numeric:tabular-nums}`;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
function cssStat(t) {
|
|
1894
|
+
return `.st{display:flex;gap:1.5rem;justify-content:center;margin:.75rem 0;font-size:.9rem;color:${t.mt}}
|
|
1895
|
+
.st span{display:flex;flex-direction:column;align-items:center;gap:.2rem}.st b{color:${t.tx};font-size:1.1rem}`;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
function cssBar(t) {
|
|
1899
|
+
return `.bar{width:100%;height:6px;background:${t.sf};border-radius:3px;margin:.5rem 0;overflow:hidden}
|
|
1900
|
+
.bar .fl{height:100%;background:linear-gradient(90deg,${t.ac},${t.pr});transition:width .1s linear;border-radius:3px}`;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
function cssInput(t) {
|
|
1904
|
+
return `.input{background:${t.sf};border:2px solid ${t.pr}44;border-radius:12px;padding:.75rem 1rem;font-size:1.1rem;color:${t.tx};width:100%;outline:none;font-family:inherit;transition:border-color .2s}
|
|
1905
|
+
.input:focus{border-color:${t.pr}}`;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function cssList(t) {
|
|
1909
|
+
return `.list{display:flex;flex-direction:column;gap:8px;margin:1rem 0;text-align:left;max-height:60vh;overflow-y:auto}
|
|
1910
|
+
.list-item{padding:.75rem 1rem;background:${t.sf};border-radius:10px;display:flex;align-items:center;gap:.75rem;transition:opacity .3s,transform .2s;animation:fadeIn .2s ease-out}
|
|
1911
|
+
.list-item.done{opacity:.5;text-decoration:line-through}
|
|
1912
|
+
.list-item .del{margin-left:auto;cursor:pointer;opacity:.4;transition:opacity .2s;background:none;border:none;color:${t.tx};font-size:1.2rem}.list-item .del:hover{opacity:1}`;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function cssFx(t) {
|
|
1916
|
+
return `.cdwn{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:6rem;font-weight:900;color:${t.pr};z-index:99;pointer-events:none;animation:cdPop .6s ease-out}
|
|
1917
|
+
@keyframes cdPop{0%{transform:scale(3);opacity:0}30%{opacity:1}100%{transform:scale(1);opacity:0}}
|
|
1823
1918
|
@keyframes pop{0%{transform:scale(1)}50%{transform:scale(1.15)}100%{transform:scale(1)}}
|
|
1824
|
-
@keyframes fadeUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-60px)}}
|
|
1919
|
+
@keyframes fadeUp{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-60px)}}
|
|
1920
|
+
@keyframes fadeIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
1921
|
+
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}
|
|
1922
|
+
.pcv{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:90}
|
|
1923
|
+
.hi{color:${t.ac}}`;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
function cssCanvas(t) {
|
|
1927
|
+
return `canvas{border-radius:12px;background:${t.sf};display:block;margin:1rem auto;touch-action:none}`;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
function cssGrid(t, cols) {
|
|
1931
|
+
return `.grid{display:grid;grid-template-columns:repeat(${cols},1fr);gap:8px;margin:1rem auto;width:min(${cols*90}px,88vw)}`;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
function cssCards(t, cols) {
|
|
1935
|
+
return `.card{aspect-ratio:1;border-radius:12px;cursor:pointer;perspective:600px;-webkit-perspective:600px}
|
|
1936
|
+
.card .inn{position:relative;width:100%;height:100%;transition:transform .4s;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}
|
|
1937
|
+
.card.flip .inn{transform:rotateY(180deg)}
|
|
1938
|
+
.card .fr,.card .bk{position:absolute;width:100%;height:100%;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:${cols<=4?'2rem':'1.4rem'};backface-visibility:hidden;-webkit-backface-visibility:hidden}
|
|
1939
|
+
.card .fr{background:linear-gradient(135deg,${t.pr},${t.sc})}
|
|
1940
|
+
.card .bk{background:${t.sf};transform:rotateY(180deg);border:2px solid ${t.pr}}
|
|
1941
|
+
.card.done .fr{background:linear-gradient(135deg,${t.ac},#059669)}
|
|
1942
|
+
.card.done{pointer-events:none}`;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// ── JS Modules ──
|
|
1946
|
+
|
|
1947
|
+
function jsHelper() {
|
|
1948
|
+
return `const $=id=>document.getElementById(id);`;
|
|
1825
1949
|
}
|
|
1826
1950
|
|
|
1827
|
-
function
|
|
1828
|
-
return
|
|
1829
|
-
.bar .fl{height:100%;background:linear-gradient(90deg,${p.ac},${p.pr});transition:width .1s linear;border-radius:3px}`;
|
|
1951
|
+
function jsScreen() {
|
|
1952
|
+
return `function show(id){document.querySelectorAll('.scr').forEach(s=>s.classList.remove('on'));$(id).classList.add('on')}`;
|
|
1830
1953
|
}
|
|
1831
1954
|
|
|
1832
|
-
function
|
|
1833
|
-
return `const
|
|
1834
|
-
|
|
1955
|
+
function jsStore(ns) {
|
|
1956
|
+
return `const _ns='${ns}';
|
|
1957
|
+
function sGet(k){try{return JSON.parse(localStorage.getItem(_ns+'_'+k))}catch(e){return null}}
|
|
1958
|
+
function sSet(k,v){localStorage.setItem(_ns+'_'+k,JSON.stringify(v))}
|
|
1959
|
+
function sDel(k){localStorage.removeItem(_ns+'_'+k)}`;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
function jsScore() {
|
|
1963
|
+
return `let _score=0,best=+(sGet('best')||0);
|
|
1835
1964
|
if($('best'))$('best').textContent=best;
|
|
1836
|
-
function
|
|
1837
|
-
function
|
|
1965
|
+
function addPts(n){_score+=n;return _score}
|
|
1966
|
+
function getScore(){return _score}
|
|
1967
|
+
function resetScore(){_score=0}
|
|
1968
|
+
function saveBest(v){if(v>best){best=v;sSet('best',best);if($('best'))$('best').textContent=best;return true}return false}`;
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function jsTimer() {
|
|
1972
|
+
return `let _tmrId;
|
|
1973
|
+
function startTimer(ms,onTick,onEnd){const t0=Date.now();(function lp(){const e=Date.now()-t0;if(e>=ms){onEnd();return}onTick(e,ms);_tmrId=requestAnimationFrame(lp)})()}
|
|
1974
|
+
function stopTimer(){cancelAnimationFrame(_tmrId)}`;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function jsSfx() {
|
|
1978
|
+
return `const _ax=new(window.AudioContext||window.webkitAudioContext)();
|
|
1979
|
+
function sfx(freq,type,dur,vol){const o=_ax.createOscillator(),g=_ax.createGain();o.type=type||'sine';o.frequency.value=freq;g.gain.value=vol||.15;g.gain.exponentialRampToValueAtTime(.001,_ax.currentTime+(dur||.15));o.connect(g);g.connect(_ax.destination);o.start();o.stop(_ax.currentTime+(dur||.15))}
|
|
1980
|
+
function sfxTap(){sfx(800,'square',.06,.12)}
|
|
1981
|
+
function sfxHit(){sfx(520,'sine',.1,.15)}
|
|
1982
|
+
function sfxScore(){sfx(660,'sine',.08,.12);setTimeout(()=>sfx(880,'sine',.08,.12),60)}
|
|
1983
|
+
function sfxWin(){[523,659,784,1047].forEach((f,i)=>setTimeout(()=>sfx(f,'sine',.2,.1),i*100))}
|
|
1984
|
+
function sfxFail(){sfx(200,'sawtooth',.3,.1)}
|
|
1985
|
+
function sfxTick(){sfx(1200,'sine',.03,.08)}
|
|
1986
|
+
function sfxCombo(){sfx(1000,'square',.05,.1);setTimeout(()=>sfx(1400,'square',.05,.1),50)}`;
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
function jsParticles(color1, color2) {
|
|
1990
|
+
return `const _pcv=document.createElement('canvas');_pcv.className='pcv';document.body.appendChild(_pcv);const _pctx=_pcv.getContext('2d');
|
|
1991
|
+
let _pts=[];function _pResize(){_pcv.width=innerWidth;_pcv.height=innerHeight}window.addEventListener('resize',_pResize);_pResize();
|
|
1992
|
+
function emitParticles(x,y,count,c1,c2){for(let i=0;i<(count||12);i++){const a=Math.random()*Math.PI*2,sp=2+Math.random()*4;_pts.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-2,life:1,sz:2+Math.random()*4,c:Math.random()>.5?(c1||'${color1}'):(c2||'${color2}')})}}
|
|
1993
|
+
function _pTick(){_pctx.clearRect(0,0,_pcv.width,_pcv.height);_pts=_pts.filter(p=>{p.x+=p.vx;p.y+=p.vy;p.vy+=.12;p.life-=.025;if(p.life<=0)return false;_pctx.globalAlpha=p.life;_pctx.fillStyle=p.c;_pctx.beginPath();_pctx.arc(p.x,p.y,p.sz*p.life,0,Math.PI*2);_pctx.fill();return true});_pctx.globalAlpha=1;requestAnimationFrame(_pTick)}_pTick();
|
|
1994
|
+
function burstAt(el,n){const r=el.getBoundingClientRect();emitParticles(r.left+r.width/2,r.top+r.height/2,n||16)}
|
|
1995
|
+
function shakeEl(el){el.style.animation='shake .2s';setTimeout(()=>el.style.animation='',200)}`;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
function jsCountdown(acColor) {
|
|
1999
|
+
return `function countdown(fn){let n=3;const t=()=>{if(n>0){const d=document.createElement('div');d.className='cdwn';d.textContent=n;document.body.appendChild(d);sfxTick();setTimeout(()=>d.remove(),600);n--;setTimeout(t,600)}else{const d=document.createElement('div');d.className='cdwn';d.textContent='GO!';d.style.color='${acColor}';document.body.appendChild(d);sfxScore();setTimeout(()=>d.remove(),500);fn()}};t()}`;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
function jsShuffle() {
|
|
2003
|
+
return `function shuf(a){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}`;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
// ── HTML Helpers ──
|
|
2007
|
+
|
|
2008
|
+
function htmlScr(id, content, active) {
|
|
2009
|
+
return `<div class="scr${active?' on':''}" id="${id}">${Array.isArray(content)?content.join('\n'):content}</div>`;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
function htmlBtn(text, onclick) {
|
|
2013
|
+
return `<button class="btn" onclick="${onclick}">${text}</button>`;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
function htmlStats(items) {
|
|
2017
|
+
return `<div class="st">${items.map(i=>`<span>${i.label}<b id="${i.id}">${i.value}</b></span>`).join('')}</div>`;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
function htmlDsp(id, val) {
|
|
2021
|
+
return `<div class="dsp" id="${id}">${val}</div>`;
|
|
1838
2022
|
}
|
|
1839
2023
|
|
|
1840
|
-
function
|
|
2024
|
+
function htmlBar() {
|
|
2025
|
+
return `<div class="bar"><div class="fl" id="bar" style="width:100%"></div></div>`;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function htmlP(text, style) {
|
|
2029
|
+
return `<p${style?' style="'+style+'"':''}>${text}</p>`;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// ── Page Assembler ──
|
|
2033
|
+
|
|
2034
|
+
function assemblePage(title, css, body, js) {
|
|
1841
2035
|
return `<!DOCTYPE html>
|
|
1842
2036
|
<html lang="en">
|
|
1843
2037
|
<head>
|
|
@@ -1851,7 +2045,7 @@ ${css}
|
|
|
1851
2045
|
<body>
|
|
1852
2046
|
<div class="ct">
|
|
1853
2047
|
<h1>${title}</h1>
|
|
1854
|
-
${
|
|
2048
|
+
${body}
|
|
1855
2049
|
</div>
|
|
1856
2050
|
<script>
|
|
1857
2051
|
${js}
|
|
@@ -1860,342 +2054,838 @@ ${js}
|
|
|
1860
2054
|
</html>`;
|
|
1861
2055
|
}
|
|
1862
2056
|
|
|
1863
|
-
// ──
|
|
2057
|
+
// ── Game Builders ──
|
|
1864
2058
|
|
|
1865
|
-
function buildTapping(title, opts) {
|
|
1866
|
-
const p = GAME_PALETTES.tapping;
|
|
2059
|
+
function buildTapping(title, t, opts) {
|
|
1867
2060
|
const dur = opts.duration || 10;
|
|
1868
2061
|
|
|
1869
|
-
const css = [
|
|
1870
|
-
`.tap{width:min(300px,75vw);height:min(300px,75vw);border-radius:50%;background:radial-gradient(circle at 35% 35%,${
|
|
1871
|
-
`.tap:active{box-shadow:0 0 60px ${
|
|
2062
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssBar(t), cssFx(t),
|
|
2063
|
+
`.tap{width:min(300px,75vw);height:min(300px,75vw);border-radius:50%;background:radial-gradient(circle at 35% 35%,${t.pr},${t.sc});display:flex;align-items:center;justify-content:center;margin:1.5rem auto;cursor:pointer;position:relative;font-size:3.5rem;font-weight:900;transition:box-shadow .15s;letter-spacing:-.05em;overflow:hidden}`,
|
|
2064
|
+
`.tap:active{box-shadow:0 0 60px ${t.pr}88}`,
|
|
1872
2065
|
`.tap.on{animation:pulse 1s infinite}`,
|
|
1873
|
-
`@keyframes pulse{0%,100%{box-shadow:0 0 0 0 ${
|
|
2066
|
+
`@keyframes pulse{0%,100%{box-shadow:0 0 0 0 ${t.pr}66}50%{box-shadow:0 0 0 20px ${t.pr}00}}`,
|
|
1874
2067
|
`.rp{position:absolute;border-radius:50%;background:rgba(255,255,255,.25);animation:rpAn .5s ease-out forwards;pointer-events:none}`,
|
|
1875
2068
|
`@keyframes rpAn{0%{width:0;height:0;opacity:.6}100%{width:200px;height:200px;opacity:0}}`,
|
|
1876
|
-
`.cb{position:fixed;font-weight:800;font-size:1.5rem;color:${
|
|
2069
|
+
`.cb{position:fixed;font-weight:800;font-size:1.5rem;color:${t.ac};pointer-events:none;animation:fadeUp .6s ease-out forwards}`,
|
|
1877
2070
|
].join('\n');
|
|
1878
2071
|
|
|
1879
|
-
const html =
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
`function tick(){const e=Date.now()-t0,pc=Math.max(0,1-e/DUR)*100;$('bar').style.width=pc+'%';$('tps').textContent=(taps/Math.max(.1,e/1000)).toFixed(1);if(e>=DUR)return end();raf=requestAnimationFrame(tick)}`,
|
|
1897
|
-
`function hit(ev){ev.preventDefault();if(Date.now()-t0>DUR)return;taps++;const now=Date.now(),gap=now-lh;combo=gap<200?Math.min(combo+1,99):gap>500?1:combo;lh=now;$('cnt').textContent=taps;$('cmb').textContent='x'+combo;$('cnt').style.animation='none';void $('cnt').offsetWidth;$('cnt').style.animation='pop .15s';const el=$('tap'),rc=el.getBoundingClientRect(),cx=(ev.touches?ev.touches[0].clientX:ev.clientX)-rc.left,cy=(ev.touches?ev.touches[0].clientY:ev.clientY)-rc.top;const r=document.createElement('div');r.className='rp';r.style.cssText='left:'+(cx-100)+'px;top:'+(cy-100)+'px';el.appendChild(r);setTimeout(()=>r.remove(),500);if(combo>=3){const c=document.createElement('div');c.className='cb';c.textContent='x'+combo;c.style.cssText='left:'+Math.random()*60+20+'%;top:'+Math.random()*30+20+'%';document.body.appendChild(c);setTimeout(()=>c.remove(),600)}}`,
|
|
1898
|
-
`function end(){cancelAnimationFrame(raf);const tps=(taps/(DUR/1000)).toFixed(1);const nb=saveBest(taps);$('last').textContent=taps;$('fin').textContent=taps;const r=taps>=100?'Insane!':taps>=70?'Amazing!':taps>=50?'Great!':taps>=30?'Nice!':'Keep trying!';$('msg').innerHTML=r+'<br>'+tps+' taps/sec'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2072
|
+
const html = [
|
|
2073
|
+
htmlScr('menu', [
|
|
2074
|
+
htmlP(`Tap as fast as you can — ${dur} seconds!`, `color:${t.mt};margin-bottom:1.5rem`),
|
|
2075
|
+
htmlStats([{label:'Best',id:'best',value:'0'},{label:'Last',id:'last',value:'-'}]),
|
|
2076
|
+
htmlBtn('Start', 'go()'),
|
|
2077
|
+
].join('\n'), true),
|
|
2078
|
+
htmlScr('play', [
|
|
2079
|
+
htmlBar(),
|
|
2080
|
+
htmlDsp('cnt', '0'),
|
|
2081
|
+
`<div class="tap on" id="tap" ontouchstart="hit(event)" onmousedown="hit(event)">TAP</div>`,
|
|
2082
|
+
htmlStats([{label:'Combo',id:'cmb',value:'x1'},{label:'TPS',id:'tps',value:'0'}]),
|
|
2083
|
+
].join('\n')),
|
|
2084
|
+
htmlScr('end', [
|
|
2085
|
+
htmlDsp('fin', '0'),
|
|
2086
|
+
`<div id="msg" style="font-size:1.2rem;line-height:2"></div>`,
|
|
2087
|
+
htmlBtn('Again', 'go()'),
|
|
2088
|
+
].join('\n')),
|
|
1899
2089
|
].join('\n');
|
|
1900
2090
|
|
|
1901
|
-
|
|
1902
|
-
}
|
|
2091
|
+
const js = [jsHelper(), jsScreen(), jsStore('tap'), jsScore(), jsTimer(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac),
|
|
2092
|
+
`const DUR=${dur*1000};let taps,t0,combo,lh;`,
|
|
2093
|
+
`function go(){show('play');countdown(startGame)}`,
|
|
2094
|
+
`function startGame(){taps=0;combo=1;lh=0;t0=Date.now();$('cnt').textContent='0';$('cmb').textContent='x1';$('bar').style.width='100%';startTimer(DUR,tick,end)}`,
|
|
2095
|
+
`function tick(e,ms){const pc=Math.max(0,1-e/ms)*100;$('bar').style.width=pc+'%';$('tps').textContent=(taps/Math.max(.1,e/1000)).toFixed(1)}`,
|
|
2096
|
+
`function hit(ev){ev.preventDefault();if(!t0||Date.now()-t0>DUR)return;taps++;sfxTap();const now=Date.now(),gap=now-lh;combo=gap<200?Math.min(combo+1,99):gap>500?1:combo;lh=now;$('cnt').textContent=taps;$('cmb').textContent='x'+combo;$('cnt').style.animation='none';void $('cnt').offsetWidth;$('cnt').style.animation='pop .15s';const el=$('tap'),rc=el.getBoundingClientRect(),cx=(ev.touches?ev.touches[0].clientX:ev.clientX)-rc.left,cy=(ev.touches?ev.touches[0].clientY:ev.clientY)-rc.top;const r=document.createElement('div');r.className='rp';r.style.cssText='left:'+(cx-100)+'px;top:'+(cy-100)+'px';el.appendChild(r);setTimeout(()=>r.remove(),500);emitParticles((ev.touches?ev.touches[0].clientX:ev.clientX),(ev.touches?ev.touches[0].clientY:ev.clientY),combo>=3?10:4);if(combo>=3){sfxCombo();shakeEl($('cnt'));const c=document.createElement('div');c.className='cb';c.textContent='x'+combo;c.style.cssText='left:'+Math.random()*60+20+'%;top:'+Math.random()*30+20+'%';document.body.appendChild(c);setTimeout(()=>c.remove(),600)}}`,
|
|
2097
|
+
`function end(){stopTimer();const tps=(taps/(DUR/1000)).toFixed(1);const nb=saveBest(taps);$('last').textContent=taps;$('fin').textContent=taps;if(nb&&taps>0){sfxWin();burstAt($('fin'),30)}else sfxFail();const r=taps>=100?'Insane!':taps>=70?'Amazing!':taps>=50?'Great!':taps>=30?'Nice!':'Keep trying!';$('msg').innerHTML=r+'<br>'+tps+' taps/sec'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2098
|
+
].join('\n');
|
|
1903
2099
|
|
|
1904
|
-
|
|
2100
|
+
return assemblePage(title, css, html, js);
|
|
2101
|
+
}
|
|
1905
2102
|
|
|
1906
|
-
function buildQuiz(title, opts) {
|
|
1907
|
-
const p = GAME_PALETTES.quiz;
|
|
2103
|
+
function buildQuiz(title, t, opts) {
|
|
1908
2104
|
const perQ = opts.duration || 15;
|
|
1909
2105
|
|
|
1910
|
-
const css = [
|
|
2106
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t),
|
|
1911
2107
|
`.q{font-size:1.3rem;margin:1.5rem 0;min-height:3em;line-height:1.5}`,
|
|
1912
2108
|
`.ch{display:flex;flex-direction:column;gap:.75rem;margin:1rem 0}`,
|
|
1913
|
-
`.ch div{padding:1rem;border:2px solid ${
|
|
1914
|
-
`.ch div:hover{border-color:${
|
|
1915
|
-
`.ch .ok{border-color:${
|
|
2109
|
+
`.ch div{padding:1rem;border:2px solid ${t.sf};border-radius:12px;background:${t.sf};cursor:pointer;font-size:1rem;text-align:left;transition:border-color .2s,background .2s}`,
|
|
2110
|
+
`.ch div:hover{border-color:${t.pr}}`,
|
|
2111
|
+
`.ch .ok{border-color:${t.ac};background:${t.ac}22}`,
|
|
1916
2112
|
`.ch .no{border-color:#F87171;background:#F8717122}`,
|
|
1917
2113
|
`.dots{display:flex;gap:4px;justify-content:center;margin-bottom:1rem}`,
|
|
1918
|
-
`.dot{width:10px;height:10px;border-radius:50%;background:${
|
|
1919
|
-
`.dot.pass{background:${
|
|
2114
|
+
`.dot{width:10px;height:10px;border-radius:50%;background:${t.sf}}`,
|
|
2115
|
+
`.dot.pass{background:${t.ac}}.dot.fail{background:#F87171}.dot.now{background:${t.pr};box-shadow:0 0 8px ${t.pr}}`,
|
|
1920
2116
|
`.tm{font-size:2.5rem;font-weight:800;font-variant-numeric:tabular-nums}`,
|
|
1921
2117
|
].join('\n');
|
|
1922
2118
|
|
|
1923
|
-
const html =
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2119
|
+
const html = [
|
|
2120
|
+
htmlScr('menu', [
|
|
2121
|
+
htmlP(`10 questions, ${perQ} seconds each`, `color:${t.mt};margin:1rem 0`),
|
|
2122
|
+
htmlStats([{label:'Best',id:'best',value:'0'},{label:'Last',id:'last',value:'-'}]),
|
|
2123
|
+
htmlBtn('Start Quiz', 'go()'),
|
|
2124
|
+
].join('\n'), true),
|
|
2125
|
+
htmlScr('play', `<div class="dots" id="dots"></div>
|
|
1929
2126
|
<div class="tm" id="timer">${perQ}</div>
|
|
1930
2127
|
<div class="q" id="qtext"></div>
|
|
1931
|
-
<div class="ch" id="choices"></div
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
const js = [jsCore('quiz_best'),
|
|
1938
|
-
`const QS=[{q:"What does HTML stand for?",a:["HyperText Markup Language","High Tech Modern Language","Home Tool Markup Language","Hyper Transfer Markup Language"],c:0},{q:"Which planet is closest to the Sun?",a:["Mercury","Venus","Mars","Earth"],c:0},{q:"What is the largest ocean on Earth?",a:["Pacific","Atlantic","Indian","Arctic"],c:0},{q:"In JavaScript, which keyword declares a constant?",a:["const","let","var","static"],c:0},{q:"What year did the World Wide Web go public?",a:["1991","1989","1995","2000"],c:0},{q:"What is the chemical symbol for gold?",a:["Au","Ag","Go","Gd"],c:0},{q:"How many bits are in a byte?",a:["8","4","16","32"],c:0},{q:"What does CSS stand for?",a:["Cascading Style Sheets","Computer Style Sheets","Creative Style System","Coded Style Sheets"],c:0},{q:"What is the speed of light (approx km/s)?",a:["300,000","150,000","500,000","1,000,000"],c:0},{q:"Which data structure uses FIFO?",a:["Queue","Stack","Tree","Graph"],c:0},{q:"What is the square root of 144?",a:["12","14","10","16"],c:0},{q:"Who painted the Mona Lisa?",a:["Da Vinci","Picasso","Van Gogh","Monet"],c:0},{q:"What gas do plants absorb?",a:["CO2","O2","N2","H2"],c:0},{q:"Which protocol is used for secure browsing?",a:["HTTPS","FTP","SMTP","SSH"],c:0},{q:"What is 2^10?",a:["1024","512","2048","4096"],c:0}];`,
|
|
1939
|
-
`const PER_Q=${perQ};let cur,score,pool,tmr,tl;`,
|
|
1940
|
-
`function shuf(a){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}`,
|
|
1941
|
-
`function go(){pool=shuf([...QS]).slice(0,10);cur=0;score=0;$('dots').innerHTML=pool.map((_,i)=>'<div class="dot" id="d'+i+'"></div>').join('');show('play');next()}`,
|
|
1942
|
-
`function next(){if(cur>=pool.length)return end();$('d'+cur).classList.add('now');const q=pool[cur],mx=q.a.map((t,i)=>({t,ok:i===q.c}));shuf(mx);$('qtext').textContent=q.q;const ch=$('choices');ch.innerHTML='';mx.forEach(m=>{const d=document.createElement('div');d.textContent=m.t;d.onclick=()=>pick(d,m.ok,ch);ch.appendChild(d)});tl=PER_Q;$('timer').textContent=tl;clearInterval(tmr);tmr=setInterval(()=>{tl--;$('timer').textContent=Math.max(0,tl);if(tl<=0){clearInterval(tmr);timeout()}},1000)}`,
|
|
1943
|
-
`function pick(el,ok,ch){clearInterval(tmr);ch.querySelectorAll('div').forEach(c=>{c.onclick=null;if(c===el)c.classList.add(ok?'ok':'no');else if(!ok){const t=pool[cur].a[pool[cur].c];if(c.textContent===t)c.classList.add('ok')}});$('d'+cur).classList.remove('now');$('d'+cur).classList.add(ok?'pass':'fail');if(ok)score++;cur++;setTimeout(next,800)}`,
|
|
1944
|
-
`function timeout(){const ch=$('choices');ch.querySelectorAll('div').forEach(c=>{c.onclick=null;if(c.textContent===pool[cur].a[pool[cur].c])c.classList.add('ok')});$('d'+cur).classList.remove('now');$('d'+cur).classList.add('fail');cur++;setTimeout(next,800)}`,
|
|
1945
|
-
`function end(){clearInterval(tmr);const nb=saveBest(score);$('last').textContent=score;$('fin').textContent=score+'/'+pool.length;const pc=Math.round(score/pool.length*100);const r=pc===100?'Perfect!':pc>=80?'Excellent!':pc>=60?'Good!':pc>=40?'Not bad!':'Study more!';$('msg').innerHTML=r+'<br>'+pc+'% correct'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2128
|
+
<div class="ch" id="choices"></div>`),
|
|
2129
|
+
htmlScr('end', [
|
|
2130
|
+
htmlDsp('fin', '0/10'),
|
|
2131
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2132
|
+
htmlBtn('Play Again', 'go()'),
|
|
2133
|
+
].join('\n')),
|
|
1946
2134
|
].join('\n');
|
|
1947
2135
|
|
|
1948
|
-
|
|
1949
|
-
}
|
|
2136
|
+
const js = [jsHelper(), jsScreen(), jsStore('quiz'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsShuffle(),
|
|
2137
|
+
`const QS=[{q:"What does HTML stand for?",a:["HyperText Markup Language","High Tech Modern Language","Home Tool Markup Language","Hyper Transfer Markup Language"],c:0},{q:"Which planet is closest to the Sun?",a:["Mercury","Venus","Mars","Earth"],c:0},{q:"What is the largest ocean on Earth?",a:["Pacific","Atlantic","Indian","Arctic"],c:0},{q:"In JavaScript, which keyword declares a constant?",a:["const","let","var","static"],c:0},{q:"What year did the World Wide Web go public?",a:["1991","1989","1995","2000"],c:0},{q:"What is the chemical symbol for gold?",a:["Au","Ag","Go","Gd"],c:0},{q:"How many bits are in a byte?",a:["8","4","16","32"],c:0},{q:"What does CSS stand for?",a:["Cascading Style Sheets","Computer Style Sheets","Creative Style System","Coded Style Sheets"],c:0},{q:"What is the speed of light (approx km/s)?",a:["300,000","150,000","500,000","1,000,000"],c:0},{q:"Which data structure uses FIFO?",a:["Queue","Stack","Tree","Graph"],c:0},{q:"What is the square root of 144?",a:["12","14","10","16"],c:0},{q:"Who painted the Mona Lisa?",a:["Da Vinci","Picasso","Van Gogh","Monet"],c:0},{q:"What gas do plants absorb?",a:["CO2","O2","N2","H2"],c:0},{q:"Which protocol is used for secure browsing?",a:["HTTPS","FTP","SMTP","SSH"],c:0},{q:"What is 2^10?",a:["1024","512","2048","4096"],c:0},{q:"What is the binary representation of 10?",a:["1010","1100","1001","0110"],c:0},{q:"Which HTTP status code means Not Found?",a:["404","500","403","301"],c:0},{q:"What is the capital of Japan?",a:["Tokyo","Osaka","Kyoto","Nagoya"],c:0},{q:"How many continents are there?",a:["7","6","5","8"],c:0},{q:"What does API stand for?",a:["Application Programming Interface","Advanced Program Integration","Application Process Interface","Automated Programming Interface"],c:0}];`,
|
|
2138
|
+
`const PER_Q=${perQ};let cur,score,pool,tmr,tl,streak=0;`,
|
|
2139
|
+
`function go(){pool=shuf([...QS]).slice(0,10);cur=0;score=0;streak=0;$('dots').innerHTML=pool.map((_,i)=>'<div class="dot" id="d'+i+'"></div>').join('');show('play');next()}`,
|
|
2140
|
+
`function next(){if(cur>=pool.length)return end();$('d'+cur).classList.add('now');const q=pool[cur],mx=q.a.map((t,i)=>({t,ok:i===q.c}));shuf(mx);$('qtext').textContent=q.q;const ch=$('choices');ch.innerHTML='';mx.forEach(m=>{const d=document.createElement('div');d.textContent=m.t;d.onclick=()=>pick(d,m.ok,ch);ch.appendChild(d)});tl=PER_Q;$('timer').textContent=tl;clearInterval(tmr);tmr=setInterval(()=>{tl--;$('timer').textContent=Math.max(0,tl);if(tl<=3&&tl>0)sfxTick();if(tl<=0){clearInterval(tmr);timeout()}},1000)}`,
|
|
2141
|
+
`function pick(el,ok,ch){clearInterval(tmr);ch.querySelectorAll('div').forEach(c=>{c.onclick=null;if(c===el)c.classList.add(ok?'ok':'no');else if(!ok){const t=pool[cur].a[pool[cur].c];if(c.textContent===t)c.classList.add('ok')}});$('d'+cur).classList.remove('now');$('d'+cur).classList.add(ok?'pass':'fail');if(ok){score++;streak++;sfxScore();burstAt(el,streak>=3?20:8)}else{streak=0;sfxFail();shakeEl($('qtext'))}cur++;setTimeout(next,800)}`,
|
|
2142
|
+
`function timeout(){sfxFail();const ch=$('choices');ch.querySelectorAll('div').forEach(c=>{c.onclick=null;if(c.textContent===pool[cur].a[pool[cur].c])c.classList.add('ok')});$('d'+cur).classList.remove('now');$('d'+cur).classList.add('fail');streak=0;cur++;setTimeout(next,800)}`,
|
|
2143
|
+
`function end(){clearInterval(tmr);const nb=saveBest(score);$('last').textContent=score;$('fin').textContent=score+'/'+pool.length;if(nb&&score>0){sfxWin();burstAt($('fin'),30)}const pc=Math.round(score/pool.length*100);const r=pc===100?'Perfect!':pc>=80?'Excellent!':pc>=60?'Good!':pc>=40?'Not bad!':'Study more!';$('msg').innerHTML=r+'<br>'+pc+'% correct'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2144
|
+
].join('\n');
|
|
1950
2145
|
|
|
1951
|
-
|
|
2146
|
+
return assemblePage(title, css, html, js);
|
|
2147
|
+
}
|
|
1952
2148
|
|
|
1953
|
-
function buildMemory(title, opts) {
|
|
1954
|
-
const p = GAME_PALETTES.memory;
|
|
2149
|
+
function buildMemory(title, t, opts) {
|
|
1955
2150
|
const cols = opts.cols || 4;
|
|
1956
2151
|
const pairs = Math.floor((cols * (opts.rows || cols)) / 2);
|
|
1957
2152
|
|
|
1958
|
-
const css = [
|
|
1959
|
-
|
|
1960
|
-
`.card{aspect-ratio:1;border-radius:12px;cursor:pointer;perspective:600px;-webkit-perspective:600px}`,
|
|
1961
|
-
`.card .inn{position:relative;width:100%;height:100%;transition:transform .4s;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}`,
|
|
1962
|
-
`.card.flip .inn{transform:rotateY(180deg)}`,
|
|
1963
|
-
`.card .fr,.card .bk{position:absolute;width:100%;height:100%;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:${cols<=4?'2rem':'1.4rem'};backface-visibility:hidden;-webkit-backface-visibility:hidden}`,
|
|
1964
|
-
`.card .fr{background:linear-gradient(135deg,${p.pr},${p.sc})}`,
|
|
1965
|
-
`.card .bk{background:${p.sf};transform:rotateY(180deg);border:2px solid ${p.pr}}`,
|
|
1966
|
-
`.card.done .fr{background:linear-gradient(135deg,${p.ac},#059669)}`,
|
|
1967
|
-
`.card.done{pointer-events:none}`,
|
|
2153
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t),
|
|
2154
|
+
cssGrid(t, cols), cssCards(t, cols),
|
|
1968
2155
|
].join('\n');
|
|
1969
2156
|
|
|
1970
|
-
const html =
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2157
|
+
const html = [
|
|
2158
|
+
htmlScr('menu', [
|
|
2159
|
+
htmlP(`Match all ${pairs} pairs with fewest moves`, `color:${t.mt};margin:1rem 0`),
|
|
2160
|
+
htmlStats([{label:'Best',id:'best',value:'-'}]),
|
|
2161
|
+
htmlBtn('Start', 'go()'),
|
|
2162
|
+
].join('\n'), true),
|
|
2163
|
+
htmlScr('play', [
|
|
2164
|
+
htmlStats([{label:'Moves',id:'mv',value:'0'},{label:'Pairs',id:'pr',value:`0/${pairs}`},{label:'Time',id:'tm',value:'0s'}]),
|
|
2165
|
+
`<div class="grid" id="grid"></div>`,
|
|
2166
|
+
].join('\n')),
|
|
2167
|
+
htmlScr('end', [
|
|
2168
|
+
htmlDsp('fin', '0'),
|
|
2169
|
+
htmlP('moves', 'font-size:1.2rem;margin:.5rem 0'),
|
|
2170
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2171
|
+
htmlBtn('Play Again', 'go()'),
|
|
2172
|
+
].join('\n')),
|
|
2173
|
+
].join('\n');
|
|
2174
|
+
|
|
2175
|
+
const js = [jsHelper(), jsScreen(), jsStore('mem'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsShuffle(),
|
|
1984
2176
|
`const EM=['🎮','🎯','🚀','💎','🔥','⚡','🌟','🎵','🎨','🏆','🍕','🌈','🦊','🐙','🎃','🍀','🎲','🌸'];`,
|
|
1985
|
-
`const PAIRS=${pairs};let cards,flp,done,moves,tmr,t0;`,
|
|
1986
|
-
`function
|
|
1987
|
-
`function
|
|
1988
|
-
`function
|
|
1989
|
-
`function end(){clearInterval(tmr);const sec=Math.floor((Date.now()-t0)/1000);const nb=saveBest(moves);$('fin').textContent=moves;const r=moves<=PAIRS+2?'Perfect memory!':moves<=PAIRS*2?'Great!':moves<=PAIRS*3?'Good!':'Keep practicing!';$('msg').innerHTML=r+'<br>Completed in '+sec+'s'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2177
|
+
`const PAIRS=${pairs};let cards,flp,done,moves,tmr,t0,cstrk=0;`,
|
|
2178
|
+
`function go(){const pick=shuf([...EM]).slice(0,PAIRS);cards=shuf([...pick,...pick]);flp=[];done=0;moves=0;cstrk=0;$('mv').textContent=0;$('pr').textContent='0/'+PAIRS;const g=$('grid');g.innerHTML='';cards.forEach((em,i)=>{const d=document.createElement('div');d.className='card';d.innerHTML='<div class="inn"><div class="fr">?</div><div class="bk">'+em+'</div></div>';d.onclick=()=>flip(d,i);g.appendChild(d)});t0=Date.now();tmr=setInterval(()=>{$('tm').textContent=Math.floor((Date.now()-t0)/1000)+'s'},200);show('play')}`,
|
|
2179
|
+
`function flip(el,i){if(flp.length>=2||el.classList.contains('flip')||el.classList.contains('done'))return;sfxTap();el.classList.add('flip');flp.push({el,i,v:cards[i]});if(flp.length===2){moves++;$('mv').textContent=moves;const[a,b]=flp;if(a.v===b.v){cstrk++;if(cstrk>=2)sfxCombo();else sfxScore();a.el.classList.add('done');b.el.classList.add('done');burstAt(a.el,cstrk>=2?16:8);burstAt(b.el,cstrk>=2?16:8);done++;$('pr').textContent=done+'/'+PAIRS;flp=[];if(done===PAIRS)end()}else{cstrk=0;sfxHit();setTimeout(()=>{a.el.classList.remove('flip');b.el.classList.remove('flip');flp=[]},700)}}}`,
|
|
2180
|
+
`function end(){clearInterval(tmr);const sec=Math.floor((Date.now()-t0)/1000);const nb=saveBest(moves);if(nb)sfxWin();$('fin').textContent=moves;burstAt($('fin'),30);const r=moves<=PAIRS+2?'Perfect memory!':moves<=PAIRS*2?'Great!':moves<=PAIRS*3?'Good!':'Keep practicing!';$('msg').innerHTML=r+'<br>Completed in '+sec+'s'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
1990
2181
|
].join('\n');
|
|
1991
2182
|
|
|
1992
|
-
return
|
|
2183
|
+
return assemblePage(title, css, html, js);
|
|
1993
2184
|
}
|
|
1994
2185
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
function buildSnake(title, opts) {
|
|
1998
|
-
const p = GAME_PALETTES.snake;
|
|
2186
|
+
function buildSnake(title, t, opts) {
|
|
1999
2187
|
const spd = opts.speed === 'fast' ? 80 : opts.difficulty === 'hard' ? 90 : 120;
|
|
2000
2188
|
const W = 16, S = 20;
|
|
2001
2189
|
|
|
2002
|
-
const css = [
|
|
2003
|
-
`canvas{border-radius:12px;background:${p.sf};display:block;margin:1rem auto;image-rendering:pixelated;touch-action:none}`,
|
|
2190
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t), cssCanvas(t),
|
|
2004
2191
|
`.ctl{display:grid;grid-template-columns:repeat(3,56px);grid-template-rows:repeat(2,56px);gap:6px;justify-content:center;margin:1rem auto}`,
|
|
2005
|
-
`.ctl button{border:none;border-radius:10px;background:${
|
|
2006
|
-
`.ctl button:active{background:${
|
|
2192
|
+
`.ctl button{border:none;border-radius:10px;background:${t.sf};color:${t.tx};font-size:1.4rem;cursor:pointer;display:flex;align-items:center;justify-content:center}`,
|
|
2193
|
+
`.ctl button:active{background:${t.pr}}`,
|
|
2194
|
+
].join('\n');
|
|
2195
|
+
|
|
2196
|
+
const html = [
|
|
2197
|
+
htmlScr('menu', [
|
|
2198
|
+
htmlP('Arrow keys or swipe to control', `color:${t.mt};margin:1rem 0`),
|
|
2199
|
+
htmlStats([{label:'Best',id:'best',value:'0'}]),
|
|
2200
|
+
htmlBtn('Play', 'go()'),
|
|
2201
|
+
].join('\n'), true),
|
|
2202
|
+
htmlScr('play', [
|
|
2203
|
+
htmlStats([{label:'Score',id:'score',value:'0'},{label:'Best',id:'hi',value:'0'}]),
|
|
2204
|
+
`<canvas id="cv" width="${W*S}" height="${W*S}"></canvas>`,
|
|
2205
|
+
`<div class="ctl"><div></div><button ontouchstart="sd(0,-1)" onclick="sd(0,-1)">▲</button><div></div><button ontouchstart="sd(-1,0)" onclick="sd(-1,0)">◀</button><button ontouchstart="sd(0,1)" onclick="sd(0,1)">▼</button><button ontouchstart="sd(1,0)" onclick="sd(1,0)">▶</button></div>`,
|
|
2206
|
+
].join('\n')),
|
|
2207
|
+
htmlScr('end', [
|
|
2208
|
+
htmlDsp('fin', '0'),
|
|
2209
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2210
|
+
htmlBtn('Play Again', 'go()'),
|
|
2211
|
+
].join('\n')),
|
|
2007
2212
|
].join('\n');
|
|
2008
2213
|
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
<button class="btn" onclick="go()">Play</button></div>
|
|
2013
|
-
<div id="s-play" class="hd">
|
|
2014
|
-
<div class="st"><span>Score<b id="score">0</b></span><span>Best<b id="hi">0</b></span></div>
|
|
2015
|
-
<canvas id="cv" width="${W*S}" height="${W*S}"></canvas>
|
|
2016
|
-
<div class="ctl">
|
|
2017
|
-
<div></div><button ontouchstart="sd(0,-1)" onclick="sd(0,-1)">▲</button><div></div>
|
|
2018
|
-
<button ontouchstart="sd(-1,0)" onclick="sd(-1,0)">◀</button><button ontouchstart="sd(0,1)" onclick="sd(0,1)">▼</button><button ontouchstart="sd(1,0)" onclick="sd(1,0)">▶</button></div></div>
|
|
2019
|
-
<div id="s-end" class="hd">
|
|
2020
|
-
<div class="sc" id="fin">0</div>
|
|
2021
|
-
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2022
|
-
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2023
|
-
|
|
2024
|
-
const js = [jsCore('snake_best'),
|
|
2025
|
-
`const GS=${S},GW=${W},cv=$('cv'),ctx=cv.getContext('2d');`,
|
|
2026
|
-
`let sn,dir,nd,food,sc,lp;$('hi').textContent=best;`,
|
|
2214
|
+
const js = [jsHelper(), jsScreen(), jsStore('snake'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac),
|
|
2215
|
+
`const GS=${S},GW=${W},cv=$('cv'),ctx=cv.getContext('2d'),BASE_SPD=${spd};`,
|
|
2216
|
+
`let sn,dir,nd,food,sc,lp,spd;$('hi').textContent=best;`,
|
|
2027
2217
|
`function rnd(){let p;do{p={x:Math.floor(Math.random()*GW),y:Math.floor(Math.random()*GW)}}while(sn.some(s=>s.x===p.x&&s.y===p.y));return p}`,
|
|
2028
|
-
`function go(){
|
|
2218
|
+
`function go(){show('play');countdown(startGame)}`,
|
|
2219
|
+
`function startGame(){sn=[{x:8,y:8},{x:7,y:8},{x:6,y:8}];dir={x:1,y:0};nd={x:1,y:0};food=rnd();sc=0;spd=BASE_SPD;$('score').textContent=0;clearInterval(lp);lp=setInterval(tick,spd)}`,
|
|
2029
2220
|
`function sd(x,y){if(x===-dir.x&&y===-dir.y)return;nd={x,y}}`,
|
|
2030
|
-
`function tick(){dir=nd;const h={x:sn[0].x+dir.x,y:sn[0].y+dir.y};if(h.x<0||h.x>=GW||h.y<0||h.y>=GW||sn.some(s=>s.x===h.x&&s.y===h.y))return end();sn.unshift(h);if(h.x===food.x&&h.y===food.y){sc
|
|
2031
|
-
`function draw(){ctx.fillStyle='${
|
|
2032
|
-
`function end(){clearInterval(lp);const nb=saveBest(sc);$('hi').textContent=best;$('fin').textContent=sc;const r=sc>=30?'Legend!':sc>=20?'Amazing!':sc>=10?'Great!':sc>=5?'Good!':'Try again!';$('msg').innerHTML=r+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2033
|
-
`document.addEventListener('keydown',e=>{if(e.key==='ArrowUp')sd(0,-1)
|
|
2221
|
+
`function tick(){dir=nd;const h={x:sn[0].x+dir.x,y:sn[0].y+dir.y};if(h.x<0||h.x>=GW||h.y<0||h.y>=GW||sn.some(s=>s.x===h.x&&s.y===h.y))return end();sn.unshift(h);if(h.x===food.x&&h.y===food.y){sc++;sfxScore();$('score').textContent=sc;const rc=cv.getBoundingClientRect();emitParticles(rc.left+food.x*GS*(rc.width/(GW*GS))+GS/2,rc.top+food.y*GS*(rc.height/(GW*GS))+GS/2,10);food=rnd();if(sc%5===0&&spd>50){spd-=8;clearInterval(lp);lp=setInterval(tick,spd)}}else sn.pop();draw()}`,
|
|
2222
|
+
`function draw(){ctx.fillStyle='${t.sf}';ctx.fillRect(0,0,GW*GS,GW*GS);for(let x=0;x<GW;x++)for(let y=0;y<GW;y++){ctx.fillStyle='rgba(255,255,255,.015)';if((x+y)%2===0)ctx.fillRect(x*GS,y*GS,GS,GS)}ctx.fillStyle='${t.ac}';ctx.beginPath();ctx.arc(food.x*GS+GS/2,food.y*GS+GS/2,GS/2-2+Math.sin(Date.now()/200)*2,0,Math.PI*2);ctx.fill();ctx.shadowColor='${t.ac}';ctx.shadowBlur=8;ctx.fill();ctx.shadowBlur=0;sn.forEach((s,i)=>{const t=i/sn.length;ctx.fillStyle=i===0?'${t.pr}':'hsl('+(140+t*40)+',70%,'+(55-t*20)+'%)';ctx.beginPath();ctx.roundRect(s.x*GS+1,s.y*GS+1,GS-2,GS-2,4);ctx.fill();if(i===0){ctx.fillStyle='${t.bg}';const ex=dir.x===1?GS-6:dir.x===-1?4:dir.y===0?GS/2:dir.y===1?GS-6:4;const ey=dir.y===1?GS-6:dir.y===-1?4:dir.x===0?GS/2:dir.x===1?GS/2-2:GS/2-2;ctx.beginPath();ctx.arc(s.x*GS+ex,s.y*GS+ey,2,0,Math.PI*2);ctx.fill()}})}`,
|
|
2223
|
+
`function end(){clearInterval(lp);sfxFail();const nb=saveBest(sc);$('hi').textContent=best;$('fin').textContent=sc;if(nb&&sc>0){sfxWin();burstAt($('fin'),30)}const r=sc>=30?'Legend!':sc>=20?'Amazing!':sc>=10?'Great!':sc>=5?'Good!':'Try again!';$('msg').innerHTML=r+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2224
|
+
`document.addEventListener('keydown',e=>{if(e.key==='ArrowUp'){e.preventDefault();sd(0,-1)}if(e.key==='ArrowDown'){e.preventDefault();sd(0,1)}if(e.key==='ArrowLeft')sd(-1,0);if(e.key==='ArrowRight')sd(1,0)});`,
|
|
2034
2225
|
`let tx,ty;cv.addEventListener('touchstart',e=>{tx=e.touches[0].clientX;ty=e.touches[0].clientY},{passive:true});cv.addEventListener('touchend',e=>{const dx=e.changedTouches[0].clientX-tx,dy=e.changedTouches[0].clientY-ty;if(Math.abs(dx)>Math.abs(dy))sd(dx>0?1:-1,0);else sd(0,dy>0?1:-1)},{passive:true});`,
|
|
2035
2226
|
].join('\n');
|
|
2036
2227
|
|
|
2037
|
-
return
|
|
2228
|
+
return assemblePage(title, css, html, js);
|
|
2038
2229
|
}
|
|
2039
2230
|
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
function buildReaction(title, opts) {
|
|
2043
|
-
const p = GAME_PALETTES.reaction;
|
|
2231
|
+
function buildReaction(title, t, opts) {
|
|
2044
2232
|
const rounds = opts.difficulty === 'hard' ? 10 : 5;
|
|
2045
2233
|
|
|
2046
|
-
const css = [
|
|
2234
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t),
|
|
2047
2235
|
`.zone{width:min(340px,85vw);height:min(340px,85vw);border-radius:24px;display:flex;align-items:center;justify-content:center;margin:1.5rem auto;cursor:pointer;font-size:1.2rem;font-weight:600;transition:background .2s}`,
|
|
2048
|
-
`.zone.wait{background:${
|
|
2049
|
-
`.zone.ready{background:${
|
|
2050
|
-
`.zone.go{background:${
|
|
2236
|
+
`.zone.wait{background:${t.sf}}`,
|
|
2237
|
+
`.zone.ready{background:${t.pr};color:#fff}`,
|
|
2238
|
+
`.zone.go{background:${t.ac};color:${t.bg}}`,
|
|
2051
2239
|
`.zone.early{background:#DC2626}`,
|
|
2052
2240
|
`.times{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:1rem 0}`,
|
|
2053
|
-
`.times span{background:${
|
|
2241
|
+
`.times span{background:${t.sf};padding:.3rem .6rem;border-radius:6px;font-size:.85rem;font-variant-numeric:tabular-nums}`,
|
|
2054
2242
|
`.ms{font-size:3.5rem;font-weight:900;font-variant-numeric:tabular-nums}`,
|
|
2055
2243
|
].join('\n');
|
|
2056
2244
|
|
|
2057
|
-
const html =
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2245
|
+
const html = [
|
|
2246
|
+
htmlScr('menu', [
|
|
2247
|
+
htmlP(`Tap when it turns green — ${rounds} rounds`, `color:${t.mt};margin:1rem 0`),
|
|
2248
|
+
htmlStats([{label:'Best Avg',id:'best',value:'-'}]),
|
|
2249
|
+
htmlBtn('Start', 'go()'),
|
|
2250
|
+
].join('\n'), true),
|
|
2251
|
+
htmlScr('play', [
|
|
2252
|
+
htmlStats([{label:'Round',id:'rnd',value:`1/${rounds}`},{label:'Avg',id:'avg',value:'-'}]),
|
|
2253
|
+
`<div class="zone wait" id="zone" onmousedown="tap()" ontouchstart="tap()"><span id="ztxt">Wait...</span></div>`,
|
|
2254
|
+
`<div class="times" id="times"></div>`,
|
|
2255
|
+
].join('\n')),
|
|
2256
|
+
htmlScr('end', [
|
|
2257
|
+
`<div class="ms" id="fin">0</div>`,
|
|
2258
|
+
htmlP('ms average', 'font-size:1.2rem;margin:.5rem 0'),
|
|
2259
|
+
`<div id="msg" style="margin:1rem 0"></div>`,
|
|
2260
|
+
`<div class="times" id="ftimes"></div>`,
|
|
2261
|
+
htmlBtn('Try Again', 'go()'),
|
|
2262
|
+
].join('\n')),
|
|
2263
|
+
].join('\n');
|
|
2264
|
+
|
|
2265
|
+
const js = [jsHelper(), jsScreen(), jsStore('react'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2073
2266
|
`if(best)$('best').textContent=best+'ms';`,
|
|
2074
2267
|
`const RDS=${rounds};let rd,res,phase,gAt,tmr;`,
|
|
2075
2268
|
`function go(){rd=0;res=[];$('times').innerHTML='';show('play');nr()}`,
|
|
2076
|
-
`function nr(){if(rd>=RDS)return end();$('rnd').textContent=(rd+1)+'/'+RDS;const z=$('zone');z.className='zone ready';$('ztxt').textContent='Wait for green...';phase='wait';clearTimeout(tmr);tmr=setTimeout(()=>{z.className='zone go';$('ztxt').textContent='TAP NOW!';gAt=Date.now();phase='go'},1500+Math.random()*3000)}`,
|
|
2077
|
-
`function tap(){if(phase==='wait'){clearTimeout(tmr);$('zone').className='zone early';$('ztxt').textContent='Too early! Tap to retry';phase='early'}else if(phase==='early'){nr()}else if(phase==='go'){const ms=Date.now()-gAt;res.push(ms);const t=document.createElement('span');t.textContent=ms+'ms';$('times').appendChild(t);$('avg').textContent=Math.round(res.reduce((a,b)=>a+b,0)/res.length)+'ms';rd++;phase='done';setTimeout(nr,600)}}`,
|
|
2078
|
-
`function end(){const avg=Math.round(res.reduce((a,b)=>a+b,0)/res.length);const nb=saveBest(avg);if(nb)$('best').textContent=avg+'ms'
|
|
2269
|
+
`function nr(){if(rd>=RDS)return end();$('rnd').textContent=(rd+1)+'/'+RDS;const z=$('zone');z.className='zone ready';$('ztxt').textContent='Wait for green...';phase='wait';clearTimeout(tmr);tmr=setTimeout(()=>{z.className='zone go';$('ztxt').textContent='TAP NOW!';sfxTick();gAt=Date.now();phase='go'},1500+Math.random()*3000)}`,
|
|
2270
|
+
`function tap(){if(phase==='wait'){clearTimeout(tmr);sfxFail();shakeEl($('zone'));$('zone').className='zone early';$('ztxt').textContent='Too early! Tap to retry';phase='early'}else if(phase==='early'){nr()}else if(phase==='go'){const ms=Date.now()-gAt;res.push(ms);sfxScore();burstAt($('zone'),ms<=250?20:8);const t=document.createElement('span');t.textContent=ms+'ms';if(ms<=200)t.style.color='${t.ac}';$('times').appendChild(t);$('avg').textContent=Math.round(res.reduce((a,b)=>a+b,0)/res.length)+'ms';rd++;phase='done';setTimeout(nr,600)}}`,
|
|
2271
|
+
`function end(){const avg=Math.round(res.reduce((a,b)=>a+b,0)/res.length);const nb=saveBest(avg);if(nb){$('best').textContent=avg+'ms';sfxWin();burstAt($('fin'),30)}$('fin').textContent=avg;$('ftimes').innerHTML=res.map((r,i)=>'<span>R'+(i+1)+': '+r+'ms</span>').join('');const r=avg<=200?'Superhuman!':avg<=250?'Lightning!':avg<=300?'Fast!':avg<=400?'Average':'Keep practicing!';$('msg').innerHTML='<span style="font-size:1.2rem">'+r+'</span>'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2079
2272
|
].join('\n');
|
|
2080
2273
|
|
|
2081
|
-
return
|
|
2274
|
+
return assemblePage(title, css, html, js);
|
|
2082
2275
|
}
|
|
2083
2276
|
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
function buildTyping(title, opts) {
|
|
2087
|
-
const p = GAME_PALETTES.typing;
|
|
2277
|
+
function buildTyping(title, t, opts) {
|
|
2088
2278
|
const dur = opts.duration || 30;
|
|
2089
2279
|
|
|
2090
|
-
const css = [
|
|
2280
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssBar(t), cssFx(t), cssInput(t),
|
|
2091
2281
|
`.wd{font-size:2rem;font-weight:700;letter-spacing:.1em;margin:1.5rem 0;min-height:3rem;display:flex;justify-content:center;gap:2px;flex-wrap:wrap}`,
|
|
2092
|
-
`.wd .c{transition:color .1s}.wd .y{color:${
|
|
2093
|
-
`.wd .cur{border-bottom:3px solid ${
|
|
2282
|
+
`.wd .c{transition:color .1s}.wd .y{color:${t.ac}}.wd .n{color:#F87171}`,
|
|
2283
|
+
`.wd .cur{border-bottom:3px solid ${t.pr};animation:blink 1s step-end infinite}`,
|
|
2094
2284
|
`@keyframes blink{50%{border-color:transparent}}`,
|
|
2095
|
-
`.
|
|
2096
|
-
`.wq{color:${p.mt};font-size:1rem;margin:.5rem 0;height:2em;overflow:hidden;display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}`,
|
|
2285
|
+
`.wq{color:${t.mt};font-size:1rem;margin:.5rem 0;height:2em;overflow:hidden;display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}`,
|
|
2097
2286
|
].join('\n');
|
|
2098
2287
|
|
|
2099
|
-
const html =
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2288
|
+
const html = [
|
|
2289
|
+
htmlScr('menu', [
|
|
2290
|
+
htmlP(`Type as many words as you can in ${dur} seconds`, `color:${t.mt};margin:1rem 0`),
|
|
2291
|
+
htmlStats([{label:'Best WPM',id:'best',value:'0'}]),
|
|
2292
|
+
htmlBtn('Start', 'go()'),
|
|
2293
|
+
].join('\n'), true),
|
|
2294
|
+
htmlScr('play', [
|
|
2295
|
+
htmlStats([{label:'Time',id:'timer',value:dur},{label:'Words',id:'wc',value:'0'},{label:'WPM',id:'wpm',value:'0'}]),
|
|
2296
|
+
htmlBar(),
|
|
2297
|
+
`<div class="wd" id="disp"></div>`,
|
|
2298
|
+
`<div class="wq" id="queue"></div>`,
|
|
2299
|
+
`<input class="input" id="inp" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" placeholder="Type here...">`,
|
|
2300
|
+
].join('\n')),
|
|
2301
|
+
htmlScr('end', [
|
|
2302
|
+
htmlDsp('fin', '0'),
|
|
2303
|
+
htmlP('WPM', 'font-size:1.2rem;margin:.5rem 0'),
|
|
2304
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2305
|
+
htmlBtn('Play Again', 'go()'),
|
|
2306
|
+
].join('\n')),
|
|
2307
|
+
].join('\n');
|
|
2308
|
+
|
|
2309
|
+
const js = [jsHelper(), jsScreen(), jsStore('type'), jsScore(), jsTimer(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac), jsShuffle(),
|
|
2116
2310
|
`const WDS=["the","be","to","and","in","that","have","for","not","with","you","this","but","from","they","say","she","or","will","my","one","all","would","there","what","so","up","out","if","about","who","get","which","go","when","make","can","like","time","just","know","take","people","into","year","good","some","could","them","see","other","than","then","now","look","only","come","its","over","think","also","back","after","use","two","how","work","first","well","even","new","want","give","most","find","here","thing","code","fast","type","game","play","next","open","read","test","data","file","line","word","text","long","name","part","best","help","made","move","hand","high","keep","last","same","tell","set","each","much","turn","real","show","full","form","left","start","might","run","need","home","life","old","big","end","point","still","call","live","away","right","hard","plan","team","late","mind","wait","stop","must","land","close","draw","press","mark","step","pick","rock","blue","deep","dark","hold","four","week","room","free","fall","base","rest","less","note","hear"];`,
|
|
2117
|
-
`const DUR=${dur*1000};let pool,ci,pos,done,t0,tmr;`,
|
|
2118
|
-
`function
|
|
2119
|
-
`function go(){pool=shuf([...WDS]);ci=0;pos=0;done=0;t0=Date.now();$('wc').textContent=0;$('wpm').textContent=0;$('inp').value='';show('play');$('inp').focus();render();tmr=setInterval(tick,100)}`,
|
|
2311
|
+
`const DUR=${dur*1000};let pool,ci,pos,done,t0,tmr,tried=0,streak=0;`,
|
|
2312
|
+
`function go(){show('play');countdown(()=>{pool=shuf([...WDS]);ci=0;pos=0;done=0;tried=0;streak=0;t0=Date.now();$('wc').textContent=0;$('wpm').textContent=0;$('inp').value='';$('inp').focus();render();tmr=setInterval(tick,100)})}`,
|
|
2120
2313
|
`function render(){const w=pool[ci%pool.length];const v=$('inp').value;$('disp').innerHTML=w.split('').map((c,i)=>{let cl=i<pos?(v[i]===c?'y':'n'):'';if(i===pos)cl+=' cur';return'<span class="c '+cl+'">'+c+'</span>'}).join('');$('queue').innerHTML='';for(let i=1;i<=5;i++)$('queue').innerHTML+='<span>'+pool[(ci+i)%pool.length]+'</span>'}`,
|
|
2121
|
-
`function tick(){const e=Date.now()-t0,pc=Math.max(0,1-e/DUR)*100;$('bar').style.width=pc+'%';$('timer').textContent=Math.max(0,Math.ceil((DUR-e)/1000));if(e>=DUR)end()}`,
|
|
2122
|
-
`$('inp').addEventListener('input',function(){if(Date.now()-t0>DUR)return;const w=pool[ci%pool.length],v=this.value;pos=v.length;if(v.endsWith(' ')||v.length>=w.length){if(v.trim()===w){done
|
|
2123
|
-
`function end(){clearInterval(tmr);$('inp').blur();const wpm=Math.round(done/(DUR/60000));const nb=saveBest(wpm);$('fin').textContent=wpm;const r=wpm>=80?'Blazing!':wpm>=60?'Excellent!':wpm>=40?'Good!':wpm>=20?'Decent!':'Keep practicing!';$('msg').innerHTML=r+'<br>'+done+' words in ${dur}s'+(nb&&wpm>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2314
|
+
`function tick(){const e=Date.now()-t0,pc=Math.max(0,1-e/DUR)*100;$('bar').style.width=pc+'%';$('timer').textContent=Math.max(0,Math.ceil((DUR-e)/1000));$('wpm').textContent=Math.round(done/Math.max(.01,(e/60000)));if(e>=DUR)end()}`,
|
|
2315
|
+
`$('inp').addEventListener('input',function(){if(!t0||Date.now()-t0>DUR)return;const w=pool[ci%pool.length],v=this.value;pos=v.length;if(v.endsWith(' ')||v.length>=w.length){tried++;if(v.trim()===w){done++;streak++;sfxScore();if(streak>=3){sfxCombo();burstAt($('disp'),12)}$('wc').textContent=done}else{streak=0;sfxHit();shakeEl($('disp'))}ci++;pos=0;this.value=''}render()});`,
|
|
2316
|
+
`function end(){clearInterval(tmr);$('inp').blur();const wpm=Math.round(done/(DUR/60000));const acc=tried>0?Math.round(done/tried*100):0;const nb=saveBest(wpm);if(nb&&wpm>0){sfxWin();burstAt($('fin'),30)}else sfxFail();$('fin').textContent=wpm;const r=wpm>=80?'Blazing!':wpm>=60?'Excellent!':wpm>=40?'Good!':wpm>=20?'Decent!':'Keep practicing!';$('msg').innerHTML=r+'<br>'+done+' words in ${dur}s — '+acc+'% accuracy'+(nb&&wpm>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2124
2317
|
].join('\n');
|
|
2125
2318
|
|
|
2126
|
-
return
|
|
2319
|
+
return assemblePage(title, css, html, js);
|
|
2127
2320
|
}
|
|
2128
2321
|
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
function buildBreakout(title, opts) {
|
|
2132
|
-
const p = GAME_PALETTES.breakout;
|
|
2322
|
+
function buildBreakout(title, t, opts) {
|
|
2133
2323
|
const COLS = 8, ROWS = opts.difficulty === 'hard' ? 7 : 5;
|
|
2134
2324
|
|
|
2135
|
-
const css = [
|
|
2136
|
-
|
|
2325
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t), cssCanvas(t)].join('\n');
|
|
2326
|
+
|
|
2327
|
+
const html = [
|
|
2328
|
+
htmlScr('menu', [
|
|
2329
|
+
htmlP(`Move the paddle to break all ${COLS * ROWS} blocks`, `color:${t.mt};margin:1rem 0`),
|
|
2330
|
+
htmlStats([{label:'Best',id:'best',value:'0'}]),
|
|
2331
|
+
htmlBtn('Play', 'go()'),
|
|
2332
|
+
].join('\n'), true),
|
|
2333
|
+
htmlScr('play', [
|
|
2334
|
+
htmlStats([{label:'Score',id:'score',value:'0'},{label:'Lives',id:'lives',value:'3'}]),
|
|
2335
|
+
`<canvas id="cv" width="360" height="480"></canvas>`,
|
|
2336
|
+
].join('\n')),
|
|
2337
|
+
htmlScr('end', [
|
|
2338
|
+
htmlDsp('fin', '0'),
|
|
2339
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2340
|
+
htmlBtn('Play Again', 'go()'),
|
|
2341
|
+
].join('\n')),
|
|
2137
2342
|
].join('\n');
|
|
2138
2343
|
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2141
|
-
<div class="st"><span>Best<b id="best">0</b></span></div>
|
|
2142
|
-
<button class="btn" onclick="go()">Play</button></div>
|
|
2143
|
-
<div id="s-play" class="hd">
|
|
2144
|
-
<div class="st"><span>Score<b id="score">0</b></span><span>Lives<b id="lives">3</b></span></div>
|
|
2145
|
-
<canvas id="cv" width="360" height="480"></canvas></div>
|
|
2146
|
-
<div id="s-end" class="hd">
|
|
2147
|
-
<div class="sc" id="fin">0</div>
|
|
2148
|
-
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2149
|
-
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2150
|
-
|
|
2151
|
-
const brickColors = `['${p.pr}','${p.sc}','#A78BFA','${p.ac}','#EC4899']`;
|
|
2152
|
-
const js = [jsCore('brk_best'),
|
|
2344
|
+
const brickColors = `['${t.pr}','${t.sc}','#A78BFA','${t.ac}','#EC4899']`;
|
|
2345
|
+
const js = [jsHelper(), jsScreen(), jsStore('brk'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2153
2346
|
`const cv=$('cv'),ctx=cv.getContext('2d'),CW=360,CH=480,COLS=${COLS},ROWS=${ROWS},BW=CW/COLS-4,BH=18,PW=70,PH=12,BR=6;`,
|
|
2154
2347
|
`const BC=${brickColors};`,
|
|
2155
|
-
`let px,ball,dx,dy,bricks,sc,lives,raf;`,
|
|
2348
|
+
`let px,ball,dx,dy,bricks,sc,lives,raf,cmb=0;`,
|
|
2156
2349
|
`function mkBricks(){bricks=[];for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)bricks.push({x:c*(BW+4)+2,y:r*(BH+4)+40,w:BW,h:BH,color:BC[r%BC.length],alive:true})}`,
|
|
2157
|
-
`function go(){px=CW/2;ball={x:CW/2,y:CH-60};dx=3*(Math.random()>.5?1:-1);dy=-3.5;sc=0;lives=3;mkBricks();$('score').textContent=0;$('lives').textContent=3;show('play');cancelAnimationFrame(raf);tick()}`,
|
|
2350
|
+
`function go(){px=CW/2;ball={x:CW/2,y:CH-60};dx=3*(Math.random()>.5?1:-1);dy=-3.5;sc=0;lives=3;cmb=0;mkBricks();$('score').textContent=0;$('lives').textContent=3;show('play');cancelAnimationFrame(raf);tick()}`,
|
|
2158
2351
|
`function tick(){update();draw();if(lives>0&&bricks.some(b=>b.alive))raf=requestAnimationFrame(tick);else end()}`,
|
|
2159
|
-
`function update(){ball.x+=dx;ball.y+=dy;if(ball.x<=BR||ball.x>=CW-BR)dx=-dx;if(ball.y<=BR)dy=-dy;if(ball.y>=CH-PH-BR-8&&ball.y<=CH-8&&ball.x>=px-PW/2&&ball.x<=px+PW/2){dy=-Math.abs(dy);dx+=((ball.x-px)/(PW/2))*2}if(ball.y>CH){lives
|
|
2160
|
-
`function draw(){ctx.clearRect(0,0,CW,CH);for(const b of bricks){if(!b.alive)continue;ctx.fillStyle=b.color;ctx.beginPath();ctx.roundRect(b.x,b.y,b.w,b.h,4);ctx.fill()}ctx.fillStyle='${
|
|
2161
|
-
`function end(){cancelAnimationFrame(raf);const won=!bricks.some(b=>b.alive);const nb=saveBest(sc);$('fin').textContent=sc;$('msg').innerHTML=(won?'You cleared it!':'Game Over')+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2352
|
+
`function update(){ball.x+=dx;ball.y+=dy;if(ball.x<=BR||ball.x>=CW-BR)dx=-dx;if(ball.y<=BR)dy=-dy;if(ball.y>=CH-PH-BR-8&&ball.y<=CH-8&&ball.x>=px-PW/2&&ball.x<=px+PW/2){dy=-Math.abs(dy);dx+=((ball.x-px)/(PW/2))*2;sfxTap();cmb=0}if(ball.y>CH){lives--;sfxFail();$('lives').textContent=lives;cmb=0;if(lives>0){ball={x:CW/2,y:CH-60};dx=3*(Math.random()>.5?1:-1);dy=-3.5}}let hitAny=false;for(const b of bricks){if(!b.alive)continue;if(ball.x+BR>b.x&&ball.x-BR<b.x+b.w&&ball.y+BR>b.y&&ball.y-BR<b.y+b.h){b.alive=false;dy=-dy;cmb++;const pts=10*(cmb>=5?3:cmb>=3?2:1);sc+=pts;hitAny=true;sfxHit();const rc=cv.getBoundingClientRect();emitParticles(rc.left+b.x*(rc.width/CW)+BW/2,rc.top+b.y*(rc.height/CH)+BH/2,cmb>=3?12:6,b.color);$('score').textContent=sc;if(cmb>=5){const sp=Math.sqrt(dx*dx+dy*dy);if(sp<7){dx*=1.05;dy*=1.05}}}}}`,
|
|
2353
|
+
`function draw(){ctx.clearRect(0,0,CW,CH);for(const b of bricks){if(!b.alive)continue;ctx.fillStyle=b.color;ctx.beginPath();ctx.roundRect(b.x,b.y,b.w,b.h,4);ctx.fill();ctx.fillStyle='rgba(255,255,255,.15)';ctx.fillRect(b.x+2,b.y+2,b.w-4,BH/3)}ctx.fillStyle='${t.pr}';ctx.shadowColor='${t.pr}';ctx.shadowBlur=10;ctx.beginPath();ctx.roundRect(px-PW/2,CH-PH-8,PW,PH,6);ctx.fill();ctx.shadowBlur=0;ctx.fillStyle='${t.ac}';ctx.shadowColor='${t.ac}';ctx.shadowBlur=6;ctx.beginPath();ctx.arc(ball.x,ball.y,BR,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0}`,
|
|
2354
|
+
`function end(){cancelAnimationFrame(raf);const won=!bricks.some(b=>b.alive);const nb=saveBest(sc);if(won||nb)sfxWin();$('fin').textContent=sc;if(nb&&sc>0)burstAt($('fin'),30);$('msg').innerHTML=(won?'You cleared it!':'Game Over')+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2162
2355
|
`cv.addEventListener('mousemove',e=>{const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.clientX-r.left)*(CW/r.width)))});`,
|
|
2163
2356
|
`cv.addEventListener('touchmove',e=>{e.preventDefault();const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.touches[0].clientX-r.left)*(CW/r.width)))},{passive:false});`,
|
|
2164
2357
|
`document.addEventListener('keydown',e=>{if(e.key==='ArrowLeft')px=Math.max(PW/2,px-20);if(e.key==='ArrowRight')px=Math.min(CW-PW/2,px+20)});`,
|
|
2165
2358
|
].join('\n');
|
|
2166
2359
|
|
|
2167
|
-
return
|
|
2360
|
+
return assemblePage(title, css, html, js);
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
function buildDodge(title, t, opts) {
|
|
2364
|
+
const spd = opts.difficulty === 'hard' ? 1.4 : opts.difficulty === 'easy' ? 0.7 : 1.0;
|
|
2365
|
+
|
|
2366
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t), cssCanvas(t)].join('\n');
|
|
2367
|
+
|
|
2368
|
+
const html = [
|
|
2369
|
+
htmlScr('menu', [
|
|
2370
|
+
htmlP('Move to dodge the falling obstacles!', `color:${t.mt};margin:1rem 0`),
|
|
2371
|
+
htmlStats([{label:'Best',id:'best',value:'0'}]),
|
|
2372
|
+
htmlBtn('Play', 'go()'),
|
|
2373
|
+
].join('\n'), true),
|
|
2374
|
+
htmlScr('play', [
|
|
2375
|
+
htmlStats([{label:'Score',id:'score',value:'0'},{label:'Best',id:'hi',value:'0'}]),
|
|
2376
|
+
`<canvas id="cv" width="360" height="520"></canvas>`,
|
|
2377
|
+
].join('\n')),
|
|
2378
|
+
htmlScr('end', [
|
|
2379
|
+
htmlDsp('fin', '0'),
|
|
2380
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2381
|
+
htmlBtn('Play Again', 'go()'),
|
|
2382
|
+
].join('\n')),
|
|
2383
|
+
].join('\n');
|
|
2384
|
+
|
|
2385
|
+
const js = [jsHelper(), jsScreen(), jsStore('dodge'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac),
|
|
2386
|
+
`const cv=$('cv'),ctx=cv.getContext('2d'),CW=360,CH=520,PW=30,PH=30,SPD_MUL=${spd};`,
|
|
2387
|
+
`let px,py,obs,sc,raf,t0,spd,spawnT,alive;$('hi').textContent=best;`,
|
|
2388
|
+
`function go(){show('play');countdown(startGame)}`,
|
|
2389
|
+
`function startGame(){px=CW/2;py=CH-60;obs=[];sc=0;spd=2*SPD_MUL;spawnT=0;alive=true;t0=Date.now();$('score').textContent=0;cancelAnimationFrame(raf);tick()}`,
|
|
2390
|
+
`function tick(){if(!alive)return;update();draw();raf=requestAnimationFrame(tick)}`,
|
|
2391
|
+
`function update(){spawnT++;const diff=Math.min(30,Math.floor((Date.now()-t0)/5000));if(spawnT%(Math.max(8,30-diff*2))===0){const w=20+Math.random()*30;obs.push({x:Math.random()*(CW-w),y:-30,w,h:20+Math.random()*10,s:spd+Math.random()*2+diff*0.3,c:['${t.pr}','${t.sc}','#FF4757','#FF6B81','#FFA502'][Math.floor(Math.random()*5)]})}obs.forEach(o=>o.y+=o.s);obs=obs.filter(o=>{if(o.y>CH){sc++;$('score').textContent=sc;if(sc%10===0)sfxScore();return false}return true});for(const o of obs){if(px+PW/2>o.x&&px-PW/2<o.x+o.w&&py+PH/2>o.y&&py-PH/2<o.y+o.h){alive=false;sfxFail();const rc=cv.getBoundingClientRect();emitParticles(rc.left+px*(rc.width/CW),rc.top+py*(rc.height/CH),25);end();return}}spd=2*SPD_MUL+Math.floor((Date.now()-t0)/10000)*0.5}`,
|
|
2392
|
+
`function draw(){ctx.fillStyle='${t.sf}';ctx.fillRect(0,0,CW,CH);for(let i=0;i<CW;i+=40)for(let j=0;j<CH;j+=40){ctx.fillStyle='rgba(255,255,255,.01)';if((Math.floor(i/40)+Math.floor(j/40))%2===0)ctx.fillRect(i,j,40,40)}obs.forEach(o=>{ctx.fillStyle=o.c;ctx.beginPath();ctx.roundRect(o.x,o.y,o.w,o.h,4);ctx.fill()});ctx.fillStyle='${t.ac}';ctx.shadowColor='${t.ac}';ctx.shadowBlur=12;ctx.beginPath();ctx.arc(px,py,PW/2,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0;ctx.fillStyle='${t.bg}';ctx.beginPath();ctx.arc(px-5,py-4,3,0,Math.PI*2);ctx.arc(px+5,py-4,3,0,Math.PI*2);ctx.fill();ctx.strokeStyle='${t.bg}';ctx.lineWidth=2;ctx.beginPath();ctx.arc(px,py+2,6,0,Math.PI);ctx.stroke()}`,
|
|
2393
|
+
`function end(){cancelAnimationFrame(raf);const nb=saveBest(sc);$('hi').textContent=best;$('fin').textContent=sc;if(nb&&sc>0){sfxWin();burstAt($('fin'),30)}const r=sc>=100?'Untouchable!':sc>=60?'Amazing!':sc>=30?'Great!':sc>=15?'Good!':'Keep dodging!';$('msg').innerHTML=r+(nb&&sc>0?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2394
|
+
`let keys={};document.addEventListener('keydown',e=>{keys[e.key]=true;e.preventDefault()});document.addEventListener('keyup',e=>keys[e.key]=false);`,
|
|
2395
|
+
`setInterval(()=>{if(!alive)return;const s=5;if(keys.ArrowLeft||keys.a)px=Math.max(PW/2,px-s);if(keys.ArrowRight||keys.d)px=Math.min(CW-PW/2,px+s);if(keys.ArrowUp||keys.w)py=Math.max(PH/2,py-s);if(keys.ArrowDown||keys.s)py=Math.min(CH-PH/2,py+s)},16);`,
|
|
2396
|
+
`let dragging=false;cv.addEventListener('touchstart',e=>{dragging=true;const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.touches[0].clientX-r.left)*(CW/r.width)));py=Math.max(PH/2,Math.min(CH-PH/2,(e.touches[0].clientY-r.top)*(CH/r.height)))},{passive:true});`,
|
|
2397
|
+
`cv.addEventListener('touchmove',e=>{e.preventDefault();if(!dragging)return;const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.touches[0].clientX-r.left)*(CW/r.width)));py=Math.max(PH/2,Math.min(CH-PH/2,(e.touches[0].clientY-r.top)*(CH/r.height)))},{passive:false});`,
|
|
2398
|
+
`cv.addEventListener('touchend',()=>dragging=false,{passive:true});`,
|
|
2399
|
+
`cv.addEventListener('mousemove',e=>{if(!alive)return;const r=cv.getBoundingClientRect();px=Math.max(PW/2,Math.min(CW-PW/2,(e.clientX-r.left)*(CW/r.width)));py=Math.max(PH/2,Math.min(CH-PH/2,(e.clientY-r.top)*(CH/r.height)))});`,
|
|
2400
|
+
].join('\n');
|
|
2401
|
+
|
|
2402
|
+
return assemblePage(title, css, html, js);
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
function buildColor(title, t, opts) {
|
|
2406
|
+
const dur = opts.duration || 30;
|
|
2407
|
+
const COLORS = [
|
|
2408
|
+
{name:'Red',hex:'#FF4757'},{name:'Blue',hex:'#3742FA'},{name:'Green',hex:'#2ED573'},
|
|
2409
|
+
{name:'Yellow',hex:'#FFA502'},{name:'Purple',hex:'#8854D0'},{name:'Orange',hex:'#FF6348'},
|
|
2410
|
+
{name:'Pink',hex:'#FF6B81'},{name:'Cyan',hex:'#18DCFF'},
|
|
2411
|
+
];
|
|
2412
|
+
|
|
2413
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssBar(t), cssFx(t),
|
|
2414
|
+
`.clr-name{font-size:2.8rem;font-weight:900;margin:1.5rem 0;letter-spacing:.05em;transition:transform .1s}`,
|
|
2415
|
+
`.opts{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:1rem 0}`,
|
|
2416
|
+
`.opts div{padding:1.2rem;border-radius:14px;cursor:pointer;font-size:1.1rem;font-weight:700;text-align:center;transition:transform .1s,box-shadow .2s;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.4)}`,
|
|
2417
|
+
`.opts div:active{transform:scale(.93)}`,
|
|
2418
|
+
`.strk{font-size:.95rem;color:${t.ac};min-height:1.5em;font-weight:700}`,
|
|
2419
|
+
].join('\n');
|
|
2420
|
+
|
|
2421
|
+
const html = [
|
|
2422
|
+
htmlScr('menu', [
|
|
2423
|
+
htmlP(`Tap the color shown, not the word! ${dur}s`, `color:${t.mt};margin:1rem 0`),
|
|
2424
|
+
htmlStats([{label:'Best',id:'best',value:'0'},{label:'Last',id:'last',value:'-'}]),
|
|
2425
|
+
htmlBtn('Start', 'go()'),
|
|
2426
|
+
].join('\n'), true),
|
|
2427
|
+
htmlScr('play', [
|
|
2428
|
+
htmlStats([{label:'Score',id:'sc',value:'0'},{label:'Time',id:'timer',value:dur},{label:'Streak',id:'strk',value:'0'}]),
|
|
2429
|
+
htmlBar(),
|
|
2430
|
+
`<div class="clr-name" id="word">-</div>`,
|
|
2431
|
+
`<div class="strk" id="stxt"></div>`,
|
|
2432
|
+
`<div class="opts" id="opts"></div>`,
|
|
2433
|
+
].join('\n')),
|
|
2434
|
+
htmlScr('end', [
|
|
2435
|
+
htmlDsp('fin', '0'),
|
|
2436
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2437
|
+
htmlBtn('Play Again', 'go()'),
|
|
2438
|
+
].join('\n')),
|
|
2439
|
+
].join('\n');
|
|
2440
|
+
|
|
2441
|
+
const colorsJS = JSON.stringify(COLORS);
|
|
2442
|
+
const js = [jsHelper(), jsScreen(), jsStore('color'), jsScore(), jsTimer(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac), jsShuffle(),
|
|
2443
|
+
`const CS=${colorsJS};`,
|
|
2444
|
+
`const DUR=${dur*1000};let sc,t0,tmrI,streak,correct;`,
|
|
2445
|
+
`function go(){show('play');countdown(startGame)}`,
|
|
2446
|
+
`function startGame(){sc=0;streak=0;t0=Date.now();$('sc').textContent=0;$('strk').textContent=0;$('stxt').textContent='';tmrI=setInterval(tickI,100);nextRound()}`,
|
|
2447
|
+
`function tickI(){const e=Date.now()-t0,pc=Math.max(0,1-e/DUR)*100;$('bar').style.width=pc+'%';$('timer').textContent=Math.max(0,Math.ceil((DUR-e)/1000));if(e>=DUR)end()}`,
|
|
2448
|
+
`function nextRound(){const pool=shuf([...CS]);const ans=pool[0];const fakeText=pool[1+Math.floor(Math.random()*(pool.length-1))];correct=ans.name;$('word').textContent=fakeText.name;$('word').style.color=ans.hex;const choices=shuf([ans,...shuf(pool.filter(c=>c.name!==ans.name)).slice(0,3)]);const o=$('opts');o.innerHTML='';choices.forEach(c=>{const d=document.createElement('div');d.textContent=c.name;d.style.background=c.hex;d.onclick=()=>pick(c.name===correct,d);o.appendChild(d)})}`,
|
|
2449
|
+
`function pick(ok,el){if(Date.now()-t0>DUR)return;$('opts').querySelectorAll('div').forEach(d=>d.onclick=null);if(ok){sc++;streak++;sfxScore();$('sc').textContent=sc;$('strk').textContent=streak;burstAt(el,streak>=5?20:8);if(streak>=3){sfxCombo();$('stxt').textContent=streak>=10?'ON FIRE!':streak>=5?'AMAZING!':'STREAK!'}}else{streak=0;sfxFail();shakeEl($('word'));$('strk').textContent=0;$('stxt').textContent=''}setTimeout(nextRound,ok?300:600)}`,
|
|
2450
|
+
`function end(){clearInterval(tmrI);const nb=saveBest(sc);$('last').textContent=sc;$('fin').textContent=sc;if(nb&&sc>0){sfxWin();burstAt($('fin'),30)}const r=sc>=50?'Color Master!':sc>=35?'Amazing!':sc>=20?'Great!':sc>=10?'Good!':'Keep trying!';$('msg').innerHTML=r+'<br>'+sc+' in ${dur}s'+(nb?' <span class=hi>★ New Best!</span>':'');show('end')}`,
|
|
2451
|
+
].join('\n');
|
|
2452
|
+
|
|
2453
|
+
return assemblePage(title, css, html, js);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
function buildPong(title, t, opts) {
|
|
2457
|
+
const winScore = opts.difficulty === 'hard' ? 11 : 7;
|
|
2458
|
+
|
|
2459
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t),
|
|
2460
|
+
`canvas{border-radius:12px;background:${t.bg};display:block;margin:1rem auto;touch-action:none;border:2px solid ${t.sf}}`,
|
|
2461
|
+
`.vs{display:flex;justify-content:center;gap:3rem;font-size:2.5rem;font-weight:900;font-variant-numeric:tabular-nums;margin:.5rem 0}`,
|
|
2462
|
+
`.vs span:first-child{color:${t.pr}}.vs span:last-child{color:${t.sc}}`,
|
|
2463
|
+
].join('\n');
|
|
2464
|
+
|
|
2465
|
+
const html = [
|
|
2466
|
+
htmlScr('menu', [
|
|
2467
|
+
htmlP(`First to ${winScore} wins — you vs AI`, `color:${t.mt};margin:1rem 0`),
|
|
2468
|
+
htmlStats([{label:'Wins',id:'best',value:'0'}]),
|
|
2469
|
+
htmlBtn('Play', 'go()'),
|
|
2470
|
+
].join('\n'), true),
|
|
2471
|
+
htmlScr('play', [
|
|
2472
|
+
`<div class="vs"><span id="p1">0</span><span id="p2">0</span></div>`,
|
|
2473
|
+
`<canvas id="cv" width="360" height="420"></canvas>`,
|
|
2474
|
+
htmlP('Move mouse/touch or arrow keys', `color:${t.mt};font-size:.8rem;margin:.5rem`),
|
|
2475
|
+
].join('\n')),
|
|
2476
|
+
htmlScr('end', [
|
|
2477
|
+
htmlDsp('fin', 'WIN'),
|
|
2478
|
+
`<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>`,
|
|
2479
|
+
htmlBtn('Rematch', 'go()'),
|
|
2480
|
+
].join('\n')),
|
|
2481
|
+
].join('\n');
|
|
2482
|
+
|
|
2483
|
+
const js = [jsHelper(), jsScreen(), jsStore('pong'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2484
|
+
`const cv=$('cv'),ctx=cv.getContext('2d'),CW=360,CH=420,PW=70,PH=10,BR=7,WIN=${winScore};`,
|
|
2485
|
+
`let py,ay,bx,by,bdx,bdy,s1,s2,raf,aiSpd;`,
|
|
2486
|
+
`function go(){py=CW/2;ay=CW/2;bx=CW/2;by=CH/2;bdx=3*(Math.random()>.5?1:-1);bdy=3*(Math.random()>.5?1:-1);s1=0;s2=0;aiSpd=3;$('p1').textContent=0;$('p2').textContent=0;show('play');cancelAnimationFrame(raf);tick()}`,
|
|
2487
|
+
`function tick(){update();draw();if(s1<WIN&&s2<WIN)raf=requestAnimationFrame(tick);else end()}`,
|
|
2488
|
+
`function update(){bx+=bdx;by+=bdy;if(bx<=BR||bx>=CW-BR)bdx=-bdx;if(by<=PH+BR+4&&bx>=ay-PW/2&&bx<=ay+PW/2){bdy=Math.abs(bdy);bdx+=((bx-ay)/(PW/2))*1.5;sfxTap();const rc=cv.getBoundingClientRect();emitParticles(rc.left+bx*(rc.width/CW),rc.top+by*(rc.height/CH),4,'${t.sc}')}if(by>=CH-PH-BR-4&&bx>=py-PW/2&&bx<=py+PW/2){bdy=-Math.abs(bdy);bdx+=((bx-py)/(PW/2))*1.5;sfxHit();const rc=cv.getBoundingClientRect();emitParticles(rc.left+bx*(rc.width/CW),rc.top+by*(rc.height/CH),4,'${t.pr}')}if(by<0){s2++;sfxScore();$('p1').textContent=s2;reset()}if(by>CH){s1++;sfxFail();$('p2').textContent=s1;reset()}const diff=bx-ay;ay+=Math.min(aiSpd,Math.abs(diff))*(diff>0?1:-1)*(0.7+Math.random()*0.3);ay=Math.max(PW/2,Math.min(CW-PW/2,ay));aiSpd=3+Math.max(s1,s2)*0.3}`,
|
|
2489
|
+
`function reset(){bx=CW/2;by=CH/2;bdx=3*(Math.random()>.5?1:-1);bdy=3*(Math.random()>.5?1:-1)}`,
|
|
2490
|
+
`function draw(){ctx.fillStyle='${t.bg}';ctx.fillRect(0,0,CW,CH);ctx.setLineDash([6,6]);ctx.strokeStyle='${t.sf}';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(0,CH/2);ctx.lineTo(CW,CH/2);ctx.stroke();ctx.setLineDash([]);ctx.fillStyle='${t.sc}';ctx.shadowColor='${t.sc}';ctx.shadowBlur=8;ctx.beginPath();ctx.roundRect(ay-PW/2,4,PW,PH,4);ctx.fill();ctx.fillStyle='${t.pr}';ctx.shadowColor='${t.pr}';ctx.beginPath();ctx.roundRect(py-PW/2,CH-PH-4,PW,PH,4);ctx.fill();ctx.shadowBlur=0;ctx.fillStyle='${t.ac}';ctx.shadowColor='${t.ac}';ctx.shadowBlur=10;ctx.beginPath();ctx.arc(bx,by,BR,0,Math.PI*2);ctx.fill();ctx.shadowBlur=0}`,
|
|
2491
|
+
`function end(){cancelAnimationFrame(raf);const won=s1<s2;if(won){sfxWin();saveBest(best+1);$('fin').textContent='YOU WIN!';$('fin').style.color='${t.ac}';burstAt($('fin'),30)}else{sfxFail();$('fin').textContent='AI WINS';$('fin').style.color='${t.pr}'}$('msg').innerHTML=s2+' - '+s1+(won?' — Great match!':' — Try again!');show('end')}`,
|
|
2492
|
+
`cv.addEventListener('mousemove',e=>{const r=cv.getBoundingClientRect();py=Math.max(PW/2,Math.min(CW-PW/2,(e.clientX-r.left)*(CW/r.width)))});`,
|
|
2493
|
+
`cv.addEventListener('touchmove',e=>{e.preventDefault();const r=cv.getBoundingClientRect();py=Math.max(PW/2,Math.min(CW-PW/2,(e.touches[0].clientX-r.left)*(CW/r.width)))},{passive:false});`,
|
|
2494
|
+
`document.addEventListener('keydown',e=>{if(e.key==='ArrowLeft')py=Math.max(PW/2,py-20);if(e.key==='ArrowRight')py=Math.min(CW-PW/2,py+20)});`,
|
|
2495
|
+
].join('\n');
|
|
2496
|
+
|
|
2497
|
+
return assemblePage(title, css, html, js);
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// ── App Builders ──
|
|
2501
|
+
|
|
2502
|
+
function buildCalculator(title, t, opts) {
|
|
2503
|
+
const css = [cssBase(t), cssBtn(t), cssFx(t),
|
|
2504
|
+
`.calc-dsp{background:${t.sf};border-radius:16px;padding:1.2rem;margin:1rem 0;text-align:right;min-height:4.5rem;display:flex;flex-direction:column;justify-content:center}`,
|
|
2505
|
+
`.calc-expr{font-size:.9rem;color:${t.mt};min-height:1.2em;word-break:break-all}`,
|
|
2506
|
+
`.calc-val{font-size:2.4rem;font-weight:800;font-variant-numeric:tabular-nums;word-break:break-all}`,
|
|
2507
|
+
`.calc-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:.5rem 0}`,
|
|
2508
|
+
`.calc-grid button{padding:1rem;border:none;border-radius:12px;font-size:1.2rem;font-weight:600;cursor:pointer;transition:transform .1s}`,
|
|
2509
|
+
`.calc-grid button:active{transform:scale(.93)}`,
|
|
2510
|
+
`.calc-num{background:${t.sf};color:${t.tx}}`,
|
|
2511
|
+
`.calc-op{background:${t.pr};color:#fff}`,
|
|
2512
|
+
`.calc-fn{background:${t.sf}88;color:${t.mt}}`,
|
|
2513
|
+
`.calc-eq{background:linear-gradient(135deg,${t.pr},${t.sc});color:#fff}`,
|
|
2514
|
+
opts.history ? `.calc-hist{max-height:30vh;overflow-y:auto;margin-top:.5rem}.calc-hist div{font-size:.85rem;color:${t.mt};padding:.3rem 0;border-bottom:1px solid ${t.sf};text-align:right}` : '',
|
|
2515
|
+
].join('\n');
|
|
2516
|
+
|
|
2517
|
+
const histHTML = opts.history ? `<div class="calc-hist" id="hist"></div>` : '';
|
|
2518
|
+
const html = `<div class="calc-dsp">
|
|
2519
|
+
<div class="calc-expr" id="expr"></div>
|
|
2520
|
+
<div class="calc-val" id="val">0</div></div>
|
|
2521
|
+
<div class="calc-grid">
|
|
2522
|
+
<button class="calc-fn" onclick="clr()">C</button>
|
|
2523
|
+
<button class="calc-fn" onclick="pct()">%</button>
|
|
2524
|
+
<button class="calc-fn" onclick="neg()">±</button>
|
|
2525
|
+
<button class="calc-op" onclick="op('/')">÷</button>
|
|
2526
|
+
<button class="calc-num" onclick="num('7')">7</button>
|
|
2527
|
+
<button class="calc-num" onclick="num('8')">8</button>
|
|
2528
|
+
<button class="calc-num" onclick="num('9')">9</button>
|
|
2529
|
+
<button class="calc-op" onclick="op('*')">×</button>
|
|
2530
|
+
<button class="calc-num" onclick="num('4')">4</button>
|
|
2531
|
+
<button class="calc-num" onclick="num('5')">5</button>
|
|
2532
|
+
<button class="calc-num" onclick="num('6')">6</button>
|
|
2533
|
+
<button class="calc-op" onclick="op('-')">−</button>
|
|
2534
|
+
<button class="calc-num" onclick="num('1')">1</button>
|
|
2535
|
+
<button class="calc-num" onclick="num('2')">2</button>
|
|
2536
|
+
<button class="calc-num" onclick="num('3')">3</button>
|
|
2537
|
+
<button class="calc-op" onclick="op('+')">+</button>
|
|
2538
|
+
<button class="calc-num" style="grid-column:span 2" onclick="num('0')">0</button>
|
|
2539
|
+
<button class="calc-num" onclick="dot()">.</button>
|
|
2540
|
+
<button class="calc-eq" onclick="eq()">=</button></div>${histHTML}`;
|
|
2541
|
+
|
|
2542
|
+
const histJS = opts.history ? `function addHist(expr,res){const d=document.createElement('div');d.textContent=expr+' = '+res;$('hist').prepend(d)}` : `function addHist(){}`;
|
|
2543
|
+
const js = [jsHelper(),
|
|
2544
|
+
`let cur='0',prev='',oper='',fresh=true;`,
|
|
2545
|
+
`function upd(){$('val').textContent=cur;$('expr').textContent=prev+(oper?{'+':' + ','-':' − ','*':' × ','/':' ÷ '}[oper]:'')}`,
|
|
2546
|
+
`function num(n){if(fresh){cur='';fresh=false}if(cur==='0'&&n!=='.')cur=n;else if(cur.length<15)cur+=n;upd()}`,
|
|
2547
|
+
`function dot(){if(!cur.includes('.')){if(fresh){cur='0';fresh=false}cur+='.'}upd()}`,
|
|
2548
|
+
`function op(o){if(prev&&oper&&!fresh)eq();prev=cur;oper=o;fresh=true;upd()}`,
|
|
2549
|
+
`function eq(){if(!oper||!prev)return;const a=parseFloat(prev),b=parseFloat(cur);let r;if(oper==='+')r=a+b;else if(oper==='-')r=a-b;else if(oper==='*')r=a*b;else r=b!==0?a/b:'Error';const expr=prev+{'+':'+','-':'-','*':'×','/':'÷'}[oper]+cur;addHist(expr,typeof r==='number'?+r.toPrecision(12):r);prev='';oper='';cur=typeof r==='number'?String(+r.toPrecision(12)):String(r);fresh=true;upd()}`,
|
|
2550
|
+
`function clr(){cur='0';prev='';oper='';fresh=true;upd()}`,
|
|
2551
|
+
`function pct(){cur=String(parseFloat(cur)/100);upd()}`,
|
|
2552
|
+
`function neg(){cur=String(-parseFloat(cur));upd()}`,
|
|
2553
|
+
histJS,
|
|
2554
|
+
`document.addEventListener('keydown',e=>{if(e.key>='0'&&e.key<='9')num(e.key);else if(e.key==='.')dot();else if(e.key==='+')op('+');else if(e.key==='-')op('-');else if(e.key==='*')op('*');else if(e.key==='/')op('/');else if(e.key==='Enter'||e.key==='=')eq();else if(e.key==='Escape'||e.key==='c'||e.key==='C')clr();e.preventDefault()});`,
|
|
2555
|
+
].join('\n');
|
|
2556
|
+
|
|
2557
|
+
return assemblePage(title, css, html, js);
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
function buildTimerApp(title, t, opts) {
|
|
2561
|
+
const css = [cssBase(t), cssBtn(t), cssDsp(), cssFx(t),
|
|
2562
|
+
`.tmr-dsp{font-size:4rem;font-weight:200;font-variant-numeric:tabular-nums;letter-spacing:.05em;margin:2rem 0;font-family:'Courier New',monospace}`,
|
|
2563
|
+
`.tmr-btns{display:flex;gap:12px;justify-content:center;margin:1rem 0}`,
|
|
2564
|
+
`.tmr-btns .btn{padding:.6rem 1.5rem;font-size:1rem}`,
|
|
2565
|
+
`.lap{max-height:40vh;overflow-y:auto;margin-top:1rem}`,
|
|
2566
|
+
`.lap div{display:flex;justify-content:space-between;padding:.4rem .5rem;font-size:.9rem;color:${t.mt};border-bottom:1px solid ${t.sf}22}`,
|
|
2567
|
+
`.lap div b{color:${t.tx}}`,
|
|
2568
|
+
].join('\n');
|
|
2569
|
+
|
|
2570
|
+
const html = `<div class="tmr-dsp" id="dsp">00:00.00</div>
|
|
2571
|
+
<div class="tmr-btns">
|
|
2572
|
+
<button class="btn" id="main" onclick="toggle()">Start</button>
|
|
2573
|
+
<button class="btn" onclick="lap()" style="background:${t.sf};color:${t.tx}">Lap</button>
|
|
2574
|
+
<button class="btn" onclick="rst()" style="background:${t.sf};color:${t.tx}">Reset</button></div>
|
|
2575
|
+
<div class="lap" id="laps"></div>`;
|
|
2576
|
+
|
|
2577
|
+
const js = [jsHelper(), jsSfx(),
|
|
2578
|
+
`let t0=0,elapsed=0,running=false,raf,lapN=0;`,
|
|
2579
|
+
`function fmt(ms){const m=Math.floor(ms/60000),s=Math.floor(ms%60000/1000),cs=Math.floor(ms%1000/10);return String(m).padStart(2,'0')+':'+String(s).padStart(2,'0')+'.'+String(cs).padStart(2,'0')}`,
|
|
2580
|
+
`function upd(){$('dsp').textContent=fmt(elapsed+(running?Date.now()-t0:0))}`,
|
|
2581
|
+
`function loop(){upd();if(running)raf=requestAnimationFrame(loop)}`,
|
|
2582
|
+
`function toggle(){if(running){elapsed+=Date.now()-t0;running=false;cancelAnimationFrame(raf);$('main').textContent='Resume';sfxHit()}else{t0=Date.now();running=true;$('main').textContent='Stop';loop();sfxTap()}}`,
|
|
2583
|
+
`function lap(){if(!running&&elapsed===0)return;lapN++;const ms=elapsed+(running?Date.now()-t0:0);const d=document.createElement('div');d.innerHTML='<span>Lap '+lapN+'</span><b>'+fmt(ms)+'</b>';$('laps').prepend(d);sfxScore()}`,
|
|
2584
|
+
`function rst(){running=false;elapsed=0;lapN=0;cancelAnimationFrame(raf);$('dsp').textContent='00:00.00';$('laps').innerHTML='';$('main').textContent='Start'}`,
|
|
2585
|
+
`document.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();toggle()}else if(e.key==='l')lap();else if(e.key==='r')rst()});`,
|
|
2586
|
+
].join('\n');
|
|
2587
|
+
|
|
2588
|
+
return assemblePage(title, css, html, js);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
function buildCounter(title, t, opts) {
|
|
2592
|
+
const css = [cssBase(t), cssBtn(t), cssDsp(), cssFx(t), cssStat(t),
|
|
2593
|
+
`.cnt-val{font-size:6rem;font-weight:900;margin:1.5rem 0;font-variant-numeric:tabular-nums;transition:transform .1s}`,
|
|
2594
|
+
`.cnt-btns{display:flex;gap:16px;justify-content:center;margin:1rem 0}`,
|
|
2595
|
+
`.cnt-btns .btn{width:70px;height:70px;border-radius:50%;font-size:2rem;padding:0;display:flex;align-items:center;justify-content:center}`,
|
|
2596
|
+
`.cnt-rst{margin-top:1.5rem;background:${t.sf}!important;color:${t.mt}!important;padding:.5rem 1.2rem!important;font-size:.9rem!important}`,
|
|
2597
|
+
].join('\n');
|
|
2598
|
+
|
|
2599
|
+
const html = [
|
|
2600
|
+
htmlStats([{label:'Total taps',id:'total',value:'0'}]),
|
|
2601
|
+
`<div class="cnt-val" id="val">0</div>`,
|
|
2602
|
+
`<div class="cnt-btns">`,
|
|
2603
|
+
`<button class="btn" onclick="dec()" style="background:${t.sf};color:${t.tx}">−</button>`,
|
|
2604
|
+
`<button class="btn" onclick="inc()">+</button>`,
|
|
2605
|
+
`</div>`,
|
|
2606
|
+
`<button class="btn cnt-rst" onclick="rst()">Reset</button>`,
|
|
2607
|
+
].join('\n');
|
|
2608
|
+
|
|
2609
|
+
const js = [jsHelper(), jsStore('cnt'), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2610
|
+
`let val=+(sGet('val')||0),total=+(sGet('total')||0);`,
|
|
2611
|
+
`$('val').textContent=val;$('total').textContent=total;`,
|
|
2612
|
+
`function upd(){$('val').textContent=val;sSet('val',val);$('val').style.animation='none';void $('val').offsetWidth;$('val').style.animation='pop .15s'}`,
|
|
2613
|
+
`function inc(){val++;total++;$('total').textContent=total;sSet('total',total);sfxTap();burstAt($('val'),4);upd()}`,
|
|
2614
|
+
`function dec(){val--;sfxHit();upd()}`,
|
|
2615
|
+
`function rst(){val=0;sfxFail();upd()}`,
|
|
2616
|
+
`document.addEventListener('keydown',e=>{if(e.key==='ArrowUp'||e.key==='+')inc();else if(e.key==='ArrowDown'||e.key==='-')dec();else if(e.key==='r')rst()});`,
|
|
2617
|
+
].join('\n');
|
|
2618
|
+
|
|
2619
|
+
return assemblePage(title, css, html, js);
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
function buildTodo(title, t, opts) {
|
|
2623
|
+
const css = [cssBase(t), cssBtn(t), cssInput(t), cssList(t), cssFx(t), cssStat(t),
|
|
2624
|
+
`.todo-add{display:flex;gap:8px;margin:1rem 0}`,
|
|
2625
|
+
`.todo-add .input{flex:1}`,
|
|
2626
|
+
`.todo-add .btn{padding:.75rem 1.2rem}`,
|
|
2627
|
+
`.list-item .chk{width:22px;height:22px;border-radius:6px;border:2px solid ${t.pr};background:none;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;color:${t.ac};font-size:14px;transition:background .2s}`,
|
|
2628
|
+
`.list-item.done .chk{background:${t.ac};border-color:${t.ac};color:#fff}`,
|
|
2629
|
+
`.list-item .text{flex:1;word-break:break-word}`,
|
|
2630
|
+
`.filters{display:flex;gap:8px;justify-content:center;margin:.5rem 0}`,
|
|
2631
|
+
`.filters button{background:none;border:none;color:${t.mt};cursor:pointer;font-size:.85rem;padding:.3rem .6rem;border-radius:6px}`,
|
|
2632
|
+
`.filters button.on{background:${t.pr};color:#fff}`,
|
|
2633
|
+
opts.categories ? `.cat{font-size:.7rem;padding:.15rem .4rem;border-radius:4px;background:${t.pr}33;color:${t.pr};margin-left:.3rem}` : '',
|
|
2634
|
+
].join('\n');
|
|
2635
|
+
|
|
2636
|
+
const html = [
|
|
2637
|
+
htmlStats([{label:'Done',id:'done',value:'0'},{label:'Total',id:'total',value:'0'}]),
|
|
2638
|
+
`<div class="todo-add">`,
|
|
2639
|
+
`<input class="input" id="inp" placeholder="Add a task..." autocomplete="off">`,
|
|
2640
|
+
`<button class="btn" onclick="add()">+</button></div>`,
|
|
2641
|
+
`<div class="filters">`,
|
|
2642
|
+
`<button class="on" id="fAll" onclick="filt('all')">All</button>`,
|
|
2643
|
+
`<button id="fAct" onclick="filt('active')">Active</button>`,
|
|
2644
|
+
`<button id="fDon" onclick="filt('done')">Done</button></div>`,
|
|
2645
|
+
`<div class="list" id="list"></div>`,
|
|
2646
|
+
].join('\n');
|
|
2647
|
+
|
|
2648
|
+
const js = [jsHelper(), jsStore('todo'), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2649
|
+
`let items=sGet('items')||[],filter='all';`,
|
|
2650
|
+
`function render(){const f=filter==='done'?items.filter(i=>i.done):filter==='active'?items.filter(i=>!i.done):items;$('list').innerHTML='';f.forEach((item,idx)=>{const realIdx=items.indexOf(item);const d=document.createElement('div');d.className='list-item'+(item.done?' done':'');d.innerHTML='<button class="chk" onclick="tog('+realIdx+')">'+(item.done?'✓':'')+'</button><span class="text">'+item.text+'</span><button class="del" onclick="del('+realIdx+')">×</button>';$('list').appendChild(d)});$('done').textContent=items.filter(i=>i.done).length;$('total').textContent=items.length;['fAll','fAct','fDon'].forEach(id=>$(id).classList.remove('on'));$({all:'fAll',active:'fAct',done:'fDon'}[filter]).classList.add('on')}`,
|
|
2651
|
+
`function save(){sSet('items',items)}`,
|
|
2652
|
+
`function add(){const v=$('inp').value.trim();if(!v)return;items.unshift({text:v,done:false,id:Date.now()});save();render();$('inp').value='';sfxTap();burstAt($('inp'),6)}`,
|
|
2653
|
+
`function tog(i){items[i].done=!items[i].done;save();render();if(items[i].done){sfxScore();burstAt($('list').children[0],8)}else sfxHit()}`,
|
|
2654
|
+
`function del(i){items.splice(i,1);save();render();sfxFail()}`,
|
|
2655
|
+
`function filt(f){filter=f;render()}`,
|
|
2656
|
+
`$('inp').addEventListener('keydown',e=>{if(e.key==='Enter')add()});`,
|
|
2657
|
+
`render();`,
|
|
2658
|
+
].join('\n');
|
|
2659
|
+
|
|
2660
|
+
return assemblePage(title, css, html, js);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
function buildPomodoro(title, t, opts) {
|
|
2664
|
+
const workMin = 25, breakMin = 5;
|
|
2665
|
+
|
|
2666
|
+
const css = [cssBase(t), cssBtn(t), cssDsp(), cssFx(t), cssStat(t),
|
|
2667
|
+
`.pom-ring{width:min(280px,70vw);height:min(280px,70vw);border-radius:50%;border:6px solid ${t.sf};display:flex;align-items:center;justify-content:center;flex-direction:column;margin:1.5rem auto;position:relative}`,
|
|
2668
|
+
`.pom-time{font-size:3.5rem;font-weight:200;font-variant-numeric:tabular-nums;font-family:monospace}`,
|
|
2669
|
+
`.pom-label{font-size:.9rem;color:${t.mt};margin-top:.3rem;text-transform:uppercase;letter-spacing:.1em}`,
|
|
2670
|
+
`.pom-ring svg{position:absolute;top:-6px;left:-6px;transform:rotate(-90deg)}`,
|
|
2671
|
+
`.pom-btns{display:flex;gap:12px;justify-content:center;margin:1rem 0}`,
|
|
2672
|
+
`.pom-btns .btn{padding:.6rem 1.5rem;font-size:1rem}`,
|
|
2673
|
+
].join('\n');
|
|
2674
|
+
|
|
2675
|
+
const ringSize = 280;
|
|
2676
|
+
const r = (ringSize - 12) / 2;
|
|
2677
|
+
const circ = Math.round(2 * Math.PI * r);
|
|
2678
|
+
|
|
2679
|
+
const html = [
|
|
2680
|
+
htmlStats([{label:'Sessions',id:'sessions',value:'0'},{label:'Total',id:'totalMin',value:'0 min'}]),
|
|
2681
|
+
`<div class="pom-ring">`,
|
|
2682
|
+
`<svg width="${ringSize}" height="${ringSize}"><circle cx="${ringSize/2}" cy="${ringSize/2}" r="${r}" fill="none" stroke="${t.pr}" stroke-width="6" stroke-dasharray="${circ}" stroke-dashoffset="0" stroke-linecap="round" id="ring"/></svg>`,
|
|
2683
|
+
`<div class="pom-time" id="time">${workMin}:00</div>`,
|
|
2684
|
+
`<div class="pom-label" id="phase">Focus</div></div>`,
|
|
2685
|
+
`<div class="pom-btns">`,
|
|
2686
|
+
`<button class="btn" id="main" onclick="toggle()">Start</button>`,
|
|
2687
|
+
`<button class="btn" onclick="skip()" style="background:${t.sf};color:${t.tx}">Skip</button>`,
|
|
2688
|
+
`<button class="btn" onclick="rst()" style="background:${t.sf};color:${t.tx}">Reset</button></div>`,
|
|
2689
|
+
].join('\n');
|
|
2690
|
+
|
|
2691
|
+
const js = [jsHelper(), jsStore('pom'), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2692
|
+
`const WORK=${workMin*60},BRK=${breakMin*60},CIRC=${circ};`,
|
|
2693
|
+
`let sessions=+(sGet('sessions')||0),totalSec=+(sGet('totalSec')||0),remain=WORK,phase='work',running=false,raf,lastT;`,
|
|
2694
|
+
`$('sessions').textContent=sessions;$('totalMin').textContent=Math.floor(totalSec/60)+' min';`,
|
|
2695
|
+
`function fmt(s){return String(Math.floor(s/60)).padStart(2,'0')+':'+String(s%60).padStart(2,'0')}`,
|
|
2696
|
+
`function upd(){const dur=phase==='work'?WORK:BRK;const pct=remain/dur;$('time').textContent=fmt(remain);$('ring').setAttribute('stroke-dashoffset',CIRC*(1-pct));$('ring').setAttribute('stroke',phase==='work'?'${t.pr}':'${t.ac}');$('phase').textContent=phase==='work'?'Focus':'Break'}`,
|
|
2697
|
+
`function tick(){if(!running)return;const now=Date.now();const dt=Math.floor((now-lastT)/1000);if(dt>=1){lastT=now;remain=Math.max(0,remain-dt);if(phase==='work')totalSec+=dt;upd();if(remain<=0){done()}}raf=requestAnimationFrame(tick)}`,
|
|
2698
|
+
`function toggle(){if(running){running=false;cancelAnimationFrame(raf);$('main').textContent='Resume';sfxHit()}else{running=true;lastT=Date.now();$('main').textContent='Pause';tick();sfxTap()}}`,
|
|
2699
|
+
`function done(){running=false;cancelAnimationFrame(raf);if(phase==='work'){sessions++;sSet('sessions',sessions);sSet('totalSec',totalSec);$('sessions').textContent=sessions;$('totalMin').textContent=Math.floor(totalSec/60)+' min';sfxWin();burstAt($('time'),20)}else{sfxScore();burstAt($('time'),10)}phase=phase==='work'?'break':'work';remain=phase==='work'?WORK:BRK;$('main').textContent='Start';upd()}`,
|
|
2700
|
+
`function skip(){running=false;cancelAnimationFrame(raf);phase=phase==='work'?'break':'work';remain=phase==='work'?WORK:BRK;$('main').textContent='Start';upd();sfxFail()}`,
|
|
2701
|
+
`function rst(){running=false;cancelAnimationFrame(raf);phase='work';remain=WORK;$('main').textContent='Start';upd()}`,
|
|
2702
|
+
`upd();`,
|
|
2703
|
+
].join('\n');
|
|
2704
|
+
|
|
2705
|
+
return assemblePage(title, css, html, js);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
function buildClock(title, t, opts) {
|
|
2709
|
+
const css = [cssBase(t), cssDsp(), cssFx(t),
|
|
2710
|
+
`.clock-face{width:min(300px,75vw);height:min(300px,75vw);border-radius:50%;border:4px solid ${t.pr}44;margin:1.5rem auto;position:relative;background:${t.sf}}`,
|
|
2711
|
+
`.clock-face .mark{position:absolute;width:2px;height:10px;background:${t.mt};top:8px;left:50%;transform-origin:0 142px}`,
|
|
2712
|
+
`.clock-face .mark.hr{height:16px;width:3px;background:${t.tx}}`,
|
|
2713
|
+
`.hand{position:absolute;bottom:50%;left:50%;transform-origin:50% 100%;border-radius:2px}`,
|
|
2714
|
+
`.hand-h{width:4px;height:60px;background:${t.tx}}`,
|
|
2715
|
+
`.hand-m{width:3px;height:80px;background:${t.pr}}`,
|
|
2716
|
+
`.hand-s{width:1.5px;height:90px;background:${t.ac}}`,
|
|
2717
|
+
`.clock-dot{position:absolute;width:8px;height:8px;border-radius:50%;background:${t.ac};top:50%;left:50%;transform:translate(-50%,-50%);z-index:5}`,
|
|
2718
|
+
`.digital{font-size:2rem;font-weight:300;font-variant-numeric:tabular-nums;font-family:monospace;margin:1rem 0;letter-spacing:.1em}`,
|
|
2719
|
+
`.date-str{color:${t.mt};font-size:.9rem}`,
|
|
2720
|
+
].join('\n');
|
|
2721
|
+
|
|
2722
|
+
const html = `<div class="clock-face" id="face"></div>
|
|
2723
|
+
<div class="digital" id="digital">00:00:00</div>
|
|
2724
|
+
<div class="date-str" id="date"></div>`;
|
|
2725
|
+
|
|
2726
|
+
const js = [jsHelper(),
|
|
2727
|
+
`const face=$('face');`,
|
|
2728
|
+
`for(let i=0;i<60;i++){const m=document.createElement('div');m.className='mark'+(i%5===0?' hr':'');m.style.transform='translateX(-50%) rotate('+i*6+'deg)';face.appendChild(m)}`,
|
|
2729
|
+
`['hand-h','hand-m','hand-s'].forEach(c=>{const h=document.createElement('div');h.className='hand '+c;h.id=c;face.appendChild(h)});`,
|
|
2730
|
+
`const dot=document.createElement('div');dot.className='clock-dot';face.appendChild(dot);`,
|
|
2731
|
+
`function tick(){const d=new Date();const h=d.getHours()%12,m=d.getMinutes(),s=d.getSeconds(),ms=d.getMilliseconds();const hDeg=h*30+m*.5;const mDeg=m*6+s*.1;const sDeg=s*6+ms*.006;$('hand-h').style.transform='translateX(-50%) rotate('+hDeg+'deg)';$('hand-m').style.transform='translateX(-50%) rotate('+mDeg+'deg)';$('hand-s').style.transform='translateX(-50%) rotate('+sDeg+'deg)';$('digital').textContent=String(d.getHours()).padStart(2,'0')+':'+String(m).padStart(2,'0')+':'+String(s).padStart(2,'0');$('date').textContent=d.toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});requestAnimationFrame(tick)}`,
|
|
2732
|
+
`tick();`,
|
|
2733
|
+
].join('\n');
|
|
2734
|
+
|
|
2735
|
+
return assemblePage(title, css, html, js);
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
function buildDrawing(title, t, opts) {
|
|
2739
|
+
const css = [cssBase(t), cssBtn(t), cssFx(t),
|
|
2740
|
+
`body{overflow:hidden}`,
|
|
2741
|
+
`.toolbar{display:flex;gap:6px;justify-content:center;align-items:center;flex-wrap:wrap;margin:.5rem 0}`,
|
|
2742
|
+
`.toolbar button{border:none;border-radius:8px;padding:.5rem .8rem;font-size:.85rem;cursor:pointer;background:${t.sf};color:${t.tx};transition:transform .1s}`,
|
|
2743
|
+
`.toolbar button:active{transform:scale(.93)}`,
|
|
2744
|
+
`.toolbar button.on{background:${t.pr};color:#fff}`,
|
|
2745
|
+
`.colors{display:flex;gap:4px;justify-content:center;margin:.3rem 0}`,
|
|
2746
|
+
`.colors div{width:28px;height:28px;border-radius:50%;cursor:pointer;border:3px solid transparent;transition:border-color .2s}`,
|
|
2747
|
+
`.colors div.on{border-color:${t.tx}}`,
|
|
2748
|
+
`.sizes{display:flex;gap:8px;align-items:center;justify-content:center;margin:.3rem 0}`,
|
|
2749
|
+
`.sizes div{border-radius:50%;background:${t.tx};cursor:pointer;opacity:.4;transition:opacity .2s}`,
|
|
2750
|
+
`.sizes div.on{opacity:1}`,
|
|
2751
|
+
`canvas{cursor:crosshair;border:2px solid ${t.sf};background:#fff}`,
|
|
2752
|
+
].join('\n');
|
|
2753
|
+
|
|
2754
|
+
const palette = ['#111827','#ef4444','#f97316','#eab308','#22c55e','#3b82f6','#8b5cf6','#ec4899','#ffffff'];
|
|
2755
|
+
const sizes = [2, 5, 10, 20];
|
|
2756
|
+
|
|
2757
|
+
const html = [
|
|
2758
|
+
`<div class="toolbar">`,
|
|
2759
|
+
`<button class="on" id="tb-pen" onclick="setTool('pen')">Pen</button>`,
|
|
2760
|
+
`<button id="tb-eraser" onclick="setTool('eraser')">Eraser</button>`,
|
|
2761
|
+
`<button onclick="undo()">Undo</button>`,
|
|
2762
|
+
`<button onclick="clearAll()">Clear</button>`,
|
|
2763
|
+
`<button onclick="save()">Save</button></div>`,
|
|
2764
|
+
`<div class="colors" id="colors">`,
|
|
2765
|
+
palette.map((c,i) => `<div style="background:${c}" onclick="setColor('${c}',this)"${i===0?' class="on"':''}></div>`).join(''),
|
|
2766
|
+
`</div>`,
|
|
2767
|
+
`<div class="sizes" id="sizes">`,
|
|
2768
|
+
sizes.map((s,i) => `<div style="width:${s+8}px;height:${s+8}px" onclick="setSize(${s},this)"${i===1?' class="on"':''}></div>`).join(''),
|
|
2769
|
+
`</div>`,
|
|
2770
|
+
`<canvas id="cv"></canvas>`,
|
|
2771
|
+
].join('\n');
|
|
2772
|
+
|
|
2773
|
+
const js = [jsHelper(), jsSfx(),
|
|
2774
|
+
`const cv=$('cv'),ctx=cv.getContext('2d');`,
|
|
2775
|
+
`let tool='pen',color='#111827',size=5,drawing=false,history=[],histIdx=-1;`,
|
|
2776
|
+
`function resize(){const w=Math.min(420,window.innerWidth*.92);cv.width=w;cv.height=w;ctx.fillStyle='#fff';ctx.fillRect(0,0,w,w);redraw()}window.addEventListener('resize',resize);resize();`,
|
|
2777
|
+
`function snapshot(){histIdx++;history=history.slice(0,histIdx);history.push(cv.toDataURL());if(history.length>50)history.shift()}`,
|
|
2778
|
+
`function redraw(){if(histIdx>=0){const img=new Image();img.onload=()=>{ctx.clearRect(0,0,cv.width,cv.height);ctx.drawImage(img,0,0)};img.src=history[histIdx]}}`,
|
|
2779
|
+
`function getPos(e){const r=cv.getBoundingClientRect();const t=e.touches?e.touches[0]:e;return{x:t.clientX-r.left,y:t.clientY-r.top}}`,
|
|
2780
|
+
`function startDraw(e){e.preventDefault();drawing=true;const p=getPos(e);ctx.beginPath();ctx.moveTo(p.x,p.y)}`,
|
|
2781
|
+
`function draw(e){if(!drawing)return;e.preventDefault();const p=getPos(e);ctx.lineTo(p.x,p.y);ctx.strokeStyle=tool==='eraser'?'#fff':color;ctx.lineWidth=tool==='eraser'?size*3:size;ctx.lineCap='round';ctx.lineJoin='round';ctx.stroke()}`,
|
|
2782
|
+
`function endDraw(){if(drawing){drawing=false;snapshot()}}`,
|
|
2783
|
+
`cv.addEventListener('mousedown',startDraw);cv.addEventListener('mousemove',draw);cv.addEventListener('mouseup',endDraw);cv.addEventListener('mouseleave',endDraw);`,
|
|
2784
|
+
`cv.addEventListener('touchstart',startDraw,{passive:false});cv.addEventListener('touchmove',draw,{passive:false});cv.addEventListener('touchend',endDraw);`,
|
|
2785
|
+
`function setTool(t){tool=t;document.querySelectorAll('.toolbar button').forEach(b=>b.classList.remove('on'));$('tb-'+t).classList.add('on');sfxTap()}`,
|
|
2786
|
+
`function setColor(c,el){color=c;document.querySelectorAll('.colors div').forEach(d=>d.classList.remove('on'));el.classList.add('on')}`,
|
|
2787
|
+
`function setSize(s,el){size=s;document.querySelectorAll('.sizes div').forEach(d=>d.classList.remove('on'));el.classList.add('on')}`,
|
|
2788
|
+
`function undo(){if(histIdx>0){histIdx--;redraw();sfxHit()}else if(histIdx===0){histIdx=-1;ctx.fillStyle='#fff';ctx.fillRect(0,0,cv.width,cv.height);sfxHit()}}`,
|
|
2789
|
+
`function clearAll(){ctx.fillStyle='#fff';ctx.fillRect(0,0,cv.width,cv.height);snapshot();sfxFail()}`,
|
|
2790
|
+
`function save(){const a=document.createElement('a');a.download='drawing.png';a.href=cv.toDataURL();a.click();sfxWin()}`,
|
|
2791
|
+
`snapshot();`,
|
|
2792
|
+
].join('\n');
|
|
2793
|
+
|
|
2794
|
+
return assemblePage(title, css, html, js);
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
function buildNotes(title, t, opts) {
|
|
2798
|
+
const css = [cssBase(t), cssBtn(t), cssInput(t), cssList(t), cssFx(t),
|
|
2799
|
+
`body{justify-content:flex-start;padding-top:2rem}`,
|
|
2800
|
+
`.note-card{background:${t.sf};border-radius:12px;padding:1rem;margin:.5rem 0;text-align:left;cursor:pointer;transition:transform .2s,box-shadow .2s;animation:fadeIn .2s ease-out}`,
|
|
2801
|
+
`.note-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.1)}`,
|
|
2802
|
+
`.note-card h3{font-size:1rem;margin-bottom:.3rem;color:${t.tx}}`,
|
|
2803
|
+
`.note-card p{font-size:.85rem;color:${t.mt};display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}`,
|
|
2804
|
+
`.note-card .time{font-size:.7rem;color:${t.mt};margin-top:.4rem}`,
|
|
2805
|
+
`.editor{display:none;flex-direction:column;gap:.5rem;margin:1rem 0}`,
|
|
2806
|
+
`.editor.on{display:flex}`,
|
|
2807
|
+
`.editor textarea{background:${t.sf};border:2px solid ${t.pr}44;border-radius:12px;padding:.75rem;font-size:1rem;color:${t.tx};min-height:200px;resize:vertical;outline:none;font-family:inherit;transition:border-color .2s}`,
|
|
2808
|
+
`.editor textarea:focus{border-color:${t.pr}}`,
|
|
2809
|
+
`.editor-btns{display:flex;gap:8px}`,
|
|
2810
|
+
`.notes-list.hide{display:none}`,
|
|
2811
|
+
].join('\n');
|
|
2812
|
+
|
|
2813
|
+
const html = [
|
|
2814
|
+
`<div class="notes-list" id="notesList">`,
|
|
2815
|
+
`<div style="display:flex;gap:8px;margin:1rem 0">`,
|
|
2816
|
+
`<input class="input" id="search" placeholder="Search notes..." oninput="render()">`,
|
|
2817
|
+
`<button class="btn" onclick="newNote()" style="padding:.75rem 1rem">+</button></div>`,
|
|
2818
|
+
`<div id="list"></div></div>`,
|
|
2819
|
+
`<div class="editor" id="editor">`,
|
|
2820
|
+
`<input class="input" id="etitle" placeholder="Title">`,
|
|
2821
|
+
`<textarea id="ebody" placeholder="Write your note..."></textarea>`,
|
|
2822
|
+
`<div class="editor-btns">`,
|
|
2823
|
+
`<button class="btn" onclick="saveNote()">Save</button>`,
|
|
2824
|
+
`<button class="btn" onclick="cancelEdit()" style="background:${t.sf};color:${t.tx}">Cancel</button>`,
|
|
2825
|
+
`<button class="btn" onclick="delNote()" style="background:#ef4444;color:#fff;margin-left:auto">Delete</button></div></div>`,
|
|
2826
|
+
].join('\n');
|
|
2827
|
+
|
|
2828
|
+
const js = [jsHelper(), jsStore('notes'), jsSfx(),
|
|
2829
|
+
`let notes=sGet('list')||[],editId=null;`,
|
|
2830
|
+
`function render(){const q=$('search').value.toLowerCase();const f=q?notes.filter(n=>n.title.toLowerCase().includes(q)||n.body.toLowerCase().includes(q)):notes;$('list').innerHTML='';f.forEach(n=>{const d=document.createElement('div');d.className='note-card';d.innerHTML='<h3>'+esc(n.title||'Untitled')+'</h3><p>'+esc(n.body||'')+'</p><div class="time">'+new Date(n.updated).toLocaleString()+'</div>';d.onclick=()=>openNote(n.id);$('list').appendChild(d)})}`,
|
|
2831
|
+
`function esc(s){return s.replace(/</g,'<').replace(/>/g,'>')}`,
|
|
2832
|
+
`function save(){sSet('list',notes)}`,
|
|
2833
|
+
`function newNote(){editId=null;$('etitle').value='';$('ebody').value='';$('editor').classList.add('on');$('notesList').classList.add('hide');$('etitle').focus();sfxTap()}`,
|
|
2834
|
+
`function openNote(id){const n=notes.find(x=>x.id===id);if(!n)return;editId=id;$('etitle').value=n.title;$('ebody').value=n.body;$('editor').classList.add('on');$('notesList').classList.add('hide')}`,
|
|
2835
|
+
`function saveNote(){const title=$('etitle').value.trim()||'Untitled';const body=$('ebody').value;const now=Date.now();if(editId){const n=notes.find(x=>x.id===editId);if(n){n.title=title;n.body=body;n.updated=now}}else{notes.unshift({id:now,title,body,created:now,updated:now})}save();cancelEdit();sfxScore()}`,
|
|
2836
|
+
`function cancelEdit(){$('editor').classList.remove('on');$('notesList').classList.remove('hide');render()}`,
|
|
2837
|
+
`function delNote(){if(editId){notes=notes.filter(n=>n.id!==editId);save();sfxFail()}cancelEdit()}`,
|
|
2838
|
+
`render();`,
|
|
2839
|
+
].join('\n');
|
|
2840
|
+
|
|
2841
|
+
return assemblePage(title, css, html, js);
|
|
2168
2842
|
}
|
|
2169
2843
|
|
|
2170
|
-
// ──
|
|
2844
|
+
// ── Builder Registry ──
|
|
2171
2845
|
|
|
2172
|
-
const
|
|
2846
|
+
const BUILDERS = {
|
|
2173
2847
|
tapping: buildTapping, quiz: buildQuiz, memory: buildMemory,
|
|
2174
|
-
snake: buildSnake, reaction: buildReaction, typing: buildTyping,
|
|
2848
|
+
snake: buildSnake, reaction: buildReaction, typing: buildTyping,
|
|
2849
|
+
breakout: buildBreakout, dodge: buildDodge, color: buildColor, pong: buildPong,
|
|
2850
|
+
calculator: buildCalculator, timer: buildTimerApp, counter: buildCounter,
|
|
2851
|
+
todo: buildTodo, pomodoro: buildPomodoro, clock: buildClock,
|
|
2852
|
+
drawing: buildDrawing, notes: buildNotes,
|
|
2175
2853
|
};
|
|
2176
2854
|
|
|
2855
|
+
function frontendHTML(type, title, rawInstruction) {
|
|
2856
|
+
const t = THEMES[type] || THEMES.tapping;
|
|
2857
|
+
const opts = extractBuildOpts(rawInstruction || '');
|
|
2858
|
+
const builder = BUILDERS[type];
|
|
2859
|
+
if (!builder) return buildTapping(title || type, t, opts);
|
|
2860
|
+
return builder(title || type, t, opts);
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2177
2863
|
function gameHTML(type, title, rawInstruction) {
|
|
2178
|
-
|
|
2179
|
-
const builder = GAME_BUILDERS[type] || buildTapping;
|
|
2180
|
-
return builder(title || type, opts);
|
|
2864
|
+
return frontendHTML(type, title, rawInstruction);
|
|
2181
2865
|
}
|
|
2182
2866
|
|
|
2183
|
-
function
|
|
2184
|
-
|
|
2867
|
+
function appNaideCode(title, port, type) {
|
|
2868
|
+
const label = GAME_TYPES[type] ? 'game' : 'app';
|
|
2869
|
+
return `server ${label} port ${port}:
|
|
2185
2870
|
cors "*"
|
|
2186
2871
|
|
|
2187
2872
|
get "/" (req, res):
|
|
2188
2873
|
res.sendFile("index.html", {root: "."})
|
|
2189
2874
|
|
|
2190
2875
|
get "/api/health" (req, res):
|
|
2191
|
-
ret {status: "ok",
|
|
2876
|
+
ret {status: "ok", ${label}: "${title || type || 'app'}"}
|
|
2192
2877
|
`;
|
|
2193
2878
|
}
|
|
2194
2879
|
|
|
2880
|
+
|
|
2195
2881
|
function compose(intents, params) {
|
|
2196
|
-
// Game intent: generate minimal server code (
|
|
2882
|
+
// Game/App intent: generate minimal server code (HTML is generated separately in project mode)
|
|
2197
2883
|
if (params.gameType && intents.has('game') && !intents.has('crud')) {
|
|
2198
|
-
return
|
|
2884
|
+
return appNaideCode(params.gameTitle || params.gameType, params.port, params.gameType);
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
if (params.appType && intents.has('app') && !intents.has('crud')) {
|
|
2888
|
+
return appNaideCode(params.appTitle || params.appType, params.port, params.appType);
|
|
2199
2889
|
}
|
|
2200
2890
|
|
|
2201
2891
|
const sections = [];
|
|
@@ -2527,7 +3217,8 @@ function heal(code, err) {
|
|
|
2527
3217
|
const KEYWORD_CHEATSHEET = {
|
|
2528
3218
|
'Intent': 'server, api, rest, bot, cli, page, test, database, ai, crud, auth, websocket, mail, graphql, game',
|
|
2529
3219
|
'Composite': 'todo, blog, chat, shop, fullstack, board, crm, inventory, booking, sns',
|
|
2530
|
-
'Game': 'tapping, quiz, memory, snake, typing, reaction, breakout',
|
|
3220
|
+
'Game': 'tapping, quiz, memory, snake, typing, reaction, breakout, dodge, color, pong',
|
|
3221
|
+
'App': 'calculator, timer, counter, todo, pomodoro, clock, drawing, notes',
|
|
2531
3222
|
'Platform': 'discord, slack, telegram, line',
|
|
2532
3223
|
'DB': 'sqlite, postgres',
|
|
2533
3224
|
'Japanese': 'サーバー, 認証, ログイン, 会員, CRUD, 管理, データベース, ボット, テスト, ページ',
|
|
@@ -2550,12 +3241,14 @@ export function generateProject(instruction, options = {}) {
|
|
|
2550
3241
|
const files = [];
|
|
2551
3242
|
const intents = new Set(result.intents);
|
|
2552
3243
|
|
|
2553
|
-
// Game project: generate standalone HTML
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
const
|
|
2558
|
-
const
|
|
3244
|
+
// Game/App project: generate standalone HTML frontend
|
|
3245
|
+
const frontendType = (intents.has('game') && result.analysis && result.analysis.gameType) ? result.analysis.gameType
|
|
3246
|
+
: (intents.has('app') && result.analysis && result.analysis.appType) ? result.analysis.appType : null;
|
|
3247
|
+
if (frontendType) {
|
|
3248
|
+
const isGame = !!result.analysis.gameType;
|
|
3249
|
+
const title = result.analysis.gameTitle || result.analysis.appTitle || frontendType;
|
|
3250
|
+
const projectName = safeName(frontendType) + (isGame ? '-game' : '-app');
|
|
3251
|
+
const html = frontendHTML(frontendType, title, instruction);
|
|
2559
3252
|
|
|
2560
3253
|
files.push({ path: 'index.html', content: html });
|
|
2561
3254
|
files.push({ path: 'app.naide', content: result.code });
|
|
@@ -2565,12 +3258,17 @@ export function generateProject(instruction, options = {}) {
|
|
|
2565
3258
|
dependencies: { express: '^4.21.0', naider: '^1.21.0' },
|
|
2566
3259
|
}, null, 2) + '\n' });
|
|
2567
3260
|
files.push({ path: '.gitignore', content: 'node_modules/\n' });
|
|
2568
|
-
|
|
3261
|
+
const verb = isGame ? 'Play' : 'Use';
|
|
3262
|
+
files.push({ path: 'README.md', content: `# ${title}\n\nGenerated by NAIDE Agent Coder (~~)\n\n## ${verb}\n\nOpen \`index.html\` directly in a browser, or:\n\n\`\`\`bash\nnpm install && npm run dev\n\`\`\`\n\nThen visit http://localhost:${result.port}\n` });
|
|
3263
|
+
|
|
3264
|
+
const plan = [`${isGame ? 'Game' : 'App'}: ${frontendType}`, 'Standalone HTML + CSS + JS'];
|
|
3265
|
+
if (isGame) plan.push('High scores saved in localStorage');
|
|
3266
|
+
plan.push('Touch & keyboard support', `Server on port ${result.port}`);
|
|
2569
3267
|
|
|
2570
3268
|
return {
|
|
2571
3269
|
...result,
|
|
2572
3270
|
files,
|
|
2573
|
-
analysis: { ...result.analysis, plan
|
|
3271
|
+
analysis: { ...result.analysis, plan },
|
|
2574
3272
|
};
|
|
2575
3273
|
}
|
|
2576
3274
|
|
|
@@ -3058,10 +3756,12 @@ export function expandDirectives(source) {
|
|
|
3058
3756
|
function buildAnalysis(instruction, intents, params) {
|
|
3059
3757
|
const plan = [];
|
|
3060
3758
|
|
|
3061
|
-
if (params.gameType) {
|
|
3062
|
-
|
|
3759
|
+
if (params.gameType || params.appType) {
|
|
3760
|
+
const type = params.gameType || params.appType;
|
|
3761
|
+
const isGame = !!params.gameType;
|
|
3762
|
+
plan.push(`${isGame ? 'Game' : 'App'}: ${type}`);
|
|
3063
3763
|
plan.push('Standalone HTML + CSS + JS');
|
|
3064
|
-
plan.push('High scores saved in localStorage');
|
|
3764
|
+
if (isGame) plan.push('High scores saved in localStorage');
|
|
3065
3765
|
plan.push('Touch & keyboard support');
|
|
3066
3766
|
return {
|
|
3067
3767
|
instruction,
|
|
@@ -3071,6 +3771,8 @@ function buildAnalysis(instruction, intents, params) {
|
|
|
3071
3771
|
plan,
|
|
3072
3772
|
gameType: params.gameType,
|
|
3073
3773
|
gameTitle: params.gameTitle,
|
|
3774
|
+
appType: params.appType,
|
|
3775
|
+
appTitle: params.appTitle,
|
|
3074
3776
|
};
|
|
3075
3777
|
}
|
|
3076
3778
|
|