naider 1.24.5 → 2.0.1
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 +133 -112
- package/SPEC.naide +8 -6
- package/bin/naide.js +509 -124
- package/bin/postinstall.js +5 -15
- package/examples/model.naide +1 -1
- package/lsp/server.js +210 -101
- package/mcp/server.js +247 -22
- package/package.json +8 -5
- package/src/gen.js +853 -324
- package/src/generator-c.js +34 -14
- package/src/generator-go.js +26 -0
- package/src/generator.js +5 -1
- package/src/index.js +34 -14
- package/src/parser.js +15 -14
- package/src/project-tools.js +785 -0
- package/src/typechecker.js +147 -18
- 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
|
@@ -105,6 +105,17 @@ const GAME_TYPES = {
|
|
|
105
105
|
pong: ['pong', 'paddle', 'ポン', 'パドル', 'ピンポン'],
|
|
106
106
|
};
|
|
107
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', 'メモ', 'ノート'],
|
|
117
|
+
};
|
|
118
|
+
|
|
108
119
|
const NOISE = new Set([
|
|
109
120
|
'a', 'an', 'the', 'for', 'with', 'and', 'or', 'on', 'at', 'to', 'of', 'in', 'by',
|
|
110
121
|
'that', 'which', 'this', 'it', 'its', 'my', 'is', 'are', 'has', 'have', 'do', 'does',
|
|
@@ -115,6 +126,8 @@ const NOISE = new Set([
|
|
|
115
126
|
'tapping', 'tap', 'clicker', 'click', 'quiz', 'trivia', 'memory', 'matching', 'typing',
|
|
116
127
|
'reaction', 'reflex', 'snake', 'breakout', 'brick', 'block', 'pong', 'puzzle',
|
|
117
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',
|
|
118
131
|
'の', 'を', 'に', 'で', 'は', 'が', 'と', 'も', 'から', 'まで', 'より', 'って',
|
|
119
132
|
'な', 'ような', 'みたいな', 'っぽい', 'ような感じ',
|
|
120
133
|
'作って', '作る', '作成', '生成', '追加', '実装', 'ください', 'して', 'つけて',
|
|
@@ -133,6 +146,16 @@ function detectGameType(words, raw) {
|
|
|
133
146
|
return 'tapping';
|
|
134
147
|
}
|
|
135
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
|
+
|
|
136
159
|
// ── Field Normalization ─────────────────────────────────────
|
|
137
160
|
|
|
138
161
|
const FIELD_SYNONYMS = {
|
|
@@ -528,6 +551,21 @@ function classify(words, raw) {
|
|
|
528
551
|
mods.isGame = true;
|
|
529
552
|
}
|
|
530
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
|
+
|
|
531
569
|
// Default fallback: if nothing detected, server + schema
|
|
532
570
|
if (intents.size === 0) {
|
|
533
571
|
intents.add('server'); intents.add('schema'); intents.add('crud'); intents.add('database');
|
|
@@ -548,6 +586,12 @@ function extractParams(words, raw, intents, mods) {
|
|
|
548
586
|
return params;
|
|
549
587
|
}
|
|
550
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
|
+
|
|
551
595
|
// Extract port number
|
|
552
596
|
for (let i = 0; i < words.length; i++) {
|
|
553
597
|
const n = parseInt(words[i]);
|
|
@@ -1785,22 +1829,30 @@ function migrationFile(entities, relationships, m2mRels) {
|
|
|
1785
1829
|
|
|
1786
1830
|
// ── Code Composer ───────────────────────────────────────────
|
|
1787
1831
|
|
|
1788
|
-
// ──
|
|
1789
|
-
|
|
1790
|
-
const
|
|
1791
|
-
tapping:
|
|
1792
|
-
quiz:
|
|
1793
|
-
memory:
|
|
1794
|
-
snake:
|
|
1795
|
-
typing:
|
|
1796
|
-
reaction:
|
|
1797
|
-
breakout:
|
|
1798
|
-
dodge:
|
|
1799
|
-
color:
|
|
1800
|
-
pong:
|
|
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' },
|
|
1801
1853
|
};
|
|
1802
1854
|
|
|
1803
|
-
function
|
|
1855
|
+
function extractBuildOpts(raw) {
|
|
1804
1856
|
const o = {};
|
|
1805
1857
|
const d = raw.match(/(\d+)\s*(?:sec|seconds|秒|s\b)/i);
|
|
1806
1858
|
if (d) o.duration = Math.min(120, Math.max(3, parseInt(d[1])));
|
|
@@ -1809,35 +1861,117 @@ function extractGameOpts(raw) {
|
|
|
1809
1861
|
if (/fast|高速|はやい/i.test(raw)) o.speed = 'fast';
|
|
1810
1862
|
if (/hard|難しい|ハード/i.test(raw)) o.difficulty = 'hard';
|
|
1811
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;
|
|
1812
1868
|
return o;
|
|
1813
1869
|
}
|
|
1814
1870
|
|
|
1815
|
-
|
|
1816
|
-
return `:root{--bg:${p.bg};--sf:${p.sf};--pr:${p.pr};--sc:${p.sc};--ac:${p.ac};--tx:${p.tx};--mt:${p.mt}}`;
|
|
1817
|
-
}
|
|
1871
|
+
// ── CSS Modules ──
|
|
1818
1872
|
|
|
1819
|
-
function cssBase(
|
|
1873
|
+
function cssBase(t) {
|
|
1820
1874
|
return `*{margin:0;padding:0;box-sizing:border-box}
|
|
1821
|
-
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;background:${
|
|
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}
|
|
1822
1876
|
.ct{width:min(420px,92vw);text-align:center;position:relative}
|
|
1823
|
-
h1{font-size:1.8rem;margin-bottom:.5rem;background:linear-gradient(135deg,${
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
.
|
|
1829
|
-
|
|
1830
|
-
|
|
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}
|
|
1831
1917
|
@keyframes cdPop{0%{transform:scale(3);opacity:0}30%{opacity:1}100%{transform:scale(1);opacity:0}}
|
|
1832
1918
|
@keyframes pop{0%{transform:scale(1)}50%{transform:scale(1.15)}100%{transform:scale(1)}}
|
|
1833
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)}}
|
|
1834
1921
|
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-4px)}75%{transform:translateX(4px)}}
|
|
1835
|
-
.pcv{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:90}
|
|
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)}`;
|
|
1836
1932
|
}
|
|
1837
1933
|
|
|
1838
|
-
function
|
|
1839
|
-
return `.
|
|
1840
|
-
.
|
|
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);`;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
function jsScreen() {
|
|
1952
|
+
return `function show(id){document.querySelectorAll('.scr').forEach(s=>s.classList.remove('on'));$(id).classList.add('on')}`;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
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);
|
|
1964
|
+
if($('best'))$('best').textContent=best;
|
|
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)}`;
|
|
1841
1975
|
}
|
|
1842
1976
|
|
|
1843
1977
|
function jsSfx() {
|
|
@@ -1861,19 +1995,43 @@ function burstAt(el,n){const r=el.getBoundingClientRect();emitParticles(r.left+r
|
|
|
1861
1995
|
function shakeEl(el){el.style.animation='shake .2s';setTimeout(()=>el.style.animation='',200)}`;
|
|
1862
1996
|
}
|
|
1863
1997
|
|
|
1864
|
-
function jsCountdown(
|
|
1865
|
-
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='
|
|
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()}`;
|
|
1866
2000
|
}
|
|
1867
2001
|
|
|
1868
|
-
function
|
|
1869
|
-
return `const
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
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>`;
|
|
1874
2014
|
}
|
|
1875
2015
|
|
|
1876
|
-
function
|
|
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>`;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
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) {
|
|
1877
2035
|
return `<!DOCTYPE html>
|
|
1878
2036
|
<html lang="en">
|
|
1879
2037
|
<head>
|
|
@@ -1887,7 +2045,7 @@ ${css}
|
|
|
1887
2045
|
<body>
|
|
1888
2046
|
<div class="ct">
|
|
1889
2047
|
<h1>${title}</h1>
|
|
1890
|
-
${
|
|
2048
|
+
${body}
|
|
1891
2049
|
</div>
|
|
1892
2050
|
<script>
|
|
1893
2051
|
${js}
|
|
@@ -1896,85 +2054,88 @@ ${js}
|
|
|
1896
2054
|
</html>`;
|
|
1897
2055
|
}
|
|
1898
2056
|
|
|
1899
|
-
// ──
|
|
2057
|
+
// ── Game Builders ──
|
|
1900
2058
|
|
|
1901
|
-
function buildTapping(title, opts) {
|
|
1902
|
-
const p = GAME_PALETTES.tapping;
|
|
2059
|
+
function buildTapping(title, t, opts) {
|
|
1903
2060
|
const dur = opts.duration || 10;
|
|
1904
2061
|
|
|
1905
|
-
const css = [
|
|
1906
|
-
`.tap{width:min(300px,75vw);height:min(300px,75vw);border-radius:50%;background:radial-gradient(circle at 35% 35%,${
|
|
1907
|
-
`.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}`,
|
|
1908
2065
|
`.tap.on{animation:pulse 1s infinite}`,
|
|
1909
|
-
`@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}}`,
|
|
1910
2067
|
`.rp{position:absolute;border-radius:50%;background:rgba(255,255,255,.25);animation:rpAn .5s ease-out forwards;pointer-events:none}`,
|
|
1911
2068
|
`@keyframes rpAn{0%{width:0;height:0;opacity:.6}100%{width:200px;height:200px;opacity:0}}`,
|
|
1912
|
-
`.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}`,
|
|
1913
2070
|
].join('\n');
|
|
1914
2071
|
|
|
1915
|
-
const html =
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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')),
|
|
2089
|
+
].join('\n');
|
|
2090
|
+
|
|
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;`,
|
|
1931
2093
|
`function go(){show('play');countdown(startGame)}`,
|
|
1932
|
-
`function startGame(){taps=0;combo=1;lh=0;t0=Date.now();$('cnt').textContent='0';$('cmb').textContent='x1';$('bar').style.width='100%';
|
|
1933
|
-
`function tick(){const
|
|
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)}`,
|
|
1934
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)}}`,
|
|
1935
|
-
`function end(){
|
|
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')}`,
|
|
1936
2098
|
].join('\n');
|
|
1937
2099
|
|
|
1938
|
-
return
|
|
2100
|
+
return assemblePage(title, css, html, js);
|
|
1939
2101
|
}
|
|
1940
2102
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
function buildQuiz(title, opts) {
|
|
1944
|
-
const p = GAME_PALETTES.quiz;
|
|
2103
|
+
function buildQuiz(title, t, opts) {
|
|
1945
2104
|
const perQ = opts.duration || 15;
|
|
1946
2105
|
|
|
1947
|
-
const css = [
|
|
2106
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t),
|
|
1948
2107
|
`.q{font-size:1.3rem;margin:1.5rem 0;min-height:3em;line-height:1.5}`,
|
|
1949
2108
|
`.ch{display:flex;flex-direction:column;gap:.75rem;margin:1rem 0}`,
|
|
1950
|
-
`.ch div{padding:1rem;border:2px solid ${
|
|
1951
|
-
`.ch div:hover{border-color:${
|
|
1952
|
-
`.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}`,
|
|
1953
2112
|
`.ch .no{border-color:#F87171;background:#F8717122}`,
|
|
1954
2113
|
`.dots{display:flex;gap:4px;justify-content:center;margin-bottom:1rem}`,
|
|
1955
|
-
`.dot{width:10px;height:10px;border-radius:50%;background:${
|
|
1956
|
-
`.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}}`,
|
|
1957
2116
|
`.tm{font-size:2.5rem;font-weight:800;font-variant-numeric:tabular-nums}`,
|
|
1958
2117
|
].join('\n');
|
|
1959
2118
|
|
|
1960
|
-
const html =
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
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>
|
|
1966
2126
|
<div class="tm" id="timer">${perQ}</div>
|
|
1967
2127
|
<div class="q" id="qtext"></div>
|
|
1968
|
-
<div class="ch" id="choices"></div
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
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')),
|
|
2134
|
+
].join('\n');
|
|
1973
2135
|
|
|
1974
|
-
const js = [
|
|
2136
|
+
const js = [jsHelper(), jsScreen(), jsStore('quiz'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsShuffle(),
|
|
1975
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}];`,
|
|
1976
2138
|
`const PER_Q=${perQ};let cur,score,pool,tmr,tl,streak=0;`,
|
|
1977
|
-
`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}`,
|
|
1978
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()}`,
|
|
1979
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)}`,
|
|
1980
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)}`,
|
|
@@ -1982,83 +2143,75 @@ function buildQuiz(title, opts) {
|
|
|
1982
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')}`,
|
|
1983
2144
|
].join('\n');
|
|
1984
2145
|
|
|
1985
|
-
return
|
|
2146
|
+
return assemblePage(title, css, html, js);
|
|
1986
2147
|
}
|
|
1987
2148
|
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
function buildMemory(title, opts) {
|
|
1991
|
-
const p = GAME_PALETTES.memory;
|
|
2149
|
+
function buildMemory(title, t, opts) {
|
|
1992
2150
|
const cols = opts.cols || 4;
|
|
1993
2151
|
const pairs = Math.floor((cols * (opts.rows || cols)) / 2);
|
|
1994
2152
|
|
|
1995
|
-
const css = [
|
|
1996
|
-
|
|
1997
|
-
`.card{aspect-ratio:1;border-radius:12px;cursor:pointer;perspective:600px;-webkit-perspective:600px}`,
|
|
1998
|
-
`.card .inn{position:relative;width:100%;height:100%;transition:transform .4s;transform-style:preserve-3d;-webkit-transform-style:preserve-3d}`,
|
|
1999
|
-
`.card.flip .inn{transform:rotateY(180deg)}`,
|
|
2000
|
-
`.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}`,
|
|
2001
|
-
`.card .fr{background:linear-gradient(135deg,${p.pr},${p.sc})}`,
|
|
2002
|
-
`.card .bk{background:${p.sf};transform:rotateY(180deg);border:2px solid ${p.pr}}`,
|
|
2003
|
-
`.card.done .fr{background:linear-gradient(135deg,${p.ac},#059669)}`,
|
|
2004
|
-
`.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),
|
|
2005
2155
|
].join('\n');
|
|
2006
2156
|
|
|
2007
|
-
const html =
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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(),
|
|
2021
2176
|
`const EM=['🎮','🎯','🚀','💎','🔥','⚡','🌟','🎵','🎨','🏆','🍕','🌈','🦊','🐙','🎃','🍀','🎲','🌸'];`,
|
|
2022
2177
|
`const PAIRS=${pairs};let cards,flp,done,moves,tmr,t0,cstrk=0;`,
|
|
2023
|
-
`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}`,
|
|
2024
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')}`,
|
|
2025
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)}}}`,
|
|
2026
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')}`,
|
|
2027
2181
|
].join('\n');
|
|
2028
2182
|
|
|
2029
|
-
return
|
|
2183
|
+
return assemblePage(title, css, html, js);
|
|
2030
2184
|
}
|
|
2031
2185
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
function buildSnake(title, opts) {
|
|
2035
|
-
const p = GAME_PALETTES.snake;
|
|
2186
|
+
function buildSnake(title, t, opts) {
|
|
2036
2187
|
const spd = opts.speed === 'fast' ? 80 : opts.difficulty === 'hard' ? 90 : 120;
|
|
2037
2188
|
const W = 16, S = 20;
|
|
2038
2189
|
|
|
2039
|
-
const css = [
|
|
2040
|
-
`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),
|
|
2041
2191
|
`.ctl{display:grid;grid-template-columns:repeat(3,56px);grid-template-rows:repeat(2,56px);gap:6px;justify-content:center;margin:1rem auto}`,
|
|
2042
|
-
`.ctl button{border:none;border-radius:10px;background:${
|
|
2043
|
-
`.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}}`,
|
|
2044
2194
|
].join('\n');
|
|
2045
2195
|
|
|
2046
|
-
const html =
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
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')),
|
|
2212
|
+
].join('\n');
|
|
2213
|
+
|
|
2214
|
+
const js = [jsHelper(), jsScreen(), jsStore('snake'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac),
|
|
2062
2215
|
`const GS=${S},GW=${W},cv=$('cv'),ctx=cv.getContext('2d'),BASE_SPD=${spd};`,
|
|
2063
2216
|
`let sn,dir,nd,food,sc,lp,spd;$('hi').textContent=best;`,
|
|
2064
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}`,
|
|
@@ -2066,94 +2219,96 @@ function buildSnake(title, opts) {
|
|
|
2066
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)}`,
|
|
2067
2220
|
`function sd(x,y){if(x===-dir.x&&y===-dir.y)return;nd={x,y}}`,
|
|
2068
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()}`,
|
|
2069
|
-
`function draw(){ctx.fillStyle='${
|
|
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()}})}`,
|
|
2070
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')}`,
|
|
2071
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)});`,
|
|
2072
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});`,
|
|
2073
2226
|
].join('\n');
|
|
2074
2227
|
|
|
2075
|
-
return
|
|
2228
|
+
return assemblePage(title, css, html, js);
|
|
2076
2229
|
}
|
|
2077
2230
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
function buildReaction(title, opts) {
|
|
2081
|
-
const p = GAME_PALETTES.reaction;
|
|
2231
|
+
function buildReaction(title, t, opts) {
|
|
2082
2232
|
const rounds = opts.difficulty === 'hard' ? 10 : 5;
|
|
2083
2233
|
|
|
2084
|
-
const css = [
|
|
2234
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssFx(t),
|
|
2085
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}`,
|
|
2086
|
-
`.zone.wait{background:${
|
|
2087
|
-
`.zone.ready{background:${
|
|
2088
|
-
`.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}}`,
|
|
2089
2239
|
`.zone.early{background:#DC2626}`,
|
|
2090
2240
|
`.times{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:1rem 0}`,
|
|
2091
|
-
`.times span{background:${
|
|
2241
|
+
`.times span{background:${t.sf};padding:.3rem .6rem;border-radius:6px;font-size:.85rem;font-variant-numeric:tabular-nums}`,
|
|
2092
2242
|
`.ms{font-size:3.5rem;font-weight:900;font-variant-numeric:tabular-nums}`,
|
|
2093
2243
|
].join('\n');
|
|
2094
2244
|
|
|
2095
|
-
const html =
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
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),
|
|
2111
2266
|
`if(best)$('best').textContent=best+'ms';`,
|
|
2112
2267
|
`const RDS=${rounds};let rd,res,phase,gAt,tmr;`,
|
|
2113
2268
|
`function go(){rd=0;res=[];$('times').innerHTML='';show('play');nr()}`,
|
|
2114
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)}`,
|
|
2115
|
-
`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='${
|
|
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)}}`,
|
|
2116
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')}`,
|
|
2117
2272
|
].join('\n');
|
|
2118
2273
|
|
|
2119
|
-
return
|
|
2274
|
+
return assemblePage(title, css, html, js);
|
|
2120
2275
|
}
|
|
2121
2276
|
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
function buildTyping(title, opts) {
|
|
2125
|
-
const p = GAME_PALETTES.typing;
|
|
2277
|
+
function buildTyping(title, t, opts) {
|
|
2126
2278
|
const dur = opts.duration || 30;
|
|
2127
2279
|
|
|
2128
|
-
const css = [
|
|
2280
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssBar(t), cssFx(t), cssInput(t),
|
|
2129
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}`,
|
|
2130
|
-
`.wd .c{transition:color .1s}.wd .y{color:${
|
|
2131
|
-
`.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}`,
|
|
2132
2284
|
`@keyframes blink{50%{border-color:transparent}}`,
|
|
2133
|
-
`.
|
|
2134
|
-
`.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}`,
|
|
2135
2286
|
].join('\n');
|
|
2136
2287
|
|
|
2137
|
-
const html =
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
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(),
|
|
2154
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"];`,
|
|
2155
2311
|
`const DUR=${dur*1000};let pool,ci,pos,done,t0,tmr,tried=0,streak=0;`,
|
|
2156
|
-
`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}`,
|
|
2157
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)})}`,
|
|
2158
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>'}`,
|
|
2159
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()}`,
|
|
@@ -2161,33 +2316,33 @@ function buildTyping(title, opts) {
|
|
|
2161
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')}`,
|
|
2162
2317
|
].join('\n');
|
|
2163
2318
|
|
|
2164
|
-
return
|
|
2319
|
+
return assemblePage(title, css, html, js);
|
|
2165
2320
|
}
|
|
2166
2321
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
function buildBreakout(title, opts) {
|
|
2170
|
-
const p = GAME_PALETTES.breakout;
|
|
2322
|
+
function buildBreakout(title, t, opts) {
|
|
2171
2323
|
const COLS = 8, ROWS = opts.difficulty === 'hard' ? 7 : 5;
|
|
2172
2324
|
|
|
2173
|
-
const css = [
|
|
2174
|
-
|
|
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')),
|
|
2175
2342
|
].join('\n');
|
|
2176
2343
|
|
|
2177
|
-
const
|
|
2178
|
-
|
|
2179
|
-
<div class="st"><span>Best<b id="best">0</b></span></div>
|
|
2180
|
-
<button class="btn" onclick="go()">Play</button></div>
|
|
2181
|
-
<div id="s-play" class="hd">
|
|
2182
|
-
<div class="st"><span>Score<b id="score">0</b></span><span>Lives<b id="lives">3</b></span></div>
|
|
2183
|
-
<canvas id="cv" width="360" height="480"></canvas></div>
|
|
2184
|
-
<div id="s-end" class="hd">
|
|
2185
|
-
<div class="sc" id="fin">0</div>
|
|
2186
|
-
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2187
|
-
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2188
|
-
|
|
2189
|
-
const brickColors = `['${p.pr}','${p.sc}','#A78BFA','${p.ac}','#EC4899']`;
|
|
2190
|
-
const js = [jsCore('brk_best'), jsSfx(), jsParticles(p.pr, p.ac),
|
|
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),
|
|
2191
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;`,
|
|
2192
2347
|
`const BC=${brickColors};`,
|
|
2193
2348
|
`let px,ball,dx,dy,bricks,sc,lives,raf,cmb=0;`,
|
|
@@ -2195,46 +2350,46 @@ function buildBreakout(title, opts) {
|
|
|
2195
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()}`,
|
|
2196
2351
|
`function tick(){update();draw();if(lives>0&&bricks.some(b=>b.alive))raf=requestAnimationFrame(tick);else end()}`,
|
|
2197
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}}}}}`,
|
|
2198
|
-
`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='${
|
|
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}`,
|
|
2199
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')}`,
|
|
2200
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)))});`,
|
|
2201
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});`,
|
|
2202
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)});`,
|
|
2203
2358
|
].join('\n');
|
|
2204
2359
|
|
|
2205
|
-
return
|
|
2360
|
+
return assemblePage(title, css, html, js);
|
|
2206
2361
|
}
|
|
2207
2362
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
function buildDodge(title, opts) {
|
|
2211
|
-
const p = GAME_PALETTES.dodge;
|
|
2363
|
+
function buildDodge(title, t, opts) {
|
|
2212
2364
|
const spd = opts.difficulty === 'hard' ? 1.4 : opts.difficulty === 'easy' ? 0.7 : 1.0;
|
|
2213
2365
|
|
|
2214
|
-
const css = [
|
|
2215
|
-
|
|
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')),
|
|
2216
2383
|
].join('\n');
|
|
2217
2384
|
|
|
2218
|
-
const
|
|
2219
|
-
<p style="color:${p.mt};margin:1rem 0">Move to dodge the falling obstacles!</p>
|
|
2220
|
-
<div class="st"><span>Best<b id="best">0</b></span></div>
|
|
2221
|
-
<button class="btn" onclick="go()">Play</button></div>
|
|
2222
|
-
<div id="s-play" class="hd">
|
|
2223
|
-
<div class="st"><span>Score<b id="score">0</b></span><span>Best<b id="hi">0</b></span></div>
|
|
2224
|
-
<canvas id="cv" width="360" height="520"></canvas></div>
|
|
2225
|
-
<div id="s-end" class="hd">
|
|
2226
|
-
<div class="sc" id="fin">0</div>
|
|
2227
|
-
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2228
|
-
<button class="btn" onclick="go()">Play Again</button></div>`;
|
|
2229
|
-
|
|
2230
|
-
const js = [jsCore('dodge_best'), jsSfx(), jsParticles(p.pr, p.ac), jsCountdown(),
|
|
2385
|
+
const js = [jsHelper(), jsScreen(), jsStore('dodge'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac),
|
|
2231
2386
|
`const cv=$('cv'),ctx=cv.getContext('2d'),CW=360,CH=520,PW=30,PH=30,SPD_MUL=${spd};`,
|
|
2232
2387
|
`let px,py,obs,sc,raf,t0,spd,spawnT,alive;$('hi').textContent=best;`,
|
|
2233
2388
|
`function go(){show('play');countdown(startGame)}`,
|
|
2234
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()}`,
|
|
2235
2390
|
`function tick(){if(!alive)return;update();draw();raf=requestAnimationFrame(tick)}`,
|
|
2236
|
-
`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:['${
|
|
2237
|
-
`function draw(){ctx.fillStyle='${
|
|
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()}`,
|
|
2238
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')}`,
|
|
2239
2394
|
`let keys={};document.addEventListener('keydown',e=>{keys[e.key]=true;e.preventDefault()});document.addEventListener('keyup',e=>keys[e.key]=false);`,
|
|
2240
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);`,
|
|
@@ -2244,13 +2399,10 @@ function buildDodge(title, opts) {
|
|
|
2244
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)))});`,
|
|
2245
2400
|
].join('\n');
|
|
2246
2401
|
|
|
2247
|
-
return
|
|
2402
|
+
return assemblePage(title, css, html, js);
|
|
2248
2403
|
}
|
|
2249
2404
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
function buildColor(title, opts) {
|
|
2253
|
-
const p = GAME_PALETTES.color;
|
|
2405
|
+
function buildColor(title, t, opts) {
|
|
2254
2406
|
const dur = opts.duration || 30;
|
|
2255
2407
|
const COLORS = [
|
|
2256
2408
|
{name:'Red',hex:'#FF4757'},{name:'Blue',hex:'#3742FA'},{name:'Green',hex:'#2ED573'},
|
|
@@ -2258,117 +2410,482 @@ function buildColor(title, opts) {
|
|
|
2258
2410
|
{name:'Pink',hex:'#FF6B81'},{name:'Cyan',hex:'#18DCFF'},
|
|
2259
2411
|
];
|
|
2260
2412
|
|
|
2261
|
-
const css = [
|
|
2413
|
+
const css = [cssBase(t), cssBtn(t), cssScr(), cssDsp(), cssStat(t), cssBar(t), cssFx(t),
|
|
2262
2414
|
`.clr-name{font-size:2.8rem;font-weight:900;margin:1.5rem 0;letter-spacing:.05em;transition:transform .1s}`,
|
|
2263
2415
|
`.opts{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:1rem 0}`,
|
|
2264
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)}`,
|
|
2265
2417
|
`.opts div:active{transform:scale(.93)}`,
|
|
2266
|
-
`.strk{font-size:.95rem;color:${
|
|
2418
|
+
`.strk{font-size:.95rem;color:${t.ac};min-height:1.5em;font-weight:700}`,
|
|
2267
2419
|
].join('\n');
|
|
2268
2420
|
|
|
2269
|
-
const html =
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
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');
|
|
2283
2440
|
|
|
2284
2441
|
const colorsJS = JSON.stringify(COLORS);
|
|
2285
|
-
const js = [
|
|
2442
|
+
const js = [jsHelper(), jsScreen(), jsStore('color'), jsScore(), jsTimer(), jsSfx(), jsParticles(t.pr, t.ac), jsCountdown(t.ac), jsShuffle(),
|
|
2286
2443
|
`const CS=${colorsJS};`,
|
|
2287
|
-
`const DUR=${dur*1000};let sc,t0,
|
|
2288
|
-
`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}`,
|
|
2444
|
+
`const DUR=${dur*1000};let sc,t0,tmrI,streak,correct;`,
|
|
2289
2445
|
`function go(){show('play');countdown(startGame)}`,
|
|
2290
|
-
`function startGame(){sc=0;streak=0;t0=Date.now();$('sc').textContent=0;$('strk').textContent=0;$('stxt').textContent='';
|
|
2291
|
-
`function
|
|
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()}`,
|
|
2292
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)})}`,
|
|
2293
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)}`,
|
|
2294
|
-
`function end(){clearInterval(
|
|
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')}`,
|
|
2295
2451
|
].join('\n');
|
|
2296
2452
|
|
|
2297
|
-
return
|
|
2453
|
+
return assemblePage(title, css, html, js);
|
|
2298
2454
|
}
|
|
2299
2455
|
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
function buildPong(title, opts) {
|
|
2303
|
-
const p = GAME_PALETTES.pong;
|
|
2456
|
+
function buildPong(title, t, opts) {
|
|
2304
2457
|
const winScore = opts.difficulty === 'hard' ? 11 : 7;
|
|
2305
2458
|
|
|
2306
|
-
const css = [
|
|
2307
|
-
`canvas{border-radius:12px;background:${
|
|
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}}`,
|
|
2308
2461
|
`.vs{display:flex;justify-content:center;gap:3rem;font-size:2.5rem;font-weight:900;font-variant-numeric:tabular-nums;margin:.5rem 0}`,
|
|
2309
|
-
`.vs span:first-child{color:${
|
|
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')),
|
|
2310
2481
|
].join('\n');
|
|
2311
2482
|
|
|
2312
|
-
const
|
|
2313
|
-
<p style="color:${p.mt};margin:1rem 0">First to ${winScore} wins — you vs AI</p>
|
|
2314
|
-
<div class="st"><span>Wins<b id="best">0</b></span></div>
|
|
2315
|
-
<button class="btn" onclick="go()">Play</button></div>
|
|
2316
|
-
<div id="s-play" class="hd">
|
|
2317
|
-
<div class="vs"><span id="p1">0</span><span id="p2">0</span></div>
|
|
2318
|
-
<canvas id="cv" width="360" height="420"></canvas>
|
|
2319
|
-
<p style="color:${p.mt};font-size:.8rem;margin:.5rem">Move mouse/touch or arrow keys</p></div>
|
|
2320
|
-
<div id="s-end" class="hd">
|
|
2321
|
-
<div class="sc" id="fin">WIN</div>
|
|
2322
|
-
<div id="msg" style="font-size:1.1rem;margin:1rem 0"></div>
|
|
2323
|
-
<button class="btn" onclick="go()">Rematch</button></div>`;
|
|
2324
|
-
|
|
2325
|
-
const js = [jsCore('pong_best'), jsSfx(), jsParticles(p.pr, p.ac),
|
|
2483
|
+
const js = [jsHelper(), jsScreen(), jsStore('pong'), jsScore(), jsSfx(), jsParticles(t.pr, t.ac),
|
|
2326
2484
|
`const cv=$('cv'),ctx=cv.getContext('2d'),CW=360,CH=420,PW=70,PH=10,BR=7,WIN=${winScore};`,
|
|
2327
2485
|
`let py,ay,bx,by,bdx,bdy,s1,s2,raf,aiSpd;`,
|
|
2328
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()}`,
|
|
2329
2487
|
`function tick(){update();draw();if(s1<WIN&&s2<WIN)raf=requestAnimationFrame(tick);else end()}`,
|
|
2330
|
-
`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,'${
|
|
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}`,
|
|
2331
2489
|
`function reset(){bx=CW/2;by=CH/2;bdx=3*(Math.random()>.5?1:-1);bdy=3*(Math.random()>.5?1:-1)}`,
|
|
2332
|
-
`function draw(){ctx.fillStyle='${
|
|
2333
|
-
`function end(){cancelAnimationFrame(raf);const won=s1<s2;if(won){sfxWin();saveBest(best+1);$('fin').textContent='YOU WIN!';$('fin').style.color='${
|
|
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')}`,
|
|
2334
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)))});`,
|
|
2335
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});`,
|
|
2336
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)});`,
|
|
2337
2495
|
].join('\n');
|
|
2338
2496
|
|
|
2339
|
-
return
|
|
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);
|
|
2340
2842
|
}
|
|
2341
2843
|
|
|
2342
|
-
// ──
|
|
2844
|
+
// ── Builder Registry ──
|
|
2343
2845
|
|
|
2344
|
-
const
|
|
2846
|
+
const BUILDERS = {
|
|
2345
2847
|
tapping: buildTapping, quiz: buildQuiz, memory: buildMemory,
|
|
2346
|
-
snake: buildSnake, reaction: buildReaction, typing: buildTyping,
|
|
2347
|
-
dodge: buildDodge, color: buildColor, pong: buildPong,
|
|
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,
|
|
2348
2853
|
};
|
|
2349
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
|
+
|
|
2350
2863
|
function gameHTML(type, title, rawInstruction) {
|
|
2351
|
-
|
|
2352
|
-
const builder = GAME_BUILDERS[type] || buildTapping;
|
|
2353
|
-
return builder(title || type, opts);
|
|
2864
|
+
return frontendHTML(type, title, rawInstruction);
|
|
2354
2865
|
}
|
|
2355
2866
|
|
|
2356
|
-
function
|
|
2357
|
-
|
|
2867
|
+
function appNaideCode(title, port, type) {
|
|
2868
|
+
const label = GAME_TYPES[type] ? 'game' : 'app';
|
|
2869
|
+
return `server ${label} port ${port}:
|
|
2358
2870
|
cors "*"
|
|
2359
2871
|
|
|
2360
2872
|
get "/" (req, res):
|
|
2361
2873
|
res.sendFile("index.html", {root: "."})
|
|
2362
2874
|
|
|
2363
2875
|
get "/api/health" (req, res):
|
|
2364
|
-
ret {status: "ok",
|
|
2876
|
+
ret {status: "ok", ${label}: "${title || type || 'app'}"}
|
|
2365
2877
|
`;
|
|
2366
2878
|
}
|
|
2367
2879
|
|
|
2880
|
+
|
|
2368
2881
|
function compose(intents, params) {
|
|
2369
|
-
// Game intent: generate minimal server code (
|
|
2882
|
+
// Game/App intent: generate minimal server code (HTML is generated separately in project mode)
|
|
2370
2883
|
if (params.gameType && intents.has('game') && !intents.has('crud')) {
|
|
2371
|
-
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);
|
|
2372
2889
|
}
|
|
2373
2890
|
|
|
2374
2891
|
const sections = [];
|
|
@@ -2700,7 +3217,8 @@ function heal(code, err) {
|
|
|
2700
3217
|
const KEYWORD_CHEATSHEET = {
|
|
2701
3218
|
'Intent': 'server, api, rest, bot, cli, page, test, database, ai, crud, auth, websocket, mail, graphql, game',
|
|
2702
3219
|
'Composite': 'todo, blog, chat, shop, fullstack, board, crm, inventory, booking, sns',
|
|
2703
|
-
'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',
|
|
2704
3222
|
'Platform': 'discord, slack, telegram, line',
|
|
2705
3223
|
'DB': 'sqlite, postgres',
|
|
2706
3224
|
'Japanese': 'サーバー, 認証, ログイン, 会員, CRUD, 管理, データベース, ボット, テスト, ページ',
|
|
@@ -2723,12 +3241,14 @@ export function generateProject(instruction, options = {}) {
|
|
|
2723
3241
|
const files = [];
|
|
2724
3242
|
const intents = new Set(result.intents);
|
|
2725
3243
|
|
|
2726
|
-
// Game project: generate standalone HTML
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
const
|
|
2731
|
-
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);
|
|
2732
3252
|
|
|
2733
3253
|
files.push({ path: 'index.html', content: html });
|
|
2734
3254
|
files.push({ path: 'app.naide', content: result.code });
|
|
@@ -2738,12 +3258,17 @@ export function generateProject(instruction, options = {}) {
|
|
|
2738
3258
|
dependencies: { express: '^4.21.0', naider: '^1.21.0' },
|
|
2739
3259
|
}, null, 2) + '\n' });
|
|
2740
3260
|
files.push({ path: '.gitignore', content: 'node_modules/\n' });
|
|
2741
|
-
|
|
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}`);
|
|
2742
3267
|
|
|
2743
3268
|
return {
|
|
2744
3269
|
...result,
|
|
2745
3270
|
files,
|
|
2746
|
-
analysis: { ...result.analysis, plan
|
|
3271
|
+
analysis: { ...result.analysis, plan },
|
|
2747
3272
|
};
|
|
2748
3273
|
}
|
|
2749
3274
|
|
|
@@ -3231,10 +3756,12 @@ export function expandDirectives(source) {
|
|
|
3231
3756
|
function buildAnalysis(instruction, intents, params) {
|
|
3232
3757
|
const plan = [];
|
|
3233
3758
|
|
|
3234
|
-
if (params.gameType) {
|
|
3235
|
-
|
|
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}`);
|
|
3236
3763
|
plan.push('Standalone HTML + CSS + JS');
|
|
3237
|
-
plan.push('High scores saved in localStorage');
|
|
3764
|
+
if (isGame) plan.push('High scores saved in localStorage');
|
|
3238
3765
|
plan.push('Touch & keyboard support');
|
|
3239
3766
|
return {
|
|
3240
3767
|
instruction,
|
|
@@ -3244,6 +3771,8 @@ function buildAnalysis(instruction, intents, params) {
|
|
|
3244
3771
|
plan,
|
|
3245
3772
|
gameType: params.gameType,
|
|
3246
3773
|
gameTitle: params.gameTitle,
|
|
3774
|
+
appType: params.appType,
|
|
3775
|
+
appTitle: params.appTitle,
|
|
3247
3776
|
};
|
|
3248
3777
|
}
|
|
3249
3778
|
|