├── res
├── logo.png
├── banner.png
└── secret.png
├── src
├── css
│ ├── font
│ │ └── IBMPlexMono-Thin.ttf
│ ├── css-reset.css
│ └── style.css
├── commands
│ ├── default.ts
│ ├── projects.ts
│ ├── banner.ts
│ ├── whoami.ts
│ ├── help.ts
│ └── about.ts
├── styles.ts
└── main.ts
├── .gitignore
├── package.json
├── tsconfig.json
├── LICENSE
├── config.json
├── index.html
└── README.md
/res/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nasan016/webshell/HEAD/res/logo.png
--------------------------------------------------------------------------------
/res/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nasan016/webshell/HEAD/res/banner.png
--------------------------------------------------------------------------------
/res/secret.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nasan016/webshell/HEAD/res/secret.png
--------------------------------------------------------------------------------
/src/css/font/IBMPlexMono-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nasan016/webshell/HEAD/src/css/font/IBMPlexMono-Thin.ttf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "terminal",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "@types/js-yaml": "^4.0.9",
13 | "typescript": "^5.2.2",
14 | "vite": "^5.0.0"
15 | },
16 | "dependencies": {
17 | "js-yaml": "^4.1.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/commands/default.ts:
--------------------------------------------------------------------------------
1 | const createDefault = () : string[] => {
2 | const defaultMsgArr = [
3 | "
",
4 | "COMMAND NOT FOUND",
5 | "Type 'help' to get started.",
6 | "
"
7 | ]
8 |
9 | const defaultMsg : string[] = [];
10 |
11 | defaultMsgArr.forEach((ele) => {
12 | defaultMsg.push(ele);
13 | })
14 |
15 | return defaultMsg;
16 | }
17 |
18 | export const DEFAULT = createDefault();
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "module": "ESNext",
6 | "lib": [
7 | "ES2020",
8 | "DOM",
9 | "DOM.Iterable"
10 | ],
11 | "skipLibCheck": true,
12 | /* Bundler mode */
13 | "moduleResolution": "bundler",
14 | "allowImportingTsExtensions": true,
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "noEmit": true,
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true
23 | },
24 | "include": [
25 | "src"
26 | ]
27 | }
--------------------------------------------------------------------------------
/src/commands/projects.ts:
--------------------------------------------------------------------------------
1 | import command from '../../config.json' assert {type: 'json'};
2 |
3 | const createProject = () : string[] => {
4 | let string = "";
5 | const projects : string[] = [];
6 | const files = `${command.projects.length} File(s)`;
7 | const SPACE = " ";
8 |
9 | projects.push("
")
10 |
11 | command.projects.forEach((ele) => {
12 | let link = `${ele[0]}`
13 | string += SPACE.repeat(2);
14 | string += link;
15 | string += SPACE.repeat(17 - ele[0].length);
16 | string += ele[1];
17 | projects.push(string);
18 | string = '';
19 | });
20 |
21 | projects.push("
");
22 | projects.push(files);
23 | projects.push("
");
24 | return projects
25 | }
26 |
27 | export const PROJECTS = createProject()
28 |
--------------------------------------------------------------------------------
/src/commands/banner.ts:
--------------------------------------------------------------------------------
1 | import command from '../../config.json' assert {type: 'json'};
2 |
3 | const createBanner = () : string[] => {
4 | const banner : string[] = [];
5 | banner.push("
")
6 | command.ascii.forEach((ele) => {
7 | let bannerString = "";
8 | //this is for the ascii art
9 | for (let i = 0; i < ele.length; i++) {
10 | if (ele[i] === " ") {
11 | bannerString += " ";
12 | } else {
13 | bannerString += ele[i];
14 | }
15 | }
16 |
17 | let eleToPush = `
${bannerString}`;
18 | banner.push(eleToPush);
19 | });
20 | banner.push("
");
21 | banner.push("Welcome to WebShell v1.0.0");
22 | banner.push("Type 'help' for a list of all available commands.");
23 | banner.push(`Type 'repo' to view the GitHub repository or click here.`);
24 | banner.push("
");
25 | return banner;
26 | }
27 |
28 | export const BANNER = createBanner();
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Nathaniel Macapinlac
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/commands/whoami.ts:
--------------------------------------------------------------------------------
1 | const whoamiObj = {
2 | "message" : [
3 | [
4 | "In the kaleidoscope of existence,",
5 | "I am but a reflection questioning the enigma - "
6 | ],
7 | [
8 | "Amidst cosmic whispers,",
9 | "I navigate the maze of self-discovery,",
10 | "echoing the eternal refrain - "
11 | ],
12 | [
13 | "In the symphony of life,",
14 | "I am a note inquiring its own melody,",
15 | "harmonizing with the universal query - ",
16 | ],
17 | [
18 | "As stardust contemplating its journey,",
19 | "I ponder the cosmic query,",
20 | "silently asking - ",
21 | ],
22 | [
23 | "In the tapestry of reality,",
24 | "I am the thread of self-inquiry,",
25 | "weaving through the eternal question - "
26 | ],
27 | ],
28 | }
29 |
30 | export const createWhoami = () : string[] => {
31 | const whoami : string[] = [];
32 | const r = Math.floor(Math.random() * whoamiObj.message.length);
33 | whoami.push("
");
34 |
35 | whoamiObj.message[r].forEach((ele, idx) => {
36 | if (idx === whoamiObj.message[r].length - 1) {
37 | ele += "who am I?";
38 | }
39 | whoami.push(ele);
40 | });
41 |
42 | whoami.push("
");
43 |
44 | return whoami
45 | }
46 |
--------------------------------------------------------------------------------
/src/commands/help.ts:
--------------------------------------------------------------------------------
1 | const helpObj = {
2 | "commands": [
3 | [
4 | "'about'",
5 | "Who made this website?",
6 | ],
7 | [
8 | "'projects'",
9 | "Maybe there's something interesting."
10 | ],
11 | [
12 | "'whoami'",
13 | "A perplexing question."
14 | ],
15 | ["'sudo'",
16 | "???"
17 | ],
18 | [
19 | "'repo'",
20 | "View the Github Repository."
21 | ],
22 | ["'banner'",
23 | "Display the banner."
24 | ],
25 | [
26 | "'clear'",
27 | "Clear the terminal."
28 | ]
29 | ],
30 | }
31 |
32 | const createHelp = () : string[] => {
33 | const help : string[] = []
34 | help.push("
")
35 |
36 | helpObj.commands.forEach((ele) => {
37 | const SPACE = " ";
38 | let string = "";
39 | string += SPACE.repeat(2);
40 | string += "";
41 | string += ele[0];
42 | string += "";
43 | string += SPACE.repeat(17 - ele[0].length);
44 | string += ele[1];
45 | help.push(string);
46 | })
47 |
48 | help.push("
");
49 | help.push("Press [Tab] for auto completion.");
50 | help.push("Press [Esc] to clear the input line.");
51 | help.push("Press [↑][↓] to scroll through your history of commands.");
52 | help.push("
");
53 | return help
54 | }
55 |
56 | export const HELP = createHelp();
57 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "ascii": [
3 | "███████╗██╗ ██╗███████╗██╗ ██╗",
4 | "██╔════╝██║ ██║██╔════╝██║ ██║",
5 | "███████╗███████║█████╗ ██║ ██║",
6 | "╚════██║██╔══██║██╔══╝ ██║ ██║",
7 | "███████║██║ ██║███████╗███████╗███████╗",
8 | "╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝"
9 | ],
10 | "title": "WebShell",
11 | "username": "visitor",
12 | "hostname": "webshell",
13 | "password": "050823",
14 | "repoLink": "https://github.com/nasan016/webshell",
15 | "social": {
16 | "email": "your@email.com",
17 | "github": "yourgithub",
18 | "linkedin": "you"
19 | },
20 | "aboutGreeting": "Hi stranger. Welcome to WebShell.",
21 | "projects": [
22 | [
23 | "GofeR",
24 | "Vue.js reactivity in Go.",
25 | "https://github.com/nasan016/gofer"
26 | ],
27 | [
28 | "WebShell",
29 | "Terminal styled website.",
30 | "https://github.com/nasan016/webshell"
31 | ]
32 | ],
33 | "colors": {
34 | "background": "#0C0623",
35 | "foreground": "#F8DDE5",
36 | "banner": "#FF9951",
37 | "border": {
38 | "visible": true,
39 | "color": "#FFADE2"
40 | },
41 | "prompt": {
42 | "default": "#A5A7A7",
43 | "user": "#FE6BC9",
44 | "host": "#70FDFF",
45 | "input": "#FF7685"
46 | },
47 | "link": {
48 | "text": "#B6AAEE",
49 | "highlightColor": "#FFADE2",
50 | "highlightText": "#0C0623"
51 | },
52 | "commands": {
53 | "textColor": "#FD9BDB"
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/commands/about.ts:
--------------------------------------------------------------------------------
1 | import command from '../../config.json' assert {type: 'json'};
2 |
3 | const createAbout = () : string[] => {
4 | const about : string[] = [];
5 |
6 | const SPACE = " ";
7 |
8 | const EMAIL = "Email";
9 | const GITHUB = "Github";
10 | const LINKEDIN = "Linkedin";
11 |
12 | const email = ` ${EMAIL}`;
13 | const github = ` ${GITHUB}`;
14 | const linkedin = ` ${LINKEDIN}`;
15 | let string = "";
16 |
17 | about.push("
");
18 | about.push(command.aboutGreeting);
19 | about.push("
");
20 | string += SPACE.repeat(2);
21 | string += email;
22 | string += SPACE.repeat(17 - EMAIL.length);
23 | string += `${command.social.email}`;
24 | about.push(string);
25 |
26 | string = '';
27 | string += SPACE.repeat(2);
28 | string += github;
29 | string += SPACE.repeat(17 - GITHUB.length);
30 | string += `github/${command.social.github}`;
31 | about.push(string);
32 |
33 | string = '';
34 | string += SPACE.repeat(2);
35 | string += linkedin;
36 | string += SPACE.repeat(17 - LINKEDIN.length);
37 | string += `linkedin/${command.social.linkedin}`;
38 | about.push(string);
39 |
40 | about.push("
");
41 | return about
42 | }
43 |
44 | export const ABOUT = createAbout();
45 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | WebShell
12 |
13 |
14 |
15 |
16 |
17 |
WebShell.x64_x86
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | @:$ ~
27 |
28 |
29 |
30 |
31 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/css/css-reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 | html,
6 | body,
7 | div,
8 | span,
9 | applet,
10 | object,
11 | iframe,
12 | h1,
13 | h2,
14 | h3,
15 | h4,
16 | h5,
17 | h6,
18 | p,
19 | blockquote,
20 | pre,
21 | a,
22 | abbr,
23 | acronym,
24 | address,
25 | big,
26 | cite,
27 | code,
28 | del,
29 | dfn,
30 | em,
31 | img,
32 | ins,
33 | kbd,
34 | q,
35 | s,
36 | samp,
37 | small,
38 | strike,
39 | strong,
40 | sub,
41 | sup,
42 | tt,
43 | var,
44 | b,
45 | u,
46 | i,
47 | center,
48 | dl,
49 | dt,
50 | dd,
51 | ol,
52 | ul,
53 | li,
54 | fieldset,
55 | form,
56 | label,
57 | legend,
58 | table,
59 | caption,
60 | tbody,
61 | tfoot,
62 | thead,
63 | tr,
64 | th,
65 | td,
66 | article,
67 | aside,
68 | canvas,
69 | details,
70 | embed,
71 | figure,
72 | figcaption,
73 | footer,
74 | header,
75 | hgroup,
76 | menu,
77 | nav,
78 | output,
79 | ruby,
80 | section,
81 | summary,
82 | time,
83 | mark,
84 | audio,
85 | video {
86 | margin: 0;
87 | padding: 0;
88 | border: 0;
89 | font-size: 100%;
90 | font: inherit;
91 | vertical-align: baseline;
92 | }
93 |
94 | /* HTML5 display-role reset for older browsers */
95 | article,
96 | aside,
97 | details,
98 | figcaption,
99 | figure,
100 | footer,
101 | header,
102 | hgroup,
103 | menu,
104 | nav,
105 | section {
106 | display: block;
107 | }
108 |
109 | body {
110 | line-height: 1;
111 | }
112 |
113 | ol,
114 | ul {
115 | list-style: none;
116 | }
117 |
118 | blockquote,
119 | q {
120 | quotes: none;
121 | }
122 |
123 | blockquote:before,
124 | blockquote:after,
125 | q:before,
126 | q:after {
127 | content: '';
128 | content: none;
129 | }
130 |
131 | table {
132 | border-collapse: collapse;
133 | border-spacing: 0;
134 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [WebShell | Terminal Portfolio Website](https://webshellx.vercel.app/)
2 |
3 |
4 |

5 |
6 |
7 | 
8 | 
9 | 
10 | 
11 | 
12 |
13 | Create your own terminal styled website! Check out [term.nasan.dev](https://term.nasan.dev/) for an example.
14 |
15 | ## Features
16 | * **[Tab]** for auto completion.
17 | * **[Esc]** to clear the input line.
18 | * **[↑][↓]** to scroll through your command history.
19 |
20 | ## ???
21 |
22 |

23 |
24 | How did we get here?
25 |
26 | ## Configuration
27 |
28 | Most of the configuration is done in the `config.json` file.
29 |
30 | ```json
31 | {
32 | "ascii": [
33 | "██████╗ ██╗ ██╗ ██████╗",
34 | "██╔══██╗██║ ██║██╔════╝",
35 | "██║ ██║██║ ██║██║ ███╗",
36 | "██║ ██║██║ ██║██║ ██║",
37 | "██████╔╝╚██████╔╝╚██████╔╝",
38 | "╚═════╝ ╚═════╝ ╚═════╝",
39 | ],
40 | "title": "Dug's Terminal",
41 | "username": "guest",
42 | "hostname": "dug.dev",
43 | "password": "squirrel",
44 | "repoLink": "https://github.com/nasan016/webshell",
45 | "social": {
46 | "email": "dug@pixar.com",
47 | "github": "dugfromup",
48 | "linkedin": "dugthedog"
49 | },
50 | "aboutGreeting": "My name is Dug. I have just met you.",
51 | "projects": [
52 | [
53 | "Project Name",
54 | "Project Description",
55 | "Project Link"
56 | ],
57 | [
58 | "Another Project Name",
59 | "Another Project Description",
60 | "Another Project Link"
61 | ]
62 | ],
63 | "colors": {
64 | ...
65 | }
66 | }
67 | ```
68 |
69 | ## Run the Project Locally:
70 |
71 | Clone the repository
72 | ```shell
73 | git clone https://github.com/nasan016/webshell.git
74 | ```
75 | Go to the project directory
76 | ```shell
77 | cd webshell
78 | ```
79 | Install the dependencies
80 | ```shell
81 | npm install
82 | ```
83 | Start the server
84 | ```shell
85 | npm run dev
86 | ```
87 |
--------------------------------------------------------------------------------
/src/styles.ts:
--------------------------------------------------------------------------------
1 | import command from '../config.json' assert {type: 'json'};
2 |
3 | (() => {
4 | const style = document.createElement('style')
5 | const head = document.head
6 | const background = `body {background: ${command.colors.background}}`
7 | const foreground = `body {color: ${command.colors.foreground}}`
8 | const inputBackground = `input {background: ${command.colors.background}}`
9 | const inputForeground = `input {color: ${command.colors.prompt.input}}`
10 | const outputColor = `.output {color: ${command.colors.prompt.input}}`
11 | const preHost = `#pre-host {color: ${command.colors.prompt.host}}`
12 | const host = `#host {color: ${command.colors.prompt.host}}`
13 | const preUser = `#pre-user {color: ${command.colors.prompt.user}}`
14 | const user = `#user {color: ${command.colors.prompt.user}}`
15 | const prompt = `#prompt {color: ${command.colors.prompt.default}}`
16 | const banner = `pre {color: ${command.colors.banner}}`
17 | const link = `a {color: ${command.colors.link.text}}`
18 | const linkHighlight = `a:hover {background: ${command.colors.link.highlightColor}}`
19 | const linkTextHighlight = `a:hover {color: ${command.colors.link.highlightText}}`
20 | const commandHighlight = `.command {color: ${command.colors.commands.textColor}}`
21 | const keys = `.keys {color: ${command.colors.banner}}`
22 |
23 | head.appendChild(style)
24 |
25 |
26 | if (!style.sheet) return
27 |
28 | if (!command.colors.border.visible) {
29 | style.sheet.insertRule("#bars {display: none}")
30 | style.sheet.insertRule("main {border: none}")
31 | } else {
32 | style.sheet.insertRule(`#bars {background: ${command.colors.background}}`)
33 | style.sheet.insertRule(`main {border-color: ${command.colors.border.color}}`)
34 | style.sheet.insertRule(`#bar-1 {background: ${command.colors.border.color}; color: ${command.colors.background}}`)
35 | style.sheet.insertRule(`#bar-2 {background: ${command.colors.border.color}}`)
36 | style.sheet.insertRule(`#bar-3 {background: ${command.colors.border.color}}`)
37 | style.sheet.insertRule(`#bar-4 {background: ${command.colors.border.color}}`)
38 | style.sheet.insertRule(`#bar-5 {background: ${command.colors.border.color}}`)
39 | }
40 |
41 | style.sheet.insertRule(background)
42 | style.sheet.insertRule(foreground)
43 | style.sheet.insertRule(inputBackground)
44 | style.sheet.insertRule(inputForeground)
45 | style.sheet.insertRule(outputColor)
46 | style.sheet.insertRule(preHost)
47 | style.sheet.insertRule(host)
48 | style.sheet.insertRule(preUser)
49 | style.sheet.insertRule(user)
50 | style.sheet.insertRule(prompt)
51 | style.sheet.insertRule(banner)
52 | style.sheet.insertRule(link)
53 | style.sheet.insertRule(linkHighlight)
54 | style.sheet.insertRule(linkTextHighlight)
55 | style.sheet.insertRule(commandHighlight)
56 | style.sheet.insertRule(keys)
57 | })()
58 |
--------------------------------------------------------------------------------
/src/css/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@200;300;400;600&family=Pixelify+Sans&family=VT323&display=swap');
2 |
3 | :root {
4 | font-family: 'IBM Plex Mono', monospace;
5 | font-weight: 200;
6 | --bg: #0C0623;
7 | --border: #FFADE2;
8 | --text: #F8DDE5;
9 | --prompt-default: #A5A7A7;
10 | --prompt-1: #FE6BC9;
11 | --prompt-2: #70FDFF;
12 | }
13 |
14 | @keyframes typing {
15 | from {
16 | width: 0
17 | }
18 |
19 | to {
20 | width: 100%
21 | }
22 | }
23 |
24 | html,
25 | body {
26 | color: var(--text);
27 | background-color: var(--bg);
28 | height: 100%;
29 | font-size: 16px;
30 | display: block;
31 | }
32 |
33 | html {
34 | overflow: auto;
35 | }
36 |
37 | body {
38 | padding: 16px;
39 | box-sizing: border-box;
40 | }
41 |
42 | main {
43 | display: block;
44 | box-sizing: border-box;
45 | height: 100%;
46 | border: var(--border) solid 2px;
47 | border-radius: 2px;
48 | overflow-y: auto;
49 | overflow-x: hidden;
50 | scrollbar-width: none;
51 | -ms-overflow-style: none;
52 | }
53 |
54 | main::-webkit-scrollbar {
55 | display: none;
56 | }
57 |
58 | p {
59 | display: block;
60 | line-height: 22px;
61 | animation: typing 0.5s steps(30, end);
62 | white-space: nowrap;
63 | overflow: hidden;
64 | }
65 |
66 | div {
67 | line-height: 22px;
68 | }
69 |
70 | @font-face {
71 | font-family: ascii;
72 | src: url("./font/IBMPlexMono-Thin.ttf")
73 | }
74 |
75 | pre {
76 | margin: 0;
77 | padding: 0;
78 | line-height: 20px !important;
79 | color: #FF9951;
80 | font-family: "ascii", monospace;
81 | }
82 |
83 | input {
84 | font-family: 'IBM Plex Mono', monospace;
85 | padding: 0px;
86 | margin: 0px;
87 | border: none;
88 | resize: none;
89 | outline: none;
90 | font-size: 16px;
91 | color: #FF7685;
92 | caret-color: var(--prompt-default);
93 | width: 50%;
94 | }
95 |
96 | a {
97 | color: #B6AAEE;
98 | }
99 |
100 | a:hover {
101 | background-color: var(--border);
102 | color: var(--bg);
103 | }
104 |
105 | #bars {
106 | font-family: 'Pixelify Sans', sans-serif;
107 | font-size: 20px;
108 | position: -webkit-sticky;
109 | position: sticky;
110 | width: 100%;
111 | top: 0;
112 | background-color: var(--bg);
113 | }
114 |
115 | #bar-1 {
116 | height: 36px;
117 | background-color: var(--border);
118 | color: var(--bg);
119 | line-height: 36px;
120 | padding-left: 10px;
121 | }
122 |
123 | #bar-2 {
124 | height: 4px;
125 | background-color: var(--border);
126 | margin-top: 1px;
127 | }
128 |
129 | #bar-3 {
130 | height: 3px;
131 | background-color: var(--border);
132 | margin-top: 2px;
133 | }
134 |
135 | #bar-4 {
136 | height: 2px;
137 | background-color: var(--border);
138 | margin-top: 3px;
139 | }
140 |
141 |
142 | #bar-5 {
143 | height: 1px;
144 | background-color: var(--border);
145 | margin-top: 4px;
146 | }
147 |
148 | #terminal {
149 | margin-left: 20px;
150 | }
151 |
152 | #input-line {
153 | margin-left: 20px;
154 | overflow-x: hidden;
155 | width: 100%;
156 | }
157 |
158 | .command {
159 | text-shadow:
160 | 0 0 7px #fff,
161 | 0 0 151px var(--border);
162 | color: #FD9BDB;
163 | }
164 |
165 | .output {
166 | font-weight: 400 !important;
167 | }
168 |
169 | .keys {
170 | color: #FF9951;
171 | font-weight: 400;
172 | }
173 |
174 | @media (max-width: 600px) {
175 | body {
176 | font-size: 10px;
177 | padding: 2px;
178 | font-weight: 300;
179 | }
180 |
181 | input {
182 | font-size: 10px;
183 | }
184 |
185 | p {
186 | line-height: 14px;
187 | }
188 |
189 | pre {
190 | line-height: 12px !important;
191 | font-size: 9px;
192 | }
193 |
194 | main {
195 | border-width: 1px;
196 | }
197 |
198 | div {
199 | line-height: 14px;
200 | }
201 |
202 | #terminal {
203 | margin-left: 8px;
204 | }
205 |
206 | #input-line {
207 | margin-left: 8px;
208 | }
209 | }
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import command from '../config.json' assert {type: 'json'};
2 | import { HELP } from "./commands/help";
3 | import { BANNER } from "./commands/banner";
4 | import { ABOUT } from "./commands/about"
5 | import { DEFAULT } from "./commands/default";
6 | import { PROJECTS } from "./commands/projects";
7 | import { createWhoami } from "./commands/whoami";
8 |
9 | //mutWriteLines gets deleted and reassigned
10 | let mutWriteLines = document.getElementById("write-lines");
11 | let historyIdx = 0
12 | let tempInput = ""
13 | let userInput : string;
14 | let isSudo = false;
15 | let isPasswordInput = false;
16 | let passwordCounter = 0;
17 | let bareMode = false;
18 |
19 | //WRITELINESCOPY is used to during the "clear" command
20 | const WRITELINESCOPY = mutWriteLines;
21 | const TERMINAL = document.getElementById("terminal");
22 | const USERINPUT = document.getElementById("user-input") as HTMLInputElement;
23 | const INPUT_HIDDEN = document.getElementById("input-hidden");
24 | const PASSWORD = document.getElementById("password-input");
25 | const PASSWORD_INPUT = document.getElementById("password-field") as HTMLInputElement;
26 | const PRE_HOST = document.getElementById("pre-host");
27 | const PRE_USER = document.getElementById("pre-user");
28 | const HOST = document.getElementById("host");
29 | const USER = document.getElementById("user");
30 | const PROMPT = document.getElementById("prompt");
31 | const COMMANDS = ["help", "about", "projects", "whoami", "repo", "banner", "clear"];
32 | const HISTORY : string[] = [];
33 | const SUDO_PASSWORD = command.password;
34 | const REPO_LINK = command.repoLink;
35 |
36 | const scrollToBottom = () => {
37 | const MAIN = document.getElementById("main");
38 | if(!MAIN) return
39 |
40 | MAIN.scrollTop = MAIN.scrollHeight;
41 | }
42 |
43 | function userInputHandler(e : KeyboardEvent) {
44 | const key = e.key;
45 |
46 | switch(key) {
47 | case "Enter":
48 | e.preventDefault();
49 | if (!isPasswordInput) {
50 | enterKey();
51 | } else {
52 | passwordHandler();
53 | }
54 |
55 | scrollToBottom();
56 | break;
57 | case "Escape":
58 | USERINPUT.value = "";
59 | break;
60 | case "ArrowUp":
61 | arrowKeys(key);
62 | e.preventDefault();
63 | break;
64 | case "ArrowDown":
65 | arrowKeys(key);
66 | break;
67 | case "Tab":
68 | tabKey();
69 | e.preventDefault();
70 | break;
71 | }
72 | }
73 |
74 | function enterKey() {
75 | if (!mutWriteLines || !PROMPT) return
76 | const resetInput = "";
77 | let newUserInput;
78 | userInput = USERINPUT.value;
79 |
80 | if (bareMode) {
81 | newUserInput = userInput;
82 | } else {
83 | newUserInput = `${userInput}`;
84 | }
85 |
86 | HISTORY.push(userInput);
87 | historyIdx = HISTORY.length
88 |
89 | //if clear then early return
90 | if (userInput === 'clear') {
91 | commandHandler(userInput.toLowerCase().trim());
92 | USERINPUT.value = resetInput;
93 | userInput = resetInput;
94 | return
95 | }
96 |
97 | const div = document.createElement("div");
98 | div.innerHTML = `${PROMPT.innerHTML} ${newUserInput}`;
99 |
100 | if (mutWriteLines.parentNode) {
101 | mutWriteLines.parentNode.insertBefore(div, mutWriteLines);
102 | }
103 |
104 | /*
105 | if input is empty or a collection of spaces,
106 | just insert a prompt before #write-lines
107 | */
108 | if (userInput.trim().length !== 0) {
109 | commandHandler(userInput.toLowerCase().trim());
110 | }
111 |
112 | USERINPUT.value = resetInput;
113 | userInput = resetInput;
114 | }
115 |
116 | function tabKey() {
117 | let currInput = USERINPUT.value;
118 |
119 | for (const ele of COMMANDS) {
120 | if(ele.startsWith(currInput)) {
121 | USERINPUT.value = ele;
122 | return
123 | }
124 | }
125 | }
126 |
127 | function arrowKeys(e : string) {
128 | switch(e){
129 | case "ArrowDown":
130 | if (historyIdx !== HISTORY.length) {
131 | historyIdx += 1;
132 | USERINPUT.value = HISTORY[historyIdx];
133 | if (historyIdx === HISTORY.length) USERINPUT.value = tempInput;
134 | }
135 | break;
136 | case "ArrowUp":
137 | if (historyIdx === HISTORY.length) tempInput = USERINPUT.value;
138 | if (historyIdx !== 0) {
139 | historyIdx -= 1;
140 | USERINPUT.value = HISTORY[historyIdx];
141 | }
142 | break;
143 | }
144 | }
145 |
146 | function commandHandler(input : string) {
147 | if(input.startsWith("rm -rf") && input.trim() !== "rm -rf") {
148 | if (isSudo) {
149 | if(input === "rm -rf src" && !bareMode) {
150 | bareMode = true;
151 |
152 | setTimeout(() => {
153 | if(!TERMINAL || !WRITELINESCOPY) return
154 | TERMINAL.innerHTML = "";
155 | TERMINAL.appendChild(WRITELINESCOPY);
156 | mutWriteLines = WRITELINESCOPY;
157 | });
158 |
159 | easterEggStyles();
160 | setTimeout(() => {
161 | writeLines(["What made you think that was a good idea?", "
"]);
162 | }, 200)
163 |
164 | setTimeout(() => {
165 | writeLines(["Now everything is ruined.", "
"]);
166 | }, 1200)
167 |
168 | } else if (input === "rm -rf src" && bareMode) {
169 | writeLines(["there's no more src folder.", "
"])
170 | } else {
171 | if(bareMode) {
172 | writeLines(["What else are you trying to delete?", "
"])
173 | } else {
174 | writeLines(["
", "Directory not found.", "type 'ls' for a list of directories.", "
"]);
175 | }
176 | }
177 | } else {
178 | writeLines(["Permission not granted.", "
"]);
179 | }
180 | return
181 | }
182 |
183 | switch(input) {
184 | case 'clear':
185 | setTimeout(() => {
186 | if(!TERMINAL || !WRITELINESCOPY) return
187 | TERMINAL.innerHTML = "";
188 | TERMINAL.appendChild(WRITELINESCOPY);
189 | mutWriteLines = WRITELINESCOPY;
190 | })
191 | break;
192 | case 'banner':
193 | if(bareMode) {
194 | writeLines(["WebShell v1.0.0", "
"])
195 | break;
196 | }
197 | writeLines(BANNER);
198 | break;
199 | case 'help':
200 | if(bareMode) {
201 | writeLines(["maybe restarting your browser will fix this.", "
"])
202 | break;
203 | }
204 | writeLines(HELP);
205 | break;
206 | case 'whoami':
207 | if(bareMode) {
208 | writeLines([`${command.username}`, "
"])
209 | break;
210 | }
211 | writeLines(createWhoami());
212 | break;
213 | case 'about':
214 | if(bareMode) {
215 | writeLines(["Nothing to see here.", "
"])
216 | break;
217 | }
218 | writeLines(ABOUT);
219 | break;
220 | case 'projects':
221 | if(bareMode) {
222 | writeLines(["I don't want you to break the other projects.", "
"])
223 | break;
224 | }
225 | writeLines(PROJECTS);
226 | break;
227 | case 'repo':
228 | writeLines(["Redirecting to github.com...", "
"]);
229 | setTimeout(() => {
230 | window.open(REPO_LINK, '_blank');
231 | }, 500);
232 | break;
233 | case 'linkedin':
234 | //add stuff here
235 | break;
236 | case 'github':
237 | //add stuff here
238 | break;
239 | case 'email':
240 | //add stuff here
241 | break;
242 | case 'rm -rf':
243 | if (bareMode) {
244 | writeLines(["don't try again.", "
"])
245 | break;
246 | }
247 |
248 | if (isSudo) {
249 | writeLines(["Usage: 'rm -rf <dir>'", "
"]);
250 | } else {
251 | writeLines(["Permission not granted.", "
"])
252 | }
253 | break;
254 | case 'sudo':
255 | if(bareMode) {
256 | writeLines(["no.", "
"])
257 | break;
258 | }
259 | if(!PASSWORD) return
260 | isPasswordInput = true;
261 | USERINPUT.disabled = true;
262 |
263 | if(INPUT_HIDDEN) INPUT_HIDDEN.style.display = "none";
264 | PASSWORD.style.display = "block";
265 | setTimeout(() => {
266 | PASSWORD_INPUT.focus();
267 | }, 100);
268 |
269 | break;
270 | case 'ls':
271 | if(bareMode) {
272 | writeLines(["", "
"])
273 | break;
274 | }
275 |
276 | if (isSudo) {
277 | writeLines(["src", "
"]);
278 | } else {
279 | writeLines(["Permission not granted.", "
"]);
280 | }
281 | break;
282 | default:
283 | if(bareMode) {
284 | writeLines(["type 'help'", "
"])
285 | break;
286 | }
287 |
288 | writeLines(DEFAULT);
289 | break;
290 | }
291 | }
292 |
293 | function writeLines(message : string[]) {
294 | message.forEach((item, idx) => {
295 | displayText(item, idx);
296 | });
297 | }
298 |
299 | function displayText(item : string, idx : number) {
300 | setTimeout(() => {
301 | if(!mutWriteLines) return
302 | const p = document.createElement("p");
303 | p.innerHTML = item;
304 | mutWriteLines.parentNode!.insertBefore(p, mutWriteLines);
305 | scrollToBottom();
306 | }, 40 * idx);
307 | }
308 |
309 | function revertPasswordChanges() {
310 | if (!INPUT_HIDDEN || !PASSWORD) return
311 | PASSWORD_INPUT.value = "";
312 | USERINPUT.disabled = false;
313 | INPUT_HIDDEN.style.display = "block";
314 | PASSWORD.style.display = "none";
315 | isPasswordInput = false;
316 |
317 | setTimeout(() => {
318 | USERINPUT.focus();
319 | }, 200)
320 | }
321 |
322 | function passwordHandler() {
323 | if (passwordCounter === 2) {
324 | if (!INPUT_HIDDEN || !mutWriteLines || !PASSWORD) return
325 | writeLines(["
", "INCORRECT PASSWORD.", "PERMISSION NOT GRANTED.", "
"])
326 | revertPasswordChanges();
327 | passwordCounter = 0;
328 | return
329 | }
330 |
331 | if (PASSWORD_INPUT.value === SUDO_PASSWORD) {
332 | if (!mutWriteLines || !mutWriteLines.parentNode) return
333 | writeLines(["
", "PERMISSION GRANTED.", "Try 'rm -rf'", "
"])
334 | revertPasswordChanges();
335 | isSudo = true;
336 | return
337 | } else {
338 | PASSWORD_INPUT.value = "";
339 | passwordCounter++;
340 | }
341 | }
342 |
343 | function easterEggStyles() {
344 | const bars = document.getElementById("bars");
345 | const body = document.body;
346 | const main = document.getElementById("main");
347 | const span = document.getElementsByTagName("span");
348 |
349 | if (!bars) return
350 | bars.innerHTML = "";
351 | bars.remove()
352 |
353 | if (main) main.style.border = "none";
354 |
355 | body.style.backgroundColor = "black";
356 | body.style.fontFamily = "VT323, monospace";
357 | body.style.fontSize = "20px";
358 | body.style.color = "white";
359 |
360 | for (let i = 0; i < span.length; i++) {
361 | span[i].style.color = "white";
362 | }
363 |
364 | USERINPUT.style.backgroundColor = "black";
365 | USERINPUT.style.color = "white";
366 | USERINPUT.style.fontFamily = "VT323, monospace";
367 | USERINPUT.style.fontSize = "20px";
368 | if (PROMPT) PROMPT.style.color = "white";
369 |
370 | }
371 |
372 | const initEventListeners = () => {
373 | if(HOST) {
374 | HOST.innerText= command.hostname;
375 | }
376 |
377 | if(USER) {
378 | USER.innerText = command.username;
379 | }
380 |
381 | if(PRE_HOST) {
382 | PRE_HOST.innerText= command.hostname;
383 | }
384 |
385 | if(PRE_USER) {
386 | PRE_USER.innerText = command.username;
387 | }
388 |
389 | window.addEventListener('load', () => {
390 | writeLines(BANNER);
391 | });
392 |
393 | USERINPUT.addEventListener('keypress', userInputHandler);
394 | USERINPUT.addEventListener('keydown', userInputHandler);
395 | PASSWORD_INPUT.addEventListener('keypress', userInputHandler);
396 |
397 | window.addEventListener('click', () => {
398 | USERINPUT.focus();
399 | });
400 |
401 | console.log(`%cPassword: ${command.password}`, "color: red; font-size: 20px;");
402 | }
403 |
404 | initEventListeners();
405 |
--------------------------------------------------------------------------------