numbpilled's picture
Add 2 files
499eca9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WRAITHPATH • Ideological Fingerprinting</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.gradient-text {
background: linear-gradient(90deg, #8b5cf6 0%, #ec4899 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.timeline-item::before {
content: '';
position: absolute;
left: -20px;
top: 0;
width: 2px;
height: 100%;
background: #4b5563;
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 0.6; }
50% { opacity: 1; }
100% { opacity: 0.6; }
}
.sentiment-bar {
transition: width 1s ease-in-out;
}
#sentimentChart {
min-height: 300px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/builds/compromise.min.js"></script>
</head>
<body class="bg-gray-900 text-gray-200 min-h-screen font-sans">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="mb-12">
<div class="flex justify-between items-center mb-6">
<h1 class="text-4xl font-bold">
<span class="gradient-text">WRAITHPATH</span>
</h1>
<button class="bg-gray-800 hover:bg-gray-700 px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-user-secret mr-2"></i>
<span>Login</span>
</button>
</div>
<p class="text-xl text-gray-400 mb-6">"Their posts were a breadcrumb trail to who they really are."</p>
<!-- Search Box -->
<div class="bg-gray-800 rounded-xl p-6 shadow-lg mb-8">
<div class="flex flex-col md:flex-row gap-4">
<div class="flex-1 relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-search text-gray-500"></i>
</div>
<input
type="text"
id="profileUrl"
class="w-full bg-gray-700 border border-gray-600 rounded-lg pl-10 pr-4 py-3 focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="Enter Reddit username (e.g., u/spez or just spez)"
value="u/spez"
>
</div>
<button
id="analyzeBtn"
class="bg-gradient-to-r from-purple-600 to-pink-500 hover:from-purple-700 hover:to-pink-600 text-white font-medium rounded-lg px-6 py-3 transition-all duration-300 flex items-center justify-center"
>
<i class="fas fa-fingerprint mr-2"></i>
Analyze Ideology
</button>
</div>
<div class="mt-4 flex flex-wrap gap-2">
<span class="text-xs bg-gray-700 px-2 py-1 rounded">Reddit: u/username or username</span>
<span class="text-xs bg-gray-700 px-2 py-1 rounded">Coming soon: Twitter, Tumblr</span>
</div>
</div>
</header>
<!-- Demo Section -->
<section class="mb-16">
<h2 class="text-2xl font-semibold mb-6 flex items-center">
<i class="fas fa-ghost mr-3 text-purple-500"></i>
How It Works
</h2>
<div class="grid md:grid-cols-3 gap-6">
<div class="bg-gray-800 p-6 rounded-xl hover:bg-gray-750 transition-all">
<div class="w-12 h-12 bg-purple-900 rounded-lg flex items-center justify-center mb-4">
<i class="fas fa-link text-purple-400 text-xl"></i>
</div>
<h3 class="text-lg font-medium mb-2">1. Input Profile</h3>
<p class="text-gray-400">Provide any public social media username. We'll analyze their posting history.</p>
</div>
<div class="bg-gray-800 p-6 rounded-xl hover:bg-gray-750 transition-all">
<div class="w-12 h-12 bg-pink-900 rounded-lg flex items-center justify-center mb-4">
<i class="fas fa-brain text-pink-400 text-xl"></i>
</div>
<h3 class="text-lg font-medium mb-2">2. Cognitive Analysis</h3>
<p class="text-gray-400">Our models detect linguistic patterns, sentiment shifts, and ideological markers.</p>
</div>
<div class="bg-gray-800 p-6 rounded-xl hover:bg-gray-750 transition-all">
<div class="w-12 h-12 bg-indigo-900 rounded-lg flex items-center justify-center mb-4">
<i class="fas fa-chart-line text-indigo-400 text-xl"></i>
</div>
<h3 class="text-lg font-medium mb-2">3. Get The Report</h3>
<p class="text-gray-400">Receive a detailed timeline of mental state evolution and belief system changes.</p>
</div>
</div>
</section>
<!-- Results Section (Initially Hidden) -->
<section id="resultsSection" class="hidden mb-16">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-semibold flex items-center">
<i class="fas fa-chart-bar mr-3 text-pink-500"></i>
Ideological Fingerprint
</h2>
<button id="downloadReport" class="bg-gray-800 hover:bg-gray-700 px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-file-pdf mr-2 text-red-400"></i>
<span>Download PDF</span>
</button>
</div>
<!-- Profile Summary -->
<div class="bg-gray-800 rounded-xl p-6 mb-8">
<div class="flex flex-col md:flex-row gap-6">
<div class="flex-shrink-0">
<div id="avatarContainer" class="w-24 h-24 rounded-full bg-gradient-to-br from-purple-600 to-pink-500 flex items-center justify-center text-4xl font-bold">
<span id="avatarInitial">?</span>
</div>
</div>
<div class="flex-1">
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-4">
<div>
<h3 id="profileName" class="text-xl font-bold">Unknown Profile</h3>
<p id="profileHandle" class="text-gray-400">@unknown</p>
</div>
<div class="mt-2 md:mt-0">
<span id="profilePlatform" class="bg-gray-700 px-3 py-1 rounded-full text-sm">Reddit</span>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<p class="text-gray-500 text-sm">Posts Analyzed</p>
<p id="postCount" class="text-lg font-medium">0</p>
</div>
<div>
<p class="text-gray-500 text-sm">Time Span</p>
<p id="timeSpan" class="text-lg font-medium">0 years</p>
</div>
<div>
<p class="text-gray-500 text-sm">Ideology Score</p>
<p id="ideologyScore" class="text-lg font-medium">0/100</p>
</div>
<div>
<p class="text-gray-500 text-sm">Volatility</p>
<p id="volatility" class="text-lg font-medium">Low</p>
</div>
</div>
</div>
</div>
</div>
<!-- Ideology Timeline -->
<div class="bg-gray-800 rounded-xl p-6 mb-8">
<h3 class="text-lg font-medium mb-4 flex items-center">
<i class="fas fa-history mr-2 text-purple-400"></i>
Ideological Timeline
</h3>
<div class="relative pl-8">
<div id="timelineContainer">
<!-- Timeline items will be added here by JavaScript -->
</div>
</div>
</div>
<!-- Sentiment Analysis -->
<div class="bg-gray-800 rounded-xl p-6 mb-8">
<h3 class="text-lg font-medium mb-4 flex items-center">
<i class="fas fa-brain mr-2 text-pink-400"></i>
Sentiment Evolution
</h3>
<div class="h-64 bg-gray-700 rounded-lg">
<canvas id="sentimentChart"></canvas>
</div>
</div>
<!-- Topic Cloud -->
<div class="bg-gray-800 rounded-xl p-6 mb-8">
<h3 class="text-lg font-medium mb-4 flex items-center">
<i class="fas fa-cloud mr-2 text-indigo-400"></i>
Topic Cloud
</h3>
<div class="flex flex-wrap gap-3" id="topicCloud">
<!-- Topics will be added here by JavaScript -->
</div>
</div>
<!-- Controversial Posts -->
<div class="bg-gray-800 rounded-xl p-6">
<h3 class="text-lg font-medium mb-4 flex items-center">
<i class="fas fa-fire mr-2 text-red-400"></i>
Most Controversial Posts
</h3>
<div id="controversialPosts" class="space-y-4">
<!-- Controversial posts will be added here -->
</div>
</div>
</section>
<!-- Loading State (Initially Hidden) -->
<div id="loadingState" class="hidden fixed inset-0 bg-gray-900 bg-opacity-90 flex items-center justify-center z-50">
<div class="text-center">
<div class="w-24 h-24 border-4 border-purple-500 border-t-pink-500 rounded-full animate-spin mb-6 mx-auto"></div>
<h3 class="text-2xl font-medium mb-2">Building Cognitive Profile</h3>
<p class="text-gray-400 max-w-md mx-auto">Analyzing linguistic patterns, sentiment shifts, and ideological markers across <span id="loadingPostCount">0</span> posts...</p>
<div class="mt-6 bg-gray-800 rounded-full h-2 w-64 mx-auto overflow-hidden">
<div id="progressBar" class="bg-gradient-to-r from-purple-500 to-pink-500 h-full" style="width: 0%"></div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-16 pt-8 border-t border-gray-800 text-gray-500">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="mb-4 md:mb-0">
<p>&copy; 2023 WRAITHPATH • Ideological Fingerprinting</p>
</div>
<div class="flex space-x-6">
<a href="#" class="hover:text-gray-300">Privacy</a>
<a href="#" class="hover:text-gray-300">Terms</a>
<a href="#" class="hover:text-gray-300">API</a>
<a href="#" class="hover:text-gray-300">Contact</a>
</div>
</div>
<div class="mt-4 text-xs text-gray-600">
<p>Disclaimer: This tool analyzes publicly available data for research purposes only. Results should not be considered definitive psychological assessments.</p>
</div>
</footer>
</div>
<script>
// Global variables
let sentimentChart;
let allPosts = [];
let analyzedData = {};
document.addEventListener('DOMContentLoaded', function() {
const analyzeBtn = document.getElementById('analyzeBtn');
const profileUrl = document.getElementById('profileUrl');
const resultsSection = document.getElementById('resultsSection');
const loadingState = document.getElementById('loadingState');
const downloadReport = document.getElementById('downloadReport');
analyzeBtn.addEventListener('click', function() {
const input = profileUrl.value.trim();
if (!input) {
alert('Please enter a valid Reddit username');
return;
}
// Extract username (handle u/ prefix or not)
let username = input.startsWith('u/') ? input.substring(2) : input;
username = username.split('/')[0].split('?')[0].trim();
if (!username) {
alert('Please enter a valid Reddit username');
return;
}
// Show loading state
loadingState.classList.remove('hidden');
resultsSection.classList.add('hidden');
// Start analysis
analyzeRedditUser(username);
});
downloadReport.addEventListener('click', function() {
if (!analyzedData.username) return;
generatePDFReport();
});
async function analyzeRedditUser(username) {
try {
// Reset progress
document.getElementById('progressBar').style.width = '0%';
// Step 1: Fetch user data
updateLoadingMessage(`Fetching profile data for u/${username}...`);
await simulateProgress(10);
const userData = await fetchRedditUserData(username);
if (!userData) {
throw new Error('User not found or private profile');
}
// Step 2: Fetch user posts
updateLoadingMessage(`Fetching posts by u/${username}...`);
await simulateProgress(20);
const posts = await fetchRedditUserPosts(username);
if (!posts || posts.length === 0) {
throw new Error('No public posts found');
}
allPosts = posts;
// Step 3: Analyze content
updateLoadingMessage(`Analyzing ${posts.length} posts...`);
await simulateProgress(40);
const analysisResults = await analyzePosts(posts);
// Step 4: Process results
updateLoadingMessage(`Compiling ideological fingerprint...`);
await simulateProgress(70);
// Complete progress
document.getElementById('progressBar').style.width = '100%';
// Display results
displayResults(username, userData, analysisResults);
// Hide loading state after a brief delay
setTimeout(() => {
loadingState.classList.add('hidden');
resultsSection.classList.remove('hidden');
// Scroll to results
resultsSection.scrollIntoView({ behavior: 'smooth' });
}, 500);
} catch (error) {
console.error('Analysis failed:', error);
loadingState.classList.add('hidden');
alert(`Analysis failed: ${error.message}`);
}
}
async function fetchRedditUserData(username) {
// In a real implementation, you would use the Reddit API with proper authentication
// For this demo, we'll use a proxy service to avoid CORS issues
try {
const response = await fetch(`https://www.reddit.com/user/${username}/about.json`);
if (!response.ok) throw new Error('User not found');
const data = await response.json();
if (data.error) throw new Error(data.message);
return data.data;
} catch (error) {
console.error('Error fetching user data:', error);
throw error;
}
}
async function fetchRedditUserPosts(username) {
// Fetch recent posts (in a real app you'd paginate to get more)
try {
const response = await fetch(`https://www.reddit.com/user/${username}/submitted.json?limit=100`);
if (!response.ok) throw new Error('Failed to fetch posts');
const data = await response.json();
if (data.error) throw new Error(data.message);
return data.data.children.map(child => child.data);
} catch (error) {
console.error('Error fetching posts:', error);
throw error;
}
}
async function analyzePosts(posts) {
// This is where the real analysis would happen
// For this demo, we'll do basic sentiment analysis and topic extraction
const results = {
totalPosts: posts.length,
earliestPost: new Date(Math.min(...posts.map(p => p.created_utc * 1000))),
latestPost: new Date(Math.max(...posts.map(p => p.created_utc * 1000))),
sentimentScores: [],
topics: {},
controversialPosts: [],
timeline: []
};
// Process each post
posts.forEach(post => {
// Basic sentiment analysis (very simplified)
const text = post.title + ' ' + (post.selftext || '');
const sentiment = analyzeSentiment(text);
results.sentimentScores.push({
date: new Date(post.created_utc * 1000),
score: sentiment.score,
magnitude: sentiment.magnitude
});
// Track controversial posts
if (post.downvotes > 0 || post.controversiality > 0) {
results.controversialPosts.push({
title: post.title,
text: post.selftext,
score: post.score,
url: `https://reddit.com${post.permalink}`,
date: new Date(post.created_utc * 1000)
});
}
// Extract topics (using compromise.js NLP)
const doc = window.nlp(text);
const nouns = doc.nouns().out('array');
const adjectives = doc.adjectives().out('array');
nouns.forEach(noun => {
results.topics[noun] = (results.topics[noun] || 0) + 1;
});
adjectives.forEach(adj => {
results.topics[adj] = (results.topics[adj] || 0) + 1;
});
});
// Sort controversial posts
results.controversialPosts.sort((a, b) => b.score - a.score);
// Process topics - get top 20
const topicEntries = Object.entries(results.topics)
.filter(([word]) => word.length > 3) // ignore short words
.sort((a, b) => b[1] - a[1])
.slice(0, 20);
results.topTopics = topicEntries.map(([text, count]) => ({
text,
weight: count / posts.length // normalize weight
}));
// Create timeline segments (group by year)
const postsByYear = {};
posts.forEach(post => {
const year = new Date(post.created_utc * 1000).getFullYear();
if (!postsByYear[year]) postsByYear[year] = [];
postsByYear[year].push(post);
});
// Create timeline items for each year with significant activity
Object.entries(postsByYear).forEach(([year, yearPosts]) => {
if (yearPosts.length < 3) return; // Skip years with few posts
// Analyze this year's posts
const yearText = yearPosts.map(p => p.title + ' ' + (p.selftext || '')).join(' ');
const yearSentiment = analyzeSentiment(yearText);
// Get top topics for this year
const yearDoc = window.nlp(yearText);
const yearNouns = yearDoc.nouns().out('array');
const yearTopics = {};
yearNouns.forEach(noun => {
if (noun.length > 3) {
yearTopics[noun] = (yearTopics[noun] || 0) + 1;
}
});
const topYearTopics = Object.entries(yearTopics)
.sort((a, b) => b[1] - a[1])
.slice(0, 3)
.map(([topic]) => topic);
// Create timeline item
results.timeline.push({
year: year.toString(),
title: getTimelineTitle(year, yearSentiment.score),
description: getTimelineDescription(year, yearSentiment.score, topYearTopics),
sentiment: yearSentiment.score > 0.2 ? 'positive' : yearSentiment.score < -0.2 ? 'negative' : 'neutral',
tags: topYearTopics
});
});
return results;
}
function analyzeSentiment(text) {
// Very basic sentiment analysis
// In a real implementation, you'd use a proper NLP library
const positiveWords = ['good', 'great', 'awesome', 'happy', 'love', 'best', 'excellent', 'positive'];
const negativeWords = ['bad', 'terrible', 'awful', 'hate', 'worst', 'negative', 'angry', 'sad'];
const words = text.toLowerCase().split(/\s+/);
let positiveCount = 0;
let negativeCount = 0;
let total = 0;
words.forEach(word => {
if (positiveWords.includes(word)) {
positiveCount++;
total++;
} else if (negativeWords.includes(word)) {
negativeCount++;
total++;
}
});
const score = total > 0 ? (positiveCount - negativeCount) / total : 0;
const magnitude = total > 0 ? (positiveCount + negativeCount) / total : 0;
return { score, magnitude };
}
function getTimelineTitle(year, sentimentScore) {
const descriptors = [
"Neutral Period",
"Active Year",
"Productive Phase",
"Engagement Spike"
];
if (sentimentScore > 0.3) {
return "Positive Outlook";
} else if (sentimentScore < -0.3) {
return "Negative Phase";
}
return descriptors[year % descriptors.length];
}
function getTimelineDescription(year, sentimentScore, topics) {
let desc = `In ${year}, the user frequently discussed ${topics.join(', ')}. `;
if (sentimentScore > 0.3) {
desc += "Their language was predominantly positive, showing optimism in their posts.";
} else if (sentimentScore < -0.3) {
desc += "Their language showed signs of negativity, with critical or pessimistic tones.";
} else {
desc += "Their language was generally neutral, with balanced emotional expression.";
}
return desc;
}
function displayResults(username, userData, analysisResults) {
// Store for PDF generation
analyzedData = {
username,
userData,
analysisResults
};
// Set profile info
document.getElementById('profileName').textContent = userData.name || username;
document.getElementById('profileHandle').textContent = `u/${username}`;
document.getElementById('avatarInitial').textContent = username.charAt(0).toUpperCase();
// Use Reddit icon if available
if (userData.icon_img) {
document.getElementById('avatarContainer').innerHTML = `
<img src="${userData.icon_img}" class="w-full h-full rounded-full object-cover" alt="Profile">
`;
}
// Set stats
document.getElementById('postCount').textContent = analysisResults.totalPosts;
const yearsActive = analysisResults.latestPost.getFullYear() - analysisResults.earliestPost.getFullYear() + 1;
document.getElementById('timeSpan').textContent = `${yearsActive} year${yearsActive !== 1 ? 's' : ''}`;
// Calculate ideology score (simplified)
const avgSentiment = analysisResults.sentimentScores.reduce((sum, item) => sum + item.score, 0) / analysisResults.sentimentScores.length;
const ideologyScore = Math.round(50 + (avgSentiment * 50)); // Convert to 0-100 scale
document.getElementById('ideologyScore').textContent = `${ideologyScore}/100`;
// Calculate volatility (standard deviation of sentiment)
const variance = analysisResults.sentimentScores.reduce((sum, item) => {
return sum + Math.pow(item.score - avgSentiment, 2);
}, 0) / analysisResults.s
</html>