rshow 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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -2
  3. package/dist/main.js +8500 -0
  4. package/package.json +48 -4
  5. package/skills/rshow/SKILL.md +144 -0
  6. package/skills/rshow/reference/cli.md +263 -0
  7. package/skills/rshow/reference/dashboard.md +122 -0
  8. package/skills/rshow/reference/mockups.md +113 -0
  9. package/skills/rshow/reference/page-contract.md +83 -0
  10. package/skills/rshow/reference/pages.md +174 -0
  11. package/skills/rshow/reference/poster.md +137 -0
  12. package/skills/rshow/reference/project-page.md +132 -0
  13. package/skills/rshow/reference/reports.md +158 -0
  14. package/skills/rshow/reference/sketch.md +78 -0
  15. package/skills/rshow/reference/terminal.md +148 -0
  16. package/skills/rshow/reference/themes.md +139 -0
  17. package/skills/rshow-implement/SKILL.md +112 -0
  18. package/skills/rshow-implement/reference/compare.md +156 -0
  19. package/skills/rshow-implement/reference/handoff.md +125 -0
  20. package/skills/rshow-implement/reference/map.md +151 -0
  21. package/skills/rshow-slides/SKILL.md +136 -0
  22. package/skills/rshow-slides/reference/layouts.md +251 -0
  23. package/themes/default/dashboard.css +521 -0
  24. package/themes/default/dashboard.js +1390 -0
  25. package/themes/default/deck.css +586 -0
  26. package/themes/default/deck.js +741 -0
  27. package/themes/default/example/capture.ans +1 -0
  28. package/themes/default/example/dashboard.html +106 -0
  29. package/themes/default/example/demo.cast +14 -0
  30. package/themes/default/example/drawing.excalidraw +377 -0
  31. package/themes/default/example/index.html +311 -0
  32. package/themes/default/example/mockup-desktop.html +331 -0
  33. package/themes/default/example/mockup-platforms.html +742 -0
  34. package/themes/default/example/mockup.html +416 -0
  35. package/themes/default/example/page.html +153 -0
  36. package/themes/default/example/poster-figure.svg +58 -0
  37. package/themes/default/example/poster.html +181 -0
  38. package/themes/default/example/project.html +229 -0
  39. package/themes/default/example/report.html +404 -0
  40. package/themes/default/example/results.csv +61 -0
  41. package/themes/default/example/sketch.html +178 -0
  42. package/themes/default/example/teaser.svg +66 -0
  43. package/themes/default/example/terminal.html +231 -0
  44. package/themes/default/example/tui.html +223 -0
  45. package/themes/default/fonts/IBMPlexMono-Regular-Latin1.woff2 +0 -0
  46. package/themes/default/fonts/IBMPlexSans-Light-Latin1.woff2 +0 -0
  47. package/themes/default/fonts/IBMPlexSans-Regular-Latin1.woff2 +0 -0
  48. package/themes/default/fonts/IBMPlexSans-SemiBold-Latin1.woff2 +0 -0
  49. package/themes/default/fonts/OFL.txt +93 -0
  50. package/themes/default/fonts.css +63 -0
  51. package/themes/default/index.html +573 -0
  52. package/themes/default/mockup.css +1078 -0
  53. package/themes/default/mockup.js +1433 -0
  54. package/themes/default/mode.js +242 -0
  55. package/themes/default/page.css +320 -0
  56. package/themes/default/poster.css +480 -0
  57. package/themes/default/poster.js +223 -0
  58. package/themes/default/project.css +482 -0
  59. package/themes/default/project.js +116 -0
  60. package/themes/default/report.css +776 -0
  61. package/themes/default/report.js +427 -0
  62. package/themes/default/sketch.css +166 -0
  63. package/themes/default/sketch.js +681 -0
  64. package/themes/default/skins/adwaita.css +669 -0
  65. package/themes/default/skins/custom.css +596 -0
  66. package/themes/default/skins/egui.css +639 -0
  67. package/themes/default/skins/fluent.css +1093 -0
  68. package/themes/default/skins/fluent.js +458 -0
  69. package/themes/default/skins/mac.css +647 -0
  70. package/themes/default/skins/material.css +692 -0
  71. package/themes/default/skins/sketch.css +436 -0
  72. package/themes/default/skins/sketch.js +494 -0
  73. package/themes/default/skins/wire.css +561 -0
  74. package/themes/default/terminal.css +292 -0
  75. package/themes/default/terminal.js +433 -0
  76. package/themes/default/theme.json +149 -0
  77. package/themes/default/tokens.css +255 -0
  78. package/themes/default/tui.css +703 -0
  79. package/themes/default/tui.js +472 -0
  80. package/worker/index.js +6918 -0
  81. package/worker/migrations/0001_init.sql +52 -0
  82. package/worker/migrations/0002_blobs.sql +5 -0
  83. package/worker/migrations/0003_projects.sql +31 -0
  84. package/worker/migrations/0004_page_data_blob.sql +19 -0
