Skip to main content

The mental model

A Slideless deck folder IS a tiny static website. The entry HTML lives at the root, assets live wherever you want, and the viewer serves them like any static server would. Relative paths in your HTML just work — ./hero.jpg, ../styles.css, /assets/logo.png all resolve the way you’d expect. Upload the folder with slideless push ./deck --title "...", and Slideless replicates the structure on its end.

Folder layouts

No forced convention — all of these are valid:
The entry file is index.html by default. Override with --entry:

What gets uploaded

Every file under the folder, recursively, except:
  • Built-in ignores: .git/, node_modules/, .DS_Store, Thumbs.db, .vercel/, .next/, *.log.
  • Anything excluded by a .slidelessignore file at the folder root (gitignore syntax).
Example .slidelessignore:

Relative paths — the rules

Paths in HTML and CSS are resolved relative to the file they appear in, against the deck root: Parent-directory escapes are a hard error at upload time. <img src="../../outside/foo.jpg"> will fail the static scan — your deck must be self-contained within its root.

CDN dependencies (external URLs)

External https:// URLs pass through unchanged and don’t count against your storage quota. three.js from unpkg.com, Google Fonts, your company logo hosted on a public CDN — all fine. Just be aware:
  • If the CDN goes down, your deck breaks.
  • The viewer’s sandbox + CSP allow https: for images, scripts, styles, fonts, and connect-src. Anything weirder (a WebSocket to a non-TLS host, a ws:// stream, etc.) will be blocked by CSP.
For typography specifically — when to pick CDN fonts vs. bundling .woff2 files inside the deck — see Custom fonts.

Static scan (pre-upload warnings)

Before uploading, the CLI scans your HTML + CSS for common reference patterns (src=, href=, CSS url(...), @import) and classifies each:
  • External (http://, https://, //, data:, blob:) — ignored, always fine.
  • Parent escape (../) — hard error.
  • Relative (resolved, file exists) — silent, good.
  • Relative (resolved, file doesn’t exist in the uploaded set)warning by default, error with --strict.
Warnings don’t block the upload because runtime-built URLs (img.src = './images/' + id + '.jpg') always escape static detection. The scan catches typos and forgotten files, not every possible broken reference. Test your uploaded URL in a browser. Example CLI output:

Content-Type detection

MIME types are detected from file extensions and shipped in the manifest, so the browser gets the right Content-Type header on every response. A few overrides make sure non-obvious formats work:
  • .glbmodel/gltf-binary
  • .gltfmodel/gltf+json
  • .usdzmodel/vnd.usdz+zip
  • .glsl / .wgsltext/plain; charset=utf-8 (shader source)
  • .hdrimage/vnd.radiance (HDR environment maps)
If you hit a format that’s served as application/octet-stream, file an issue — the override table is small and easy to extend.

Video and big files: Range requests

Every asset response advertises Accept-Ranges: bytes. <video> tags seek correctly because the browser can request byte ranges. The viewer caps each Range response at 50 MB per request to prevent memory spikes, and <video src="./demo.mp4"> just works.

Dedup on update

Because assets are content-addressed (stored by SHA-256 of their contents), an update that changes only one image re-uploads only that image. The CLI output makes this visible:
On the backend, storage grows by the size of the ONE new blob plus the tiny new manifest — not by 194 MB.

Size limits

Plan-dependent. See Presentations → Size and file-count caps for the full table.

Example: three.js glTF viewer

index.html:
three/scene.js:
Upload:
That’s it. The glTF loads from your Slideless CDN, three.js loads from unpkg.