// filter event handlers document.getElementById("searchInput").addEventListener("input", (e) => currentSearchTerm = e.target.value; renderApps(); );
.badge font-size: 0.7rem; background: #f0f4f7; padding: 0.2rem 0.6rem; border-radius: 30px;
function renderApps() let filtered = [...appsData]; // category filter if (currentFilterCategory !== "all") filtered = filtered.filter(app => app.category === currentFilterCategory); // search filter (name + desc) if (currentSearchTerm.trim() !== "") app.desc.toLowerCase().includes(term)); // safety threshold if (currentSafetyThreshold > 0) filtered = filtered.filter(app => app.score >= currentSafetyThreshold); const container = document.getElementById("appsContainer"); if (filtered.length === 0) container.innerHTML = `<div style="grid-column:1/-1; text-align:center; padding:3rem; background:white; border-radius:32px;">🤔 No apps match your filters. Try adjusting safety threshold or search.</div>`; return; container.innerHTML = filtered.map(app => let scoreClass = "score-mid"; if (app.score >= 85) scoreClass = "score-high"; else if (app.score < 70) scoreClass = "score-low"; const safetyBadges = []; if (app.score >= 85) safetyBadges.push('<span class="badge green">🔒 Privacy-first</span>'); if (app.trackers === "0 trackers" ).join(''); // attach event listeners to each "view report" button document.querySelectorAll('.review-btn').forEach(btn => btn.addEventListener('click', (e) => const appId = parseInt(btn.getAttribute('data-id')); const app = appsData.find(a => a.id === appId); if (app) openModal(app); ); );
.logo span background: #ffcd3c; color: #1a4a5f; padding: 0.2rem 0.6rem; border-radius: 40px; font-size: 1rem; margin-left: 8px; vertical-align: middle; appsafe club
// category filter buttons const catBtns = document.querySelectorAll(".cat-btn"); catBtns.forEach(btn => btn.addEventListener("click", () => catBtns.forEach(b => b.classList.remove("active")); btn.classList.add("active"); currentFilterCategory = btn.getAttribute("data-cat"); renderApps(); ); );
<script> // Mock app database (AppSafe Club dataset) const appsData = [ id: 1, name: "Signal Messenger", category: "messaging", score: 96, icon: "📞", desc: "End-to-end encrypted messaging, minimal metadata.", permissions: "Contacts, Camera, Microphone", trackers: "0 trackers", privacy: "Excellent, open-source", notes: "Gold standard for privacy." , id: 2, name: "Bitwarden", category: "password", score: 94, icon: "🔐", desc: "Open-source password manager, zero-knowledge architecture.", permissions: "Camera (optional), Accessibility", trackers: "None", privacy: "Transparent & audited", notes: "Highly recommended." , id: 3, name: "ProtonMail", category: "messaging", score: 92, icon: "📧", desc: "Encrypted email with Swiss privacy laws.", permissions: "Contacts, Storage", trackers: "No third-party", privacy: "Strict no-logs", notes: "Best for email security." , id: 4, name: "DuckDuckGo", category: "productivity", score: 90, icon: "🦆", desc: "Private search & browser with tracker blocking.", permissions: "Location (optional)", trackers: "Blocked by default", privacy: "No personal data collection", notes: "Easy privacy." , id: 5, name: "Mullvad VPN", category: "productivity", score: 98, icon: "🛡️", desc: "Anonymous VPN with cash payments.", permissions: "VPN, Notifications", trackers: "Zero", privacy: "Audited no-logs", notes: "Top-tier anonymity." , id: 6, name: "Joplin", category: "productivity", score: 88, icon: "📓", desc: "Open-source note taking with E2EE.", permissions: "Storage, Camera (optional)", trackers: "None", privacy: "Self-host optional", notes: "Great for notes." , id: 7, name: "Monzo", category: "finance", score: 78, icon: "💳", desc: "Digital bank with good security features.", permissions: "Location, Contacts (optional)", trackers: "Limited analytics", privacy: "Standard bank privacy", notes: "Solid but not fully anonymous." , id: 8, name: "MyFitnessPal", category: "health", score: 68, icon: "🏋️", desc: "Calorie counter but shares data with parent company.", permissions: "Location, Camera, Storage", trackers: "Multiple ad trackers", privacy: "Moderate, aggregated data", notes: "Use with caution." , id: 9, name: "Telegram", category: "messaging", score: 72, icon: "✈️", desc: "Cloud-based chats, not fully E2EE by default.", permissions: "Contacts, Microphone, Storage", trackers: "Some analytics", privacy: "Better than average", notes: "Enable secret chats for E2EE." , id: 10, name: "Firefox Focus", category: "productivity", score: 91, icon: "🦊", desc: "Privacy browser that auto-deletes history.", permissions: "Minimal", trackers: "Tracker blocking", privacy: "Strong privacy", notes: "Great for quick searches." , id: 11, name: "1Password", category: "password", score: 89, icon: "🔑", desc: "Feature-rich password manager with secret key.", permissions: "Accessibility, Camera", trackers: "First-party telemetry", privacy: "Strong encryption", notes: "Excellent UI." , id: 12, name: "Luno", category: "finance", score: 74, icon: "₿", desc: "Bitcoin wallet/exchange with 2FA.", permissions: "Identity verification", trackers: "Some analytics", privacy: "KYC required", notes: "Good but centralized." ];
<!-- Modal --> <div id="appModal" class="modal"> <div class="modal-content"> <span class="close-modal">×</span> <h3 id="modalAppName">App Name</h3> <div id="modalCategory" style="font-size:0.8rem; color:#608b9b;"></div> <ul class="detail-list" id="modalDetails"> <li><strong>Safety Score:</strong> <span id="modalScore"></span></li> <li><strong>Permissions used:</strong> <span id="modalPermissions"></span></li> <li><strong>Trackers blocked:</strong> <span id="modalTrackers"></span></li> <li><strong>Privacy policy:</strong> <span id="modalPrivacy"></span></li> <li><strong>Community notes:</strong> <span id="modalNotes"></span></li> </ul> <button id="trustBtn" style="background:#1e6f5c; color:white; border:none; padding:8px 16px; border-radius:40px; cursor:pointer;">✔️ Mark as Trusted by me</button> <small style="display:block; margin-top:12px;">🔐 AppSafe club review — data updated weekly</small> </div> </div> // filter event handlers document
.score-high color: #1e6f5c; .score-mid color: #e9b35f; .score-low color: #c95c4a;
.review-btn:hover background: #1e6f5c; color: white;
.badge.orange background: #fff0e0; color: #c26e2e; currentSearchTerm = e.target.value
@media (max-width: 700px) .logo-area flex-direction: column; .filters-panel flex-direction: column; align-items: stretch; </style> </head> <body>
.badge.green background: #dff0e8; color: #116b56;