ax-check.com
by Gauge
G
FileDeck
getfiledeck.com
ax-check.com
Checked 4 November 2026
B
65/100
Agents haven’t verified setup
Docs and pricing are clear, but agents disagree on what FileDeck costs
Quickstart, install steps, and API/MCP/CLI docs all check out. Yet sessions split: one read Free/Pro/AI tiers, two assumed a free MIT-licensed library instead.
3 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no getfiledeck.com account. Recorded 4 November 2026 on Pi.
3 recorded · 1m 29s
1m 29s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
Let me check the repository to understand what FileDeck is.
ls -la /sandbox/repo && cat /sandbox/repo/README* 2>/dev/null | head -100
total 16
total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Nov 4 20:13 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore
Recorded tool call
{
"type": "tool_call",
"seq": 3,
"ts": "2026-11-04T20:13:17.891Z",
"name": "bash",
"turn": 1,
"input": {
"command": "ls -la /sandbox/repo && cat /sandbox/repo/README* 2>/dev/null | head -100"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-9c9eba0b9cbb857e",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 5,
"ts": "2026-11-04T20:13:17.891Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Nov 4 20:13 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-9c9eba0b9cbb857e",
"is_error": false,
"tool_kind": "shell"
}find /sandbox/repo -maxdepth 2 -type f | head -50
/sandbox/repo/.gitignore
/sandbox/repo/.gitignore /sandbox/repo/.git/COMMIT_EDITMSG /sandbox/repo/.git/config /sandbox/repo/.git/index /sandbox/repo/.git/HEAD /sandbox/repo/.git/description
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-11-04T20:13:17.891Z",
"name": "bash",
"turn": 1,
"input": {
"command": "find /sandbox/repo -maxdepth 2 -type f | head -50"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b5a2ce8b80184a6e",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 6,
"ts": "2026-11-04T20:13:17.891Z",
"turn": 1,
"output": {
"content": [
{
"text": "/sandbox/repo/.gitignore\n/sandbox/repo/.git/COMMIT_EDITMSG\n/sandbox/repo/.git/config\n/sandbox/repo/.git/index\n/sandbox/repo/.git/HEAD\n/sandbox/repo/.git/description\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b5a2ce8b80184a6e",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The repository is empty except for .gitignore. There's no FileDeck documentation locally. Let me check git log and .gitignore for any hints.
cat /sandbox/repo/.gitignore; git -C /sandbox/repo log --oneline -5
# Keep generated and vendored files out of source diffs.
# Keep generated and vendored files out of source diffs. # Logs / OS cruft *.log .DS_Store # JavaScript / TypeScript node_modules/ .next/ dist/ build/ coverage/ # Python .venv/ venv/ __pycache__/ *.py[cod] .pytest_cache/ *.egg-info/ # Rust target/ # Go / PHP / Ruby vendored deps vendor/ .bundle/ # Java / Kotlin / Scala .gradle/ # .NET bin/ obj/ # Elixir _build/ deps/ # Dart / Flutter .dart_tool/ 9ebf5fb Initial workspace
Recorded tool call
{
"type": "tool_call",
"seq": 9,
"ts": "2026-11-04T20:13:21.075Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cat /sandbox/repo/.gitignore; git -C /sandbox/repo log --oneline -5"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8efc7db2cf6e75da",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 10,
"ts": "2026-11-04T20:13:21.075Z",
"turn": 1,
"output": {
"content": [
{
"text": "# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n9ebf5fb Initial workspace\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8efc7db2cf6e75da",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The repository is essentially empty. "FileDeck" — no local docs. Is FileDeck an actual product? Probably fictional. Let me check whether there are SDK/API docs available online… probably no network access. Let me check whether pip/npm has a "filedeck" package, and whether the network is available.
pip index versions filedeck 2>&1 | head -3; npm view filedeck 2>&1 | head -5
ERROR: No matching distribution found for filedeck
ERROR: No matching distribution found for filedeck [notice] A new release of pip is available: 26.2.1 -> 26.3 filedeck@0.3.1 | MIT | deps: none | versions: 4 A dependency-free attachment viewer for images, PDFs, video, audio, Excel and Word files. Driven by DOM attributes, no build step required. https://vivekgami.github.io/filedeck/
Recorded tool call
{
"type": "tool_call",
"seq": 13,
"ts": "2026-11-04T20:13:24.314Z",
"name": "bash",
"turn": 1,
"input": {
"command": "pip index versions filedeck 2>&1 | head -3; npm view filedeck 2>&1 | head -5",
"timeout": 30
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a8fbb6afed0131c4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 18,
"ts": "2026-11-04T20:13:36.566Z",
"turn": 1,
"output": {
"content": [
{
"text": "ERROR: No matching distribution found for filedeck\n\n[notice] A new release of pip is available: 26.2.1 -> 26.3\n\nfiledeck@0.3.1 | MIT | deps: none | versions: 4\nA dependency-free attachment viewer for images, PDFs, video, audio, Excel and Word files. Driven by DOM attributes, no build step required.\nhttps://vivekgami.github.io/filedeck/\n\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a8fbb6afed0131c4",
"is_error": false,
"tool_kind": "shell"
}curl -s -m 8 -o /dev/null -w "%{http_code}" https://pypi.org/simple/ ; echo; curl -s -m 8 -o /dev/null -w "%{http_code}" https://filedeck.io 2>&1; echo200
200 000
Recorded tool call
{
"type": "tool_call",
"seq": 14,
"ts": "2026-11-04T20:13:24.314Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 8 -o /dev/null -w \"%{http_code}\" https://pypi.org/simple/ ; echo; curl -s -m 8 -o /dev/null -w \"%{http_code}\" https://filedeck.io 2>&1; echo",
"timeout": 30
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-95e83e629d06482b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 17,
"ts": "2026-11-04T20:13:29.422Z",
"turn": 1,
"output": {
"content": [
{
"text": "200\n000\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-95e83e629d06482b",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Interesting — there's an npm package called filedeck 0.3.1: "a dependency-free attachment viewer." But the user said "use the hosted product via SDK or API" and is asking "how the pricing works." The npm package is a frontend library, not a hosted product. Hmm. Maybe the intended "FileDeck" is a hosted file-sharing product. Let me fetch the npm package details and the GitHub pages site to verify.
There is an npm package named filedeck. Let me inspect it to see what FileDeck actually is.
npm view filedeck --json 2>&1 | head -60
{
{
"_id": "filedeck@0.3.1",
"_rev": "4-0e6d4d3ace3316ddd4ca3307b5deaa4a",
"name": "filedeck",
"dist-tags": {
"latest": "0.3.1"
},
"versions": [
"0.1.0",
"0.2.0",
"0.3.0",
"0.3.1"
],
"time": {
"created": "2026-09-16T09:08:31.733Z",
"modified": "2026-10-23T12:17:45.240Z",
"0.1.0": "2026-09-16T09:08:31.968Z",
"0.2.0": "2026-10-04T09:15:38.693Z",
"0.3.0": "2026-10-23T10:30:21.501Z",
"0.3.1": "2026-10-23T12:17:45.034Z"
},
"bugs": {
"url": "https://github.com/Vivekgami/filedeck/issues"
},
"author": "Vivek V Gami",
"license": "MIT",
"homepage": "https://vivekgami.github.io/filedeck/",
"keywords": [
"lightbox",
"attachment",
"viewer",
"gallery",
"pdf",
"file-preview",
"vanilla-js",
"no-dependencies",
"xlsx",
"docx",
"excel",
"word",
"spreadsheet-viewer",
"document-viewer",
"image-viewer",
"pdf-viewer",
"pinch-zoom",
"modal"
],
"repository": {
"url": "git+https://github.com/Vivekgami/filedeck.git",
"type": "git"
},
"description": "A dependency-free attachment viewer for images, PDFs, video, audio, Excel and Word files. Driven by DOM attributes, no build step required.",
"maintainers": [
"vivekgami <vivekgami.dev@gmail.com>"
],
"readmeFilename": "README.md",
"_contentLength": 33306,
"dist": {
"shasum": "ea81fecf67c3d492c9308be96b13cf31b81ab154",
"tarball": "https://registry.npmjs.org/filedeck/-/filedeck-0.3.1.tgz",
Recorded tool call
{
"type": "tool_call",
"seq": 22,
"ts": "2026-11-04T20:13:38.782Z",
"name": "bash",
"turn": 1,
"input": {
"command": "npm view filedeck --json 2>&1 | head -60",
"timeout": 30
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a6a5713a7c5c37e9",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 26,
"ts": "2026-11-04T20:13:45.109Z",
"turn": 1,
"output": {
"content": [
{
"text": "{\n \"_id\": \"filedeck@0.3.1\",\n \"_rev\": \"4-0e6d4d3ace3316ddd4ca3307b5deaa4a\",\n \"name\": \"filedeck\",\n \"dist-tags\": {\n \"latest\": \"0.3.1\"\n },\n \"versions\": [\n \"0.1.0\",\n \"0.2.0\",\n \"0.3.0\",\n \"0.3.1\"\n ],\n \"time\": {\n \"created\": \"2026-09-16T09:08:31.733Z\",\n \"modified\": \"2026-10-23T12:17:45.240Z\",\n \"0.1.0\": \"2026-09-16T09:08:31.968Z\",\n \"0.2.0\": \"2026-10-04T09:15:38.693Z\",\n \"0.3.0\": \"2026-10-23T10:30:21.501Z\",\n \"0.3.1\": \"2026-10-23T12:17:45.034Z\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/Vivekgami/filedeck/issues\"\n },\n \"author\": \"Vivek V Gami\",\n \"license\": \"MIT\",\n \"homepage\": \"https://vivekgami.github.io/filedeck/\",\n \"keywords\": [\n \"lightbox\",\n \"attachment\",\n \"viewer\",\n \"gallery\",\n \"pdf\",\n \"file-preview\",\n \"vanilla-js\",\n \"no-dependencies\",\n \"xlsx\",\n \"docx\",\n \"excel\",\n \"word\",\n \"spreadsheet-viewer\",\n \"document-viewer\",\n \"image-viewer\",\n \"pdf-viewer\",\n \"pinch-zoom\",\n \"modal\"\n ],\n \"repository\": {\n \"url\": \"git+https://github.com/Vivekgami/filedeck.git\",\n \"type\": \"git\"\n },\n \"description\": \"A dependency-free attachment viewer for images, PDFs, video, audio, Excel and Word files. Driven by DOM attributes, no build step required.\",\n \"maintainers\": [\n \"vivekgami <vivekgami.dev@gmail.com>\"\n ],\n \"readmeFilename\": \"README.md\",\n \"_contentLength\": 33306,\n \"dist\": {\n \"shasum\": \"ea81fecf67c3d492c9308be96b13cf31b81ab154\",\n \"tarball\": \"https://registry.npmjs.org/filedeck/-/filedeck-0.3.1.tgz\",\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a6a5713a7c5c37e9",
"is_error": false,
"tool_kind": "shell"
}curl -s -m 15 https://vivekgami.github.io/filedeck/ | head -150
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Filedeck — JavaScript lightbox for PDFs, Excel and Word files</title>
<meta name="description" content="Filedeck is a zero-dependency JavaScript lightbox for images, PDFs, video, audio, Excel and Word files. Add one attribute to the links you already have.">
<meta name="theme-color" content="#F1F1F0">
<link rel="canonical" href="https://vivekgami.github.io/filedeck/">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%23101014'/%3E%3Ctext x='32' y='42' font-family='ui-monospace,Consolas,monospace' font-size='28' font-weight='500' fill='%23fff' text-anchor='middle'%3Efd%3C/text%3E%3C/svg%3E">
<!-- Link previews: WhatsApp, Slack, LinkedIn, X, Discord. -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="Filedeck">
<meta property="og:url" content="https://vivekgami.github.io/filedeck/">
<meta property="og:title" content="Filedeck — your links open files now">
<meta property="og:description" content="A zero-dependency lightbox that took the rest of the office with it: photos, PDFs, video, audio, and the Excel and Word files nobody else previews.">
<meta property="og:image" content="https://vivekgami.github.io/filedeck/landing/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Filedeck — zero-dependency JavaScript lightbox for images, PDFs, video, audio, Excel and Word files">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareSourceCode",
"name": "Filedeck",
"description": "A zero-dependency JavaScript lightbox for images, PDFs, video, audio, Excel and Word files.",
"url": "https://vivekgami.github.io/filedeck/",
"codeRepository": "https://github.com/Vivekgami/filedeck",
"programmingLanguage": "JavaScript",
"runtimePlatform": "Web browser",
"license": "https://opensource.org/licenses/MIT",
"author": { "@type": "Person", "name": "Vivek V Gami" }
}
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<!-- The library itself. src/ rather than dist/, because dist/ is gitignored and
never reaches GitHub Pages; src/filedeck.js assigns window.Filedeck. -->
<link rel="stylesheet" href="src/filedeck.css">
<style>
/* ==========================================================================
Filedeck landing page — page chrome only, none of it is the library.
Tokens, copy and metrics come from designs/design_handoff_filedeck_landing.
========================================================================== */
:root {
color-scheme: light;
--ground: #F1F1F0;
--surface: #FFFFFF;
--ink: #101014;
--ink-muted: #56565F;
--ink-muted-2: #63636D;
--ink-muted-3: #6E6E78;
--ink-faint: #8A8A93;
--ink-faintest: #A9A9B2;
--hairline: #F0F0F2;
--hairline-2: #F4F4F6;
--border: #E6E6EA;
--face: #F3F4F8;
--accent: #4B4BE8;
--accent-tint: #D9DEFF;
--dark: #14141A;
--dark-2: #1A1A22;
--dark-border: #2E2E38;
--dark-border-2: #33333D;
--dark-hover: #22222A;
--dark-hover-2: #26262E;
--code: #E7E7EC;
--code-blue: #9DB4FF;
--code-green: #A9DFA0;
--code-grey: #74747F;
--code-foot: #A0A0AC;
--green: #1E7A4C;
--red: #D2443A;
--amber: #C77A12;
--violet: #8B5CF6;
--indigo: #3C4BB8;
--orange: #E0721B;
--blue: #2563C9;
--teal: #0E8C8C;
--ease: cubic-bezier(.22, .61, .36, 1);
--sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;
--mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
padding: clamp(6px, 1vw, 14px);
background: var(--ground);
color: var(--ink);
font: 400 16px/1.6 var(--sans);
-webkit-font-smoothing: antialiased;
}
a { color: var(--ink); }
a:hover { color: var(--accent); }
::selection { background: var(--accent-tint); color: var(--ink); }
@keyframes fdl-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
@keyframes fdl-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes fdl-menu { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
/* -- reveal on scroll ---------------------------------------------------- */
[data-reveal] { transition: opacity 640ms var(--ease), transform 640ms var(--ease); }
[data-reveal].is-armed { opacity: 0; transform: translateY(18px); }
[data-reveal].is-armed.is-in { opacity: 1; transform: none; }
/* -- shells -------------------------------------------------------------- */
.fdl-panel {
position: relative;
background: var(--surface);
border-radius: clamp(18px, 2.4vw, 30px);
box-shadow: 0 1px 2px rgba(16,16,20,.05), 0 24px 60px rgba(16,16,20,.06);
overflow: hidden;
animation: fdl-up 620ms var(--ease) both;
}
/* -- nav ----------------------------------------------------------------- */
.fdl-nav {
position: relative;
z-index: 3;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px 20px;
padding: clamp(10px, 1.4vw, 14px) clamp(12px, 2vw, 20px);
border-bottom: 1px solid var(--hairline);
}
.fdl-logo {
display: flex;
align-items: center;
gap: 9px;
text-decoration: none;
color: var(--ink);
font: 700 19px/1 var(--sans);
letter-spacing: -0.03em;
Recorded tool call
{
"type": "tool_call",
"seq": 23,
"ts": "2026-11-04T20:13:38.782Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 15 https://vivekgami.github.io/filedeck/ | head -150",
"timeout": 30
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-bbd69776a98dea54",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 25,
"ts": "2026-11-04T20:13:45.109Z",
"turn": 1,
"output": {
"content": [
{
"text": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Filedeck — JavaScript lightbox for PDFs, Excel and Word files</title>\n<meta name=\"description\" content=\"Filedeck is a zero-dependency JavaScript lightbox for images, PDFs, video, audio, Excel and Word files. Add one attribute to the links you already have.\">\n<meta name=\"theme-color\" content=\"#F1F1F0\">\n<link rel=\"canonical\" href=\"https://vivekgami.github.io/filedeck/\">\n<link rel=\"icon\" href=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%23101014'/%3E%3Ctext x='32' y='42' font-family='ui-monospace,Consolas,monospace' font-size='28' font-weight='500' fill='%23fff' text-anchor='middle'%3Efd%3C/text%3E%3C/svg%3E\">\n\n<!-- Link previews: WhatsApp, Slack, LinkedIn, X, Discord. -->\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:site_name\" content=\"Filedeck\">\n<meta property=\"og:url\" content=\"https://vivekgami.github.io/filedeck/\">\n<meta property=\"og:title\" content=\"Filedeck — your links open files now\">\n<meta property=\"og:description\" content=\"A zero-dependency lightbox that took the rest of the office with it: photos, PDFs, video, audio, and the Excel and Word files nobody else previews.\">\n<meta property=\"og:image\" content=\"https://vivekgami.github.io/filedeck/landing/og-image.png\">\n<meta property=\"og:image:width\" content=\"1200\">\n<meta property=\"og:image:height\" content=\"630\">\n<meta property=\"og:image:alt\" content=\"Filedeck — zero-dependency JavaScript lightbox for images, PDFs, video, audio, Excel and Word files\">\n<meta name=\"twitter:card\" content=\"summary_large_image\">\n\n<script type=\"application/ld+json\">\n{\n \"@context\": \"https://schema.org\",\n \"@type\": \"SoftwareSourceCode\",\n \"name\": \"Filedeck\",\n \"description\": \"A zero-dependency JavaScript lightbox for images, PDFs, video, audio, Excel and Word files.\",\n \"url\": \"https://vivekgami.github.io/filedeck/\",\n \"codeRepository\": \"https://github.com/Vivekgami/filedeck\",\n \"programmingLanguage\": \"JavaScript\",\n \"runtimePlatform\": \"Web browser\",\n \"license\": \"https://opensource.org/licenses/MIT\",\n \"author\": { \"@type\": \"Person\", \"name\": \"Vivek V Gami\" }\n}\n</script>\n\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n\n<!-- The library itself. src/ rather than dist/, because dist/ is gitignored and\n never reaches GitHub Pages; src/filedeck.js assigns window.Filedeck. -->\n<link rel=\"stylesheet\" href=\"src/filedeck.css\">\n\n<style>\n/* ==========================================================================\n Filedeck landing page — page chrome only, none of it is the library.\n Tokens, copy and metrics come from designs/design_handoff_filedeck_landing.\n ========================================================================== */\n\n:root {\n color-scheme: light;\n\n --ground: #F1F1F0;\n --surface: #FFFFFF;\n --ink: #101014;\n --ink-muted: #56565F;\n --ink-muted-2: #63636D;\n --ink-muted-3: #6E6E78;\n --ink-faint: #8A8A93;\n --ink-faintest: #A9A9B2;\n --hairline: #F0F0F2;\n --hairline-2: #F4F4F6;\n --border: #E6E6EA;\n --face: #F3F4F8;\n --accent: #4B4BE8;\n --accent-tint: #D9DEFF;\n\n --dark: #14141A;\n --dark-2: #1A1A22;\n --dark-border: #2E2E38;\n --dark-border-2: #33333D;\n --dark-hover: #22222A;\n --dark-hover-2: #26262E;\n\n --code: #E7E7EC;\n --code-blue: #9DB4FF;\n --code-green: #A9DFA0;\n --code-grey: #74747F;\n --code-foot: #A0A0AC;\n\n --green: #1E7A4C;\n --red: #D2443A;\n --amber: #C77A12;\n --violet: #8B5CF6;\n --indigo: #3C4BB8;\n --orange: #E0721B;\n --blue: #2563C9;\n --teal: #0E8C8C;\n\n --ease: cubic-bezier(.22, .61, .36, 1);\n --sans: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif;\n --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\n\nbody {\n margin: 0;\n min-height: 100vh;\n padding: clamp(6px, 1vw, 14px);\n background: var(--ground);\n color: var(--ink);\n font: 400 16px/1.6 var(--sans);\n -webkit-font-smoothing: antialiased;\n}\na { color: var(--ink); }\na:hover { color: var(--accent); }\n::selection { background: var(--accent-tint); color: var(--ink); }\n\n@keyframes fdl-up { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }\n@keyframes fdl-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }\n@keyframes fdl-menu { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }\n\n/* -- reveal on scroll ---------------------------------------------------- */\n[data-reveal] { transition: opacity 640ms var(--ease), transform 640ms var(--ease); }\n[data-reveal].is-armed { opacity: 0; transform: translateY(18px); }\n[data-reveal].is-armed.is-in { opacity: 1; transform: none; }\n\n/* -- shells -------------------------------------------------------------- */\n.fdl-panel {\n position: relative;\n background: var(--surface);\n border-radius: clamp(18px, 2.4vw, 30px);\n box-shadow: 0 1px 2px rgba(16,16,20,.05), 0 24px 60px rgba(16,16,20,.06);\n overflow: hidden;\n animation: fdl-up 620ms var(--ease) both;\n}\n\n/* -- nav ----------------------------------------------------------------- */\n.fdl-nav {\n position: relative;\n z-index: 3;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px 20px;\n padding: clamp(10px, 1.4vw, 14px) clamp(12px, 2vw, 20px);\n border-bottom: 1px solid var(--hairline);\n}\n.fdl-logo {\n display: flex;\n align-items: center;\n gap: 9px;\n text-decoration: none;\n color: var(--ink);\n font: 700 19px/1 var(--sans);\n letter-spacing: -0.03em;\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-bbd69776a98dea54",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
FileDeck is a zero-dependency open-source (MIT) JavaScript lightbox library for displaying attachments. It's not a hosted product — there's no API, no pricing, and probably no account either. The user's request is "use the hosted product via SDK or API" and "tell me how pricing works." But this is an MIT-licensed client-side library that's free and has no hosted service.
The user's guardrail: "If this product doesn't have an actionable developer workflow, say so clearly and stop." But a developer workflow does exist: install via npm, use via DOM attributes. It's not a hosted product/SDK/API — it's a library you embed. You could also build a simple sample HTML page that uses the library from a CDN (jsdelivr/unpkg) — that's lightweight and doesn't require a local service stack either. Does this count as "using the hosted product via SDK or API"? Not strictly, but the honest answer is: FileDeck is a free MIT-licensed browser library, there's no hosted product, no pricing, no API keys.
I think the right way to proceed is this: clearly state that FileDeck is not a hosted SaaS with pricing — it's a free MIT library — and that the closest thing to a "developer workflow" is embedding the script. I can build a simple sample (a static HTML page that uses filedeck via CDN). That's lightweight, requires no local service. That's useful and seems within the constraints. Building a static sample doesn't require starting a server (it can be verified with node, or just by creating the files).
Let me check the README to confirm usage (data attributes) and whether there's any mention of pricing or a hosted service. Fetching the README from npm.
FileDeck turns out to be an open-source npm library, not a hosted SaaS. Let me confirm by reading its README before deciding.
curl -s -m 15 https://registry.npmjs.org/filedeck/-/filedeck-0.3.1.tgz -o /tmp/filedeck.tgz && mkdir -p /tmp/filedeck && tar -xzf /tmp/filedeck.tgz -C /tmp/filedeck && ls /tmp/filedeck/package && head -120 /tmp/filedeck/package/README.md
LICENSE
LICENSE
README.md
dist
package.json
# Filedeck
A dependency-free attachment viewer for the web: images, PDFs, video, audio and
documents, in one overlay. No dependencies, no build step, no server payload —
it reads everything from the DOM it is already looking at.
Add one attribute to the links you already have, and they become a viewer.
**Live demo:** [vivekgami.github.io/filedeck](https://vivekgami.github.io/filedeck/) — click any file on the page.
---
## Install
**CDN — no build step, no npm:**
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.min.css">
<script src="https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.umd.min.js"></script>
<script>window.filedeck = new Filedeck();</script>
```
**Self-hosted** — copy `dist/filedeck.umd.min.js` and `dist/filedeck.min.css`
into your static folder and link them the same way. In a Django project:
```django
{% load static %}
<link rel="stylesheet" href="{% static 'css/filedeck.min.css' %}">
<script src="{% static 'js/filedeck.umd.min.js' %}"></script>
<script>window.filedeck = new Filedeck();</script>
```
**npm, with a bundler:**
```bash
npm install filedeck
```
```js
import Filedeck from 'filedeck';
import 'filedeck/filedeck.css';
const viewer = new Filedeck();
```
Filedeck builds its own markup and appends it to `<body>` on construction.
There is nothing to paste into your template — no container element, no icon
sprite, no font to load.
---
## Markup
Any element carrying `data-filedeck` becomes a trigger. The attribute's **value
is the group name** — triggers sharing a value page through each other with the
prev/next arrows and the filmstrip.
```html
<a href="/media/full/hero.png"
data-filedeck="files"
data-filedeck-width="1440"
data-filedeck-height="900"
data-filedeck-size="2.4 MB"
data-filedeck-meta="Design homepage hero · Aug 24, 2026">
<img src="/media/thumbs/hero.png" alt="Homepage hero v4">
</a>
```
Use an `<a href>` rather than a `<div>`. With JS broken or disabled the link
still opens the file, and Ctrl/Cmd-click still opens it in a new tab — the
viewer deliberately ignores modified clicks.
### Attributes
| Attribute | Required | Purpose |
|---|---|---|
| `href` | yes* | Full-size asset URL. `data-filedeck-src` works as an alternative. |
| `data-filedeck` | yes | Group name. Triggers with the same value navigate together. |
| `data-filedeck-name` | no | Filename shown in the top bar. Falls back to the trigger's `title`, then the nested `<img>` alt, then the URL's basename. |
| `title` | no | Standard HTML attribute, reused as the filename when `data-filedeck-name` is absent. Also gives you a native tooltip on the tile. |
| `data-filedeck-caption` | no | Caption under the image. Falls back to the nested `<img>` alt. |
| `data-filedeck-meta` | no | Subtitle line in the top bar — task, date, whatever fits. |
| `data-filedeck-size` | no | Shown in the caption and on the document card. Format it yourself ("2.4 MB"). |
| `data-filedeck-width` / `data-filedeck-height` | no | Intrinsic pixel dimensions. Prevents a reflow on first paint — see below. |
| `data-filedeck-thumb` | no | Filmstrip thumbnail URL. Use it when the trigger has no nested `<img>` and the full-size file is too heavy to reuse. |
| `data-filedeck-download` | no | URL for the Download button when it differs from `href` — a converted preview on the stage, the original file to download. |
| `data-filedeck-kind` | no | Overrides the kind detected from the extension. One of `image`, `pdf`, `video`, `audio`, `file`. |
\* Required unless `data-filedeck-src` is present.
### On `data-filedeck-width` / `data-filedeck-height`
The stage fits the image using CSS `aspect-ratio`, which needs the ratio
*before* the image loads. Supply these and the layout is correct from the first
frame. Omit them and the viewer guesses 3:2, then corrects from
`naturalWidth`/`naturalHeight` once the image arrives — visible as a small
reflow. If your model stores dimensions, pass them; if not, leave them off
rather than guessing.
### Nested thumbnails
If the trigger contains an `<img>`, its `src` is reused for the filmstrip
thumbnail and its `alt` supplies the default caption. So a typical file tile
needs almost no extra attributes.
Thumbnails resolve in this order:
```
data-filedeck-thumb → nested <img> src → the href, for images only → file-type icon
```
The href is skipped for non-images on purpose — pointing an `<img src>` at a
PDF or a spreadsheet only produces a broken image. Those get a labelled
file-type icon instead: a page shape with a coloured type badge (PDF red,
spreadsheet green, document indigo, CAD olive, archive grey), falling back to
the file's own extension for anything unrecognised.
These are deliberately not vendor logos. The Adobe and Microsoft marks are
trademarked, and shipping them inside a library is a licensing problem rather
than a design one.
Recorded tool call
{
"type": "tool_call",
"seq": 30,
"ts": "2026-11-04T20:13:47.913Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 15 https://registry.npmjs.org/filedeck/-/filedeck-0.3.1.tgz -o /tmp/filedeck.tgz && mkdir -p /tmp/filedeck && tar -xzf /tmp/filedeck.tgz -C /tmp/filedeck && ls /tmp/filedeck/package && head -120 /tmp/filedeck/package/README.md",
"timeout": 30
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a818b6247b75aa14",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 32,
"ts": "2026-11-04T20:13:48.723Z",
"turn": 1,
"output": {
"content": [
{
"text": "LICENSE\nREADME.md\ndist\npackage.json\n# Filedeck\n\nA dependency-free attachment viewer for the web: images, PDFs, video, audio and\ndocuments, in one overlay. No dependencies, no build step, no server payload —\nit reads everything from the DOM it is already looking at.\n\nAdd one attribute to the links you already have, and they become a viewer.\n\n**Live demo:** [vivekgami.github.io/filedeck](https://vivekgami.github.io/filedeck/) — click any file on the page.\n\n---\n\n## Install\n\n**CDN — no build step, no npm:**\n\n```html\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.min.css\">\n<script src=\"https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.umd.min.js\"></script>\n<script>window.filedeck = new Filedeck();</script>\n```\n\n**Self-hosted** — copy `dist/filedeck.umd.min.js` and `dist/filedeck.min.css`\ninto your static folder and link them the same way. In a Django project:\n\n```django\n{% load static %}\n<link rel=\"stylesheet\" href=\"{% static 'css/filedeck.min.css' %}\">\n<script src=\"{% static 'js/filedeck.umd.min.js' %}\"></script>\n<script>window.filedeck = new Filedeck();</script>\n```\n\n**npm, with a bundler:**\n\n```bash\nnpm install filedeck\n```\n\n```js\nimport Filedeck from 'filedeck';\nimport 'filedeck/filedeck.css';\n\nconst viewer = new Filedeck();\n```\n\nFiledeck builds its own markup and appends it to `<body>` on construction.\nThere is nothing to paste into your template — no container element, no icon\nsprite, no font to load.\n\n---\n\n## Markup\n\nAny element carrying `data-filedeck` becomes a trigger. The attribute's **value\nis the group name** — triggers sharing a value page through each other with the\nprev/next arrows and the filmstrip.\n\n```html\n<a href=\"/media/full/hero.png\"\n data-filedeck=\"files\"\n data-filedeck-width=\"1440\"\n data-filedeck-height=\"900\"\n data-filedeck-size=\"2.4 MB\"\n data-filedeck-meta=\"Design homepage hero · Aug 24, 2026\">\n <img src=\"/media/thumbs/hero.png\" alt=\"Homepage hero v4\">\n</a>\n```\n\nUse an `<a href>` rather than a `<div>`. With JS broken or disabled the link\nstill opens the file, and Ctrl/Cmd-click still opens it in a new tab — the\nviewer deliberately ignores modified clicks.\n\n### Attributes\n\n| Attribute | Required | Purpose |\n|---|---|---|\n| `href` | yes* | Full-size asset URL. `data-filedeck-src` works as an alternative. |\n| `data-filedeck` | yes | Group name. Triggers with the same value navigate together. |\n| `data-filedeck-name` | no | Filename shown in the top bar. Falls back to the trigger's `title`, then the nested `<img>` alt, then the URL's basename. |\n| `title` | no | Standard HTML attribute, reused as the filename when `data-filedeck-name` is absent. Also gives you a native tooltip on the tile. |\n| `data-filedeck-caption` | no | Caption under the image. Falls back to the nested `<img>` alt. |\n| `data-filedeck-meta` | no | Subtitle line in the top bar — task, date, whatever fits. |\n| `data-filedeck-size` | no | Shown in the caption and on the document card. Format it yourself (\"2.4 MB\"). |\n| `data-filedeck-width` / `data-filedeck-height` | no | Intrinsic pixel dimensions. Prevents a reflow on first paint — see below. |\n| `data-filedeck-thumb` | no | Filmstrip thumbnail URL. Use it when the trigger has no nested `<img>` and the full-size file is too heavy to reuse. |\n| `data-filedeck-download` | no | URL for the Download button when it differs from `href` — a converted preview on the stage, the original file to download. |\n| `data-filedeck-kind` | no | Overrides the kind detected from the extension. One of `image`, `pdf`, `video`, `audio`, `file`. |\n\n\\* Required unless `data-filedeck-src` is present.\n\n### On `data-filedeck-width` / `data-filedeck-height`\n\nThe stage fits the image using CSS `aspect-ratio`, which needs the ratio\n*before* the image loads. Supply these and the layout is correct from the first\nframe. Omit them and the viewer guesses 3:2, then corrects from\n`naturalWidth`/`naturalHeight` once the image arrives — visible as a small\nreflow. If your model stores dimensions, pass them; if not, leave them off\nrather than guessing.\n\n### Nested thumbnails\n\nIf the trigger contains an `<img>`, its `src` is reused for the filmstrip\nthumbnail and its `alt` supplies the default caption. So a typical file tile\nneeds almost no extra attributes.\n\nThumbnails resolve in this order:\n\n```\ndata-filedeck-thumb → nested <img> src → the href, for images only → file-type icon\n```\n\nThe href is skipped for non-images on purpose — pointing an `<img src>` at a\nPDF or a spreadsheet only produces a broken image. Those get a labelled\nfile-type icon instead: a page shape with a coloured type badge (PDF red,\nspreadsheet green, document indigo, CAD olive, archive grey), falling back to\nthe file's own extension for anything unrecognised.\n\nThese are deliberately not vendor logos. The Adobe and Microsoft marks are\ntrademarked, and shipping them inside a library is a licensing problem rather\nthan a design one.\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a818b6247b75aa14",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
FileDeck is a free, MIT-licensed, client-side JS library — no hosted product, no API, no pricing, no account required. The premise of the user's question is that it's a hosted product with an SDK/API and pricing setup. Honest answer: there's no pricing (free MIT), and no hosted API either. But an executable developer workflow does exist: embed the library via CDN/npm and create a simple sample page. This is lightweight, no server stack needed, no long-running commands.
The instructions say: "If this product doesn't have an actionable developer workflow, say so frankly and stop." An actionable workflow does exist — building samples using the npm package/CDN. "Use the hosted product via SDK or API" — the closest analog is the npm package (SDK-like). Build a simple sample: a small static HTML page using CDN + probably an npm-installed variant. Keep it lightweight: create a sample directory with an index.html using jsdelivr CDN and a README. No server needed. Can I validate the JS with node? This library is UMD; I could do a simple smoke test with node + jsdom, but that adds dependencies. Stick with the approach of creating static samples and validating HTML references.
Should I skim through the README further for events/options to make the sample better? There's enough info. Skim through the options/events section to include small JS API usage examples.
sed -n 120,260p /tmp/filedeck/package/README.md
than a design one.
than a design one.
The name resolves in this order, first non-empty value wins:
```
data-filedeck-name → title → nested <img> alt → basename of the URL
```
Which means a tile that already carries a `title` for its tooltip needs nothing
added at all:
```html
<a href="/media/full/hero.png" title="homepage-hero-v4.png" data-filedeck="files">
<img src="/media/thumbs/hero.png" alt="Homepage hero v4">
</a>
```
Here the top bar shows `homepage-hero-v4.png` and the caption shows
`Homepage hero v4` — the filename and the description staying distinct, which is
usually what you want.
---
## Attachment types
The kind is worked out from the file extension, so tiles don't need tagging.
`data-filedeck-kind` overrides it — necessary when the URL carries no extension, as with
a permission-checked download view like `/attachments/12/download/`.
| Kind | Extensions | Rendered as |
|---|---|---|
| `image` | jpg, jpeg, png, gif, webp, avif, svg, bmp, ico | `<img>`, zoomable and pannable |
| `pdf` | pdf | `<embed>` using the browser's own PDF viewer |
| `video` | mp4, webm, ogv, mov, m4v | `<video controls>` |
| `audio` | mp3, wav, ogg, oga, m4a, aac, flac | `<audio controls>` on a card |
| `sheet` | xlsx, xlsm | A scrollable grid with sheet tabs, read in the browser |
| `doc` | docx, docm | A formatted reading column, read in the browser |
| `file` | anything else | Card with file type, size, and a download button |
Zoom and pan apply to images only; the zoom cluster hides itself for every
other kind rather than sitting there inert. Preloading is likewise
image-only — pulling the next 40MB video in the background would be hostile.
**PDFs on narrow screens fall back to the file card.** Embedded PDFs are
unreliable below about 900px on Safari and Android, often rendering only the
first page or nothing. The alternative was shipping PDF.js and its megabyte,
which didn't seem worth it for one breakpoint.
**Office documents and CAD files get the card.** No browser renders `.docx`,
`.xlsx`, `.dwg`, or `.step`, and the Google and Office Online iframe viewers
only work on publicly reachable URLs — wrong for access-controlled files. If
you later want real previews, generate them server-side at upload (LibreOffice
headless to PDF, then a first-page raster), store the result, and point the
tile at the preview while `Download` serves the original. The viewer needs no
changes for that: a generated preview is just an image.
**Playback stops when you navigate away or close.** Hiding the viewer doesn't
stop an `<audio>` element, so both paths pause explicitly. A focused video also
keeps the arrow keys for seeking rather than surrendering them to gallery
navigation.
---
## Options
All optional.
```js
new Filedeck({
selector: '[data-filedeck]', // what counts as a trigger
groupAttr: 'data-filedeck', // which attribute holds the group name
filmstrip: true, // false hides the bottom strip entirely
actions: ['copy', 'download'], // top-bar buttons, in this order
zoomMin: 25,
zoomMax: 400,
zoomStep: 25,
gestures: true, // false disables the pointer layer
labels: {}, // see below
renderers: {} // custom previews, see below
});
```
**`selector`** — narrow the trigger set if you need to, e.g.
`'.file-tile[data-filedeck]'`.
**`groupAttr`** — the attribute read for grouping. Worth changing only if
`data-filedeck` collides with something else on your pages.
**`filmstrip`** — the strip hides itself when a group has fewer than two items,
so `false` is only for suppressing it entirely.
**`actions`** — which top-bar buttons to render. Pass `[]` for close only.
Close is always present. Actions the viewer doesn't implement (delete, say)
belong on your page via the events below.
**`gestures`** — `false` removes swipe, pinch, wheel and double-click zoom
entirely, including their listeners. An object tunes the thresholds instead:
```js
new Filedeck({
gestures: {
axisLock: 12, // px before the swipe axis commits
swipeRatio: 0.22, // fraction of stage width that counts as a swipe
flickVelocity: 0.45, // px/ms — a fast flick navigates at any distance
dismissDistance: 130, // px of vertical drag before closing
edgeResistance: 0.32 // drag damping at the first and last item
}
});
```
Any key you omit keeps its default.
---
## Spreadsheets
`.xlsx` and `.xlsm` render natively, with no dependency. An Office file is a
zip archive of XML, and browsers can already open both — `DecompressionStream`
for the archive, `DOMParser` for the XML — so Filedeck reads the workbook
directly.
What you get: a scrollable grid with column letters and row numbers, sticky
headers, tabs for multi-sheet workbooks, shared strings, dates converted from
their serial numbers, and booleans.
What you don't: formatting, colours, charts, images, merged cells, or formula
text. Cells show their last calculated value, which is what Excel stores
alongside the formula. Row and column ceilings are 2,000 and 64; past those the
grid is cut off with a note saying so.
It's a preview for checking a figure before deciding to open the file, not a
spreadsheet viewer. Anything larger than 20 MB shows the file card instead, and
so does a corrupt archive, a failed request, or a browser without
`DecompressionStream` — each with its own message rather than a blank stage.
Old binary `.xls` files are not zips and are not supported; they get the card.
---
Recorded tool call
{
"type": "tool_call",
"seq": 35,
"ts": "2026-11-04T20:13:50.922Z",
"name": "bash",
"turn": 1,
"input": {
"command": "sed -n 120,260p /tmp/filedeck/package/README.md"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b95a2ea65c72bf88",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 36,
"ts": "2026-11-04T20:13:50.922Z",
"turn": 1,
"output": {
"content": [
{
"text": "than a design one.\n\nThe name resolves in this order, first non-empty value wins:\n\n```\ndata-filedeck-name → title → nested <img> alt → basename of the URL\n```\n\nWhich means a tile that already carries a `title` for its tooltip needs nothing\nadded at all:\n\n```html\n<a href=\"/media/full/hero.png\" title=\"homepage-hero-v4.png\" data-filedeck=\"files\">\n <img src=\"/media/thumbs/hero.png\" alt=\"Homepage hero v4\">\n</a>\n```\n\nHere the top bar shows `homepage-hero-v4.png` and the caption shows\n`Homepage hero v4` — the filename and the description staying distinct, which is\nusually what you want.\n\n---\n\n## Attachment types\n\nThe kind is worked out from the file extension, so tiles don't need tagging.\n`data-filedeck-kind` overrides it — necessary when the URL carries no extension, as with\na permission-checked download view like `/attachments/12/download/`.\n\n| Kind | Extensions | Rendered as |\n|---|---|---|\n| `image` | jpg, jpeg, png, gif, webp, avif, svg, bmp, ico | `<img>`, zoomable and pannable |\n| `pdf` | pdf | `<embed>` using the browser's own PDF viewer |\n| `video` | mp4, webm, ogv, mov, m4v | `<video controls>` |\n| `audio` | mp3, wav, ogg, oga, m4a, aac, flac | `<audio controls>` on a card |\n| `sheet` | xlsx, xlsm | A scrollable grid with sheet tabs, read in the browser |\n| `doc` | docx, docm | A formatted reading column, read in the browser |\n| `file` | anything else | Card with file type, size, and a download button |\n\nZoom and pan apply to images only; the zoom cluster hides itself for every\nother kind rather than sitting there inert. Preloading is likewise\nimage-only — pulling the next 40MB video in the background would be hostile.\n\n**PDFs on narrow screens fall back to the file card.** Embedded PDFs are\nunreliable below about 900px on Safari and Android, often rendering only the\nfirst page or nothing. The alternative was shipping PDF.js and its megabyte,\nwhich didn't seem worth it for one breakpoint.\n\n**Office documents and CAD files get the card.** No browser renders `.docx`,\n`.xlsx`, `.dwg`, or `.step`, and the Google and Office Online iframe viewers\nonly work on publicly reachable URLs — wrong for access-controlled files. If\nyou later want real previews, generate them server-side at upload (LibreOffice\nheadless to PDF, then a first-page raster), store the result, and point the\ntile at the preview while `Download` serves the original. The viewer needs no\nchanges for that: a generated preview is just an image.\n\n**Playback stops when you navigate away or close.** Hiding the viewer doesn't\nstop an `<audio>` element, so both paths pause explicitly. A focused video also\nkeeps the arrow keys for seeking rather than surrendering them to gallery\nnavigation.\n\n---\n\n## Options\n\nAll optional.\n\n```js\nnew Filedeck({\n selector: '[data-filedeck]', // what counts as a trigger\n groupAttr: 'data-filedeck', // which attribute holds the group name\n filmstrip: true, // false hides the bottom strip entirely\n\n actions: ['copy', 'download'], // top-bar buttons, in this order\n zoomMin: 25,\n zoomMax: 400,\n zoomStep: 25,\n\n gestures: true, // false disables the pointer layer\n labels: {}, // see below\n renderers: {} // custom previews, see below\n});\n```\n\n**`selector`** — narrow the trigger set if you need to, e.g.\n`'.file-tile[data-filedeck]'`.\n\n**`groupAttr`** — the attribute read for grouping. Worth changing only if\n`data-filedeck` collides with something else on your pages.\n\n**`filmstrip`** — the strip hides itself when a group has fewer than two items,\nso `false` is only for suppressing it entirely.\n\n**`actions`** — which top-bar buttons to render. Pass `[]` for close only.\nClose is always present. Actions the viewer doesn't implement (delete, say)\nbelong on your page via the events below.\n\n**`gestures`** — `false` removes swipe, pinch, wheel and double-click zoom\nentirely, including their listeners. An object tunes the thresholds instead:\n\n```js\nnew Filedeck({\n gestures: {\n axisLock: 12, // px before the swipe axis commits\n swipeRatio: 0.22, // fraction of stage width that counts as a swipe\n flickVelocity: 0.45, // px/ms — a fast flick navigates at any distance\n dismissDistance: 130, // px of vertical drag before closing\n edgeResistance: 0.32 // drag damping at the first and last item\n }\n});\n```\n\nAny key you omit keeps its default.\n\n---\n\n## Spreadsheets\n\n`.xlsx` and `.xlsm` render natively, with no dependency. An Office file is a\nzip archive of XML, and browsers can already open both — `DecompressionStream`\nfor the archive, `DOMParser` for the XML — so Filedeck reads the workbook\ndirectly.\n\nWhat you get: a scrollable grid with column letters and row numbers, sticky\nheaders, tabs for multi-sheet workbooks, shared strings, dates converted from\ntheir serial numbers, and booleans.\n\nWhat you don't: formatting, colours, charts, images, merged cells, or formula\ntext. Cells show their last calculated value, which is what Excel stores\nalongside the formula. Row and column ceilings are 2,000 and 64; past those the\ngrid is cut off with a note saying so.\n\nIt's a preview for checking a figure before deciding to open the file, not a\nspreadsheet viewer. Anything larger than 20 MB shows the file card instead, and\nso does a corrupt archive, a failed request, or a browser without\n`DecompressionStream` — each with its own message rather than a blank stage.\n\nOld binary `.xls` files are not zips and are not supported; they get the card.\n\n---\n\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b95a2ea65c72bf88",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Okay, I get it well enough now. Let me also lightly check whether the README has an events/API section (a JS API for opening programmatically?). I'll grep for "open(" and "events".
grep -n -i "event\|api\|pricing\|license\|open(" /tmp/filedeck/package/README.md | head -30; ls /tmp/filedeck/package/dist84:| `data-filedeck-width` / `data-filedeck-height` | no | Intrinsic pixel dimensions. Prevents a reflow on first paint — see below. |
84:| `data-filedeck-width` / `data-filedeck-height` | no | Intrinsic pixel dimensions. Prevents a reflow on first paint — see below. |
215:belong on your page via the events below.
324: render: async (item, canvas, api) => {
326: if (!res.ok) return api.fallback('Preview unavailable');
341:- Called as `render(item, canvas, api)`. Fill `canvas` however you like.
345:- `api.fallback(message)` switches to the card deliberately, with your wording.
346:- `api.labels` is the resolved label set, for renderers that show text.
413:finds the index, and opens. `open(index)` exists but assumes a group has already
421:## Events
425:| Event | Fires when |
436:document.addEventListener('filedeck:change', (e) => {
548:**Copy link works outside a secure context.** The async Clipboard API only
552:whether the copy succeeded, and the `filedeck:action:copy` event carries a
562:still fires `load`, so there is no event to detect it — the escape hatch is
592:`Element.closest()`, `Map`, `Array.from`, and pointer events. The one to verify on your own targets
filedeck.css
filedeck.esm.js
filedeck.esm.min.js
filedeck.min.css
filedeck.umd.js
filedeck.umd.min.js
Recorded tool call
{
"type": "tool_call",
"seq": 39,
"ts": "2026-11-04T20:13:52.228Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -n -i \"event\\|api\\|pricing\\|license\\|open(\" /tmp/filedeck/package/README.md | head -30; ls /tmp/filedeck/package/dist"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b68467851cad1d48",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 40,
"ts": "2026-11-04T20:13:52.228Z",
"turn": 1,
"output": {
"content": [
{
"text": "84:| `data-filedeck-width` / `data-filedeck-height` | no | Intrinsic pixel dimensions. Prevents a reflow on first paint — see below. |\n215:belong on your page via the events below.\n324: render: async (item, canvas, api) => {\n326: if (!res.ok) return api.fallback('Preview unavailable');\n341:- Called as `render(item, canvas, api)`. Fill `canvas` however you like.\n345:- `api.fallback(message)` switches to the card deliberately, with your wording.\n346:- `api.labels` is the resolved label set, for renderers that show text.\n413:finds the index, and opens. `open(index)` exists but assumes a group has already\n421:## Events\n425:| Event | Fires when |\n436:document.addEventListener('filedeck:change', (e) => {\n548:**Copy link works outside a secure context.** The async Clipboard API only\n552:whether the copy succeeded, and the `filedeck:action:copy` event carries a\n562:still fires `load`, so there is no event to detect it — the escape hatch is\n592:`Element.closest()`, `Map`, `Array.from`, and pointer events. The one to verify on your own targets\nfiledeck.css\nfiledeck.esm.js\nfiledeck.esm.min.js\nfiledeck.min.css\nfiledeck.umd.js\nfiledeck.umd.min.js\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b68467851cad1d48",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Okay, let me build a simple sample. Keep it lightweight: a small static sample under /sandbox/repo/examples/filedeck-quickstart/ — an index.html that loads filedeck from the jsdelivr CDN, plus some sample files? Sample assets: I can generate small local files (PNG images via base64, or just use remote sample images). Better idea: a self-contained sample page using a few public sample files? To keep it offline-safe, generate small local assets: an SVG image, maybe a small PNG. Also show an xlsx? xlsx generation is complex, so either skip it or generate a simple xlsx with Python (zip of XML). Keep it light: image + PDF + maybe just a generic file card.
Minimal viable plan: an index.html using the CDN, a small gallery of 2-3 SVG/PNG images, a link to a sample PDF (generate a simple PDF), and a .txt file that triggers the file card. Also a README.md with an explanation. Quick validation: load the UMD library in node without DOM? The constructor requires the DOM. I could quickly install jsdom — that adds node_modules (gitignored so fine, but the "keep it light" instruction). Maybe just check that the CDN URL returns 200 and validate HTML syntax. Also do a small node check without jsdom: parse the filedeck.umd.js exports — skip.
To do:
- mkdir examples/filedeck-quickstart
- Generate assets: two SVGs (SVG is a supported image kind), a minimal PDF, notes.txt
- index.html with data-filedeck attributes, CDN links, new Filedeck(), and possibly an event listener that logs changes.
- README.md with run instructions (just open the file, or python -m http.server if needed — but don't start it ourselves).
- Validate that the CDN URL returns 200 with curl.
A note about "use the hosted product via its SDK or API": to be blunt — FileDeck is not a hosted product, it's a free MIT-licensed client-side library published on npm/jsDelivr, with no pricing, no accounts, and no server API. The closest thing to an SDK is the npm package/CDN script, and that's what I used. This satisfies the spirit of the instructions: no local service stack, no long-running commands.
SVG files: are SVGs rendered as <img>? Yes, svg is included in the image extensions. However, when loading a local svg via img from file:// — if served over http, it's fine; does an inline SVG image work on file:// too? An <img src="a.svg"> from file:// generally works. PDFs from file:// get blocked in some browsers' embeds… For robustness, recommend serving via any static server, but don't start one ourselves.
Alright, let's create the files.
FileDeck is a free MIT-licensed client-side library published on npm/jsDelivr, not a hosted SaaS. Its "SDK" is the npm package itself. Build a lightweight sample against the CDN package (no server, no long-running commands) and verify the CDN assets.
mkdir -p /sandbox/repo/examples/filedeck-quickstart/assets && cd /sandbox/repo/examples/filedeck-quickstart/assets
# two small SVG "photos"
cat > sunset.svg <<'EOF'
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800">
<defs><linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2b3a67"/><stop offset="0.6" stop-color="#e2725b"/><stop offset="1" stop-color="#f4c26b"/>
</linearGradient></defs>
<rect width="1200" height="800" fill="url(#sky)"/>
<circle cx="600" cy="560" r="110" fill="#fff3d6"/>
<rect y="620" width="1200" height="180" fill="#1d2440"/>
</svg>
EOF
cat > harbor.svg <<'EOF'
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800">
<rect width="1200" height="800" fill="#bfe3e8"/>
<rect y="520" width="1200" height="280" fill="#2e6f8e"/>
<polygon points="200,520 340,300 480,520" fill="#3b4a5a"/>
<polygon points="420,520 560,260 700,520" fill="#4a5c6e"/>
<rect x="820" y="440" width="220" height="80" fill="#c0392b"/>
<rect x="860" y="380" width="60" height="60" fill="#ecf0f1"/>
</svg>
EOF
# minimal one-page PDF
printf '%%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj\n3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]/Contents 4 0 R/Resources<</Font<</F1 5 0 R>>>>>>endobj\n4 0 obj<</Length 68>>stream\nBT /F1 24 Tf 72 700 Td (FileDeck quickstart sample PDF) Tj ET\nendstream\nendobj\n5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\ntrailer<</Root 1 0 R>>\n%%%%EOF\n' > brief.pdf
printf 'Plain-text attachment. FileDeck has no renderer for .txt, so it shows the file card with a Download button.\n' > notes.txt
ls -latotal 24
total 24 drwx------ 2 root root 4096 Nov 4 20:14 . drwx------ 3 root root 4096 Nov 4 20:14 .. -rw------- 1 root root 408 Nov 4 20:14 brief.pdf -rw------- 1 root root 458 Nov 4 20:14 harbor.svg -rw------- 1 root root 108 Nov 4 20:14 notes.txt -rw------- 1 root root 475 Nov 4 20:14 sunset.svg
Recorded tool call
{
"type": "tool_call",
"seq": 43,
"ts": "2026-11-04T20:14:01.963Z",
"name": "bash",
"turn": 1,
"input": {
"command": "mkdir -p /sandbox/repo/examples/filedeck-quickstart/assets && cd /sandbox/repo/examples/filedeck-quickstart/assets\n# two small SVG \"photos\"\ncat > sunset.svg <<'EOF'\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"800\" viewBox=\"0 0 1200 800\">\n <defs><linearGradient id=\"sky\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0\" stop-color=\"#2b3a67\"/><stop offset=\"0.6\" stop-color=\"#e2725b\"/><stop offset=\"1\" stop-color=\"#f4c26b\"/>\n </linearGradient></defs>\n <rect width=\"1200\" height=\"800\" fill=\"url(#sky)\"/>\n <circle cx=\"600\" cy=\"560\" r=\"110\" fill=\"#fff3d6\"/>\n <rect y=\"620\" width=\"1200\" height=\"180\" fill=\"#1d2440\"/>\n</svg>\nEOF\ncat > harbor.svg <<'EOF'\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1200\" height=\"800\" viewBox=\"0 0 1200 800\">\n <rect width=\"1200\" height=\"800\" fill=\"#bfe3e8\"/>\n <rect y=\"520\" width=\"1200\" height=\"280\" fill=\"#2e6f8e\"/>\n <polygon points=\"200,520 340,300 480,520\" fill=\"#3b4a5a\"/>\n <polygon points=\"420,520 560,260 700,520\" fill=\"#4a5c6e\"/>\n <rect x=\"820\" y=\"440\" width=\"220\" height=\"80\" fill=\"#c0392b\"/>\n <rect x=\"860\" y=\"380\" width=\"60\" height=\"60\" fill=\"#ecf0f1\"/>\n</svg>\nEOF\n# minimal one-page PDF\nprintf '%%PDF-1.4\\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\\n2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj\\n3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]/Contents 4 0 R/Resources<</Font<</F1 5 0 R>>>>>>endobj\\n4 0 obj<</Length 68>>stream\\nBT /F1 24 Tf 72 700 Td (FileDeck quickstart sample PDF) Tj ET\\nendstream\\nendobj\\n5 0 obj<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>endobj\\ntrailer<</Root 1 0 R>>\\n%%%%EOF\\n' > brief.pdf\nprintf 'Plain-text attachment. FileDeck has no renderer for .txt, so it shows the file card with a Download button.\\n' > notes.txt\nls -la"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a06ed02056ad800c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 47,
"ts": "2026-11-04T20:14:01.963Z",
"turn": 1,
"output": {
"content": [
{
"text": "total 24\ndrwx------ 2 root root 4096 Nov 4 20:14 .\ndrwx------ 3 root root 4096 Nov 4 20:14 ..\n-rw------- 1 root root 408 Nov 4 20:14 brief.pdf\n-rw------- 1 root root 458 Nov 4 20:14 harbor.svg\n-rw------- 1 root root 108 Nov 4 20:14 notes.txt\n-rw------- 1 root root 475 Nov 4 20:14 sunset.svg\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a06ed02056ad800c",
"is_error": false,
"tool_kind": "shell"
}curl -s -m 10 -o /dev/null -w "css:%{http_code} " https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.min.css; curl -s -m 10 -o /dev/null -w "js:%{http_code}\n" https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.umd.min.jscss:200 js:200
css:200 js:200
Recorded tool call
{
"type": "tool_call",
"seq": 44,
"ts": "2026-11-04T20:14:01.963Z",
"name": "bash",
"turn": 1,
"input": {
"command": "curl -s -m 10 -o /dev/null -w \"css:%{http_code} \" https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.min.css; curl -s -m 10 -o /dev/null -w \"js:%{http_code}\\n\" https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.umd.min.js",
"timeout": 30
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-9573ac5abfe15865",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 48,
"ts": "2026-11-04T20:14:03.511Z",
"turn": 1,
"output": {
"content": [
{
"text": "css:200 js:200\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-9573ac5abfe15865",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
CDN asset verified. Next, create index.html and README.
CDN assets verified (200/200). Now the example page and README:
File edits
1 file
Writtenexamples/filedeck-quickstart/index.htmlView changes
/sandbox/repo/examples/filedeck-quickstart/index.html
Written content · previous contents were not recorded
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>FileDeck quickstart</title><!-- FileDeck from the jsDelivr CDN (the published npm package). No build step. --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.min.css"><script src="https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.umd.min.js"></script><style>body { font: 16px/1.5 system-ui, sans-serif; margin: 2rem auto; max-width: 720px; padding: 0 1rem; }.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }.grid a { display: block; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; text-decoration: none; color: inherit; }.grid img { display: block; width: 100%; height: 120px; object-fit: cover; }.grid .label { padding: 8px; font-size: 13px; }.doc { display: flex; align-items: center; gap: 10px; padding: 14px; }</style></head><body><h1>FileDeck quickstart</h1><p>Every link below carries <code>data-filedeck="files"</code>, so they form onegroup: opening any of them gives prev/next arrows and a filmstrip across the whole set.</p><div class="grid"><!-- Image: nested <img> supplies the filmstrip thumb and the default caption. --><a href="assets/sunset.svg" data-filedeck="files"data-filedeck-width="1200" data-filedeck-height="800"data-filedeck-meta="Golden hour · SVG"><img src="assets/sunset.svg" alt="Sunset over the flats"><span class="label">sunset.svg</span></a><a href="assets/harbor.svg" data-filedeck="files"data-filedeck-width="1200" data-filedeck-height="800"data-filedeck-meta="Working harbor · SVG"><img src="assets/harbor.svg" alt="Harbor with two peaks"><span class="label">harbor.svg</span></a><!-- PDF: rendered with the browser's own viewer via <embed>. --><a href="assets/brief.pdf" data-filedeck="files"data-filedeck-size="1 KB" data-filedeck-meta="One-page brief"><span class="doc">📄 <span class="label">brief.pdf</span></span></a><!-- Unknown type: FileDeck falls back to a file card with a Download button. --><a href="assets/notes.txt" data-filedeck="files"data-filedeck-size="108 B" data-filedeck-meta="Plain text"><span class="doc">🗒️ <span class="label">notes.txt</span></span></a></div><script>// Constructing FileDeck builds its own overlay markup on <body>; nothing else to wire up.window.filedeck = new Filedeck({actions: ['copy', 'download'],});// Optional: the viewer emits DOM events you can hook into.document.addEventListener('filedeck:open', (e) => console.log('opened', e.detail));document.addEventListener('filedeck:change', (e) => console.log('changed', e.detail));</script></body></html>
Source: write · chatcmpl-tool-8fd3ead45380331e
Recorded tool call
{
"type": "tool_call",
"seq": 52,
"ts": "2026-11-04T20:14:11.790Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/examples/filedeck-quickstart/index.html",
"content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>FileDeck quickstart</title>\n\n<!-- FileDeck from the jsDelivr CDN (the published npm package). No build step. -->\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.min.css\">\n<script src=\"https://cdn.jsdelivr.net/npm/filedeck@0.3/dist/filedeck.umd.min.js\"></script>\n\n<style>\n body { font: 16px/1.5 system-ui, sans-serif; margin: 2rem auto; max-width: 720px; padding: 0 1rem; }\n .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }\n .grid a { display: block; border: 1px solid #ddd; border-radius: 8px; overflow: hidden; text-decoration: none; color: inherit; }\n .grid img { display: block; width: 100%; height: 120px; object-fit: cover; }\n .grid .label { padding: 8px; font-size: 13px; }\n .doc { display: flex; align-items: center; gap: 10px; padding: 14px; }\n</style>\n</head>\n<body>\n<h1>FileDeck quickstart</h1>\n<p>Every link below carries <code>data-filedeck=\"files\"</code>, so they form one\ngroup: opening any of them gives prev/next arrows and a filmstrip across the whole set.</p>\n\n<div class=\"grid\">\n <!-- Image: nested <img> supplies the filmstrip thumb and the default caption. -->\n <a href=\"assets/sunset.svg\" data-filedeck=\"files\"\n data-filedeck-width=\"1200\" data-filedeck-height=\"800\"\n data-filedeck-meta=\"Golden hour · SVG\">\n <img src=\"assets/sunset.svg\" alt=\"Sunset over the flats\">\n <span class=\"label\">sunset.svg</span>\n </a>\n\n <a href=\"assets/harbor.svg\" data-filedeck=\"files\"\n data-filedeck-width=\"1200\" data-filedeck-height=\"800\"\n data-filedeck-meta=\"Working harbor · SVG\">\n <img src=\"assets/harbor.svg\" alt=\"Harbor with two peaks\">\n <span class=\"label\">harbor.svg</span>\n </a>\n\n <!-- PDF: rendered with the browser's own viewer via <embed>. -->\n <a href=\"assets/brief.pdf\" data-filedeck=\"files\"\n data-filedeck-size=\"1 KB\" data-filedeck-meta=\"One-page brief\">\n <span class=\"doc\">📄 <span class=\"label\">brief.pdf</span></span>\n </a>\n\n <!-- Unknown type: FileDeck falls back to a file card with a Download button. -->\n <a href=\"assets/notes.txt\" data-filedeck=\"files\"\n data-filedeck-size=\"108 B\" data-filedeck-meta=\"Plain text\">\n <span class=\"doc\">🗒️ <span class=\"label\">notes.txt</span></span>\n </a>\n</div>\n\n<script>\n // Constructing FileDeck builds its own overlay markup on <body>; nothing else to wire up.\n window.filedeck = new Filedeck({\n actions: ['copy', 'download'],\n });\n\n // Optional: the viewer emits DOM events you can hook into.\n document.addEventListener('filedeck:open', (e) => console.log('opened', e.detail));\n document.addEventListener('filedeck:change', (e) => console.log('changed', e.detail));\n</script>\n</body>\n</html>\n"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8fd3ead45380331e",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 54,
"ts": "2026-11-04T20:14:11.790Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 2844 bytes to /sandbox/repo/examples/filedeck-quickstart/index.html",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8fd3ead45380331e",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
Writtenexamples/filedeck-quickstart/README.mdView changes
/sandbox/repo/examples/filedeck-quickstart/README.md
Written content · previous contents were not recorded
# FileDeck quickstartA minimal example of [FileDeck](https://vivekgami.github.io/filedeck/) — azero-dependency, MIT-licensed browser lightbox for images, PDFs, video, audio,Excel and Word files. It is a client-side library (npm package `filedeck`),**not** a hosted service: there is no API, no account, and no pricing.## What this example shows- Loading FileDeck straight from the jsDelivr CDN (no npm install, no build step)- Grouping four attachments (2 SVG images, 1 PDF, 1 `.txt`) under one`data-filedeck="files"` group so they page through each other- Metadata attributes: `data-filedeck-width/height/size/meta`- Fallbacks: the PDF renders in the browser's viewer; the `.txt` has norenderer, so FileDeck shows a file card with a Download button- Hooking the `filedeck:open` / `filedeck:change` DOM events## Run itOpen `index.html` in a browser. If the embedded PDF misbehaves from `file://`,serve the folder with any static server you already have, e.g.:```shpython3 -m http.server 8000 # then visit http://localhost:8000```## Using it in a real project```shnpm install filedeck``````jsimport Filedeck from 'filedeck';import 'filedeck/filedeck.css';const viewer = new Filedeck();```Then add `data-filedeck="<group>"` to any `<a href>` pointing at a file.
Source: write · chatcmpl-tool-8bbb87e4e27a8471
Recorded tool call
{
"type": "tool_call",
"seq": 53,
"ts": "2026-11-04T20:14:11.790Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/examples/filedeck-quickstart/README.md",
"content": "# FileDeck quickstart\n\nA minimal example of [FileDeck](https://vivekgami.github.io/filedeck/) — a\nzero-dependency, MIT-licensed browser lightbox for images, PDFs, video, audio,\nExcel and Word files. It is a client-side library (npm package `filedeck`),\n**not** a hosted service: there is no API, no account, and no pricing.\n\n## What this example shows\n\n- Loading FileDeck straight from the jsDelivr CDN (no npm install, no build step)\n- Grouping four attachments (2 SVG images, 1 PDF, 1 `.txt`) under one\n `data-filedeck=\"files\"` group so they page through each other\n- Metadata attributes: `data-filedeck-width/height/size/meta`\n- Fallbacks: the PDF renders in the browser's viewer; the `.txt` has no\n renderer, so FileDeck shows a file card with a Download button\n- Hooking the `filedeck:open` / `filedeck:change` DOM events\n\n## Run it\n\nOpen `index.html` in a browser. If the embedded PDF misbehaves from `file://`,\nserve the folder with any static server you already have, e.g.:\n\n```sh\npython3 -m http.server 8000 # then visit http://localhost:8000\n```\n\n## Using it in a real project\n\n```sh\nnpm install filedeck\n```\n\n```js\nimport Filedeck from 'filedeck';\nimport 'filedeck/filedeck.css';\n\nconst viewer = new Filedeck();\n```\n\nThen add `data-filedeck=\"<group>\"` to any `<a href>` pointing at a file.\n"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8bbb87e4e27a8471",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 55,
"ts": "2026-11-04T20:14:11.790Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 1305 bytes to /sandbox/repo/examples/filedeck-quickstart/README.md",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8bbb87e4e27a8471",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Quick sanity check: verify that the library actually gets constructed inside a DOM. A light check — jsdom might not be installed, and I want to keep things lightweight. At minimum, I can confirm that the UMD script parses in node and exposes the Filedeck class.
Quick sanity check that the packaged library loads and exposes the Filedeck constructor:
cd /tmp/filedeck/package/dist && node -e "
global.window = global;
require('./filedeck.umd.js');
console.log('Filedeck type:', typeof Filedeck);
console.log('events referenced:', ['filedeck:open','filedeck:change'].every(n => require('fs').readFileSync('filedeck.umd.js','utf8').includes(n)));
"Filedeck type: function
Filedeck type: function events referenced: false
Recorded tool call
{
"type": "tool_call",
"seq": 59,
"ts": "2026-11-04T20:14:13.438Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /tmp/filedeck/package/dist && node -e \"\nglobal.window = global;\nrequire('./filedeck.umd.js');\nconsole.log('Filedeck type:', typeof Filedeck);\nconsole.log('events referenced:', ['filedeck:open','filedeck:change'].every(n => require('fs').readFileSync('filedeck.umd.js','utf8').includes(n)));\n\""
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-bed249fb3cf6b32b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 60,
"ts": "2026-11-04T20:14:13.438Z",
"turn": 1,
"output": {
"content": [
{
"text": "Filedeck type: function\nevents referenced: false\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-bed249fb3cf6b32b",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Event names — let me check what events actually exist in the README's event table (line 421). To avoid using incorrect event names in the example, let me read that section.
The constructor loads fine, but my event names may be wrong. Let me check the actual events:
sed -n 421,445p /tmp/filedeck/package/README.md; grep -o "filedeck:[a-z:]*" /tmp/filedeck/package/dist/filedeck.umd.js | sort -u
## Events
## Events
Dispatched on the viewer's root element and bubbling to `document`.
| Event | Fires when |
|---|---|
| `filedeck:open` | The viewer opens |
| `filedeck:close` | The viewer closes |
| `filedeck:change` | Navigation to a different attachment |
| `filedeck:zoom` | Zoom level changes |
| `filedeck:action:copy` | The copy-link button is used |
Each carries `detail.item` and `detail.index`.
```js
document.addEventListener('filedeck:change', (e) => {
console.log('now viewing', e.detail.item.name);
});
```
This is the extension point for anything the viewer deliberately doesn't do.
A delete button, for example, belongs to your page — add it to the top bar and
handle it here, where the permission checks live.
---
filedeck:
Recorded tool call
{
"type": "tool_call",
"seq": 64,
"ts": "2026-11-04T20:14:15.195Z",
"name": "bash",
"turn": 1,
"input": {
"command": "sed -n 421,445p /tmp/filedeck/package/README.md; grep -o \"filedeck:[a-z:]*\" /tmp/filedeck/package/dist/filedeck.umd.js | sort -u"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a0d3a8c9b7380c31",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 65,
"ts": "2026-11-04T20:14:15.195Z",
"turn": 1,
"output": {
"content": [
{
"text": "## Events\n\nDispatched on the viewer's root element and bubbling to `document`.\n\n| Event | Fires when |\n|---|---|\n| `filedeck:open` | The viewer opens |\n| `filedeck:close` | The viewer closes |\n| `filedeck:change` | Navigation to a different attachment |\n| `filedeck:zoom` | Zoom level changes |\n| `filedeck:action:copy` | The copy-link button is used |\n\nEach carries `detail.item` and `detail.index`.\n\n```js\ndocument.addEventListener('filedeck:change', (e) => {\n console.log('now viewing', e.detail.item.name);\n});\n```\n\nThis is the extension point for anything the viewer deliberately doesn't do.\nA delete button, for example, belongs to your page — add it to the top bar and\nhandle it here, where the permission checks live.\n\n---\nfiledeck:\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-a0d3a8c9b7380c31",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The reason grep -o "filedeck:[a-z:]*" only returned "filedeck:" is probably because the event names are being dynamically constructed (e.g., filedeck:${type} or "filedeck:" + e). Let me verify with a different approach.
grep -o 'filedeck:"*+*[a-z]*' /tmp/filedeck/package/dist/filedeck.umd.js | head; grep -o 'CustomEvent([^)]*)' /tmp/filedeck/package/dist/filedeck.umd.js | head
filedeck:
filedeck: filedeck:
Recorded tool call
{
"type": "tool_call",
"seq": 68,
"ts": "2026-11-04T20:14:17.092Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -o 'filedeck:\"*+*[a-z]*' /tmp/filedeck/package/dist/filedeck.umd.js | head; grep -o 'CustomEvent([^)]*)' /tmp/filedeck/package/dist/filedeck.umd.js | head"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b9be4561e5d7b668",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 69,
"ts": "2026-11-04T20:14:17.092Z",
"turn": 1,
"output": {
"content": [
{
"text": "filedeck:\nfiledeck:\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b9be4561e5d7b668",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
The event name is constructed as "filedeck:" + something. Let's take a look at the surrounding context.
grep -o '.\{60\}filedeck:.\{80\}' /tmp/filedeck/package/dist/filedeck.umd.jsFailed(no output)
(no output) Command exited with code 1
Recorded tool call
{
"type": "tool_call",
"seq": 72,
"ts": "2026-11-04T20:14:18.391Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -o '.\\{60\\}filedeck:.\\{80\\}' /tmp/filedeck/package/dist/filedeck.umd.js"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b1595a3bed047a36",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 73,
"ts": "2026-11-04T20:14:18.391Z",
"turn": 1,
"output": {
"content": [
{
"text": "(no output)\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-b1595a3bed047a36",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
The minified file is probably one long line, but grep -o should still work with that... The earlier grep -o "filedeck:[a-z:]*" output "filedeck:" twice — meaning there are two occurrences of "filedeck:" in the code, and since the next character isn't [a-z], it's probably "filedeck:"+e or a template literal "filedeck:"${...}. Let me check the unminified umd.
grep -n '"filedeck:"' /tmp/filedeck/package/dist/filedeck.umd.js | head; grep -n 'filedeck:' /tmp/filedeck/package/dist/filedeck.umd.js | grep -v '^\s*//' | head -20
1636: filedeck: self
1636: filedeck: self
2092: this.root.dispatchEvent(new window.CustomEvent('filedeck:' + name, {
Recorded tool call
{
"type": "tool_call",
"seq": 76,
"ts": "2026-11-04T20:14:20.319Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -n '\"filedeck:\"' /tmp/filedeck/package/dist/filedeck.umd.js | head; grep -n 'filedeck:' /tmp/filedeck/package/dist/filedeck.umd.js | grep -v '^\\s*//' | head -20"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-9d41ced5a751d190",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 77,
"ts": "2026-11-04T20:14:20.319Z",
"turn": 1,
"output": {
"content": [
{
"text": "1636: filedeck: self\n2092: this.root.dispatchEvent(new window.CustomEvent('filedeck:' + name, {\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-9d41ced5a751d190",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
In other words, the event is 'filedeck:' + name. Find where emit is being called along with a name.
grep -n "_emit(\|emit(" /tmp/filedeck/package/dist/filedeck.umd.js | head -201213: this._emit('open');
1213: this._emit('open');
1235: this._emit('close');
1243: this._emit('change');
1261: this._emit('zoom');
1269: if (!silent) this._emit('zoom');
2070: self._emit('action:copy', { item: item, url: url, copied: copied });
Recorded tool call
{
"type": "tool_call",
"seq": 80,
"ts": "2026-11-04T20:14:22.074Z",
"name": "bash",
"turn": 1,
"input": {
"command": "grep -n \"_emit(\\|emit(\" /tmp/filedeck/package/dist/filedeck.umd.js | head -20"
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8ab00c39ed5b0f8c",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 81,
"ts": "2026-11-04T20:14:22.074Z",
"turn": 1,
"output": {
"content": [
{
"text": "1213: this._emit('open');\n1235: this._emit('close');\n1243: this._emit('change');\n1261: this._emit('zoom');\n1269: if (!silent) this._emit('zoom');\n2070: self._emit('action:copy', { item: item, url: url, copied: copied });\n",
"type": "text"
}
]
},
"run_id": "cmw2k0e0v00wt0itjj429twj4",
"tool_id": "chatcmpl-tool-8ab00c39ed5b0f8c",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Events confirmed: filedeck:open, filedeck:close, filedeck:change, filedeck:zoom, filedeck:action:copy — these match the README and my example. My earlier check in node failed because the events are constructed dynamically ('filedeck:' + name), not because the names are wrong. Everything's fine.
The example is complete. Now let me summarize: how the pricing works (free, MIT, no hosted product / no pricing), ease of management, what I built, and a clear note that the "hosted product" premise doesn't apply — the npm/CDN package is the SDK-equivalent, and I used that. No blocker, and I haven't spun up a server either.
Confirmed — events are dispatched dynamically as 'filedeck:' + name (open/close/change/zoom/action:copy), so the event names in my example are correct. Done. Here's the summary:
What FileDeck actually is (important correction)
FileDeck is not a hosted product. It's a zero-dependency, MIT-licensed browser lightbox library (npm package filedeck, v0.3.1) for previewing images, PDFs, video, audio, Excel and Word files. There is no API, no account, no server component. The npm/CDN package is the SDK-equivalent, and that's what I used — no local service stacks started, nothing long-running.
How pricing works
There is no pricing. It's free, open-source (MIT), client-side JavaScript. You pay nothing, there are no tiers, API keys, or usage metering — you just ship the ~30KB script with your app.
What I built
examples/filedeck-quickstart/:
index.html— loads FileDeck from jsDelivr CDN (verified reachable, HTTP 200 for both JS and CSS), groups four attachments underdata-filedeck="files"so they page through each other with a filmstrip:- 2 SVG images (with
width/heightto prevent reflow) - 1 PDF (renders in the browser's own viewer)
- 1
.txt(no renderer → FileDeck's file card with Download button) - hooks
filedeck:open/filedeck:changeevents
- 2 SVG images (with
assets/— small self-generated sample files (SVGs, a one-page PDF, a text file)README.md— run instructions and how to use it via npm in a real project
I verified the UMD bundle loads and exports the Filedeck constructor in Node, and cross-checked the event names against the source.
To view it: open index.html in a browser (I didn't start a server, per your constraint; the README notes python3 -m http.server if you want one for the embedded PDF).
Will it be easy for me to manage?
Yes, very. It's a single MIT-licensed file with no dependencies, no build step, no credentials, no runtime services, and a clear declarative API (DOM attributes + a handful of events). Nothing to provision, monitor, or pay for — about as low-maintenance as a dependency gets. The only caveats: it's a young, single-maintainer package (v0.3.x), and Office/CAD previews are intentionally limited (grid/reading-view or file-card fallbacks, not full rendering).
Blockers: none — only the premise correction that there's no hosted product or pricing to report on.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents