🐉 Welcome to George's Quests!

Which Year Group are you in?

Who are you?

Show George your finished quest!


⚠️

Demo Version Limited

Uploads won't work on this version and you will need to download and install on your own server to test fully.

Because this preview is hosted on the public internet, file uploads have been disabled for security and data privacy. To see the automatic file sorting, IP tracking, and teacher dashboard in action, please deploy the code on your private local school network.

⚙️ IT Setup & Data Compliance

1. Data Privacy & Legal Compliance

This software architecture is explicitly designed to solve cloud-based data sovereignty concerns for schools in Europe.

2. Step-by-Step Local Server Installation

To run this system, you need a local computer or server (Windows, Mac, or Linux) connected to the school Wi-Fi. This system operates entirely from a single root folder. Do not use subfolders like /public.

Step A: Install Prerequisites

Ensure Node.js is installed on your local server.

Step B: Terminal Setup

Open your terminal (or Command Prompt) and run these exact commands to create your single root folder and install the required Node.js libraries:

# 1. Create a single folder for the app and navigate into it
mkdir georges-quests
cd georges-quests

# 2. Initialize Node.js and install the required backend modules
npm init -y
npm install express multer cors

Step C: Create the Backend (server.js)

In your georges-quests folder, create a file named server.js and paste the code below.

const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const app = express();
const PORT = 3000; 

app.use(express.json());
// Serves HTML and Icons directly from this single root folder
app.use(express.static(__dirname));

const getClientIp = (req) => req.headers['x-forwarded-for'] || req.socket.remoteAddress;
const getTimestamp = () => new Date().toISOString().replace(/:/g, '-').slice(0, 19);

const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        const dir = path.join(__dirname, 'uploads', req.body.yearGroup, req.body.studentName);
        if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
        cb(null, dir);
    },
    filename: (req, file, cb) => {
        const ext = path.extname(file.originalname) || '.jpg';
        cb(null, `${getTimestamp()}_IP-${getClientIp(req).replace(/:/g, '-')}_Work${ext}`);
    }
});

app.post('/upload', multer({ storage }).single('workFile'), (req, res) => {
    console.log(`Received work from ${req.body.studentName} in ${req.body.yearGroup}`);
    res.json({ success: true });
});

app.listen(PORT, '0.0.0.0', () => {
    console.log(`🐉 George's Quests is active at http://YOUR_SERVER_IP:${PORT}`);
});

Step D: Enable PWA (Add to Home Screen)

To allow iPads and computers to install this as a full-screen app, you must place 6 specific files directly inside your georges-quests root folder. Do not put them in a subfolder.

⚠️ THE STRICT PWA ICON RULES: Google Chrome and edge browsers will silently block the installation if you do not provide three exact image formats. You can use a free tool like Favicon.io to generate them from a square logo.

Code for manifest.json:

{
  "id": "/georges-quests/",
  "name": "George's Quests",
  "short_name": "Quests",
  "start_url": "./index.html",
  "display": "standalone",
  "background_color": "#f0f8ff",
  "theme_color": "#4caf50",
  "icons": [
    {
      "src": "icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any"
    },
    {
      "src": "icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any"
    }
  ]
}

Code for sw.js:

const CACHE_NAME = 'georges-quests-v1';
const ASSETS = [
    './',
    './index.html',
    './manifest.json',
    './icon-192.png',
    './icon-512.png',
    './favicon.ico'
];

self.addEventListener('install', (e) => {
    e.waitUntil(
        caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
    );
    self.skipWaiting();
});

self.addEventListener('activate', (e) => {
    e.waitUntil(
        caches.keys().then((keyList) => {
            return Promise.all(keyList.map((key) => {
                if (key !== CACHE_NAME) return caches.delete(key);
            }));
        })
    );
    self.clients.claim();
});

self.addEventListener('fetch', (e) => {
    e.respondWith(
        caches.match(e.request).then((response) => {
            return response || fetch(e.request);
        })
    );
});

Step E: Start the Server

In your terminal, run the following command to start the system:

node server.js

To install on an iPad: Open the local IP in Safari (e.g., http://192.168.1.15:3000), tap the 'Share' icon (square with an up arrow), and select "Add to Home Screen".