├── AUTHORS ├── .gitignore ├── screenshot.png ├── src ├── fonts │ ├── monofonto.eot │ ├── monofonto.otf │ ├── monofonto.ttf │ ├── monofonto.woff │ └── monofonto.svg ├── styles.scss ├── _responsive.scss ├── _fonts.scss ├── _layout.scss ├── index.html ├── _variables.scss ├── _animations.scss ├── _crt.scss └── terminal.js ├── .babelrc ├── .stylelintrc ├── Dockerfile ├── README.md ├── nginx └── default.conf ├── LICENSE ├── .eslintrc ├── package.json ├── webpack.config.js └── index.js /AUTHORS: -------------------------------------------------------------------------------- 1 | Anders Evenrud 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.tmp 3 | *.swp 4 | dist 5 | api 6 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccs/rc3-bib-search-frontend/master/screenshot.png -------------------------------------------------------------------------------- /src/fonts/monofonto.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccs/rc3-bib-search-frontend/master/src/fonts/monofonto.eot -------------------------------------------------------------------------------- /src/fonts/monofonto.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccs/rc3-bib-search-frontend/master/src/fonts/monofonto.otf -------------------------------------------------------------------------------- /src/fonts/monofonto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccs/rc3-bib-search-frontend/master/src/fonts/monofonto.ttf -------------------------------------------------------------------------------- /src/fonts/monofonto.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cccs/rc3-bib-search-frontend/master/src/fonts/monofonto.woff -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-transform-runtime" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-scss" 4 | ], 5 | "rules": { 6 | "at-rule-no-unknown": [true, { 7 | "ignoreAtRules": ["extend", "mixin", "include", "keyframes"] 8 | }] 9 | }, 10 | "extends": "stylelint-config-standard" 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | WORKDIR /data 4 | COPY *.js package* /data/ 5 | COPY src /data/src 6 | RUN npm install && npm run-script build 7 | 8 | FROM nginxinc/nginx-unprivileged:stable-alpine 9 | COPY --from=0 /data/dist/ /usr/share/nginx/html/ 10 | COPY nginx/ /etc/nginx/conf.d/ 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnderShell 3000 2 | 3 | A retro-looking shell using CSS and JavaScript. 4 | 5 | Somewhat inspired by PipBoy and old-school UNIX terminals. 6 | 7 | ![Screenshot](https://raw.githubusercontent.com/andersevenrud/retro-css-shell-demo/master/screenshot.png) 8 | 9 | # Demo 10 | 11 | [See it in action here](https://crt.no/) ([old version](http://andersevenrud.github.io/shell/)) 12 | 13 | *Please note that this demo is currently outdated and will be updated asap* 14 | 15 | # Running 16 | 17 | Just serve `dist/` and you're good to go. 18 | 19 | # Development 20 | 21 | Run `npm install`. 22 | 23 | * `npm run serve` - Development server 24 | * `npm run build` - Build 25 | * `npm run watch` - Watch for changes (not needed with dev server) 26 | * `npm run eslint` - Run eslint pass 27 | * `npm run stylelint` - Run stylelint pass 28 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name _; 4 | 5 | location /api/ { 6 | proxy_set_header Host $host; 7 | proxy_set_header X-Real-IP $remote_addr; 8 | proxy_pass http://rc3bib-backend:9000; 9 | } 10 | 11 | location / { 12 | root /usr/share/nginx/html; 13 | index index.html index.htm; 14 | } 15 | 16 | #error_page 404 /404.html; 17 | 18 | # redirect server error pages to the static page /50x.html 19 | # 20 | error_page 500 502 503 504 /50x.html; 21 | location = /50x.html { 22 | root /usr/share/nginx/html; 23 | } 24 | 25 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 26 | # 27 | #location ~ \.php$ { 28 | # proxy_pass http://127.0.0.1; 29 | #} 30 | 31 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 32 | # 33 | #location ~ \.php$ { 34 | # root html; 35 | # fastcgi_pass 127.0.0.1:9000; 36 | # fastcgi_index index.php; 37 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 38 | # include fastcgi_params; 39 | #} 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018, Anders Evenrud 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "parserOptions": { 7 | "sourceType": "module", 8 | "ecmaVersion": 8 9 | }, 10 | "globals": { 11 | "Promise": true, 12 | "ArrayBuffer": true, 13 | "File": true, 14 | "Blob": true, 15 | "OSjs": true, 16 | "OSJS_VERSION": true 17 | }, 18 | "extends": "eslint:recommended", 19 | "rules": { 20 | "linebreak-style": ["error", "unix"], 21 | "semi": ["error", "always"], 22 | "indent": ["error", 2], 23 | "radix": 1, 24 | "eol-last": ["error", "always"], 25 | "consistent-return": 1, 26 | "space-in-parens": ["error", "never"], 27 | "space-before-function-paren": ["error", { 28 | "anonymous": "never", 29 | "named": "never", 30 | "asyncArrow": "always" 31 | }], 32 | "space-before-blocks": ["error", "always"], 33 | "space-infix-ops": ["error", {"int32Hint": false}], 34 | "space-unary-ops": ["error", {"words": true, "nonwords": false}], 35 | "spaced-comment": ["warn", "always"], 36 | "prefer-arrow-callback": 1, 37 | "prefer-spread": 1, 38 | "prefer-rest-params": 1, 39 | "template-curly-spacing": ["warn", "never"], 40 | "no-unused-vars": ["error", {"vars": "all", "args": "none"}], 41 | "no-useless-constructor": 1, 42 | "no-var": 2, 43 | "no-duplicate-imports": 2, 44 | "no-console": "off", 45 | "no-extra-semi": 2, 46 | "no-eval": 2, 47 | "no-invalid-this": 1, 48 | "no-new-func": 2, 49 | "no-return-await": 1, 50 | "no-self-compare": 2, 51 | "no-with": 2, 52 | "no-multi-assign": 1, 53 | "no-new-object": 2, 54 | "no-tabs": 1, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | @import "fonts"; 29 | @import "variables"; 30 | @import "layout"; 31 | @import "crt"; 32 | @import "animations"; 33 | @import "responsive"; 34 | -------------------------------------------------------------------------------- /src/_responsive.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | @media (max-width: 1280px) { 29 | #crt { 30 | width: 95vw; 31 | height: 90vh; 32 | font-size: $font-size / 2; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crt", 3 | "version": "1.0.0", 4 | "description": "CRT Monitor Demo", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "webpack", 9 | "watch": "webpack --watch", 10 | "serve": "webpack-dev-server", 11 | "eslint": "eslint src/**/*.js", 12 | "stylelint": "eslint src/**/*.scss" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/andersevenrud/retro-css-shell-demo.git" 17 | }, 18 | "keywords": [ 19 | "css" 20 | ], 21 | "author": "Anders Evenrud ", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/andersevenrud/retro-css-shell-demo/issues" 25 | }, 26 | "homepage": "https://github.com/andersevenrud/retro-css-shell-demo#readme", 27 | "devDependencies": { 28 | "@babel/core": "^7.0.0-rc.1", 29 | "@babel/plugin-transform-runtime": "^7.0.0-rc.1", 30 | "@babel/preset-env": "^7.0.0-rc.1", 31 | "@babel/runtime": "^7.0.0-rc.1", 32 | "autoprefixer": "^9.1.0", 33 | "babel-loader": "^8.0.0-beta.4", 34 | "css-loader": "^1.0.0", 35 | "eslint": "^5.3.0", 36 | "file-loader": "^1.1.11", 37 | "html-webpack-plugin": "^4.0.0-alpha", 38 | "mini-css-extract-plugin": "^0.4.1", 39 | "node-sass": "^4.9.3", 40 | "optimize-css-assets-webpack-plugin": "^5.0.0", 41 | "postcss-loader": "^3.0.0", 42 | "sass-loader": "^7.1.0", 43 | "stylelint": "^9.4.0", 44 | "stylelint-config-standard": "^18.2.0", 45 | "stylelint-scss": "^3.2.0", 46 | "webpack": "^4.16.5", 47 | "webpack-cli": "^3.1.0", 48 | "webpack-dev-server": "^3.1.5" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/_fonts.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | @font-face { 29 | font-family: monofont; 30 | font-weight: normal; 31 | font-style: normal; 32 | src: url('fonts/monofonto.eot'); 33 | src: url('fonts/monofonto.eot?#iefix') format('embedded-opentype'), 34 | url('fonts/monofonto.woff') format('woff'), 35 | url('fonts/monofonto.ttf') format('truetype'), 36 | url('fonts/monofonto.svg#monofonto') format('svg'); 37 | } 38 | -------------------------------------------------------------------------------- /src/_layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | body, 29 | html { 30 | width: 100%; 31 | height: 100%; 32 | } 33 | 34 | body { 35 | background-color: $background-color; 36 | font-size: 14px; 37 | font-family: sans-serif; 38 | color: $color; 39 | display: flex; 40 | margin: 0; 41 | padding: 0; 42 | align-items: center; 43 | justify-content: center; 44 | } 45 | 46 | #info { 47 | position: absolute; 48 | bottom: 0; 49 | right: 0; 50 | padding: $base-unit / 2; 51 | } 52 | 53 | #ga { 54 | position: absolute; 55 | bottom: 0; 56 | left: 0; 57 | } 58 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 | Virtual library 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /src/_variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | $background-color: #000; 29 | $color: #fff; 30 | $base-unit: 1em; 31 | $font-size: 36px; 32 | $chassis-background: #7b8e78; 33 | $chassis-margin: $base-unit * 3; 34 | $interlace-background: linear-gradient(#888 50%, #000 50%); 35 | $interlace-size: 4px; 36 | $envelope-background: rgba(#000, 0.25); 37 | $envelope-margin: 2em; 38 | $scanline-background: linear-gradient(to bottom, transparent 0%, rgba(255, 250, 250, 1) 50%, rgba(100, 255, 100, 1) 50%, transparent 100%); 39 | $output-font-family: 'monofont'; 40 | $output-color: #18ff62; 41 | $output-text-shadow: rgba(10, 255, 10, 0.8); 42 | $output-line-height: 1.25; 43 | $gradient-bottom: rgb(0, 30, 30); 44 | $gradient-inner: rgb(0, 255, 119); 45 | 46 | @mixin fill($top: 0, $left: 0, $right: 0, $bottom: 0) { 47 | position: absolute; 48 | left: $left; 49 | top: $top; 50 | right: $right; 51 | bottom: $bottom; 52 | } 53 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | 6 | const mode = process.env.NODE_ENV || 'development'; 7 | const minimize = mode === 'production'; 8 | const plugins = []; 9 | 10 | if (mode === 'production') { 11 | plugins.push(new OptimizeCSSAssetsPlugin({ 12 | cssProcessorOptions: { 13 | discardComments: true 14 | }, 15 | })); 16 | } 17 | 18 | module.exports = { 19 | mode, 20 | devtool: 'source-map', 21 | entry: [ 22 | path.resolve(__dirname, 'index.js'), 23 | ], 24 | output: { 25 | sourceMapFilename: '[file].map', 26 | filename: '[name].js' 27 | }, 28 | optimization: { 29 | minimize, 30 | }, 31 | plugins: [ 32 | new HtmlWebpackPlugin({ 33 | template: path.resolve(__dirname, 'src/index.html') 34 | }), 35 | new MiniCssExtractPlugin({ 36 | filename: '[name].css', 37 | chunkFilename: '[id].css' 38 | }), 39 | ...plugins 40 | ], 41 | devServer: { 42 | host: '0.0.0.0', 43 | port: 9001, 44 | compress: true 45 | }, 46 | module: { 47 | rules: [ 48 | { 49 | test: /\.(eot|woff|ttf|svg)$/, 50 | exclude: /(node_modules|bower_components)/, 51 | use: [ 52 | { 53 | loader: 'file-loader' 54 | } 55 | ] 56 | }, 57 | { 58 | test: /\.(sa|sc|c)ss$/, 59 | exclude: /(node_modules|bower_components)/, 60 | use: [ 61 | MiniCssExtractPlugin.loader, 62 | { 63 | loader: 'css-loader', 64 | options: { 65 | sourceMap: true 66 | } 67 | }, 68 | { 69 | loader: 'postcss-loader', 70 | options: { 71 | sourceMap: true, 72 | plugins: [ 73 | require('autoprefixer')() 74 | ] 75 | } 76 | }, 77 | { 78 | loader: 'sass-loader', 79 | options: { 80 | minimize, 81 | sourceMap: true 82 | } 83 | } 84 | ] 85 | }, 86 | { 87 | test: /\.js$/, 88 | exclude: /(node_modules|bower_components)/, 89 | use: { 90 | loader: 'babel-loader' 91 | } 92 | } 93 | ] 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/_animations.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | @keyframes scanline { 29 | 0% { 30 | top: 0; 31 | } 32 | 33 | 100% { 34 | top: 100%; 35 | } 36 | } 37 | 38 | @keyframes skew { 39 | 0% { 40 | transform: skewX(0.5deg); 41 | } 42 | 43 | 8% { 44 | transform: skewX(1deg) scale(1.0001); 45 | } 46 | 47 | 15% { 48 | transform: skewX(0.6deg) skewY(-0.05deg); 49 | } 50 | 51 | 30% { 52 | transform: skewX(0.6deg); 53 | } 54 | 55 | 100% { 56 | transform: skewX(0.1deg); 57 | } 58 | } 59 | 60 | @keyframes pulse { 61 | 0% { 62 | transform: scale(1); 63 | } 64 | 65 | 50% { 66 | transform: scale(1.005); 67 | } 68 | 69 | 100% { 70 | transform: scale(1); 71 | } 72 | } 73 | 74 | @keyframes blur { 75 | 50% { 76 | color: darken($output-color, 10%); 77 | text-shadow: 2px 0 2px lighten($output-text-shadow, 50%); 78 | } 79 | } 80 | 81 | @keyframes glow { 82 | 50% { 83 | transform: scale(1.2); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/_crt.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #crt { 29 | position: relative; 30 | z-index: 10; 31 | background: $chassis-background; 32 | width: 1280px; 33 | height: 900px; 34 | max-width: 1280px; 35 | max-height: 1024px; 36 | border-radius: $base-unit; 37 | box-shadow: inset 0.25em 0.25em 2px rgba(255, 255, 255, 0.4), inset -0.25em -0.25em 2px rgba(0, 0, 0, 0.4); 38 | user-select: none; 39 | font-size: $font-size; 40 | transform: translate3d(0, 0, 0); 41 | backface-visibility: hidden; 42 | perspective: 1000; 43 | } 44 | 45 | #screen { 46 | position: relative; 47 | z-index: 20; 48 | width: calc(100% - #{$chassis-margin}); 49 | height: calc(100% - #{$chassis-margin}); 50 | margin-top: $chassis-margin / 2; 51 | margin-left: $chassis-margin / 2; 52 | background: #000; 53 | border-radius: $base-unit; 54 | overflow: hidden; 55 | box-shadow: 0 0 1px 3px rgba(10, 10, 10, 0.7); 56 | 57 | &::before { 58 | @include fill(); 59 | 60 | content: ''; 61 | z-index: 70; 62 | box-shadow: inset 0 0 $base-unit ($base-unit / 2) rgba(#fff, 0.08); 63 | border-radius: $base-unit; 64 | } 65 | 66 | &::after { 67 | @include fill(); 68 | 69 | content: ''; 70 | z-index: 60; 71 | opacity: 0.1; 72 | background: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.2)); 73 | } 74 | } 75 | 76 | #wrapper { 77 | @include fill(); 78 | 79 | z-index: 30; 80 | transform-origin: 50% 50%; 81 | transform: skewX(0.5deg); 82 | animation: pulse 5s linear infinite; 83 | 84 | &::before { 85 | @include fill(); 86 | 87 | content: ''; 88 | z-index: 31; 89 | background: radial-gradient(ellipse at center, rgba($gradient-bottom, 0) 0%, rgba($gradient-bottom, 0.5) 100%); 90 | pointer-events: none; 91 | } 92 | 93 | &::after { 94 | @include fill(); 95 | 96 | content: ''; 97 | z-index: 32; 98 | opacity: 0.5; 99 | background: radial-gradient(ellipse at center, rgba($gradient-inner, 1) 0%, rgba($gradient-inner, 0) 100%); 100 | pointer-events: none; 101 | animation: glow 2s linear infinite; 102 | } 103 | } 104 | 105 | #interlace { 106 | @include fill(); 107 | 108 | z-index: 21; 109 | opacity: 0.25; 110 | background: $interlace-background; 111 | background-size: 100% $interlace-size; 112 | background-repeat: repeat-y; 113 | } 114 | 115 | #envelope { 116 | @include fill($envelope-margin, $envelope-margin, $envelope-margin, $envelope-margin); 117 | 118 | z-index: 40; 119 | border-radius: $base-unit / 2; 120 | background: $envelope-background; 121 | animation: skew 5s linear infinite; 122 | } 123 | 124 | #scanline { 125 | position: absolute; 126 | top: 0; 127 | left: 0; 128 | right: 0; 129 | height: $base-unit; 130 | opacity: 0.1; 131 | background: $scanline-background; 132 | animation: scanline 1.25s linear infinite; 133 | } 134 | 135 | #terminal { 136 | position: relative; 137 | width: 100%; 138 | height: 100%; 139 | z-index: 50; 140 | 141 | textarea { 142 | display: block; 143 | background: transparent; 144 | resize: none; 145 | width: 100%; 146 | height: 100%; 147 | border: 0 none; 148 | outline: 0 none; 149 | box-sizing: border-box; 150 | pointer-events: none; 151 | margin: 0; 152 | overflow: hidden; 153 | padding: $base-unit; 154 | color: $output-color; 155 | text-shadow: 0 0 2px $output-text-shadow; 156 | font-family: $output-font-family; 157 | font-size: inherit; 158 | animation: blur 5s linear infinite; 159 | line-height: $output-line-height; 160 | white-space: pre-wrap; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | import './src/styles.scss'; 28 | import {terminal} from './src/terminal.js'; 29 | 30 | // Banner text 31 | const banner = ` 32 | Virtual library of Stuttgart 33 | Chaos search index 34 | 35 | Type 'help' for a list of available commands. 36 | Type 'exit' to return control to 2d world. 37 | 38 | Create a hacker's library (but respect copyright, please) by suggesting content! 39 | 40 | `; 41 | 42 | // Help text 43 | const helpText = ` 44 | Available commands: 45 | 46 | help - This output 47 | info - Show some data on the library 48 | list - Show all books on given level 49 | search - Search for books 50 | suggest - Suggest new entry for library 51 | clear - Clears the display 52 | exit - Detach from terminal 53 | `; 54 | 55 | 56 | function booksAsText(json) { 57 | let result = ''; 58 | json.books.forEach((item) => { 59 | result += `${item.title} - level ${item.level} ${item.levelDescr}\n`; 60 | }); 61 | result += json.info; 62 | if (json.info) { 63 | console.log(json.info); 64 | } 65 | return result.trimEnd(); 66 | } 67 | 68 | 69 | function info() { 70 | let req = new XMLHttpRequest(); 71 | req.open("GET", `${window.location.href}api/info`, false); 72 | req.send(); 73 | if (req.status===200) { 74 | let data = JSON.parse(req.response); 75 | return `${data.bookCount} books on ${data.levels} levels`; 76 | } else { 77 | return `Unable to get result (${req.status})`; 78 | } 79 | } 80 | 81 | 82 | function list() { 83 | return `Can't list all books!`; 84 | } 85 | 86 | 87 | function listLevel(level) { 88 | let req = new XMLHttpRequest(); 89 | req.open("GET", `${window.location.href}api/list/${level}`, false); 90 | req.send(); 91 | if (req.status===200) { 92 | let data = JSON.parse(req.response); 93 | return booksAsText(data); 94 | } else { 95 | return `Unable to get result (${req.status})`; 96 | } 97 | } 98 | 99 | 100 | function search() { 101 | let args = Array.prototype.slice.call(arguments); 102 | let query = { 103 | query: args.join(' '), 104 | } 105 | 106 | let req = new XMLHttpRequest(); 107 | req.open("POST", `${window.location.href}api/search`, false); 108 | req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 109 | req.send(JSON.stringify(query)); 110 | if (req.status===200) { 111 | let data = JSON.parse(req.response); 112 | return booksAsText(data); 113 | } else { 114 | return `Unable to get result (${req.status})`; 115 | } 116 | } 117 | 118 | 119 | function suggest() { 120 | let args = Array.prototype.slice.call(arguments); 121 | let suggestion = { 122 | url: args[0], 123 | title: args.slice(1).join(' '), 124 | } 125 | 126 | let req = new XMLHttpRequest(); 127 | req.open("POST", `${window.location.href}api/suggest`, false); 128 | req.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); 129 | req.send(JSON.stringify(suggestion)); 130 | if (req.status===200 || req.status===403) { 131 | return req.response; 132 | } else { 133 | return `Unable to submit suggestion (${req.status})`; 134 | } 135 | } 136 | 137 | 138 | /////////////////////////////////////////////////////////////////////////////// 139 | // MAIN 140 | /////////////////////////////////////////////////////////////////////////////// 141 | 142 | const load = () => { 143 | const t = terminal({ 144 | prompt: () => '> ', 145 | banner, 146 | commands: { 147 | help: () => helpText, 148 | info: info, 149 | list: (level) => { return (level ? listLevel(level) : list()) }, 150 | search: search, 151 | suggest: suggest, 152 | clear: () => t.clear(), 153 | exit: () => { t.unregister(); document.getElementById('terminal').remove(); return "\nYou're free to go\n\n"; } 154 | } 155 | }); 156 | }; 157 | 158 | document.addEventListener('DOMContentLoaded', load); 159 | -------------------------------------------------------------------------------- /src/terminal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AnderShell - Just a small CSS demo 3 | * 4 | * Copyright (c) 2011-2018, Anders Evenrud 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions are met: 9 | * 10 | * 1. Redistributions of source code must retain the above copyright notice, this 11 | * list of conditions and the following disclaimer. 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Creates initial options 29 | const createOptions = opts => Object.assign({}, { 30 | banner: 'Hello World', 31 | prompt: () => '$ > ', 32 | tickrate: 1000 / 60, 33 | buflen: 8, 34 | commands: {} 35 | }, opts || {}); 36 | 37 | // Creates our textarea element 38 | const createElement = root => { 39 | const el = document.createElement('textarea'); 40 | el.contentEditable = true; 41 | el.spellcheck = false; 42 | el.value = ''; 43 | 44 | root.appendChild(el); 45 | 46 | return el; 47 | }; 48 | 49 | // Keys that must be ignored 50 | 51 | // Sets text selection range 52 | const setSelectionRange = input => { 53 | const length = input.value.length; 54 | 55 | if (input.setSelectionRange) { 56 | input.focus(); 57 | input.setSelectionRange(length, length); 58 | } else if (input.createTextRange) { 59 | const range = input.createTextRange(); 60 | range.collapse(true); 61 | range.moveEnd('character', length); 62 | range.moveStart('character', length); 63 | range.select(); 64 | } 65 | }; 66 | 67 | // Gets the font size of an element 68 | const getFontSize = element => parseInt(window.getComputedStyle(element) 69 | .getPropertyValue('font-size'), 10); 70 | 71 | // Creates the rendering loop 72 | const renderer = (tickrate, onrender) => { 73 | let lastTick = 0; 74 | 75 | const tick = (time) => { 76 | const now = performance.now(); 77 | const delta = now - lastTick; 78 | 79 | if (delta > tickrate) { 80 | lastTick = now - (delta % tickrate); 81 | 82 | onrender(); 83 | } 84 | 85 | window.requestAnimationFrame(tick); 86 | }; 87 | 88 | return tick; 89 | }; 90 | 91 | // Pronts buffer onto the textarea 92 | const printer = ($element, buflen) => buffer => { 93 | if (buffer.length > 0) { 94 | const len = Math.min(buflen, buffer.length); 95 | const val = buffer.splice(0, len); 96 | 97 | $element.value += val.join(''); 98 | 99 | setSelectionRange($element); 100 | $element.scrollTop = $element.scrollHeight; 101 | 102 | return true; 103 | } 104 | 105 | return false; 106 | }; 107 | 108 | // Parses input 109 | const parser = onparsed => str => { 110 | if (str.length) { 111 | const args = str.split(' ').map(s => s.trim()); 112 | const cmd = args.splice(0, 1)[0]; 113 | console.debug(cmd, args); 114 | onparsed(cmd, ...args); 115 | } 116 | }; 117 | 118 | // Command executor 119 | const executor = commands => (cmd, ...args) => cb => { 120 | try { 121 | commands[cmd] 122 | ? cb(commands[cmd](...args) + '\n') 123 | : cb(`No such command '${cmd}'\n`); 124 | } catch (e) { 125 | console.warn(e); 126 | cb(`Exception: ${e}\n`); 127 | } 128 | }; 129 | 130 | // Handle keyboard events 131 | const keyboard = (parse, continue_out, paging) => { 132 | let input = []; 133 | const keys = {8: 'backspace', 13: 'enter'}; 134 | const ignoreKey = code => code >= 33 && code <= 40; 135 | const key = ev => keys[ev.which || ev.keyCode]; 136 | 137 | return { 138 | keypress: (ev) => { 139 | if (paging.on) { 140 | if (ev.key === ' ') { 141 | continue_out(false); 142 | } else if (ev.key === 'q') { 143 | continue_out(true); 144 | } 145 | } else { 146 | if (key(ev) === 'enter') { 147 | const str = input.join('').trim(); 148 | parse(str); 149 | input = []; 150 | } else if (key(ev) !== 'backspace') { 151 | input.push(String.fromCharCode(ev.which || ev.keyCode)); 152 | } 153 | } 154 | }, 155 | 156 | keydown: (ev) => { 157 | if (paging.on) { 158 | if (ev.key !== 'q' && ev.key !== ' ') { 159 | ev.preventDefault(); 160 | } 161 | } else { 162 | if (key(ev) === 'backspace') { 163 | if (input.length > 0) { 164 | input.pop(); 165 | } else { 166 | ev.preventDefault(); 167 | } 168 | } else if (ignoreKey(ev.keyCode)) { 169 | ev.preventDefault(); 170 | } 171 | } 172 | } 173 | }; 174 | }; 175 | 176 | // Creates the terminal 177 | export const terminal = (opts) => { 178 | let buffer = []; // What will be output to display 179 | let busy = false; // If we cannot type at the moment 180 | let paging = { 181 | on: false, 182 | }; 183 | let lines = []; 184 | 185 | const {prompt, banner, commands, buflen, tickrate} = createOptions(opts); 186 | const $root = document.querySelector('#terminal'); 187 | const $element = createElement($root); 188 | const fontSize = getFontSize($element); 189 | const width = $element.offsetWidth; 190 | const cwidth = Math.round((width / fontSize) * 1.9); // FIXME: Should be calculated via canvas 191 | 192 | const continue_out = (abort) => { 193 | const page_size = 15; 194 | let append = ''; 195 | 196 | if (paging.on) { 197 | append = '\n'; 198 | } 199 | if (abort) { 200 | append = append + prompt(); 201 | lines = []; 202 | paging.on = false; 203 | } else { 204 | if (lines.length < page_size - 2) { 205 | // output rest 206 | append = append + lines.join('\n') + '\n' + prompt(); 207 | lines = []; 208 | paging.on = false; 209 | } else { 210 | append = append + lines.splice(0, page_size - 1).join('\n') + '\n--- Space to continue, q to abort ---'; 211 | paging.on = true; 212 | } 213 | } 214 | buffer = buffer.concat(append.split('')); 215 | } 216 | 217 | const output = (output, center) => { 218 | lines = output.split(/\n/); 219 | if (center) { 220 | lines = lines.map(line => line.length > 0 221 | ? line.padStart(line.length + ((cwidth / 2) - (line.length / 2)), ' ') 222 | : line); 223 | } 224 | 225 | continue_out(); 226 | }; 227 | 228 | const print = printer($element, buflen); 229 | const execute = executor(commands); 230 | const onrender = () => (busy = print(buffer)); 231 | const onparsed = (cmd, ...args) => execute(cmd, ...args)(output); 232 | const render = renderer(tickrate, onrender); 233 | const parse = parser(onparsed); 234 | const focus = () => setTimeout(() => $element.focus(), 1); 235 | const kbd = keyboard(parse, continue_out, paging); 236 | const clear = () => ($element.value = ''); 237 | const input = ev => busy 238 | ? ev.preventDefault() 239 | : kbd[ev.type](ev, paging); 240 | const focusListener = () => setSelectionRange($element); 241 | 242 | $element.addEventListener('focus', focusListener); 243 | $element.addEventListener('blur', focus); 244 | $element.addEventListener('keypress', input); 245 | $element.addEventListener('keydown', input); 246 | window.addEventListener('focus', focus); 247 | $root.addEventListener('click', focus); 248 | $root.appendChild($element); 249 | 250 | const unregister = () => { 251 | $element.removeEventListener('focus', focusListener); 252 | $element.removeEventListener('blur', focus); 253 | $element.removeEventListener('keypress', input); 254 | $element.removeEventListener('keydown', input); 255 | window.removeEventListener('focus', focus); 256 | $root.removeEventListener('click', focus); 257 | console.log('Event listeners removed'); 258 | }; 259 | 260 | render(); 261 | output(banner, true); 262 | focus(); 263 | 264 | return {focus, parse, clear, print: output, unregister}; 265 | }; 266 | -------------------------------------------------------------------------------- /src/fonts/monofonto.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Created by FontForge 20120731 at Sat Apr 19 19:44:55 2014 6 | By World Wide Web Server 7 | (c) 1999-2012 Typodermic Fonts Inc. See attached license agreement. If agreement is missing visit typodermicfonts.com for more info. 8 | 9 | 10 | 11 | 26 | 29 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 69 | 71 | 73 | 76 | 78 | 81 | 84 | 86 | 90 | 93 | 95 | 97 | 99 | 101 | 103 | 106 | 110 | 112 | 115 | 117 | 119 | 121 | 123 | 126 | 128 | 130 | 132 | 134 | 136 | 138 | 140 | 142 | 144 | 146 | 148 | 151 | 153 | 155 | 157 | 159 | 161 | 163 | 165 | 167 | 169 | 171 | 173 | 175 | 177 | 180 | 183 | 186 | 189 | 192 | 194 | 197 | 199 | 201 | 203 | 205 | 207 | 209 | 211 | 214 | 217 | 220 | 222 | 225 | 227 | 229 | 231 | 233 | 235 | 237 | 239 | 242 | 244 | 247 | 249 | 251 | 253 | 255 | 258 | 261 | 263 | 265 | 269 | 271 | 274 | 276 | 278 | 281 | 283 | 286 | 288 | 290 | 292 | 294 | 296 | 298 | 301 | 303 | 305 | 307 | 310 | 313 | 316 | 318 | 320 | 322 | 324 | 326 | 328 | 330 | 332 | 334 | 336 | 338 | 341 | 344 | 347 | 350 | 353 | 356 | 358 | 361 | 363 | 365 | 367 | 370 | 372 | 374 | 377 | 380 | 383 | 386 | 390 | 394 | 398 | 402 | 405 | 408 | 411 | 414 | 417 | 419 | 421 | 423 | 425 | 428 | 431 | 434 | 437 | 440 | 444 | 448 | 450 | 453 | 455 | 457 | 459 | 462 | 464 | 467 | 469 | 471 | 474 | 476 | 480 | 482 | 485 | 487 | 490 | 493 | 496 | 499 | 502 | 504 | 509 | 511 | 514 | 516 | 519 | 521 | 524 | 526 | 529 | 531 | 534 | 536 | 539 | 542 | 546 | 549 | 552 | 555 | 558 | 560 | 562 | 564 | 566 | 568 | 570 | 572 | 575 | 577 | 579 | 581 | 584 | 586 | 588 | 590 | 592 | 594 | 596 | 598 | 600 | 602 | 604 | 606 | 608 | 610 | 612 | 614 | 616 | 618 | 620 | 622 | 624 | 627 | 630 | 633 | 636 | 639 | 642 | 644 | 647 | 649 | 651 | 653 | 655 | 657 | 659 | 662 | 665 | 668 | 671 | 674 | 677 | 679 | 681 | 683 | 685 | 687 | 689 | 692 | 695 | 697 | 699 | 702 | 705 | 708 | 711 | 713 | 715 | 718 | 721 | 723 | 725 | 727 | 729 | 731 | 733 | 735 | 737 | 739 | 741 | 743 | 745 | 748 | 751 | 753 | 755 | 757 | 759 | 761 | 763 | 766 | 768 | 770 | 772 | 774 | 777 | 779 | 781 | 783 | 785 | 787 | 789 | 792 | 795 | 797 | 799 | 801 | 803 | 805 | 807 | 809 | 811 | 813 | 815 | 817 | 819 | 821 | 824 | 826 | 828 | 830 | 833 | 835 | 838 | 840 | 843 | 845 | 847 | 849 | 851 | 854 | 856 | 859 | 861 | 863 | 865 | 867 | 869 | 871 | 873 | 875 | 877 | 880 | 882 | 884 | 887 | 889 | 892 | 894 | 897 | 899 | 901 | 904 | 906 | 908 | 910 | 912 | 915 | 917 | 920 | 922 | 924 | 926 | 929 | 932 | 936 | 940 | 943 | 946 | 949 | 952 | 956 | 957 | 958 | --------------------------------------------------------------------------------