Skip to content

Commit 33bb888

Browse files
authored
Add system log; stabilze (#255)
1 parent 5e8571b commit 33bb888

File tree

4 files changed

+316
-7
lines changed

4 files changed

+316
-7
lines changed

src/_includes/footer.njk

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,26 @@
124124
[ESC] EXIT_SIMULATION
125125
</button>
126126
</div>
127+
<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">
128+
<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>
129+
</button>
130+
<div id="matrix-console-container" class="fixed bottom-6 right-6 w-80 z-[1000] transition-all duration-300 ease-in-out">
131+
<div class="bg-black/90 backdrop-blur-xl border border-green-500/30 rounded-t-lg overflow-hidden shadow-2xl">
132+
133+
<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">
134+
<div class="flex items-center gap-2">
135+
<span class="text-[10px] font-mono text-green-500 uppercase tracking-widest opacity-70">System_Log</span>
136+
</div>
137+
138+
<div class="flex gap-2">
139+
<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>
140+
<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>
141+
<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>
142+
</div>
143+
</div>
144+
145+
<div id="matrix-console-output" class="h-48 p-3 overflow-y-auto font-mono text-[11px] leading-tight transition-all duration-300">
146+
<p class="text-green-500/40">// Terminal link established...</p>
147+
</div>
148+
</div>
149+
</div>

src/assets/css/style.css

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,3 +583,150 @@ a:hover {
583583
filter: brightness(1.2);
584584
text-decoration: underline;
585585
}
586+
587+
@keyframes badgePop {
588+
0% { transform: scale(1); filter: brightness(1); }
589+
50% { transform: scale(1.4); filter: brightness(1.5); }
590+
100% { transform: scale(1); filter: brightness(1); }
591+
}
592+
593+
.animate-badge-pop {
594+
animation: badgePop 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
595+
z-index: 101; /* Ensure it pops above the header bar */
596+
}
597+
598+
/* Optional: Add a glow to the level number too */
599+
#level-number {
600+
transition: all 0.3s ease;
601+
}
602+
.animate-badge-pop #level-number {
603+
color: white;
604+
text-shadow: 0 0 10px rgba(255,255,255,1);
605+
}
606+
607+
608+
609+
.level-up-toast {
610+
position: fixed;
611+
top: 20px;
612+
left: 50%;
613+
transform: translateX(-50%);
614+
background: rgba(15, 23, 42, 0.9); /* Dark background for contrast */
615+
backdrop-filter: blur(10px);
616+
border: 1px solid rgba(255, 255, 255, 0.1);
617+
padding: 16px 24px;
618+
border-radius: 12px;
619+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5),
620+
0 0 15px var(--accent); /* Glow matches current level */
621+
z-index: 1000;
622+
animation: slideDownIn 0.5s cubic-bezier(0.18, 0.89, 0.32, 1.28) forwards;
623+
}
624+
625+
.toast-content {
626+
display: flex;
627+
align-items: center;
628+
gap: 16px;
629+
}
630+
631+
.toast-emoji {
632+
font-size: 2rem;
633+
}
634+
635+
.toast-title {
636+
color: #94a3b8;
637+
font-size: 0.7rem;
638+
font-weight: 800;
639+
letter-spacing: 0.1rem;
640+
margin: 0;
641+
}
642+
643+
.toast-rank {
644+
font-size: 1.25rem;
645+
font-weight: 900;
646+
margin: 0;
647+
text-transform: uppercase;
648+
}
649+
650+
@keyframes slideDownIn {
651+
from { transform: translate(-50%, -100px); opacity: 0; }
652+
to { transform: translate(-50%, 0); opacity: 1; }
653+
}
654+
655+
.fade-out {
656+
opacity: 0;
657+
transform: translate(-50%, -20px);
658+
transition: all 0.5s ease;
659+
}
660+
.matrix-alert {
661+
position: fixed;
662+
bottom: 20%;
663+
right: 20px;
664+
background: rgba(0, 0, 0, 0.9);
665+
border-left: 4px solid #10b981;
666+
color: #10b981;
667+
padding: 15px;
668+
font-family: 'Courier New', monospace;
669+
z-index: 2000;
670+
box-shadow: 0 0 20px rgba(16, 185, 129, 0.2);
671+
animation: matrixSlideIn 0.3s ease-out;
672+
}
673+
674+
@keyframes matrixSlideIn {
675+
from { transform: translateX(100%); opacity: 0; }
676+
to { transform: translateX(0); opacity: 1; }
677+
}
678+
679+
#matrix-console-output {
680+
/* Subtle green glow on text */
681+
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
682+
scroll-behavior: smooth;
683+
display: flex;
684+
flex-direction: column;
685+
}
686+
687+
/* Custom scrollbar for the terminal */
688+
#matrix-console-output::-webkit-scrollbar {
689+
width: 3px;
690+
}
691+
#matrix-console-output::-webkit-scrollbar-thumb {
692+
background: rgba(16, 185, 129, 0.3);
693+
}
694+
695+
/* The Matrix Line Animation */
696+
.matrix-line {
697+
border-left: 2px solid #10b981;
698+
padding-left: 8px;
699+
margin-bottom: 4px;
700+
animation: matrixLineFade 0.3s ease-out;
701+
}
702+
703+
@keyframes matrixLineFade {
704+
from { opacity: 0; transform: translateX(-10px); }
705+
to { opacity: 1; transform: translateX(0); }
706+
}
707+
/* Maximize State */
708+
.console-maximized {
709+
width: 90vw !important;
710+
height: auto !important;
711+
bottom: 5vh !important;
712+
right: 5vw !important;
713+
left: 5vw !important;
714+
z-index: 2000 !important;
715+
}
716+
717+
#matrix-console-output {
718+
scrollbar-width: thin;
719+
scrollbar-color: #10b981 transparent;
720+
}
721+
722+
/* Add a scanline effect for that retro terminal feel */
723+
#matrix-console-output::before {
724+
content: " ";
725+
position: absolute;
726+
top: 0; left: 0; bottom: 0; right: 0;
727+
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%),
728+
linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
729+
z-index: 10;
730+
background-size: 100% 2px, 3px 100%;
731+
pointer-events: none;
732+
}

