create-textmode 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +120 -86
  3. package/bin/index.js +8 -8
  4. package/package.json +76 -54
  5. package/src/args.js +60 -30
  6. package/src/banner.js +57 -57
  7. package/src/cli.js +161 -154
  8. package/src/constants.js +59 -23
  9. package/src/fs-utils.js +133 -85
  10. package/src/packageManager.js +18 -18
  11. package/src/prompts.js +103 -90
  12. package/src/runCommand.js +46 -46
  13. package/src/summary.js +38 -33
  14. package/src/textmodeVersion.js +57 -45
  15. package/src/usage.js +23 -18
  16. package/src/versions.js +56 -52
  17. package/templates/vanilla-js/.prettierrc +8 -8
  18. package/templates/vanilla-js/_gitignore +2 -2
  19. package/templates/vanilla-js/eslint.config.js +22 -22
  20. package/templates/vanilla-js/index.html +25 -25
  21. package/templates/vanilla-js/package.json +26 -26
  22. package/templates/vanilla-js/src/sketch.js +28 -28
  23. package/templates/vanilla-js/vite.config.js +7 -7
  24. package/templates/vanilla-js-tweakpane/_gitignore +2 -2
  25. package/templates/vanilla-js-tweakpane/eslint.config.js +22 -22
  26. package/templates/vanilla-js-tweakpane/index.html +37 -37
  27. package/templates/vanilla-js-tweakpane/package.json +27 -27
  28. package/templates/vanilla-js-tweakpane/src/sketch.js +62 -62
  29. package/templates/vanilla-js-tweakpane/vite.config.js +7 -7
  30. package/templates/vanilla-ts/.prettierrc +8 -8
  31. package/templates/vanilla-ts/_gitignore +1 -1
  32. package/templates/vanilla-ts/eslint.config.js +23 -23
  33. package/templates/vanilla-ts/index.html +26 -26
  34. package/templates/vanilla-ts/package.json +30 -30
  35. package/templates/vanilla-ts/src/sketch.ts +28 -28
  36. package/templates/vanilla-ts/tsconfig.json +15 -15
  37. package/templates/vanilla-ts/vite.config.ts +7 -7
  38. package/templates/vanilla-ts-tweakpane/_gitignore +2 -2
  39. package/templates/vanilla-ts-tweakpane/eslint.config.js +23 -23
  40. package/templates/vanilla-ts-tweakpane/index.html +37 -37
  41. package/templates/vanilla-ts-tweakpane/package.json +32 -32
  42. package/templates/vanilla-ts-tweakpane/src/sketch.ts +72 -72
  43. package/templates/vanilla-ts-tweakpane/tsconfig.json +15 -15
  44. package/templates/vanilla-ts-tweakpane/vite.config.ts +7 -7
  45. package/templates/vanilla-js-fxhash/_gitignore +0 -2
  46. package/templates/vanilla-js-fxhash/eslint.config.js +0 -22
  47. package/templates/vanilla-js-fxhash/index.html +0 -27
  48. package/templates/vanilla-js-fxhash/package.json +0 -26
  49. package/templates/vanilla-js-fxhash/public/fxhash.min.js +0 -1
  50. package/templates/vanilla-js-fxhash/src/sketch.js +0 -101
  51. package/templates/vanilla-js-fxhash/vite.config.js +0 -7
  52. package/templates/vanilla-ts-fxhash/_gitignore +0 -2
  53. package/templates/vanilla-ts-fxhash/eslint.config.js +0 -23
  54. package/templates/vanilla-ts-fxhash/index.html +0 -27
  55. package/templates/vanilla-ts-fxhash/package.json +0 -30
  56. package/templates/vanilla-ts-fxhash/public/fxhash.min.js +0 -1
  57. package/templates/vanilla-ts-fxhash/src/fxhash.d.ts +0 -54
  58. package/templates/vanilla-ts-fxhash/src/sketch.ts +0 -108
  59. package/templates/vanilla-ts-fxhash/tsconfig.json +0 -15
  60. package/templates/vanilla-ts-fxhash/vite.config.ts +0 -7