@@ -0,0 +1,113 @@
1
+ # Mockups
2
+
3
+ ## Contents
4
+
5
+ - A guided kit
6
+ - Markup
7
+ - Frames
8
+ - Skins
9
+ - One flow per file
10
+ - States
11
+ - Annotations and comments
12
+ - Placeholders
13
+ - Look at it
14
+
15
+ A mockup is a drawing of a product that does not exist yet, so it follows that product and not rshow.
16
+ Decks, reports, posters, project pages and dashboards are fixed templates, while a mockup is a guided kit: the kit gives you a frame, a floor and a checklist, and the drawing is the work.
17
+ `themes/MOCKUPS.md` in the rshow repo holds the kit itself, and this file is what to do with it.
18
+
19
+ ## A guided kit
20
+
21
+ - Start in `wire` every time, because a tidy drawing invites a room to argue about the colour of a button instead of the order of the steps.
22
+ - Move to `sketch` when the room is too polite about a finished looking picture.
23
+ - Move to a platform skin only when the look of the product is the decision.
24
+ - Keep the inventing to layout, spacing, hierarchy and the order of steps.
25
+ Data, copy, names and prices are placeholders and say so.
26
+
27
+ ## Markup
28
+
29
+ ```html
30
+ <main class="mockup" data-skin="wire" data-name="rshow admin" data-store="../_data/mockup-comments">
31
+ <section class="artboard" id="gallery" data-frame="browser" data-title="Gallery"
32
+ data-url="art.example.com/admin" data-states="empty,loading,error"
33
+ data-w="1280" data-h="800">
34
+ <div class="wf-col">...</div>
35
+ </section>
36
+ </main>
37
+ ```
38
+
39
+ Link `fonts.css`, `tokens.css`, `mockup.css` and one skin, then `mode.js` in the head and `mockup.js` and `skins/sketch.js` deferred.
40
+ The skin link carries `id="mk-skin"` and `data-mk-skin`, so the toolbar can swap it.
41
+ `data-flush` drops the default padding of a screen for an app shell that runs to the edges.
42
+ `mockup.js` builds the toolbar, the frame chrome, the tabs, the notes panel and the comments panel.
43
+ With JavaScript off the artboards are a plain column of screens and the skin still draws them.
44
+
45
+ One file is one flow, so a second flow is a second file and a second artifact.
46
+
47
+ ## Frames
48
+
49
+ | `data-frame` | Use |
50
+ |---|---|
51
+ | `browser` | a web app, with `data-url` for the address bar |
52
+ | `phone` | a mobile client |
53
+ | `tablet` | a tablet client |
54
+ | `desktop` | a desktop app, with `data-os` set to `mac`, `windows` or `linux` |
55
+ | `custom` | an app that paints its own title bar, with the drag area marked `drag-region` |
56
+ | `bare` | egui, a game, a kiosk or anything that draws every pixel itself |
57
+
58
+ Electron, Tauri and egui ignore the native conventions of the platform they ship on, so match the product and use `custom` or `bare` rather than forcing a title bar it does not have.
59
+
60
+ ## Skins
61
+
62
+ One skin loads at a time and it styles what is inside a screen, never the tool.
63
+
64
+ | Skin | For |
65
+ |---|---|
66
+ | `wire` | structure, hierarchy and flow, for anything still being argued about |
67
+ | `sketch` | a first idea, a whiteboard or a workshop, where the drawing should look unfinished |
68
+ | `custom` | a product with a look of its own, driven by `--mk-*` tokens |
69
+ | `fluent` | a Windows app, with Fluent's corner radii and its bottom edge focus bar |
70
+ | `mac` | a macOS app, with a hairline separator and a segmented control for tabs |
71
+ | `adwaita` | a GTK 4 or GNOME app, with a header bar and rows that are boxes |
72
+ | `egui` | an immediate mode tool, flat and dark by default with one selection blue |
73
+ | `material` | an Android or Material 3 app, where elevation is a tone and then a shadow |
74
+
75
+ Every skin draws the same `wf-*` kit: `wf-row`, `wf-col`, `wf-card`, `wf-button`, `wf-input`, `wf-tabs`, `wf-table`, `wf-chart` and the rest.
76
+ Learn the kit once and a skin swap restyles every screen.
77
+ The sketch skin fetches rough.js at one pinned version and falls back to wire when it cannot, so a reader never gets a half drawn screen.
78
+
79
+ ## One flow per file
80
+
81
+ Name an artboard by what the person is doing there, such as Gallery, Share dialog or Invite accepted.
82
+ Never name one by the widget that happens to be on it.
83
+ Link the flow with `<a href="#other-id">` or with `data-goto="other-id"`, and play it in Play mode.
84
+ A screen nobody can reach from the entry artboard is a screen to delete or to link.
85
+
86
+ ## States
87
+
88
+ `data-states="empty,loading,error"` puts a tab under the title, and a block marked `data-state="empty"` draws only in that state.
89
+ A block may name more than one state, so `data-state="default,error"` is the list that stays when the error message appears.
90
+ Always cover empty, loading, error, long content and narrow width, for every screen where they can happen.
91
+ A state is where the argument about a mockup usually happens, because the happy path is the picture everybody already agreed on.
92
+
93
+ ## Annotations and comments
94
+
95
+ Annotate a decision, never something a reader can already see.
96
+ A `<span class="note-pin" data-note="Copy link is the one action, the rest supports it">` sits on the screen and the notes panel in focus view collects the pins by number.
97
+ Comments are separate and they are the review: they live in the artifact JSON store named by `data-store`, which defaults to `../_data/mockup-comments`, and the server records who wrote each one.
98
+ A public viewer cannot write, so a comment needs a signed in viewer.
99
+ The store holds 64 KiB, so a comment thread is a thread and not a document.
100
+
101
+ ## Placeholders
102
+
103
+ Layout, spacing, hierarchy and the order of steps are free to invent, because that is the design.
104
+ Data, copy, names, numbers and prices are not, so mark them as placeholders in a note or in an HTML comment.
105
+ Never present invented data as if somebody measured it.
106
+
107
+ ## Look at it
108
+
109
+ - `rshow check <dir>` exits 0, then `rshow preview <dir>`.
110
+ - Look at the canvas, the focus view and Play mode, in both modes, at desktop width and at 360px.
111
+ - Switch the skin in the toolbar and check the drawing still reads.
112
+ - Print puts one artboard on a page with no tool chrome, which is what a review meeting wants on paper.
113
+ - `rshow publish <dir> --slug <slug> --label "<what changed>"`, then read `rshow-implement` before anybody writes code from it.
@@ -0,0 +1,83 @@
1
+ # Page contract
2
+
3
+ Every artifact on rshow follows this contract, whether a person wrote the HTML or a build did.
4
+ `rshow check <dir>` enforces it and exits 6 on any error, and it prints findings as `path:line: error|warning: msg`.
5
+ It reads the files a publish would upload, so for a prerendered site it reads the built output and not the source tree.
6
+
7
+ ## Files
8
+
9
+ - One `index.html` at the root of the folder you publish.
10
+ - Every other path inside the artifact is relative and has no leading slash, such as `chart.svg`.
11
+ - The two root-relative prefixes are `/_themes/<name>@<n>/` and `/_data/`, and both point at the host on purpose.
12
+ - At most 2000 files and 100 MiB per version, and at most 25 MiB per file.
13
+ - No theme file in the artifact.
14
+ No `deck.css`, no `page.css`, no font file, no `assets/` copy of a theme.
15
+
16
+ ## Content Security Policy
17
+
18
+ The host sends these directives, and a request outside them fails in the browser console.
19
+
20
+ | Directive | Allowed sources |
21
+ |---|---|
22
+ | `script-src` | `'self'`, inline, `cdnjs.cloudflare.com`, `cdn.jsdelivr.net`, `unpkg.com` |
23
+ | `style-src` | `'self'`, inline, `fonts.googleapis.com` |
24
+ | `font-src` | `'self'`, `fonts.gstatic.com` |
25
+ | `img-src` | `'self'`, `data:`, `blob:` |
26
+ | `connect-src` | `'self'` |
27
+
28
+ `connect-src` is `'self'` only, so `fetch`, `XMLHttpRequest` and a websocket to any other host are blocked.
29
+ Bake the data into the page as a literal.
30
+ The artifact data store is the other place a value can live.
31
+
32
+ The theme files under `/_themes/` count as `'self'`, so a linked theme needs no policy exception.
33
+
34
+ ## CDN builds, pinned
35
+
36
+ Pin the exact version in the URL and never write `@latest` or a moving tag, which `rshow check` counts as an error.
37
+
38
+ | Library | Pinned URL |
39
+ |---|---|
40
+ | Chart.js 4.4.1 | `https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.js` |
41
+ | D3 7.9.0 | `https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js` |
42
+ | Mermaid 10.9.1 | `https://cdn.jsdelivr.net/npm/mermaid@10.9.1/dist/mermaid.min.js` |
43
+ | Marked 12.0.2 | `https://cdn.jsdelivr.net/npm/marked@12.0.2/marked.min.js` |
44
+ | Lodash 4.17.21 | `https://unpkg.com/lodash@4.17.21/lodash.min.js` |
45
+
46
+ The same file is fine from `cdnjs.cloudflare.com` or `unpkg.com`, as long as the version is in the path.
47
+ A page that needs no library is the better page, so reach for one only when the chart or the diagram is the point.
48
+
49
+ ## The data store
50
+
51
+ `/_data/<key>` is a per artifact JSON store, and it is the only network call a page may make.
52
+ `GET ./_data/<key>` returns `{value}` or a 404, and `PUT` with a body of `{value}` needs a signed-in viewer and takes at most 64 KiB.
53
+
54
+ ```js
55
+ const r1 = await fetch('./_data/settings');
56
+ const value = r1.ok ? (await r1.json()).value : null;
57
+
58
+ await fetch('./_data/settings', {
59
+ method: 'PUT',
60
+ headers: { 'content-type': 'application/json' },
61
+ body: JSON.stringify({ value: { title: 'A talk' } }),
62
+ });
63
+ ```
64
+
65
+ Handle a 401 or a 403 by drawing the page without the stored value, because a public viewer cannot write.
66
+
67
+ ## Phone width
68
+
69
+ The page is read on a phone more often than on a projector.
70
+
71
+ - `page.css` goes single column below 640px, so do not fight it with a fixed width.
72
+ - Wrap every table in `<div class="table">`, which scrolls sideways instead of squeezing.
73
+ - Body text stays at 16px or more, and an input stays at 16px or more so Safari does not zoom.
74
+ - A tap target is at least 40px tall.
75
+ - Look at the page at 360x640 before you publish.
76
+
77
+ ## Nothing secret
78
+
79
+ Anyone who holds the link can read the HTML source, the inline script and every file in the folder.
80
+ So no token, no key, no password, no private URL, no customer name and no internal data.
81
+ Search the folder for `token`, `secret`, `key=`, `password` and `rsh_` before a publish.
82
+
83
+ `rshow check` catches some of these patterns and a `.env` file, and the rest of the search is yours to finish.
@@ -0,0 +1,174 @@
1
+ # Pages
2
+
3
+ ## Contents
4
+
5
+ - Folder shape
6
+ - The skeleton
7
+ - Modes
8
+ - Token overrides
9
+ - Figures and charts
10
+ - Prerendered sites
11
+
12
+ A page is a folder with an `index.html` in it and whatever files that page needs.
13
+ This file holds the skeleton, the token override, the figures and the prerendered shapes.
14
+ Decks are a different skeleton, and `rshow-slides` holds that one.
15
+
16
+ ## Folder shape
17
+
18
+ ```text
19
+ reports/q3/
20
+ index.html
21
+ page.html # optional, a second page of the same artifact
22
+ chart.svg
23
+ ```
24
+
25
+ Publish the folder, not a file, so the extra pages and assets travel with it.
26
+ A single file published on its own becomes `index.html` and nothing else is uploaded.
27
+
28
+ ## The skeleton
29
+
30
+ Replace `<name>@<n>` with the `theme` value that `rshow config <dir>` prints, which the server resolves to the current version. Use `default@3` only when `rshow config` warns that it cannot resolve the theme.
31
+ Every link carries the same version on purpose, because a mixed version renders somewhere between two designs.
32
+
33
+ ```html
34
+ <!doctype html>
35
+ <html lang="en">
36
+ <head>
37
+ <meta charset="utf-8">
38
+ <meta name="viewport" content="width=device-width, initial-scale=1">
39
+ <title>Q3 latency report</title>
40
+ <link rel="stylesheet" href="/_themes/default@3/fonts.css">
41
+ <link rel="stylesheet" href="/_themes/default@3/tokens.css">
42
+ <link rel="stylesheet" href="/_themes/default@3/page.css">
43
+ <script src="/_themes/default@3/mode.js"></script>
44
+ <style>:root{--accent: #0f62fe;}</style>
45
+ </head>
46
+ <body>
47
+ <main>
48
+
49
+ <h1>Latency after the cache change</h1>
50
+ <p class="lede">One sentence that says what this page answers.</p>
51
+
52
+ <h2>What changed</h2>
53
+ <p>Body copy sits at the theme measure. <strong>Bold</strong> is a weight, not a colour.</p>
54
+
55
+ <div class="table">
56
+ <table>
57
+ <caption>Example data, results per configuration</caption>
58
+ <thead><tr><th scope="col">Config</th><th scope="col" class="num-cell">P50</th></tr></thead>
59
+ <tbody><tr><td>baseline</td><td class="num-cell">[PLACEHOLDER]</td></tr></tbody>
60
+ </table>
61
+ </div>
62
+
63
+ <h2>The write path</h2>
64
+ <pre><code>rshow publish ./report --label "first public version"</code></pre>
65
+
66
+ <footer>Source: [PLACEHOLDER]. Written [PLACEHOLDER].</footer>
67
+
68
+ </main>
69
+ </body>
70
+ </html>
71
+ ```
72
+
73
+ `page.css` gives you `lede`, `table`, `num-cell`, `muted`, `small` and `mono`, plus the heading, list, figure, table and code rules.
74
+ Use them and keep your own CSS to the one `:root` block.
75
+ There is no card class, no label class and no number tile.
76
+ A page that needs one is usually a page with a table it has not written yet.
77
+
78
+ The theme carries both modes, so the same page reads on white and on g100 with nothing in the page itself.
79
+
80
+ ## Modes
81
+
82
+ One theme, two modes, system by default.
83
+ `mode.js` goes in the head, not at the end of the body, because it sets `data-mode` before the body paints and stops the flash of a white page on the way to dark.
84
+ It reads the storage key `rshow-mode` (`light`, `dark` or `system`) inside a try/catch, because a private window refuses storage and the page still has to work, and it exposes `window.rshowMode = { get, set, cycle }`.
85
+
86
+ The toggle is a real `<button>` with an aria-label, a 44px target and the Carbon ghost look.
87
+ A page that has a place for it writes the button itself:
88
+
89
+ ```html
90
+ <button type="button" data-rshow-mode-toggle>Mode</button>
91
+ ```
92
+
93
+ Without that element `mode.js` mounts a small fixed button in the bottom right corner, which stays out of print either way.
94
+ A deck shares the key, and `t` cycles the same three modes with the mode shown in the counter.
95
+ Print is always light, whatever the screen was.
96
+
97
+ ## Token overrides
98
+
99
+ Override a token instead of writing rules.
100
+ Five declarations cover most restyling.
101
+
102
+ ```html
103
+ <style>:root{
104
+ --accent: #0f62fe;
105
+ --pad-x: 80px;
106
+ --border-subtle: #e0e0e0;
107
+ }
108
+ @media (prefers-color-scheme: dark) {
109
+ :root:not([data-mode="light"]) { --accent: #78a9ff; }
110
+ }
111
+ :root[data-mode="dark"] { --accent: #78a9ff; }
112
+ </style>
113
+ ```
114
+
115
+ A token that carries text needs a value per mode, because a colour that reads on white is not a colour that reads on g100.
116
+ A token that only fills a shape can be set once.
117
+
118
+ The block goes after the theme links so it wins, and it stays in `index.html` so one artifact stays one folder.
119
+ Change a role, not a rule: a page that sets `--border-subtle` to near black gets heavy table rows, which is the point of a role.
120
+ A new font has to be a linked stylesheet or a system stack, because a `@font-face` in an override needs a font file in the artifact and the contract says no theme files.
121
+ If the look has to change past what an override can carry, read `themes.md` and publish a theme.
122
+
123
+ ## Figures and charts
124
+
125
+ - Draw the figure as inline `<svg>` in the page, with `role="img"` and an `aria-label` that says what it shows.
126
+ - Take the colours from tokens with a style attribute, such as `style="stroke: var(--accent)"` and `style="fill: var(--layer-01)"`, so light and dark both work.
127
+ A `var()` inside a presentation attribute is not reliable, so put it in `style`.
128
+ - Keep the text in the SVG as text, so it stays selectable and readable on a phone.
129
+ - Add a `<figure>` and a `<figcaption>` when the figure carries an argument.
130
+ - Label a line at its end or a bar above its head, so the figure needs no legend box.
131
+ - Draw one series in `var(--accent)`, the same blue as a link, because one series is not a category.
132
+ Two or more series take `--chart-1`, `--chart-2` and so on in order.
133
+ That order holds in every figure in the artifact.
134
+ - Mark example data as example data in the caption, with round numbers and no claim of a measurement.
135
+ - Reach for Mermaid only when the graph changes often, and pin it with the exact version.
136
+ - Never ship a diagram as a screenshot of a slide, because it stops scaling at phone width.
137
+
138
+ A screenshot of a real interface is fine, as a `png` or a `webp` next to the page, with an `alt` that says what to look at.
139
+
140
+ ## Prerendered sites
141
+
142
+ The host serves files.
143
+ There is no Node process, no server rendering, no API route and no edge function, so a framework works only when it writes static files.
144
+
145
+ Astro works when `output` is `'static'` and the interactive part is an island, so everything else ships as HTML.
146
+ Vite with vanilla, Svelte or React works the same way, because all three write a folder.
147
+
148
+ Publish the build output, never the source tree:
149
+
150
+ ```bash
151
+ rshow check dist
152
+ rshow publish . --build "npm run build" --slug status --label "first public build"
153
+ ```
154
+
155
+ `--build` runs the command in `<dir>` through your shell, so the build has to work in that folder with no extra arguments.
156
+ When a build runs, the folder that gets published is `<dir>/<out>`.
157
+ The `out` field defaults to `dist`.
158
+
159
+ Put the same thing in `rshow.json` when it should not be retyped:
160
+
161
+ ```json
162
+ {
163
+ "slug": "status",
164
+ "title": "Cluster status",
165
+ "theme": "default@3",
166
+ "build": "npm run build",
167
+ "out": "dist"
168
+ }
169
+ ```
170
+
171
+ With `rshow.json` in place, `rshow publish .` builds and publishes `dist` with no flags at all.
172
+ `rshow check .` reads the same `rshow.json` and checks `<dir>/<out>`, so run the build once before a check on a clean tree.
173
+
174
+ A prerendered site still has to pass the page contract, and the parts that break it are almost always the same three: an absolute asset path from the framework, an API call in the client bundle and a font loaded from a third party host.
@@ -0,0 +1,137 @@
1
+ # Poster
2
+
3
+ ## Contents
4
+
5
+ - The sheet
6
+ - Skeleton
7
+ - Blocks
8
+ - Figures
9
+ - QR code
10
+ - Print
11
+ - Checks
12
+
13
+ A poster is one sheet that prints from the browser, in the same theme as a page, a report and a project page.
14
+ It is a fixed template, so the sheet, the columns and the print are set and the blocks are the work.
15
+ `poster.css` sets the look and `poster.js` sets the scale, the QR code and the one page print.
16
+
17
+ ## The sheet
18
+
19
+ A sheet is an attribute on the poster and never a second stylesheet.
20
+
21
+ | `data-size` | Size | Columns |
22
+ |---|---|---|
23
+ | `a0` | 841 x 1189 mm, the default | three |
24
+ | `a1` | 594 x 841 mm | three |
25
+ | `36x48` | 3 x 4 ft, shown landscape | four |
26
+ | `48x36` | the same sheet in portrait | three |
27
+
28
+ `?size=a1` in the address picks a sheet without a click, which is what a print check and an automated print use.
29
+ The status line beside the buttons always names the width and the height, so the sheet a print will use is never a guess.
30
+
31
+ ## Skeleton
32
+
33
+ ```html
34
+ <!doctype html>
35
+ <html lang="en">
36
+ <head>
37
+ <meta charset="utf-8">
38
+ <meta name="viewport" content="width=device-width, initial-scale=1">
39
+ <title>Immutable artifacts for reproducible figures</title>
40
+ <link rel="stylesheet" href="/_themes/default@3/fonts.css">
41
+ <link rel="stylesheet" href="/_themes/default@3/tokens.css">
42
+ <link rel="stylesheet" href="/_themes/default@3/poster.css">
43
+ <script src="/_themes/default@3/mode.js"></script>
44
+ <script defer src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.js"></script>
45
+ <script defer src="/_themes/default@3/poster.js"></script>
46
+ </head>
47
+ <body>
48
+
49
+ <div class="poster-toolbar">
50
+ <div class="poster-sizes" role="group" aria-label="Poster size">
51
+ <button type="button" data-poster-size="a0" aria-pressed="true">A0</button>
52
+ <button type="button" data-poster-size="a1" aria-pressed="false">A1</button>
53
+ </div>
54
+ <button type="button" data-poster-print>Print</button>
55
+ <span class="poster-status" data-poster-status>A0, 841 mm wide x 1189 mm tall, three columns</span>
56
+ </div>
57
+
58
+ <div class="poster-stage">
59
+ <div class="poster" data-size="a0">
60
+ <header class="poster-head">
61
+ <h1 class="poster-title">[One line of the claim]</h1>
62
+ <div class="poster-byline">
63
+ <div class="poster-who">
64
+ <p class="poster-authors">[Author]<sup>1</sup>, [Author]<sup>2</sup></p>
65
+ <p class="poster-affil"><sup>1</sup> [Affiliation]</p>
66
+ <p class="poster-venue">[Venue] 2025, poster [n]</p>
67
+ </div>
68
+ <p class="poster-logos">[Logo] [Logo]</p>
69
+ <div class="poster-qr" data-qr="https://art.example.com/my-poster/">
70
+ <p class="poster-qr-url"><a href="https://art.example.com/my-poster/">art.example.com/my-poster</a></p>
71
+ </div>
72
+ </div>
73
+ </header>
74
+
75
+ <main class="poster-cols">
76
+ <section>
77
+ <h2>The problem</h2>
78
+ <p>[PLACEHOLDER] one paragraph.</p>
79
+ </section>
80
+ <section>
81
+ <h2>What we did</h2>
82
+ <p>[PLACEHOLDER] one paragraph and a short list.</p>
83
+ </section>
84
+ <section>
85
+ <h2>What we found</h2>
86
+ <figure>
87
+ <img src="figure.svg" width="720" height="640" alt="[What the figure shows]">
88
+ <figcaption>[One line, with the unit and the source]</figcaption>
89
+ </figure>
90
+ </section>
91
+ </main>
92
+
93
+ <footer class="poster-foot">
94
+ <p>[1] [Reference]. [2] [Reference].</p>
95
+ </footer>
96
+ </div>
97
+ </div>
98
+
99
+ </body>
100
+ </html>
101
+ ```
102
+
103
+ ## Blocks
104
+
105
+ - One `<section>` per block of the argument, in reading order from top left.
106
+ - The title is the claim, and it is short enough to read at 3 metres.
107
+ - Three to five sections is the range, and each one has a heading a reader can say back.
108
+ - Type is in `pt`, because 110pt here is 110pt on paper.
109
+ `--poster-type` carries one multiplier per sheet.
110
+ - A block that only holds a paragraph is a block that wants a figure.
111
+
112
+ ## Figures
113
+
114
+ A figure is the argument on a poster, so it takes the width of its column and it is labelled at the end of the line.
115
+ No legend box, no gridline that carries nothing and no chart with two y axes.
116
+ Every value in an example figure is example data, and the caption says so.
117
+ Never invent a number.
118
+
119
+ ## QR code
120
+
121
+ `data-qr` holds the address the code points at, and `poster.js` draws the code as an inline SVG.
122
+ Keep the plain address in `poster-qr-url` as the fallback, because a reader with a dead phone still needs to type it.
123
+ Point the code at the artifact's own link, so a reader who scans it lands on the page with the paper and the data.
124
+
125
+ ## Print
126
+
127
+ `poster.js` copies the two dimensions of the current sheet into one `@page` rule, so a print is exactly one page of that size with no margin.
128
+ On screen the poster keeps its real width in mm and the script scales the whole sheet down to the window, so a preview is a preview and never a second responsive layout.
129
+ Print is always light, and the tokens do that.
130
+
131
+ ## Checks
132
+
133
+ - `rshow check <dir>` exits 0, then print to PDF at the chosen size and count the pages.
134
+ - The sheet prints as one page with no scale set to fit.
135
+ - The title reads from 3 metres and the body from 1 metre, so check the type at 100 percent and at print size.
136
+ - No text sits closer than the sheet margin to the edge.
137
+ - `rshow publish <dir> --slug <slug> --label "<what changed>"` and report the printed `live-url`.
@@ -0,0 +1,132 @@
1
+ # Project page
2
+
3
+ ## Contents
4
+
5
+ - The shape
6
+ - Skeleton
7
+ - Title block
8
+ - Teaser
9
+ - Key results
10
+ - BibTeX and the copy button
11
+ - Checks
12
+
13
+ A project page is the landing page of a paper, and it uses the same theme as a page, a report and a poster.
14
+ It is a fixed template, so the sections are set and the content is the work.
15
+ `project.css` sets the look and `project.js` does one thing, the copy button on the BibTeX entry.
16
+
17
+ A project page is not a project.
18
+ A project is the grouping on the host, and it gets a group page at `art.example.com/_p/<project>/` that the host renders.
19
+ `reference/cli.md` holds that one.
20
+
21
+ ## The shape
22
+
23
+ ```text
24
+ paper/
25
+ index.html
26
+ teaser.svg # optional, a figure with its own light and dark values
27
+ ```
28
+
29
+ Link `fonts.css`, `tokens.css` and `project.css`, then `mode.js` in the head and `project.js` with `defer`.
30
+ `project.css` is self contained, so it needs no `page.css` beside it.
31
+
32
+ ## Skeleton
33
+
34
+ ```html
35
+ <!doctype html>
36
+ <html lang="en">
37
+ <head>
38
+ <meta charset="utf-8">
39
+ <meta name="viewport" content="width=device-width, initial-scale=1">
40
+ <title>Immutable artifacts for reproducible figures</title>
41
+ <link rel="stylesheet" href="/_themes/default@3/fonts.css">
42
+ <link rel="stylesheet" href="/_themes/default@3/tokens.css">
43
+ <link rel="stylesheet" href="/_themes/default@3/project.css">
44
+ <script src="/_themes/default@3/mode.js"></script>
45
+ <script defer src="/_themes/default@3/project.js"></script>
46
+ </head>
47
+ <body>
48
+ <div class="project">
49
+
50
+ <header class="project-head">
51
+ <h1>Immutable artifacts for reproducible figures</h1>
52
+ <p class="authors">[Author One]<sup>1</sup>, [Author Two]<sup>2</sup></p>
53
+ <p class="affiliations"><sup>1</sup> [Affiliation], <sup>2</sup> [Affiliation]</p>
54
+ <p class="marks"><sup>*</sup> Equal contribution.</p>
55
+ <p class="venue">[Venue], 2025</p>
56
+ <nav class="links" aria-label="Paper resources">
57
+ <a class="btn btn--tertiary" href="paper.pdf"><span class="btn-label">Paper PDF</span></a>
58
+ <a class="btn btn--tertiary" href="#cite"><span class="btn-label">BibTeX</span></a>
59
+ </nav>
60
+ </header>
61
+
62
+ <figure class="teaser wide">
63
+ <img src="teaser.svg" width="928" height="360" alt="[What the figure shows]">
64
+ <figcaption>[One line that says what the reader is looking at]</figcaption>
65
+ </figure>
66
+
67
+ <main>
68
+ <h2>Abstract</h2>
69
+ <p class="tldr">One sentence a reader could repeat after skimming.</p>
70
+ <p>[PLACEHOLDER] body copy at the theme measure.</p>
71
+ </main>
72
+
73
+ <footer class="project-foot">
74
+ <p class="ack">Acknowledgements. [PLACEHOLDER]</p>
75
+ </footer>
76
+
77
+ </div>
78
+ </body>
79
+ </html>
80
+ ```
81
+
82
+ Every child of `.project` is one column of prose at `--prose`, which is about 72 characters at 18px.
83
+ A child marked `class="wide"` takes `--wide`, which is 928px, and the teaser is the one that usually wants it.
84
+
85
+ ## Title block
86
+
87
+ - The title is one line of the claim, not the name of the artifact.
88
+ - The authors line is the paper's author list with superscript numbers, and the affiliation line gives each number once.
89
+ - `marks` holds the footnote the author list points at, such as equal contribution.
90
+ - `venue` holds the venue and the year, in `--muted`.
91
+ - `links` holds the paper PDF, the code, the data and the BibTeX jump, each one a `btn`.
92
+
93
+ ## Teaser
94
+
95
+ The teaser is an `<img>` inside `figure.teaser.wide`, and it carries its own light and dark values.
96
+ Use an SVG so the dark block inside it resolves against the page's used color scheme, and give the `img` a width and a height so the page does not move when it loads.
97
+ The caption says what the figure shows and nothing about why it is good.
98
+
99
+ ## Key results
100
+
101
+ A key results block is a small table or one figure with a direct label, and it always says what the numbers are.
102
+ Two exhibits is the ceiling, and each one names its source.
103
+ Never invent a number, and print `[PLACEHOLDER]` when the result is not in hand.
104
+
105
+ ## BibTeX and the copy button
106
+
107
+ ```html
108
+ <h2 id="cite">BibTeX</h2>
109
+ <pre id="bibtex-entry"><code>@inproceedings{key2025,
110
+ title = {[Title]},
111
+ author = {[Author One] and [Author Two]},
112
+ booktitle = {[Venue]},
113
+ year = {2025}
114
+ }</code></pre>
115
+ <div class="copy-row">
116
+ <button class="btn" type="button" data-rshow-copy="bibtex-entry">
117
+ <span class="btn-label">Copy</span>
118
+ </button>
119
+ <p class="copy-status" role="status" aria-live="polite"></p>
120
+ </div>
121
+ ```
122
+
123
+ `data-rshow-copy` names the id of the element to copy, and `project.js` uses the clipboard API with a select the text fallback.
124
+ The entry is ordinary text in the page, so a reader with JavaScript off selects it by hand.
125
+ Keep the entry true, because a wrong BibTeX entry is the one bug a reader notices a year later.
126
+
127
+ ## Checks
128
+
129
+ - `rshow check <dir>` exits 0, then `rshow preview <dir>` and read the page at 360px and at desktop width.
130
+ - The copy button announces `Copied` and then goes back to `Copy`, and the status line clears.
131
+ - Print preview is one column of black on white with no theme chrome.
132
+ - Every link and every figure is in the folder, so the version is self contained.