Everything Forge Studio is built on, what your browser needs to run it, and how to serve it locally.
Forge Studio is a zero-build, zero-framework web application. Every page is plain HTML, CSS and vanilla JavaScript running directly in the browser — there is no bundler, no package manager, no server-side code, and nothing is ever uploaded. The only third-party dependency is ffmpeg.wasm, vendored locally for offline MP4 export.
| Page | Role | Source |
|---|---|---|
| index.html | Dashboard / launcher | — |
| photo.html | GraphicForge — layer-based image & graphics editor | js/photo.js |
| video.html | MotionForge — multi-track video editor | js/video.js |
| word.html | DocForge — word processor (.docx / .doc) | js/word.js |
| sheet.html | SheetForge — spreadsheet (.xlsx / .xls / .csv) | js/sheet.js |
| pdf.html | PDFForge — PDF viewer & editor | js/pdf.js |
| docs.html | This page | — |
| Technology | Used for | Documentation |
|---|---|---|
| HTML5 / CSS3 | UI shell, panels, timeline layout. Custom properties for theming, Flexbox & Grid for layout — no CSS framework. | MDN: CSS |
| JavaScript (ES2020+) | All application logic, in two self-contained scripts. No framework, no transpiler, no build step. | MDN: JavaScript |
| Canvas 2D API | All rendering in both editors: layer compositing, blend modes, filters, gradients, text-on-path, video preview compositing, thumbnails. | MDN: CanvasRenderingContext2D |
| Pointer Events | Tools, transform handles, clip dragging, trimming, scrubbing — one input model for mouse, trackpad and pen. | MDN: Pointer events |
| Drag & Drop + File APIs | Dropping images onto the photo canvas, media import, bin-to-timeline drags, layer reordering; FileReader / Blob / object URLs for all file I/O. |
MDN: File API |
| HTMLMediaElement | Frame-accurate seek & playback of video/audio clips on the timeline. | MDN: HTMLMediaElement |
| MediaRecorder API | Recording the timeline to WebM on export, and webcam capture. | MDN: MediaRecorder |
| canvas.captureStream() | Turning the preview canvas into a live video stream for the export recorder. | MDN: captureStream |
| Web Audio API | Mixing every clip's audio into one stream during export (MediaElementAudioSourceNode → MediaStreamAudioDestinationNode). |
MDN: Web Audio API |
| getUserMedia / MediaDevices | Webcam & microphone capture, with device enumeration for the camera/mic pickers. | MDN: getUserMedia |
| ffmpeg.wasm 0.12 | WebM → MP4 transcode (H.264 + AAC) entirely in the browser via WebAssembly, running in a Web Worker. Vendored in vendor/ffmpeg/ (@ffmpeg/ffmpeg 0.12.10 + @ffmpeg/core 0.12.6, ~32 MB) so it works offline. |
ffmpeg.wasm docs · GitHub |
| mammoth.js 1.8 | Reading .docx files into clean HTML for DocForge. Vendored in vendor/mammoth/. |
mammoth.js on GitHub |
| JSZip 3.10 | Packaging DocForge's generated WordprocessingML into real .docx files (a .docx is a zip). Vendored in vendor/jszip/. |
JSZip docs |
| SheetJS (xlsx) 0.20 | Reading .xlsx / legacy .xls / .csv and writing .xlsx for SheetForge. Vendored in vendor/xlsx/. Formulas are evaluated by SheetForge's own engine. |
SheetJS docs |
| pdf.js 3.11 | Rendering PDF pages in PDFForge (Mozilla's PDF engine, runs in a worker). Vendored in vendor/pdfjs/. |
pdf.js docs |
| pdf-lib 1.17 | Writing PDFs: baking annotations into pages as vector content, page rotate/reorder/delete, merging documents, creating blank PDFs. Vendored in vendor/pdflib/. |
pdf-lib docs |
| Web Workers / WebAssembly | ffmpeg runs off the main thread in a module worker so the UI stays responsive during conversion. | MDN: Web Workers · MDN: WebAssembly |
MediaRecorder and canvas capture support differ, so video export and webcam recording may not work there.
Chrome / Edge: full support Safari: editors onlyfile://: webcam access requires a
secure context
(localhost counts), and the ffmpeg worker can't load from file:// URLs. Any static file server works.sw.js) precaches the entire suite —
including the 32 MB ffmpeg core — on your first visit, so pages keep loading even if the local server is
stopped. In Chrome/Edge you can also install Forge Studio from the address-bar install icon to get a
standalone app window with its own dock icon.
MDN: Progressive Web AppsEasiest: double-click Forge Studio.command in the project folder — it starts the
server and opens the app. Or run any static server yourself:
# Python (preinstalled on macOS)
python3 -m http.server 8642
# then open http://localhost:8642
# or Node, if you prefer
npx serve .
Editors/
├── index.html ← dashboard
├── photo.html ← GraphicForge (image & graphics editor)
├── video.html ← MotionForge (video editor)
├── docs.html ← this page
├── css/
│ ├── base.css ← shared theme & controls
│ ├── photo.css
│ └── video.css
├── js/
│ ├── photo.js ← photo editor engine (layers, tools, history, I/O)
│ └── video.js ← video editor engine (timeline, playback, export, webcam)
└── vendor/ffmpeg/ ← ffmpeg.wasm (offline MP4 export)
Professional sample files ship in samples/ — every one was generated by the
editors' own engines, so they double as proof that the file formats are genuine:
| File | Shows off | Try it |
|---|---|---|
| business-proposal.docx | DocForge: headings, lists, a table, colors and an embedded chart image | Open in DocForge |
| letter-template.docx + customers.csv | Mail merge: «Name» «Company» «Amount» placeholders plus a matching CSV — open the letter, hit ⧉ Mail Merge, load the CSV | Open in DocForge · CSV |
| sales-report.xlsx | SheetForge: two sheets, live SUM/AVERAGE/MAX/COUNTIF formulas and a VLOOKUP demo — select data and hit 📊 Chart | Open in SheetForge |
| company-report.pdf | PDFForge: branded two-page report with a chart and a fillable sign-off form (text fields + checkbox) | Open in PDFForge |
| demo-project.forge.json | GraphicForge: five-layer project — photo, gradient title, curved text, star badge (use Open… in GraphicForge) | Download project |
| demo-clip.webm + demo-audio.wav | MotionForge: 6-second motion-graphics clip (made by the suite itself) and a synth soundtrack — use ✨ Load demo media in the bin | Open MotionForge |
| barcode & photo samples | Five generated test images for background removal, clone stamp and filters — GraphicForge's Samples… menu | Open GraphicForge |
Every editor is a plain HTML page, so embedding one is just an <iframe> —
no SDK, no build step, no API key, no account. The full reference lives in
EMBEDDING.md (served beside the editors); this is the working summary.
| Editor | Page | embed | view | src | postMessage | Loads | Exports |
|---|---|---|---|---|---|---|---|
| DocForge | word.html | ✅ | ✅ | ✅ | ✅ | docx, html, txt | docx |
| SheetForge | sheet.html | ✅ | ✅ | ✅ | ✅ | xlsx, csv | xlsx |
| PDFForge | pdf.html | ✅ | ✅ | ✅ | ✅ | pdf | pdf |
| GraphicForge | photo.html | ✅ | ✅ | — | — | no file API — users drag files in | |
| MotionForge | video.html | ✅ | ✅ | — | — | no file API — users drag files in | |
?embed=1 — hides the app bar so the editor looks like part of your page. Use it on every embed.?view=1 — read-only: toolbars, panels and side bars hidden, editing disabled. Combine with embed for a pure viewer.?src=URL — auto-opens a file on load. Must be same-origin with the editor or CORS-accessible.?savepath=URL — DocForge: persist to your server (GET on load, POST on save). Use an absolute URL when your server is a different origin than the editor.?encrypt=1 — with savepath: seal the bytes client-side (AES-256-GCM) before upload; your server stores ciphertext it cannot read.<!-- read-only spreadsheet viewer -->
<iframe src="https://editors.jnc360.tech/sheet.html?embed=1&view=1&src=reports/q4.xlsx"
style="width:100%;height:420px;border:0"></iframe>
<!-- editable document editor -->
<iframe id="doc" src="https://editors.jnc360.tech/word.html?embed=1"
style="width:100%;height:560px;border:0"></iframe>
An iframe defaults to 150px tall and there is no content-based auto-resize, so
always set an explicit height — ~400–600px for viewers, ≥520px for editing.
view=1 hides the editing UI; it is not access control, since the file is still
in the visitor's browser.
DocForge, SheetForge and PDFForge speak a small
postMessage
protocol; DocForge adds a save/open family that activates when a savepath is
configured.
| Direction | Message | Payload |
|---|---|---|
| host → editor | forge:load | { format, data, name? } — base64 for docx/xlsx/pdf, plain string for html/txt/csv |
| host → editor | forge:export | — |
| editor → host | forge:ready | sent once at startup, only inside an iframe |
| editor → host | forge:loaded | a load finished successfully |
| editor → host | forge:document | { format, name, data } — data is always base64 |
| editor → host | forge:error | { message } — a load, export or open threw |
| host → editor | forge:save / forge:saveas | save to the savepath now / under a new name (DocForge) |
| host → editor | forge:config | { savepath?, encrypt?, passphrase? } — change save settings; the only safe way to hand over a passphrase |
| host → editor | forge:list / forge:open | { prefix? } list stored files / { path } open one |
| editor → host | forge:saved | { ok, status?, error? } — after every savepath save |
| editor → host | forge:files | { files: [{path, size, at}] } — reply to forge:list |
// Attach the listener BEFORE the iframe loads, or you miss forge:ready.
window.addEventListener('message', e => {
if (e.origin !== 'https://editors.jnc360.tech') return; // always check
const d = e.data;
if (!d || typeof d.type !== 'string') return;
if (d.type === 'forge:ready') frame.postMessage(
{ type: 'forge:load', format: 'html', data: '<h1>Hi</h1>' }, ORIGIN);
if (d.type === 'forge:document') save(d.name, fromBase64(d.data));
if (d.type === 'forge:error') console.error(d.message);
});
// ask for the edited file back
frame.postMessage({ type: 'forge:export' }, ORIGIN);
// base64 → bytes
const fromBase64 = b64 => Uint8Array.from(atob(b64), c => c.charCodeAt(0));
forge:ready fires once, as soon as the
editor's scripts run — attach your listener before inserting the iframe. You don't strictly
need it though: the editor listens for the life of the page, so a forge:load sent
on the iframe's own load event always works too.
Hand the embed savepath=https://your-site.com/api/docs/<userId>/report.docx
and it round-trips against your backend: GET on load (404 = start blank),
POST of raw application/octet-stream bytes on save, and a
GET of the directory (trailing slash, Accept: application/json)
returning [{path, size, at}] for the Open picker. Identity lives in the path — your
server authenticates the session cookie and authorizes it. Requests are sent with
credentials: 'include', so your CORS must echo the exact editor origin
(a wildcard * is rejected) plus Access-Control-Allow-Credentials: true,
and answer the OPTIONS preflight. New documents prompt for a name and warn before
overwriting; encrypt=1 stores FORGEENC1 ciphertext instead of a
.docx (only for zero-knowledge storage — plain server-side encryption at rest
needs nothing from the editor). Apps that authenticate with Bearer tokens instead of cookies
bridge auth with short-lived capability tickets in the savepath, delivered via
forge:config after load so they never touch a URL. Embed the prod
origin (editors.jnc360.tech) in production and point staging at
editors-qa.jnc360.tech to test upcoming versions.
EMBEDDING.md ("The host endpoint contract") and demo-site/store-server.py.
See it running cross-origin at
embed-demo.jnc360.tech.
The editor accepts forge:* messages from any origin and replies with a
'*' target origin — deliberately, to keep embedding trivial. That puts the burden on
your page: check e.origin in your listener, and prefer passing the
editor's real origin instead of '*' when you send. Don't put secrets in
?src — URLs reach history, referrers and logs. When self-hosting, make sure the
server sends no X-Frame-Options header (or frame-ancestors directive)
that blocks framing.
api/convert/… path first, then falls back to a
hard-coded https://editors.jnc360.tech/api/convert/…. So a self-hosted copy
without its own converter uploads that document to jnc360.tech. Nothing else does
this — every other editor, format and export is genuinely local. Run your own converter at
api/convert/pdf2docx + docx2pdf, block the fallback with CSP, or
simply avoid PDF import/export in DocForge. See EMBEDDING.md.
Forge Studio's own code has no external runtime dependencies.
ffmpeg.wasm is free software:
the JavaScript wrapper is MIT-licensed, and the bundled FFmpeg core is
LGPL/GPL (it includes the GPL x264 encoder).
Using it as shipped is free for any use; if you redistribute this app commercially, keep the ffmpeg core as a
separate, replaceable file (as it is now in vendor/ffmpeg/) and preserve its license notices.