src/assets/js/script.js

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,67 @@ function getRank(lvl) {
354354
return rank;
355355
}
356356

357+
358+
const consoleContainer = document.getElementById('matrix-console-container');
359+
const consoleOutput = document.getElementById('matrix-console-output');
360+
361+
function minimizeConsole() {
362+
// Toggles the height of the output area
363+
if (consoleOutput.style.display === 'none') {
364+
consoleOutput.style.display = 'block';
365+
consoleContainer.style.width = '20rem'; // w-80
366+
} else {
367+
consoleOutput.style.display = 'none';
368+
consoleContainer.style.width = '150px'; // Compact view
369+
}
370+
}
371+
372+
function maximizeConsole() {
373+
// Toggles a full-screen-ish mode
374+
consoleContainer.classList.toggle('console-maximized');
375+
376+
// Adjust height when maximized
377+
if (consoleContainer.classList.contains('console-maximized')) {
378+
consoleOutput.style.height = '70vh';
379+
consoleOutput.style.display = 'block';
380+
} else {
381+
consoleOutput.style.height = '12rem'; // h-48
382+
}
383+
}
384+
385+
function closeConsole() {
386+
const container = document.getElementById('matrix-console-container');
387+
const reopenBtn = document.getElementById('reopen-console-btn');
388+
389+
// Hide the console
390+
container.style.opacity = '0';
391+
container.style.transform = 'translateY(20px)';
392+
393+
setTimeout(() => {
394+
container.classList.add('hidden');
395+
// Show the small reopen button
396+
if (reopenBtn) reopenBtn.classList.remove('hidden');
397+
}, 300);
398+
}
399+
400+
function reopenConsole() {
401+
const container = document.getElementById('matrix-console-container');
402+
const reopenBtn = document.getElementById('reopen-console-btn');
403+
404+
// Show the console
405+
container.classList.remove('hidden');
406+
407+
// Trigger reflow for animation
408+
void container.offsetWidth;
409+
410+
container.style.opacity = '1';
411+
container.style.transform = 'translateY(0)';
412+
413+
// Hide the reopen button
414+
if (reopenBtn) reopenBtn.classList.add('hidden');
415+
}
416+
417+
357418
let isProcessingXP = false;
358419

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

