1/** 2 * command-terminal.js â Interactive Command Demonstrations 3 * âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 4 * Simulates typing commands and shows results. 5 * Makes the landing page feel like using Discord with Mep. 6 */ 7 8(function() { 9 'use strict'; 10 11 const terminals = document.querySelectorAll('.command-terminal'); 12 if (!terminals.length) return; 13 14 // Command scenarios with typing animation 15 const scenarios = { 16 pixiv: { 17 command: '/pixiv follow', 18 input: 'https://pixiv.net/users/1234567', 19 result: 'â Now watching artist updates', 20 description: 'New artwork auto-posts to your channel' 21 }, 22 bluesky: { 23 command: '/bluesky follow', 24 input: 'artist.bsky.social', 25 result: 'â Following on Bluesky', 26 description: 'Posts delivered every 5 minutes' 27 }, 28 delviant: { 29 command: '/delviant random', 30 input: null, 31 result: 'ðµ Now playing: paleheart', 32 description: 'Angelcore, witch house, chill phonk' 33 }, 34 colorroles: { 35 command: '/colorroles setup', 36 input: null, 37 result: 'â 36 color roles created', 38 description: 'Members can pick their own colors' 39 } 40 }; 41 42 // Typing effect 43 function typeText(element, text, speed = 80) { 44 return new Promise(resolve => { 45 element.textContent = ''; 46 let i = 0; 47 48 const interval = setInterval(() => { 49 if (i < text.length) { 50 element.textContent += text[i]; 51 i++; 52 } else { 53 clearInterval(interval); 54 resolve(); 55 } 56 }, speed); 57 }); 58 } 59 60 // Track if terminal has already been animated to prevent looping 61 const animatedTerminals = new WeakSet(); 62 63 // Animate a terminal 64 async function animateTerminal(terminal) { 65 const scenarioType = terminal.dataset.scenario; 66 const scenario = scenarios[scenarioType]; 67 if (!scenario) return; 68 69 const commandEl = terminal.querySelector('.terminal-command'); 70 const inputEl = terminal.querySelector('.terminal-input'); 71 const resultEl = terminal.querySelector('.terminal-result'); 72 const descEl = terminal.querySelector('.terminal-description'); 73 74 // Reset with stable state 75 commandEl.textContent = ''; 76 commandEl.style.minHeight = '1.5rem'; 77 if (inputEl) { 78 inputEl.textContent = ''; 79 inputEl.style.height = '1.2rem'; 80 } 81 resultEl.textContent = ''; 82 resultEl.classList.remove('show'); 83 if (descEl) { 84 descEl.textContent = ''; 85 descEl.classList.remove('show'); 86 } 87 88 await new Promise(r => setTimeout(r, 100)); 89 90 // Type command (slower for readability) 91 await typeText(commandEl, scenario.command, 150); 92 await new Promise(r => setTimeout(r, 500)); 93 94 // Type input if exists (slower for readability) 95 if (scenario.input && inputEl) { 96 await typeText(inputEl, scenario.input, 100); 97 await new Promise(r => setTimeout(r, 700)); 98 } 99 100 // Show result with fade-in 101 resultEl.textContent = scenario.result; 102 resultEl.classList.add('show'); 103 await new Promise(r => setTimeout(r, 500)); 104 105 // Show description 106 if (descEl && scenario.description) { 107 descEl.textContent = scenario.description; 108 descEl.classList.add('show'); 109 } 110 } 111 112 // Intersection Observer to trigger animations when visible (ONCE ONLY) 113 const observer = new IntersectionObserver((entries) => { 114 entries.forEach(entry => { 115 if (entry.isIntersecting) { 116 const terminal = entry.target; 117 118 // Only animate if not already animated (prevents looping/scrambling) 119 if (!animatedTerminals.has(terminal)) { 120 animatedTerminals.add(terminal); 121 122 // Delay based on index for stagger effect 123 const delay = parseInt(terminal.dataset.delay) || 0; 124 setTimeout(() => { 125 animateTerminal(terminal); 126 }, delay); 127 } 128 } 129 });
130 }, { threshold: 0.3 }); 131 132 terminals.forEach(terminal => observer.observe(terminal)); 133 134})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.