PageSourceSearch

https://eepymep.netlify.app/js/command-terminal.js

js eepymep.netlify.app collected 2026-10-03 10:41:11 UTC 4,258 bytes, 134 lines download raw bytes

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.