948+
function showLevelUpToast(rank) {
949+
// 1. Create the container
950+
const toast = document.createElement('div');
951+
toast.className = 'level-up-toast';
952+
953+
// 2. Build the inner content
954+
// We use the rank color for the name and emoji to make it feel custom
955+
toast.innerHTML = `
956+
<div class="toast-content">
957+
<span class="toast-emoji">${rank.emoji}</span>
958+
<div class="toast-text">
959+
<p class="toast-title">LEVEL UP!</p>
960+
<p class="toast-rank" style="color: ${rank.color}">${rank.name}</p>
961+
</div>
962+
</div>
963+
`;
964+
965+
document.body.appendChild(toast);
966+
967+
// 3. Auto-remove after animation
968+
setTimeout(() => {
969+
toast.classList.add('fade-out');
970+
setTimeout(() => toast.remove(), 500);
971+
}, 2500);
972+
}
973+
974+
975+
function matrixConsoleLog(level) {
976+
const rank = getRank(level);
977+
978+
// This looks awesome in the F12 Dev Console
979+
console.log(
980+
`%c [SYSTEM] %c LEVEL UP: %c ${rank.name.toUpperCase()} %c [LVL ${level}] `,
981+
"color: #10b981; font-weight: bold; background: #064e3b; padding: 2px;",
982+
"color: #ffffff; background: #1e293b; padding: 2px;",
983+
`color: ${rank.color}; font-weight: 900; background: #1e293b; padding: 2px;`,
984+
"color: #94a3b8; background: #1e293b; padding: 2px;"
985+
);
986+
987+
// 3. If you have an on-screen Matrix Console element, push there too:
988+
const matrixConsole = document.getElementById('matrix-console-output');
989+
if (matrixConsole) {
990+
const line = document.createElement('p');
991+
line.className = 'matrix-line text-xs font-mono mb-1';
992+
line.innerHTML = `<span class="text-green-500">>></span> Rank Updated: <span style="color: ${rank.color}">${rank.name}</span>`;
993+
matrixConsole.appendChild(line);
994+
// Auto-scroll to bottom
995+
matrixConsole.scrollTop = matrixConsole.scrollHeight;
996+
}
997+
}
998+
999+
document.addEventListener('keydown', (e) => {
1000+
// Check if user pressed 'L' (for Log) and isn't typing in an input field
1001+
if (e.key.toLowerCase() === 'l' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
1002+
const container = document.getElementById('matrix-console-container');
1003+
if (container.classList.contains('hidden')) {
1004+
reopenConsole();
1005+
} else {
1006+
closeConsole();
1007+
}
1008+
}
1009+
});
1010+
8871011
async function addExperience(amount) {
8881012
// 1. Force strict numeric types to prevent "1" + "1" = "11"
8891013
let xpToAdd = Number(amount) || 0;
@@ -898,14 +1022,29 @@ async function addExperience(amount) {
8981022
// Using a while loop ensures that if you gain 100 XP,
8991023
// it processes Level 1, then Level 2, with the remainder left over.
9001024
while (currentXP >= XP_THRESHOLD && currentLevel < 200) {
901-
currentXP -= XP_THRESHOLD; // Subtract exactly the cost of one level
1025+
currentXP -= XP_THRESHOLD;
9021026
currentLevel++;
1027+
// 1. Trigger the Visual Toast (Top of screen)
1028+
if (typeof showLevelUpToast === 'function') {
1029+
showLevelUpToast(getRank(currentLevel));
1030+
}
9031031

904-
// Safety: Ensure we don't end up with negative XP from rounding
905-
currentXP = Math.max(0, currentXP);
1032+
// 2. Trigger the "Matrix" Console Log
1033+
matrixConsoleLog(currentLevel);
1034+
1035+
// --- THE POPUP TRIGGER ---
1036+
const badge = document.getElementById('level-badge');
1037+
if (badge) {
1038+
// Remove the class if it exists (to reset animation)
1039+
badge.classList.remove('animate-badge-pop');
1040+
// Trigger a "reflow" (magic trick to allow re-animation)
1041+
void badge.offsetWidth;
1042+
// Re-add the class
1043+
badge.classList.add('animate-badge-pop');
1044+
}
1045+
// --------------------------
9061046

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

9111050
// 4. Persistence: Save clean numbers

src/index.njk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ layout: false
6464
</div>
6565
</main>
6666

67-
{% include "footer.njk" %}
68-
6967
<div id="matrix-overlay" class="fixed inset-0 bg-black hidden z-[9999]">
7068
<canvas id="matrix-canvas"></canvas>
7169
<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>
7270
</div>
7371

72+
{% include "footer.njk" %}
73+
7474
<script src="/assets/js/script.js"></script>
7575
</body>
7676
</html>

0 commit comments

Comments
 (0)