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: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
.slidelessignorefile at the folder root (gitignore syntax).
.slidelessignore:
Relative paths — the rules
Paths in HTML and CSS are resolved relative to the file they appear in, against the deck root:| In source | Resolves to |
|---|---|
./hero.jpg from index.html | hero.jpg |
./hero.jpg from sub/page.html | sub/hero.jpg |
../common.css from sub/page.html | common.css |
/assets/logo.png (root-absolute) | assets/logo.png |
https://cdn.example.com/x.js | unchanged (external) |
data:image/png;base64,... | unchanged (inline) |
<img src="../../outside/foo.jpg"> will fail the static scan — your deck must be self-contained within its root.
CDN dependencies (external URLs)
Externalhttps:// 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, andconnect-src. Anything weirder (a WebSocket to a non-TLS host, aws://stream, etc.) will be blocked by CSP.
.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.
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 rightContent-Type header on every response. A few overrides make sure non-obvious formats work:
.glb→model/gltf-binary.gltf→model/gltf+json.usdz→model/vnd.usdz+zip.glsl/.wgsl→text/plain; charset=utf-8(shader source).hdr→image/vnd.radiance(HDR environment maps)
application/octet-stream, file an issue — the override table is small and easy to extend.
Video and big files: Range requests
Every asset response advertisesAccept-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: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: