omnibot3000 1.8.2

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 (83) hide show
  1. package/.prettierrc +18 -0
  2. package/README.md +9 -0
  3. package/api/server.ts +153 -0
  4. package/eslint.config.js +103 -0
  5. package/index.html +22 -0
  6. package/netlify.toml +9 -0
  7. package/nodemon.json +4 -0
  8. package/omnibot3000.code-workspace +55 -0
  9. package/package.json +58 -0
  10. package/pnpm-workspace.yaml +2 -0
  11. package/public/fonts/vt220.woff2 +0 -0
  12. package/src/App.module.css +128 -0
  13. package/src/App.tsx +193 -0
  14. package/src/Error.tsx +80 -0
  15. package/src/commons/OmnibotSpeak.module.css +43 -0
  16. package/src/commons/OmnibotSpeak.tsx +31 -0
  17. package/src/commons/api/api.ts +150 -0
  18. package/src/commons/constants.ts +34 -0
  19. package/src/commons/favicon.ts +69 -0
  20. package/src/commons/hooks/useConfig.tsx +50 -0
  21. package/src/commons/hooks/useKeyPress.tsx +76 -0
  22. package/src/commons/hooks/useStorage.tsx +38 -0
  23. package/src/commons/layout/Background.module.css +47 -0
  24. package/src/commons/layout/Background.tsx +138 -0
  25. package/src/commons/layout/Breadcrumb.module.css +28 -0
  26. package/src/commons/layout/Breadcrumb.tsx +54 -0
  27. package/src/commons/layout/Container.module.css +51 -0
  28. package/src/commons/layout/Container.tsx +60 -0
  29. package/src/commons/layout/Footer.module.css +36 -0
  30. package/src/commons/layout/Footer.tsx +74 -0
  31. package/src/commons/layout/Header.module.css +73 -0
  32. package/src/commons/layout/Header.tsx +102 -0
  33. package/src/commons/layout/Menu.module.css +36 -0
  34. package/src/commons/layout/Menu.tsx +37 -0
  35. package/src/commons/persona.txt +38 -0
  36. package/src/commons/styles/debug.css +71 -0
  37. package/src/commons/styles/main.css +221 -0
  38. package/src/commons/styles/vt220.css +69 -0
  39. package/src/commons/ui/Button.tsx +22 -0
  40. package/src/commons/ui/Caret.tsx +20 -0
  41. package/src/commons/ui/Line.tsx +64 -0
  42. package/src/commons/ui/ScrollSnap.tsx +51 -0
  43. package/src/commons/ui/Separator.tsx +19 -0
  44. package/src/commons/ui/Spacer.tsx +12 -0
  45. package/src/commons/utils/canvas.ts +20 -0
  46. package/src/commons/utils/color.ts +4 -0
  47. package/src/commons/utils/math.ts +43 -0
  48. package/src/commons/utils/strings.ts +47 -0
  49. package/src/commons/utils/styles.ts +11 -0
  50. package/src/commons/utils/system.ts +6 -0
  51. package/src/commons/utils/version.ts +24 -0
  52. package/src/features/chat/Chat.module.css +8 -0
  53. package/src/features/chat/Chat.tsx +188 -0
  54. package/src/features/chat/commons/strings.ts +6 -0
  55. package/src/features/chat/components/Message.module.css +28 -0
  56. package/src/features/chat/components/Message.tsx +45 -0
  57. package/src/features/chat/components/Toolbar.module.css +19 -0
  58. package/src/features/chat/components/Toolbar.tsx +44 -0
  59. package/src/features/chat/hooks/useChatCompletionStore.tsx +160 -0
  60. package/src/features/cli/Cli.module.css +75 -0
  61. package/src/features/cli/Cli.tsx +303 -0
  62. package/src/features/console/cmd.ts +93 -0
  63. package/src/features/console/config.ts +106 -0
  64. package/src/features/help/Help.module.css +8 -0
  65. package/src/features/help/Help.tsx +78 -0
  66. package/src/features/history/History.module.css +77 -0
  67. package/src/features/history/History.tsx +92 -0
  68. package/src/features/home/Home.module.css +26 -0
  69. package/src/features/home/Home.tsx +101 -0
  70. package/src/features/life/Life.module.css +8 -0
  71. package/src/features/life/Life.tsx +16 -0
  72. package/src/features/life/generation.ts +103 -0
  73. package/src/features/life/lifeforms.ts +138 -0
  74. package/src/features/life/types.ts +5 -0
  75. package/src/features/version/Version.module.css +8 -0
  76. package/src/features/version/Version.tsx +70 -0
  77. package/src/global.d.ts +10 -0
  78. package/src/main.tsx +32 -0
  79. package/src/vite-env.d.ts +16 -0
  80. package/tsconfig.api.json +16 -0
  81. package/tsconfig.json +47 -0
  82. package/upgrade.sh +22 -0
  83. package/vite.config.ts +51 -0
