tractatus/public/koha/transparency.html
TheFlow ae16d64082 feat: add Koha pre-production deployment configuration
Deployment Strategy:
- Deploy all Koha infrastructure to production
- Keep user-facing functionality disabled until Stripe keys configured
- Allow backend testing and validation before payment processing activation

Changes:
- Add coming-soon-overlay.js component for Koha pages
- Add Stripe configuration check in koha.controller.js (returns 503 if PLACEHOLDER keys detected)
- Update all Koha HTML pages with coming soon overlay script
- Create comprehensive deployment guide (KOHA_PRODUCTION_DEPLOYMENT.md)
- Create automated deployment script (deploy-koha-to-production.sh)

Pre-Production Features:
- Database initialization ready (init-koha.js)
- API endpoints functional but protected
- Transparency dashboard returns empty data structure
- Coming soon overlay prevents user access to incomplete functionality
- All code deployed and testable

Activation Checklist:
- Configure live Stripe keys
- Remove coming-soon overlay scripts
- Remove PLACEHOLDER checks from controller
- Add navigation links to Koha pages
- Test end-to-end donation flow

Estimated Time to Activate: 2-3 hours once Stripe keys ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 21:00:54 +13:00

312 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transparency Dashboard | Tractatus Koha</title>
<meta name="description" content="Full transparency on donations received and how they're allocated to support the Tractatus AI Safety Framework.">
<link rel="stylesheet" href="/css/tailwind.css?v=1759833751">
<style>
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: 0; z-index: 100; background: white; padding: 1rem; }
/* Accessibility: Focus indicators (WCAG 2.4.7) */
a:focus, button:focus {
outline: 3px solid #3b82f6;
outline-offset: 2px;
}
a:focus:not(:focus-visible) { outline: none; }
a:focus-visible { outline: 3px solid #3b82f6; outline-offset: 2px; }
/* Progress bar animation */
.progress-bar {
transition: width 1s ease-out;
}
/* Stats card */
.stat-card {
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-4px);
}
</style>
</head>
<body class="bg-gray-50">
<!-- Skip Link for Keyboard Navigation -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Navigation (injected by navbar.js) -->
<script src="/js/components/navbar.js?v=1759875690"></script>
<!-- Main Content -->
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<!-- Header -->
<div class="text-center mb-12">
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 mb-4">
Transparency Dashboard
</h1>
<p class="text-xl text-gray-600 max-w-3xl mx-auto">
Full visibility into donations received and how we allocate them to support the Tractatus Framework.
</p>
<p class="text-sm text-gray-500 mt-4" id="last-updated">
Last updated: Loading...
</p>
</div>
<!-- Key Metrics -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
<!-- Total Received -->
<div class="stat-card bg-white shadow rounded-lg p-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-sm font-medium text-gray-600">Total Received</h2>
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div class="text-4xl font-bold text-gray-900" id="total-received">$0</div>
<div class="text-sm text-gray-500 mt-1">NZD lifetime</div>
</div>
<!-- Monthly Supporters -->
<div class="stat-card bg-white shadow rounded-lg p-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-sm font-medium text-gray-600">Monthly Supporters</h2>
<svg class="w-6 h-6 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</div>
<div class="text-4xl font-bold text-gray-900" id="monthly-supporters">0</div>
<div class="text-sm text-gray-500 mt-1">Active subscriptions</div>
</div>
<!-- Monthly Recurring -->
<div class="stat-card bg-white shadow rounded-lg p-6">
<div class="flex items-center justify-between mb-2">
<h2 class="text-sm font-medium text-gray-600">Monthly Recurring</h2>
<svg class="w-6 h-6 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
</svg>
</div>
<div class="text-4xl font-bold text-gray-900" id="monthly-revenue">$0</div>
<div class="text-sm text-gray-500 mt-1">NZD per month</div>
</div>
</div>
<!-- Allocation Breakdown -->
<div class="bg-white shadow rounded-lg p-8 mb-12">
<h2 class="text-2xl font-bold text-gray-900 mb-6">How Donations Are Allocated</h2>
<div class="space-y-6">
<!-- Development: 40% -->
<div>
<div class="flex justify-between items-center mb-2">
<div>
<span class="font-semibold text-gray-900">Development</span>
<span class="text-sm text-gray-600 ml-2">Feature development, bug fixes, security updates</span>
</div>
<span class="font-bold text-blue-600">40%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div class="progress-bar bg-blue-600 h-3 rounded-full" style="width: 0%;" data-width="40"></div>
</div>
</div>
<!-- Hosting: 30% -->
<div>
<div class="flex justify-between items-center mb-2">
<div>
<span class="font-semibold text-gray-900">Hosting & Infrastructure</span>
<span class="text-sm text-gray-600 ml-2">Servers, database, CDN, domain, SSL</span>
</div>
<span class="font-bold text-green-600">30%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div class="progress-bar bg-green-600 h-3 rounded-full" style="width: 0%;" data-width="30"></div>
</div>
</div>
<!-- Research: 20% -->
<div>
<div class="flex justify-between items-center mb-2">
<div>
<span class="font-semibold text-gray-900">Research</span>
<span class="text-sm text-gray-600 ml-2">AI safety research, academic partnerships, publications</span>
</div>
<span class="font-bold text-purple-600">20%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div class="progress-bar bg-purple-600 h-3 rounded-full" style="width: 0%;" data-width="20"></div>
</div>
</div>
<!-- Community: 10% -->
<div>
<div class="flex justify-between items-center mb-2">
<div>
<span class="font-semibold text-gray-900">Community</span>
<span class="text-sm text-gray-600 ml-2">Documentation, outreach, contributor support</span>
</div>
<span class="font-bold text-orange-600">10%</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div class="progress-bar bg-orange-600 h-3 rounded-full" style="width: 0%;" data-width="10"></div>
</div>
</div>
</div>
</div>
<!-- One-Time Donations -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
<div class="bg-white shadow rounded-lg p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-3">One-Time Donations</h3>
<div class="text-3xl font-bold text-gray-900" id="onetime-count">0</div>
<div class="text-sm text-gray-500">Total gifts received</div>
</div>
<div class="bg-white shadow rounded-lg p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-3">Average Donation</h3>
<div class="text-3xl font-bold text-gray-900" id="average-donation">$0</div>
<div class="text-sm text-gray-500">NZD across all donations</div>
</div>
</div>
<!-- Recent Supporters (Public Acknowledgements) -->
<div class="bg-white shadow rounded-lg p-8">
<h2 class="text-2xl font-bold text-gray-900 mb-6">Recent Supporters</h2>
<p class="text-gray-600 mb-6">
Thank you to these generous supporters who have opted to be publicly acknowledged. All supporters are valued equally, whether listed here or donating anonymously.
</p>
<div id="recent-donors" class="space-y-3">
<p class="text-gray-500 text-center py-8">Loading supporters...</p>
</div>
<div id="no-donors" class="text-center py-8 text-gray-500" style="display: none;">
No public acknowledgements yet. Be the first to support Tractatus!
</div>
</div>
<!-- Call to Action -->
<div class="mt-12 bg-gradient-to-r from-blue-600 to-purple-600 rounded-lg p-8 text-center text-white">
<h2 class="text-2xl font-bold mb-4">Support the Tractatus Framework</h2>
<p class="text-blue-100 mb-6 max-w-2xl mx-auto">
Help us build architectural AI safety constraints that preserve human agency. Every contribution makes a difference.
</p>
<a href="/koha.html" class="inline-block bg-white text-blue-600 px-8 py-3 rounded-lg font-semibold hover:bg-blue-50 transition">
Make a Donation
</a>
</div>
</main>
<!-- Footer -->
<script src="/js/components/footer.js"></script>
<!-- Coming Soon Overlay (remove when Stripe is configured) -->
<script src="/js/components/coming-soon-overlay.js"></script>
<script>
// Load transparency metrics
async function loadMetrics() {
try {
const response = await fetch('/api/koha/transparency');
const data = await response.json();
if (data.success && data.data) {
const metrics = data.data;
// Update stats
document.getElementById('total-received').textContent = `$${metrics.total_received.toFixed(2)}`;
document.getElementById('monthly-supporters').textContent = metrics.monthly_supporters;
document.getElementById('monthly-revenue').textContent = `$${metrics.monthly_recurring_revenue.toFixed(2)}`;
document.getElementById('onetime-count').textContent = metrics.one_time_donations || 0;
// Calculate average
const totalCount = metrics.monthly_supporters + (metrics.one_time_donations || 0);
const avgDonation = totalCount > 0 ? metrics.total_received / totalCount : 0;
document.getElementById('average-donation').textContent = `$${avgDonation.toFixed(2)}`;
// Update last updated time
const lastUpdated = new Date(metrics.last_updated);
document.getElementById('last-updated').textContent = `Last updated: ${lastUpdated.toLocaleString()}`;
// Animate progress bars
setTimeout(() => {
document.querySelectorAll('.progress-bar').forEach(bar => {
const width = bar.getAttribute('data-width');
bar.style.width = width + '%';
});
}, 100);
// Display recent donors
if (metrics.recent_donors && metrics.recent_donors.length > 0) {
const donorsHtml = metrics.recent_donors.map(donor => {
const date = new Date(donor.date);
const dateStr = date.toLocaleDateString('en-NZ', { year: 'numeric', month: 'short' });
const freqBadge = donor.frequency === 'monthly'
? '<span class="inline-block bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded">Monthly</span>'
: '<span class="inline-block bg-green-100 text-green-800 text-xs px-2 py-1 rounded">One-time</span>';
// Format currency display
const currency = (donor.currency || 'nzd').toUpperCase();
const amountDisplay = `$${donor.amount.toFixed(2)} ${currency}`;
// Show NZD equivalent if different currency
const nzdEquivalent = currency !== 'NZD'
? `<div class="text-xs text-gray-500">≈ $${donor.amount_nzd.toFixed(2)} NZD</div>`
: '';
return `
<div class="flex items-center justify-between py-3 border-b border-gray-200 last:border-0">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<span class="text-blue-600 font-semibold">${donor.name.charAt(0).toUpperCase()}</span>
</div>
<div>
<div class="font-medium text-gray-900">${donor.name}</div>
<div class="text-sm text-gray-500">${dateStr}</div>
</div>
</div>
<div class="text-right">
<div class="font-semibold text-gray-900">${amountDisplay}</div>
${nzdEquivalent}
${freqBadge}
</div>
</div>
`;
}).join('');
document.getElementById('recent-donors').innerHTML = donorsHtml;
} else {
document.getElementById('recent-donors').style.display = 'none';
document.getElementById('no-donors').style.display = 'block';
}
} else {
throw new Error('Failed to load metrics');
}
} catch (error) {
console.error('Error loading transparency metrics:', error);
document.getElementById('recent-donors').innerHTML = `
<div class="text-center py-8 text-red-600">
Failed to load transparency data. Please try again later.
</div>
`;
}
}
// Load metrics on page load
loadMetrics();
// Refresh every 5 minutes
setInterval(loadMetrics, 5 * 60 * 1000);
</script>
</body>
</html>