@zakmandhro/bunti 0.1.4 → 0.2.0

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 (203) hide show
  1. package/AGENTS.md +65 -0
  2. package/README.md +145 -34
  3. package/dist/cli.d.ts +44 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +147 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/colors.d.ts +144 -0
  8. package/dist/colors.d.ts.map +1 -0
  9. package/dist/colors.js +426 -0
  10. package/dist/colors.js.map +1 -0
  11. package/dist/components/Box.d.ts +11 -0
  12. package/dist/components/Box.d.ts.map +1 -0
  13. package/dist/components/Box.js +9 -0
  14. package/dist/components/Box.js.map +1 -0
  15. package/dist/components/Button.d.ts +24 -0
  16. package/dist/components/Button.d.ts.map +1 -0
  17. package/dist/components/Button.js +102 -0
  18. package/dist/components/Button.js.map +1 -0
  19. package/dist/components/Card.d.ts +33 -0
  20. package/dist/components/Card.d.ts.map +1 -0
  21. package/dist/components/Card.js +79 -0
  22. package/dist/components/Card.js.map +1 -0
  23. package/dist/components/Header.d.ts +23 -0
  24. package/dist/components/Header.d.ts.map +1 -0
  25. package/dist/components/Header.js +58 -0
  26. package/dist/components/Header.js.map +1 -0
  27. package/dist/components/Input.d.ts +25 -0
  28. package/dist/components/Input.d.ts.map +1 -0
  29. package/dist/components/Input.js +232 -0
  30. package/dist/components/Input.js.map +1 -0
  31. package/dist/components/Link.d.ts +21 -0
  32. package/dist/components/Link.d.ts.map +1 -0
  33. package/dist/components/Link.js +29 -0
  34. package/dist/components/Link.js.map +1 -0
  35. package/dist/components/Modal.d.ts +38 -0
  36. package/dist/components/Modal.d.ts.map +1 -0
  37. package/dist/components/Modal.js +87 -0
  38. package/dist/components/Modal.js.map +1 -0
  39. package/dist/components/Progress.d.ts +27 -0
  40. package/dist/components/Progress.d.ts.map +1 -0
  41. package/dist/components/Progress.js +39 -0
  42. package/dist/components/Progress.js.map +1 -0
  43. package/dist/components/Spinner.d.ts +19 -0
  44. package/dist/components/Spinner.d.ts.map +1 -0
  45. package/dist/components/Spinner.js +27 -0
  46. package/dist/components/Spinner.js.map +1 -0
  47. package/dist/components/index.d.ts +10 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/components/index.js +10 -0
  50. package/dist/components/index.js.map +1 -0
  51. package/dist/data/nf-glyphs.d.ts +11 -0
  52. package/dist/data/nf-glyphs.d.ts.map +1 -0
  53. package/dist/data/nf-glyphs.js +535 -0
  54. package/dist/data/nf-glyphs.js.map +1 -0
  55. package/dist/data/nf-names.d.ts +8 -0
  56. package/dist/data/nf-names.d.ts.map +1 -0
  57. package/dist/data/nf-names.js +2 -0
  58. package/dist/data/nf-names.js.map +1 -0
  59. package/dist/demo-registry.d.ts +27 -0
  60. package/dist/demo-registry.d.ts.map +1 -0
  61. package/dist/demo-registry.js +57 -0
  62. package/dist/demo-registry.js.map +1 -0
  63. package/dist/demos/2048.ts +743 -0
  64. package/dist/demos/animation.ts +246 -0
  65. package/dist/demos/demo-layout.ts +124 -0
  66. package/dist/demos/engine.ts +374 -0
  67. package/dist/demos/interaction.ts +91 -0
  68. package/dist/demos/login.ts +203 -0
  69. package/dist/demos/mission-control.ts +558 -0
  70. package/dist/demos/showcase.ts +224 -0
  71. package/dist/detect.d.ts +104 -0
  72. package/dist/detect.d.ts.map +1 -0
  73. package/dist/detect.js +227 -0
  74. package/dist/detect.js.map +1 -0
  75. package/dist/diagnostics.d.ts +73 -0
  76. package/dist/diagnostics.d.ts.map +1 -0
  77. package/dist/diagnostics.js +225 -0
  78. package/dist/diagnostics.js.map +1 -0
  79. package/dist/dsl/context.d.ts +11 -0
  80. package/dist/dsl/context.d.ts.map +1 -0
  81. package/dist/dsl/context.js +563 -0
  82. package/dist/dsl/context.js.map +1 -0
  83. package/dist/dsl/hooks.d.ts +12 -0
  84. package/dist/dsl/hooks.d.ts.map +1 -0
  85. package/dist/dsl/hooks.js +148 -0
  86. package/dist/dsl/hooks.js.map +1 -0
  87. package/dist/dsl/interaction.d.ts +16 -0
  88. package/dist/dsl/interaction.d.ts.map +1 -0
  89. package/dist/dsl/interaction.js +101 -0
  90. package/dist/dsl/interaction.js.map +1 -0
  91. package/dist/dsl/layers.d.ts +14 -0
  92. package/dist/dsl/layers.d.ts.map +1 -0
  93. package/dist/dsl/layers.js +87 -0
  94. package/dist/dsl/layers.js.map +1 -0
  95. package/dist/dsl/motion.d.ts +10 -0
  96. package/dist/dsl/motion.d.ts.map +1 -0
  97. package/dist/dsl/motion.js +150 -0
  98. package/dist/dsl/motion.js.map +1 -0
  99. package/dist/dsl/render.d.ts +22 -0
  100. package/dist/dsl/render.d.ts.map +1 -0
  101. package/dist/dsl/render.js +69 -0
  102. package/dist/dsl/render.js.map +1 -0
  103. package/dist/dsl/types.d.ts +510 -0
  104. package/dist/dsl/types.d.ts.map +1 -0
  105. package/dist/dsl/types.js +33 -0
  106. package/dist/dsl/types.js.map +1 -0
  107. package/dist/dsl.d.ts +18 -0
  108. package/dist/dsl.d.ts.map +1 -0
  109. package/dist/dsl.js +17 -0
  110. package/dist/dsl.js.map +1 -0
  111. package/dist/easing.d.ts +45 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +121 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/geometry.d.ts +77 -0
  116. package/dist/geometry.d.ts.map +1 -0
  117. package/dist/geometry.js +130 -0
  118. package/dist/geometry.js.map +1 -0
  119. package/dist/icons-full.d.ts +5 -0
  120. package/dist/icons-full.d.ts.map +1 -0
  121. package/dist/icons-full.js +31 -0
  122. package/dist/icons-full.js.map +1 -0
  123. package/dist/icons.d.ts +442 -0
  124. package/dist/icons.d.ts.map +1 -0
  125. package/dist/icons.js +314 -0
  126. package/dist/icons.js.map +1 -0
  127. package/dist/index.d.ts +228 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +46 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/input.d.ts +177 -0
  132. package/dist/input.d.ts.map +1 -0
  133. package/dist/input.js +533 -0
  134. package/dist/input.js.map +1 -0
  135. package/dist/layout.d.ts +194 -0
  136. package/dist/layout.d.ts.map +1 -0
  137. package/dist/layout.js +673 -0
  138. package/dist/layout.js.map +1 -0
  139. package/dist/render.d.ts +49 -0
  140. package/dist/render.d.ts.map +1 -0
  141. package/dist/render.js +650 -0
  142. package/dist/render.js.map +1 -0
  143. package/dist/state.d.ts +226 -0
  144. package/dist/state.d.ts.map +1 -0
  145. package/dist/state.js +130 -0
  146. package/dist/state.js.map +1 -0
  147. package/dist/theme.d.ts +129 -0
  148. package/dist/theme.d.ts.map +1 -0
  149. package/dist/theme.js +167 -0
  150. package/dist/theme.js.map +1 -0
  151. package/dist/themes/catppuccin-mocha.d.ts +19 -0
  152. package/dist/themes/catppuccin-mocha.d.ts.map +1 -0
  153. package/dist/themes/catppuccin-mocha.js +36 -0
  154. package/dist/themes/catppuccin-mocha.js.map +1 -0
  155. package/dist/themes/dracula.d.ts +21 -0
  156. package/dist/themes/dracula.d.ts.map +1 -0
  157. package/dist/themes/dracula.js +37 -0
  158. package/dist/themes/dracula.js.map +1 -0
  159. package/dist/themes/github-light.d.ts +21 -0
  160. package/dist/themes/github-light.d.ts.map +1 -0
  161. package/dist/themes/github-light.js +36 -0
  162. package/dist/themes/github-light.js.map +1 -0
  163. package/dist/themes/index.d.ts +19 -0
  164. package/dist/themes/index.d.ts.map +1 -0
  165. package/dist/themes/index.js +17 -0
  166. package/dist/themes/index.js.map +1 -0
  167. package/dist/themes/nord.d.ts +23 -0
  168. package/dist/themes/nord.d.ts.map +1 -0
  169. package/dist/themes/nord.js +38 -0
  170. package/dist/themes/nord.js.map +1 -0
  171. package/dist/themes/one-dark-pro.d.ts +21 -0
  172. package/dist/themes/one-dark-pro.d.ts.map +1 -0
  173. package/dist/themes/one-dark-pro.js +36 -0
  174. package/dist/themes/one-dark-pro.js.map +1 -0
  175. package/dist/themes/tokyo-night.d.ts +20 -0
  176. package/dist/themes/tokyo-night.d.ts.map +1 -0
  177. package/dist/themes/tokyo-night.js +37 -0
  178. package/dist/themes/tokyo-night.js.map +1 -0
  179. package/dist/utils.d.ts +28 -0
  180. package/dist/utils.d.ts.map +1 -0
  181. package/dist/utils.js +158 -0
  182. package/dist/utils.js.map +1 -0
  183. package/dist/vendor/colors.d.ts +45 -0
  184. package/dist/vendor/colors.d.ts.map +1 -0
  185. package/dist/vendor/colors.js +72 -0
  186. package/dist/vendor/colors.js.map +1 -0
  187. package/llms.txt +79 -0
  188. package/package.json +52 -11
  189. package/src/colors.ts +0 -255
  190. package/src/components/Button.ts +0 -104
  191. package/src/components/Card.ts +0 -53
  192. package/src/components/Header.ts +0 -65
  193. package/src/components/Input.ts +0 -124
  194. package/src/components/index.ts +0 -4
  195. package/src/data/glyphs.ts +0 -30
  196. package/src/detect.ts +0 -60
  197. package/src/dsl.ts +0 -661
  198. package/src/icons.ts +0 -186
  199. package/src/index.ts +0 -72
  200. package/src/layout.ts +0 -640
  201. package/src/render.ts +0 -302
  202. package/src/state.ts +0 -148
  203. package/src/utils.ts +0 -165
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Animation Sequence demo — dogfoods the motion minimum:
3
+ * - animate() with easing curves
4
+ * - stagger() cascading entrances (restartable id clock)
5
+ * - transition() enter/exit progress (SPACE toggles the status overlay)
6
+ * - restartAnimation() timeline restarts (no componentState poking)
7
+ * - time-based flicker() and the dt/frame counters
8
+ */
9
+
10
+ import { Box } from '../components/index.js';
11
+ import type { BuntiContext } from '../dsl/index.js';
12
+ import { easeOutBack, easeOutCubic, lerp } from '../index.js';
13
+ import { demo } from './demo-layout';
14
+
15
+ const LOGO = [
16
+ ' ┏┓ ┳ ┳ ┏┓┓ ┏┳┓ ┳ ',
17
+ ' ┣┻┓ ┃ ┃ ┃┃┃ ┃ ┃ ',
18
+ ' ┗━┛ ┗━┛ ┛┗┛ ┻ ┻ ',
19
+ ];
20
+
21
+ const TIMELINE_IDS = [
22
+ 'container',
23
+ 'progress',
24
+ 'bunti-logo',
25
+ 'boot-copy',
26
+ 'restart-hint',
27
+ ];
28
+
29
+ function gradientLogoLine(
30
+ ctx: BuntiContext,
31
+ line: string,
32
+ progress: number,
33
+ muted: { r: number; g: number; b: number },
34
+ ) {
35
+ return Array.from(line)
36
+ .map((char, index) => {
37
+ if (char === ' ') return char;
38
+
39
+ const phase = index / Math.max(1, line.length - 1);
40
+ const target =
41
+ phase < 0.5
42
+ ? ctx.fade('#39d7ff', '#bd5cff', phase * 2)
43
+ : ctx.fade('#bd5cff', '#ffe66d', (phase - 0.5) * 2);
44
+
45
+ return ctx.color.fg(ctx.fade(muted, target, progress), char);
46
+ })
47
+ .join('');
48
+ }
49
+
50
+ demo(
51
+ 'Animation Sequence',
52
+ (ctx, bounds) => {
53
+ const { color, elapsedTime, lastKey, requestStop } = ctx;
54
+ if (lastKey === 'q') requestStop();
55
+ if (lastKey === 'enter') {
56
+ for (const id of TIMELINE_IDS) ctx.restartAnimation(id);
57
+ }
58
+
59
+ const [showStatus, setShowStatus] = ctx.useState('show-status', false);
60
+ if (lastKey === ' ') setShowStatus(!showStatus);
61
+
62
+ const panelWidth = Math.min(Math.max(bounds.w - 4, 28), 76);
63
+ const panel = bounds.place({
64
+ y: Math.max(1, Math.floor(bounds.h / 2) - 7),
65
+ width: panelWidth,
66
+ height: Math.min(Math.max(bounds.h - 2, 12), 15),
67
+ });
68
+ const innerX = panel.x + 2;
69
+ const innerY = panel.y + 1;
70
+ const innerW = Math.max(1, panel.width - 4);
71
+ const innerH = Math.max(1, panel.height - 2);
72
+
73
+ const containerProgress = ctx.animate(650, {
74
+ id: 'container',
75
+ easing: easeOutCubic,
76
+ });
77
+ const progress = ctx.animate(1800, {
78
+ id: 'progress',
79
+ loop: true,
80
+ delay: 650,
81
+ });
82
+ const logoProgress = ctx.animate(900, { id: 'bunti-logo', delay: 650 });
83
+ const hintProgress = ctx.animate(650, {
84
+ id: 'restart-hint',
85
+ delay: 2850,
86
+ easing: easeOutCubic,
87
+ });
88
+ const logoReady = logoProgress >= 1;
89
+ const showLogo = logoReady || ctx.flicker(0.72, { id: 'logo-boot' });
90
+ const pulse = ctx.animate(440, { loop: 'yoyo' });
91
+ const amber = ctx.rgb(255, Math.floor(170 + pulse * 70), 40);
92
+ const muted = { r: 92, g: 98, b: 112 };
93
+ const panelBg = ctx.fade('#0a0a0b', '#101118', containerProgress);
94
+ const innerBg = ctx.fade('#0a0a0b', '#151724', containerProgress);
95
+ const trackBg = ctx.fade('#0a0a0b', '#202338', containerProgress);
96
+ const borderColor = ctx.fade(
97
+ '#0a0a0b',
98
+ logoReady ? 'mint' : 'gray',
99
+ containerProgress,
100
+ );
101
+ const typed = ctx.typewriter('Bunti features terminal animations', {
102
+ id: 'boot-copy',
103
+ cps: 28,
104
+ delay: 1550,
105
+ cursor: '█',
106
+ });
107
+
108
+ ctx.rect(panel.x, panel.y, panel.width, panel.height, {
109
+ char: ' ',
110
+ bg: panelBg,
111
+ });
112
+ ctx.rect(innerX, innerY, innerW, innerH, {
113
+ char: ' ',
114
+ bg: innerBg,
115
+ });
116
+ ctx.rect(innerX, panel.y + panel.height - 3, innerW, 1, {
117
+ char: ' ',
118
+ bg: trackBg,
119
+ });
120
+ for (let x = 0; x < innerW; x++) {
121
+ const phase = (x / innerW + progress) % 1;
122
+ const bg =
123
+ phase < 0.33
124
+ ? ctx.fade('#39d7ff', '#bd5cff', phase / 0.33)
125
+ : phase < 0.66
126
+ ? ctx.fade('#bd5cff', '#ffe66d', (phase - 0.33) / 0.33)
127
+ : ctx.fade('#ffe66d', '#39d7ff', (phase - 0.66) / 0.34);
128
+
129
+ ctx.rect(innerX + x, panel.y + panel.height - 3, 1, 1, {
130
+ char: ' ',
131
+ bg,
132
+ });
133
+ }
134
+
135
+ Box(
136
+ ctx,
137
+ {
138
+ x: panel.x,
139
+ y: panel.y,
140
+ width: panel.width,
141
+ height: panel.height,
142
+ border: 'rounded',
143
+ borderColor,
144
+ padding: [1, 2],
145
+ align: 'center',
146
+ valign: 'middle',
147
+ },
148
+ (sub) => {
149
+ sub.text(color.cyan(color.bold('BOOT SEQUENCE')));
150
+ sub.text('\n\n');
151
+
152
+ // Staggered cascade: each logo line rides its own slice of the
153
+ // 'bunti-logo' clock, so ENTER replays the whole cascade.
154
+ // (+4 index slots ≈ 640ms base delay so the container lands first.)
155
+ LOGO.forEach((line, index) => {
156
+ const lineProgress = ctx.stagger(index + 4, {
157
+ id: 'bunti-logo',
158
+ delay: 160,
159
+ duration: 520,
160
+ easing: easeOutCubic,
161
+ });
162
+ sub.text(
163
+ showLogo
164
+ ? gradientLogoLine(ctx, line, lineProgress, muted)
165
+ : ' '.repeat(line.length),
166
+ );
167
+ sub.text('\n');
168
+ });
169
+
170
+ sub.text('\n');
171
+ sub.text(
172
+ logoReady
173
+ ? color.fg(amber, '>>> SYSTEMS READY <<<')
174
+ : color.fg(muted, 'Booting systems...'),
175
+ );
176
+ sub.text('\n');
177
+ sub.text(color.fg(typed.done ? 'mint' : 'silver', typed.text));
178
+ sub.text(color.fg(amber, typed.cursor));
179
+ },
180
+ );
181
+
182
+ // Status overlay: transition() keeps it mounted through the exit leg,
183
+ // so it slides/fades out instead of vanishing.
184
+ const status = ctx.transition('status-overlay', showStatus, {
185
+ duration: 320,
186
+ exitDuration: 220,
187
+ easing: easeOutBack,
188
+ });
189
+ if (status.mounted) {
190
+ const overlayW = Math.min(34, Math.max(24, panel.width - 12));
191
+ const overlayX = panel.x + Math.floor((panel.width - overlayW) / 2);
192
+ const overlayY = Math.round(
193
+ lerp(panel.y + panel.height + 2, panel.y + 3, status.progress),
194
+ );
195
+ const overlayBorder = ctx.fade('#0a0a0b', '#39d7ff', status.progress);
196
+ const overlayText = ctx.fade('#0a0a0b', '#e8ecf4', status.progress);
197
+
198
+ ctx.layer(10, (layer) => {
199
+ layer.box(
200
+ {
201
+ x: overlayX,
202
+ y: overlayY,
203
+ width: overlayW,
204
+ height: 6,
205
+ border: 'rounded',
206
+ borderColor: overlayBorder,
207
+ bgColor: '#101426',
208
+ padding: [0, 2],
209
+ align: 'left',
210
+ title: 'STATUS',
211
+ },
212
+ (sub) => {
213
+ sub.text(color.fg(overlayText, `frame ${ctx.frame}`));
214
+ sub.text('\n');
215
+ sub.text(color.fg(overlayText, `dt ${ctx.dt}ms`));
216
+ sub.text('\n');
217
+ sub.text(
218
+ color.fg(overlayText, `elapsed ${Math.round(elapsedTime)}ms`),
219
+ );
220
+ sub.text('\n');
221
+ sub.text(color.fg(overlayText, 'SPACE to dismiss'));
222
+ },
223
+ );
224
+ });
225
+ }
226
+
227
+ const hint = 'ENTER restart | SPACE status | q quit';
228
+ ctx.blit(
229
+ panel.x + Math.max(0, Math.floor((panel.width - hint.length) / 2)),
230
+ panel.y + panel.height + 1,
231
+ color.fg(
232
+ ctx.fade('#0a0a0b', { r: 96, g: 102, b: 118 }, hintProgress),
233
+ hint,
234
+ ),
235
+ );
236
+ },
237
+ {
238
+ fps: 20,
239
+ alternateBuffer: true,
240
+ hideCursor: true,
241
+ nerdFont: true,
242
+ keyboard: true,
243
+ mouse: true,
244
+ footer: false,
245
+ },
246
+ );
@@ -0,0 +1,124 @@
1
+ import { Box, Header } from '../components/index.js';
2
+ import type { BuntiContext } from '../dsl/index.js';
3
+ import {
4
+ bunti,
5
+ type PlacedRectInput,
6
+ type Rect,
7
+ resolvePlacedRect,
8
+ type SplitOptions,
9
+ splitRect,
10
+ } from '../index.js';
11
+ import type { ScreenOptions } from '../state.js';
12
+
13
+ export interface DemoViewport {
14
+ x: number;
15
+ y: number;
16
+ w: number;
17
+ h: number;
18
+ rect: Rect;
19
+ place: (input: PlacedRectInput) => Rect;
20
+ split: (options: SplitOptions) => Rect[];
21
+ }
22
+
23
+ export type DemoBounds = DemoViewport;
24
+
25
+ export interface DemoOptions extends ScreenOptions {
26
+ header?: boolean;
27
+ footer?: boolean | 'telemetry';
28
+ wallpaper?: Parameters<BuntiContext['wallpaper']>[0];
29
+ exitHint?: string;
30
+ padding?: number | [x: number, y: number];
31
+ }
32
+
33
+ const DEFAULT_DEMO_OPTIONS = {
34
+ fps: 10,
35
+ alternateBuffer: true,
36
+ hideCursor: true,
37
+ nerdFont: true,
38
+ mouse: true,
39
+ keyboard: true,
40
+ defaultFg: 'silver',
41
+ header: true,
42
+ footer: 'telemetry',
43
+ wallpaper: '#0a0a0b',
44
+ exitHint: 'q',
45
+ padding: 0,
46
+ } satisfies DemoOptions;
47
+
48
+ function viewportPadding(padding: DemoOptions['padding']) {
49
+ if (Array.isArray(padding)) return { x: padding[0], y: padding[1] };
50
+ return { x: padding ?? 0, y: padding ?? 0 };
51
+ }
52
+
53
+ export async function demo(
54
+ title: string,
55
+ contentRender: (ctx: BuntiContext, bounds: DemoViewport) => void,
56
+ options: DemoOptions = {},
57
+ ) {
58
+ await bunti.init({ nerdFont: true });
59
+ const config = { ...DEFAULT_DEMO_OPTIONS, ...options };
60
+ const {
61
+ header,
62
+ footer,
63
+ wallpaper: wallpaperColor,
64
+ exitHint,
65
+ padding,
66
+ ...screenOptions
67
+ } = config;
68
+
69
+ bunti.render((ctx) => {
70
+ const { color, width, height, wallpaper, icon } = ctx;
71
+ if (ctx.lastKey === 'q') ctx.requestStop();
72
+
73
+ wallpaper(wallpaperColor);
74
+
75
+ if (header) {
76
+ Header(ctx, {
77
+ title: title.toUpperCase(),
78
+ leftIcon: icon('bunti'),
79
+ rightLabel: exitHint,
80
+ });
81
+ }
82
+
83
+ if (footer) {
84
+ Box(
85
+ ctx,
86
+ {
87
+ anchor: 'bottom',
88
+ width: '100%',
89
+ align: 'center',
90
+ border: 'none',
91
+ padding: [0, 0],
92
+ },
93
+ ({ text }) => {
94
+ text(
95
+ color.dim(
96
+ ` 󰇄 Screen: ${width}x${height} | Mouse: ${ctx.mouseX},${ctx.mouseY} | Logic: Stateless `,
97
+ ),
98
+ );
99
+ },
100
+ );
101
+ }
102
+
103
+ const chromeTop = header ? 2 : 0;
104
+ const chromeBottom = footer ? 2 : 0;
105
+ const pad = viewportPadding(padding);
106
+ const rect: Rect = {
107
+ x: pad.x,
108
+ y: chromeTop + pad.y,
109
+ width: Math.max(0, width - pad.x * 2),
110
+ height: Math.max(0, height - chromeTop - chromeBottom - pad.y * 2),
111
+ };
112
+ const bounds: DemoViewport = {
113
+ x: rect.x,
114
+ y: rect.y,
115
+ w: rect.width,
116
+ h: rect.height,
117
+ rect,
118
+ place: (input) => resolvePlacedRect(rect, input),
119
+ split: (options) => splitRect(rect, options),
120
+ };
121
+
122
+ contentRender(ctx, bounds);
123
+ }, screenOptions);
124
+ }