package/src/versions.js CHANGED
@@ -1,52 +1,56 @@
1
- import https from 'https';
2
-
3
- let cachedVersions = null;
4
-
5
- function fetchJson(url) {
6
- return new Promise((resolve, reject) => {
7
- https
8
- .get(url, (res) => {
9
- let data = '';
10
- res.on('data', (chunk) => {
11
- data += chunk;
12
- });
13
- res.on('end', () => {
14
- try {
15
- resolve(JSON.parse(data));
16
- } catch (err) {
17
- reject(err);
18
- }
19
- });
20
- })
21
- .on('error', reject);
22
- });
23
- }
24
-
25
- export function isStable(version) {
26
- // Exclude prerelease tags like -beta, -rc, etc.
27
- return !version.includes('-');
28
- }
29
-
30
- export function compareSemverDesc(a, b) {
31
- const pa = a.split('.').map(Number);
32
- const pb = b.split('.').map(Number);
33
- for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
34
- const va = pa[i] || 0;
35
- const vb = pb[i] || 0;
36
- if (va > vb) return -1;
37
- if (va < vb) return 1;
38
- }
39
- return 0;
40
- }
41
-
42
- export async function getTextmodeVersions(limit = 20) {
43
- if (cachedVersions) return cachedVersions.slice(0, limit);
44
-
45
- const data = await fetchJson('https://registry.npmjs.org/textmode.js');
46
- const versions = Object.keys(data.versions || {})
47
- .filter(isStable)
48
- .sort(compareSemverDesc);
49
-
50
- cachedVersions = versions;
51
- return versions.slice(0, limit);
52
- }
1
+ import https from 'https';
2
+
3
+ let cachedVersions = null;
4
+
5
+ function fetchJson(url) {
6
+ return new Promise((resolve, reject) => {
7
+ https
8
+ .get(url, (res) => {
9
+ let data = '';
10
+ res.on('data', (chunk) => {
11
+ data += chunk;
12
+ });
13
+ res.on('end', () => {
14
+ try {
15
+ resolve(JSON.parse(data));
16
+ } catch (err) {
17
+ reject(err);
18
+ }
19
+ });
20
+ })
21
+ .on('error', reject);
22
+ });
23
+ }
24
+
25
+ export function isStable(version) {
26
+ // Exclude prerelease tags like -beta, -rc, etc.
27
+ return !version.includes('-');
28
+ }
29
+
30
+ export function compareSemverDesc(a, b) {
31
+ const pa = a.split('.').map(Number);
32
+ const pb = b.split('.').map(Number);
33
+ for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
34
+ const va = pa[i] || 0;
35
+ const vb = pb[i] || 0;
36
+ if (va > vb) return -1;
37
+ if (va < vb) return 1;
38
+ }
39
+ return 0;
40
+ }
41
+
42
+ export function filterAtLeast(versions, min) {
43
+ return versions.filter((v) => compareSemverDesc(v, min) <= 0);
44
+ }
45
+
46
+ export async function getTextmodeVersions(limit = 20) {
47
+ if (cachedVersions) return cachedVersions.slice(0, limit);
48
+
49
+ const data = await fetchJson('https://registry.npmjs.org/textmode.js');
50
+ const versions = Object.keys(data.versions || {})
51
+ .filter(isStable)
52
+ .sort(compareSemverDesc);
53
+
54
+ cachedVersions = versions;
55
+ return versions.slice(0, limit);
56
+ }
@@ -1,8 +1,8 @@
1
- {
2
- "$schema": "https://json.schemastore.org/prettierrc",
3
- "printWidth": 80,
4
- "tabWidth": 2,
5
- "singleQuote": true,
6
- "trailingComma": "es5",
7
- "semi": true
8
- }
1
+ {
2
+ "$schema": "https://json.schemastore.org/prettierrc",
3
+ "printWidth": 80,
4
+ "tabWidth": 2,
5
+ "singleQuote": true,
6
+ "trailingComma": "es5",
7
+ "semi": true
8
+ }
@@ -1,2 +1,2 @@
1
- node_modules
2
- dist
1
+ node_modules
2
+ dist
@@ -1,22 +1,22 @@
1
- import { defineConfig } from "eslint/config";
2
- import js from "@eslint/js";
3
- import globals from "globals";
4
- import prettierConfig from "eslint-config-prettier";
5
-
6
- export default defineConfig([
7
- {
8
- ignores: ["dist/**", "node_modules/**"],
9
- },
10
- js.configs.recommended,
11
- prettierConfig,
12
- {
13
- files: ["**/*.js"],
14
- languageOptions: {
15
- ecmaVersion: "latest",
16
- sourceType: "module",
17
- globals: {
18
- ...globals.browser,
19
- },
20
- },
21
- },
22
- ]);
1
+ import { defineConfig } from "eslint/config";
2
+ import js from "@eslint/js";
3
+ import globals from "globals";
4
+ import prettierConfig from "eslint-config-prettier";
5
+
6
+ export default defineConfig([
7
+ {
8
+ ignores: ["dist/**", "node_modules/**"],
9
+ },
10
+ js.configs.recommended,
11
+ prettierConfig,
12
+ {
13
+ files: ["**/*.js"],
14
+ languageOptions: {
15
+ ecmaVersion: "latest",
16
+ sourceType: "module",
17
+ globals: {
18
+ ...globals.browser,
19
+ },
20
+ },
21
+ },
22
+ ]);
@@ -1,26 +1,26 @@
1
- <!doctype html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>{{name}}</title>
8
-
9
- <style>
10
- html,
11
- body {
12
- margin: 0;
13
- padding: 0;
14
- }
15
-
16
- canvas {
17
- display: block;
18
- }
19
- </style>
20
- </head>
21
-
22
- <body>
23
- <script type="module" src="/src/sketch.js"></script>
24
- </body>
25
-
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{name}}</title>
8
+
9
+ <style>
10
+ html,
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ canvas {
17
+ display: block;
18
+ }
19
+ </style>
20
+ </head>
21
+
22
+ <body>
23
+ <script type="module" src="/src/sketch.js"></script>
24
+ </body>
25
+
26
26
  </html>
