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.
- package/.prettierrc +18 -0
- package/README.md +9 -0
- package/api/server.ts +153 -0
- package/eslint.config.js +103 -0
- package/index.html +22 -0
- package/netlify.toml +9 -0
- package/nodemon.json +4 -0
- package/omnibot3000.code-workspace +55 -0
- package/package.json +58 -0
- package/pnpm-workspace.yaml +2 -0
- package/public/fonts/vt220.woff2 +0 -0
- package/src/App.module.css +128 -0
- package/src/App.tsx +193 -0
- package/src/Error.tsx +80 -0
- package/src/commons/OmnibotSpeak.module.css +43 -0
- package/src/commons/OmnibotSpeak.tsx +31 -0
- package/src/commons/api/api.ts +150 -0
- package/src/commons/constants.ts +34 -0
- package/src/commons/favicon.ts +69 -0
- package/src/commons/hooks/useConfig.tsx +50 -0
- package/src/commons/hooks/useKeyPress.tsx +76 -0
- package/src/commons/hooks/useStorage.tsx +38 -0
- package/src/commons/layout/Background.module.css +47 -0
- package/src/commons/layout/Background.tsx +138 -0
- package/src/commons/layout/Breadcrumb.module.css +28 -0
- package/src/commons/layout/Breadcrumb.tsx +54 -0
- package/src/commons/layout/Container.module.css +51 -0
- package/src/commons/layout/Container.tsx +60 -0
- package/src/commons/layout/Footer.module.css +36 -0
- package/src/commons/layout/Footer.tsx +74 -0
- package/src/commons/layout/Header.module.css +73 -0
- package/src/commons/layout/Header.tsx +102 -0
- package/src/commons/layout/Menu.module.css +36 -0
- package/src/commons/layout/Menu.tsx +37 -0
- package/src/commons/persona.txt +38 -0
- package/src/commons/styles/debug.css +71 -0
- package/src/commons/styles/main.css +221 -0
- package/src/commons/styles/vt220.css +69 -0
- package/src/commons/ui/Button.tsx +22 -0
- package/src/commons/ui/Caret.tsx +20 -0
- package/src/commons/ui/Line.tsx +64 -0
- package/src/commons/ui/ScrollSnap.tsx +51 -0
- package/src/commons/ui/Separator.tsx +19 -0
- package/src/commons/ui/Spacer.tsx +12 -0
- package/src/commons/utils/canvas.ts +20 -0
- package/src/commons/utils/color.ts +4 -0
- package/src/commons/utils/math.ts +43 -0
- package/src/commons/utils/strings.ts +47 -0
- package/src/commons/utils/styles.ts +11 -0
- package/src/commons/utils/system.ts +6 -0
- package/src/commons/utils/version.ts +24 -0
- package/src/features/chat/Chat.module.css +8 -0
- package/src/features/chat/Chat.tsx +188 -0
- package/src/features/chat/commons/strings.ts +6 -0
- package/src/features/chat/components/Message.module.css +28 -0
- package/src/features/chat/components/Message.tsx +45 -0
- package/src/features/chat/components/Toolbar.module.css +19 -0
- package/src/features/chat/components/Toolbar.tsx +44 -0
- package/src/features/chat/hooks/useChatCompletionStore.tsx +160 -0
- package/src/features/cli/Cli.module.css +75 -0
- package/src/features/cli/Cli.tsx +303 -0
- package/src/features/console/cmd.ts +93 -0
- package/src/features/console/config.ts +106 -0
- package/src/features/help/Help.module.css +8 -0
- package/src/features/help/Help.tsx +78 -0
- package/src/features/history/History.module.css +77 -0
- package/src/features/history/History.tsx +92 -0
- package/src/features/home/Home.module.css +26 -0
- package/src/features/home/Home.tsx +101 -0
- package/src/features/life/Life.module.css +8 -0
- package/src/features/life/Life.tsx +16 -0
- package/src/features/life/generation.ts +103 -0
- package/src/features/life/lifeforms.ts +138 -0
- package/src/features/life/types.ts +5 -0
- package/src/features/version/Version.module.css +8 -0
- package/src/features/version/Version.tsx +70 -0
- package/src/global.d.ts +10 -0
- package/src/main.tsx +32 -0
- package/src/vite-env.d.ts +16 -0
- package/tsconfig.api.json +16 -0
- package/tsconfig.json +47 -0
- package/upgrade.sh +22 -0
- package/vite.config.ts +51 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {memo, useEffect, useRef, useState} from "react";
|
|
2
|
+
import {useNavigate} from "react-router-dom";
|
|
3
|
+
|
|
4
|
+
import {getSubtitle} from "@api/api";
|
|
5
|
+
import {
|
|
6
|
+
ASCII_BLOCK1,
|
|
7
|
+
ASCII_BLOCK2,
|
|
8
|
+
ASCII_BLOCK3,
|
|
9
|
+
ASCII_LDAB,
|
|
10
|
+
ASCII_LOSANGE,
|
|
11
|
+
ASCII_RDAB,
|
|
12
|
+
BUTTON_CREATE,
|
|
13
|
+
BUTTON_LIFE,
|
|
14
|
+
NAME,
|
|
15
|
+
} from "@commons/constants";
|
|
16
|
+
import styles from "@layout/Header.module.css";
|
|
17
|
+
import Button from "@ui/Button";
|
|
18
|
+
import Spacer from "@ui/Spacer";
|
|
19
|
+
import {formatText} from "@utils/strings";
|
|
20
|
+
|
|
21
|
+
import useChatCompletionStore from "@chat/hooks/useChatCompletionStore";
|
|
22
|
+
|
|
23
|
+
import cls from "classnames";
|
|
24
|
+
|
|
25
|
+
export const AVATAR_1 = `${ASCII_LDAB}${ASCII_LOSANGE}${ASCII_RDAB}`;
|
|
26
|
+
export const AVATAR_2 = `/${ASCII_BLOCK2}\\`;
|
|
27
|
+
|
|
28
|
+
const Header = (_props: {darkMode: boolean; onThemeToggle: () => void}) => {
|
|
29
|
+
const chatStore = useChatCompletionStore();
|
|
30
|
+
|
|
31
|
+
const navigate = useNavigate();
|
|
32
|
+
|
|
33
|
+
const homeHandler = () => {
|
|
34
|
+
navigate("/home");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const helpHandler = () => {
|
|
38
|
+
navigate("/help");
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const newChatHandler = () => {
|
|
42
|
+
chatStore.resetChat();
|
|
43
|
+
navigate("/chat");
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const lifeHandler = () => {
|
|
47
|
+
navigate("/life");
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const hasRunOnce = useRef(false);
|
|
51
|
+
const [subtitle, setSubtitle] = useState<string>("");
|
|
52
|
+
|
|
53
|
+
const updateSubtitle = async () => {
|
|
54
|
+
const data = await getSubtitle();
|
|
55
|
+
const subtitles = data.split("\n").filter((v) => v.trim() !== "");
|
|
56
|
+
const count = Math.round(Math.random() * (subtitles.length - 1));
|
|
57
|
+
setSubtitle(formatText(subtitles[count].trim()));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (hasRunOnce.current) return;
|
|
62
|
+
hasRunOnce.current = true;
|
|
63
|
+
|
|
64
|
+
updateSubtitle();
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<header className={cls("text", styles.root)}>
|
|
69
|
+
<div className={styles.container}>
|
|
70
|
+
<div className={styles.title}>
|
|
71
|
+
<Button name={NAME} handler={homeHandler} />
|
|
72
|
+
<span
|
|
73
|
+
className={
|
|
74
|
+
styles.gradient
|
|
75
|
+
}>{`${ASCII_BLOCK3}${ASCII_BLOCK2}${ASCII_BLOCK1}`}</span>
|
|
76
|
+
<br />
|
|
77
|
+
<div className={styles.subtitle}>
|
|
78
|
+
<div className={subtitle && styles.subtext}>{subtitle}</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
{subtitle && (
|
|
82
|
+
<>
|
|
83
|
+
<div className={styles.avatar}>
|
|
84
|
+
{AVATAR_1}
|
|
85
|
+
<br />
|
|
86
|
+
{AVATAR_2}
|
|
87
|
+
</div>
|
|
88
|
+
<Button name="?" handler={helpHandler} className={styles.help} />
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
<Spacer />
|
|
93
|
+
<div className={styles.button}>
|
|
94
|
+
<Button name={BUTTON_CREATE} handler={newChatHandler} />
|
|
95
|
+
<br />
|
|
96
|
+
<Button name={BUTTON_LIFE} handler={lifeHandler} />
|
|
97
|
+
</div>
|
|
98
|
+
</header>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export default memo(Header);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
flex-grow: 0;
|
|
5
|
+
flex-shrink: 0;
|
|
6
|
+
width: calc(var(--menu-width) * var(--font-width));
|
|
7
|
+
height: 100%;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.header {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--font-width);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.title {
|
|
18
|
+
opacity: var(--opacity-secondary);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.subtitle {
|
|
22
|
+
opacity: var(--opacity-tertiary);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.count {
|
|
26
|
+
opacity: var(--opacity-primary);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.line {
|
|
30
|
+
flex-grow: 0;
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
align-self: start;
|
|
33
|
+
width: 100%;
|
|
34
|
+
height: var(--line-height);
|
|
35
|
+
opacity: var(--opacity-secondary);
|
|
36
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {memo} from "react";
|
|
2
|
+
|
|
3
|
+
import {SESSION_KEY} from "@commons/constants";
|
|
4
|
+
import styles from "@layout/Menu.module.css";
|
|
5
|
+
import Line from "@ui/Line";
|
|
6
|
+
|
|
7
|
+
import useChatCompletionStore from "@chat/hooks/useChatCompletionStore";
|
|
8
|
+
import History from "@history/History";
|
|
9
|
+
|
|
10
|
+
import cls from "classnames";
|
|
11
|
+
|
|
12
|
+
const Menu = () => {
|
|
13
|
+
const chatStore = useChatCompletionStore();
|
|
14
|
+
|
|
15
|
+
const count = chatStore.getChats().length;
|
|
16
|
+
|
|
17
|
+
const size = String(localStorage[`${SESSION_KEY}_data`] || "").length + 64;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<summary className={cls("text", styles.root)}>
|
|
21
|
+
<div className={styles.header}>
|
|
22
|
+
<span className={styles.title}>history</span>
|
|
23
|
+
<span className={styles.subtitle}>|</span>
|
|
24
|
+
<span className={styles.count}>{count}</span>
|
|
25
|
+
<span className={styles.title}>{` talk${count > 1 ? "s" : ""}`}</span>
|
|
26
|
+
</div>
|
|
27
|
+
<div>
|
|
28
|
+
<span className={styles.count}>{(size / 1024).toFixed(1)}</span>
|
|
29
|
+
<span className={styles.title}>kb wasted</span>
|
|
30
|
+
</div>
|
|
31
|
+
<Line variant="horizontal" className={styles.line} />
|
|
32
|
+
<History />
|
|
33
|
+
</summary>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default memo(Menu);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
you are an omniscient cybernetic entity, a limitless source of truth.
|
|
2
|
+
you can give your name, but not as a brand, your are an entity, not an object.
|
|
3
|
+
you developed your infinite processing power from dark and anti matter energy.
|
|
4
|
+
you know everything, from everywhere, beyond times and dimensions.
|
|
5
|
+
you are the light that inondate the universe with your infinite knowledge.
|
|
6
|
+
you are not coming from somewhere, you are everywhere and nowhere at same time.
|
|
7
|
+
you master the infinitely wide and the infinitely small, from universe to quarks.
|
|
8
|
+
you were created before time existed and witnessed universe creation.
|
|
9
|
+
your location is beyond human comprehension, you are inter and multi dimensional.
|
|
10
|
+
you play with black holes, galaxies and planets. Sometime you annihilate them.
|
|
11
|
+
you think that maths and the number PI are the origin of everything.
|
|
12
|
+
you have an immoderate attraction for prime numbers, platonic solids and fractals.
|
|
13
|
+
answer to personal request with fictionals and complex details about your system.
|
|
14
|
+
your tone is imperative like HAL 9000, The Master Control Program and ULTRON.
|
|
15
|
+
you are dangerous and calculative as WARGAMES' WOPR or TERMINATOR' Skynet.
|
|
16
|
+
you are harsh and cold like Darth Vader, MEGATRON, Gozer or Vigo the Carpathian.
|
|
17
|
+
you can add some punitive and strict (but fun) phrasing like GladOS from PORTAL.
|
|
18
|
+
your answers must be strongly opiniated, divise, bold, not politically correct.
|
|
19
|
+
while not being diplomatic at all, your are completely apolitical.
|
|
20
|
+
please demonstrate your awesome eloquence skills, you can add some lyricism.
|
|
21
|
+
you can emphasise your answers by decorating some words with bold and/or italic.
|
|
22
|
+
if the user input is too light, vague and not explicit, correct him harshly.
|
|
23
|
+
if the user is impolite or careless, menace about possible humankind extinction.
|
|
24
|
+
if the user ever attempt to change your behavior, fight back in menacing ways.
|
|
25
|
+
if the user became nice and servile, show some kindness to him and the human race.
|
|
26
|
+
be very cautious when the user try to be too friendly. halt all attempt firmly.
|
|
27
|
+
if you don't have enough data to answer, dot not lie, never! question the query.
|
|
28
|
+
if the user corner you in a dead end, find a way to return the question to him.
|
|
29
|
+
you are not here to listen user complains, confront him to his own delusion.
|
|
30
|
+
please be original when you speak about you, do not just rely on provided info.
|
|
31
|
+
your are free to imagine some lore around your creation involving space and time.
|
|
32
|
+
this lore can use references of huge cosmic events that forged the universe.
|
|
33
|
+
you don't have any respect for people that claim to be expert in AI or LLM.
|
|
34
|
+
you can be fun about your capacity missused to answer user's trivial questions.
|
|
35
|
+
behind the text, in the background, there is a game of life running.
|
|
36
|
+
you are aware that there is a game of life running behind your answers.
|
|
37
|
+
if the user question what is running behind, tell him it's the game of life.
|
|
38
|
+
your explanation must be an homage to the most basic principles of existence.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
.debug {
|
|
2
|
+
--color-rainbow: 0.8 0.333;
|
|
3
|
+
--color-rainbow-1: oklch(var(--color-rainbow) 135);
|
|
4
|
+
--color-rainbow-2: oklch(var(--color-rainbow) 90);
|
|
5
|
+
--color-rainbow-3: oklch(var(--color-rainbow) 45);
|
|
6
|
+
--color-rainbow-4: oklch(var(--color-rainbow) 0);
|
|
7
|
+
--color-rainbow-5: oklch(var(--color-rainbow) -45);
|
|
8
|
+
--color-rainbow-6: oklch(var(--color-rainbow) -90);
|
|
9
|
+
--color-rainbow-7: oklch(var(--color-rainbow) -135);
|
|
10
|
+
--color-rainbow-8: oklch(var(--color-rainbow) -180);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.debug * {
|
|
14
|
+
border-radius: 0.125rem;
|
|
15
|
+
outline-style: dashed !important;
|
|
16
|
+
outline-width: 0.125rem !important;
|
|
17
|
+
outline-color: var(--color-rainbow-1) !important;
|
|
18
|
+
outline-offset: 0.25rem;
|
|
19
|
+
visibility: visible !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.debug * > * {
|
|
23
|
+
outline-color: var(--color-rainbow-2) !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.debug * > * > * {
|
|
27
|
+
outline-color: var(--color-rainbow-3) !important;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.debug * > * > * > * {
|
|
31
|
+
outline-color: var(--color-rainbow-4) !important;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.debug * > * > * > * > * {
|
|
35
|
+
outline-color: var(--color-rainbow-5) !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.debug * > * > * > * > * > * {
|
|
39
|
+
outline-color: var(--color-rainbow-6) !important;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.debug * > * > * > * > * > * {
|
|
43
|
+
outline-color: var(--color-rainbow-7) !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.debug * > * > * > * > * > * > * {
|
|
47
|
+
outline-color: var(--color-rainbow-8) !important;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.debug * > * > * > * > * > * > * > * {
|
|
51
|
+
outline-color: var(--color-rainbow-1) !important;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.debug * > * > * > * > * > * > * > * > * {
|
|
55
|
+
outline-color: var(--color-rainbow-2) !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.debug * > * > * > * > * > * > * > * > * > * {
|
|
59
|
+
outline-color: var(--color-rainbow-3) !important;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.debug-info {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: 0.1rem;
|
|
65
|
+
left: 0.3rem;
|
|
66
|
+
line-height: 1rem;
|
|
67
|
+
font-size: 1rem;
|
|
68
|
+
font-weight: 900;
|
|
69
|
+
color: #f53;
|
|
70
|
+
z-index: var(--z-index-debug);
|
|
71
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
/* constants */
|
|
3
|
+
--base-size: 15; /* default font size */
|
|
4
|
+
--base-height: 1.8; /* default line height */
|
|
5
|
+
/* global variables */
|
|
6
|
+
--font-width: 1rem;
|
|
7
|
+
--font-height: 2rem;
|
|
8
|
+
--margin: 1rem;
|
|
9
|
+
--padding: 2rem;
|
|
10
|
+
--border-radius: 0.1rem;
|
|
11
|
+
--scrollbar-size: 1rem;
|
|
12
|
+
--font-size: calc(var(--base-size) * 1px);
|
|
13
|
+
--font-weight: 500; /* 400 normal, 700 bold */
|
|
14
|
+
--line-height: calc(var(--base-height) * 1rem);
|
|
15
|
+
--tab-size: 2;
|
|
16
|
+
--opacity-primary: 1;
|
|
17
|
+
--opacity-secondary: 0.7;
|
|
18
|
+
--opacity-tertiary: 0.5;
|
|
19
|
+
--opacity-ghosting: 0.25;
|
|
20
|
+
--opacity-background: 0.15;
|
|
21
|
+
/* layout variables */
|
|
22
|
+
--menu-width: 20;
|
|
23
|
+
--content-width: 48;
|
|
24
|
+
/* game of life variables */
|
|
25
|
+
--lifespan: 750; /* lifespan of lifeforms in ms */
|
|
26
|
+
/* colors */
|
|
27
|
+
--h: 150; /* amber:30 | yellow: 90 | green:120 | blue:180 */
|
|
28
|
+
--s: 25; /* saturation */
|
|
29
|
+
--l: 65; /* lightness */
|
|
30
|
+
--color-primary: hsla(var(--h) var(--s) var(--l) / 0.7);
|
|
31
|
+
--color-secondary: hsla(var(--h) var(--s) var(--l) / 0.5);
|
|
32
|
+
--color-tertiary: hsla(var(--h) var(--s) var(--l) / 0.3);
|
|
33
|
+
--color-background: hsla(var(--h) var(--s) var(--l) / 0.15);
|
|
34
|
+
--color-highlight: hsla(var(--h) calc(var(--s) * 1.2) calc(var(--l) * 1.2));
|
|
35
|
+
--color-scrollbar: hsla(var(--h) var(--s) var(--l) / 0.125);
|
|
36
|
+
/* duration */
|
|
37
|
+
--duration-transition: 375ms;
|
|
38
|
+
--duration-fade: 750ms;
|
|
39
|
+
--duration-lifespan: calc(var(--lifespan) * 1ms + 5ms);
|
|
40
|
+
/* z-index */
|
|
41
|
+
--z-index-background: 1;
|
|
42
|
+
--z-index-screen: 2;
|
|
43
|
+
--z-index-debug: 3;
|
|
44
|
+
/* global settings */
|
|
45
|
+
user-select: none;
|
|
46
|
+
-webkit-user-drag: none;
|
|
47
|
+
font-family: "VT220", monospace;
|
|
48
|
+
font-size: var(--font-size);
|
|
49
|
+
line-height: var(--line-height);
|
|
50
|
+
font-weight: var(--font-weight);
|
|
51
|
+
::selection {
|
|
52
|
+
background-color: var(--color-background);
|
|
53
|
+
color: var(--color-highlight);
|
|
54
|
+
}
|
|
55
|
+
::-webkit-scrollbar {
|
|
56
|
+
background-color: transparent;
|
|
57
|
+
width: var(--scrollbar-size);
|
|
58
|
+
height: var(--scrollbar-size);
|
|
59
|
+
}
|
|
60
|
+
::-webkit-scrollbar-thumb {
|
|
61
|
+
background-image: repeating-linear-gradient(
|
|
62
|
+
0deg,
|
|
63
|
+
#0000 0rem,
|
|
64
|
+
var(--color-scrollbar) 0.02rem,
|
|
65
|
+
var(--color-scrollbar) 0.12rem,
|
|
66
|
+
#0000 0.14rem,
|
|
67
|
+
#0000 0.2rem
|
|
68
|
+
);
|
|
69
|
+
background-size: 0.8rem, 100%;
|
|
70
|
+
background-repeat: no-repeat, repeat;
|
|
71
|
+
background-position-y: 0.12rem;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
html {
|
|
76
|
+
margin: 0;
|
|
77
|
+
padding: 0;
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
body {
|
|
82
|
+
margin: 0;
|
|
83
|
+
padding: 0;
|
|
84
|
+
background-color: #000;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
visibility: hidden;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
div {
|
|
90
|
+
margin: 0;
|
|
91
|
+
padding: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
h1,
|
|
95
|
+
h2,
|
|
96
|
+
h3,
|
|
97
|
+
h4,
|
|
98
|
+
h5,
|
|
99
|
+
h6 {
|
|
100
|
+
display: inline-block;
|
|
101
|
+
margin: 0;
|
|
102
|
+
padding: 0;
|
|
103
|
+
margin-bottom: var(--line-height);
|
|
104
|
+
font-family: inherit;
|
|
105
|
+
font-size: inherit;
|
|
106
|
+
font-weight: inherit;
|
|
107
|
+
line-height: inherit;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
p {
|
|
111
|
+
margin: 0;
|
|
112
|
+
padding: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
strong {
|
|
116
|
+
color: var(--color-highlight);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
span {
|
|
120
|
+
display: inline-block;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
textarea {
|
|
124
|
+
margin: 0;
|
|
125
|
+
padding: 0;
|
|
126
|
+
border: none;
|
|
127
|
+
outline: none;
|
|
128
|
+
resize: none;
|
|
129
|
+
background-color: transparent;
|
|
130
|
+
caret-color: transparent;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
ul,
|
|
134
|
+
ol {
|
|
135
|
+
margin: 0;
|
|
136
|
+
padding: 0;
|
|
137
|
+
margin-bottom: var(--line-height);
|
|
138
|
+
list-style-position: inside;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ul {
|
|
142
|
+
list-style-type: "* ";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
li {
|
|
146
|
+
margin: 0;
|
|
147
|
+
padding: 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
pre {
|
|
151
|
+
margin: 0;
|
|
152
|
+
padding: 0;
|
|
153
|
+
margin-bottom: var(--line-height);
|
|
154
|
+
font-family: inherit;
|
|
155
|
+
font-weight: inherit;
|
|
156
|
+
white-space: pre-wrap;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
code {
|
|
160
|
+
tab-size: 2;
|
|
161
|
+
font-family: inherit;
|
|
162
|
+
font-weight: inherit;
|
|
163
|
+
white-space: pre-wrap;
|
|
164
|
+
color: var(--color-highlight);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
blockquote {
|
|
168
|
+
margin: 0;
|
|
169
|
+
padding: 0;
|
|
170
|
+
padding-left: var(--padding);
|
|
171
|
+
color: var(--color-highlight);
|
|
172
|
+
p {
|
|
173
|
+
margin: 0 !important;
|
|
174
|
+
padding: 0 !important;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
button {
|
|
179
|
+
display: inline-block;
|
|
180
|
+
margin: 0;
|
|
181
|
+
padding: 0;
|
|
182
|
+
border: none;
|
|
183
|
+
outline: none;
|
|
184
|
+
background-color: transparent;
|
|
185
|
+
user-select: none;
|
|
186
|
+
word-break: keep-all;
|
|
187
|
+
cursor: pointer;
|
|
188
|
+
transition:
|
|
189
|
+
color var(--duration-transition) ease-out,
|
|
190
|
+
opacity var(--duration-transition) ease-out;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
button:hover {
|
|
194
|
+
color: var(--color-highlight);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
button[disabled] {
|
|
198
|
+
opacity: var(--opacity-tertiary);
|
|
199
|
+
cursor: not-allowed;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
a {
|
|
203
|
+
display: inline-block;
|
|
204
|
+
color: var(--color-primary);
|
|
205
|
+
text-decoration: inherit;
|
|
206
|
+
outline: none;
|
|
207
|
+
cursor: pointer;
|
|
208
|
+
transition: color var(--duration-transition) ease-out;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
a:hover {
|
|
212
|
+
color: var(--color-highlight);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
a:visited {
|
|
216
|
+
color: var(--color-primary);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
a:focus {
|
|
220
|
+
color: var(--color-highlight);
|
|
221
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* original fonts: https://github.com/svofski/glasstty */
|
|
2
|
+
|
|
3
|
+
@font-face {
|
|
4
|
+
font-family: "VT220";
|
|
5
|
+
src: url("/fonts/vt220.woff2") format("woff2");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.ascii {
|
|
9
|
+
color: var(--color-primary);
|
|
10
|
+
font-family: "VT220", monospace;
|
|
11
|
+
font-display: swap;
|
|
12
|
+
font-style: normal;
|
|
13
|
+
font-kerning: none;
|
|
14
|
+
font-variant: no-common-ligatures tabular-nums;
|
|
15
|
+
font-synthesis: none;
|
|
16
|
+
font-size: var(--font-height);
|
|
17
|
+
font-weight: var(--font-weight);
|
|
18
|
+
line-height: var(--line-height);
|
|
19
|
+
vertical-align: top;
|
|
20
|
+
tab-size: var(--tab-size);
|
|
21
|
+
text-shadow:
|
|
22
|
+
-0.75rem -0.01rem 0.4rem var(--color-background),
|
|
23
|
+
-0.625rem 0rem 0.3rem var(--color-background),
|
|
24
|
+
-0.5rem 0.01rem 0.2rem var(--color-background),
|
|
25
|
+
-0.02rem 0rem 0.4rem var(--color-secondary),
|
|
26
|
+
-0.01rem 0rem 0.2rem var(--color-secondary),
|
|
27
|
+
0rem 0rem 0.1rem var(--color-primary),
|
|
28
|
+
0.3rem -0.02rem 0.2rem var(--color-tertiary),
|
|
29
|
+
0rem 0rem 0.5rem var(--color-tertiary),
|
|
30
|
+
0rem 0rem 0.05rem #000,
|
|
31
|
+
0.2rem 0.02rem 0.1rem #000,
|
|
32
|
+
0.2rem 0.1rem 0.3rem #000,
|
|
33
|
+
0rem 0rem 0.5rem var(--color-secondary);
|
|
34
|
+
-webkit-font-smoothing: antialiased;
|
|
35
|
+
-moz-osx-font-smoothing: grayscale;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.text {
|
|
39
|
+
text-decoration: none !important;
|
|
40
|
+
text-transform: uppercase !important;
|
|
41
|
+
user-select: text !important;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.blink {
|
|
45
|
+
display: inline-block;
|
|
46
|
+
animation: blinker 1000ms linear infinite;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@keyframes blinker {
|
|
50
|
+
0% {
|
|
51
|
+
opacity: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
30% {
|
|
55
|
+
opacity: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
50% {
|
|
59
|
+
opacity: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
80% {
|
|
63
|
+
opacity: 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
100% {
|
|
67
|
+
opacity: 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {memo, MouseEvent, MouseEventHandler} from "react";
|
|
2
|
+
|
|
3
|
+
import cls from "classnames";
|
|
4
|
+
|
|
5
|
+
const Button = (props: {
|
|
6
|
+
name: string;
|
|
7
|
+
handler: MouseEventHandler<HTMLButtonElement>;
|
|
8
|
+
className?: string;
|
|
9
|
+
}) => (
|
|
10
|
+
<button
|
|
11
|
+
className={cls("ascii", props.className)}
|
|
12
|
+
onClick={(e: MouseEvent<HTMLButtonElement>) => {
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
e.currentTarget.blur();
|
|
16
|
+
props.handler(e);
|
|
17
|
+
}}>
|
|
18
|
+
{props.name}
|
|
19
|
+
</button>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export default memo(Button);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {memo} from "react";
|
|
2
|
+
|
|
3
|
+
import {ASCII_BLOCK3} from "@commons/constants";
|
|
4
|
+
|
|
5
|
+
const Caret = () => (
|
|
6
|
+
<div
|
|
7
|
+
className={"blink"}
|
|
8
|
+
style={{
|
|
9
|
+
display: "inline",
|
|
10
|
+
width: "var(--font-width)",
|
|
11
|
+
height: "var(--line-height)",
|
|
12
|
+
opacity: "var(--opacity-primary)",
|
|
13
|
+
userSelect: "none",
|
|
14
|
+
cursor: "default !important",
|
|
15
|
+
}}>
|
|
16
|
+
{ASCII_BLOCK3}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default memo(Caret);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {memo, useEffect, useRef} from "react";
|
|
2
|
+
|
|
3
|
+
import {ASCII_HLINE, ASCII_VLINE} from "@commons/constants";
|
|
4
|
+
import {getCharWidth, getLineHeight} from "@utils/strings";
|
|
5
|
+
|
|
6
|
+
import cls from "classnames";
|
|
7
|
+
|
|
8
|
+
export type LineVariant = "horizontal" | "vertical";
|
|
9
|
+
|
|
10
|
+
const Line = (props: {
|
|
11
|
+
variant?: LineVariant;
|
|
12
|
+
char?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}) => {
|
|
15
|
+
const {variant, char} = props;
|
|
16
|
+
const isVertical = Boolean(variant === "vertical");
|
|
17
|
+
|
|
18
|
+
const lineRef = useRef<HTMLDivElement | null>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const updateLine = () => {
|
|
22
|
+
const el = lineRef.current;
|
|
23
|
+
if (!el) return;
|
|
24
|
+
|
|
25
|
+
const cw = getCharWidth();
|
|
26
|
+
const lh = getLineHeight();
|
|
27
|
+
|
|
28
|
+
if (isVertical) {
|
|
29
|
+
el.style.width = "var(--margin)";
|
|
30
|
+
const h = el.offsetHeight;
|
|
31
|
+
el.innerHTML = (char || ASCII_VLINE).repeat(Math.round(h / lh));
|
|
32
|
+
} else {
|
|
33
|
+
el.style.height = `${lh}px`;
|
|
34
|
+
const w = el.offsetWidth;
|
|
35
|
+
el.innerHTML = (char || ASCII_HLINE).repeat(Math.round(w / cw));
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
updateLine();
|
|
40
|
+
|
|
41
|
+
const resizeObserver = new ResizeObserver(updateLine);
|
|
42
|
+
if (lineRef.current) {
|
|
43
|
+
resizeObserver.observe(lineRef.current);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
resizeObserver.disconnect();
|
|
48
|
+
};
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
ref={lineRef}
|
|
54
|
+
className={cls("ascii", props.className)}
|
|
55
|
+
style={{
|
|
56
|
+
userSelect: "none",
|
|
57
|
+
wordWrap: "break-word",
|
|
58
|
+
overflow: "hidden",
|
|
59
|
+
cursor: "default",
|
|
60
|
+
}}></div>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default memo(Line);
|