# Horizonweft 1.0 — a little room for a wider view

Original responsive **jQuery panorama viewer**, a local configuration studio, a complete JSON project format and self-contained HTML export. Uses flat image scrolling with optional horizontal edge repetition; it is not a WebGL sphere or virtual tour. The included LICENSE.txt grants approved use and customization of original Nishvault files in the purchaser's own personal and commercial projects and sharing finished outputs. The separate free fixed demo has its own approved terms.

## Open the studio

Unzip the developer package. Serve its root on a local web server for reliable chooser/download/clipboard support, then open `app/index.html`. For example: `python3 -m http.server 8797 --bind 127.0.0.1` from the extracted directory. This is a normal product instruction; it does not authorize bypassing any existing browser/security restriction. No npm build, remote service, login or paid API is needed by the customer files. Original Quiet Ridge3000×800 illustration is a fictional sample, not a photograph/location.

Drag across the panorama or use Left/Right. At zoom, Up/Down can pan vertically; plus/minus zoom and Home resets. The controls have names and visible focus. Wheel zoom is off by default so normal page scrolling remains available. Turning it on captures wheel events only over the stage. At narrow sizes controls wrap below the image. Single-pointer browser handling is coded, but physical touch/multitouch/other engines are not claimed tested until actual evidence exists; no pinch feature is promised.

## Configure, back up and export

1. Choose a PNG/JPEG/WebP you have rights to use. Maximum12MB decoded,8192×4096,24MP and aspect ratio2–8. Corrupt/unsupported files and mismatched declared pixel sizes are rejected. SVG uploads are not accepted. Horizontal wrap is appropriate only if image edges meet; turn it off for ordinary nonseamless panoramas.
2. Replacement image/project is validated and decoded first. Use the inline **Replace working project** / **Keep current project** decision; opening never silently discards your applied panorama. No native JavaScript confirm dialog is used. A pending replacement is not applied until that button is pressed.
3. Edit title, start heading, zoom1–4, vertical0–1, stage height200–700px, wrap and wheel. **Apply settings** commits form fields. Drag/zoom changes the live view; **Use current view as start** captures it into the project. Save/export always use the applied project, not unapplied form fields or uncaptured live navigation.
4. **Save project** downloads the entire JSON including embedded raster pixels, dimensions and applied options. **Open project** uses a chooser and the same replacement decision. Automatic browser draft is best effort and may exceed browser quota for large images; the status explicitly says when portable JSON is needed. No promise of crash recovery if storage fails; download a backup. No native beforeunload confirmation is installed.
5. **Export HTML** downloads one file with the exact tested jQuery, model, plugin, CSS and embedded image. Open that actual file in a supported browser. It does not fetch jQuery/images/fonts/telemetry. The native jQuery viewer remains working inside it.
6. **Copy integration snippet** copies actual applied options and embedded image plus relative script/CSS paths. Keep those runtime files at the documented paths. For several viewers assign unique element IDs. `samples/integration.html` demonstrates two independent instances, scoped events, destroy and reinitialize. `samples/quiet-ridge.html` is a generated functional export example, not evidence that a real browser download was tested.

## Native jQuery API

Load `vendor/jquery-3.7.1.min.js`, `app/core.js`, `app/horizonweft.js`, and `app/viewer.css`. Initialize:

```js
jQuery('#my-panorama').horizonweft({
  image: 'assets/quiet-ridge.png', title: 'Quiet Ridge',
  height: 400, yaw: 0, zoom: 1, vertical: 0.5, wrap: true, wheel: false
});
const state = jQuery('#my-panorama').horizonweft('state');
jQuery('#my-panorama').horizonweft('setView', {yaw: 90, zoom: 2});
jQuery('#my-panorama').on('horizonweft:change', (event, state) => console.log(state.yaw));
jQuery('#my-panorama').horizonweft('destroy');
```

Image can be a safe relative/local HTTP/HTTPS image or PNG/JPEG/WebP data URL. The browser's normal image/local-file rules still apply. Explicit unsupported URL schemes and embedded HTTP credentials are rejected. The portable project format accepts embedded raster data only. Direct developer URLs may cause the browser to request that image; the studio/project/export adds no remote fetch. Natural decoded dimensions are checked before displaying.

