rabbit samurai
diff --git a/images/thumbnails/gnmath.svg b/images/thumbnails/gnmath.svg
new file mode 100644
index 000000000..bc55a3eed
--- /dev/null
+++ b/images/thumbnails/gnmath.svg
@@ -0,0 +1,19 @@
+
diff --git a/scripts/add-gnmath-games.js b/scripts/add-gnmath-games.js
new file mode 100644
index 000000000..2677fb5af
--- /dev/null
+++ b/scripts/add-gnmath-games.js
@@ -0,0 +1,132 @@
+#!/usr/bin/env node
+
+/**
+ * Script to add GN Math games to Verdis
+ * Since we can't fetch zones.json externally, this uses a curated list
+ * of popular GN Math games and creates wrapper pages for them
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// Popular GN Math games (sample list - would normally come from zones.json)
+// Format: { id, name, genre, special tags }
+const GNMATH_GAMES = [
+ { id: 1, name: "Drive Mad", genre: "arcade", tags: ["popular", "driving"] },
+ { id: 2, name: "Crazy Cattle 3D", genre: "arcade", tags: ["3d"] },
+ { id: 3, name: "Moto X3M", genre: "arcade", tags: ["popular", "driving"] },
+ { id: 4, name: "Tunnel Rush", genre: "arcade", tags: ["popular", "3d"] },
+ { id: 5, name: "Vex 5", genre: "platformer", tags: ["popular"] },
+ { id: 6, name: "Vex 6", genre: "platformer", tags: ["popular"] },
+ { id: 7, name: "Vex 7", genre: "platformer", tags: ["popular"] },
+ { id: 8, name: "Slope", genre: "arcade", tags: ["popular", "3d"] },
+ { id: 9, name: "Run 3", genre: "platformer", tags: ["popular"] },
+ { id: 10, name: "Geometry Dash", genre: "platformer", tags: ["popular"] },
+];
+
+function normalizeGameName(name) {
+ return name.toLowerCase()
+ .replace(/[^a-z0-9\s]/g, '')
+ .replace(/\s+/g, '');
+}
+
+function createGameHTML(game) {
+ const template = `
+
+
+
+
+
${game.name} | Verdis
+
+
+
+
Loading ${game.name}...
+
+
+
+
+`;
+ return template;
+}
+
+async function main() {
+ const gamesDir = path.join(__dirname, '..', 'games');
+ const existingGames = fs.readdirSync(gamesDir)
+ .filter(f => {
+ const fullPath = path.join(gamesDir, f);
+ return fs.statSync(fullPath).isDirectory();
+ });
+
+ console.log(`Found ${existingGames.length} existing games`);
+
+ let added = 0;
+ let skipped = 0;
+
+ for (const game of GNMATH_GAMES) {
+ const dirName = normalizeGameName(game.name);
+
+ // Check if game already exists
+ if (existingGames.includes(dirName)) {
+ console.log(`⏭️ Skipping "${game.name}" - already exists`);
+ skipped++;
+ continue;
+ }
+
+ // Create game directory
+ const gameDir = path.join(gamesDir, dirName);
+ fs.mkdirSync(gameDir, { recursive: true });
+
+ // Create index.html
+ const htmlContent = createGameHTML(game);
+ const htmlPath = path.join(gameDir, 'index.html');
+ fs.writeFileSync(htmlPath, htmlContent);
+
+ console.log(`✅ Added "${game.name}" (${dirName})`);
+ added++;
+ }
+
+ console.log(`\n=== Summary ===`);
+ console.log(`Games added: ${added}`);
+ console.log(`Games skipped (duplicates): ${skipped}`);
+ console.log(`\nNext steps:`);
+ console.log(`1. Add game entries to games/index.html`);
+ console.log(`2. Add thumbnail images to images/thumbnails/`);
+ console.log(`3. Test the games load correctly`);
+}
+
+main().catch(console.error);
diff --git a/scripts/fetch-gnmath-games.js b/scripts/fetch-gnmath-games.js
new file mode 100755
index 000000000..68ab34c8b
--- /dev/null
+++ b/scripts/fetch-gnmath-games.js
@@ -0,0 +1,91 @@
+#!/usr/bin/env node
+
+/**
+ * Script to fetch GN Math games and add them to Verdis
+ * This script will:
+ * 1. Fetch the zones.json from GN Math
+ * 2. Check for duplicates against existing Verdis games
+ * 3. Generate game entries for games/index.html
+ */
+
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const ZONES_URL = 'https://cdn.jsdelivr.net/gh/gn-math/assets@main/zones.json';
+const COVER_URL = 'https://cdn.jsdelivr.net/gh/gn-math/covers@main';
+const HTML_URL = 'https://cdn.jsdelivr.net/gh/gn-math/html@main';
+
+function fetchJSON(url) {
+ return new Promise((resolve, reject) => {
+ https.get(url, (res) => {
+ let data = '';
+ res.on('data', chunk => data += chunk);
+ res.on('end', () => {
+ try {
+ resolve(JSON.parse(data));
+ } catch (e) {
+ reject(e);
+ }
+ });
+ }).on('error', reject);
+ });
+}
+
+function normalizeGameName(name) {
+ // Convert game name to directory-friendly format
+ return name.toLowerCase()
+ .replace(/[^a-z0-9\s]/g, '')
+ .replace(/\s+/g, '');
+}
+
+async function main() {
+ console.log('Fetching GN Math games list...');
+
+ try {
+ const zones = await fetchJSON(ZONES_URL);
+ console.log(`Found ${zones.length} games from GN Math`);
+
+ // Get existing game directories
+ const gamesDir = path.join(__dirname, '..', 'games');
+ const existingGames = fs.readdirSync(gamesDir)
+ .filter(f => fs.statSync(path.join(gamesDir, f)).isDirectory());
+
+ console.log(`Found ${existingGames.length} existing games in Verdis`);
+
+ // Filter out duplicates and special entries
+ const newGames = zones.filter(zone => {
+ const normalizedName = normalizeGameName(zone.name);
+ const isDuplicate = existingGames.includes(normalizedName);
+ const isExternal = zone.url && zone.url.startsWith('http');
+ return !isDuplicate && !isExternal;
+ });
+
+ console.log(`${newGames.length} new games to add (after filtering duplicates and external links)`);
+
+ // Output game data as JSON for further processing
+ const outputPath = path.join(__dirname, 'gnmath-games.json');
+ fs.writeFileSync(outputPath, JSON.stringify(newGames, null, 2));
+ console.log(`Game data saved to ${outputPath}`);
+
+ // Generate summary
+ console.log('\n=== Summary ===');
+ console.log(`Total GN Math games: ${zones.length}`);
+ console.log(`Existing Verdis games: ${existingGames.length}`);
+ console.log(`New games to add: ${newGames.length}`);
+ console.log(`Skipped (duplicates or external): ${zones.length - newGames.length}`);
+
+ if (newGames.length > 0) {
+ console.log('\n=== Sample of new games ===');
+ newGames.slice(0, 10).forEach(game => {
+ console.log(` - ${game.name} (ID: ${game.id})`);
+ });
+ }
+
+ } catch (error) {
+ console.error('Error:', error.message);
+ process.exit(1);
+ }
+}
+
+main();