diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..11476d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2023 BFHS Open contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/index.css b/index.css new file mode 100644 index 0000000..3fd54f6 --- /dev/null +++ b/index.css @@ -0,0 +1,174 @@ +html, body { + margin: 0; +} + +html { + /* + hack for dynamic unit + 1rem = 1in when width of camera is 1920px + */ + font-size: calc(0.05 * min(100vw, calc(100vh * 4 / 3))); +} + +body { + font-size: 16px; +} + +.container { + width: 100vw; + height: 100vh; + background-color: black; + display: flex; + align-items: center; + justify-content: center; +} + +.camera { + position: relative; + width: min(100vw, calc(100vh * 4 / 3)); + height: min(100vh, calc(100vw * 3 / 4)); + background: radial-gradient(circle at 50% -100%, #404040, #101010); + perspective: 18rem; + overflow: hidden; +} + +.world { + position: relative; + width: 100%; + height: 100%; + transform-style: preserve-3d; + transform: translateZ(10rem) rotateX(0) translateZ(-10rem); + transition: transform 2s ease-in-out; + pointer-events: none; +} + +.computer { + position: absolute; + left: 50%; + top: 50%; + width: 12rem; + height: 9rem; + transform: + translate(-50%,-50%) + rotateX(10deg) + ; + background-color: #606060; + box-shadow: inset 0 0 4px #202020; + pointer-events: all; + --screen-color: lime; +} + +/* +https://dev.to/ekeijl/retro-crt-terminal-screen-in-css-js-4afh +http://aleclownes.com/2017/02/01/crt-display.html +https://css-tricks.com/old-timey-terminal-styling/ +*/ +.screen { + position: absolute; + top: .2rem; + right: .2rem; + bottom: .2rem; + left: .2rem; + background: + radial-gradient( + color-mix(in srgb, var(--screen-color) 12.5%, black), transparent 150% + ), + #202020 + ; + padding: 1em; + box-shadow: 0 0 4px color-mix(in srgb, var(--screen-color), transparent); + text-shadow: 0 0 2px var(--screen-color); + font-family: "Consolas"; + font-size: 24px; + color: var(--screen-color); +} + +.screen::after { + content: ""; + position: absolute; + display: block; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: + linear-gradient( + rgba(18, 16, 16, 0) 50%, + rgba(0, 0, 0, 0.25) 50% + ), + linear-gradient( + 90deg, + rgba(255, 0, 0, 0.06), + rgba(0, 255, 0, 0.02), + rgba(0, 0, 255, 0.06) + ) + ; + background-size: 100% 2px, 3px 100%; + pointer-events: none; +} + +.content { + height: 100%; + overflow-y: scroll; + scrollbar-width: none; +} + +.content::-webkit-scrollbar { + display: none; +} + +.cli { + margin: 0; + display: inline; + white-space: break-spaces; + word-break: break-all; +} + +.input { + display: inline; + outline: none; + white-space: break-spaces; + word-break: break-all; +} + +.input.empty { + caret-color: transparent; +} + +@keyframes cursor { + 0% { + visibility: visible; + } + 50% { + visibility: hidden; + } +} + +.input.empty::after { + content: "█"; + animation: cursor 1s infinite step-end; +} + +.paper { + position: absolute; + left: 50%; + top: 50%; + width: 8.5rem; + height: 11rem; + background-color: beige; + padding: 1rem; + transform: + translate(-50%,-50%) + translate3d(0,5rem,8rem) + rotateX(90deg) + ; + overflow-y: scroll; + scrollbar-width: none; + font-size: 12pt; + font-family: "Courier New"; + pointer-events: all; +} + +:is(.computer, .paper):hover:not(.selected) { + outline: white solid 10px; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..81938ad --- /dev/null +++ b/index.html @@ -0,0 +1,43 @@ + + + + Concept + + + + +
+
+
+
+
+
+

+              
+
+
+
+

About the club

+

+ Lorem ipsum dolor sit amet, + consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + Excepteur sint occaecat cupidatat non proident, + sunt in culpa qui officia deserunt mollit anim id est laborum. +

+

+ Officers: +

    +
  • Alice
  • +
  • Bob
  • +
+

+
+
+
+
+ + diff --git a/index.js b/index.js new file mode 100644 index 0000000..ba4b2c6 --- /dev/null +++ b/index.js @@ -0,0 +1,143 @@ +const world = document.getElementsByClassName("world")[0]; + +const comp = document.getElementById("main"); + +const content = comp.getElementsByClassName("content")[0]; + +const cli = comp.getElementsByClassName("cli")[0]; + +const input = comp.getElementsByClassName("input")[0]; + +// https://stackoverflow.com/a/64001839 +if (input.contentEditable !== "plaintext-only") { + const type = (elem, text) => { + const sel = window.getSelection(); + const content = elem.textContent; + const start = Math.min(sel.focusOffset, sel.anchorOffset); + const end = Math.max(sel.focusOffset, sel. anchorOffset); + elem.textContent = content.slice(0, start) + text + (content.slice(end) || "\n"); + + sel.removeAllRanges(); + const range = document.createRange(); + range.setStart(elem.childNodes[0], start + text.length); + range.setEnd(elem.childNodes[0], start + text.length); + sel.addRange(range); + + elem.dispatchEvent(new Event("input")); + }; + + input.contentEditable = "true"; + input.addEventListener("paste", (e) => { + e.preventDefault(); + type(input, e.clipboardData.getData("text/plain")); + }); + input.addEventListener("input", (e) => { + if (input.children.length === 0) return; + input.textContent = ""; + }); +} + +let selectId = comp; + +comp.addEventListener("click", (e) => { + if (window.getSelection().isCollapsed) input.focus(); + if (comp == selectId) return; + selectId.classList.remove("selected"); + selectId = comp; + comp.classList.add("selected"); + world.style.transform = "translateZ(10rem) rotateX(0) translateZ(-10rem)"; +}); + +input.addEventListener("focus", (e) => { + window.getSelection().selectAllChildren(input); + window.getSelection().collapseToEnd(); +}); + +const run = (str) => { + const res = str.trim().split(/\s+/u); + if (res[0] === "") return ""; + const cmd = res[0]; + if (cmd == "help") { + return `WIP sorry :(\nTry "color [deg]"!\n` + } else if (cmd == "color") { + const hue = res[1] === undefined ? Math.random() : +res[1]/360; + if (!Number.isFinite(hue)) return `Invalid hue angle "${res[1]}"\n`; + comp.style.setProperty("--screen-color", `hsl(${hue ?? Math.random()}turn 100% 50%)`); + return ""; + } + return `Unrecognized command "${cmd}"\n`; +}; + +input.addEventListener("keydown", (e) => { + if (e.keyCode != 13) return; + e.preventDefault(); + cli.insertAdjacentText("beforeend", input.textContent + "\n" + run(input.textContent) + "> "); + input.textContent = ""; + content.scrollTop = content.scrollHeight; + input.classList.add("empty"); +}); + +input.addEventListener("input", (e) => { + if (input.textContent.length === 0) { + input.classList.add("empty"); + } else { + input.classList.remove("empty"); + } +}); + +const paper = document.getElementsByClassName("paper")[0]; + +paper.addEventListener("click", (e) => { + if (paper == selectId) return; + selectId.classList.remove("selected"); + selectId = paper; + paper.classList.add("selected"); + world.style.transform = "translateZ(4rem) rotateX(-70deg) translateZ(-10rem)"; +}); + +const text1 = `Welcome to the + +██████╗░███████╗██╗░░██╗░██████╗ +██╔══██╗██╔════╝██║░░██║██╔════╝ +██████╦╝█████╗░░███████║╚█████╗░ +██╔══██╗██╔══╝░░██╔══██║░╚═══██╗ +██████╦╝██║░░░░░██║░░██║██████╔╝ +╚═════╝░╚═╝░░░░░╚═╝░░╚═╝╚═════╝░ + +░█████╗░░██████╗  ░█████╗░██╗░░░░░██╗░░░██╗██████╗░██╗ +██╔══██╗██╔════╝  ██╔══██╗██║░░░░░██║░░░██║██╔══██╗██║ +██║░░╚═╝╚█████╗░  ██║░░╚═╝██║░░░░░██║░░░██║██████╦╝██║ +██║░░██╗░╚═══██╗  ██║░░██╗██║░░░░░██║░░░██║██╔══██╗╚═╝ +╚█████╔╝██████╔╝  ╚█████╔╝███████╗╚██████╔╝██████╦╝██╗ +░╚════╝░╚═════╝░  ░╚════╝░╚══════╝░╚═════╝░╚═════╝░╚═╝ +`; + +const text1s = `Welcome to the + +█▄▄ █▀▀ █░█ █▀ +█▄█ █▀░ █▀█ ▄█ + +█▀▀ █▀ █▀▀ █░░ █░█ █▄▄ █ +█▄▄ ▄█ █▄▄ █▄▄ █▄█ █▄█ ▄ +`; + +const text2 = ` +Run "help" on this awful terminal +or click on the paper to look down! + +> `; + +const sleep = (ms) => new Promise((res) => { + setInterval(res, ms); +}); + +const init = async () => { + await sleep(800); + const size = parseFloat(window.getComputedStyle(document.documentElement).fontSize); + console.log(size); + cli.insertAdjacentText("beforeend", size < 71 ? text1s : text1); + await sleep(1300); + cli.insertAdjacentText("beforeend", text2); +}; + +init();