diff --git a/assets/static/js/main.ts b/assets/static/js/main.ts
index 4765504..18968d2 100644
--- a/assets/static/js/main.ts
+++ b/assets/static/js/main.ts
@@ -4,7 +4,7 @@
// Side-effect import: installs the replaceChildren shim for the older-browser
// degraded mode. Must stay first so the shim is in place before any render.
-import './polyfills'
+import '@screenly-labs/signage-kit/polyfills'
import { computeState, pad2, parseTarget } from './timer'
diff --git a/assets/static/js/polyfills.ts b/assets/static/js/polyfills.ts
deleted file mode 100644
index 4262c22..0000000
--- a/assets/static/js/polyfills.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-// Minimal runtime shim for the older-browser degraded mode. esbuild lowers modern
-// *syntax* (?., ??, spread) at build time, but it does not add missing runtime
-// APIs. The one DOM gap the apps can hit at the support floor is
-// Element.prototype.replaceChildren (Chrome 86 / Safari 14, 2020); it's included
-// as a defensive shim even in apps that don't call it directly. Everything else
-// the apps use (fetch, Intl, URLSearchParams) predates the floor, so no core-js.
-//
-// Imported only for its side effect (installing the shim) as the first line of
-// main.ts; it exports nothing.
-
-if (typeof Element !== 'undefined' && !Element.prototype.replaceChildren) {
- Element.prototype.replaceChildren = function replaceChildren(
- this: Element,
- ...nodes: (Node | string)[]
- ): void {
- // Build the new children in a fragment first so a bad node throws before we
- // touch the element, keeping the swap effectively atomic like the native API.
- const frag = document.createDocumentFragment()
- frag.append(...nodes)
- while (this.firstChild) this.removeChild(this.firstChild)
- this.appendChild(frag)
- }
-}
diff --git a/assets/static/styles/tailwind.css b/assets/static/styles/tailwind.css
index 64b6906..8f93957 100644
--- a/assets/static/styles/tailwind.css
+++ b/assets/static/styles/tailwind.css
@@ -9,6 +9,10 @@
========================================================================= */
@import 'tailwindcss';
+/* Shared base (brand tokens + fluid root + the degraded-mode kill-switch) from
+ @screenly-labs/signage-kit. The app's own @theme + component styles below layer
+ on top; the design identity stays here, per-app. */
+@import '@screenly-labs/signage-kit/styles/preset.css';
/* Design tokens — also generate utilities (bg-ink, text-mint, font-display…). */
@theme {
@@ -228,13 +232,9 @@
}
/* =========================================================================
- Degraded mode — old/weak signage players (html.legacy set by the inline gate
- in index.html). Assume the device can't afford animation; drop it entirely.
+ Degraded mode — old/weak signage players (html.legacy set by the shared gate).
+ The generic kill-switch (drops animation/transition/will-change) now comes from
+ the kit's preset, imported above. The clock ticks in JS and the only CSS motion
+ is the one-off .timer entrance, which the kill-switch already disables — there's
+ no animated resting state to hold, so this app needs no extra html.legacy rule.
========================================================================= */
-html.legacy *,
-html.legacy *::before,
-html.legacy *::after {
- animation: none !important;
- transition: none !important;
- will-change: auto !important;
-}
diff --git a/build.js b/build.js
index 7be0882..e3c90c0 100644
--- a/build.js
+++ b/build.js
@@ -14,22 +14,16 @@
// dist/ is gitignored; CI uploads it as the Pages artifact.
import { rm, mkdir, cp, readFile, writeFile } from 'node:fs/promises'
-import cascadeLayers from '@csstools/postcss-cascade-layers'
-import browserslist from 'browserslist'
-import { build as esbuild } from 'esbuild'
-import { browserslistToTargets, transform as lightningcss } from 'lightningcss'
-import postcss from 'postcss'
+import { bundleJs, injectGate, processCss } from '@screenly-labs/signage-kit/build'
import { run as syncFonts } from './sync-fonts.js'
const DIST = 'dist'
const DOMAIN = 'timer.srly.io'
-// The `browserslist` field in package.json is the CSS support floor: Lightning
-// CSS down-levels the stylesheet to it. The JS is lowered separately by esbuild to
-// a fixed ES2017 syntax floor (kept at/below the browserslist minimum); esbuild
-// can't read browserslist, so keep the two in sync if you change the floor. See
-// the degraded-mode notes in index.html / tailwind.css.
-const cssTargets = browserslistToTargets(browserslist())
+// The degraded-mode support floor, the CSS down-leveling recipe (cascade-layers
+// flatten + Lightning CSS), the JS bundler, and the inline degraded-mode gate all
+// come from @screenly-labs/signage-kit. This file only orchestrates the
+// app-specific steps.
// 1. Vendor the Bun-managed webfonts into ./assets before copying.
await syncFonts()
@@ -44,16 +38,15 @@ await mkdir(`${DIST}/static/styles`, { recursive: true })
await mkdir(`${DIST}/static/js`, { recursive: true })
await cp('assets/static/fonts', `${DIST}/static/fonts`, { recursive: true })
await cp('assets/static/images', `${DIST}/static/images`, { recursive: true })
-await cp('index.html', `${DIST}/index.html`)
+// Copy the page shell with the shared degraded-mode gate injected before the
+// stylesheet so it runs before first paint.
+await writeFile(`${DIST}/index.html`, injectGate(await readFile('index.html', 'utf8')))
// Signage app manifest served at /.well-known/signage-app.json (see the
// app-store's docs/app-manifest.md). GitHub Pages returns it as application/json
// with Access-Control-Allow-Origin: * so the store and players can fetch it.
await cp('.well-known', `${DIST}/.well-known`, { recursive: true })
-// 3. Tailwind: compile the source CSS (unminified), then down-level + minify it
-// for the browserslist floor. cascade-layers flattens @layer into :not(#\#)
-// specificity so the cascade survives on engines that drop @layer contents;
-// Lightning CSS then lowers color-mix()/nesting, adds prefixes, and minifies.
+// 3. Tailwind -> the kit's CSS pipeline (flatten @layer, down-level to the floor).
const cssOut = `${DIST}/static/styles/main.css`
const tailwind = Bun.spawn(
[
@@ -70,42 +63,23 @@ if ((await tailwind.exited) !== 0) {
process.exit(1)
}
try {
- const flattened = await postcss([cascadeLayers()]).process(await readFile(cssOut, 'utf8'), {
- from: cssOut
- })
- const { code: cssCode } = lightningcss({
- filename: cssOut,
- code: Buffer.from(flattened.css),
- minify: true,
- targets: cssTargets
- })
- await writeFile(cssOut, cssCode)
-} catch (err) {
+ await writeFile(cssOut, await processCss(await readFile(cssOut, 'utf8'), { flattenLayers: true, filename: cssOut }))
+} catch (error) {
console.error(`✗ CSS build failed (${cssOut})`)
- console.error(err)
+ console.error(error)
process.exit(1)
}
-console.log(`✓ CSS: ${cssOut} (Tailwind → cascade-layers flatten → Lightning CSS)`)
+console.log(`✓ CSS: ${cssOut}`)
-// 4. TypeScript → browser JS with esbuild. Bundles main.ts (inlining ./timer and
-// the polyfills shim), lowers modern syntax (?., ??, spread) to the ES2017 floor
-// so old engines can parse it, and emits an IIFE so the output stays a
-// self-contained self-executing classic script loadable from a plain
+
diff --git a/package.json b/package.json
index 4e8966c..ff9c781 100644
--- a/package.json
+++ b/package.json
@@ -13,15 +13,10 @@
"dev": "bun run build.js && bunx serve dist"
},
"license": "AGPL-3.0-only",
- "browserslist": [
- "chrome >= 87",
- "firefox >= 78",
- "safari >= 14.1",
- "edge >= 87"
- ],
"dependencies": {
"@fontsource-variable/bricolage-grotesque": "^5.2.5",
- "@fontsource-variable/hanken-grotesk": "^5.2.8"
+ "@fontsource-variable/hanken-grotesk": "^5.2.8",
+ "@screenly-labs/signage-kit": "github:Screenly-Labs/signage-kit#2026.7.0"
},
"devDependencies": {
"@biomejs/biome": "^2.5.1",