diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 13849ee..e2fcacf 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,10 +8,12 @@
"name": "slabuploader-frontend",
"version": "0.0.0",
"devDependencies": {
+ "@playwright/test": "^1.63.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"pngjs": "^7.0.0",
+ "sirv-cli": "^3.0.1",
"svelte": "^5.0.0",
"svelte-check": "^4.1.1",
"typescript": "^5.6.3",
@@ -496,6 +498,21 @@
"node": "^22.20 || ^24.12 || >=25"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
+ "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -1128,6 +1145,15 @@
"node": ">=6"
}
},
+ "node_modules/console-clear": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz",
+ "integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
@@ -1297,6 +1323,18 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/get-port": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
+ "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-reference": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
@@ -1315,6 +1353,15 @@
"node": ">=6"
}
},
+ "node_modules/local-access": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/local-access/-/local-access-1.1.0.tgz",
+ "integrity": "sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/locate-character": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
@@ -1411,6 +1458,33 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
+ "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.63.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.63.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
+ "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/pngjs": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
@@ -1518,6 +1592,15 @@
"node": ">=6"
}
},
+ "node_modules/semiver": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz",
+ "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/set-cookie-parser": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz",
@@ -1544,6 +1627,28 @@
"node": ">=18"
}
},
+ "node_modules/sirv-cli": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-3.0.1.tgz",
+ "integrity": "sha512-ICXaF2u6IQhLZ0EXF6nqUF4YODfSQSt+mGykt4qqO5rY+oIiwdg7B8w2PVDBJlQulaS2a3J8666CUoDoAuCGvg==",
+ "dev": true,
+ "dependencies": {
+ "console-clear": "^1.1.0",
+ "get-port": "^5.1.1",
+ "kleur": "^4.1.4",
+ "local-access": "^1.0.1",
+ "sade": "^1.6.0",
+ "semiver": "^1.0.0",
+ "sirv": "^3.0.0",
+ "tinydate": "^1.0.0"
+ },
+ "bin": {
+ "sirv": "bin.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1621,6 +1726,15 @@
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
"dev": true
},
+ "node_modules/tinydate": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz",
+ "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 6c1abda..83ece0e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,10 +11,12 @@
"test:watch": "vitest"
},
"devDependencies": {
+ "@playwright/test": "^1.63.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"pngjs": "^7.0.0",
+ "sirv-cli": "^3.0.1",
"svelte": "^5.0.0",
"svelte-check": "^4.1.1",
"typescript": "^5.6.3",
diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte
index 93d14a9..6f7c043 100644
--- a/frontend/src/routes/+page.svelte
+++ b/frontend/src/routes/+page.svelte
@@ -453,8 +453,8 @@
Capture Photos
Select 1–5 photos of the slab. Supported: JPG, PNG, WEBP.
- handleFiles(e.target.files)} class="file-input" />
-
{/each}
-