diff --git a/bun.lock b/bun.lock
index 3d1c4ab..8692a21 100644
--- a/bun.lock
+++ b/bun.lock
@@ -6,7 +6,7 @@
"name": "airtable-app",
"devDependencies": {
"@playwright/test": "^1.59.1",
- "@screenly/edge-apps": "^1.2.1",
+ "@screenly/edge-apps": "^1.4.0",
"@types/bun": "^1.3.9",
"@types/jsdom": "^28.0.1",
"bun-types": "^1.3.9",
@@ -44,7 +44,7 @@
"@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
- "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
+ "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
@@ -122,53 +122,57 @@
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
- "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
+ "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="],
- "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
+ "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="],
- "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
+ "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "os": "freebsd" }, "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg=="],
- "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
+ "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="],
- "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
+ "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="],
- "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
+ "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="],
- "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
+ "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="],
- "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
+ "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw=="],
- "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
+ "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w=="],
- "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
+ "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="],
- "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
+ "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="],
- "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
+ "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="],
- "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
+ "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="],
- "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
+ "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="],
- "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
+ "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="],
- "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
+ "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.2" }, "os": "linux", "cpu": "ppc64" }, "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA=="],
- "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
+ "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.2" }, "os": "linux", "cpu": "none" }, "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ=="],
- "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
+ "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="],
- "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
+ "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="],
- "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
+ "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="],
- "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
+ "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="],
- "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
+ "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.3", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w=="],
- "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
+ "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "cpu": "none" }, "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q=="],
- "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
+ "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w=="],
+
+ "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="],
+
+ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
@@ -416,7 +420,7 @@
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
- "@screenly/edge-apps": ["@screenly/edge-apps@1.2.1", "", { "dependencies": { "@eslint/js": "^10.0.1", "@photostructure/tz-lookup": "^11.5.0", "@sentry/browser": "^10.59.0", "@tailwindcss/vite": "^4.2.1", "country-locale-map": "^1.9.12", "dayjs": "^1.11.19", "eslint": "^10.0.3", "jiti": "^2.6.1", "jsdom": "^28.1.0", "offline-geocode-city": "^1.0.2", "panic-overlay": "^1.0.51", "sharp": "^0.34.5", "tailwindcss": "^4.2.1", "typescript": "^5.9.3", "typescript-eslint": "^8.57.0", "vite": "^8.1.2", "yaml": "^2.8.2" }, "peerDependencies": { "@playwright/test": "^1.58.0" }, "optionalPeers": ["@playwright/test"], "bin": { "edge-apps-scripts": "bin/edge-apps-scripts.js" } }, "sha512-9CTA8eaYtVlrACHWU9uhml3Yf7hIgc6mELmXhqDdHKDC2fmrgma3sAOpvr0sBjTJr37QRvpeqUjlZ9BJlwT0nQ=="],
+ "@screenly/edge-apps": ["@screenly/edge-apps@1.4.0", "", { "dependencies": { "@eslint/js": "^10.0.1", "@photostructure/tz-lookup": "^11.5.0", "@sentry/browser": "^10.59.0", "@tailwindcss/vite": "^4.2.1", "country-locale-map": "^1.9.12", "dayjs": "^1.11.19", "eslint": "^10.0.3", "jiti": "^2.6.1", "jsdom": "^28.1.0", "offline-geocode-city": "^1.0.2", "panic-overlay": "^1.0.51", "sharp": "^0.35.3", "tailwindcss": "^4.2.1", "typescript": "^5.9.3", "typescript-eslint": "^8.57.0", "vite": "^8.1.2", "yaml": "^2.8.2" }, "peerDependencies": { "@playwright/test": "^1.58.0" }, "optionalPeers": ["@playwright/test"], "bin": { "edge-apps-scripts": "bin/edge-apps-scripts.js" } }, "sha512-+tMvSLoZQ/rmkmrY+gmlQpFpWN4NgRa+TDQN83pDyo6xg/2tC2DQuZoHwl6xTSZ2P4q2mIhhqeC6+VV+Ltaktg=="],
"@sentry/browser": ["@sentry/browser@10.67.0", "", { "dependencies": { "@sentry/browser-utils": "10.67.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.67.0", "@sentry/feedback": "10.67.0", "@sentry/replay": "10.67.0", "@sentry/replay-canvas": "10.67.0" } }, "sha512-/ZhsAvte4rYhg0A0RtSFFgAgXhyMOfQIeOAfMfptN+X6IVSYOfkA9jtrP+Ej4+6vlaUFWRir1HweF56y63dEEA=="],
@@ -876,11 +880,11 @@
"saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
- "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+ "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
- "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
+ "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -978,9 +982,19 @@
"@jsheaven/easybuild/typescript": ["typescript@4.9.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="],
+ "@parcel/core/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
+ "@parcel/node-resolver-core/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
+ "@parcel/package-manager/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
"@parcel/source-map/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
- "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
+ "@parcel/transformer-babel/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
+ "@parcel/transformer-js/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
+ "@parcel/transformer-postcss/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
"@screenly/edge-apps/jsdom": ["jsdom@28.1.0", "", { "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.8.1", "@bramus/specificity": "^2.4.2", "@exodus/bytes": "^1.11.0", "cssstyle": "^6.0.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug=="],
@@ -1002,6 +1016,8 @@
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+ "@typescript-eslint/typescript-estree/semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
"@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
diff --git a/index.html b/index.html
index dd09491..dc9f4c3 100644
--- a/index.html
+++ b/index.html
@@ -6,40 +6,85 @@
Airtable - Screenly Edge App
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
Something went wrong
-
+
+
+
+
+ Something went wrong
+
+
The Airtable data could not be loaded. Please check your
configuration or contact your administrator.
-
diff --git a/package.json b/package.json
index 92eb4b1..570aab8 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
"prettier": "./.prettierrc.json",
"devDependencies": {
"@playwright/test": "^1.59.1",
- "@screenly/edge-apps": "^1.2.1",
+ "@screenly/edge-apps": "^1.4.0",
"@types/bun": "^1.3.9",
"@types/jsdom": "^28.0.1",
"bun-types": "^1.3.9",
diff --git a/screenshots/grid-1080x1920.webp b/screenshots/grid-1080x1920.webp
index f0553ac..48b7bcb 100644
Binary files a/screenshots/grid-1080x1920.webp and b/screenshots/grid-1080x1920.webp differ
diff --git a/screenshots/grid-1280x720.webp b/screenshots/grid-1280x720.webp
index 220d957..fac24f3 100644
Binary files a/screenshots/grid-1280x720.webp and b/screenshots/grid-1280x720.webp differ
diff --git a/screenshots/grid-1920x1080.webp b/screenshots/grid-1920x1080.webp
index 1e44352..55522af 100644
Binary files a/screenshots/grid-1920x1080.webp and b/screenshots/grid-1920x1080.webp differ
diff --git a/screenshots/grid-2160x3840.webp b/screenshots/grid-2160x3840.webp
index 9374bd7..3dfa463 100644
Binary files a/screenshots/grid-2160x3840.webp and b/screenshots/grid-2160x3840.webp differ
diff --git a/screenshots/grid-2160x4096.webp b/screenshots/grid-2160x4096.webp
index 259c498..c1d7168 100644
Binary files a/screenshots/grid-2160x4096.webp and b/screenshots/grid-2160x4096.webp differ
diff --git a/screenshots/grid-3840x2160.webp b/screenshots/grid-3840x2160.webp
index b28df52..8671af0 100644
Binary files a/screenshots/grid-3840x2160.webp and b/screenshots/grid-3840x2160.webp differ
diff --git a/screenshots/grid-4096x2160.webp b/screenshots/grid-4096x2160.webp
index 4978575..0ddc281 100644
Binary files a/screenshots/grid-4096x2160.webp and b/screenshots/grid-4096x2160.webp differ
diff --git a/screenshots/grid-480x800.webp b/screenshots/grid-480x800.webp
index a71fb1e..704c7e1 100644
Binary files a/screenshots/grid-480x800.webp and b/screenshots/grid-480x800.webp differ
diff --git a/screenshots/grid-720x1280.webp b/screenshots/grid-720x1280.webp
index ef710d7..bafe06e 100644
Binary files a/screenshots/grid-720x1280.webp and b/screenshots/grid-720x1280.webp differ
diff --git a/screenshots/grid-800x480.webp b/screenshots/grid-800x480.webp
index ef2dc42..804df58 100644
Binary files a/screenshots/grid-800x480.webp and b/screenshots/grid-800x480.webp differ
diff --git a/screenshots/kanban-1080x1920.webp b/screenshots/kanban-1080x1920.webp
index 0ca9dd9..e8e91c1 100644
Binary files a/screenshots/kanban-1080x1920.webp and b/screenshots/kanban-1080x1920.webp differ
diff --git a/screenshots/kanban-1280x720.webp b/screenshots/kanban-1280x720.webp
index f5a6020..b43969e 100644
Binary files a/screenshots/kanban-1280x720.webp and b/screenshots/kanban-1280x720.webp differ
diff --git a/screenshots/kanban-1920x1080.webp b/screenshots/kanban-1920x1080.webp
index 72edfac..076b951 100644
Binary files a/screenshots/kanban-1920x1080.webp and b/screenshots/kanban-1920x1080.webp differ
diff --git a/screenshots/kanban-2160x3840.webp b/screenshots/kanban-2160x3840.webp
index 476c658..d2131a0 100644
Binary files a/screenshots/kanban-2160x3840.webp and b/screenshots/kanban-2160x3840.webp differ
diff --git a/screenshots/kanban-2160x4096.webp b/screenshots/kanban-2160x4096.webp
index 35c534d..109c07d 100644
Binary files a/screenshots/kanban-2160x4096.webp and b/screenshots/kanban-2160x4096.webp differ
diff --git a/screenshots/kanban-3840x2160.webp b/screenshots/kanban-3840x2160.webp
index 3e85a20..786b9e0 100644
Binary files a/screenshots/kanban-3840x2160.webp and b/screenshots/kanban-3840x2160.webp differ
diff --git a/screenshots/kanban-4096x2160.webp b/screenshots/kanban-4096x2160.webp
index 8e62b55..1d51ab4 100644
Binary files a/screenshots/kanban-4096x2160.webp and b/screenshots/kanban-4096x2160.webp differ
diff --git a/screenshots/kanban-480x800.webp b/screenshots/kanban-480x800.webp
index 44901d8..cbb7a8d 100644
Binary files a/screenshots/kanban-480x800.webp and b/screenshots/kanban-480x800.webp differ
diff --git a/screenshots/kanban-720x1280.webp b/screenshots/kanban-720x1280.webp
index ad2e540..b57d10d 100644
Binary files a/screenshots/kanban-720x1280.webp and b/screenshots/kanban-720x1280.webp differ
diff --git a/screenshots/kanban-800x480.webp b/screenshots/kanban-800x480.webp
index acd52bc..420ffe5 100644
Binary files a/screenshots/kanban-800x480.webp and b/screenshots/kanban-800x480.webp differ
diff --git a/src/app.ts b/src/app.ts
index 44252ea..98ae10a 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -73,10 +73,11 @@ export function createErrorReporter(displayErrors: boolean): ErrorReporter {
export function createPillsContainer(pills: Pill[]): HTMLDivElement {
const container = document.createElement('div')
- container.className = 'cell-pills'
+ container.className = 'cell-pills flex flex-nowrap gap-1 items-center'
pills.forEach((pill) => {
const span = document.createElement('span')
- span.className = 'pill'
+ span.className =
+ 'pill inline-flex items-center px-[0.6rem] py-[0.2rem] rounded-2xl text-xs font-medium whitespace-nowrap'
span.textContent = pill.label
const colors = (pill.color && AIRTABLE_COLORS[pill.color]) || PILL_FALLBACK
span.style.backgroundColor = colors.bg
@@ -101,8 +102,12 @@ export function renderTable(headers: string[], rows: CellValue[][]): void {
}
const headerRow = document.createElement('tr')
+ headerRow.className =
+ 'bg-[linear-gradient(rgba(255,255,255,0.15),rgba(255,255,255,0.15))] bg-[length:calc(100%-4rem)_1px] bg-center bg-no-repeat'
headers.forEach((header) => {
const th = document.createElement('th')
+ th.className =
+ 'text-left py-5 px-8 text-xs font-semibold tracking-[0.08em] uppercase text-[#9d9d9f] whitespace-nowrap portrait:py-4 portrait:px-5 portrait:text-[0.7rem]'
th.textContent = header
headerRow.appendChild(th)
})
@@ -110,8 +115,12 @@ export function renderTable(headers: string[], rows: CellValue[][]): void {
rows.forEach((row) => {
const tr = document.createElement('tr')
+ tr.className =
+ '[&:not(:last-child)]:bg-[linear-gradient(rgba(255,255,255,0.15),rgba(255,255,255,0.15))] [&:not(:last-child)]:bg-[length:calc(100%-4rem)_1px] [&:not(:last-child)]:bg-center [&:not(:last-child)]:bg-no-repeat'
row.forEach((cell) => {
const td = document.createElement('td')
+ td.className =
+ 'py-[1.125rem] px-8 text-lg font-normal text-[#dadadb] whitespace-nowrap align-middle portrait:py-[0.875rem] portrait:px-5 portrait:text-base'
if (typeof cell === 'string') {
td.textContent = cell
} else {
diff --git a/src/css/grid.css b/src/css/grid.css
deleted file mode 100644
index 5d7b850..0000000
--- a/src/css/grid.css
+++ /dev/null
@@ -1,71 +0,0 @@
-.table-card {
- background: rgba(255, 255, 255, 0.08);
- border: 1px solid rgba(255, 255, 255, 0.12);
- border-radius: 1.5rem;
- overflow: hidden;
- backdrop-filter: blur(20px);
- box-shadow:
- 0 1.25rem 3rem rgba(0, 0, 0, 0.4),
- 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
-}
-
-#data-table {
- width: 100%;
- border-collapse: collapse;
-}
-
-#data-table thead tr {
- background-image: linear-gradient(
- rgba(255, 255, 255, 0.15),
- rgba(255, 255, 255, 0.15)
- );
- background-size: calc(100% - 4rem) 1px;
- background-position: center bottom;
- background-repeat: no-repeat;
-}
-
-#data-table th {
- padding: 1.25rem 2rem;
- text-align: left;
- font-size: 0.75rem;
- font-weight: 600;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- color: var(--color-text-muted);
- white-space: nowrap;
-}
-
-#data-table td {
- padding: 1.125rem 2rem;
- font-size: 1.125rem;
- font-weight: 400;
- color: var(--color-text-secondary);
- white-space: nowrap;
- vertical-align: middle;
-}
-
-#data-table tbody tr {
- background-image: linear-gradient(
- rgba(255, 255, 255, 0.15),
- rgba(255, 255, 255, 0.15)
- );
- background-size: calc(100% - 4rem) 1px;
- background-position: center bottom;
- background-repeat: no-repeat;
-}
-
-#data-table tbody tr:last-child {
- background-image: none;
-}
-
-@media (orientation: portrait) {
- #data-table th {
- padding: 1rem 1.25rem;
- font-size: 0.7rem;
- }
-
- #data-table td {
- padding: 0.875rem 1.25rem;
- font-size: 1rem;
- }
-}
diff --git a/src/css/kanban.css b/src/css/kanban.css
deleted file mode 100644
index 9181093..0000000
--- a/src/css/kanban.css
+++ /dev/null
@@ -1,145 +0,0 @@
-#kanban-container {
- flex: 1;
- min-height: 0;
- overflow: hidden;
-}
-
-#kanban-board {
- display: flex;
- gap: 1.25rem;
- height: 100%;
- overflow-x: auto;
- overflow-y: hidden;
- align-items: flex-start;
- padding-bottom: 0.5rem;
-}
-
-#kanban-board::-webkit-scrollbar {
- height: 0.375rem;
-}
-
-#kanban-board::-webkit-scrollbar-track {
- background: transparent;
-}
-
-#kanban-board::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.2);
- border-radius: 1rem;
-}
-
-.kanban-column {
- flex: 0 0 17rem;
- max-height: 100%;
- display: flex;
- flex-direction: column;
- background: rgba(255, 255, 255, 0.06);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 1rem;
- overflow: hidden;
-}
-
-.kanban-column-header {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.875rem 1rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-.kanban-column-dot {
- flex-shrink: 0;
- width: 0.625rem;
- height: 0.625rem;
- border-radius: 50%;
-}
-
-.kanban-column-title {
- flex: 1;
- font-size: 0.75rem;
- font-weight: 600;
- letter-spacing: 0.07em;
- text-transform: uppercase;
- color: var(--color-text-muted);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-.kanban-column-count {
- flex-shrink: 0;
- font-size: 0.7rem;
- font-weight: 500;
- color: var(--color-text-muted);
- background: rgba(255, 255, 255, 0.1);
- padding: 0.1rem 0.45rem;
- border-radius: 1rem;
-}
-
-.kanban-cards {
- flex: 1;
- min-height: 0;
- overflow-y: auto;
- padding: 0.75rem;
- display: flex;
- flex-direction: column;
- gap: 0.625rem;
-}
-
-.kanban-cards::-webkit-scrollbar {
- width: 0.25rem;
-}
-
-.kanban-cards::-webkit-scrollbar-track {
- background: transparent;
-}
-
-.kanban-cards::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.15);
- border-radius: 1rem;
-}
-
-.kanban-card {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- padding: 0.75rem;
- background: rgba(255, 255, 255, 0.07);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 0.625rem;
-}
-
-.kanban-card-title {
- margin: 0;
- font-size: 0.875rem;
- font-weight: 500;
- color: var(--color-text-primary);
- line-height: 1.4;
- word-break: break-word;
-}
-
-.kanban-card-field {
- display: flex;
- flex-direction: column;
- gap: 0.2rem;
-}
-
-.kanban-card-field-label {
- font-size: 0.65rem;
- font-weight: 600;
- letter-spacing: 0.06em;
- text-transform: uppercase;
- color: var(--color-text-muted);
-}
-
-.kanban-card-field-value {
- font-size: 0.75rem;
- color: var(--color-text-secondary);
- word-break: break-word;
-}
-
-@media (orientation: portrait) {
- .kanban-column {
- flex: 0 0 14rem;
- }
-}
diff --git a/src/css/style.css b/src/css/style.css
index 7e37d9b..ea94d3b 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -1,206 +1,10 @@
-@import '@screenly/edge-apps/styles';
-
-:root {
- --color-surface-low: #08080d;
- --color-text-primary: #f2f2f3;
- --color-text-secondary: #dadadb;
- --color-text-muted: #9d9d9f;
- --color-error: #ff6b6b;
- --color-error-bg: rgba(255, 80, 80, 0.08);
- --color-error-border: rgba(255, 80, 80, 0.2);
- --card-radius: 1.5rem;
-}
-
-* {
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
- padding: 0;
- overflow: hidden;
- color: var(--color-text-primary);
- font-family: 'Inter', system-ui, sans-serif;
- background: url('/static/images/bg.webp') no-repeat center center;
- background-size: cover;
- background-color: var(--color-surface-low);
-}
-
-body::before {
- content: '';
- position: fixed;
- inset: 0;
- z-index: var(--z-index-backdrop);
- pointer-events: none;
- background: rgba(0, 0, 0, 0.45);
- backdrop-filter: blur(7px);
-}
-
-auto-scaler {
- position: relative;
- z-index: var(--z-index-content);
-}
-
-#app {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.header {
- flex-shrink: 0;
- background: rgba(255, 255, 255, 0.08);
- border: 1px solid rgba(255, 255, 255, 0.12);
- box-shadow:
- 0 1.25rem 3rem rgba(0, 0, 0, 0.4),
- 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
-}
-
-.content {
- flex: 1;
- display: flex;
- justify-content: center;
- align-items: flex-start;
- padding: 2rem 3rem;
- min-height: 0;
- overflow: hidden;
-}
-
-#table-wrapper {
- width: 100%;
- max-width: 100rem;
- max-height: 100%;
- display: flex;
- flex-direction: column;
- gap: 3rem;
- min-height: 0;
-}
-
-#table-title {
- font-size: 2rem;
- font-weight: 600;
- color: var(--color-text-primary);
- margin: 0;
- letter-spacing: -0.03em;
- text-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.4);
- flex-shrink: 0;
-}
-
-.cell-pills {
- display: flex;
- flex-wrap: nowrap;
- gap: 0.25rem;
- align-items: center;
-}
-
-.pill {
- display: inline-flex;
- align-items: center;
- padding: 0.2rem 0.6rem;
- border-radius: 1rem;
- font-size: 0.75rem;
- font-weight: 500;
- white-space: nowrap;
-}
-
-#error-screen {
- width: 100%;
- align-self: stretch;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.error-card {
- background: rgba(255, 255, 255, 0.05);
- border: 0.0625rem solid var(--color-error-border);
- border-radius: var(--card-radius);
- display: flex;
- flex-direction: row;
- overflow: hidden;
- max-width: 60rem;
- width: 100%;
- backdrop-filter: blur(20px);
- box-shadow:
- 0 1.25rem 3rem rgba(0, 0, 0, 0.4),
- 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
-}
-
-.error-card-left {
- background: var(--color-error-bg);
- border-right: 0.0625rem solid var(--color-error-border);
- padding: 2.5rem 2rem;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- gap: 1rem;
- flex: 0 0 20rem;
-}
-
-.error-card-left h2 {
- font-size: 2rem;
- font-weight: 700;
- line-height: 1.2;
- letter-spacing: -0.03em;
- color: var(--color-error);
- margin: 0;
-}
-
-.error-card-left p {
- font-size: 1rem;
- color: var(--color-text-secondary);
- line-height: 1.6;
- margin: 0;
-}
-
-.error-card-right {
- flex: 1;
- padding: 2.5rem 2rem;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- gap: 0.75rem;
-}
-
-.error-detail-label {
- font-size: 0.75rem;
- font-weight: 600;
- color: var(--color-text-muted);
- text-transform: uppercase;
- letter-spacing: 0.08em;
-}
-
-.error-detail-message {
- font-size: 1rem;
- color: var(--color-text-secondary);
- line-height: 1.6;
- word-break: break-word;
- margin: 0;
-}
-
-@media (orientation: portrait) {
- .content {
- padding: 1.5rem 2rem;
- align-items: flex-start;
- }
-
- #table-wrapper {
- max-width: 100%;
- }
-
- #table-title {
- font-size: 1.75rem;
- }
-
- .error-card {
- flex-direction: column;
- max-width: 100%;
- }
-
- .error-card-left {
- flex: none;
- border-right: none;
- border-bottom: 0.0625rem solid var(--color-error-border);
- }
-}
+/* Tailwind CSS, skipping Preflight (resets border/margin/padding on every
+ element via a universal selector and breaks custom-element hosts like
+
). @screenly/edge-apps/styles goes in the `base` layer so any
+ Tailwind utility classes added later always win the cascade, regardless
+ of selector specificity. */
+@layer theme, base, utilities;
+
+@import 'tailwindcss/theme.css' layer(theme);
+@import '@screenly/edge-apps/styles' layer(base);
+@import 'tailwindcss/utilities.css' layer(utilities);
diff --git a/src/kanban.ts b/src/kanban.ts
index ab00d01..4b64603 100644
--- a/src/kanban.ts
+++ b/src/kanban.ts
@@ -15,12 +15,14 @@ function buildCard(
primaryField: AirtableField | undefined,
): HTMLDivElement {
const card = document.createElement('div')
- card.className = 'kanban-card'
+ card.className =
+ 'kanban-card flex flex-col gap-2 p-3 bg-white/[0.07] border border-white/10 rounded-[0.625rem]'
const primaryVal = record.fields[primaryField?.name ?? '']
if (primaryVal !== null && primaryVal !== undefined && primaryVal !== '') {
const cardTitle = document.createElement('p')
- cardTitle.className = 'kanban-card-title'
+ cardTitle.className =
+ 'kanban-card-title m-0 text-sm font-medium text-[#f2f2f3] leading-[1.4] break-words'
cardTitle.textContent = String(primaryVal)
card.appendChild(cardTitle)
}
@@ -33,18 +35,20 @@ function buildColumnHeader(
color: string | undefined,
): HTMLDivElement {
const header = document.createElement('div')
- header.className = 'kanban-column-header'
+ header.className =
+ 'kanban-column-header flex-shrink-0 flex items-center gap-2 py-3.5 px-4 border-b border-white/[0.08]'
if (color) {
const colors = AIRTABLE_COLORS[color] || PILL_FALLBACK
const dot = document.createElement('span')
- dot.className = 'kanban-column-dot'
+ dot.className = 'kanban-column-dot flex-shrink-0 w-2.5 h-2.5 rounded-full'
dot.style.backgroundColor = colors.bg
header.appendChild(dot)
}
const titleEl = document.createElement('span')
- titleEl.className = 'kanban-column-title'
+ titleEl.className =
+ 'kanban-column-title flex-1 text-xs font-semibold tracking-[0.07em] uppercase text-[#9d9d9f] whitespace-nowrap overflow-hidden text-ellipsis'
titleEl.textContent = name || 'No Status'
header.appendChild(titleEl)
@@ -58,11 +62,13 @@ function buildColumn(
primaryField: AirtableField | undefined,
): HTMLDivElement {
const column = document.createElement('div')
- column.className = 'kanban-column'
+ column.className =
+ 'kanban-column flex-[0_0_17rem] max-h-full flex flex-col bg-white/[0.06] border border-white/10 rounded-2xl overflow-hidden portrait:flex-[0_0_14rem]'
column.appendChild(buildColumnHeader(name, color))
const cardsEl = document.createElement('div')
- cardsEl.className = 'kanban-cards'
+ cardsEl.className =
+ 'kanban-cards flex-1 min-h-0 overflow-y-auto p-3 flex flex-col gap-2.5 [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/15 [&::-webkit-scrollbar-thumb]:rounded-2xl'
colRecords.forEach((record) => {
cardsEl.appendChild(buildCard(record, primaryField))
})
diff --git a/src/main.ts b/src/main.ts
index aaaec73..e47c3ad 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,6 +1,4 @@
import './css/style.css'
-import './css/grid.css'
-import './css/kanban.css'
import '@screenly/edge-apps/components'
import {
getCredentials,