package/.prettierrc ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "useTabs": false,
3
+ "bracketSpacing": false,
4
+ "bracketSameLine": true,
5
+ "semi": true,
6
+ "trailingComma": "all",
7
+ "singleQuote": false,
8
+ "printWidth": 80,
9
+ "tabWidth": 2,
10
+ "proseWrap": "always",
11
+ "endOfLine": "lf",
12
+ "overrides": [
13
+ {
14
+ "files": ".prettierrc",
15
+ "options": {"parser": "json"}
16
+ }
17
+ ]
18
+ }
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ ## OMNIBOT 3000
2
+
3
+ YOUR OMNISCIENT SOURCE OF TRUTH
4
+
5
+ ![image](https://github.com/user-attachments/assets/f8954b78-290e-469f-b85f-99a4fef8281a)
6
+
7
+ ### Install
8
+
9
+ ```pnpm i omnibot3000```
package/api/server.ts ADDED
@@ -0,0 +1,153 @@
1
+ import {exec} from "child_process";
2
+ import {
3
+ accessSync,
4
+ constants as FS,
5
+ Dirent,
6
+ readdirSync,
7
+ statSync,
8
+ writeFileSync,
9
+ } from "fs";
10
+ import {createServer, IncomingMessage, ServerResponse} from "http";
11
+ import path from "path";
12
+
13
+ import "dotenv/config";
14
+ import OpenAI from "openai";
15
+ import type {ChatCompletionChunk} from "openai/resources/index.mjs";
16
+ import type {Stream} from "openai/streaming";
17
+
18
+ type Package = {
19
+ name: string;
20
+ version: [number, number, number];
21
+ size: number;
22
+ };
23
+
24
+ const DOMAIN = process.env.DOMAIN || "localhost";
25
+ const API_PATH = process.env.API_PATH || "/api";
26
+ const API_PORT = process.env.API_PORT || 3001;
27
+ const BASE_PATH = process.cwd();
28
+ const JSON_PATH = path.join(BASE_PATH, "dist", "packages.json");
29
+
30
+ const getFolderSize = (folder: string): number => {
31
+ let total = 0;
32
+ try {
33
+ accessSync(folder, FS.R_OK);
34
+ } catch {
35
+ return total;
36
+ }
37
+ const files: Dirent[] = readdirSync(folder, {withFileTypes: true});
38
+ for (const file of files) {
39
+ const fullPath = path.join(folder, file.name);
40
+ if (file.isDirectory()) total += getFolderSize(fullPath);
41
+ else total += statSync(fullPath).size;
42
+ }
43
+ return total;
44
+ };
45
+
46
+ const server = createServer((req: IncomingMessage, res: ServerResponse) => {
47
+ /* CORS headers */
48
+ res.setHeader("Access-Control-Allow-Origin", "*");
49
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
50
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
51
+
52
+ /* preflight requests */
53
+ if (req.method === "OPTIONS") {
54
+ res.writeHead(204);
55
+ res.end();
56
+ return;
57
+ }
58
+
59
+ const url = req.url || "";
60
+
61
+ if (url.startsWith(`${API_PATH}/completion`)) {
62
+ let body = "";
63
+ req.on("data", (chunk) => {
64
+ body += chunk.toString();
65
+ });
66
+ req.on("end", async () => {
67
+ try {
68
+ const {messages, stream} = JSON.parse(body);
69
+
70
+ const openai = new OpenAI({
71
+ apiKey: process.env.OPENAI_API_KEY,
72
+ organization: process.env.OPENAI_ORG_ID,
73
+ project: process.env.OPENAI_PROJECT_ID,
74
+ });
75
+
76
+ const response = await openai.chat.completions.create({
77
+ /* https://openai.com/api/pricing/ */
78
+ model: "gpt-4.1-mini",
79
+ messages,
80
+ max_completion_tokens: 1000,
81
+ temperature: 1.0, // lower temperature to get stricter completion (good for code)
82
+ //reasoning: {effort: "high"},
83
+ stream,
84
+ });
85
+
86
+ if (stream) {
87
+ /* server-sent events headers */
88
+ res.writeHead(200, {
89
+ "Content-Type": "text/event-stream",
90
+ "Cache-Control": "no-cache",
91
+ Connection: "keep-alive",
92
+ });
93
+ /* forward chunks to browser as SSE */
94
+ for await (const chunk of response as unknown as Stream<ChatCompletionChunk>) {
95
+ res.write(`data: ${JSON.stringify(chunk)}\n\n`);
96
+ }
97
+ /* end the SSE stream */
98
+ res.write("data: [DONE]\n\n");
99
+ res.end();
100
+ } else {
101
+ res.writeHead(200, {"Content-Type": "application/json"});
102
+ res.end(JSON.stringify(response));
103
+ }
104
+ } catch (err) {
105
+ const error = err instanceof Error ? err.message : "unknown error";
106
+ res.writeHead(500, {"Content-Type": "application/json"});
107
+ res.end(JSON.stringify({error}));
108
+ }
109
+ });
110
+ } else if (url.startsWith(`${API_PATH}/packages`)) {
111
+ exec("npm list --json --depth=0 --silent", (err, stdout) => {
112
+ if (err) {
113
+ const error = err instanceof Error ? err.message : "unknown error";
114
+ res.writeHead(500, {"Content-Type": "application/json"});
115
+ res.end(JSON.stringify({error}));
116
+ return;
117
+ }
118
+ const json = JSON.parse(stdout);
119
+ const data = json?.dependencies || {};
120
+ const list = Object.keys(data)
121
+ .map((key) => {
122
+ const dir = data[key].resolved.replace("file:", "");
123
+ return {
124
+ name: key,
125
+ version: data[key].version.split(".") as Package["version"],
126
+ size: getFolderSize(path.join(BASE_PATH, "node_modules", dir)),
127
+ };
128
+ })
129
+ .sort((a, b) => (a.name < b.name ? -1 : 1));
130
+ res.writeHead(200, {"Content-Type": "application/json"});
131
+ res.end(JSON.stringify(list));
132
+ writeFileSync(JSON_PATH, JSON.stringify(list, null, 2));
133
+ });
134
+ } else {
135
+ res.writeHead(404);
136
+ res.end("nothing to see here");
137
+ }
138
+ });
139
+
140
+ server.listen(API_PORT, () => {
141
+ console.log(
142
+ "\n\x1b[1m\x1b[32m%s\x1b[0m %s \x1b[36m%s\x1b[0m",
143
+ " →",
144
+ "API running at",
145
+ `http://${DOMAIN}${API_PATH}/`,
146
+ );
147
+ console.log(
148
+ "\x1b[1m\x1b[32m%s\x1b[0m %s \x1b[36m%s\x1b[0m\n",
149
+ " →",
150
+ "path:",
151
+ BASE_PATH,
152
+ );
153
+ });
@@ -0,0 +1,103 @@
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import reactHooks from "eslint-plugin-react-hooks";
4
+ import reactRefresh from "eslint-plugin-react-refresh";
5
+ import tseslint from "typescript-eslint";
6
+ import importPlugin from "eslint-plugin-import";
7
+ import simpleImportSort from "eslint-plugin-simple-import-sort";
8
+
9
+ export default tseslint.config(
10
+ {ignores: ["dist", ".vite", "node_modules"]},
11
+ js.configs.recommended,
12
+ tseslint.configs.recommended,
13
+ {
14
+ extends: [
15
+ importPlugin.flatConfigs.recommended,
16
+ importPlugin.flatConfigs.typescript,
17
+ ],
18
+ files: ["**/*.{ts,tsx}"],
19
+ languageOptions: {
20
+ globals: globals.browser,
21
+ parserOptions: {
22
+ sourceType: "module",
23
+ ecmaVersion: "latest",
24
+ },
25
+ },
26
+ plugins: {
27
+ "react-hooks": reactHooks,
28
+ "react-refresh": reactRefresh,
29
+ "simple-import-sort": simpleImportSort,
30
+ },
31
+ rules: {
32
+ ...reactHooks.configs.recommended.rules,
33
+ "no-undef": "off", // typescript handles this
34
+ "@typescript-eslint/no-unused-vars": ["warn", {argsIgnorePattern: "^_"}],
35
+ "react-hooks/exhaustive-deps": "off", // to fix later
36
+ "react-hooks/set-state-in-effect": "off",
37
+ "import/first": "error",
38
+ "import/no-unresolved": "off",
39
+ "import/newline-after-import": "error",
40
+ "import/no-duplicates": "error",
41
+ "no-console": ["error", {allow: ["info", "warn", "error"]}],
42
+ "simple-import-sort/imports": [
43
+ "error",
44
+ {
45
+ groups: [
46
+ ["^(vite.*)(/.*|$)"],
47
+ ["^(react.*|zustand)(/.*|$)"],
48
+ ["^(openai)(/.*|$)"],
49
+ ["^(@mui.*)(/.*|$)"],
50
+ ["^(@root)(/.*|$)"],
51
+ ["^(@api|@commons|@layout|@ui|@utils)(/.*|$)"],
52
+ ["^(@hooks)(/.*|$)"],
53
+ ["^(@styles)(/.*|$)"],
54
+ ["^(@home|@chat|@history|@console|@help)(/.*|$)"],
55
+ ["^(@)(/.*|$)"],
56
+ ["^\\u0000"],
57
+ // parent imports. put ".." last
58
+ ["^\\.\\.(?!/?$)", "^\\.\\./?$"],
59
+ // other relative imports. put same folder imports and "." last
60
+ ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
61
+ // style imports
62
+ ["^.+\\.s?css$"],
63
+ ],
64
+ },
65
+ ],
66
+ },
67
+ },
68
+ {
69
+ extends: [
70
+ importPlugin.flatConfigs.recommended,
71
+ importPlugin.flatConfigs.typescript,
72
+ ],
73
+ files: ["api/**/*.ts"],
74
+ languageOptions: {
75
+ globals: globals.node,
76
+ parserOptions: {
77
+ sourceType: "module",
78
+ ecmaVersion: "latest",
79
+ },
80
+ },
81
+ plugins: {
82
+ "simple-import-sort": simpleImportSort,
83
+ },
84
+ rules: {
85
+ "no-console": "off",
86
+ "import/no-unresolved": "off",
87
+ "import/first": "error",
88
+ "import/newline-after-import": "error",
89
+ "import/no-duplicates": "error",
90
+ "simple-import-sort/imports": [
91
+ "error",
92
+ {
93
+ groups: [
94
+ // node built-in modules
95
+ ["^(child_process|fs|http|path)(/.*|$)"],
96
+ // relative imports
97
+ ["^\\./"],
98
+ ],
99
+ },
100
+ ],
101
+ },
102
+ },
103
+ );
package/index.html ADDED
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <title>OMNIBOT 3000</title>
5
+ <meta charset="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <meta name="robots" content="follow index archive" />
8
+ <meta name="author" content="REZ" />
9
+ <meta name="description" content="YOUR OMNISCIENT SOURCE OF TRUTH" />
10
+ <link rel="icon" href="data:," />
11
+ <link
12
+ rel="preload"
13
+ href="/fonts/vt220.woff2"
14
+ as="font"
15
+ type="font/woff2"
16
+ crossorigin="anonymous" />
17
+ <script type="module" src="/src/main.tsx"></script>
18
+ </head>
19
+ <body>
20
+ <div id="root"></div>
21
+ </body>
22
+ </html>
package/netlify.toml ADDED
@@ -0,0 +1,9 @@
1
+ [[redirects]]
2
+ from = "/*"
3
+ to = "/index.html"
4
+ status = 200
5
+
6
+ [[redirects]]
7
+ from = "/api/*"
8
+ to = "/api"
9
+ status = 200
package/nodemon.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "watch": ["api/server.ts"],
3
+ "exec": "rm -rf ./dist/api/server.js && pnpm tsc --project tsconfig.api.json && node ./dist/api/server.js"
4
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": ".",
5
+ "name": "root",
6
+ },
7
+ {
8
+ "path": "src",
9
+ "name": "source",
10
+ },
11
+ {
12
+ "path": "api",
13
+ },
14
+ ],
15
+ "launch": {
16
+ "configurations": [
17
+ {
18
+ "name": "run start dev",
19
+ "type": "node",
20
+ "request": "launch",
21
+ "runtimeExecutable": "pnpm",
22
+ "runtimeArgs": ["run", "start", "dev"],
23
+ "console": "integratedTerminal",
24
+ "cwd": "${workspaceFolder}",
25
+ },
26
+ {
27
+ "name": "run start api",
28
+ "type": "node",
29
+ "request": "launch",
30
+ "runtimeExecutable": "pnpm",
31
+ "runtimeArgs": ["run", "start", "api"],
32
+ "console": "integratedTerminal",
33
+ "cwd": "${workspaceFolder}",
34
+ },
35
+ {
36
+ "name": "run build",
37
+ "type": "node",
38
+ "request": "launch",
39
+ "runtimeExecutable": "pnpm",
40
+ "runtimeArgs": ["run", "build"],
41
+ "console": "integratedTerminal",
42
+ "cwd": "${workspaceFolder}",
43
+ },
44
+ {
45
+ "name": "upgrade",
46
+ "type": "node",
47
+ "request": "launch",
48
+ "runtimeExecutable": "/bin/bash",
49
+ "args": ["-c", "./upgrade.sh"],
50
+ "console": "integratedTerminal",
51
+ "cwd": "${workspaceFolder}",
52
+ },
53
+ ],
54
+ },
55
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "omnibot3000",
3
+ "x-display-name": "OMNIBOT 3000",
4
+ "description": "your omniscient source of truth",
5
+ "private": false,
6
+ "version": "1.8.2",
7
+ "type": "module",
8
+ "author": {
9
+ "name": "rez",
10
+ "email": "rez@lol.pm",
11
+ "url": "https://github.com/chiptune"
12
+ },
13
+ "homepage": "https://www.omnibot3000.com",
14
+ "scripts": {
15
+ "start": "pnpm run-p start:dev start:api",
16
+ "start:dev": "pnpm run dev",
17
+ "start:api": "nodemon api/server.ts",
18
+ "start:prod": "pnpm run build && pnpm run-p prod api",
19
+ "dev": "vite",
20
+ "prod": "vite preview --port 4173",
21
+ "api": "node dist/api/server.js",
22
+ "build": "pnpm run build:client && pnpm run build:api",
23
+ "build:client": "tsc -b && vite build",
24
+ "build:api": "tsc --project tsconfig.api.json",
25
+ "lint": "eslint --fix .",
26
+ "prettify": "prettier --write ."
27
+ },
28
+ "dependencies": {
29
+ "openai": "^6.10.0",
30
+ "react": "^19.2.3",
31
+ "react-dom": "^19.2.3",
32
+ "react-markdown": "^10.1.0",
33
+ "react-router-dom": "^7.10.1",
34
+ "zustand": "^5.0.9"
35
+ },
36
+ "devDependencies": {
37
+ "@eslint/js": "^9.39.2",
38
+ "@types/node": "^25.0.1",
39
+ "@types/react": "^19.2.7",
40
+ "@types/react-dom": "^19.2.3",
41
+ "@vitejs/plugin-react": "^5.1.2",
42
+ "classnames": "^2.5.1",
43
+ "dotenv": "^17.2.3",
44
+ "eslint": "^9.39.2",
45
+ "eslint-plugin-import": "^2.32.0",
46
+ "eslint-plugin-react-hooks": "^7.0.1",
47
+ "eslint-plugin-react-refresh": "^0.4.24",
48
+ "eslint-plugin-simple-import-sort": "^12.1.1",
49
+ "globals": "^16.5.0",
50
+ "nodemon": "^3.1.11",
51
+ "npm-run-all": "^4.1.5",
52
+ "prettier": "^3.7.4",
53
+ "typescript": "^5.9.3",
54
+ "typescript-eslint": "^8.49.0",
55
+ "vite": "^7.2.7",
56
+ "vite-tsconfig-paths": "^5.1.4"
57
+ }
58
+ }
@@ -0,0 +1,2 @@
1
+ onlyBuiltDependencies:
2
+ - esbuild
Binary file
@@ -0,0 +1,128 @@
1
+ .root {
2
+ position: relative;
3
+ margin: 0;
4
+ padding: 0;
5
+ border-radius: var(--margin);
6
+ width: 100vw;
7
+ height: 100vh;
8
+ filter: brightness(1.25) contrast(1.5) saturate(1.5) blur(0.025rem)
9
+ drop-shadow(0 0 0.25rem var(--color-background));
10
+ opacity: var(--opacity-primary);
11
+ }
12
+
13
+ .screen {
14
+ position: relative;
15
+ margin: 0;
16
+ padding: var(--margin);
17
+ border-radius: var(--margin);
18
+ width: 100%;
19
+ height: 100%;
20
+ box-shadow: inset 0 0 0.2rem #000;
21
+ background:
22
+ linear-gradient(
23
+ 0deg,
24
+ var(--color-background) 0%,
25
+ var(--color-background) 100%
26
+ ),
27
+ linear-gradient(
28
+ 30deg,
29
+ hsla(calc(var(--h) - 90) 30% 30% / var(--opacity-background)) 0%,
30
+ hsla(calc(var(--h) - 45) 50% 20% / var(--opacity-background)) 25%,
31
+ hsla(calc(var(--h) + 0) 70% 10% / var(--opacity-background)) 50%,
32
+ hsla(calc(var(--h) + 45) 50% 20% / var(--opacity-background)) 75%,
33
+ hsla(calc(var(--h) + 90) 30% 30% / var(--opacity-background)) 100%
34
+ ),
35
+ radial-gradient(
36
+ circle,
37
+ hsla(calc(var(--h) - 270) 30% 30% / var(--opacity-background)) 0%,
38
+ hsla(calc(var(--h) - 180) 30% 30% / var(--opacity-background)) 100%
39
+ ),
40
+ radial-gradient(
41
+ circle,
42
+ hsla(0 100% 100% / 0.0125) 0%,
43
+ hsla(0 0% 0% / 0.25) 80%,
44
+ hsla(0 0% 0% / 0.5) 100%
45
+ );
46
+ animation: fade-tty var(--duration-fade) linear;
47
+ }
48
+
49
+ .tty {
50
+ position: absolute;
51
+ top: 0;
52
+ left: 0;
53
+ display: flex;
54
+ flex-direction: row;
55
+ place-items: start;
56
+ margin: 0;
57
+ padding: var(--margin);
58
+ z-index: var(--z-index-screen);
59
+ animation: fade-tty var(--duration-fade) linear;
60
+ background-image: repeating-linear-gradient(
61
+ 0deg,
62
+ #0000 0rem,
63
+ hsla(0 0% 0% / 0.3) 0.02rem,
64
+ hsla(0 0% 0% / 0.3) 0.12rem,
65
+ #0000 0.14rem,
66
+ #0000 0.2rem
67
+ );
68
+ background-size: 100%, 100%;
69
+ background-repeat: no-repeat, repeat;
70
+ background-position: 0rem -0.0333rem;
71
+ background-blend-mode: overlay;
72
+ }
73
+
74
+ .content {
75
+ display: flex;
76
+ flex-direction: column;
77
+ flex-grow: 1;
78
+ align-items: center;
79
+ align-self: stretch;
80
+ height: 100%;
81
+ }
82
+
83
+ .body {
84
+ display: flex;
85
+ flex-direction: column;
86
+ flex-grow: 1;
87
+ align-items: center;
88
+ align-self: stretch;
89
+ overflow: hidden;
90
+ }
91
+
92
+ .v-line {
93
+ flex-grow: 0;
94
+ flex-shrink: 0;
95
+ align-self: start;
96
+ width: var(--font-width);
97
+ height: 100%;
98
+ opacity: var(--opacity-secondary);
99
+ }
100
+
101
+ .h-line {
102
+ flex-grow: 0;
103
+ flex-shrink: 0;
104
+ align-self: start;
105
+ width: 100%;
106
+ height: var(--line-height);
107
+ opacity: var(--opacity-secondary);
108
+ }
109
+
110
+ .error {
111
+ display: flex;
112
+ flex-direction: column;
113
+ text-wrap: wrap;
114
+ width: 100%;
115
+ height: 100%;
116
+ }
117
+
118
+ @keyframes fade-tty {
119
+ 0% {
120
+ opacity: 0;
121
+ }
122
+ 20% {
123
+ opacity: 0;
124
+ }
125
+ 100% {
126
+ opacity: 1;
127
+ }
128
+ }