a new start
25
public/apple-touch-icon.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180" viewBox="0 0 180 180">
|
||||
<defs>
|
||||
<linearGradient id="bg-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#9733EE;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background with rounded corners for iOS -->
|
||||
<rect width="180" height="180" rx="40" fill="url(#bg-gradient)" />
|
||||
|
||||
<!-- Awesome icon centered and scaled -->
|
||||
<g transform="translate(90, 90) scale(2.8)">
|
||||
<path fill="#FFFFFF" opacity="0.3" d="m14.8 8.625l-.3-1.5-4.95 1.05V2.5h-1.5v4.6125l-5.2125-3.375-.825 1.275 5.7 3.675-5.7 8.25 1.2.9 4.2375-5.55 3.15 4.7625 1.275-.825-3.15-4.6875z" />
|
||||
<circle cx="9" cy="9" r="2.625" fill="#FFFFFF" />
|
||||
<g fill="#FFFFFF" opacity="0.9">
|
||||
<circle cx="9" cy="3" r="1.875" />
|
||||
<circle cx="14.625" cy="7.875" r="1.875" />
|
||||
<circle cx="2.625" cy="4.875" r="1.875" />
|
||||
<circle cx="4.125" cy="15.375" r="1.875" />
|
||||
<circle cx="12.75" cy="14.625" r="1.875" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
14
public/awesome-icon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 48 48">
|
||||
<!-- Main structure - Awesome Purple -->
|
||||
<path fill="#9733EE" d="m39.4 23l-.8-4L26 21.6V8h-4v12.3l-13.9-9l-2.2 3.4l15.2 9.8L9.4 39.8l3.2 2.4l11.3-14.8l8.4 12.7l3.4-2.2l-8.4-12.5z" />
|
||||
|
||||
<!-- Center circle - Awesome Pink -->
|
||||
<circle cx="24" cy="24" r="7" fill="#FF69B4" />
|
||||
|
||||
<!-- Outer circles - Gradient from Pink to Purple to Gold -->
|
||||
<circle cx="24" cy="8" r="5" fill="#DA22FF" /> <!-- Top: Primary Purple -->
|
||||
<circle cx="39" cy="21" r="5" fill="#FF69B4" /> <!-- Right: Pink -->
|
||||
<circle cx="7" cy="13" r="5" fill="#FFD700" /> <!-- Left: Gold -->
|
||||
<circle cx="11" cy="41" r="5" fill="#FF1493" /> <!-- Bottom Left: Dark Pink -->
|
||||
<circle cx="34" cy="39" r="5" fill="#E855FF" /> <!-- Bottom Right: Light Purple -->
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 864 B |
14
public/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<defs>
|
||||
<linearGradient id="favicon-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF69B4;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Simplified for 16x16 favicon -->
|
||||
<circle cx="8" cy="8" r="7" fill="url(#favicon-gradient)" />
|
||||
<circle cx="8" cy="8" r="4" fill="#FF69B4" />
|
||||
<circle cx="8" cy="8" r="2" fill="#FFFFFF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 659 B |
27
public/icon-192.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 48 48">
|
||||
<defs>
|
||||
<linearGradient id="bg-192" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#9733EE;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="48" height="48" fill="url(#bg-192)" />
|
||||
|
||||
<!-- Main structure - White with opacity -->
|
||||
<path fill="#FFFFFF" opacity="0.3" d="m39.4 23l-.8-4L26 21.6V8h-4v12.3l-13.9-9l-2.2 3.4l15.2 9.8L9.4 39.8l3.2 2.4l11.3-14.8l8.4 12.7l3.4-2.2l-8.4-12.5z" />
|
||||
|
||||
<!-- Center circle - White -->
|
||||
<circle cx="24" cy="24" r="7" fill="#FFFFFF" />
|
||||
|
||||
<!-- Outer circles - White with high opacity -->
|
||||
<g fill="#FFFFFF" opacity="0.9">
|
||||
<circle cx="24" cy="8" r="5" />
|
||||
<circle cx="39" cy="21" r="5" />
|
||||
<circle cx="7" cy="13" r="5" />
|
||||
<circle cx="11" cy="41" r="5" />
|
||||
<circle cx="34" cy="39" r="5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
27
public/icon-512.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 48 48">
|
||||
<defs>
|
||||
<linearGradient id="bg-512" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#9733EE;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="48" height="48" fill="url(#bg-512)" />
|
||||
|
||||
<!-- Main structure - White with opacity -->
|
||||
<path fill="#FFFFFF" opacity="0.3" d="m39.4 23l-.8-4L26 21.6V8h-4v12.3l-13.9-9l-2.2 3.4l15.2 9.8L9.4 39.8l3.2 2.4l11.3-14.8l8.4 12.7l3.4-2.2l-8.4-12.5z" />
|
||||
|
||||
<!-- Center circle - White -->
|
||||
<circle cx="24" cy="24" r="7" fill="#FFFFFF" />
|
||||
|
||||
<!-- Outer circles - White with high opacity -->
|
||||
<g fill="#FFFFFF" opacity="0.9">
|
||||
<circle cx="24" cy="8" r="5" />
|
||||
<circle cx="39" cy="21" r="5" />
|
||||
<circle cx="7" cy="13" r="5" />
|
||||
<circle cx="11" cy="41" r="5" />
|
||||
<circle cx="34" cy="39" r="5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
15
public/icon.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<!-- Simplified awesome icon for small sizes -->
|
||||
<defs>
|
||||
<linearGradient id="awesome-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#FF69B4;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Center star shape -->
|
||||
<circle cx="16" cy="16" r="14" fill="url(#awesome-gradient)" />
|
||||
<circle cx="16" cy="16" r="8" fill="#FF69B4" />
|
||||
<circle cx="16" cy="16" r="4" fill="#DA22FF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
46
public/manifest.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "Awesome - Curated Lists Explorer",
|
||||
"short_name": "Awesome",
|
||||
"description": "Next-level ground-breaking AAA webapp for exploring awesome lists from GitHub",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#DA22FF",
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icon-192.svg",
|
||||
"sizes": "192x192",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.svg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/apple-touch-icon.svg",
|
||||
"sizes": "180x180",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"categories": ["productivity", "education", "developer-tools"],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Search",
|
||||
"short_name": "Search",
|
||||
"description": "Search awesome lists",
|
||||
"url": "/?action=search",
|
||||
"icons": [{ "src": "/icons/search-96x96.png", "sizes": "96x96" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
73
public/og-image.svg
Normal file
@@ -0,0 +1,73 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">
|
||||
<defs>
|
||||
<linearGradient id="og-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="30%" style="stop-color:#9733EE;stop-opacity:1" />
|
||||
<stop offset="70%" style="stop-color:#FF69B4;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="text-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#DA22FF;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#9733EE;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#FFD700;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
|
||||
<!-- Glow filter -->
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="1200" height="630" fill="url(#og-gradient)" />
|
||||
|
||||
<!-- Decorative circles -->
|
||||
<circle cx="100" cy="100" r="150" fill="#FFFFFF" opacity="0.05" />
|
||||
<circle cx="1100" cy="530" r="200" fill="#FFFFFF" opacity="0.05" />
|
||||
<circle cx="900" cy="100" r="120" fill="#FFD700" opacity="0.1" />
|
||||
<circle cx="300" cy="500" r="80" fill="#FF69B4" opacity="0.15" />
|
||||
|
||||
<!-- Main content container -->
|
||||
<rect x="100" y="150" width="1000" height="330" rx="20" fill="#FFFFFF" opacity="0.95" />
|
||||
|
||||
<!-- Awesome Icon (centered at top) -->
|
||||
<g transform="translate(500, 200) scale(3.5)" filter="url(#glow)">
|
||||
<path fill="#9733EE" d="m14.8 8.625l-.3-1.5-4.95 1.05V2.5h-1.5v4.6125l-5.2125-3.375-.825 1.275 5.7 3.675-5.7 8.25 1.2.9 4.2375-5.55 3.15 4.7625 1.275-.825-3.15-4.6875z" />
|
||||
<circle cx="9" cy="9" r="2.625" fill="#FF69B4" />
|
||||
<g fill="url(#text-gradient)">
|
||||
<circle cx="9" cy="3" r="1.875" />
|
||||
<circle cx="14.625" cy="7.875" r="1.875" />
|
||||
<circle cx="2.625" cy="4.875" r="1.875" />
|
||||
<circle cx="4.125" cy="15.375" r="1.875" />
|
||||
<circle cx="12.75" cy="14.625" r="1.875" />
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="600" y="340" font-family="system-ui, -apple-system, sans-serif" font-size="80" font-weight="900" text-anchor="middle" fill="url(#text-gradient)">
|
||||
AWESOME
|
||||
</text>
|
||||
|
||||
<!-- Subtitle -->
|
||||
<text x="600" y="400" font-family="system-ui, -apple-system, sans-serif" font-size="32" font-weight="600" text-anchor="middle" fill="#666666">
|
||||
Curated Lists Explorer
|
||||
</text>
|
||||
|
||||
<!-- Stats -->
|
||||
<g transform="translate(600, 440)">
|
||||
<text x="-250" y="0" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700" text-anchor="middle" fill="#9733EE">
|
||||
209 Lists
|
||||
</text>
|
||||
<text x="0" y="0" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700" text-anchor="middle" fill="#FF69B4">
|
||||
14K+ Repos
|
||||
</text>
|
||||
<text x="250" y="0" font-family="system-ui, -apple-system, sans-serif" font-size="24" font-weight="700" text-anchor="middle" fill="#FFD700">
|
||||
FTS5 Search
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
170
public/worker.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Awesome Web Worker
|
||||
* Intelligently polls for database updates and manages cache invalidation
|
||||
*/
|
||||
|
||||
const POLL_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
const DB_VERSION_ENDPOINT = '/api/db-version';
|
||||
|
||||
let currentDbVersion = null;
|
||||
let pollTimer = null;
|
||||
|
||||
// Smart polling with exponential backoff
|
||||
class SmartPoller {
|
||||
constructor(interval) {
|
||||
this.baseInterval = interval;
|
||||
this.currentInterval = interval;
|
||||
this.maxInterval = interval * 4;
|
||||
this.minInterval = interval / 2;
|
||||
this.consecutiveErrors = 0;
|
||||
}
|
||||
|
||||
increaseInterval() {
|
||||
this.currentInterval = Math.min(this.currentInterval * 1.5, this.maxInterval);
|
||||
console.log('[Worker] Increased poll interval to', this.currentInterval / 1000, 'seconds');
|
||||
}
|
||||
|
||||
decreaseInterval() {
|
||||
this.currentInterval = Math.max(this.currentInterval * 0.75, this.minInterval);
|
||||
console.log('[Worker] Decreased poll interval to', this.currentInterval / 1000, 'seconds');
|
||||
}
|
||||
|
||||
resetInterval() {
|
||||
this.currentInterval = this.baseInterval;
|
||||
}
|
||||
|
||||
getInterval() {
|
||||
return this.currentInterval;
|
||||
}
|
||||
}
|
||||
|
||||
const poller = new SmartPoller(POLL_INTERVAL);
|
||||
|
||||
// Check for database updates
|
||||
async function checkForUpdates() {
|
||||
try {
|
||||
const response = await fetch(DB_VERSION_ENDPOINT, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// First time or version changed
|
||||
if (currentDbVersion === null) {
|
||||
currentDbVersion = data.version;
|
||||
console.log('[Worker] Initial DB version:', currentDbVersion);
|
||||
} else if (data.version !== currentDbVersion) {
|
||||
console.log('[Worker] New DB version detected!', data.version);
|
||||
|
||||
// Notify all clients
|
||||
const clients = await self.clients.matchAll();
|
||||
clients.forEach(client => {
|
||||
client.postMessage({
|
||||
type: 'DB_UPDATE',
|
||||
version: data.version,
|
||||
metadata: data,
|
||||
});
|
||||
});
|
||||
|
||||
currentDbVersion = data.version;
|
||||
|
||||
// Invalidate cache
|
||||
await invalidateCache();
|
||||
}
|
||||
|
||||
// Reset error counter and adjust interval
|
||||
poller.consecutiveErrors = 0;
|
||||
poller.resetInterval();
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Worker] Poll failed:', error);
|
||||
poller.consecutiveErrors++;
|
||||
|
||||
if (poller.consecutiveErrors > 3) {
|
||||
poller.increaseInterval();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate cache
|
||||
async function invalidateCache() {
|
||||
try {
|
||||
const cacheNames = await caches.keys();
|
||||
|
||||
await Promise.all(
|
||||
cacheNames.map(cacheName => {
|
||||
if (cacheName.includes('awesome')) {
|
||||
console.log('[Worker] Clearing cache:', cacheName);
|
||||
return caches.delete(cacheName);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
console.log('[Worker] Cache invalidated');
|
||||
} catch (error) {
|
||||
console.error('[Worker] Cache invalidation failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Start polling
|
||||
function startPolling() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer);
|
||||
}
|
||||
|
||||
// Initial check
|
||||
checkForUpdates();
|
||||
|
||||
// Schedule periodic checks
|
||||
pollTimer = setInterval(() => {
|
||||
checkForUpdates();
|
||||
}, poller.getInterval());
|
||||
|
||||
console.log('[Worker] Polling started');
|
||||
}
|
||||
|
||||
// Stop polling
|
||||
function stopPolling() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer);
|
||||
pollTimer = null;
|
||||
console.log('[Worker] Polling stopped');
|
||||
}
|
||||
}
|
||||
|
||||
// Service Worker event listeners
|
||||
self.addEventListener('install', (event) => {
|
||||
console.log('[Worker] Installing...');
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
console.log('[Worker] Activated');
|
||||
event.waitUntil(self.clients.claim());
|
||||
startPolling();
|
||||
});
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
console.log('[Worker] Message received:', event.data);
|
||||
|
||||
if (event.data.type === 'CHECK_UPDATE') {
|
||||
checkForUpdates();
|
||||
} else if (event.data.type === 'START_POLLING') {
|
||||
startPolling();
|
||||
} else if (event.data.type === 'STOP_POLLING') {
|
||||
stopPolling();
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch event - cache strategy
|
||||
self.addEventListener('fetch', (event) => {
|
||||
// Add caching strategy here if needed
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||