Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/_includes/footer.njk
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,26 @@
[ESC] EXIT_SIMULATION
</button>
</div>
<button id="reopen-console-btn" onclick="reopenConsole()" class="fixed bottom-6 right-6 p-3 bg-black/80 border border-green-500/30 rounded-full text-green-500 hidden hover:scale-110 transition-all z-[999]" title="Open System Log">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>
</button>
<div id="matrix-console-container" class="fixed bottom-6 right-6 w-80 z-[1000] transition-all duration-300 ease-in-out">
<div class="bg-black/90 backdrop-blur-xl border border-green-500/30 rounded-t-lg overflow-hidden shadow-2xl">

<div class="bg-green-500/10 border-b border-green-500/30 px-3 py-2 flex justify-between items-center cursor-default select-none">
<div class="flex items-center gap-2">
<span class="text-[10px] font-mono text-green-500 uppercase tracking-widest opacity-70">System_Log</span>
</div>

<div class="flex gap-2">
<button onclick="minimizeConsole()" class="w-3 h-3 rounded-full bg-yellow-500/20 border border-yellow-500/50 hover:bg-yellow-500/50 transition-colors" title="Minimize"></button>
<button onclick="maximizeConsole()" class="w-3 h-3 rounded-full bg-green-500/20 border border-green-500/50 hover:bg-green-500/50 transition-colors" title="Maximize"></button>
<button onclick="closeConsole()" class="w-3 h-3 rounded-full bg-red-500/20 border border-red-500/50 hover:bg-red-500/50 transition-colors" title="Close"></button>
</div>
</div>

<div id="matrix-console-output" class="h-48 p-3 overflow-y-auto font-mono text-[11px] leading-tight transition-all duration-300">
<p class="text-green-500/40">// Terminal link established...</p>
</div>
</div>
</div>
147 changes: 147 additions & 0 deletions src/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,150 @@ a:hover {
filter: brightness(1.2);
text-decoration: underline;
}

@keyframes badgePop {
0% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.4); filter: brightness(1.5); }
100% { transform: scale(1); filter: brightness(1); }
}

.animate-badge-pop {
animation: badgePop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
z-index: 101; /* Ensure it pops above the header bar */
}

/* Optional: Add a glow to the level number too */
#level-number {
transition: all 0.3s ease;
}
.animate-badge-pop #level-number {
color: white;
text-shadow: 0 0 10px rgba(255,255,255,1);
}



.level-up-toast {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(15, 23, 42, 0.9); /* Dark background for contrast */
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5),
0 0 15px var(--accent); /* Glow matches current level */
z-index: 1000;
animation: slideDownIn 0.5s cubic-bezier(0.18, 0.89, 0.32, 1.28) forwards;
}

.toast-content {
display: flex;
align-items: center;
gap: 16px;
}

.toast-emoji {
font-size: 2rem;
}

.toast-title {
color: #94a3b8;
font-size: 0.7rem;
font-weight: 800;
letter-spacing: 0.1rem;
margin: 0;
}

.toast-rank {
font-size: 1.25rem;
font-weight: 900;
margin: 0;
text-transform: uppercase;
}

@keyframes slideDownIn {
from { transform: translate(-50%, -100px); opacity: 0; }
to { transform: translate(-50%, 0); opacity: 1; }
}

.fade-out {
opacity: 0;
transform: translate(-50%, -20px);
transition: all 0.5s ease;
}
.matrix-alert {
position: fixed;
bottom: 20%;
right: 20px;
background: rgba(0, 0, 0, 0.9);
border-left: 4px solid #10b981;
color: #10b981;
padding: 15px;
font-family: 'Courier New', monospace;
z-index: 2000;
box-shadow: 0 0 20px rgba(16, 185, 129, 0.2);
animation: matrixSlideIn 0.3s ease-out;
}

@keyframes matrixSlideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}

#matrix-console-output {
/* Subtle green glow on text */
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
scroll-behavior: smooth;
display: flex;
flex-direction: column;
}

/* Custom scrollbar for the terminal */
#matrix-console-output::-webkit-scrollbar {
width: 3px;
}
#matrix-console-output::-webkit-scrollbar-thumb {
background: rgba(16, 185, 129, 0.3);
}

/* The Matrix Line Animation */
.matrix-line {
border-left: 2px solid #10b981;
padding-left: 8px;
margin-bottom: 4px;
animation: matrixLineFade 0.3s ease-out;
}

@keyframes matrixLineFade {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
/* Maximize State */
.console-maximized {
width: 90vw !important;
height: auto !important;
bottom: 5vh !important;
right: 5vw !important;
left: 5vw !important;
z-index: 2000 !important;
}

#matrix-console-output {
scrollbar-width: thin;
scrollbar-color: #10b981 transparent;
}

/* Add a scanline effect for that retro terminal feel */
#matrix-console-output::before {
content: " ";
position: absolute;
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));
z-index: 10;
background-size: 100% 2px, 3px 100%;
pointer-events: none;
}
149 changes: 144 additions & 5 deletions src/assets/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,67 @@ function getRank(lvl) {
return rank;
}


const consoleContainer = document.getElementById('matrix-console-container');
const consoleOutput = document.getElementById('matrix-console-output');

function minimizeConsole() {
// Toggles the height of the output area
if (consoleOutput.style.display === 'none') {
consoleOutput.style.display = 'block';
consoleContainer.style.width = '20rem'; // w-80
} else {
consoleOutput.style.display = 'none';
consoleContainer.style.width = '150px'; // Compact view
}
}

