Refactors site to use interactive terminal UI

Moves site from static HTML to a dynamic terminal interface.

This commit represents a major overhaul, introducing:
- A functional terminal emulator implemented in JavaScript.
- Command parsing and execution framework.
- Various terminal commands for navigation and utility functions.
- SASS conversion.

The old CSS and simple HTML are completely removed. A new Hugo theme is implemented.
This commit is contained in:
Dan 2025-12-08 13:46:35 +00:00
parent d22b4ec65a
commit 23369f4ace
46 changed files with 3524 additions and 860 deletions

View file

@ -1,48 +0,0 @@
.buy-me-a-coffee {
text-align: center;
border-radius: var(--radius);
background: var(--code-bg);
border: 1px solid var(--border);
background-color: var(--secondary);
color: var(--tertiary);
padding: var(--gap);
}
article a[href^="http"]:not(article .social-icons a, .paginav a, .buy-me-a-coffee a, .post-tags a, a.entry-link),
article a[href^="https://"]:not(article .social-icons a, .paginav a, .buy-me-a-coffee a, .post-tags a, a.entry-link)
{
box-shadow: none;
background: linear-gradient(to left, #c34722, #fdbb2d 100%);
background-position: 0 100%;
background-size: 100% 2px;
background-repeat: repeat-x;
}
.row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
width: 100%;
gap: 14px;
}
.col {
display: flex;
flex-direction: column;
flex-basis: 100%;
flex: 1;
}
.photo {
flex: 25%;
img {
border-radius: var(--radius);
}
}
.intro-text {
flex: 75%;
}

View file

@ -0,0 +1,41 @@
// Core Commands Module
// These are essential commands for the terminal
// Help command
window.terminal.registerCommand("help", "Display available commands", () => {
window.terminal.print("Available commands:", "success");
window.terminal.print("");
const commands = Object.keys(window.terminal.commands).sort();
commands.forEach((cmd) => {
const desc = window.terminal.commands[cmd].description || "No description";
window.terminal.print(` ${cmd.padEnd(15)} - ${desc}`);
});
window.terminal.print("");
});
// Clear command
window.terminal.registerCommand("clear", "Clear the terminal screen", () => {
window.terminal.clear();
});
// Echo command
window.terminal.registerCommand("echo", "Echo text to the terminal", (args) => {
window.terminal.print(args.join(" "));
});
// History command
window.terminal.registerCommand("history", "Show command history", () => {
if (window.terminal.history.length === 0) {
window.terminal.print("No commands in history");
return;
}
window.terminal.print("Command history:", "info");
window.terminal.history
.slice()
.reverse()
.forEach((cmd, idx) => {
window.terminal.print(` ${idx + 1}. ${cmd}`);
});
});

View file

