# Browser-local Panorama Tour Maker

Original Free4AllHub authoring tool. Static HTML / CSS / native ES modules.
No dependency installation, build framework, API, backend, image upload, external
image URLs, storage, autosave, accounts, geolocation, or analytics.

## Integration and qualification boundary

Verification scope (2026-11-07): original code tests passed. In cloud Chromium,
the three-scene demo was edited (title, starting scene, notes and connection
yaw), project JSON was downloaded and reopened in a fresh tab, a scene title was
revised, and a second JSON plus self-contained HTML were downloaded. The HTML
was served as an independent HTTP document and its three images, notes, four
directed transitions and named navigation were checked without external assets.
Project reopen re-rasterizes JPEGs, so byte equality is not expected and repeated
reopening can alter image quality and color. Keep original source images.

The test browser lacked WebGL. 360-degree rendering, visual hotspot navigation
and current-view aiming remain UNQUALIFIED. Capture buttons stay disabled until
a viewer has successfully loaded and disable again on errors or scene loading.
Physical-phone, screen-reader, real-beginner and color-fidelity checks remain
unqualified. A real 128 × 64 PNG with fictional text metadata was imported as a 128 × 64
JPEG; the metadata marker was absent from the decoded exported JPEG,
and rejection of malformed JSON left the next JSON download byte-identical. The initial `export-assets.mjs` was deliberately empty. It is now built from
the exact reviewed repaired distribution; do not
replace it with an unaudited release. Actual browser verification and final release acceptance are separate from
the original-code test suite.

Required qualified inputs:
- `vendor/pannellum.js` (Pannellum 2.5.7, with reviewed changes)
- `vendor/pannellum.css`
- `vendor/COPYING`
- `vendor/NOTICE.txt` and any additional third-party license texts

Both viewer configurations force `escapeHTML: true`, `ignoreGPanoXMP: true`,
`autoRotate: false`, `dynamic: false`, `crossOrigin: anonymous`, and
`orientationOnByDefault: false`; orientation controls are hidden, but that
is not a substitute for the independently reviewed hard sensor-disable patch.
The code never imports Pannellum configuration from a project. CSP allows
`connect-src data:` only because Pannellum loads embedded JPEG bytes through
XHR; it permits no HTTP(S), WebSocket, or other network destination.

After reviewing those inputs, run:

    python build.py --qualified-vendor
    node --test tests/*.test.mjs

The full repaired distribution is retained under `vendor/repaired/`, including
source, SVG assets, provenance, all license files and its reproduction recipe.
Top-level vendor files are byte-identical aliases for integration.

This generates `export-assets.mjs` with embedded viewer source/styles/notices and
`downloads/panorama-tour-source.zip`, a complete source-and-license package.
The script only reads vendor files as text; it does not execute vendor code.
Link the authoring folder from the Hub. The paths to the Hub homepage and
creative tools currently assume the tool lives at `/tools/tours/`.

## Authored components

- `core.mjs`: strict format/version/shape/text/ID/edge/data-URL/header validation,
  bounds, HTML escaping, owned viewer config, and async operation gate.
- `editor.mjs`: browser decode + dimension verification and JPEG rasterization;
  atomic project replacement; scene/connection CRUD; position capture; explicit
  download saves. New edits or operations cancel older pending replacements. Named-scene selection
  creates a fresh viewer, avoiding ignored Pannellum loadScene calls while busy;
  generation guards reject callbacks from destroyed instances.
- `demos.mjs`: three original, fictional orientation diagrams drawn on canvas,
  not photographs and not real locations.
- `exporter.mjs`: self-contained HTML with embedded viewer assets, one copy of
  every image, escaped metadata, and a complete static scene/image/text tour.
- `generate-fixtures.py`: deterministic original image fixtures. PNG generation
  is standard-library-only. Optional installed Pillow regenerates the JPEG;
  the checked-in JPEG means tests do not require Pillow or any installation.
- `tests/core.test.mjs`: original code tests; no upstream execution. Export tests
  inject inert placeholder strings rather than importing or executing vendor.

## Native project schema, version 1

Top-level exact keys: `format`, `version`, `title`, `startScene`, `scenes`,
`connections`. Format is `free4allhub-panorama-tour` and version is `1`.

A scene has exactly `id`, `title`, `description`, `image`, `width`, `height`.
Project image data is only canonical JPEG base64. Uploaded PNG/JPEG bytes are
checked, browser-decoded, and drawn to a new opaque-white canvas before being
stored as JPEG. Project reopen also decodes and re-rasterizes every image.
Header validation alone is intentionally not evidence that an image decodes:
`editor.mjs` requires successful real browser decode before committing a load.
The original metadata and exact original bytes are never exported. This does
not remove sensitive content visible in the pixels. Color and quality can
change, so users must retain their originals.

A connection is a single two-way edge with exact fields `id`, `a`, `b`, `aYaw`,
`aPitch`, `bYaw`, `bPitch`. Its two endpoint hotspots always target each other.
One unordered pair per connection; no orphan/self edges. Angle bounds are
±180° yaw and ±90° pitch. Start scene must exist.

Up to 4 scenes, 8 MiB each original/saved image, 24 MiB combined images, exact
2:1 ratio and up to 16 million decoded pixels. Project files are bounded at
34 MiB. Unknown fields/versions, external URLs, SVG, noncanonical base64, unsafe
IDs, wrong dimensions, corrupt decodes and stale async completions are rejected
without replacing current work. All user text is literal DOM text or escaped
HTML/JSON. No user-controlled callbacks or imported upstream settings.

## Remaining qualification checklist

This is a limited early release of local authoring plus image/text tour output.
The checked subset is described above. It is not a qualified 360-degree viewing
or visual hotspot-aiming release. The usable image/text fallback does not prove
WebGL, device compatibility, screen-reader usability, or comprehensive security.

These checks remain before making broader claims:

1. Render all three scenes on a WebGL-capable browser. Follow both directions of
   each visual hotspot, aim and capture both connection endpoints, and verify
   arrow-key/zoom behavior and focus. Repeat scene changes and viewer destruction.
2. Test physical-phone and narrow-screen layouts, screen readers, and ordinary
   beginner workflows. The original-code focus tests use DOM stubs, not users.
3. Expand real-browser image import beyond the checked 128 × 64 PNG: JPEG input,
   corrupt and mismatched bytes, all resource limits, memory pressure, transparency
   and color/quality behavior. Decoding and rejection have original-code tests,
   but those tests do not establish every browser/device boundary.
4. Exercise interrupted browser imports, new/demo/cancel/newer-import races and
   failures repeatedly. The original adapter suite covers them with controlled
   promises and decode stubs; actual browser timing still needs qualification.
5. Test the standalone HTML's interactive viewer under independent static HTTP(S)
   with WebGL available. The real downloaded HTML's image/text content, three
   images, notes, four directed routes and named navigation were checked without
   external assets. This is not a broad network/security certification.
6. Extend hostile-text, malformed-image and download/reopen checks across browser
   families. Literal text/schema/atomicity checks currently combine focused
   original-code tests with the specifically recorded browser checks above.

The source ZIP rebuilds byte-for-byte with the included Python packaging script.
It excludes hidden hosting/tooling metadata. No `file://` portability, physical
phone, universal offline, color-fidelity or blanket security claim is made.
