/ Documentation ← Back to dashboard

Tech Stack & Requirements

Everything Forge Studio is built on, what your browser needs to run it, and how to serve it locally.

Overview

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.

PageRoleSource
index.htmlDashboard / launcher
photo.htmlGraphicForge — layer-based image & graphics editorjs/photo.js
video.htmlMotionForge — multi-track video editorjs/video.js
word.htmlDocForge — word processor (.docx / .doc)js/word.js
sheet.htmlSheetForge — spreadsheet (.xlsx / .xls / .csv)js/sheet.js
pdf.htmlPDFForge — PDF viewer & editorjs/pdf.js
docs.htmlThis page

Core technologies

TechnologyUsed forDocumentation
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 (MediaElementAudioSourceNodeMediaStreamAudioDestinationNode). 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

Requirements

Running locally

Easiest: 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 .

Project layout

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)

Demo documents

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:

FileShows offTry 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

Embedding & read-only mode

Live example: open embed-demo.html — a mock "company intranet" page with three working embeds (read-only spreadsheet, host-controlled document editor with the postMessage API, and a PDF viewer). View its source to copy the patterns.

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.

What each editor supports

EditorPageembedviewsrcpostMessageLoadsExports
DocForgeword.htmldocx, html, txtdocx
SheetForgesheet.htmlxlsx, csvxlsx
PDFForgepdf.htmlpdfpdf
GraphicForgephoto.htmlno file API — users drag files in
MotionForgevideo.htmlno file API — users drag files in

URL parameters

<!-- 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.

The postMessage API

DocForge, SheetForge and PDFForge speak a small postMessage protocol; DocForge adds a save/open family that activates when a savepath is configured.

DirectionMessagePayload
host → editorforge:load{ format, data, name? } — base64 for docx/xlsx/pdf, plain string for html/txt/csv
host → editorforge:export
editor → hostforge:readysent once at startup, only inside an iframe
editor → hostforge:loadeda load finished successfully
editor → hostforge:document{ format, name, data }data is always base64
editor → hostforge:error{ message } — a load, export or open threw
host → editorforge:save / forge:saveassave to the savepath now / under a new name (DocForge)
host → editorforge:config{ savepath?, encrypt?, passphrase? } — change save settings; the only safe way to hand over a passphrase
host → editorforge:list / forge:open{ prefix? } list stored files / { path } open one
editor → hostforge:saved{ ok, status?, error? } — after every savepath save
editor → hostforge: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));
Don't miss the handshake. 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.

Saving to your server (DocForge)

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.

Full contract & reference implementation: the endpoint table, curl verification checklist and a ~150-line dependency-free Python store live in EMBEDDING.md ("The host endpoint contract") and demo-site/store-server.py. See it running cross-origin at embed-demo.jnc360.tech.

Security

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.

One feature is not client-side. DocForge's PDF ↔ DOCX conversion calls a server: it tries a relative 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.

Licensing notes

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.

Good to know: video export runs in real time (a 30-second timeline takes ~30 seconds to record, plus roughly the same again for MP4 conversion), and the tab should stay visible during the recording pass — browsers throttle hidden tabs.