@ -0,0 +1,101 @@
// Custom Commands Module
// Add your custom commands here!
// This file is a template for creating your own commands
// Example: Weather command (placeholder)
// window.terminal.registerCommand(
// "weather",
// "Show weather information",
// (args) => {
// const city = args.length > 0 ? args.join(" ") : "your location";
// window.terminal.printInfo(`Fetching weather for ${city}...`);
// window.terminal.print("☀️ Sunny, 72°F");
// window.terminal.print("");
// window.terminal.printWarning(
// "Note: This is a placeholder. Integrate with a real weather API!",
// );
// },
// );
// Example: Random quote
// window.terminal.registerCommand("quote", "Display a random quote", () => {
// const quotes = [
// "The only way to do great work is to love what you do. - Steve Jobs",
// "Innovation distinguishes between a leader and a follower. - Steve Jobs",
// "Stay hungry, stay foolish. - Steve Jobs",
// "First, solve the problem. Then, write the code. - John Johnson",
// ];
// const randomQuote = quotes[Math.floor(Math.random() * quotes.length)];
// window.terminal.printSuccess(randomQuote);
// });
// Example: List command (demonstrates dynamic content)
// window.terminal.registerCommand(
// "list",
// "List items in a collection",
// (args) => {
// if (args.length === 0) {
// window.terminal.printError("Usage: list <category>");
// window.terminal.print("Available categories: files, users, tasks");
// return;
// }
// const category = args[0].toLowerCase();
// switch (category) {
// case "files":
// window.terminal.print("Files:", "info");
// window.terminal.print(" 📄 document.txt");
// window.terminal.print(" 📄 notes.md");
// window.terminal.print(" 📁 projects/");
// break;
// case "users":
// window.terminal.print("Users:", "info");
// window.terminal.print(" 👤 admin");
// window.terminal.print(" 👤 guest");
// break;
// case "tasks":
// window.terminal.print("Tasks:", "info");
// window.terminal.print(" ✓ Complete terminal setup");
// window.terminal.print(" ☐ Add more commands");
// window.terminal.print(" ☐ Customize appearance");
// break;
// default:
// window.terminal.printError(`Unknown category: ${category}`);
// }
// },
// );
// Example: Color command (demonstrates HTML output)
window.terminal.registerCommand("colors", "Display available colors", () => {
window.terminal.print("Available terminal colors:", "info");
window.terminal.print("");
window.terminal.printHTML("<span>● Standard (green)</span>");
window.terminal.printHTML('<span class="error">● Error (red)</span>');
window.terminal.printHTML(
'<span class="success">● Success (bright green)</span>',
);
window.terminal.printHTML('<span class="info">● Info (blue)</span>');
window.terminal.printHTML('<span class="warning">● Warning (orange)</span>');
});
// ADD YOUR OWN COMMANDS BELOW THIS LINE
// ========================================
// Template for new command:
/*
window.terminal.registerCommand('commandname', 'Command description', (args) => {
// args is an array of arguments
// Example: if user types "mycommand hello world"
// args will be ['hello', 'world']
// Print output using:
window.terminal.print('Regular text');
window.terminal.printSuccess('Success message');
window.terminal.printError('Error message');
window.terminal.printInfo('Info message');
window.terminal.printWarning('Warning message');
window.terminal.printHTML('<strong>HTML content</strong>');
});
*/

View file

@ -0,0 +1,52 @@
// Navigation Commands Module
// Commands for navigating to different pages or URLs
// Navigate to URL command
window.terminal.registerCommand("goto", "Navigate to a URL", (args) => {
if (args.length === 0) {
window.terminal.printError("Usage: goto <url>");
window.terminal.print("Examples:");
window.terminal.print(" goto google.com");
window.terminal.print(" goto https://github.com");
return;
}
const url = args[0];
window.terminal.printInfo(`Navigating to ${url}...`);
setTimeout(() => {
window.location.href = url.startsWith("http") ? url : `https://${url}`;
}, 500);
});
// Open in new tab command
window.terminal.registerCommand("open", "Open URL in new tab", (args) => {
if (args.length === 0) {
window.terminal.printError("Usage: open <url>");
window.terminal.print("Examples:");
window.terminal.print(" open google.com");
window.terminal.print(" open https://github.com");
return;
}
const url = args[0];
window.terminal.printInfo(`Opening ${url} in new tab...`);
const fullUrl = url.startsWith("http") ? url : `https://${url}`;
window.open(fullUrl, "_blank");
});
// Reload page command
window.terminal.registerCommand("reload", "Reload the current page", () => {
window.terminal.printInfo("Reloading page...");
setTimeout(() => {
window.location.reload();
}, 500);
});
// PAGE NAVIGATION
// About command
window.terminal.registerCommand("about", "About this site", () => {
window.location.href = "/about/";
});

View file