function maximizeConsole() {
// Toggles a full-screen-ish mode
consoleContainer.classList.toggle('console-maximized');

// Adjust height when maximized
if (consoleContainer.classList.contains('console-maximized')) {
consoleOutput.style.height = '70vh';
consoleOutput.style.display = 'block';
} else {
consoleOutput.style.height = '12rem'; // h-48
}
}

function closeConsole() {
const container = document.getElementById('matrix-console-container');
const reopenBtn = document.getElementById('reopen-console-btn');

// Hide the console
container.style.opacity = '0';
container.style.transform = 'translateY(20px)';

setTimeout(() => {
container.classList.add('hidden');
// Show the small reopen button
if (reopenBtn) reopenBtn.classList.remove('hidden');
}, 300);
}

function reopenConsole() {
const container = document.getElementById('matrix-console-container');
const reopenBtn = document.getElementById('reopen-console-btn');

// Show the console
container.classList.remove('hidden');

// Trigger reflow for animation
void container.offsetWidth;

container.style.opacity = '1';
container.style.transform = 'translateY(0)';

// Hide the reopen button
if (reopenBtn) reopenBtn.classList.add('hidden');
}


let isProcessingXP = false;

// Ensure this is in the GLOBAL scope (not hidden inside another function)
Expand Down Expand Up @@ -884,6 +945,69 @@ function renderXP(value) {
// console.log(`XP: ${currentXPNum}, Percent: ${percentage}%`);
}

function showLevelUpToast(rank) {
// 1. Create the container
const toast = document.createElement('div');
toast.className = 'level-up-toast';

// 2. Build the inner content
// We use the rank color for the name and emoji to make it feel custom
toast.innerHTML = `
<div class="toast-content">
<span class="toast-emoji">${rank.emoji}</span>
<div class="toast-text">
<p class="toast-title">LEVEL UP!</p>
<p class="toast-rank" style="color: ${rank.color}">${rank.name}</p>
</div>
</div>
`;

document.body.appendChild(toast);

// 3. Auto-remove after animation
setTimeout(() => {
toast.classList.add('fade-out');
setTimeout(() => toast.remove(), 500);
}, 2500);
}


function matrixConsoleLog(level) {
const rank = getRank(level);

// This looks awesome in the F12 Dev Console
console.log(
`%c [SYSTEM] %c LEVEL UP: %c ${rank.name.toUpperCase()} %c [LVL ${level}] `,
"color: #10b981; font-weight: bold; background: #064e3b; padding: 2px;",
"color: #ffffff; background: #1e293b; padding: 2px;",
`color: ${rank.color}; font-weight: 900; background: #1e293b; padding: 2px;`,
"color: #94a3b8; background: #1e293b; padding: 2px;"
);

// 3. If you have an on-screen Matrix Console element, push there too:
const matrixConsole = document.getElementById('matrix-console-output');
if (matrixConsole) {
const line = document.createElement('p');
line.className = 'matrix-line text-xs font-mono mb-1';
line.innerHTML = `<span class="text-green-500">>></span> Rank Updated: <span style="color: ${rank.color}">${rank.name}</span>`;
matrixConsole.appendChild(line);
// Auto-scroll to bottom
matrixConsole.scrollTop = matrixConsole.scrollHeight;
}
}

document.addEventListener('keydown', (e) => {
// Check if user pressed 'L' (for Log) and isn't typing in an input field
if (e.key.toLowerCase() === 'l' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
const container = document.getElementById('matrix-console-container');
if (container.classList.contains('hidden')) {
reopenConsole();
} else {
closeConsole();
}
}
});

async function addExperience(amount) {
// 1. Force strict numeric types to prevent "1" + "1" = "11"
let xpToAdd = Number(amount) || 0;
Expand All @@ -898,14 +1022,29 @@ async function addExperience(amount) {
// Using a while loop ensures that if you gain 100 XP,
// it processes Level 1, then Level 2, with the remainder left over.
while (currentXP >= XP_THRESHOLD && currentLevel < 200) {
currentXP -= XP_THRESHOLD; // Subtract exactly the cost of one level
currentXP -= XP_THRESHOLD;
currentLevel++;
// 1. Trigger the Visual Toast (Top of screen)
if (typeof showLevelUpToast === 'function') {
showLevelUpToast(getRank(currentLevel));
}

// Safety: Ensure we don't end up with negative XP from rounding
currentXP = Math.max(0, currentXP);
// 2. Trigger the "Matrix" Console Log
matrixConsoleLog(currentLevel);

// --- THE POPUP TRIGGER ---
const badge = document.getElementById('level-badge');
if (badge) {
// Remove the class if it exists (to reset animation)
badge.classList.remove('animate-badge-pop');
// Trigger a "reflow" (magic trick to allow re-animation)
void badge.offsetWidth;
// Re-add the class
badge.classList.add('animate-badge-pop');
}
// --------------------------

// Optional: Trigger level-up specific effects here
console.log(`Leveled Up! Now Level: ${currentLevel}`);
console.log(`Leveled Up to ${currentLevel}!`);
}

// 4. Persistence: Save clean numbers
Expand Down
4 changes: 2 additions & 2 deletions src/index.njk
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ layout: false
</div>
</main>

{% include "footer.njk" %}

<div id="matrix-overlay" class="fixed inset-0 bg-black hidden z-[9999]">
<canvas id="matrix-canvas"></canvas>
<button onclick="closeMatrix()" class="fixed top-8 right-8 z-[10000] bg-white/10 text-white px-4 py-2 rounded-full border border-white/20 backdrop-blur-md text-xs">EXIT [ESC]</button>
</div>

{% include "footer.njk" %}

<script src="/assets/js/script.js"></script>
</body>
</html>
Loading