// SKATEMAKER PRO — exports. A park leaves here two ways: // 1. park.json — the native data format // 2. level..js — a drop-in for BOOKQUOY: exports the exact interface // bookquoy's js/level.js has (heightAt, normalAt, BOUNDS, // RAILS, GAPS, LETTERS, SPAWN, buildLevel). Ship it with // parkmath.js + parkbuild.js + assets/uploads/. function download(name, text, type = 'application/octet-stream') { const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([text], { type })); a.download = name; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 5000); } const slug = park => (park.name || 'park').toLowerCase().replace(/[^\w]+/g, '_'); export function downloadParkJSON(park) { download(slug(park) + '.json', JSON.stringify(park, null, 1), 'application/json'); } export function generateLevelJS(park) { return `// ${park.name} — generated by SKATEMAKER PRO ${new Date().toISOString().slice(0, 10)} // Drop-in for BOOKQUOY: copy this file + parkmath.js + parkbuild.js (+ assets/uploads/) // into bookquoy/js/ and point main.js/player.js imports at it. Same contract as level.js: // heightAt(x,z) is the single source of truth for visuals AND collision. import { makeHeightAt, makeNormalAt, flattenRails } from './parkmath.js'; import { buildParkScene } from './parkbuild.js'; export const PARK = ${JSON.stringify(park, null, 1)}; export const BOUNDS = PARK.bounds; export const RAILS = flattenRails(PARK); export const GAPS = PARK.gaps; export const LETTERS = PARK.letters; export const SPAWN = PARK.spawn; export const heightAt = makeHeightAt(PARK); export const normalAt = makeNormalAt(heightAt); export function buildLevel(scene) { const { ground, letterMeshes } = buildParkScene(scene, PARK); return { ground, letterMeshes }; } `; } export function downloadLevelJS(park) { download('level.' + slug(park) + '.js', generateLevelJS(park), 'text/javascript'); }