@ -0,0 +1,97 @@
// Utility Commands Module
// Useful utility commands
// Time command
window.terminal.registerCommand("time", "Display current time", () => {
const now = new Date();
window.terminal.print(`Current time: ${now.toLocaleString()}`);
});
// Calculator command
window.terminal.registerCommand("calc", "Simple calculator", (args) => {
if (args.length === 0) {
window.terminal.printError("Usage: calc <expression>");
window.terminal.print("Example: calc 5 + 3");
return;
}
try {
const expression = args.join(" ");
// Note: eval is dangerous in production! This is just for demo purposes
const result = eval(expression);
window.terminal.printSuccess(`Result: ${result}`);
} catch (error) {
window.terminal.printError("Invalid expression");
}
});
// Demo command
// window.terminal.registerCommand("demo", "Show demo content", () => {
// window.terminal.printSuccess("=== Demo Content ===");
// window.terminal.print("");
// window.terminal.print("This is regular text");
// window.terminal.printInfo("This is info text");
// window.terminal.printWarning("This is warning text");
// window.terminal.printError("This is error text");
// window.terminal.print("");
// window.terminal.printHTML(
// 'You can also use <strong>HTML</strong> with <a href="#">links</a>!',
// );
// });
// ASCII art command
// window.terminal.registerCommand("ascii", "Display ASCII art", () => {
// const art = `
// _____ _ _
// |_ _|__ _ __ _ __ ___ (_)_ __ __ _| |
// | |/ _ \\ '__| '_ \` _ \\| | '_ \\ / _\` | |
// | | __/ | | | | | | | | | | | (_| | |
// |_|\\___|_| |_| |_| |_|_|_| |_|\\__,_|_|
// `;
// window.terminal.print(art, "success");
// });
// ASCII art command
window.terminal.registerCommand("nerv", "Display NERV logo", () => {
const art = `
# ## %*###
#******************
#*******************
********************
%* **************************
*** #*****************************
** *******************************%
*# *********************************
%* %************************************
******************************************
*************************************
**********************# ******#*
*** *% %** %**********************
*%**# * ** #*************************
* *** * ** *************************
* %**# * **#####*************************#
* *** * ** * %**********************%
* %**#* ** #*********************
* *** ** %% ********************#
*% %* %** ** *******************
%******************
#********# #*******************
** #** ******************
** **# *** *************#
** #** **# ************
*******# **% %**********
** %** ** *%#*******#
** *** #**#* ******
** #**% *** ****#
%****% ***% * %***
#*
`;
window.terminal.print(art, "success");
});
// Greet command
window.terminal.registerCommand("greet", "Greet the user", (args) => {
const name = args.length > 0 ? args.join(" ") : "User";
window.terminal.printSuccess(`Hello, ${name}! Welcome to the terminal.`);
});

16
assets/js/init.js Normal file
View file

@ -0,0 +1,16 @@
// Terminal Initialization Script
// This script creates the terminal instance immediately
// so command modules can register commands
// Create global terminal instance immediately
window.terminal = new TerminalShell();
// Boot the terminal when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
terminal.boot();
});
} else {
// DOM already loaded
terminal.boot();
}

70
assets/js/lavalamp.js Normal file
View file

@ -0,0 +1,70 @@
(function () {
const lavaLamp = document.getElementById("lavaLamp");
let blobs = [];
let baseSpeed = 0.8;
function createBlob() {
const blob = document.createElement("div");
blob.className = "blob";
const size = Math.random() * 30 + 20; // Smaller blobs (20-50px)
const startY = Math.random() * 100; // Within ~150px height
const duration = (Math.random() * 8 + 12) / baseSpeed;
const maxX = 60 - size; // Adjusted for narrower tube (80px wide)
const startX = Math.random() * maxX;
blob.style.width = `${size}px`;
blob.style.height = `${size}px`;
blob.style.left = `${startX}px`;
blob.style.setProperty("--duration", `${duration}s`);
blob.style.setProperty("--start-x", "0px");
blob.style.setProperty("--start-y", `${startY}px`);
blob.style.setProperty("--mid1-x", `${Math.random() * 15 - 15}px`);
blob.style.setProperty("--mid1-y", `${Math.random() * -40 - 40}px`);
blob.style.setProperty("--mid2-x", `${Math.random() * 20 - 20}px`);
blob.style.setProperty("--mid2-y", `${Math.random() * -80 - 40}px`);
blob.style.setProperty("--mid3-x", `${Math.random() * 15 - 15}px`);
blob.style.setProperty("--mid3-y", `${Math.random() * -60 - 10}px`);
blob.style.setProperty("--scale1", (Math.random() * 0.3 + 1.0).toFixed(2));
blob.style.setProperty("--scale2", (Math.random() * 0.3 + 0.85).toFixed(2));
blob.style.setProperty("--scale3", (Math.random() * 0.3 + 0.95).toFixed(2));
blob.style.animationDelay = `${Math.random() * -20}s`;
return blob;
}
function updateBlobColors() {
const color1 = "#FF7800";
const color2 = "#E01B24";
const gradient = `radial-gradient(circle at 30% 30%, ${color1}, ${color2})`;
blobs.forEach((blob) => {
blob.style.background = gradient;
});
}
function updateLampBackground() {
const bg1 = "#F8E45C";
const bg2 = "#FF7800";
lavaLamp.style.background = `linear-gradient(180deg, ${bg1} 0%, ${bg2} 100%)`;
}
function updateBlobCount() {
const count = parseInt(6);
while (blobs.length > count) {
const blob = blobs.pop();
lavaLamp.removeChild(blob);
}
while (blobs.length < count) {
const blob = createBlob();
blobs.push(blob);
lavaLamp.appendChild(blob);
updateBlobColors();
}
}
function init() {
updateBlobCount();
updateLampBackground();
}
init();
})();

190
assets/js/terminal.js Normal file
View file

@ -0,0 +1,190 @@
// Terminal Shell System
class TerminalShell {
constructor() {
this.output = document.getElementById("output");
this.input = document.getElementById("input");
this.inputContainer = document.getElementById("input-container");
this.history = [];
this.historyIndex = -1;
this.commands = {};
this.setupEventListeners();
}
// Boot sequence
async boot() {
const bootMessages = [
" _ _ _____ ______ __",
" | \\ | | ____| _ \\ \\ / /",
" | \\| | _| | |_) \\ \\ / / ",
" | |\\ | |___| _ < \\ V / ",
" |_| \\_|_____|_| \\_\\ \\_/ ",
"",
"NERV OS v2.015 - MAGI System Interface",
"Initializing A.T. Field protocols...",
"CASPER... ONLINE",
"BALTHASAR... ONLINE",
"MELCHIOR... ONLINE",
"Synchronization ratio: 41.3%... 67.8%... 89.2%... OK",
"Loading Evangelion Unit-01 core drivers... OK",
"Mounting LCL interface... OK",
"Neural connection established... OK",
"",
"Running pattern analysis... PATTERN BLUE",
"",
"All systems optimal.",
"",
"",
];
for (let i = 0; i < bootMessages.length; i++) {
await this.sleep(100);
const line = document.createElement("div");
line.className = "output-line boot-line";
line.textContent = bootMessages[i];
line.style.animationDelay = "0s";
this.output.appendChild(line);
this.scrollToBottom();
}
this.printHTML(
'<span class="info">Type "help" for available commands.</span>',
);
this.printHTML(
'<span class="warning">This site is under contstruction. There\'s nothing of interest here yet.</span>',
);
this.inputContainer.classList.remove("hidden");
this.input.focus();
}
// Utility function for delays
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Setup event listeners
setupEventListeners() {
this.input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
this.executeCommand(this.input.value.trim());
this.input.value = "";
} else if (e.key === "ArrowUp") {
e.preventDefault();
this.navigateHistory("up");
} else if (e.key === "ArrowDown") {
e.preventDefault();
this.navigateHistory("down");
}
});
// Keep input focused
document.addEventListener("click", () => {
this.input.focus();
});
}
// Command history navigation
navigateHistory(direction) {
if (this.history.length === 0) return;
if (direction === "up") {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.input.value = this.history[this.historyIndex];
}
} else if (direction === "down") {
if (this.historyIndex > 0) {
this.historyIndex--;
this.input.value = this.history[this.historyIndex];
} else {
this.historyIndex = -1;
this.input.value = "";
}
}
}
// Execute command
executeCommand(commandString) {
if (!commandString) return;
// Echo the command
this.print(`> ${commandString}`);
// Add to history
this.history.unshift(commandString);
this.historyIndex = -1;
// Parse command and arguments
const parts = commandString.split(" ");
const command = parts[0].toLowerCase();
const args = parts.slice(1);
// Execute command
if (this.commands[command]) {
try {
this.commands[command](args);
} catch (error) {
this.printError(`Error executing command: ${error.message}`);
}
} else {
this.printError(`Command not found: ${command}`);
}
this.scrollToBottom();
}
// Register a new command
registerCommand(name, description, callback) {
this.commands[name.toLowerCase()] = callback;
this.commands[name.toLowerCase()].description = description;
}
// Print methods
print(text, className = "") {
const line = document.createElement("div");
line.className = `output-line ${className}`;
if (typeof text === "string") {
line.textContent = text;
} else {
line.appendChild(text);
}
this.output.appendChild(line);
}
printHTML(html, className = "") {
const line = document.createElement("div");
line.className = `output-line ${className}`;
line.innerHTML = html;
this.output.appendChild(line);
}
printError(text) {
this.print(text, "error");
}
printSuccess(text) {
this.print(text, "success");
}
printInfo(text) {
this.print(text, "info");
}
printWarning(text) {
this.print(text, "warning");
}
scrollToBottom() {
this.output.parentElement.scrollTop =
this.output.parentElement.scrollHeight;
}
// Clear the terminal
clear() {
this.output.innerHTML = "";
}
}

255
assets/sass/_normalize.scss Normal file
View file

@ -0,0 +1,255 @@
/*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */
/* Document
========================================================================== */
/**
* Use a better box model (opinionated).
*/
html {
box-sizing: border-box;
}
* {
box-sizing: inherit;
&::before, &::after {
box-sizing: inherit;
}
}
/**
* Use a more readable tab size (opinionated).
*/
:root {
-moz-tab-size: 4;
tab-size: 4;
}
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
/**
* Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
*/
/* Grouping content
========================================================================== */
/**
* Add the correct height in Firefox.
*/
hr {
height: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr[title] {
text-decoration: underline dotted;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b, strong {
font-weight: bolder;
}
/**
* 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
* 2. Correct the odd `em` font sizing in all browsers.
*/
code, kbd, samp, pre {
font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace;
/* 1 */
font-size: 1em;
/* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button, input, optgroup, select, textarea {
font-family: inherit;
/* 1 */
font-size: 100%;
/* 1 */
line-height: 1.15;
/* 1 */
margin: 0;
/* 2 */
}
/**
* Remove the inheritance of text transform in Edge and Firefox.
* 1. Remove the inheritance of text transform in Firefox.
*/
button, select {
/* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button, [type='button'], [type='reset'], [type='submit'] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner, [type='button']::-moz-focus-inner, [type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring, [type='button']:-moz-focusring, [type='reset']:-moz-focusring, [type='submit']:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.
*/
legend {
padding: 0;
}
/**
* Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/**
* Correct the cursor style of increment and decrement buttons in Safari.
*/
[type='number'] {
&::-webkit-inner-spin-button, &::-webkit-outer-spin-button {
height: auto;
}
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
&::-webkit-search-decoration {
-webkit-appearance: none;
}
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}

View file

@ -0,0 +1,23 @@
footer[role="contentinfo"] {
position: absolute;
bottom: 0;
right: 0;
z-index: 10000;
background: #000; // or whatever color you want
color: #0f0; // match your terminal green theme
padding: 5px;
text-align: center;
font-family: monospace;
border-top: 1px solid #0f0;
border-left: 1px solid #0f0;
border-top-left-radius: 5px;
a {
color: #0f0;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}

View file

@ -0,0 +1,122 @@
.lava-lamp-container {
position: absolute;
bottom: 20%;
left: 20%;
width: 80px;
height: 150px;
display: flex;
flex-direction: column;
align-items: center;
z-index: 999;
}
.lamp-cap {
width: 60%;
height: 10%;
background: linear-gradient(180deg, #c0c0c0 0%, #888 50%, #666 100%);
border-radius: 60px 60px 0 0;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.5),
inset 0 2px 4px rgba(255, 255, 255, 0.3);
position: relative;
z-index: 10;
}
.lava-lamp {
position: relative;
width: 100%;
height: 102%; /*Being above 100% fixes the occasional gap when resizing the page, theoretically */
background: var(--lamp-bg, linear-gradient(180deg, #2a1a4a 0%, #1a0a3a 100%));
clip-path: polygon(20% 0, 80% 0, 100% 100%, 0% 100%);
overflow: hidden;
box-shadow:
inset 0 0 60px rgba(0, 0, 0, 0.3),
inset -10px 0 40px rgba(255, 255, 255, 0.1),
inset 10px 0 40px rgba(0, 0, 0, 0.2);
filter: drop-shadow(0 10px 30px rgba(0, 0, 0, 0.5));
}
.lava-lamp::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.15) 20%,
rgba(255, 255, 255, 0.05) 40%,
transparent 60%
);
pointer-events: none;
}
.lamp-base {
width: 100%;
height: 20%;
background: linear-gradient(180deg, #888 0%, #555 40%, #333 100%);
border-radius: 0 0 40px 40px / 0 0 60px 60px;
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.6),
inset 0 5px 10px rgba(255, 255, 255, 0.2),
inset 0 -5px 10px rgba(0, 0, 0, 0.5);
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.blob {
position: absolute;
border-radius: 50%;
background: var(
--blob-color,
radial-gradient(circle at 30% 30%, #ff6b9d, #c44569)
);
filter: url(#goo);
animation: float var(--duration) ease-in-out infinite;
opacity: 0.95;
mix-blend-mode: normal;
z-index: 3;
}
@keyframes float {
0%,
100% {
transform: translate(var(--start-x), var(--start-y)) scale(1);
}
25% {
transform: translate(var(--mid1-x), var(--mid1-y)) scale(var(--scale1, 1.1));
}
50% {
transform: translate(var(--mid2-x), var(--mid2-y)) scale(var(--scale2, 0.9));
}
75% {
transform: translate(var(--mid3-x), var(--mid3-y))
scale(var(--scale3, 1.05));
}
}
.lamp-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-35%, -50%) rotate(85deg);
font-size: 30px;
font-weight: bold;
color: rgba(224, 27, 36, 0);
transition: color 0.5s ease;
pointer-events: none;
z-index: 1;
letter-spacing: 2px;
}
.lava-lamp:hover .lamp-text {
color: rgba(224, 27, 36, 0.7);
}
.lava-lamp:hover {
cursor: pointer;
}

View file

@ -0,0 +1,28 @@
.nowplayingcard {
margin: auto;
}
.nowplayingcontainer-inner {
transition: 0.3s;
display: inline-flex;
.trackInfo {
width: 100%;
}
#album {
display: none;
}
}
img#trackart {
transition: 0.3s;
width: 100%;
height: 100%;
object-fit: cover;
filter: grayscale(60%);
}
img#trackart[src=""] {
display: none;
}

View file

@ -0,0 +1,99 @@
#terminal {
padding: 3px;
height: 100vh;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.output-line {
margin: 2px 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.boot-line {
opacity: 0;
animation: fadeIn 0.1s forwards;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.input-line {
display: flex;
align-items: center;
margin-top: 10px;
}
.prompt {
color: #00ff00;
margin-right: 8px;
}
#input {
background: transparent;
border: none;
color: #00ff00;
font-family: monospace;
font-size: 12px;
outline: none;
flex: 1;
caret-color: #00ff00;
}
.cursor {
display: inline-block;
width: 8px;
height: 18px;
background: #00ff00;
animation: blink 1s infinite;
margin-left: 2px;
}
@keyframes blink {
0%,
49% {
opacity: 1;
}
50%,
100% {
opacity: 0;
}
}
.error {
color: #ff4444;
}
.success {
color: #44ff44;
}
.info {
color: #00ffff;
}
.warning {
color: #ff9900;
}
#terminal::-webkit-scrollbar {
width: 10px;
}
#terminal::-webkit-scrollbar-track {
background: #1a1a1a;
}
#terminal::-webkit-scrollbar-thumb {
background: #00ff00;
border-radius: 5px;
}
.hidden {
display: none;
}

