├── 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 |
32 |
33 | 35 |

36 | @:$ ~ 37 | 38 | 40 |

41 |
42 |
43 |
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 | banner 5 |
6 | 7 | ![Vercel](https://img.shields.io/badge/vercel-%23000000.svg?style=for-the-badge&logo=vercel&logoColor=white) 8 | ![Vite](https://img.shields.io/badge/vite-%23646CFF.svg?style=for-the-badge&logo=vite&logoColor=white) 9 | ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) 10 | ![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white) 11 | ![CSS3](https://img.shields.io/badge/css3-%231572B6.svg?style=for-the-badge&logo=css3&logoColor=white) 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 | banner 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 | --------------------------------------------------------------------------------