Last active
September 30, 2024 07:41
-
-
Save pgagnidze/303cdd60bc71ada6917a3cc0c4d415b1 to your computer and use it in GitHub Desktop.
Personal website on omg.lol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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