View file

@ -0,0 +1,191 @@
/* VU Meter on desk */
.vu-meter {
position: absolute;
bottom: 18%;
right: 30%;
width: 120px;
height: 60px;
z-index: 8;
}
.vu-meter-body {
width: 100%;
height: 100%;
background: linear-gradient(180deg, #2a2a2a 0%, #1a1a1a 100%);
border: 2px solid #333;
border-radius: 4px;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.7),
inset 0 2px 4px rgba(255, 255, 255, 0.1),
inset 0 -2px 4px rgba(0, 0, 0, 0.3);
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
}
.vu-meter-screen {
flex: 1;
background: #0a0a0a;
border-radius: 2px;
padding: 6px 4px;
position: relative;
overflow: hidden;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.8);
}
.vu-bars {
display: flex;
align-items: flex-end;
justify-content: space-around;
height: 100%;
gap: 2px;
}
.vu-bar {
flex: 1;
background: linear-gradient(
to top,
#0f0 0%,
#0f0 60%,
#ff0 60%,
#ff0 85%,
#f00 85%,
#f00 100%
);
border-radius: 1px;
box-shadow:
0 0 4px rgba(0, 255, 0, 0.6),
inset 0 0 2px rgba(255, 255, 255, 0.2);
animation: vu-bounce 1.5s ease-in-out infinite;
animation-delay: var(--delay);
height: var(--height);
min-height: 10%;
}
@keyframes vu-bounce {
0%,
100% {
height: var(--height);
filter: brightness(1);
}
25% {
height: calc(var(--height) * 0.6);
filter: brightness(0.8);
}
50% {
height: calc(var(--height) * 1.2);
filter: brightness(1.2);
}
75% {
height: calc(var(--height) * 0.8);
filter: brightness(0.9);
}
}
/* Peak indicator line */
.vu-peak-line {
position: absolute;
top: 15%;
left: 4px;
right: 4px;
height: 1px;
background: #f00;
opacity: 0.6;
box-shadow: 0 0 3px #f00;
animation: peak-pulse 2s ease-in-out infinite;
}
@keyframes peak-pulse {
0%,
100% {
opacity: 0.6;
top: 15%;
}
50% {
opacity: 0.3;
top: 25%;
}
}
/* LED indicators */
.vu-leds {
display: flex;
justify-content: center;
gap: 8px;
}
.vu-led {
width: 5px;
height: 5px;
border-radius: 50%;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
.vu-led.green {
background: #0f0;
box-shadow:
0 0 6px #0f0,
inset 0 1px 2px rgba(0, 0, 0, 0.5);
animation: led-blink-green 2s ease-in-out infinite;
}
.vu-led.yellow {
background: #880;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
animation: led-blink-yellow 2s ease-in-out infinite;
}
.vu-led.red {
background: #400;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
animation: led-blink-red 2s ease-in-out infinite;
}
@keyframes led-blink-green {
0%,
100% {
background: #0f0;
box-shadow:
0 0 6px #0f0,
inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
50% {
background: #0a0;
box-shadow:
0 0 3px #0a0,
inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
}
@keyframes led-blink-yellow {
0%,
40%,
100% {
background: #880;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
50%,
60% {
background: #ff0;
box-shadow:
0 0 6px #ff0,
inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
}
@keyframes led-blink-red {
0%,
85%,
100% {
background: #400;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
90%,
95% {
background: #f00;
box-shadow:
0 0 6px #f00,
inset 0 1px 2px rgba(0, 0, 0, 0.5);
}
}

1762
assets/sass/style.scss Normal file

File diff suppressed because it is too large Load diff