Josh Bruce Online

School Route Mapper - Setup & Deployment Guide

Table of Contents

  1. Quick Start
  2. Development Setup
  3. Production Deployment
  4. Configuration
  5. Platform-Specific Guides
  6. Troubleshooting
  7. Performance Optimization
  8. Security Considerations

Quick Start

Immediate Testing

The School Route Mapper requires no build process and can run directly in any modern web browser.

# Option 1: Simple file server
cd route/
python3 -m http.server 8000
# Open http://localhost:8000 in browser

# Option 2: Node.js http-server
npx http-server route/ -p 8000

# Option 3: PHP built-in server
cd route/
php -S localhost:8000

Browser Requirements

Essential Features Needed


Development Setup

Prerequisites

Development Environment

1. Clone or Download

# If using Git
git clone <repository-url>
cd school-route-mapper/route

# Or download and extract ZIP file
unzip school-route-mapper.zip
cd school-route-mapper/route

2. Local Web Server Setup

# Python 3.x
python3 -m http.server 8000

# Python 2.x (if needed)
python -m SimpleHTTPServer 8000
Node.js
# Install http-server globally
npm install -g http-server

# Serve from route directory
http-server -p 8000 -c-1

# Alternative: use npx (no global install)
npx http-server -p 8000 -c-1
PHP
# PHP 5.4+
php -S localhost:8000

# With specific document root
php -S localhost:8000 -t ./
Live Server (VS Code Extension)
  1. Install “Live Server” extension in VS Code
  2. Right-click index.html
  3. Select “Open with Live Server”

3. Development Tools

Browser Developer Tools
// Access application instance in console
window.schoolRouteMapper

// Check application state
window.schoolRouteMapper.getState()

// Access components
window.schoolRouteMapper.canvasGrid
window.schoolRouteMapper.drawingTools
window.schoolRouteMapper.poiManager
Debug Mode

Create debug.html for development:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>School Route Mapper - Debug</title>
    <link rel="stylesheet" href="styles/main.css">
    <link rel="stylesheet" href="styles/themes.css">
    <style>
        .debug-panel {
            position: fixed;
            top: 10px;
            right: 10px;
            background: rgba(0,0,0,0.8);
            color: white;
            padding: 10px;
            border-radius: 5px;
            font-family: monospace;
            font-size: 12px;
            z-index: 1000;
        }
    </style>
</head>
<body>
    <!-- Debug panel -->
    <div class="debug-panel" id="debug-panel">
        <div>FPS: <span id="fps">0</span></div>
        <div>Features: <span id="feature-count">0</span></div>
        <div>Zoom: <span id="zoom-level">0</span></div>
        <div>Mouse: <span id="mouse-pos">0, 0</span></div>
    </div>
    
    <!-- Include all normal content from index.html -->
    <!-- ... -->
    
    <script type="module">
        import app from './js/app.js';
        
        // Debug information updates
        let frameCount = 0;
        let lastTime = performance.now();
        
        function updateDebugInfo() {
            const now = performance.now();
            const deltaTime = now - lastTime;
            frameCount++;
            
            if (deltaTime >= 1000) {
                const fps = Math.round(frameCount * 1000 / deltaTime);
                document.getElementById('fps').textContent = fps;
                frameCount = 0;
                lastTime = now;
            }
            
            if (app.isInitialized) {
                const state = app.getState();
                const project = state.project;
                
                if (project) {
                    const featureCount = 
                        project.features.polylines.length + 
                        project.features.pois.length;
                    document.getElementById('feature-count').textContent = featureCount;
                }
                
                if (app.canvasGrid) {
                    const zoom = app.canvasGrid.viewport.zoom.toFixed(1);
                    document.getElementById('zoom-level').textContent = zoom + 'x';
                    
                    const mouse = app.canvasGrid.mouse.world;
                    const mouseText = `${mouse[0].toFixed(2)}, ${mouse[1].toFixed(2)}`;
                    document.getElementById('mouse-pos').textContent = mouseText;
                }
            }
            
            requestAnimationFrame(updateDebugInfo);
        }
        
        updateDebugInfo();
    </script>
</body>
</html>

Testing

Unit Testing Setup

Create test.html for basic testing:

