feat: interactive terminal portfolio with dino game + resume PDF download
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
FROM nginx:alpine
|
||||
COPY index.html /usr/share/nginx/html/index.html
|
||||
COPY Omair_Saleh_Resume.pdf /usr/share/nginx/html/Omair_Saleh_Resume.pdf
|
||||
EXPOSE 80
|
||||
Binary file not shown.
+934
@@ -0,0 +1,934 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>omair@quikcue:~$</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0a0a0a;
|
||||
--green: #00ff41;
|
||||
--red: #ff6b6b;
|
||||
--blue: #61dafb;
|
||||
--gold: #ffd700;
|
||||
--dim: #4a4a4a;
|
||||
--mid: #888;
|
||||
--white: #e0e0e0;
|
||||
--font: 'JetBrains Mono', monospace;
|
||||
--fs: clamp(12px, 2.2vw, 15px);
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: var(--bg);
|
||||
color: var(--green);
|
||||
font-family: var(--font);
|
||||
font-size: var(--fs);
|
||||
line-height: 1.6;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#terminal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
/* ── Top bar ── */
|
||||
#topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 0 8px;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
.dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
||||
.dot-r { background: #ff5f57; }
|
||||
.dot-y { background: #febc2e; }
|
||||
.dot-g { background: #28c840; }
|
||||
#topbar-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: var(--mid);
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* ── Output area ── */
|
||||
#output {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px 0;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
#output::-webkit-scrollbar { width: 4px; }
|
||||
#output::-webkit-scrollbar-track { background: transparent; }
|
||||
#output::-webkit-scrollbar-thumb { background: #222; border-radius: 2px; }
|
||||
|
||||
.line {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
min-height: 1.6em;
|
||||
}
|
||||
.line.cmd { color: var(--green); }
|
||||
.line.out { color: var(--white); }
|
||||
.line.err { color: var(--red); }
|
||||
.line.hi { color: var(--blue); }
|
||||
.line.gold { color: var(--gold); }
|
||||
.line.dim { color: var(--dim); }
|
||||
.line.mid { color: var(--mid); }
|
||||
.line.green { color: var(--green); }
|
||||
.line.empty { height: 0.8em; }
|
||||
|
||||
/* ── Prompt row ── */
|
||||
#input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
padding-top: 4px;
|
||||
}
|
||||
#prompt-label {
|
||||
color: var(--green);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
#prompt-label span.at { color: var(--blue); }
|
||||
#prompt-label span.host { color: var(--gold); }
|
||||
#prompt-label span.sep { color: var(--mid); }
|
||||
#prompt-label span.dir { color: var(--green); }
|
||||
#prompt-label span.dol { color: var(--white); }
|
||||
|
||||
#cmd-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--green);
|
||||
font-family: var(--font);
|
||||
font-size: var(--fs);
|
||||
caret-color: var(--green);
|
||||
min-width: 0;
|
||||
}
|
||||
#cmd-input::selection { background: #00ff4133; }
|
||||
|
||||
/* Blinking cursor simulation (we keep native caret + add blink effect) */
|
||||
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
|
||||
.cursor-block {
|
||||
display: inline-block;
|
||||
width: 0.6em;
|
||||
height: 1em;
|
||||
background: var(--green);
|
||||
vertical-align: text-bottom;
|
||||
animation: blink 1.1s step-start infinite;
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
/* ── Dino game ── */
|
||||
#dino-container {
|
||||
display: none;
|
||||
border: 1px solid #1e1e1e;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin: 8px 0;
|
||||
background: #050505;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
#dino-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
#dino-score-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--mid);
|
||||
font-size: 0.85em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
#dino-msg {
|
||||
text-align: center;
|
||||
color: var(--gold);
|
||||
font-size: 0.85em;
|
||||
min-height: 1.4em;
|
||||
}
|
||||
|
||||
/* ── Boot animation ── */
|
||||
@keyframes fadeIn { from{opacity:0} to{opacity:1} }
|
||||
.boot-line { animation: fadeIn 0.1s ease forwards; opacity: 0; }
|
||||
|
||||
/* ── Progress bar ── */
|
||||
.progress-bar { color: var(--green); letter-spacing: -1px; }
|
||||
|
||||
/* ── Help table ── */
|
||||
.help-cmd { color: var(--gold); }
|
||||
.help-sep { color: var(--dim); }
|
||||
.help-desc { color: var(--white); }
|
||||
|
||||
/* Scrollbar for Firefox */
|
||||
#output { scrollbar-width: thin; scrollbar-color: #222 transparent; }
|
||||
|
||||
/* Mobile touch — larger tap target for input */
|
||||
@media (max-width: 600px) {
|
||||
#topbar-title { display: none; }
|
||||
#terminal { padding: 0 8px 8px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal">
|
||||
<div id="topbar">
|
||||
<div class="dot dot-r"></div>
|
||||
<div class="dot dot-y"></div>
|
||||
<div class="dot dot-g"></div>
|
||||
<div id="topbar-title">omair@quikcue — terminal portfolio</div>
|
||||
</div>
|
||||
|
||||
<div id="output"></div>
|
||||
|
||||
<div id="input-row">
|
||||
<div id="prompt-label">
|
||||
<span class="at">omair</span><span class="sep">@</span><span class="host">quikcue</span><span class="sep">:</span><span class="dir">~</span><span class="dol">$</span>
|
||||
</div>
|
||||
<input id="cmd-input" type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" aria-label="terminal input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dino game (hidden until invoked) -->
|
||||
<div id="dino-container" style="display:none; position:fixed; bottom:0; left:0; right:0; z-index:100; background:#050505; border-top:1px solid #1e1e1e; padding:10px 16px;">
|
||||
<div id="dino-score-bar">
|
||||
<span>DINO RUNNER</span>
|
||||
<span>SCORE: <span id="dino-score-val">0</span> HI: <span id="dino-hi-val">0</span></span>
|
||||
</div>
|
||||
<canvas id="dino-canvas" width="800" height="120"></canvas>
|
||||
<div id="dino-msg">SPACE / ↑ to jump | ESC to quit</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
/* ─────────────────────────── helpers ─────────────────────────── */
|
||||
const out = document.getElementById('output');
|
||||
const inp = document.getElementById('cmd-input');
|
||||
|
||||
let dinoActive = false;
|
||||
|
||||
function scrollBottom() {
|
||||
out.scrollTop = out.scrollHeight;
|
||||
}
|
||||
|
||||
function addLine(text = '', cls = 'out') {
|
||||
if (text === '') { const d = document.createElement('div'); d.className='line empty'; out.appendChild(d); scrollBottom(); return; }
|
||||
// Support newlines inside text
|
||||
text.split('\n').forEach(t => {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'line ' + cls;
|
||||
d.textContent = t;
|
||||
out.appendChild(d);
|
||||
});
|
||||
scrollBottom();
|
||||
}
|
||||
|
||||
function addHTML(html, cls = 'out') {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'line ' + cls;
|
||||
d.innerHTML = html;
|
||||
out.appendChild(d);
|
||||
scrollBottom();
|
||||
}
|
||||
|
||||
function printPrompt(cmd) {
|
||||
addHTML(
|
||||
`<span style="color:var(--blue)">omair</span><span style="color:var(--mid)">@</span><span style="color:var(--gold)">quikcue</span><span style="color:var(--mid)">:</span><span style="color:var(--green)">~</span><span style="color:var(--white)">$</span> <span style="color:var(--green)">${escHtml(cmd)}</span>`,
|
||||
'cmd'
|
||||
);
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
/* ─────────────────────────── command history ─────────────────── */
|
||||
const history = [];
|
||||
let histIdx = -1;
|
||||
|
||||
/* ─────────────────────────── commands ───────────────────────── */
|
||||
const COMMANDS = ['help','about','experience','skills','projects','stats','contact','git log','cat resume.pdf','download','sudo hire omair','dino','play dino','game','clear','exit'];
|
||||
const ALIASES = { 'game': 'dino', 'play dino': 'dino' };
|
||||
|
||||
function runCommand(raw) {
|
||||
const cmd = raw.trim();
|
||||
if (!cmd) return;
|
||||
|
||||
// history
|
||||
if (history[0] !== cmd) history.unshift(cmd);
|
||||
if (history.length > 50) history.pop();
|
||||
histIdx = -1;
|
||||
|
||||
if (dinoActive) { addLine('Game active. Press ESC to quit.', 'err'); return; }
|
||||
|
||||
printPrompt(cmd);
|
||||
|
||||
const key = ALIASES[cmd.toLowerCase()] || cmd.toLowerCase();
|
||||
|
||||
switch (key) {
|
||||
case 'help': cmdHelp(); break;
|
||||
case 'about': cmdAbout(); break;
|
||||
case 'experience': cmdExperience(); break;
|
||||
case 'skills': cmdSkills(); break;
|
||||
case 'projects': cmdProjects(); break;
|
||||
case 'stats': cmdStats(); break;
|
||||
case 'contact': cmdContact(); break;
|
||||
case 'git log': cmdGitLog(); break;
|
||||
case 'cat resume.pdf': cmdCatResume(); break;
|
||||
case 'download': cmdDownload(); break;
|
||||
case 'sudo hire omair': cmdHire(); break;
|
||||
case 'dino': cmdDino(); break;
|
||||
case 'clear': cmdClear(); break;
|
||||
case 'exit': cmdExit(); break;
|
||||
default:
|
||||
addLine(`Command not found: ${cmd}. Type 'help' for available commands.`, 'err');
|
||||
}
|
||||
}
|
||||
|
||||
/* ── help ─────────────────────────────────────────────────────── */
|
||||
function cmdHelp() {
|
||||
addLine();
|
||||
addLine(' Available Commands', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
const rows = [
|
||||
['about', 'Who is Omair Saleh'],
|
||||
['experience', 'Career timeline (12 years)'],
|
||||
['skills', 'Languages, frameworks, tools'],
|
||||
['projects', 'Notable production projects'],
|
||||
['stats', 'Git contribution stats'],
|
||||
['contact', 'Email, LinkedIn, phone'],
|
||||
['git log', 'Recent commit history'],
|
||||
['cat resume.pdf', 'Easter egg 🐣'],
|
||||
['download', 'Get the resume PDF'],
|
||||
['sudo hire omair', 'The most important command'],
|
||||
['dino', 'Play the dinosaur game 🦕'],
|
||||
['clear', 'Clear the terminal'],
|
||||
['exit', 'Say goodbye'],
|
||||
];
|
||||
rows.forEach(([c, d]) => {
|
||||
addHTML(
|
||||
` <span class="help-cmd">${c.padEnd(20)}</span><span class="help-sep">— </span><span class="help-desc">${d}</span>`
|
||||
);
|
||||
});
|
||||
addLine();
|
||||
addLine(' Tip: Use ↑ / ↓ for history, Tab to autocomplete.', 'dim');
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── about ────────────────────────────────────────────────────── */
|
||||
function cmdAbout() {
|
||||
addLine();
|
||||
addLine(' Omair Saleh', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine(' Senior Full-Stack Engineer with 12 years of experience.', 'out');
|
||||
addLine(' Builds web apps, payment platforms, data pipelines,', 'out');
|
||||
addLine(' and AI-enabled workflows.', 'out');
|
||||
addLine();
|
||||
addLine(' Shipped 29+ production applications across fintech,', 'out');
|
||||
addLine(' logistics, nonprofit, and SaaS.', 'out');
|
||||
addLine();
|
||||
addLine(' Remote (UTC+8) · Malaysia', 'hi');
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── experience ───────────────────────────────────────────────── */
|
||||
function cmdExperience() {
|
||||
addLine();
|
||||
addLine(' Career Timeline', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine();
|
||||
|
||||
const roles = [
|
||||
{
|
||||
branch: '┌─',
|
||||
title: 'Systems & Automation Consultant',
|
||||
co: 'QuikCue',
|
||||
period: 'Jul 2025 – Present',
|
||||
lines: [
|
||||
'│ Built 8 production apps in 7 weeks. SaaS pledge platform,',
|
||||
'│ AI assistant with 37 tools, clinic management, analytics dashboards.',
|
||||
]
|
||||
},
|
||||
{
|
||||
branch: '├─',
|
||||
title: 'Lead Full-Stack Developer',
|
||||
co: 'Charity Right',
|
||||
period: 'Oct 2024 – Present',
|
||||
lines: [
|
||||
'│ Sole tech hire for UK charity (~$4M/year). Built donation data',
|
||||
'│ warehouse, Next.js checkout, 25+ analytics reports, fraud prevention.',
|
||||
]
|
||||
},
|
||||
{
|
||||
branch: '├─',
|
||||
title: 'Technical Operations Lead',
|
||||
co: 'C.I.E.F Worldwide',
|
||||
period: 'Sep 2018 – Sep 2024',
|
||||
lines: [
|
||||
'│ 6 years, 4,300+ commits. Currency exchange platform, shipping portal,',
|
||||
'│ 1688/Alibaba integration, billing engine.',
|
||||
]
|
||||
},
|
||||
{
|
||||
branch: '├─',
|
||||
title: 'Full-Stack Developer',
|
||||
co: 'IP ServerOne',
|
||||
period: 'Sep 2017 – Sep 2018',
|
||||
lines: []
|
||||
},
|
||||
{
|
||||
branch: '├─',
|
||||
title: 'Independent Web Developer',
|
||||
co: 'MediaSkip Enterprise',
|
||||
period: 'Sep 2016 – Sep 2017',
|
||||
lines: []
|
||||
},
|
||||
{
|
||||
branch: '└─',
|
||||
title: 'Web Developer',
|
||||
co: 'Monkey Media Web Agency',
|
||||
period: 'Dec 2014 – Dec 2016',
|
||||
lines: []
|
||||
},
|
||||
];
|
||||
|
||||
roles.forEach((r, i) => {
|
||||
addHTML(
|
||||
` <span style="color:var(--dim)">${r.branch}</span> <span style="color:var(--gold)">${r.title}</span>` +
|
||||
` <span style="color:var(--dim)">──</span> <span style="color:var(--blue)">${r.co}</span>` +
|
||||
` <span style="color:var(--mid)">${r.period}</span>`
|
||||
);
|
||||
r.lines.forEach(l => addLine(' ' + l, 'out'));
|
||||
if (i < roles.length - 1 && r.lines.length > 0) addLine(' │', 'dim');
|
||||
else if (i < roles.length - 1 && r.lines.length === 0) { /* no gap for short entries */ }
|
||||
});
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── skills ───────────────────────────────────────────────────── */
|
||||
function cmdSkills() {
|
||||
addLine();
|
||||
addLine(' Technical Skills', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine();
|
||||
|
||||
const groups = [
|
||||
{ label: 'Languages ', items: 'TypeScript · JavaScript · PHP · Python · SQL · Shell', color: 'var(--green)' },
|
||||
{ label: 'Frameworks ', items: 'Laravel · Next.js · React · Vue.js · Filament · Prisma', color: 'var(--blue)' },
|
||||
{ label: 'Payments ', items: 'Stripe · GoCardless · PayPal · Billplz · Apple Pay', color: 'var(--gold)' },
|
||||
{ label: 'AI ', items: 'GPT-5 · Gemini · Agent Orchestration · Generative UI', color: 'var(--blue)' },
|
||||
{ label: 'Infra ', items: 'Docker · Nginx · Traefik · PostgreSQL · MySQL · Redis', color: 'var(--mid)' },
|
||||
{ label: 'Integrations', items: 'WhatsApp · Auth0 · 1688/Alibaba · Meta Ads · N3O CRM', color: 'var(--green)' },
|
||||
];
|
||||
|
||||
groups.forEach(g => {
|
||||
addHTML(
|
||||
` <span style="color:var(--dim)">${g.label}</span> <span style="color:${g.color}">${g.items}</span>`
|
||||
);
|
||||
});
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── projects ─────────────────────────────────────────────────── */
|
||||
function cmdProjects() {
|
||||
addLine();
|
||||
addLine(' Notable Projects', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine();
|
||||
|
||||
const projects = [
|
||||
{ id: '1', name: 'PledgeNow ', desc: 'SaaS pledge-to-payment platform (Stripe Connect, GoCardless, WhatsApp)' },
|
||||
{ id: '2', name: 'Charity Right ', desc: 'Donation platform, data warehouse, 25+ analytics reports' },
|
||||
{ id: '3', name: 'Exchange 2.0 ', desc: 'Cross-border currency exchange (MYR/RMB/USD) with wallet system' },
|
||||
{ id: '4', name: 'Shipping Portal', desc: 'End-to-end logistics (China→Malaysia) with 3 carrier APIs' },
|
||||
{ id: '5', name: 'Calvana ', desc: 'Charity ops dashboard — 11 AI features shipped in 3 days' },
|
||||
{ id: '6', name: 'Clinera ', desc: 'WhatsApp-first clinic management for GCC region' },
|
||||
];
|
||||
|
||||
projects.forEach(p => {
|
||||
addHTML(
|
||||
` <span style="color:var(--dim)">[</span><span style="color:var(--gold)">${p.id}</span><span style="color:var(--dim)">]</span>` +
|
||||
` <span style="color:var(--blue)">${p.name}</span>` +
|
||||
` <span style="color:var(--white)">${p.desc}</span>`
|
||||
);
|
||||
});
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── stats ────────────────────────────────────────────────────── */
|
||||
function cmdStats() {
|
||||
addLine();
|
||||
addLine(' Git Contribution Stats', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine();
|
||||
|
||||
const stats = [
|
||||
['Total Commits ', '4,700+'],
|
||||
['Repos Contributed', '29+'],
|
||||
['Years Active ', '12'],
|
||||
['Largest Platform ', 'Exchange 2.0 (1,798 commits over 4 years)'],
|
||||
['Fastest Ship ', 'JustVitamin (4 versions in 1 day)'],
|
||||
['Most Intense ', '8 production apps in 7 weeks (QuikCue)'],
|
||||
];
|
||||
|
||||
stats.forEach(([k, v]) => {
|
||||
addHTML(
|
||||
` <span style="color:var(--dim)">${k}</span> <span style="color:var(--green)">${v}</span>`
|
||||
);
|
||||
});
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── contact ──────────────────────────────────────────────────── */
|
||||
function cmdContact() {
|
||||
addLine();
|
||||
addLine(' Contact', 'gold');
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine();
|
||||
addHTML(` <span style="color:var(--dim)">Email </span> <a href="mailto:omair@quikcue.com" style="color:var(--blue);text-decoration:none;" target="_blank">omair@quikcue.com</a>`);
|
||||
addHTML(` <span style="color:var(--dim)">LinkedIn </span> <a href="https://linkedin.com/in/omair-rescues" style="color:var(--blue);text-decoration:none;" target="_blank">linkedin.com/in/omair-rescues</a>`);
|
||||
addHTML(` <span style="color:var(--dim)">Phone </span> <span style="color:var(--white)">+601162222512</span>`);
|
||||
addHTML(` <span style="color:var(--dim)">Timezone </span> <span style="color:var(--mid)">UTC+8 (Malaysia)</span>`);
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── git log ──────────────────────────────────────────────────── */
|
||||
function cmdGitLog() {
|
||||
addLine();
|
||||
addHTML(` <span style="color:var(--gold)">commit log</span> <span style="color:var(--dim)">(HEAD → main, origin/main)</span>`);
|
||||
addLine(' ──────────────────────────────────────────────────────', 'dim');
|
||||
addLine();
|
||||
|
||||
const logs = [
|
||||
{ hash: 'a3f7b2e', date: '2026-04-14', msg: 'feat: AI self-improvement feedback loop with DB persistence' },
|
||||
{ hash: '8c1d4f9', date: '2026-04-11', msg: 'fix: auth guards on 20+ unprotected API routes' },
|
||||
{ hash: '2e9a1b3', date: '2026-04-09', msg: 'feat: generative UI canvas with 12-col adaptive layout' },
|
||||
{ hash: 'f1c3a07', date: '2026-04-06', msg: 'feat: WhatsApp webhook — appointment reminders & confirmations' },
|
||||
{ hash: 'd8b24e1', date: '2026-04-02', msg: 'refactor: payment intent flow extracted to reusable service layer' },
|
||||
{ hash: '7a9f3c5', date: '2026-03-28', msg: 'feat: donor data warehouse — 25 analytics reports, 0 raw queries' },
|
||||
{ hash: 'c4e2b81', date: '2026-03-24', msg: 'feat: Stripe Connect onboarding + payout scheduling' },
|
||||
{ hash: '91d6a3f', date: '2026-03-20', msg: 'fix: race condition in concurrent pledge fulfillment queue' },
|
||||
{ hash: 'b52c7e9', date: '2026-03-16', msg: 'chore: migrate 4-year Laravel monolith to TypeScript + Prisma' },
|
||||
{ hash: '3f8d1a4', date: '2026-03-10', msg: 'feat: GoCardless DD mandate flow with failure retry engine' },
|
||||
];
|
||||
|
||||
logs.forEach(l => {
|
||||
addHTML(
|
||||
` <span style="color:var(--gold)">${l.hash}</span>` +
|
||||
` <span style="color:var(--mid)">${l.date}</span>` +
|
||||
` <span style="color:var(--white)">${l.msg}</span>`
|
||||
);
|
||||
});
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── cat resume.pdf ───────────────────────────────────────────── */
|
||||
function cmdCatResume() {
|
||||
addLine();
|
||||
addLine(" Nice try. The PDF is what brought you here. 😉", 'hi');
|
||||
addLine(" Type 'download' to get the actual PDF.", 'mid');
|
||||
addLine();
|
||||
}
|
||||
|
||||
/* ── download ─────────────────────────────────────────────────── */
|
||||
function cmdDownload() {
|
||||
addLine();
|
||||
addLine(' 📄 Downloading resume...', 'hi');
|
||||
const a = document.createElement('a');
|
||||
a.href = './Omair_Saleh_Resume.pdf';
|
||||
a.download = 'Omair_Saleh_Resume.pdf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
setTimeout(() => {
|
||||
addLine(' ✅ Download started!', 'green');
|
||||
addHTML(` Or open directly: <a href="./Omair_Saleh_Resume.pdf" style="color:var(--blue);text-decoration:underline;" target="_blank">Omair_Saleh_Resume.pdf</a>`);
|
||||
addLine();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/* ── sudo hire omair ──────────────────────────────────────────── */
|
||||
function cmdHire() {
|
||||
addLine();
|
||||
const steps = [
|
||||
{ text: ' [sudo] password for recruiter: ********', cls: 'mid', delay: 0 },
|
||||
{ text: ' Verifying credentials...', cls: 'out', delay: 700 },
|
||||
{ text: ' Verifying credentials... ✓', cls: 'green', delay: 1400, replace: true },
|
||||
{ text: ' Checking availability...', cls: 'out', delay: 1600 },
|
||||
{ text: ' Checking availability... ✓', cls: 'green', delay: 2300, replace: true },
|
||||
{ text: ' Matching skills to requirements...', cls: 'out', delay: 2500 },
|
||||
{ text: ' Matching skills to requirements... ✓', cls: 'green', delay: 3400, replace: true },
|
||||
{ text: '', cls: 'empty', delay: 3600 },
|
||||
{ text: ' ✅ Hiring approved. Omair has been added to your team.', cls: 'gold', delay: 3700 },
|
||||
{ text: ' Starting date: Whenever you\'re ready.', cls: 'hi', delay: 4000 },
|
||||
{ text: '', cls: 'empty', delay: 4200 },
|
||||
{ text: " Contact: omair@quikcue.com | +601162222512", cls: 'green', delay: 4400 },
|
||||
{ text: '', cls: 'empty', delay: 4500 },
|
||||
];
|
||||
|
||||
let lastEl = null;
|
||||
steps.forEach(s => {
|
||||
setTimeout(() => {
|
||||
if (s.replace && lastEl) {
|
||||
lastEl.textContent = s.text;
|
||||
lastEl.className = 'line ' + s.cls;
|
||||
} else {
|
||||
if (s.cls === 'empty') {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'line empty';
|
||||
out.appendChild(d);
|
||||
} else {
|
||||
const d = document.createElement('div');
|
||||
d.className = 'line ' + s.cls;
|
||||
d.textContent = s.text;
|
||||
out.appendChild(d);
|
||||
lastEl = d;
|
||||
}
|
||||
}
|
||||
scrollBottom();
|
||||
}, s.delay);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── clear ────────────────────────────────────────────────────── */
|
||||
function cmdClear() {
|
||||
out.innerHTML = '';
|
||||
}
|
||||
|
||||
/* ── exit ─────────────────────────────────────────────────────── */
|
||||
function cmdExit() {
|
||||
addLine();
|
||||
addLine(" Thanks for visiting! Don't forget to reach out:", 'out');
|
||||
addHTML(` <a href="mailto:omair@quikcue.com" style="color:var(--blue)">omair@quikcue.com</a>`);
|
||||
addLine();
|
||||
addLine(' Closing session...', 'dim');
|
||||
setTimeout(() => {
|
||||
addLine(' Connection closed.', 'dim');
|
||||
inp.disabled = true;
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
/* ─────────────────────────── Dino Game ──────────────────────── */
|
||||
function cmdDino() {
|
||||
addLine();
|
||||
addLine(' Launching Dino Runner… Press SPACE or ↑ to jump!', 'gold');
|
||||
addLine(' (Press ESC to quit)', 'dim');
|
||||
addLine();
|
||||
startDinoGame();
|
||||
}
|
||||
|
||||
(function DinoGame() {
|
||||
const container = document.getElementById('dino-container');
|
||||
const canvas = document.getElementById('dino-canvas');
|
||||
const scoreEl = document.getElementById('dino-score-val');
|
||||
const hiEl = document.getElementById('dino-hi-val');
|
||||
const msgEl = document.getElementById('dino-msg');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const W = 800, H = 120;
|
||||
const GROUND = H - 20;
|
||||
const DINO_W = 28, DINO_H = 34;
|
||||
const CACTUS_W = 16;
|
||||
const GRAVITY = 0.7;
|
||||
const JUMP_V = -13;
|
||||
|
||||
let state, dino, cacti, score, hiScore, speed, frameId, spawnTimer;
|
||||
|
||||
hiScore = 0;
|
||||
|
||||
function reset() {
|
||||
state = 'idle'; // idle | running | dead
|
||||
dino = { x: 60, y: GROUND - DINO_H, vy: 0, onGround: true };
|
||||
cacti = [];
|
||||
score = 0;
|
||||
speed = 4;
|
||||
spawnTimer = 80;
|
||||
msgEl.textContent = 'SPACE / ↑ to jump | ESC to quit';
|
||||
}
|
||||
|
||||
function jump() {
|
||||
if (state === 'dead') { reset(); state = 'running'; return; }
|
||||
if (state === 'idle') { state = 'running'; return; }
|
||||
if (dino.onGround) {
|
||||
dino.vy = JUMP_V;
|
||||
dino.onGround = false;
|
||||
}
|
||||
}
|
||||
|
||||
function drawGround() {
|
||||
ctx.strokeStyle = '#1e5c1e';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, GROUND + DINO_H);
|
||||
ctx.lineTo(W, GROUND + DINO_H);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawDino(x, y) {
|
||||
ctx.fillStyle = '#00ff41';
|
||||
// Body
|
||||
ctx.fillRect(x + 4, y + 10, 18, 20);
|
||||
// Head
|
||||
ctx.fillRect(x + 10, y, 14, 14);
|
||||
// Eye
|
||||
ctx.fillStyle = '#0a0a0a';
|
||||
ctx.fillRect(x + 19, y + 3, 3, 3);
|
||||
// Tail
|
||||
ctx.fillStyle = '#00ff41';
|
||||
ctx.fillRect(x, y + 14, 8, 8);
|
||||
// Legs (alternate based on score)
|
||||
const leg = Math.floor(score / 6) % 2;
|
||||
ctx.fillRect(x + 6, y + 30, 5, leg === 0 ? 8 : 4);
|
||||
ctx.fillRect(x + 14, y + 30, 5, leg === 1 ? 8 : 4);
|
||||
}
|
||||
|
||||
function drawCactus(x) {
|
||||
ctx.fillStyle = '#ff6b6b';
|
||||
const h = 30 + (Math.floor(x) % 2) * 10;
|
||||
ctx.fillRect(x, GROUND + DINO_H - h, CACTUS_W, h);
|
||||
ctx.fillRect(x - 6, GROUND + DINO_H - h * 0.6, 6, 8);
|
||||
ctx.fillRect(x + CACTUS_W, GROUND + DINO_H - h * 0.5, 6, 8);
|
||||
}
|
||||
|
||||
function collides(c) {
|
||||
const pad = 4;
|
||||
return (
|
||||
dino.x + pad + 10 < c.x + CACTUS_W - pad &&
|
||||
dino.x + DINO_W - pad > c.x + pad &&
|
||||
dino.y + pad < GROUND + DINO_H &&
|
||||
dino.y + DINO_H > GROUND + DINO_H - 40
|
||||
);
|
||||
}
|
||||
|
||||
function frame() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
drawGround();
|
||||
|
||||
if (state === 'idle') {
|
||||
drawDino(dino.x, dino.y);
|
||||
ctx.fillStyle = '#4a4a4a';
|
||||
ctx.font = '14px JetBrains Mono, monospace';
|
||||
ctx.fillText('PRESS SPACE TO START', W/2 - 110, H/2 - 10);
|
||||
frameId = requestAnimationFrame(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
if (state === 'dead') {
|
||||
drawDino(dino.x, dino.y);
|
||||
cacti.forEach(c => drawCactus(c.x));
|
||||
ctx.fillStyle = '#ff6b6b';
|
||||
ctx.font = 'bold 16px JetBrains Mono, monospace';
|
||||
ctx.fillText('GAME OVER!', W/2 - 52, H/2 - 8);
|
||||
frameId = requestAnimationFrame(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
// Physics
|
||||
dino.vy += GRAVITY;
|
||||
dino.y += dino.vy;
|
||||
if (dino.y >= GROUND - DINO_H) {
|
||||
dino.y = GROUND - DINO_H;
|
||||
dino.vy = 0;
|
||||
dino.onGround = true;
|
||||
}
|
||||
|
||||
// Spawn cacti
|
||||
spawnTimer--;
|
||||
if (spawnTimer <= 0) {
|
||||
cacti.push({ x: W + 10 });
|
||||
spawnTimer = 60 + Math.floor(Math.random() * 60);
|
||||
}
|
||||
|
||||
// Move cacti
|
||||
cacti.forEach(c => c.x -= speed);
|
||||
cacti = cacti.filter(c => c.x > -40);
|
||||
|
||||
// Score
|
||||
score++;
|
||||
speed = 4 + Math.floor(score / 300) * 0.5;
|
||||
scoreEl.textContent = Math.floor(score / 6);
|
||||
|
||||
// Collision
|
||||
if (cacti.some(c => collides(c))) {
|
||||
state = 'dead';
|
||||
const s = Math.floor(score / 6);
|
||||
if (s > hiScore) { hiScore = s; hiEl.textContent = hiScore; }
|
||||
msgEl.textContent = `GAME OVER! Score: ${s} | SPACE to restart | ESC to quit`;
|
||||
}
|
||||
|
||||
drawDino(dino.x, dino.y);
|
||||
cacti.forEach(c => drawCactus(c.x));
|
||||
|
||||
frameId = requestAnimationFrame(frame);
|
||||
}
|
||||
|
||||
window.startDinoGame = function() {
|
||||
if (dinoActive) return;
|
||||
dinoActive = true;
|
||||
container.style.display = 'block';
|
||||
inp.blur();
|
||||
reset();
|
||||
frameId = requestAnimationFrame(frame);
|
||||
};
|
||||
|
||||
window.stopDinoGame = function() {
|
||||
dinoActive = false;
|
||||
container.style.display = 'none';
|
||||
if (frameId) cancelAnimationFrame(frameId);
|
||||
inp.focus();
|
||||
addLine(' Dino game closed. Score: ' + Math.floor(score / 6), 'mid');
|
||||
addLine();
|
||||
};
|
||||
|
||||
// Controls
|
||||
document.addEventListener('keydown', e => {
|
||||
if (!dinoActive) return;
|
||||
if (e.code === 'Space' || e.code === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
jump();
|
||||
}
|
||||
if (e.code === 'Escape') {
|
||||
stopDinoGame();
|
||||
}
|
||||
});
|
||||
|
||||
// Touch for mobile
|
||||
canvas.addEventListener('touchstart', e => {
|
||||
e.preventDefault();
|
||||
jump();
|
||||
}, { passive: false });
|
||||
|
||||
reset();
|
||||
})();
|
||||
|
||||
/* ─────────────────────────── Input handling ─────────────────── */
|
||||
inp.addEventListener('keydown', e => {
|
||||
if (dinoActive) return;
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
const val = inp.value;
|
||||
inp.value = '';
|
||||
runCommand(val);
|
||||
return;
|
||||
}
|
||||
|
||||
// History navigation
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (histIdx < history.length - 1) {
|
||||
histIdx++;
|
||||
inp.value = history[histIdx];
|
||||
// cursor to end
|
||||
setTimeout(() => inp.setSelectionRange(inp.value.length, inp.value.length), 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (histIdx > 0) {
|
||||
histIdx--;
|
||||
inp.value = history[histIdx];
|
||||
} else {
|
||||
histIdx = -1;
|
||||
inp.value = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Tab autocomplete
|
||||
if (e.key === 'Tab') {
|
||||
e.preventDefault();
|
||||
const partial = inp.value.toLowerCase();
|
||||
if (!partial) return;
|
||||
const match = COMMANDS.find(c => c.startsWith(partial));
|
||||
if (match) inp.value = match;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Keep focus on input
|
||||
document.addEventListener('click', () => {
|
||||
if (!dinoActive) inp.focus();
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if (!dinoActive && document.activeElement !== inp && e.key.length === 1) {
|
||||
inp.focus();
|
||||
}
|
||||
});
|
||||
|
||||
/* ─────────────────────────── Boot sequence ─────────────────── */
|
||||
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||
|
||||
async function boot() {
|
||||
const bootLines = [
|
||||
{ text: 'Booting omair-os v12.0...', cls: 'green', delay: 0 },
|
||||
{ text: '', cls: 'empty', delay: 300 },
|
||||
];
|
||||
|
||||
for (const l of bootLines) {
|
||||
await sleep(l.delay);
|
||||
if (l.cls === 'empty') {
|
||||
const d = document.createElement('div'); d.className = 'line empty'; out.appendChild(d);
|
||||
} else {
|
||||
addLine(' ' + l.text, l.cls);
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bar animation
|
||||
await sleep(200);
|
||||
const barEl = document.createElement('div');
|
||||
barEl.className = 'line out';
|
||||
barEl.innerHTML = ' Loading modules: [<span id="pbar"></span>] <span id="ppct">0%</span>';
|
||||
out.appendChild(barEl);
|
||||
scrollBottom();
|
||||
|
||||
const pbar = document.getElementById('pbar');
|
||||
const ppct = document.getElementById('ppct');
|
||||
const total = 20;
|
||||
for (let i = 0; i <= total; i++) {
|
||||
pbar.textContent = '█'.repeat(i) + '░'.repeat(total - i);
|
||||
ppct.textContent = Math.round((i / total) * 100) + '%';
|
||||
pbar.style.color = 'var(--green)';
|
||||
scrollBottom();
|
||||
await sleep(40);
|
||||
}
|
||||
ppct.style.color = 'var(--gold)';
|
||||
|
||||
await sleep(200);
|
||||
addLine(' System ready.', 'green');
|
||||
await sleep(300);
|
||||
addLine();
|
||||
addLine(' Welcome to Omair\'s Terminal Portfolio.', 'hi');
|
||||
addLine(' Type \'help\' to see available commands.', 'mid');
|
||||
addLine();
|
||||
|
||||
inp.focus();
|
||||
|
||||
// Auto-show help after short pause
|
||||
await sleep(600);
|
||||
runCommand('help');
|
||||
}
|
||||
|
||||
boot();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user