@514sid/signagekit 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +44 -0
  3. package/README.md +106 -2
  4. package/bin/signagekit.js +4 -0
  5. package/dist/chunks/chart-WERjiRho.js +2 -0
  6. package/dist/chunks/empty--6iNLsra.js +2 -0
  7. package/dist/chunks/icon-paths-Dl0Rqja7.js +2 -0
  8. package/dist/chunks/lists-qOUpM5us.js +2 -0
  9. package/dist/chunks/measures-D0av_G5P.js +2 -0
  10. package/dist/chunks/media-DI35kQ5D.js +3 -0
  11. package/dist/chunks/qr-size-Dq23do6_.js +2 -0
  12. package/dist/chunks/ready-Boq20M-G.js +2 -0
  13. package/dist/chunks/share-D64VSp1B.js +2 -0
  14. package/dist/chunks/stat-DEE3meII.js +2 -0
  15. package/dist/chunks/time-KYYBrpD9.js +2 -0
  16. package/dist/cli.js +10 -0
  17. package/dist/index.js +3 -0
  18. package/dist/style.css +2 -0
  19. package/dist/types/alert.d.ts +20 -0
  20. package/dist/types/binds.d.ts +6 -0
  21. package/dist/types/chart.d.ts +16 -0
  22. package/dist/types/check.d.ts +31 -0
  23. package/dist/types/checks.d.ts +36 -0
  24. package/dist/types/colors.d.ts +18 -0
  25. package/dist/types/components.d.ts +13 -0
  26. package/dist/types/cycle-fit.d.ts +6 -0
  27. package/dist/types/data.d.ts +19 -0
  28. package/dist/types/empty.d.ts +6 -0
  29. package/dist/types/fit.d.ts +3 -0
  30. package/dist/types/flow.d.ts +7 -0
  31. package/dist/types/icon-paths.d.ts +2 -0
  32. package/dist/types/icon.d.ts +7 -0
  33. package/dist/types/icons.d.ts +3 -0
  34. package/dist/types/index.d.ts +20 -0
  35. package/dist/types/lists.d.ts +29 -0
  36. package/dist/types/measure.d.ts +4 -0
  37. package/dist/types/measures.d.ts +22 -0
  38. package/dist/types/media.d.ts +15 -0
  39. package/dist/types/mount.d.ts +45 -0
  40. package/dist/types/parse.d.ts +11 -0
  41. package/dist/types/qr-size.d.ts +2 -0
  42. package/dist/types/ready.d.ts +3 -0
  43. package/dist/types/share.d.ts +33 -0
  44. package/dist/types/sizes.d.ts +15 -0
  45. package/dist/types/specs.d.ts +7 -0
  46. package/dist/types/stat.d.ts +38 -0
  47. package/dist/types/table-fit.d.ts +8 -0
  48. package/dist/types/table.d.ts +11 -0
  49. package/dist/types/time.d.ts +28 -0
  50. package/dist/types/trend-fit.d.ts +14 -0
  51. package/dist/types/type-scale.d.ts +10 -0
  52. package/dist/types/types.d.ts +271 -0
  53. package/dist/types/validate-layout.d.ts +2 -0
  54. package/dist/types/validate.d.ts +7 -0
  55. package/dist/types/version.d.ts +24 -0
  56. package/dist/types/views.d.ts +29 -0
  57. package/docs/api.md +106 -0
  58. package/docs/assistants.md +55 -0
  59. package/docs/format.md +183 -0
  60. package/docs/getting-started.md +77 -0
  61. package/docs/preview.md +84 -0
  62. package/examples/clinic-queue/data.json +17 -0
  63. package/examples/clinic-queue/layout.yml +43 -0
  64. package/examples/fire-alarm/data.yml +23 -0
  65. package/examples/fire-alarm/layout.yml +30 -0
  66. package/examples/gym-classes/data.yml +17 -0
  67. package/examples/gym-classes/layout.yml +47 -0
  68. package/examples/hotel-lobby/data.yml +2 -0
  69. package/examples/hotel-lobby/layout.yml +29 -0
  70. package/examples/lunch/data.yml +16 -0
  71. package/examples/lunch/layout.yml +43 -0
  72. package/examples/meeting-room/data.json +19 -0
  73. package/examples/meeting-room/layout.json +107 -0
  74. package/examples/noc-wall/data.yml +18 -0
  75. package/examples/noc-wall/layout.yml +63 -0
  76. package/examples/oee-line/data.yml +25 -0
  77. package/examples/oee-line/layout.yml +55 -0
  78. package/examples/office-air/data.yml +25 -0
  79. package/examples/office-air/layout.yml +71 -0
  80. package/examples/sales-today/data.json +28 -0
  81. package/examples/sales-today/layout.json +62 -0
  82. package/examples/snow-closure/data.yml +28 -0
  83. package/examples/snow-closure/layout.json +35 -0
  84. package/examples/station-departures/data.json +24 -0
  85. package/examples/station-departures/layout.json +34 -0
  86. package/examples/store-promo/data.yml +18 -0
  87. package/examples/store-promo/layout.yml +54 -0
  88. package/examples/storm-banner/data.json +20 -0
  89. package/examples/storm-banner/layout.json +33 -0
  90. package/examples/trading-floor/data.json +23 -0
  91. package/examples/trading-floor/layout.json +65 -0
  92. package/examples/warehouse/data.json +20 -0
  93. package/examples/warehouse/layout.json +50 -0
  94. package/package.json +100 -4
  95. package/schema.json +962 -0