`state` returns applied title/wrap/height/wheel, current numeric yaw/zoom/vertical, ready and natural image sizes. `setView` accepts yaw/zoom/vertical only; clamps zoom/vertical, wraps heading in loop mode and bounds0–360 in nonloop mode. In loop mode heading is an illustrative angular scroll position, not geographic bearing. Nonloop0/360 represent left/right bounds. `horizonweft:ready`, `horizonweft:change` and `horizonweft:error` fire on the original element; they are local events and are not sent anywhere. Live change events can be frequent; throttle your own handlers if needed.

Call destroy before reinitializing. Destroy detaches pointer/key/wheel/resize handlers, disconnects its observer, clears instance data and restores cloned original child DOM. It does not preserve handlers attached to the original fallback children; attach application handlers outside that fallback or rebind after destruction. Existing application listeners on the host remain. CSS classes are namespaced hw-; no global selectors are added by viewer.css. Existing controls in a host are replaced while the plugin is mounted. Never mount it around unrelated application controls.

## Privacy, licensing and verification

Original plugin/studio/core/CSS and sample artwork are Nishvault work. jQuery3.7.1 is bundled under its separate MIT notice in vendor/jquery-LICENSE.txt. jsdom and Python/Pillow are development tools, excluded from customer runtime. No competitor code, paid assets or branding is included.

The fixed free preview at preview/index.html has its own minimal viewer runtime and exact approved LICENSE.txt. It does not contain the configurable jQuery API, upload studio or project/export engine. The paid developer package supplies those files and integration documentation. The selected price is USD19 one-time, tax external; applicable taxes are calculated at checkout. Do not resell or redistribute the source/editor/plugin package as a standalone product or template, or claim authorship of original files. Setup/use documentation is included; no hosted service, guaranteed personal support, SLA, update schedule or business-result guarantee is provided. Third-party jQuery MIT and the fixed free preview retain their separate rights. License approval does not activate provider checkout, payment, public release, revenue or traffic outcomes. Current independent QC and the authorized publication workflow still precede distribution.

The canonical customer ZIP excludes the historical purchaser-terms proposal and internal QA records. Technical unit/jsdom tests are distinct from real interaction proof. Documentation/license-only changes retain prior execution evidence only when the existing QA engine verifies behavior and artifact hashes; retained evidence is not a new test. Consult evidence/qa/report.json: all required real CUA startup, drag/keyboard/resize/lifecycle, invalid preservation, actual JSON chooser/whole-field roundtrip, actual downloaded HTML/copied integration and desktop/mobile review must pass before completion. Synthetic DOM pointer events are explicitly unit tests, never browser receipts. On17October2026, actual controls in this product’s own supported IAB test tab demonstrated access recovery. Current per-case results and their limits are in evidence/qa/report.json. No physical-device, pinch or alternate-engine claim.

## Development

`npm ci --ignore-scripts`; `node --test --test-reporter=tap tests/core.test.cjs tests/plugin.test.cjs tests/metrics.test.cjs`; `node scripts/build.cjs`; `python3 scripts/package.py`. Run build before QA when core/plugin/CSS/scene changes to bind exact export strings and sample files. Build is not run during tests. Original art generator `scripts/create_scene.py` uses Pillow; the bundled workspace Python path was used locally. Customer files are already built. HOSTED_HANDOFF.md scopes optional first-party hosted measurement separately from offline output.

### Integration without clipboard access
**Copy integration snippet** always reveals the exact current image/options in a labeled read-only code field. If clipboard permission is unavailable, select and copy that field manually. **Download integration HTML** saves that same integration as a real HTML file; place it in the extracted package root beside `app/` and `vendor/` before opening. This file uses the supplied relative jQuery/core/plugin/CSS files; **Export HTML** remains the fully self-contained option. Unapplied edits are excluded from both.

### Ordered production entry and optional rebuilding
The studio loads one generated `app/studio-runtime.js` in dependency order. Individual editable sources remain supplied for jQuery integration and customization. After changing the core/plugin/studio/sample sources, run the included `node scripts/build.cjs` from the extracted package root to refresh the studio entry and export strings. This optional command uses only Node built-ins; no npm install is needed to run or rebuild the customer ZIP. The npm/QA/test/packaging commands above apply to the full development checkout, whose test dependencies and QA evidence are intentionally outside the customer ZIP.
