Skip to content

Instantly share code, notes, and snippets.

@pgagnidze
Last active September 30, 2024 07:41
Show Gist options
  • Save pgagnidze/303cdd60bc71ada6917a3cc0c4d415b1 to your computer and use it in GitHub Desktop.
Save pgagnidze/303cdd60bc71ada6917a3cc0c4d415b1 to your computer and use it in GitHub Desktop.
Personal website on omg.lol
<!DOCTYPE html>
<html lang="en">
<head>
<title>
Papuna Gagnidze | DevOps Engineer
</title>
<meta charset="utf-8" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Papuna Gagnidze | DevOps Engineer" />
<meta property="og:description" content="Personal website of Papuna Gagnidze, a DevOps Engineer with expertise in QA, Networking, and various development fields." />
<meta property="og:image" content="https://profiles.cache.lol/papu/picture?v=1727682048.2386" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico?v=1727682036" />
<link href="https://cdn.cache.lol/profiles/themes/css/base.css?v=2024-09-10" rel="stylesheet" />
<link href="https://cdn.cache.lol/profiles/themes/css/nord-dark.css" rel="stylesheet" />
<style>
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap");
:root {
--text-dark: #e0def4;
--highlight: #f6c177;
--secondary: #31748f;
--foam: #9ccfd8;
--transparent-dark: rgba(25, 23, 36, 0.8);
}
body {
margin: 0;
padding: 20px;
font-family: "Inconsolata", monospace;
line-height: 1.5;
font-size: 16px;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow-x: hidden;
}
a,
a:link,
a:visited {
color: var(--foam);
text-decoration: none;
transition: color 0.3s;
border-bottom: none !important;
}
a:hover {
color: var(--highlight);
}
main {
max-width: 600px;
margin: 0 auto;
padding: 2em;
text-align: center;
}
#overview {
font-size: 1.1rem;
text-align: center;
}
#terminal-icon {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
cursor: pointer;
font-size: 48px;
color: var(--foam);
transition: transform 0.3s ease;
}
#terminal-icon:hover {
transform: scale(1.1);
}
#terminal-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 40vh;
min-height: 200px;
max-height: 90vh;
background: var(--transparent-dark);
color: var(--text-dark);
z-index: 999;
transform: translateY(-100%);
display: flex;
flex-direction: column;
transition: transform 0.3s ease-out;
backdrop-filter: blur(10px);
font-size: 14px;
overflow: hidden;
resize: none;
}
#terminal-output {
flex-grow: 1;
padding: 10px 15px;
overflow-y: auto;
font-family: monospace;
}
#terminal-input-container {
display: flex;
padding: 10px 15px;
border-top: 1px solid var(--secondary);
}
#terminal-input {
flex-grow: 1;
background: transparent;
border: none;
font-family: inherit;
color: inherit;
outline: none;
font-size: 14px;
}
#terminal-resize-handle {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 5px;
background: var(--secondary);
cursor: ns-resize;
border-radius: 2px 2px 0 0;
}
#metadata-container {
display: flex;
justify-content: center;
align-items: center;
margin: 0.5em 0;
}
#occupation,
#location {
padding: 0 0.5em;
color: var(--text-dark);
}
#occupation i,
#location i {
color: var(--highlight);
}
#social-links {
display: flex;
justify-content: center;
margin: 20px 0;
}
#social-links a {
margin: 0 10px;
font-size: 28px;
color: var(--foam);
transition: color 0.3s, transform 0.3s;
}
#social-links a:hover {
color: var(--highlight);
transform: scale(1.2);
}
</style>
<script>
document.addEventListener("DOMContentLoaded", () => {
const body = document.body;
const terminalIcon = createElementWithAttributes("div", { id: "terminal-icon", class: "icon", innerHTML: "⌨" });
const terminalContainer = createElementWithAttributes("div", {
id: "terminal-container",
innerHTML: `
<div id="terminal-output"></div>
<div id="terminal-input-container">
<span>> </span>
<input id="terminal-input" aria-label="Terminal Input" type="text" autofocus />
</div>
<div id="terminal-resize-handle"></div>
`,
});
body.append(terminalIcon, terminalContainer);
const terminalOutput = terminalContainer.querySelector("#terminal-output");
const terminalInput = terminalContainer.querySelector("#terminal-input");
const resizeHandle = terminalContainer.querySelector("#terminal-resize-handle");
let isTerminalOpen = false;
let isResizing = false;
let resizeCooldown = false;
let commandHistory = [];
let historyIndex = -1;
let gameActive = false;
let secretNumber;
const asciiArt = `
_________________________________________
/ Welcome to Papu's Terminal! \\
\\ Type 'help' to see available commands. /
-----------------------------------------
\\ ^__^
\\ (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||
`;
const motd = `${asciiArt}`;
const commands = {
help: () => "Available commands: help, about, skills, projects, clear, history, whoami, pwd, date, echo, game",
about: () => "Papuna Gagnidze is a developer and tech enthusiast from Georgia.",
skills: () => "Skills include: DevOps, QA, Networking, Many kinds of development.",
projects: () => "Projects include: Ena, Bolbo, AI Printed Art, and Owloops!",
clear: () => {
terminalOutput.innerHTML = "";
displayMOTD();
return "";
},
history: () => commandHistory.join("\n"),
whoami: () => "guest",
pwd: () => "/home/guest",
date: () => new Date().toString(),
echo: (args) => args.join(" "),
game: startGame,
};
terminalIcon.addEventListener("click", toggleTerminal);
terminalInput.addEventListener("keydown", handleKeyDown);
resizeHandle.addEventListener("mousedown", startResize);
function createElementWithAttributes(tag, attributes) {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => (element[key] = value));
return element;
}
function toggleTerminal() {
isTerminalOpen = !isTerminalOpen;
terminalContainer.style.transform = `translateY(${isTerminalOpen ? "0" : "-100%"})`;
if (isTerminalOpen) {
terminalInput.focus();
if (terminalOutput.innerHTML === "") {
displayMOTD();
}
document.addEventListener("mousedown", handleDocumentClick);
} else {
document.removeEventListener("mousedown", handleDocumentClick);
}
}
function handleKeyDown(e) {
if (e.key === "Enter") {
const fullCommand = terminalInput.value.trim();
const [command, ...args] = fullCommand.split(" ");
if (command) {
commandHistory.push(fullCommand);
historyIndex = commandHistory.length;
let output;
if (gameActive) {
output = handleGameGuess(command);
} else {
const lowerCommand = command.toLowerCase();
if (commands.hasOwnProperty(lowerCommand)) {
output = commands[lowerCommand](args);
} else {
output = `Command not found: ${command}`;
}
}
terminalOutput.innerHTML += `<div>> ${fullCommand}</div><div>${output}</div>`;
terminalInput.value = "";
terminalOutput.scrollTop = terminalOutput.scrollHeight;
}
} else if (e.key === "Tab") {
e.preventDefault();
autoComplete();
} else if (e.key === "ArrowUp") {
e.preventDefault();
navigateHistory(-1);
} else if (e.key === "ArrowDown") {
e.preventDefault();
navigateHistory(1);
} else if (e.key === "c" && e.ctrlKey) {
e.preventDefault();
terminalInput.value = "";
terminalOutput.innerHTML += "<div>^C</div>";
if (gameActive) {
gameActive = false;
return "Game aborted.";
}
}
}
function displayMOTD() {
terminalOutput.innerHTML += `<pre>${motd}</pre>`;
}
function handleDocumentClick(e) {
if (!isResizing && !resizeCooldown && !terminalContainer.contains(e.target) && e.target !== terminalIcon) {
toggleTerminal();
}
}
function autoComplete() {
const originalCase = terminalInput.value;
const input = originalCase.toLowerCase();
const possibilities = Object.keys(commands).filter((cmd) => cmd.startsWith(input));
if (possibilities.length === 1) {
terminalInput.value = originalCase + possibilities[0].slice(input.length);
} else if (possibilities.length > 1) {
terminalOutput.innerHTML += `<div>> ${originalCase}</div><div>${possibilities.join(" ")}</div>`;
}
}
function navigateHistory(direction) {
historyIndex += direction;
if (historyIndex < 0) historyIndex = 0;
if (historyIndex > commandHistory.length) historyIndex = commandHistory.length;
terminalInput.value = commandHistory[historyIndex] || "";
}
function startGame() {
gameActive = true;
secretNumber = Math.floor(Math.random() * 100) + 1;
return "I'm thinking of a number between 1 and 100. Can you guess it? (Type 'quit' to end the game)";
}
function handleGameGuess(guess) {
if (guess.toLowerCase() === "quit") {
gameActive = false;
return `Game over. The number was ${secretNumber}.`;
}
const number = parseInt(guess);
if (isNaN(number)) {
return "Please enter a valid number or 'quit' to end the game.";
}
if (number < secretNumber) {
return "Too low! Try again.";
}
if (number > secretNumber) {
return "Too high! Try again.";
}
gameActive = false;
return `Congratulations! You guessed the number ${secretNumber}!`;
}
function startResize(e) {
isResizing = true;
resizeCooldown = true;
document.addEventListener("mousemove", resize);
document.addEventListener("mouseup", stopResize);
e.preventDefault();
e.stopPropagation();
}
function resize(e) {
if (isResizing) {
const containerRect = terminalContainer.getBoundingClientRect();
const newHeight = e.clientY - containerRect.top;
const maxHeight = window.innerHeight * 0.9;
const minHeight = 200;
if (newHeight > minHeight && newHeight < maxHeight) {
terminalContainer.style.height = `${newHeight}px`;
}
}
}
function stopResize(e) {
isResizing = false;
document.removeEventListener("mousemove", resize);
document.removeEventListener("mouseup", stopResize);
e.preventDefault();
e.stopPropagation();
setTimeout(() => {
resizeCooldown = false;
}, 200);
}
terminalContainer.addEventListener("mousedown", (e) => {
e.stopPropagation();
});
terminalContainer.addEventListener("click", (e) => {
if (e.target !== terminalInput) {
terminalInput.focus();
}
});
});
</script>
</head>
<body>
<main class="h-card">
<div id="profile-picture-container">
<img class="u-photo" alt="papu" id="profile-picture" src="https://profiles.cache.lol/papu/picture?v=1727682036" />
</div>
<h1 class="p-name" id="name">
Papuna Gagnidze <a id="verification" style="text-decoration: none; border: 0;" href="https://home.omg.lol/lookup/papu"><i class="omg-icon omg-verified"></i></a>
</h1>
<div class="metadata p-job-title" id="occupation"><i class="fa-solid fa-fw fa-briefcase"></i> DevOps Engineer</div>
<div class="metadata p-region" id="location"><i class="fa-solid fa-fw fa-location-dot"></i> Not that Georgia</div>
<div id="overview">
<p>
I started with customizing my <a class="u-url" rel="me" href="https://forum.xda-developers.com/t/help-me-please-im-formated-system-on-x8.1327078/">Xperia X8</a>, which led me down the path of using
<a class="u-url" rel="me" href="https://bbs.archlinux.org/viewtopic.php?pid=1308523#p1308523">Linux</a>. At 16, I earned my
<a class="u-url" rel="me" href="https://drive.proton.me/urls/TDVW670PXC#kqyYRdVkJj3k">CCNA</a> and later my <a class="u-url" rel="me" href="https://drive.proton.me/urls/FEAYMYD4QG#zYjjhLpu8rX8">CCNP</a> certification.
Over the years, I interviewed over 200 QA engineers, built <a class="u-url" rel="me" href="https://youtube.com/shorts/dmbboR47-c4">AI Printed Art</a> and relaunched
<a class="u-url" rel="me" href="https://www.owloops.com/">Owloops</a> with added features. I went on to create <a class="u-url" rel="me" href="https://ena-lang.org/">Ena</a>, a programming language in Georgian, and
developed <a class="u-url" rel="me" href="https://bolbo.live">Bolbo</a>, a multiplayer football game.
</p>
</div>
<div id="social-links">
<p>
<a class="u-url" rel="me" target="_blank" href="https://www.linkedin.com/in/papuna-gagnidze"><i class="fa-solid fa-brands fa-linkedin"></i></a>
<a class="u-url" rel="me" target="_blank" href="https://papu.substack.com"><i class="fa-solid fa-solid fa-bookmark"></i></a>
<a class="u-url" rel="me" target="_blank" href="https://github.com/pgagnidze"><i class="fa-solid fa-brands fa-github"></i></a>
<a class="u-url" rel="me" target="_blank" href="https://twitter.com/papungag"><i class="fa-solid fa-brands fa-twitter"></i></a>
</p>
</div>
<div id="footer"></div>
</main>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment