chore: cleanup website files

- Remove website/dashboards/ directory
- Remove website/aitbc-proxy.conf
- Remove website/README.md
- Update website documentation files with minimum Python version
- Update website assets (CSS, JS) for improved performance
- Update docs/ with minimum Python version and updated paths
This commit is contained in:
aitbc
2026-04-21 21:15:55 +02:00
parent cdba253fb2
commit cad12ab2fe
22 changed files with 616 additions and 265 deletions

View File

@@ -21,10 +21,18 @@ body.dark {
--global-header-cta-text: #0f172a;
}
body.light {
--global-header-bg: rgba(255, 255, 255, 0.97);
--global-header-border: rgba(15, 23, 42, 0.08);
--global-header-text: #111827;
--global-header-muted: #6b7280;
--global-header-pill: rgba(37, 99, 235, 0.07);
--global-header-pill-hover: rgba(37, 99, 235, 0.15);
--global-header-accent: #2563eb;
--global-header-cta-text: #fff;
}
.global-header {
height: 90px;
box-sizing: border-box;
position: sticky;
top: 0;
width: 100%;
@@ -35,13 +43,13 @@ body.dark {
}
.global-header__inner {
max-width: 1160px;
max-width: 1200px;
margin: 0 auto;
padding: 0 1.25rem;
height: 100%;
padding: 0.85rem 1.25rem;
display: flex;
align-items: center;
gap: 1.25rem;
flex-wrap: wrap;
justify-content: space-between;
}
@@ -127,10 +135,27 @@ body.dark {
flex-wrap: wrap;
}
.global-dark-toggle {
border: 1px solid var(--global-header-border);
background: transparent;
color: var(--global-header-text);
padding: 0.35rem 0.9rem;
border-radius: 999px;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 0.35rem;
cursor: pointer;
transition: all 0.2s ease;
}
.global-dark-toggle:hover {
border-color: var(--global-header-accent);
color: var(--global-header-accent);
}
.global-subnav {
max-width: 1160px;
max-width: 1200px;
margin: 0 auto;
padding: 0 1.25rem 0.75rem;
display: flex;
@@ -159,18 +184,10 @@ body.dark {
color: var(--global-header-accent);
}
@media (max-width: 960px) {
.global-header {
height: auto;
min-height: 90px;
padding: 1rem 0;
}
.global-header__inner {
flex-direction: column;
align-items: flex-start;
height: auto;
}
.global-header__actions {
@@ -182,6 +199,7 @@ body.dark {
width: 100%;
}
}
@media (max-width: 640px) {
.global-brand__text span {
font-size: 1rem;

View File

@@ -1,12 +1,4 @@
(function () {
// Always enforce dark theme
document.documentElement.setAttribute('data-theme', 'dark');
document.documentElement.classList.add('dark');
// Clean up any old user preferences
if (localStorage.getItem('theme')) localStorage.removeItem('theme');
if (localStorage.getItem('exchangeTheme')) localStorage.removeItem('exchangeTheme');
const NAV_ITEMS = [
{ key: 'home', label: 'Home', href: '/' },
{ key: 'explorer', label: 'Explorer', href: '/explorer/' },
@@ -15,6 +7,8 @@
{ key: 'docs', label: 'Docs', href: '/docs/index.html' },
];
const CTA = { label: 'Launch Marketplace', href: '/marketplace/' };
function determineActiveKey(pathname) {
if (pathname.startsWith('/explorer')) return 'explorer';
if (pathname.startsWith('/marketplace')) return 'marketplace';
@@ -42,11 +36,64 @@
</div>
</a>
<nav class="global-nav">${navLinks}</nav>
<div class="global-header__actions">
<button type="button" class="global-dark-toggle" data-role="global-theme-toggle">
<span class="global-dark-toggle__emoji">🌙</span>
<span class="global-dark-toggle__text">Dark</span>
</button>
<a href="${CTA.href}" class="global-nav__cta">${CTA.label}</a>
</div>
</div>
</header>
`;
}
function getCurrentTheme() {
if (document.documentElement.hasAttribute('data-theme')) {
return document.documentElement.getAttribute('data-theme');
}
if (document.documentElement.classList.contains('dark')) return 'dark';
if (document.body && document.body.classList.contains('light')) return 'light';
return 'light';
}
function updateToggleLabel(theme) {
const emojiEl = document.querySelector('.global-dark-toggle__emoji');
const textEl = document.querySelector('.global-dark-toggle__text');
if (!emojiEl || !textEl) return;
if (theme === 'dark') {
emojiEl.textContent = '🌙';
textEl.textContent = 'Dark';
} else {
emojiEl.textContent = '☀️';
textEl.textContent = 'Light';
}
}
function bindThemeToggle() {
const toggle = document.querySelector('[data-role="global-theme-toggle"]');
if (!toggle) return;
toggle.addEventListener('click', () => {
if (typeof window.toggleDarkMode === 'function') {
window.toggleDarkMode();
} else if (typeof window.toggleTheme === 'function') {
window.toggleTheme();
} else {
const isDark = document.documentElement.classList.toggle('dark');
if (isDark) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.removeAttribute('data-theme');
}
}
setTimeout(() => updateToggleLabel(getCurrentTheme()), 0);
});
updateToggleLabel(getCurrentTheme());
}
function initHeader() {
const activeKey = determineActiveKey(window.location.pathname);
const headerHTML = buildHeader(activeKey);
@@ -59,6 +106,8 @@
} else {
document.body.insertAdjacentHTML('afterbegin', headerHTML);
}
bindThemeToggle();
}
if (document.readyState === 'loading') {

View File

@@ -37,6 +37,78 @@ document.querySelectorAll('.feature-card, .arch-component').forEach(el => {
observer.observe(el);
});
// Dark mode functionality with enhanced persistence and system preference detection
function toggleDarkMode() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
}
function setTheme(theme) {
// Apply theme immediately
document.documentElement.setAttribute('data-theme', theme);
// Save to localStorage for persistence
localStorage.setItem('theme', theme);
// Update button display if it exists
updateThemeButton(theme);
// Send analytics event
if (window.analytics) {
window.analytics.track('theme_changed', { theme });
}
}
function updateThemeButton(theme) {
const emoji = document.getElementById('darkModeEmoji');
const text = document.getElementById('darkModeText');
if (emoji && text) {
if (theme === 'dark') {
emoji.textContent = '🌙';
text.textContent = 'Dark';
} else {
emoji.textContent = '☀️';
text.textContent = 'Light';
}
}
}
function getPreferredTheme() {
// 1. Check localStorage first (user preference)
const saved = localStorage.getItem('theme');
if (saved) {
return saved;
}
// 2. Check system preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
// 3. Default to dark (AITBC brand preference)
return 'dark';
}
function initializeTheme() {
const theme = getPreferredTheme();
setTheme(theme);
// Listen for system preference changes
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
// Only auto-switch if user hasn't manually set a preference
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
}
}
// Initialize theme immediately (before DOM loads)
initializeTheme();
// Touch gesture support for mobile navigation
class TouchNavigation {
constructor() {
@@ -46,53 +118,190 @@ class TouchNavigation {
this.touchEndY = 0;
this.minSwipeDistance = 50;
this.maxVerticalDistance = 100;
this.initializeTouchEvents();
// Get all major sections for navigation
this.sections = ['hero', 'features', 'architecture', 'achievements', 'documentation'];
this.currentSectionIndex = 0;
this.bindEvents();
this.setupMobileOptimizations();
}
initializeTouchEvents() {
document.addEventListener('touchstart', (e) => {
this.touchStartX = e.changedTouches[0].screenX;
this.touchStartY = e.changedTouches[0].screenY;
}, { passive: true });
document.addEventListener('touchend', (e) => {
this.touchEndX = e.changedTouches[0].screenX;
this.touchEndY = e.changedTouches[0].screenY;
this.handleSwipe();
}, { passive: true });
bindEvents() {
document.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: false });
document.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
document.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: false });
}
handleSwipe() {
const xDiff = this.touchEndX - this.touchStartX;
const yDiff = Math.abs(this.touchEndY - this.touchStartY);
// Ensure swipe is mostly horizontal
if (yDiff > this.maxVerticalDistance) return;
if (Math.abs(xDiff) > this.minSwipeDistance) {
if (xDiff > 0) {
// Swipe Right - potentially open menu or go back
this.onSwipeRight();
handleTouchStart(e) {
this.touchStartX = e.touches[0].clientX;
this.touchStartY = e.touches[0].clientY;
}
handleTouchMove(e) {
// Prevent scrolling when detecting horizontal swipes
const touchCurrentX = e.touches[0].clientX;
const touchCurrentY = e.touches[0].clientY;
const deltaX = Math.abs(touchCurrentX - this.touchStartX);
const deltaY = Math.abs(touchCurrentY - this.touchStartY);
// If horizontal movement is greater than vertical, prevent default scrolling
if (deltaX > deltaY && deltaX > 10) {
e.preventDefault();
}
}
handleTouchEnd(e) {
this.touchEndX = e.changedTouches[0].clientX;
this.touchEndY = e.changedTouches[0].clientY;
const deltaX = this.touchEndX - this.touchStartX;
const deltaY = Math.abs(this.touchEndY - this.touchStartY);
// Only process swipe if vertical movement is minimal
if (deltaY < this.maxVerticalDistance && Math.abs(deltaX) > this.minSwipeDistance) {
if (deltaX > 0) {
this.swipeRight();
} else {
// Swipe Left - potentially close menu or go forward
this.onSwipeLeft();
this.swipeLeft();
}
}
}
onSwipeRight() {
// Can be implemented if a side menu is added
// console.log('Swiped right');
swipeLeft() {
// Navigate to next section
const nextIndex = Math.min(this.currentSectionIndex + 1, this.sections.length - 1);
this.navigateToSection(nextIndex);
}
onSwipeLeft() {
// Can be implemented if a side menu is added
// console.log('Swiped left');
swipeRight() {
// Navigate to previous section
const prevIndex = Math.max(this.currentSectionIndex - 1, 0);
this.navigateToSection(prevIndex);
}
navigateToSection(index) {
const sectionId = this.sections[index];
const element = document.getElementById(sectionId);
if (element) {
this.currentSectionIndex = index;
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Update URL hash without triggering scroll
history.replaceState(null, null, `#${sectionId}`);
}
}
setupMobileOptimizations() {
// Add touch-friendly interactions
this.setupTouchButtons();
this.setupScrollOptimizations();
this.setupMobileMenu();
}
setupTouchButtons() {
// Make buttons more touch-friendly
const buttons = document.querySelectorAll('button, .cta-button, .nav-button');
buttons.forEach(button => {
button.addEventListener('touchstart', () => {
button.style.transform = 'scale(0.98)';
}, { passive: true });
button.addEventListener('touchend', () => {
button.style.transform = '';
}, { passive: true });
});
}
setupScrollOptimizations() {
// Improve momentum scrolling on iOS
if ('webkitOverflowScrolling' in document.body.style) {
document.body.style.webkitOverflowScrolling = 'touch';
}
// Add smooth scrolling for anchor links with touch feedback
document.querySelectorAll('a[href^="#"]').forEach(link => {
link.addEventListener('touchstart', () => {
link.style.opacity = '0.7';
}, { passive: true });
link.addEventListener('touchend', () => {
link.style.opacity = '';
}, { passive: true });
});
}
setupMobileMenu() {
// Create mobile menu toggle if nav is hidden on mobile
const nav = document.querySelector('nav');
if (nav && window.innerWidth < 768) {
this.createMobileMenu();
}
}
createMobileMenu() {
// Create hamburger menu for mobile
const header = document.querySelector('header');
if (!header) return;
const mobileMenuBtn = document.createElement('button');
mobileMenuBtn.className = 'mobile-menu-btn';
mobileMenuBtn.innerHTML = '☰';
mobileMenuBtn.setAttribute('aria-label', 'Toggle mobile menu');
const nav = document.querySelector('nav');
if (nav) {
nav.style.display = 'none';
mobileMenuBtn.addEventListener('click', () => {
const isOpen = nav.style.display !== 'none';
nav.style.display = isOpen ? 'none' : 'flex';
mobileMenuBtn.innerHTML = isOpen ? '☰' : '✕';
});
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (!header.contains(e.target)) {
nav.style.display = 'none';
mobileMenuBtn.innerHTML = '☰';
}
});
header.appendChild(mobileMenuBtn);
}
}
updateCurrentSection() {
// Update current section based on scroll position
const scrollY = window.scrollY + window.innerHeight / 2;
this.sections.forEach((sectionId, index) => {
const element = document.getElementById(sectionId);
if (element) {
const rect = element.getBoundingClientRect();
const elementTop = rect.top + window.scrollY;
const elementBottom = elementTop + rect.height;
if (scrollY >= elementTop && scrollY < elementBottom) {
this.currentSectionIndex = index;
}
}
});
}
}
// Initialize touch navigation when DOM is loaded
// Initialize touch navigation when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new TouchNavigation();
// Update current section on scroll
window.addEventListener('scroll', () => {
if (window.touchNav) {
window.touchNav.updateCurrentSection();
}
}, { passive: true });
});

View File

@@ -5,24 +5,176 @@
'use strict';
function sendToAnalytics(metric) {
const safeEntries = Array.isArray(metric.entries) ? metric.entries : [];
const safeValue = Number.isFinite(metric.value) ? Math.round(metric.value) : 0;
const safeDelta = Number.isFinite(metric.delta) ? Math.round(metric.delta) : 0;
// In production we log to console. The /api/web-vitals endpoint was removed
// to reduce unnecessary network noise as we are not running a telemetry backend.
console.log(`[Web Vitals] ${metric.name}: ${safeValue}`);
}
// Load web-vitals from CDN
const script = document.createElement('script');
script.src = 'https://unpkg.com/web-vitals@3/dist/web-vitals.iife.js';
script.onload = function() {
if (window.webVitals) {
window.webVitals.onCLS(sendToAnalytics);
window.webVitals.onFID(sendToAnalytics);
window.webVitals.onLCP(sendToAnalytics);
window.webVitals.onFCP(sendToAnalytics);
window.webVitals.onTTFB(sendToAnalytics);
const data = {
name: metric.name,
value: safeValue,
id: metric.id || 'unknown',
delta: safeDelta,
entries: safeEntries.map(e => ({
name: e.name,
startTime: e.startTime,
duration: e.duration
})),
url: window.location.href,
timestamp: new Date().toISOString()
};
const payload = JSON.stringify(data);
// Send to analytics endpoint
if (navigator.sendBeacon) {
const blob = new Blob([payload], { type: 'application/json' });
const ok = navigator.sendBeacon('/api/web-vitals', blob);
if (!ok) {
fetch('/api/web-vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true
}).catch(() => {});
}
} else {
fetch('/api/web-vitals', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: payload,
keepalive: true
}).catch(() => {});
}
};
document.head.appendChild(script);
// Also log to console in development
console.log(`[Web Vitals] ${metric.name}: ${metric.value}`, metric);
}
// Largest Contentful Paint (LCP)
function observeLCP() {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
sendToAnalytics({
name: 'LCP',
value: lastEntry.renderTime || lastEntry.loadTime,
id: lastEntry.id,
delta: 0,
entries: entries
});
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });
} catch (e) {}
}
// First Input Delay (FID)
function observeFID() {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
sendToAnalytics({
name: 'FID',
value: entry.processingStart - entry.startTime,
id: entry.id,
delta: 0,
entries: [entry]
});
});
});
observer.observe({ entryTypes: ['first-input'] });
} catch (e) {}
}
// Cumulative Layout Shift (CLS)
function observeCLS() {
try {
let clsValue = 0;
const observer = new PerformanceObserver((list) => {
try {
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
});
sendToAnalytics({
name: 'CLS',
value: clsValue,
id: 'cls',
delta: 0,
entries: entries
});
} catch (e) {
// CLS measurement failed, but don't crash the whole script
console.log('CLS measurement error:', e.message);
}
});
// Try to observe layout-shift, but handle if it's not supported
try {
observer.observe({ entryTypes: ['layout-shift'] });
} catch (e) {
console.log('Layout-shift observation not supported:', e.message);
// CLS will not be measured, but other metrics will still work
}
} catch (e) {
console.log('CLS observer setup failed:', e.message);
}
}
// Time to First Byte (TTFB)
function measureTTFB() {
try {
const nav = performance.getEntriesByType('navigation')[0];
if (nav) {
sendToAnalytics({
name: 'TTFB',
value: nav.responseStart,
id: 'ttfb',
delta: 0,
entries: [nav]
});
}
} catch (e) {}
}
// First Contentful Paint (FCP)
function observeFCP() {
try {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (entry.name === 'first-contentful-paint') {
sendToAnalytics({
name: 'FCP',
value: entry.startTime,
id: entry.id,
delta: 0,
entries: [entry]
});
}
});
});
observer.observe({ entryTypes: ['paint'] });
} catch (e) {}
}
// Initialize when page loads
if (document.readyState === 'complete') {
observeLCP();
observeFID();
observeCLS();
observeFCP();
measureTTFB();
} else {
window.addEventListener('load', () => {
observeLCP();
observeFID();
observeCLS();
observeFCP();
measureTTFB();
});
}
})();