package/docs/format.md ADDED
@@ -0,0 +1,183 @@
1
+ # Document format
2
+
3
+ A screen is one document, JSON or YAML, plus an optional data object. The document holds the shape of the screen; the data holds anything that changes: values, states, list rows, messages. Something that updates the screen then only rewrites the data.
4
+
5
+ [schema.json](../schema.json) has every property with its default; editors that read JSON Schema autocomplete and check documents against it.
6
+
7
+ ## Example
8
+
9
+ ```yaml
10
+ # yaml-language-server: $schema=https://signagekit.514sid.com/schema.json
11
+ colors:
12
+ scheme: dark
13
+ accent: '#2563eb'
14
+
15
+ layout:
16
+ design: landscape
17
+ flow:
18
+ - { zone: head, span: 9, height: 150px }
19
+ - { zone: clock, span: 3, height: 150px }
20
+ - { zone: menu, span: 8, height: 3 }
21
+ - { zone: side, span: 4, height: 3 }
22
+
23
+ zones:
24
+ head: { type: heading, text: Today's lunch }
25
+ clock: { type: clock, show_date: true }
26
+ menu:
27
+ { type: table, title: Menu, columns: [Dish, Price], rows: { bind: /menu } }
28
+ side: { type: list, title: Notices, items: { bind: /notices } }
29
+ ```
30
+
31
+ [examples/lunch](../examples/lunch/) has this screen in full. [examples/](../examples/) has more, one folder per screen with its layout and data.
32
+
33
+ ## Layout
34
+
35
+ The canvas is 1080 px on the screen's short side and follows its shape, so a 4:3, 21:9 or portrait screen is never letterboxed. All sizes in a document are px on that canvas.
36
+
37
+ A `flow` layout lists zones in reading order and is designed once, for landscape or portrait (`design`, default landscape). `span` is the zone's width in the design orientation, out of 12 columns (default 12). `height` is a share of the free height, like CSS `fr` (default 1), or fixed pixels such as `150px`.
38
+
39
+ On a screen of another shape each zone keeps about its designed width. On a narrower screen zones take more columns and move to new rows; on a wider one they shrink up to a quarter to share a row. Every row fills the full width, and zones in a row take the tallest height in it. A fixed-height strip, like a header or footer, never shares a row with zones that grow. On a screen of the design's own orientation, a row whose spans add up to 12 stays exactly as written.
40
+
41
+ For a screen that must stay exactly as drawn, `areas` takes a fixed CSS grid instead (`areas`, `columns`, `rows`). It doesn't reflow for other shapes.
42
+
43
+ Zone names use lowercase letters, digits, `-` and `_`, and start with a letter. A zone in `zones` that the layout doesn't place is a warning; a name in the layout without a zone is left empty.
44
+
45
+ ## Components
46
+
47
+ Every component can also take `title` (a small label above it), `icon` (shown before the title), `surface` (`base`, `raised` or `accent`), `size` and `weight` (see [Adjusting by hand](#adjusting-by-hand)).
48
+
49
+ | Type | Shows | Required |
50
+ | ----------- | -------------------------------------------------------------------------------------- | -------------------------- |
51
+ | `heading` | Large title filling its zone | `text` |
52
+ | `text` | Paragraph; `fit: true` fills the zone, else `size` s, m, l or xl | `text` |
53
+ | `stat` | Big number with `unit`, `label`, `state`, `thresholds`, `trend`, `spark` | `value` |
54
+ | `meter` | One value against `max` (default 100) with a bar | `value` |
55
+ | `meters` | One row per item, value against limit, bars lined up | `items` |
56
+ | `bars` | Ranked horizontal bars, largest first | `items` |
57
+ | `split` | One whole in up to six parts, shares printed | `items` |
58
+ | `chart` | Line, area or column chart, with optional `target` line | `kind`, `labels`, `series` |
59
+ | `table` | Rows and columns; pages when rows don't fit | `columns`, `rows` |
60
+ | `list` | Bulleted list | `items` |
61
+ | `status` | Rows with a state icon each, like service health | `items` |
62
+ | `schedule` | Entries from the current one on, with Now and Next marked | `items` |
63
+ | `queue` | "Now serving" number and the recently called ones | `current` |
64
+ | `clock` | Current time, optionally the date | |
65
+ | `clocks` | One clock per time zone | `zones` |
66
+ | `countdown` | Time left until `to`; shows `done` (default "Now") once it has passed | `to` |
67
+ | `cycle` | One-line messages that swap instantly on a timer, never scrolling | `items` |
68
+ | `image` | Image from an https URL | `src` |
69
+ | `slideshow` | Images swapped instantly on a timer | `images` |
70
+ | `qr` | QR code, black on white so phones can scan it | `value` |
71
+ | `stack` | Splits a zone into parts holding other components; `direction` row or column (default) | `items` |
72
+
73
+ The schema has every property, with defaults.
74
+
75
+ ## Data
76
+
77
+ ### Binding
78
+
79
+ Any value can be a literal or a bind: `{ bind: /menu/0/2 }` reads key `menu`, item 0, item 2. Add `fallback` to show something when the path is missing or empty: `{ bind: /wait, fallback: Ask at the desk }`.
80
+
81
+ Bound lists take the same shape as their literal form:
82
+
83
+ | Component | Each item |
84
+ | ---------------------------- | --------------------------------- |
85
+ | `table.rows` | an array of cells |
86
+ | `list`, `cycle`, `slideshow` | a string |
87
+ | `meters`, `bars`, `split` | `{ label, value, max?, state? }` |
88
+ | `status` | `{ label, state, note? }` |
89
+ | `schedule` | `{ start, end?, title, detail? }` |
90
+ | chart `labels` and `values` | a string or number per point |
91
+
92
+ ### Missing and empty values
93
+
94
+ Nothing breaks and nothing renders as a blank box:
95
+
96
+ - A missing single value (a stat, a meter, a queue number, a table cell) shows a dash, unless its bind has a `fallback`.
97
+ - Empty list items are skipped, and an empty list, table, chart, cycle or schedule shows "Nothing to show" (or similar).
98
+ - A countdown past its time shows its `done` text.
99
+ - `check()`, `mount()` and the `signagekit check` command warn about every bind whose path isn't in the data.
100
+
101
+ ### Numbers and text
102
+
103
+ YAML data is read with every value as text, so a price written as `4.50` stays `4.50`. JSON data keeps its numbers. Either way, components that need numbers (meters, bars, charts, thresholds) read numeric text like `1,240` as a number, and stats shorten values from 10,000 up (`format: full` shows every digit).
104
+
105
+ A YAML layout uses the core schema, so `true`, `24` and `+6` are a boolean and numbers. Quote layout values that must stay text: `delta: '+0.04'` keeps its plus sign. In YAML generally, quote any value that starts with `%`, `#`, `@` or `*`, and any value containing `: ` or, inside `{ }` and `[ ]`, a comma.
106
+
107
+ ## How text is sized
108
+
109
+ You don't set sizes; signagekit does, after every render, for the zone sizes on the actual screen:
110
+
111
+ - Small zones get tighter padding, and short strips put their title beside the content, so content always has room.
112
+ - Text grows to fill its zone, then snaps to a type scale. Row-based components (tables, lists, status, meters, bars, schedules, world clocks) share one body size across the screen. Stats and countdowns in a row share a size, so values and labels line up. Headings and cycles stay within a few times the body size.
113
+ - Text never goes below a readable size (22 px). Lists that don't fit drop whole rows from the end and say "+N more"; tables page; a table too wide truncates its widest column with an ellipsis; long titles and labels end in an ellipsis.
114
+ - A row of three or more narrow stats in a stack wraps into two columns.
115
+
116
+ ## Adjusting by hand
117
+
118
+ Every automatic choice can be overridden:
119
+
120
+ | Property | On | Does |
121
+ | ------------------ | ------------------ | ----------------------------------------------------------------------- |
122
+ | `size` | any component | Pins the text size in px and skips automatic sizing for that zone |
123
+ | `weight` | parts of a `stack` | Share of the space relative to siblings, like 1 and 3 (default 1) |
124
+ | `match_row: false` | `stat` | Keeps its own size instead of matching its row |
125
+ | `format: full` | `stat` | Shows every digit instead of 1.2M |
126
+ | `page_rows` | `table` | Most rows per page (pages still shrink if fewer fit) |
127
+ | `widths` | `table` | Relative column widths, like `[3, 1, 1]`; long cells end in an ellipsis |
128
+ | `y_min`, `y_max` | `chart` | Fixed y-axis range |
129
+ | `limit` | `bars` | Rows to show (default 12) |
130
+
131
+ ## States, thresholds and trends
132
+
133
+ `stat` and `meter` take `state`: `neutral`, `good`, `warning` or `danger`, literal or bound. A state adds a colored strip across the top of the zone (down its left edge in short zones) and a badge by the label with a check, an exclamation mark or a cross, so it never relies on color alone. In short zones, where the label may not fit, the badge moves into the title line. The label defaults to Good, Warning or Danger.
134
+
135
+ `thresholds` sets the state from the value, so data only carries numbers: `{ warning: 80, danger: 95 }` shows warning from 80 and danger from 95. With danger below warning, lower is worse: `{ warning: 40, danger: 20 }` for a battery. An explicit `state` wins. On `meters`, thresholds apply to each row without its own state.
136
+
137
+ A `stat` can show a trend arrow: `trend` is `up`, `down` or `flat`, `delta` is text beside it like `+12%`, and `good_when` (`up` or `down`) colors the arrow green when the move is good news and red when it isn't. Without `good_when` the arrow stays neutral. `spark` adds recent values as a small line under the number.
138
+
139
+ ## Charts
140
+
141
+ `chart` draws line, area and column charts with [uPlot](https://github.com/leeoniya/uPlot), loaded only on screens that have one. Series take colors from a fixed order checked for color blindness and contrast on both schemes. Two or more series get a legend with each series' latest value; column charts print each value on its bar. `target` draws a labelled line, like a goal or an SLO, and stays in view. Column and area charts start at zero; `y_min` and `y_max` fix the range. Charts don't animate and have no hover, since screens aren't interactive.
142
+
143
+ ## Alerts
144
+
145
+ One `alert` block covers every screen-wide message, from an evacuation to a maintenance notice:
146
+
147
+ ```yaml
148
+ alert:
149
+ level: danger # info, good (all clear), warning or danger
150
+ icon: fire # names the situation; see the icon list below
151
+ title: Fire alarm # short, shown very large
152
+ message: Leave the building now by the nearest exit.
153
+ steps: # numbered instructions
154
+ - Do not use the lifts
155
+ - Gather at assembly point B
156
+ footer: Wait for the all clear.
157
+ mode: takeover # or banner: a strip across the top, screen stays visible
158
+ until: '2026-11-14T15:00:00Z' # optional: stops showing after this time
159
+ ```
160
+
161
+ It shows while its `title` or `message` is set. Bind the whole alert to the data, `alert: { bind: /alert }`, and an assistant raises or clears it by writing the data alone: an object with the fields above shows it, and `null` or an empty object brings the screen back. Unknown levels show as danger and unknown icons fall back to the level's icon, so a bad value never hides an alert. The older `text` and `state` fields still work as `message` and `level`.
162
+
163
+ ### Icons
164
+
165
+ Alerts and zone titles share one set of icons from [Lucide](https://lucide.dev): `info`, `check`, `warning`, `danger`, `fire`, `evacuate`, `assembly`, `medical`, `first-aid`, `storm`, `wind`, `snow`, `flood`, `heat`, `lockdown`, `shelter`, `security`, `power`, `water`, `network`, `closed`, `delay`, `maintenance`, `announcement`, `phone`, `biohazard`, `radiation`, `chemical`, `event`, `location` and `accessibility`. Any zone can set `icon` to show one before its title in place of the square marker. Storybook shows them all under Components → Alerts → Icons.
166
+
167
+ ## Colors
168
+
169
+ All colors come from the `colors` block as hex values: `background`, `surface`, `text`, `muted`, `line`, `accent`, `on_accent`, and the state colors `good`, `warning` and `danger`. `scheme` (`dark` or `light`) fills in whatever is left out; without it the `scheme` passed to `mount()` decides (default dark), so set it when a screen must look the same everywhere. `accent` defaults to the accent passed to `mount()`, else `#2563eb`, and `on_accent` to whichever of near-black and white reads better on it. signagekit warns when text has less than 4.5:1 contrast with its background, and when a state color leaves its icon under 3:1.
170
+
171
+ ## Versions
172
+
173
+ `version` says which format a document is written in. Leaving it out means format 1, the current one; writing `version: 1` keeps the document unambiguous when the format changes later.
174
+
175
+ When the format changes, signagekit keeps reading the old one. A document in an older format is upgraded on load, one version at a time, and any property with no place in the new format is reported as a warning. Each upgrade step is its own small file, loaded only for documents that need it, so current documents load no upgrade code.
176
+
177
+ A document in a newer format than the library reads is shown as far as it can be. Everything the library knows renders; newer properties and component types are skipped with warnings, and the first warning names the format the document was written for. A screen therefore never goes blank because a player runs an older version.
178
+
179
+ The data has no version of its own. Its shape follows the binds in the layout.
180
+
181
+ ## Errors
182
+
183
+ Errors drop only what they affect: a broken layout renders nothing, a broken zone is left out and the rest of the screen renders, and warnings never block rendering. Every message names the exact path, like `zones.menu.rows: expected an array or a bind`.
@@ -0,0 +1,77 @@
1
+ # Getting started
2
+
3
+ signagekit turns a short document into a screen. The document names the zones of the screen and what each one shows; a separate data object holds the values that change. signagekit lays the zones out for the screen's shape and sizes all the text together.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @514sid/signagekit
9
+ ```
10
+
11
+ ## A first screen
12
+
13
+ Write the layout. This one has a heading, a clock, a table and a stat:
14
+
15
+ ```yaml
16
+ # lunch.layout.yml
17
+ layout:
18
+ flow:
19
+ - { zone: head, span: 9, height: 150px }
20
+ - { zone: clock, span: 3, height: 150px }
21
+ - { zone: menu, span: 8 }
22
+ - { zone: wait, span: 4 }
23
+ zones:
24
+ head: { type: heading, text: Today's lunch }
25
+ clock: { type: clock, show_date: true }
26
+ menu: { type: table, columns: [Dish, Price], rows: { bind: /menu } }
27
+ wait: { type: stat, title: Wait time, value: { bind: /wait }, unit: min }
28
+ ```
29
+
30
+ Then the data. Each `{ bind: /path }` in the layout reads a value from it:
31
+
32
+ ```yaml
33
+ # lunch.data.yml
34
+ menu:
35
+ - [Tomato soup, 4.50]
36
+ - [Chickpea curry, 7.90]
37
+ - [Grilled halloumi wrap, 6.50]
38
+ wait: 6
39
+ ```
40
+
41
+ Mount it into an element. The screen fills the element, so size the element the way the screen should be:
42
+
43
+ ```js
44
+ import { mount } from '@514sid/signagekit'
45
+ import '@514sid/signagekit/style.css'
46
+
47
+ const screen = await mount(document.getElementById('screen'), {
48
+ layout: await fetch('lunch.layout.yml').then((r) => r.text()),
49
+ data: await fetch('lunch.data.yml').then((r) => r.text()),
50
+ })
51
+ ```
52
+
53
+ When the values change, update the data and leave the layout alone:
54
+
55
+ ```js
56
+ await screen.update({ data: { menu, wait: 12 } })
57
+ ```
58
+
59
+ ## Check it
60
+
61
+ ```bash
62
+ npx @514sid/signagekit check lunch.layout.yml lunch.data.yml
63
+ ```
64
+
65
+ The check lists every error and warning with its exact path: unknown properties, low contrast, and binds that find nothing in the data. It exits with 1 on errors.
66
+
67
+ ## See it without a display
68
+
69
+ ```bash
70
+ npx @514sid/signagekit url lunch.layout.yml lunch.data.yml --mode sizes
71
+ ```
72
+
73
+ This prints a link to the [preview tool](preview.md) with the screen packed into it. Open it to see the screen at five resolutions at once, from Full HD to portrait and ultrawide, or point a headless browser at it for a screenshot.
74
+
75
+ ## Next
76
+
77
+ The [document format](format.md) covers every layout option and component. [Writing with assistants](assistants.md) has the rules to give an AI that writes screens for you. The [API](api.md) lists every option of `mount()` and `check()`.
@@ -0,0 +1,84 @@
1
+ # Preview tool
2
+
3
+ The preview page renders a screen from its URL, so a person can share a screen as a link and an AI assistant can screenshot what it built. Everything it needs is in the URL; nothing is uploaded.
4
+
5
+ ```bash
6
+ npx @514sid/signagekit url screen.layout.yml screen.data.yml --mode sizes
7
+ ```
8
+
9
+ prints a link like `https://signagekit.514sid.com/preview/?mode=sizes#s=rVXfb9s2...`. The layout and data are compressed into the part after `#s=`, which browsers never send to a server.
10
+
11
+ ## Modes
12
+
13
+ | `mode` | Shows |
14
+ | --------- | ----------------------------------------------------------------------------------------------------------------- |
15
+ | `screen` | One screen. With `size`, at that resolution; without, the window is the screen. The default. |
16
+ | `sizes` | The screen at several resolutions side by side, to check that a flow layout adapts. Problems are listed. |
17
+ | `schemes` | The screen in the dark and the light scheme side by side. |
18
+ | `edit` | An editor for the layout and data with the screen beside it, and a resolution menu. The link updates as you type. |
19
+
20
+ ## Parameters
21
+
22
+ | Parameter | Example | Does |
23
+ | ------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------ |
24
+ | `#s=` | from `signagekit url` | The layout and data, compressed. Use this one when you can. |
25
+ | `layout`, `data` | `?layout=%7B...%7D` | The layout and data as URL-encoded JSON or YAML, for short documents. |
26
+ | `layout_url`, `data_url` | `?layout_url=https://...` | Fetch the layout and data from URLs. The server must allow cross-origin requests. |
27
+ | `example` | `noc-wall` | One of the [examples](../examples/). |
28
+ | `mode` | `sizes` | See above. |
29
+ | `size` | `portrait` or `fullhd,1366x768` | Resolutions, by name or as `WIDTHxHEIGHT`. One for `screen`, `schemes` and `edit`; a list for `sizes`. |
30
+ | `frame` | `480` | In `sizes` and `schemes`, the long side of each screen on the page in px. Default 640. |
31
+ | `scheme` | `light` | Shows the screen in this scheme, even when the document sets its own. |
32
+ | `accent` | `%23e11d48` | Accent when the document doesn't set one. Encode `#` as `%23`. |
33
+ | `tz`, `locale` | `Europe/Berlin`, `de-DE` | Time zone and locale for clocks, dates and schedules. |
34
+ | `now` | `2026-11-14T09:30` | Freezes the clock, so clocks, schedules and countdowns come out the same every time. |
35
+ | `problems` | `1` | In `screen` mode, show errors and warnings over the screen. |
36
+
37
+ ## Resolutions
38
+
39
+ Every screen renders at its real resolution and is scaled down to fit, so text sizes come out exactly as on that display. Any `WIDTHxHEIGHT` works, from 64 to 16384 px a side. These have names:
40
+
41
+ | Name | Resolution | Also |
42
+ | -------------- | ---------- | ----------------- |
43
+ | `hd` | 1280×720 | `720p` |
44
+ | `fullhd` | 1920×1080 | `1080p`, `fhd` |
45
+ | `qhd` | 2560×1440 | `1440p` |
46
+ | `4k` | 3840×2160 | `uhd`, `2160p` |
47
+ | `hd-portrait` | 720×1280 | |
48
+ | `portrait` | 1080×1920 | `fullhd-portrait` |
49
+ | `4k-portrait` | 2160×3840 | |
50
+ | `xga` | 1024×768 | 4:3 |
51
+ | `xga-portrait` | 768×1024 | 3:4 |
52
+ | `sxga` | 1280×1024 | 5:4 |
53
+ | `ultrawide` | 2560×1080 | 21:9 |
54
+ | `bar` | 1920×540 | 32:9 strip |
55
+ | `square` | 1080×1080 | |
56
+
57
+ Without `size`, `sizes` mode shows `fullhd`, `portrait`, `xga`, `ultrawide` and `square`. The library exports the same list as `SIZES`, with `parseSize()` to read either form.
58
+
59
+ ## Taking screenshots
60
+
61
+ The page sets `data-status` on `<html>` to `ready` once every screen on it is drawn, charts included, or to `error` when the layout couldn't be read. Wait for it before taking the screenshot. The same result is on `window.signagekit` as `{ status, errors, warnings }`, and every error and warning is also logged to the console.
62
+
63
+ For a screenshot of one screen at its real pixels, use `screen` mode without `size` and make the window that size:
64
+
65
+ ```js
66
+ await page.setViewportSize({ width: 1080, height: 1920 })
67
+ await page.goto(url)
68
+ await page.waitForSelector(
69
+ 'html[data-status="ready"], html[data-status="error"]',
70
+ )
71
+ await page.screenshot({ path: 'screen.png' })
72
+ ```
73
+
74
+ With Chrome alone, give the page a few seconds of virtual time:
75
+
76
+ ```bash
77
+ chrome --headless --window-size=1920,1080 --virtual-time-budget=5000 --screenshot=screen.png "$URL"
78
+ ```
79
+
80
+ When you can't set the window size, pass `size` instead: the screen lays out for that resolution and scales to fit the window. `sizes` mode needs a window about 1400 px wide and a full-page screenshot.
81
+
82
+ ## Running your own
83
+
84
+ The site is static. Build it with `bun run site:build` and serve `site/dist` from anywhere; pass `--base https://your.host/preview/` to `signagekit url`, or `base` to `previewUrl()`, to link to your copy.
@@ -0,0 +1,17 @@
1
+ {
2
+ "current": "B 047",
3
+ "recent": ["A 112", "B 046", "A 111", "B 045"],
4
+ "wait": 18,
5
+ "clinics": [
6
+ { "start": "10:00", "end": "12:00", "title": "Diabetes review", "detail": "Room 4, Nurse Hughes" },
7
+ { "start": "12:00", "end": "14:00", "title": "Walk-in minor illness", "detail": "Room 1, Dr Okafor" },
8
+ { "start": "14:00", "end": "16:00", "title": "Child immunisations", "detail": "Room 3, Nurse Patel" },
9
+ { "start": "16:00", "end": "17:30", "title": "Flu jab drop-in", "detail": "Room 4, Nurse Hughes" },
10
+ { "start": "17:30", "end": "18:00", "title": "Prescription pick-up", "detail": "Reception" }
11
+ ],
12
+ "messages": [
13
+ "Please keep your ticket until called",
14
+ "Masks available at reception",
15
+ "Flu jab drop-in 16:00 to 17:30 today"
16
+ ]
17
+ }
@@ -0,0 +1,43 @@
1
+ # yaml-language-server: $schema=https://signagekit.514sid.com/schema.json
2
+ # Elm Park Surgery waiting room. Light scheme for a bright room, ticket
3
+ # numbers only so no patient names are on the wall.
4
+ colors:
5
+ scheme: light
6
+ accent: '#0f766e'
7
+
8
+ layout:
9
+ design: landscape
10
+ flow:
11
+ - { zone: head, span: 8, height: 170px }
12
+ - { zone: clock, span: 4, height: 170px }
13
+ - { zone: serving, span: 5, height: 4 }
14
+ - { zone: clinics, span: 7, height: 4 }
15
+ - { zone: ticker, height: 110px }
16
+
17
+ zones:
18
+ head: { type: heading, text: Elm Park Surgery, icon: medical }
19
+ clock: { type: clock, surface: raised, hour12: false, show_date: true }
20
+ serving:
21
+ type: stack
22
+ items:
23
+ - type: queue
24
+ weight: 4
25
+ label: Now calling
26
+ current: { bind: /current }
27
+ recent: { bind: /recent }
28
+ - type: stat
29
+ weight: 2
30
+ title: Average wait
31
+ value: { bind: /wait }
32
+ unit: min
33
+ thresholds: { warning: 20, danger: 40 }
34
+ clinics:
35
+ type: schedule
36
+ title: Clinics today
37
+ rows: 5
38
+ items: { bind: /clinics }
39
+ ticker:
40
+ type: cycle
41
+ surface: accent
42
+ seconds: 8
43
+ items: { bind: /messages }
@@ -0,0 +1,23 @@
1
+ hours:
2
+ - [Maternity, '14:00 - 20:00']
3
+ - [Surgical, '15:00 - 19:00']
4
+ - [Paediatrics, '10:00 - 20:00']
5
+ - [Cardiology, '14:00 - 18:00']
6
+ notices:
7
+ - Two visitors per bed
8
+ - Use the hand gel at every ward door
9
+ - Parking validation at Reception
10
+
11
+ alert:
12
+ level: danger
13
+ icon: fire
14
+ title: Fire alarm
15
+ message: 'Fire reported on Level 2, East Wing. Leave the building by the nearest exit.'
16
+ steps:
17
+ - Do not use the lifts
18
+ - Follow the green exit signs
19
+ - Staff will guide patients and visitors
20
+ - Gather in Car Park B, away from the entrance
21
+ footer: 'Do not re-enter until a fire officer gives the all clear. Updated 14:32'
22
+ mode: takeover
23
+ until: '2026-11-15T00:00:00Z'
@@ -0,0 +1,30 @@
1
+ # yaml-language-server: $schema=https://signagekit.514sid.com/schema.json
2
+ # St. Mary's Hospital main entrance: visitor information, with a fire alarm
3
+ # takeover bound to /alert. Clear the alert by setting /alert to null.
4
+ colors:
5
+ scheme: light
6
+ accent: '#0b5cad'
7
+
8
+ layout:
9
+ design: landscape
10
+ flow:
11
+ - { zone: head, span: 9, height: 170px }
12
+ - { zone: clock, span: 3, height: 170px }
13
+ - { zone: hours, span: 7, height: 2 }
14
+ - { zone: info, span: 5, height: 2 }
15
+
16
+ zones:
17
+ head: { type: heading, text: Welcome to St. Mary's }
18
+ clock: { type: clock, surface: raised, show_date: true }
19
+ hours:
20
+ type: table
21
+ title: Visiting hours
22
+ columns: [Ward, Hours]
23
+ align: [start, end]
24
+ rows: { bind: /hours }
25
+ info:
26
+ type: list
27
+ title: Visitor notices
28
+ items: { bind: /notices }
29
+
30
+ alert: { bind: /alert }
@@ -0,0 +1,17 @@
1
+ # Leeds is on GMT in November, so Z matches local time.
2
+ next_at: '2026-11-14T14:00:00Z'
3
+ next_label: Spin Power, Studio 1
4
+ spots_left: 3
5
+ spots_label: Book in the app
6
+ classes:
7
+ - ['14:00', Spin Power, Studio 1, Dani, 3 left]
8
+ - ['14:30', Yoga Flow, Studio 2, Mei, 8 left]
9
+ - ['15:15', HIIT 45, Studio 1, Marcus, 11 left]
10
+ - ['16:00', Pilates Core, Studio 2, Sofia, Full]
11
+ - ['17:00', Boxfit, Studio 3, Jayden, 6 left]
12
+ - ['17:30', Spin Endurance, Studio 1, Dani, 9 left]
13
+ - ['18:00', Stretch and Restore, Studio 2, Mei, 14 left]
14
+ messages:
15
+ - Towels at the front desk
16
+ - Late cancellations after 2 hours are charged
17
+ - Sauna closed for cleaning 15:00 to 15:30
@@ -0,0 +1,47 @@
1
+ # yaml-language-server: $schema=https://signagekit.514sid.com/schema.json
2
+ # Pulse Fitness Leeds, reception wall. Landscape, dark with a hot accent.
3
+ colors:
4
+ scheme: dark
5
+ accent: '#f97316'
6
+
7
+ layout:
8
+ design: landscape
9
+ flow:
10
+ - { zone: head, span: 8, height: 170px }
11
+ - { zone: clock, span: 4, height: 170px }
12
+ - { zone: classes, span: 8, height: 4 }
13
+ - { zone: next, span: 4, height: 4 }
14
+ - { zone: ticker, height: 110px }
15
+
16
+ zones:
17
+ head: { type: heading, text: Classes today }
18
+ clock: { type: clock, surface: raised, hour12: false }
19
+ classes:
20
+ type: table
21
+ columns: [Time, Class, Studio, Coach, Spots]
22
+ align: [start, start, start, start, end]
23
+ widths: [1.1, 2.6, 1.4, 1.8, 1.3]
24
+ page_rows: 7
25
+ seconds: 12
26
+ rows: { bind: /classes }
27
+ next:
28
+ type: stack
29
+ items:
30
+ - type: countdown
31
+ weight: 2
32
+ title: Next class
33
+ label: { bind: /next_label }
34
+ to: { bind: /next_at }
35
+ done: Starting now
36
+ - type: stat
37
+ weight: 2
38
+ title: Studio 1 spots
39
+ value: { bind: /spots_left }
40
+ unit: left
41
+ thresholds: { warning: 5, danger: 2 }
42
+ label: { bind: /spots_label }
43
+ ticker:
44
+ type: cycle
45
+ surface: raised
46
+ seconds: 8
47
+ items: { bind: /messages }
@@ -0,0 +1,2 @@
1
+ guest_url: https://guest.harborhouse.example
2
+ desk_note: Front desk is staffed 24 hours. Dial 0 from any room phone for anything you need, including a late checkout or a taxi.
@@ -0,0 +1,29 @@
1
+ # yaml-language-server: $schema=https://signagekit.514sid.com/schema.json
2
+ # Sparse lobby screen: four zones, light scheme, landscape.
3
+ colors:
4
+ scheme: light
5
+ accent: '#0f5132'
6
+
7
+ layout:
8
+ design: landscape
9
+ flow:
10
+ - { zone: head, span: 12, height: 200px }
11
+ - { zone: time, span: 4, height: 1 }
12
+ - { zone: guide, span: 4, height: 1 }
13
+ - { zone: desk, span: 4, height: 1 }
14
+
15
+ zones:
16
+ head: { type: heading, title: Lobby, text: Welcome to Harbor House, align: start }
17
+ time: { type: clock, title: Local time, surface: raised, show_date: true, hour12: false }
18
+ guide:
19
+ type: qr
20
+ title: Guest guide
21
+ value: { bind: /guest_url }
22
+ label: Wi-Fi, room service and checkout
23
+ desk:
24
+ type: text
25
+ title: Front desk
26
+ surface: accent
27
+ text: { bind: /desk_note }
28
+ fit: true
29
+ align: start
@@ -0,0 +1,16 @@
1
+ # Every value in YAML data is read as text, so 4.50 stays "4.50".
2
+ menu:
3
+ - [Tomato soup, VG, 4.50]
4
+ - [Chicken curry, GF, 8.90]
5
+ - [Mushroom risotto, V, 8.20]
6
+ - [Fish and chips, '', 9.50]
7
+ - [Falafel wrap, VG, 6.80]
8
+ wait: 6
9
+ seats: 42
10
+ notices:
11
+ - 'Fire drill Thursday at 10:00'
12
+ - New vegan station opens Monday
13
+ messages:
14
+ - 'Coffee bar open until 16:00'
15
+ - 'Card payments only after 14:00'
16
+ - Reusable cups get 20p off
@@ -0,0 +1,43 @@
1
+ # yaml-language-server: $schema=https://signagekit.514sid.com/schema.json
2
+ # A canteen screen: the menu, wait time, free seats and notices. Designed for
3
+ # landscape; on a portrait screen the zones move onto rows of their own.
4
+ colors:
5
+ scheme: dark
6
+ accent: '#2563eb'
7
+
8
+ layout:
9
+ design: landscape
10
+ flow:
11
+ - { zone: head, span: 9, height: 170px }
12
+ - { zone: clock, span: 3, height: 170px }
13
+ - { zone: menu, span: 6, height: 2 }
14
+ - { zone: info, span: 6, height: 2 }
15
+ - { zone: news, height: 110px }
16
+
17
+ zones:
18
+ head: { type: heading, text: Today's lunch }
19
+ clock: { type: clock, surface: raised, show_date: true }
20
+ menu:
21
+ type: table
22
+ title: Menu
23
+ columns: [Dish, Diet, Price]
24
+ align: [start, start, end]
25
+ rows: { bind: /menu }
26
+ info:
27
+ type: stack
28
+ items:
29
+ - type: stack
30
+ direction: row
31
+ items:
32
+ - { type: stat, title: Wait time, value: { bind: /wait }, unit: min }
33
+ - { type: stat, title: Seats free, value: { bind: /seats } }
34
+ - type: stack
35
+ direction: row
36
+ items:
37
+ - { type: list, title: Notices, items: { bind: /notices } }
38
+ - type: text
39
+ surface: accent
40
+ title: Allergens
41
+ fit: true
42
+ text: Ask at the counter for allergen information.
43
+ news: { type: cycle, surface: raised, items: { bind: /messages }, seconds: 8 }
@@ -0,0 +1,19 @@
1
+ {
2
+ "room": {
3
+ "name": "Ada Lovelace",
4
+ "floor": "Floor 3, east wing",
5
+ "book_url": "https://rooms.northwind.example/r/ada-lovelace"
6
+ },
7
+ "status": {
8
+ "text": "In use",
9
+ "label": "Busy until 15:00",
10
+ "state": "danger",
11
+ "ends_at": "2026-11-14T15:00:00Z"
12
+ },
13
+ "agenda": [
14
+ { "start": "13:00", "end": "15:00", "title": "Pricing review", "detail": "Organiser: M. Oyelaran" },
15
+ { "start": "15:00", "end": "15:30", "title": "Interview debrief" },
16
+ { "start": "15:30", "end": "16:30", "title": "Sprint planning, Payments squad", "detail": "Organiser: J. Whitcombe-Tarasova" },
17
+ { "start": "17:00", "end": "17:30", "title": "1:1 Priya / Daniel" }
18
+ ]
19
+ }