<!DOCTYPE html>
<html>
<head>
    <title>School Route Mapper - Tests</title>
    <style>
        .test-result { margin: 5px 0; padding: 5px; border-radius: 3px; }
        .pass { background: #d4edda; color: #155724; }
        .fail { background: #f8d7da; color: #721c24; }
    </style>
</head>
<body>
    <h1>School Route Mapper Tests</h1>
    <div id="test-results"></div>
    
    <script type="module">
        import { createEmptyProject, validateProject, CategoryManager } from './js/data-model.js';
        
        class TestRunner {
            constructor() {
                this.results = [];
            }
            
            test(name, fn) {
                try {
                    fn();
                    this.results.push({ name, status: 'pass' });
                } catch (error) {
                    this.results.push({ name, status: 'fail', error: error.message });
                }
            }
            
            render() {
                const container = document.getElementById('test-results');
                container.innerHTML = this.results.map(result => `
                    <div class="test-result ${result.status}">
                        ${result.status.toUpperCase()}: ${result.name}
                        ${result.error ? `<br>Error: ${result.error}` : ''}
                    </div>
                `).join('');
            }
        }
        
        const runner = new TestRunner();
        
        // Data model tests
        runner.test('Create empty project', () => {
            const project = createEmptyProject('Test Project');
            if (!project.meta.title === 'Test Project') {
                throw new Error('Project title not set correctly');
            }
        });
        
        runner.test('Validate valid project', () => {
            const project = createEmptyProject();
            const result = validateProject(project);
            if (!result.valid) {
                throw new Error('Valid project failed validation');
            }
        });
        
        runner.test('Category manager operations', () => {
            const manager = new CategoryManager();
            const category = {
                id: 'test',
                name: 'Test Category',
                color: '#ff0000',
                walkability: 'normal'
            };
            
            manager.addCategory(category);
            const retrieved = manager.getCategory('test');
            
            if (!retrieved || retrieved.name !== 'Test Category') {
                throw new Error('Category not added/retrieved correctly');
            }
        });
        
        // Add more tests as needed
        
        runner.render();
    </script>
</body>
</html>

Manual Testing Checklist


Production Deployment

Static Web Hosting

File Preparation

  1. Minification (Optional)
    # CSS minification
    npm install -g clean-css-cli
    cleancss -o styles/main.min.css styles/main.css styles/themes.css
       
    # JavaScript minification
    npm install -g terser
    find js/ -name "*.js" -exec terser {} -o {}.min -m \;
    
  2. Gzip Compression
    # Pre-compress files for nginx
    find . -name "*.js" -o -name "*.css" -o -name "*.html" -o -name "*.json" | \
    xargs gzip -k -9
    

Web Server Configuration

Nginx
server {
    listen 80;
    server_name your-domain.com;
    root /path/to/route/;
    index index.html;
    
    # Enable gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    gzip_min_length 1000;
    
    # Cache static assets
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Security headers
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    
    # MIME type for .schoolmap.json files
    location ~* \.schoolmap\.json$ {
        add_header Content-Type application/json;
    }
    
    # Fallback for SPA-like behavior
    location / {
        try_files $uri $uri/ /index.html;
    }
}
Apache (.htaccess)
# Enable compression
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/x-javascript
</IfModule>

# Cache control
<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresByType text/css "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>

# Security headers
<IfModule mod_headers.c>
    Header always set X-Content-Type-Options nosniff
    Header always set X-Frame-Options DENY
    Header always set X-XSS-Protection "1; mode=block"
</IfModule>

# MIME type for .schoolmap.json
AddType application/json .schoolmap.json

# Enable HTTPS redirect
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

Cloud Deployment

GitHub Pages

# .github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
    
    - name: Install dependencies (if using build tools)
      run: npm ci
      working-directory: ./route
    
    - name: Build (if needed)
      run: npm run build
      working-directory: ./route
    
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: $
        publish_dir: ./route

Netlify

# netlify.toml
[build]
  publish = "route/"
  command = "echo 'No build required'"

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    X-Content-Type-Options = "nosniff"

[[headers]]
  for = "*.js"
  [headers.values]
    Cache-Control = "public, max-age=31536000"

[[headers]]
  for = "*.css"
  [headers.values]
    Cache-Control = "public, max-age=31536000"

# Redirects for SPA behavior (if needed)
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200
  conditions = {Role = ["admin"]}

Vercel

{
  "version": 2,
  "builds": [
    {
      "src": "route/**",
      "use": "@vercel/static"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "/route/$1"
    }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        }
      ]
    }
  ]
}

AWS S3 + CloudFront

# Upload to S3
aws s3 sync route/ s3://your-bucket-name/ --delete

# Configure bucket for static website hosting
aws s3 website s3://your-bucket-name --index-document index.html

# Create CloudFront distribution (optional)
aws cloudfront create-distribution --distribution-config file://cloudfront-config.json

CDN Setup

CloudFlare Configuration

  1. DNS Settings: Point domain to your hosting
  2. SSL/TLS: Set to “Full (strict)”
  3. Speed Optimizations:
    • Enable Auto Minify (CSS, JavaScript, HTML)
    • Enable Brotli compression
    • Set Browser Cache TTL to 1 year for static assets
  4. Security Rules:
    • Block access to /js/ directory browsing
    • Rate limiting for API endpoints (if added)

Performance Rules

// CloudFlare Page Rules
// Rule 1: Cache static assets
// URL: yourdomain.com/*.js, yourdomain.com/*.css
// Settings: 
//   - Cache Level: Cache Everything
//   - Edge Cache TTL: 1 year
//   - Browser Cache TTL: 1 year

// Rule 2: Security headers
// URL: yourdomain.com/*
// Settings:
//   - Security Level: Medium
//   - Browser Integrity Check: On

Configuration

Environment Configuration

Production Environment Variables

# .env (if using build tools)
NODE_ENV=production
PUBLIC_URL=https://yourdomain.com
ENABLE_DEBUGGING=false
ANALYTICS_ID=your-analytics-id

Configuration File

// config.js
const CONFIG = {
  production: {
    debug: false,
    analytics: {
      enabled: true,
      trackingId: 'GA-XXXXXXXX-X'
    },
    features: {
      fileExport: true,
      backgroundImages: true,
      demo: true
    },
    performance: {
      autosaveInterval: 30000,
      maxHistoryItems: 10,
      highQualityRendering: true
    }
  },
  
  development: {
    debug: true,
    analytics: {
      enabled: false
    },
    features: {
      fileExport: true,
      backgroundImages: true,
      demo: true,
      debugPanel: true
    },
    performance: {
      autosaveInterval: 10000,
      maxHistoryItems: 50,
      highQualityRendering: true
    }
  }
};

// Auto-detect environment
const environment = window.location.hostname === 'localhost' ? 'development' : 'production';
window.APP_CONFIG = CONFIG[environment];

Security Configuration

Content Security Policy

<meta http-equiv="Content-Security-Policy" content="
  default-src 'self';
  script-src 'self' 'unsafe-inline';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: blob:;
  connect-src 'self';
  font-src 'self';
  object-src 'none';
  media-src 'self';
  child-src 'none';
">

Feature Policy

<meta http-equiv="Permissions-Policy" content="
  camera=(),
  microphone=(),
  geolocation=(),
  payment=(),
  usb=()
">

Platform-Specific Guides

School Network Deployment

Considerations for Educational Environments

  1. Firewall Rules
    • Allow HTTPS traffic on standard ports
    • Whitelist your domain
    • Ensure WebSocket connections work (if added)
  2. Content Filtering
    • Request category exemption for educational tools
    • Provide documentation of educational value
    • Test on school network before full deployment
  3. Device Compatibility
    • Test on school-provided devices
    • Consider older browser versions
    • Ensure touch interface works on tablets

Deployment Script for Schools

#!/bin/bash
# school-deploy.sh

# Configuration
DOMAIN="schoolmaps.yourdomain.edu"
BACKUP_DIR="/backup/schoolmaps"
DEPLOY_DIR="/var/www/schoolmaps"

# Create backup
echo "Creating backup..."
tar -czf "$BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S).tar.gz" "$DEPLOY_DIR"

# Deploy new version
echo "Deploying new version..."
rsync -av --delete route/ "$DEPLOY_DIR/"

# Set permissions
chown -R www-data:www-data "$DEPLOY_DIR"
chmod -R 644 "$DEPLOY_DIR"
find "$DEPLOY_DIR" -type d -exec chmod 755 {} \;

# Test deployment
echo "Testing deployment..."
curl -f "https://$DOMAIN" > /dev/null && echo "✓ Site accessible" || echo "✗ Site not accessible"

echo "Deployment complete!"

Mobile App Integration

PWA Configuration

// manifest.json
{
  "name": "School Route Mapper",
  "short_name": "RouteMapper",
  "description": "Interactive school campus mapping tool",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#3b82f6",
  "orientation": "any",
  "icons": [
    {
      "src": "icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "categories": ["education", "navigation", "productivity"],
  "lang": "en",
  "scope": "/"
}

Service Worker

// sw.js - Basic service worker for PWA
const CACHE_NAME = 'school-route-mapper-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/styles/themes.css',
  '/js/app.js',
  '/js/data-model.js',
  '/js/canvas-grid.js',
  '/js/drawing-tools.js',
  '/js/poi.js',
  '/js/storage.js',
  '/js/ui-controls.js',
  '/js/spline.js',
  '/js/demo-data.js',
  '/js/pathfinding-worker.js'
];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        return cache.addAll(urlsToCache);
      })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});

Docker Deployment

Dockerfile

FROM nginx:alpine

# Copy application files
COPY route/ /usr/share/nginx/html/

# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Add security headers
RUN echo 'add_header X-Content-Type-Options nosniff;' >> /etc/nginx/conf.d/security.conf && \
    echo 'add_header X-Frame-Options DENY;' >> /etc/nginx/conf.d/security.conf && \
    echo 'add_header X-XSS-Protection "1; mode=block";' >> /etc/nginx/conf.d/security.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Docker Compose

version: '3.8'

services:
  school-route-mapper:
    build: .
    ports:
      - "80:80"
    volumes:
      - ./route:/usr/share/nginx/html:ro
    restart: unless-stopped
    environment:
      - NGINX_ENVSUBST_TEMPLATE_SUFFIX=.template
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.schoolmaps.rule=Host(`schoolmaps.yourdomain.com`)"
      - "traefik.http.services.schoolmaps.loadbalancer.server.port=80"

networks:
  default:
    external:
      name: web

Troubleshooting

Common Issues

Application Won’t Load

# Check console for errors
# 1. Open browser developer tools (F12)
# 2. Check Console tab for error messages
# 3. Check Network tab for failed requests

# Common causes:
# - CORS issues (need proper web server)
# - Missing files (check file paths)
# - Browser compatibility (check browser version)

JavaScript Module Errors

# Error: "Failed to resolve module specifier"
# Solution: Ensure you're serving from a web server, not file:// protocol

# Error: "Unexpected token '<'"
# Solution: Check that .js files are served with correct MIME type

# Add to .htaccess or server config:
AddType application/javascript .js

Performance Issues

// Debug performance in browser console
console.time('render');
app.render();
console.timeEnd('render');

// Check memory usage
console.log('Memory usage:', performance.memory);

// Monitor frame rate
let lastTime = 0;
let frameCount = 0;

function checkFPS() {
  const now = performance.now();
  frameCount++;
  
  if (now - lastTime >= 1000) {
    console.log('FPS:', frameCount);
    frameCount = 0;
    lastTime = now;
  }
  
  requestAnimationFrame(checkFPS);
}
checkFPS();

Storage Issues

// Check localStorage quota
function checkStorageQuota() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    navigator.storage.estimate().then(estimate => {
      console.log('Storage quota:', estimate.quota);
      console.log('Storage usage:', estimate.usage);
      console.log('Storage available:', estimate.quota - estimate.usage);
    });
  }
}
checkStorageQuota();

// Clear storage if needed
localStorage.removeItem('schoolRouteMapper');
localStorage.removeItem('schoolRouteMapper_autosave');
localStorage.removeItem('schoolRouteMapper_history');

Browser-Specific Issues

Safari Issues

// Safari requires user interaction for file downloads
// Ensure export is triggered by user click

// Safari canvas memory limits
// Reduce canvas size if needed
const maxCanvasSize = 4096; // Safari limit
if (canvas.width > maxCanvasSize || canvas.height > maxCanvasSize) {
  // Scale down canvas
}

Mobile Browser Issues

/* Prevent zoom on input focus */
input, select, textarea {
  font-size: 16px; /* Minimum to prevent zoom */
}

/* Handle viewport units on mobile */
.full-height {
  height: 100vh;
  height: -webkit-fill-available; /* Safari mobile fix */
}

Internet Explorer (Legacy Support)

<!-- IE compatibility (if needed) -->
<!--[if IE]>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<p style="background: #ffebee; padding: 20px; margin: 20px; border: 1px solid #f44336;">
  This application requires a modern browser. Please upgrade to Chrome, Firefox, Safari, or Edge.
</p>
<![endif]-->

Debug Tools

Performance Monitor

class PerformanceMonitor {
  constructor() {
    this.metrics = [];
    this.isRecording = false;
  }
  
  start() {
    this.isRecording = true;
    this.startTime = performance.now();
  }
  
  mark(label) {
    if (!this.isRecording) return;
    
    this.metrics.push({
      label,
      timestamp: performance.now(),
      memory: performance.memory ? performance.memory.usedJSHeapSize : 0
    });
  }
  
  stop() {
    this.isRecording = false;
    return this.metrics;
  }
  
  generateReport() {
    console.table(this.metrics);
    
    // Memory usage over time
    const memoryData = this.metrics.map(m => m.memory);
    console.log('Memory usage (bytes):', {
      min: Math.min(...memoryData),
      max: Math.max(...memoryData),
      avg: memoryData.reduce((a, b) => a + b, 0) / memoryData.length
    });
  }
}

