diff --git a/Anson-Projects/badfriends/index.html b/Anson-Projects/badfriends/index.html
index 6268a312a..9d8e85f3f 100644
--- a/Anson-Projects/badfriends/index.html
+++ b/Anson-Projects/badfriends/index.html
@@ -65,8 +65,11 @@
Analyzing your follows...
-
-
+
+
diff --git a/Anson-Projects/badfriends/src/main.js b/Anson-Projects/badfriends/src/main.js
index fd8fdd5da..51e01bcfb 100644
--- a/Anson-Projects/badfriends/src/main.js
+++ b/Anson-Projects/badfriends/src/main.js
@@ -43,6 +43,7 @@ const elements = {
loginForm: document.getElementById('login-form'),
handleInput: document.getElementById('handle-input'),
loadingMessage: document.getElementById('loading-message'),
+ loadingLog: document.getElementById('loading-log'),
loadingDetail: document.getElementById('loading-detail'),
loadingEta: document.getElementById('loading-eta'),
userHandle: document.getElementById('user-handle'),
@@ -195,7 +196,8 @@ async function startAnalysis() {
elements.userHandle.textContent = `@${profile.handle}`;
// Run analysis
- updateLoadingMessage('Starting analysis...');
+ updateLoadingMessage('Analyzing your follows...');
+ clearLoadingLog();
// Reset ETA tracker, unfollowed accounts, and deep scan progress
etaTracker = {
@@ -212,7 +214,7 @@ async function startAnalysis() {
const results = await analyzeFollows({
onStatus: (message) => {
- updateLoadingMessage(message);
+ addLogEntry(message);
// Parse deep scan progress from status messages
// Format: "Deep scan: X% (Y/Z accounts)"
@@ -801,13 +803,74 @@ function showScreen(screenName) {
}
}
+// Track the last log message to avoid duplicates
+let lastLogMessage = '';
+
/**
- * Update loading message
+ * Update loading message (the main header)
*/
function updateLoadingMessage(message) {
elements.loadingMessage.textContent = message;
}
+/**
+ * Clear the loading log
+ */
+function clearLoadingLog() {
+ if (elements.loadingLog) {
+ elements.loadingLog.innerHTML = '';
+ lastLogMessage = '';
+ }
+}
+
+/**
+ * Add a message to the loading log
+ * Messages fade as new ones are added
+ */
+function addLogEntry(message) {
+ if (!elements.loadingLog || !message) return;
+
+ // Skip duplicate consecutive messages (e.g., repeated "Fetching posts...")
+ // But allow updates with counts (e.g., "Fetching posts... 10 of 100")
+ const baseMessage = message.replace(/\d+/g, '#');
+ const lastBaseMessage = lastLogMessage.replace(/\d+/g, '#');
+
+ if (baseMessage === lastBaseMessage) {
+ // Update the last entry instead of adding a new one
+ const lastEntry = elements.loadingLog.querySelector('.log-entry.current');
+ if (lastEntry) {
+ lastEntry.textContent = message;
+ lastLogMessage = message;
+ return;
+ }
+ }
+
+ lastLogMessage = message;
+
+ // Fade previous "current" entries
+ const currentEntries = elements.loadingLog.querySelectorAll('.log-entry.current');
+ currentEntries.forEach(entry => {
+ entry.classList.remove('current');
+ entry.classList.add('fade');
+ });
+
+ // Create new entry
+ const entry = document.createElement('div');
+ entry.className = 'log-entry current';
+ entry.textContent = message;
+
+ elements.loadingLog.appendChild(entry);
+
+ // Scroll to bottom
+ elements.loadingLog.scrollTop = elements.loadingLog.scrollHeight;
+
+ // Limit entries to prevent memory issues (keep last 20)
+ const entries = elements.loadingLog.querySelectorAll('.log-entry');
+ if (entries.length > 20) {
+ entries[0].remove();
+ }
+}
+
/**
* Update loading detail
*/
diff --git a/Anson-Projects/badfriends/src/styles.css b/Anson-Projects/badfriends/src/styles.css
index 6bf84b29e..b9bf92601 100644
--- a/Anson-Projects/badfriends/src/styles.css
+++ b/Anson-Projects/badfriends/src/styles.css
@@ -204,6 +204,53 @@ input[type="text"] {
font-size: 0.95rem;
}
+/* Loading Log */
+.loading-log {
+ max-height: 200px;
+ overflow-y: auto;
+ margin: 1.5rem 0;
+ text-align: left;
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
+ font-size: 0.875rem;
+ padding: 1rem;
+ background: var(--bg-secondary);
+ border-radius: 8px;
+ min-height: 100px;
+}
+
+.loading-log:empty {
+ display: none;
+}
+
+.log-entry {
+ padding: 0.25rem 0;
+ color: var(--text-secondary);
+ opacity: 1;
+ transition: opacity 0.5s ease;
+ display: flex;
+ align-items: flex-start;
+ gap: 0.5rem;
+}
+
+.log-entry::before {
+ content: "→";
+ color: var(--primary-color);
+ flex-shrink: 0;
+}
+
+.log-entry.fade {
+ opacity: 0.4;
+}
+
+.log-entry.current {
+ color: var(--text-primary);
+ font-weight: 500;
+}
+
+.loading-footer {
+ margin-top: 1rem;
+}
+
/* Results Screen */
.controls {
background: var(--card-background);