@@ -1,26 +1,26 @@
1
- {
2
- "name": "{{name}}",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview",
10
- "format": "prettier --write \"src/**/*.{js,ts}\"",
11
- "format:check": "prettier --check \"src/**/*.{js,ts}\"",
12
- "lint": "eslint src --ext .js",
13
- "lint:fix": "eslint src --ext .js --fix"
14
- },
15
- "dependencies": {
16
- "textmode.js": "latest"
17
- },
18
- "devDependencies": {
19
- "@eslint/js": "^9.39.1",
20
- "eslint": "^9.39.1",
21
- "eslint-config-prettier": "^10.1.8",
22
- "globals": "^15.14.0",
23
- "prettier": "^3.7.4",
24
- "vite": "^7.2.7"
25
- }
26
- }
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "format": "prettier --write \"src/**/*.{js,ts}\"",
11
+ "format:check": "prettier --check \"src/**/*.{js,ts}\"",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix"
14
+ },
15
+ "dependencies": {
16
+ "textmode.js": "latest"
17
+ },
18
+ "devDependencies": {
19
+ "@eslint/js": "^10.0.1",
20
+ "eslint": "^10.8.1",
21
+ "eslint-config-prettier": "^10.1.8",
22
+ "globals": "^16.0.0",
23
+ "prettier": "^3.9.5",
24
+ "vite": "^8.1.3"
25
+ }
26
+ }
@@ -1,28 +1,28 @@
1
- import { textmode } from 'textmode.js';
2
-
3
- const tm = textmode.create({ width: window.innerWidth, height: window.innerHeight });
4
-
5
- tm.draw(() => {
6
- tm.background(0);
7
-
8
- const halfCols = (tm.grid.cols / 2) + 1;
9
- const halfRows = (tm.grid.rows / 2) + 1;
10
-
11
- for (let y = -halfRows; y < halfRows; y++) {
12
- for (let x = -halfCols; x < halfCols; x++) {
13
- const dist = Math.sqrt(x * x + y * y);
14
- const wave = Math.sin(dist * 0.2 - tm.frameCount * 0.1);
15
-
16
- tm.push();
17
- tm.translate(x, y, 0);
18
- tm.char(wave > 0.5 ? '▓' : wave > 0 ? '▒' : '░');
19
- tm.charColor(0, 150 + wave * 100, 255);
20
- tm.point();
21
- tm.pop();
22
- }
23
- }
24
- });
25
-
26
- tm.windowResized(() => {
27
- tm.resizeCanvas(window.innerWidth, window.innerHeight);
28
- });
1
+ import { textmode } from 'textmode.js';
2
+
3
+ const tm = textmode.create({ width: window.innerWidth, height: window.innerHeight });
4
+
5
+ tm.draw(() => {
6
+ tm.background(0);
7
+
8
+ const halfCols = (tm.grid.cols / 2) + 1;
9
+ const halfRows = (tm.grid.rows / 2) + 1;
10
+
11
+ for (let y = -halfRows; y < halfRows; y++) {
12
+ for (let x = -halfCols; x < halfCols; x++) {
13
+ const dist = Math.sqrt(x * x + y * y);
14
+ const wave = Math.sin(dist * 0.2 - tm.frameCount * 0.1);
15
+
16
+ tm.push();
17
+ tm.translate(x, y, 0);
18
+ tm.char(wave > 0.5 ? '▓' : wave > 0 ? '▒' : '░');
19
+ tm.charColor(0, 150 + wave * 100, 255);
20
+ tm.point();
21
+ tm.pop();
22
+ }
23
+ }
24
+ });
25
+
26
+ tm.windowResized(() => {
27
+ tm.resizeCanvas(window.innerWidth, window.innerHeight);
28
+ });
@@ -1,7 +1,7 @@
1
- import { defineConfig } from 'vite';
2
-
3
- export default defineConfig({
4
- server: {
5
- open: true
6
- }
7
- });
1
+ import { defineConfig } from 'vite';
2
+
3
+ export default defineConfig({
4
+ server: {
5
+ open: true
6
+ }
7
+ });
@@ -1,2 +1,2 @@
1
- node_modules
2
- dist
1
+ node_modules
2
+ dist
@@ -1,22 +1,22 @@
1
- import { defineConfig } from "eslint/config";
2
- import js from "@eslint/js";
3
- import globals from "globals";
4
- import prettierConfig from "eslint-config-prettier";
5
-
6
- export default defineConfig([
7
- {
8
- ignores: ["dist/**", "node_modules/**"],
9
- },
10
- js.configs.recommended,
11
- prettierConfig,
12
- {
13
- files: ["**/*.js"],
14
- languageOptions: {
15
- ecmaVersion: "latest",
16
- sourceType: "module",
17
- globals: {
18
- ...globals.browser,
19
- },
20
- },
21
- },
22
- ]);
1
+ import { defineConfig } from "eslint/config";
2
+ import js from "@eslint/js";
3
+ import globals from "globals";
4
+ import prettierConfig from "eslint-config-prettier";
5
+
6
+ export default defineConfig([
7
+ {
8
+ ignores: ["dist/**", "node_modules/**"],
9
+ },
10
+ js.configs.recommended,
11
+ prettierConfig,
12
+ {
13
+ files: ["**/*.js"],
14
+ languageOptions: {
15
+ ecmaVersion: "latest",
16
+ sourceType: "module",
17
+ globals: {
18
+ ...globals.browser,
19
+ },
20
+ },
21
+ },
22
+ ]);
@@ -1,37 +1,37 @@
1
- <!doctype html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>{{name}}</title>
8
-
9
- <style>
10
- html,
11
- body {
12
- margin: 0;
13
- padding: 0;
14
- }
15
-
16
- canvas {
17
- display: block;
18
- }
19
-
20
- #pane {
21
- position: fixed;
22
- top: 12px;
23
- right: 12px;
24
- width: 260px;
25
- z-index: 10;
26
- pointer-events: auto;
27
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
28
- }
29
- </style>
30
- </head>
31
-
32
- <body>
33
- <div id="pane"></div>
34
- <script type="module" src="/src/sketch.js"></script>
35
- </body>
36
-
37
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>{{name}}</title>
8
+
9
+ <style>
10
+ html,
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+
16
+ canvas {
17
+ display: block;
18
+ }
19
+
20
+ #pane {
21
+ position: fixed;
22
+ top: 12px;
23
+ right: 12px;
24
+ width: 260px;
25
+ z-index: 10;
26
+ pointer-events: auto;
27
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
28
+ }
29
+ </style>
30
+ </head>
31
+
32
+ <body>
33
+ <div id="pane"></div>
34
+ <script type="module" src="/src/sketch.js"></script>
35
+ </body>
36
+
37
+ </html>
@@ -1,27 +1,27 @@
1
- {
2
- "name": "{{name}}",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview",
10
- "format": "prettier --write \"src/**/*.{js,ts}\"",
11
- "format:check": "prettier --check \"src/**/*.{js,ts}\"",
12
- "lint": "eslint src --ext .js",
13
- "lint:fix": "eslint src --ext .js --fix"
14
- },
15
- "dependencies": {
16
- "textmode.js": "latest",
17
- "tweakpane": "^4.0.5"
18
- },
19
- "devDependencies": {
20
- "@eslint/js": "^9.39.1",
21
- "eslint": "^9.39.1",
22
- "eslint-config-prettier": "^10.1.8",
23
- "globals": "^15.14.0",
24
- "prettier": "^3.7.4",
25
- "vite": "^7.2.7"
26
- }
27
- }
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "format": "prettier --write \"src/**/*.{js,ts}\"",
11
+ "format:check": "prettier --check \"src/**/*.{js,ts}\"",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix"
14
+ },
15
+ "dependencies": {
16
+ "textmode.js": "latest",
17
+ "tweakpane": "^4.0.5"
18
+ },
19
+ "devDependencies": {
20
+ "@eslint/js": "^10.0.1",
21
+ "eslint": "^10.8.1",
22
+ "eslint-config-prettier": "^10.1.8",
23
+ "globals": "^16.0.0",
24
+ "prettier": "^3.9.5",
25
+ "vite": "^8.1.3"
26
+ }
27
+ }
@@ -1,62 +1,62 @@
1
- import { textmode } from 'textmode.js';
2
- import { Pane } from 'tweakpane';
3
-
4
- const tm = textmode.create({ width: window.innerWidth, height: window.innerHeight });
5
-
6
- const params = {
7
- speed: 0.1,
8
- frequency: 0.22,
9
- amplitude: 1.1,
10
- baseCharColor: { r: 0, g: 170, b: 255 },
11
- baseCellColor: { r: 0, g: 0, b: 0 }
12
- };
13
-
14
- const paneContainer = document.querySelector('#pane') ?? undefined;
15
- const pane = new Pane({ title: 'Controls', container: paneContainer });
16
-
17
- pane.addBinding(params, 'speed', { min: 0, max: 0.4, step: 0.005, label: 'Speed' });
18
- pane.addBinding(params, 'frequency', { min: 0.05, max: 0.8, step: 0.01, label: 'Frequency' });
19
- pane.addBinding(params, 'amplitude', { min: 0.5, max: 2.0, step: 0.05, label: 'Amplitude' });
20
- pane.addBinding(params, 'baseCharColor', { label: 'Base Char Color' });
21
- pane.addBinding(params, 'baseCellColor', { label: 'Base Cell Color' });
22
-
23
- const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
24
-
25
- const getChar = (wave) => {
26
- if (wave > 0.6) return '▓';
27
- if (wave > 0.2) return '▒';
28
- return '░';
29
- };
30
-
31
- tm.draw(() => {
32
- const halfCols = Math.round(tm.grid.cols / 2) + 1;
33
- const halfRows = Math.round(tm.grid.rows / 2) + 1;
34
-
35
- for (let y = -halfRows; y < halfRows; y++) {
36
- for (let x = -halfCols; x < halfCols; x++) {
37
- const dist = Math.hypot(x, y);
38
- const wave = Math.sin(dist * params.frequency - tm.frameCount * params.speed) * params.amplitude;
39
- const char = getChar(wave);
40
-
41
- const lightness = 0.35 + 0.65 * ((wave + params.amplitude) / (2 * params.amplitude));
42
- const charR = clamp(params.baseCharColor.r * lightness, 0, 255);
43
- const charG = clamp(params.baseCharColor.g * lightness, 0, 255);
44
- const charB = clamp(params.baseCharColor.b * lightness, 0, 255);
45
- const cellR = clamp(params.baseCellColor.r * lightness, 0, 255);
46
- const cellG = clamp(params.baseCellColor.g * lightness, 0, 255);
47
- const cellB = clamp(params.baseCellColor.b * lightness, 0, 255);
48
-
49
- tm.push();
50
- tm.translate(x, y, 0);
51
- tm.char(char);
52
- tm.charColor(charR, charG, charB);
53
- tm.cellColor(cellR, cellG, cellB);
54
- tm.point();
55
- tm.pop();
56
- }
57
- }
58
- });
59
-
60
- tm.windowResized(() => {
61
- tm.resizeCanvas(window.innerWidth, window.innerHeight);
62
- });
1
+ import { textmode } from 'textmode.js';
2
+ import { Pane } from 'tweakpane';
3
+
4
+ const tm = textmode.create({ width: window.innerWidth, height: window.innerHeight });
5
+
6
+ const params = {
7
+ speed: 0.1,
8
+ frequency: 0.22,
9
+ amplitude: 1.1,
10
+ baseCharColor: { r: 0, g: 170, b: 255 },
11
+ baseCellColor: { r: 0, g: 0, b: 0 }
12
+ };
13
+
14
+ const paneContainer = document.querySelector('#pane') ?? undefined;
15
+ const pane = new Pane({ title: 'Controls', container: paneContainer });
16
+
17
+ pane.addBinding(params, 'speed', { min: 0, max: 0.4, step: 0.005, label: 'Speed' });
18
+ pane.addBinding(params, 'frequency', { min: 0.05, max: 0.8, step: 0.01, label: 'Frequency' });
19
+ pane.addBinding(params, 'amplitude', { min: 0.5, max: 2.0, step: 0.05, label: 'Amplitude' });
20
+ pane.addBinding(params, 'baseCharColor', { label: 'Base Char Color' });
21
+ pane.addBinding(params, 'baseCellColor', { label: 'Base Cell Color' });
22
+
23
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
24
+
25
+ const getChar = (wave) => {
26
+ if (wave > 0.6) return '▓';
27
+ if (wave > 0.2) return '▒';
28
+ return '░';
29
+ };
30
+
31
+ tm.draw(() => {
32
+ const halfCols = Math.round(tm.grid.cols / 2) + 1;
33
+ const halfRows = Math.round(tm.grid.rows / 2) + 1;
34
+
35
+ for (let y = -halfRows; y < halfRows; y++) {
36
+ for (let x = -halfCols; x < halfCols; x++) {
37
+ const dist = Math.hypot(x, y);
38
+ const wave = Math.sin(dist * params.frequency - tm.frameCount * params.speed) * params.amplitude;
39
+ const char = getChar(wave);
40
+
41
+ const lightness = 0.35 + 0.65 * ((wave + params.amplitude) / (2 * params.amplitude));
42
+ const charR = clamp(params.baseCharColor.r * lightness, 0, 255);
43
+ const charG = clamp(params.baseCharColor.g * lightness, 0, 255);
44
+ const charB = clamp(params.baseCharColor.b * lightness, 0, 255);
45
+ const cellR = clamp(params.baseCellColor.r * lightness, 0, 255);
46
+ const cellG = clamp(params.baseCellColor.g * lightness, 0, 255);
47
+ const cellB = clamp(params.baseCellColor.b * lightness, 0, 255);
48
+
49
+ tm.push();
50
+ tm.translate(x, y, 0);
51
+ tm.char(char);
52
+ tm.charColor(charR, charG, charB);
53
+ tm.cellColor(cellR, cellG, cellB);
54
+ tm.point();
55
+ tm.pop();
56
+ }
57
+ }
58
+ });
59
+
60
+ tm.windowResized(() => {
61
+ tm.resizeCanvas(window.innerWidth, window.innerHeight);
62
+ });