// Usage
const monitor = new PerformanceMonitor();
monitor.start();
monitor.mark('App initialized');
// ... app operations ...
monitor.mark('Features loaded');
monitor.stop();
monitor.generateReport();

Performance Optimization

Frontend Optimization

Code Splitting

// Dynamic imports for large features
async function loadPathfindingWorker() {
  const { PathfindingEngine } = await import('./js/pathfinding-worker.js');
  return new PathfindingEngine();
}

// Load only when needed
document.getElementById('calculate-route-btn').addEventListener('click', async () => {
  if (!this.pathfinder) {
    this.pathfinder = await loadPathfindingWorker();
  }
  // ... route calculation
});

Image Optimization

# Optimize PNG images
pngquant --quality=65-80 --ext .png --force icons/*.png

# Convert to WebP
for file in icons/*.png; do
  cwebp -q 80 "$file" -o "${file%.png}.webp"
done

# Use in HTML with fallback
<picture>
  <source srcset="icon.webp" type="image/webp">
  <img src="icon.png" alt="Icon">
</picture>

CSS Optimization

/* Use efficient selectors */
.feature-selected { /* Good */ }
div.feature.selected { /* Less efficient */ }

/* Minimize reflows */
.canvas-container {
  will-change: transform; /* Hint to browser */
  contain: layout style paint; /* CSS containment */
}

/* Use hardware acceleration */
.animated-element {
  transform: translateZ(0); /* Force GPU acceleration */
}

JavaScript Optimization

// Use requestAnimationFrame for animations
function animate() {
  // Update animation
  requestAnimationFrame(animate);
}

// Debounce expensive operations
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

const debouncedResize = debounce(() => {
  app.canvasGrid.resize();
}, 250);

window.addEventListener('resize', debouncedResize);

Server Optimization

Caching Strategy

// Service Worker caching strategy
const CACHE_STRATEGIES = {
  // App shell - cache first
  appShell: ['/index.html', '/js/', '/styles/'],
  
  // Static assets - cache first with update
  static: ['/icons/', '/fonts/'],
  
  // User data - network first
  userData: ['/api/'],
  
  // Background images - cache with fallback
  images: ['/uploads/', '/backgrounds/']
};

Compression

# nginx.conf - Advanced compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
  application/json
  application/javascript
  application/xml
  text/css
  text/javascript
  text/plain
  text/xml;

# Brotli compression (if available)
brotli on;
brotli_comp_level 6;
brotli_types
  application/json
  application/javascript
  text/css
  text/javascript
  text/plain;

Security Considerations

Input Validation

// Sanitize user inputs
function sanitizeInput(input) {
  if (typeof input !== 'string') return '';
  
  return input
    .replace(/[<>]/g, '') // Remove potential HTML
    .trim()
    .substring(0, 1000); // Limit length
}

// Validate coordinates
function validateCoordinate(coord) {
  const num = parseFloat(coord);
  return !isNaN(num) && isFinite(num) && Math.abs(num) < 1000000;
}

File Upload Security

// Validate imported files
function validateImportedFile(file) {
  // Check file size (max 10MB)
  if (file.size > 10 * 1024 * 1024) {
    throw new Error('File too large');
  }
  
  // Check file type
  if (!file.name.endsWith('.json') && !file.name.endsWith('.schoolmap.json')) {
    throw new Error('Invalid file type');
  }
  
  // Validate JSON structure
  try {
    const data = JSON.parse(content);
    const validation = validateProject(data);
    if (!validation.valid) {
      throw new Error('Invalid project structure');
    }
  } catch (e) {
    throw new Error('Invalid JSON format');
  }
}

XSS Prevention

// Escape HTML in user content
function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

// Use when displaying user content
poiNameElement.innerHTML = escapeHtml(poi.name);

Data Privacy

// Local-only storage notice
const PRIVACY_NOTICE = `
This application stores all data locally in your browser.
No information is sent to external servers.
Your map data remains private and under your control.
`;

// Optional analytics (with consent)
function initializeAnalytics() {
  if (window.APP_CONFIG.analytics.enabled) {
    const consent = localStorage.getItem('analytics-consent');
    if (consent === 'granted') {
      // Initialize analytics
    } else {
      // Show consent dialog
      showAnalyticsConsentDialog();
    }
  }
}

This deployment guide provides comprehensive instructions for setting up the School Route Mapper in various environments. Choose the deployment method that best fits your infrastructure and requirements.