svelte-stageshow 0.0.0-stage → 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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +32 -0
  3. package/README.md +34 -2
  4. package/dist/Avatar.svelte +57 -0
  5. package/dist/Avatar.svelte.d.ts +20 -0
  6. package/dist/Bar.svelte +27 -0
  7. package/dist/Bar.svelte.d.ts +18 -0
  8. package/dist/Bubble.svelte +255 -0
  9. package/dist/Bubble.svelte.d.ts +37 -0
  10. package/dist/Caret.svelte +10 -0
  11. package/dist/Caret.svelte.d.ts +7 -0
  12. package/dist/Chart.svelte +183 -0
  13. package/dist/Chart.svelte.d.ts +6 -0
  14. package/dist/Chip.svelte +44 -0
  15. package/dist/Chip.svelte.d.ts +16 -0
  16. package/dist/Code.svelte +95 -0
  17. package/dist/Code.svelte.d.ts +15 -0
  18. package/dist/Cursor.svelte +89 -0
  19. package/dist/Cursor.svelte.d.ts +13 -0
  20. package/dist/Keyboard.svelte +180 -0
  21. package/dist/Keyboard.svelte.d.ts +22 -0
  22. package/dist/Keycaps.svelte +20 -0
  23. package/dist/Keycaps.svelte.d.ts +8 -0
  24. package/dist/LockScreen.svelte +75 -0
  25. package/dist/LockScreen.svelte.d.ts +22 -0
  26. package/dist/Notification.svelte +38 -0
  27. package/dist/Notification.svelte.d.ts +16 -0
  28. package/dist/Phone.svelte +215 -0
  29. package/dist/Phone.svelte.d.ts +25 -0
  30. package/dist/Prompt.svelte +23 -0
  31. package/dist/Prompt.svelte.d.ts +13 -0
  32. package/dist/Receipt.svelte +24 -0
  33. package/dist/Receipt.svelte.d.ts +11 -0
  34. package/dist/Row.svelte +90 -0
  35. package/dist/Row.svelte.d.ts +28 -0
  36. package/dist/Stage.svelte +645 -0
  37. package/dist/Stage.svelte.d.ts +86 -0
  38. package/dist/Stamp.svelte +16 -0
  39. package/dist/Stamp.svelte.d.ts +10 -0
  40. package/dist/StillProvider.svelte +15 -0
  41. package/dist/StillProvider.svelte.d.ts +8 -0
  42. package/dist/Text.svelte +42 -0
  43. package/dist/Text.svelte.d.ts +20 -0
  44. package/dist/Thread.svelte +192 -0
  45. package/dist/Thread.svelte.d.ts +32 -0
  46. package/dist/Window.svelte +255 -0
  47. package/dist/Window.svelte.d.ts +40 -0
  48. package/dist/act.d.ts +36 -0
  49. package/dist/act.js +369 -0
  50. package/dist/brands/1password-dark.svg +1 -0
  51. package/dist/brands/1password.svg +1 -0
  52. package/dist/brands/LICENSE-CRYPTO-FLAT.txt +121 -0
  53. package/dist/brands/LICENSE-PAYMENT-ICONS.txt +201 -0
  54. package/dist/brands/LICENSE-SIMPLE-ICONS.txt +30 -0
  55. package/dist/brands/LICENSE-SVGL.txt +21 -0
  56. package/dist/brands/SOURCES.md +57 -0
  57. package/dist/brands/amex-tile.svg +1 -0
  58. package/dist/brands/amex.svg +1 -0
  59. package/dist/brands/bankofamerica-dark.svg +1 -0
  60. package/dist/brands/bankofamerica.svg +1 -0
  61. package/dist/brands/barclays.svg +1 -0
  62. package/dist/brands/bitcoin.svg +1 -0
  63. package/dist/brands/chase.svg +1 -0
  64. package/dist/brands/discover.svg +1 -0
  65. package/dist/brands/dropbox-glyph.svg +1 -0
  66. package/dist/brands/dropbox.svg +1 -0
  67. package/dist/brands/ethereum.svg +1 -0
  68. package/dist/brands/figma.svg +14 -0
  69. package/dist/brands/github-dark.svg +3 -0
  70. package/dist/brands/github.svg +1 -0
  71. package/dist/brands/gmail.svg +11 -0
  72. package/dist/brands/google-calendar.svg +17 -0
  73. package/dist/brands/google-docs.svg +1 -0
  74. package/dist/brands/google-drive.svg +8 -0
  75. package/dist/brands/google-meet.svg +16 -0
  76. package/dist/brands/google-sheets.svg +55 -0
  77. package/dist/brands/google-slides.svg +61 -0
  78. package/dist/brands/hsbc.svg +1 -0
  79. package/dist/brands/linear-dark.svg +5 -0
  80. package/dist/brands/linear.svg +5 -0
  81. package/dist/brands/mastercard.svg +5 -0
  82. package/dist/brands/monzo-dark.svg +1 -0
  83. package/dist/brands/monzo.svg +1 -0
  84. package/dist/brands/notion.svg +1 -0
  85. package/dist/brands/paypal.svg +1 -0
  86. package/dist/brands/revolut-dark.svg +1 -0
  87. package/dist/brands/revolut.svg +1 -0
  88. package/dist/brands/shopify.svg +5 -0
  89. package/dist/brands/slack.svg +24 -0
  90. package/dist/brands/solana.svg +1 -0
  91. package/dist/brands/stripe.svg +1 -0
  92. package/dist/brands/tether.svg +1 -0
  93. package/dist/brands/usdc.svg +6 -0
  94. package/dist/brands/vercel-dark.svg +1 -0
  95. package/dist/brands/vercel.svg +1 -0
  96. package/dist/brands/visa.svg +3 -0
  97. package/dist/brands/wellsfargo.svg +1 -0
  98. package/dist/brands/zoom.svg +1 -0
  99. package/dist/brands.d.ts +296 -0
  100. package/dist/brands.js +215 -0
  101. package/dist/chart.d.ts +55 -0
  102. package/dist/chart.js +91 -0
  103. package/dist/clock.d.ts +22 -0
  104. package/dist/clock.js +57 -0
  105. package/dist/code.d.ts +15 -0
  106. package/dist/code.js +37 -0
  107. package/dist/controls/Badge.svelte +29 -0
  108. package/dist/controls/Badge.svelte.d.ts +15 -0
  109. package/dist/controls/Field.svelte +145 -0
  110. package/dist/controls/Field.svelte.d.ts +34 -0
  111. package/dist/controls/Segmented.svelte +34 -0
  112. package/dist/controls/Segmented.svelte.d.ts +16 -0
  113. package/dist/controls/Slot.svelte +34 -0
  114. package/dist/controls/Slot.svelte.d.ts +16 -0
  115. package/dist/controls/Tabs.svelte +44 -0
  116. package/dist/controls/Tabs.svelte.d.ts +14 -0
  117. package/dist/controls/Toggle.svelte +24 -0
  118. package/dist/controls/Toggle.svelte.d.ts +11 -0
  119. package/dist/cursors/MacCrosshair.svelte +5 -0
  120. package/dist/cursors/MacCrosshair.svelte.d.ts +18 -0
  121. package/dist/cursors/MacGrab.svelte +7 -0
  122. package/dist/cursors/MacGrab.svelte.d.ts +18 -0
  123. package/dist/cursors/MacIbeam.svelte +5 -0
  124. package/dist/cursors/MacIbeam.svelte.d.ts +18 -0
  125. package/dist/cursors/MacOpenHand.svelte +12 -0
  126. package/dist/cursors/MacOpenHand.svelte.d.ts +18 -0
  127. package/dist/cursors/MacPointer.svelte +7 -0
  128. package/dist/cursors/MacPointer.svelte.d.ts +18 -0
  129. package/dist/cursors/MacPointingHand.svelte +11 -0
  130. package/dist/cursors/MacPointingHand.svelte.d.ts +18 -0
  131. package/dist/cursors/MacResize.svelte +23 -0
  132. package/dist/cursors/MacResize.svelte.d.ts +6 -0
  133. package/dist/cursors/MacResizeEW.svelte +5 -0
  134. package/dist/cursors/MacResizeEW.svelte.d.ts +18 -0
  135. package/dist/cursors/MacResizeNESW.svelte +5 -0
  136. package/dist/cursors/MacResizeNESW.svelte.d.ts +18 -0
  137. package/dist/cursors/MacResizeNS.svelte +5 -0
  138. package/dist/cursors/MacResizeNS.svelte.d.ts +18 -0
  139. package/dist/cursors/MacResizeNWSE.svelte +5 -0
  140. package/dist/cursors/MacResizeNWSE.svelte.d.ts +18 -0
  141. package/dist/cursors/MacWait.svelte +5 -0
  142. package/dist/cursors/MacWait.svelte.d.ts +18 -0
  143. package/dist/cursors.d.ts +12 -0
  144. package/dist/cursors.js +15 -0
  145. package/dist/ease.d.ts +19 -0
  146. package/dist/ease.js +55 -0
  147. package/dist/index.d.ts +71 -0
  148. package/dist/index.js +73 -0
  149. package/dist/ios.d.ts +1 -0
  150. package/dist/ios.js +2 -0
  151. package/dist/keys.d.ts +10 -0
  152. package/dist/keys.js +20 -0
  153. package/dist/look.d.ts +15 -0
  154. package/dist/look.js +14 -0
  155. package/dist/looks.d.ts +5 -0
  156. package/dist/looks.js +4 -0
  157. package/dist/measure.d.ts +61 -0
  158. package/dist/measure.js +103 -0
  159. package/dist/messages.d.ts +4 -0
  160. package/dist/messages.js +6 -0
  161. package/dist/motion/Camera.svelte +91 -0
  162. package/dist/motion/Camera.svelte.d.ts +20 -0
  163. package/dist/motion/Collapse.svelte +91 -0
  164. package/dist/motion/Collapse.svelte.d.ts +31 -0
  165. package/dist/motion/Connector.svelte +242 -0
  166. package/dist/motion/Connector.svelte.d.ts +34 -0
  167. package/dist/motion/Count.svelte +170 -0
  168. package/dist/motion/Count.svelte.d.ts +17 -0
  169. package/dist/motion/Expand.svelte +148 -0
  170. package/dist/motion/Expand.svelte.d.ts +18 -0
  171. package/dist/motion/Fly.svelte +100 -0
  172. package/dist/motion/Fly.svelte.d.ts +22 -0
  173. package/dist/motion/Follow.svelte +185 -0
  174. package/dist/motion/Follow.svelte.d.ts +23 -0
  175. package/dist/motion/Popover.svelte +80 -0
  176. package/dist/motion/Popover.svelte.d.ts +16 -0
  177. package/dist/motion/Roll.svelte +236 -0
  178. package/dist/motion/Roll.svelte.d.ts +24 -0
  179. package/dist/motion/Scroll.svelte +53 -0
  180. package/dist/motion/Scroll.svelte.d.ts +14 -0
  181. package/dist/motion/States.svelte +50 -0
  182. package/dist/motion/States.svelte.d.ts +35 -0
  183. package/dist/motion/Stream.svelte +28 -0
  184. package/dist/motion/Stream.svelte.d.ts +13 -0
  185. package/dist/motion/Swap.svelte +149 -0
  186. package/dist/motion/Swap.svelte.d.ts +14 -0
  187. package/dist/parts/BrandMark.svelte +59 -0
  188. package/dist/parts/BrandMark.svelte.d.ts +19 -0
  189. package/dist/parts/Check.svelte +32 -0
  190. package/dist/parts/Check.svelte.d.ts +13 -0
  191. package/dist/parts/Facepile.svelte +39 -0
  192. package/dist/parts/Facepile.svelte.d.ts +16 -0
  193. package/dist/parts/Icon.svelte +225 -0
  194. package/dist/parts/Icon.svelte.d.ts +267 -0
  195. package/dist/parts/Menu.svelte +29 -0
  196. package/dist/parts/Menu.svelte.d.ts +14 -0
  197. package/dist/parts/Meter.svelte +27 -0
  198. package/dist/parts/Meter.svelte.d.ts +12 -0
  199. package/dist/parts/Pin.svelte +44 -0
  200. package/dist/parts/Pin.svelte.d.ts +20 -0
  201. package/dist/parts/Spinner.svelte +26 -0
  202. package/dist/parts/Spinner.svelte.d.ts +11 -0
  203. package/dist/parts/Toast.svelte +47 -0
  204. package/dist/parts/Toast.svelte.d.ts +15 -0
  205. package/dist/parts/Tooltip.svelte +34 -0
  206. package/dist/parts/Tooltip.svelte.d.ts +17 -0
  207. package/dist/reduced-motion.d.ts +8 -0
  208. package/dist/reduced-motion.js +37 -0
  209. package/dist/stageshow.css +4754 -0
  210. package/dist/still.d.ts +2 -0
  211. package/dist/still.js +10 -0
  212. package/dist/style/avatar.css +102 -0
  213. package/dist/style/chart.css +59 -0
  214. package/dist/style/chip.css +66 -0
  215. package/dist/style/code.css +87 -0
  216. package/dist/style/controls/Badge.css +56 -0
  217. package/dist/style/controls/Field.css +201 -0
  218. package/dist/style/controls/Segmented.css +40 -0
  219. package/dist/style/controls/Slot.css +39 -0
  220. package/dist/style/controls/Tabs.css +33 -0
  221. package/dist/style/controls/Toggle.css +46 -0
  222. package/dist/style/cursor.css +256 -0
  223. package/dist/style/ios.css +177 -0
  224. package/dist/style/keys.css +60 -0
  225. package/dist/style/messages.css +462 -0
  226. package/dist/style/motion/Camera.css +27 -0
  227. package/dist/style/motion/Collapse.css +97 -0
  228. package/dist/style/motion/Connector.css +128 -0
  229. package/dist/style/motion/Count.css +68 -0
  230. package/dist/style/motion/Expand.css +80 -0
  231. package/dist/style/motion/Fly.css +51 -0
  232. package/dist/style/motion/Follow.css +17 -0
  233. package/dist/style/motion/Popover.css +65 -0
  234. package/dist/style/motion/Roll.css +77 -0
  235. package/dist/style/motion/Scroll.css +49 -0
  236. package/dist/style/motion/States.css +49 -0
  237. package/dist/style/motion/Stream.css +32 -0
  238. package/dist/style/motion/Swap.css +48 -0
  239. package/dist/style/parts/BrandMark.css +45 -0
  240. package/dist/style/parts/Check.css +94 -0
  241. package/dist/style/parts/Facepile.css +17 -0
  242. package/dist/style/parts/Icon.css +8 -0
  243. package/dist/style/parts/Menu.css +34 -0
  244. package/dist/style/parts/Meter.css +46 -0
  245. package/dist/style/parts/Pin.css +58 -0
  246. package/dist/style/parts/Spinner.css +12 -0
  247. package/dist/style/parts/Toast.css +35 -0
  248. package/dist/style/parts/Tooltip.css +42 -0
  249. package/dist/style/phone.css +539 -0
  250. package/dist/style/row.css +211 -0
  251. package/dist/style/stage.css +448 -0
  252. package/dist/style/window.css +612 -0
  253. package/dist/types.d.ts +87 -0
  254. package/dist/types.js +1 -0
  255. package/dist/util.d.ts +4 -0
  256. package/dist/util.js +10 -0
  257. package/dist/wallpaper.d.ts +3 -0
  258. package/dist/wallpaper.js +3 -0
  259. package/package.json +41 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Benji Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,32 @@
1
+ # Notices
2
+
3
+ Stageshow source is MIT licensed; see `LICENSE`.
4
+
5
+ The Svelte 5 port in this repository (`svelte-stageshow`, 2026) is by Ammar
6
+ (maven-ammar). It keeps the original MIT licence and this notice. The ported
7
+ components and the two gallery scenes come from the public Stageshow source in
8
+ `benjitaylor/skills`; the original gold stills were filmed from that source and
9
+ are kept in `shots/` for the parity record in the repository's `REHEARSAL.md`.
10
+
11
+ Product and operating-system names, familiar device geometry, app icons, and
12
+ illustrated interfaces are used as examples. Those names and marks remain
13
+ with their owners; the Stageshow license does not grant trademark rights or
14
+ imply endorsement. Adapt examples to the product you are illustrating.
15
+
16
+ The Radix-inspired look uses publicly documented color and spacing values;
17
+ its source is identified in `examples/looks/radix.css`. The app icons in `assets/app-icons.ts` are artwork from their respective app
18
+ bundles, as identified in that file; they are not covered by the Stageshow MIT
19
+ license. They remain illustrative references to those products. The Wikipedia
20
+ study includes two local photographs dedicated under CC0 1.0 (also used
21
+ in the Finder, Telegram and annotation studies); their authors,
22
+ original source pages and modifications are recorded in
23
+ [`examples/wikipedia/PHOTOS.md`](examples/wikipedia/PHOTOS.md). Other scenes
24
+ use CSS and vector illustrations. No third-party fonts are bundled, and the
25
+ playground does not fetch fonts or photographs from external services at runtime.
26
+
27
+ The local brand registry in `kit/brands/` includes SVG artwork from SVGL (MIT), payment-icons (Apache 2.0), Cryptocurrency Icons (CC0 1.0), and official Linear, American Express and Circle assets. Per-file sources, hashes and license files are listed in [`kit/brands/SOURCES.md`](kit/brands/SOURCES.md). The original artwork and brand usage terms remain with their respective owners.
28
+
29
+ `Phone frame="photo"` is optional. Supply your own permitted bezel image via
30
+ `--ss-phone-photo-light: url(...)` and optionally `--ss-phone-photo-dark`.
31
+ Without it, the kit uses its CSS-drawn `pro` frame. No bezel photographs are
32
+ bundled.
package/README.md CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # svelte-stageshow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Svelte 5 port of Stageshow: framed faux product UI for stills and scripted
4
+ demos. Stage, Window, Bar, Text, Row, Phone, Avatar and Chip; one CSS import;
5
+ an Act scheduler with deterministic frame stepping.
6
+
7
+ ```sh
8
+ pnpm add svelte-stageshow
9
+ ```
10
+
11
+ ```svelte
12
+ <script lang="ts">
13
+ import { Avatar, Row, Stage, Window } from 'svelte-stageshow'
14
+ import 'svelte-stageshow/style.css'
15
+ </script>
16
+
17
+ <Stage width={640} height={320} className="my-stage">
18
+ {#snippet children(state, rect, layout)}
19
+ <Window title="Customers" className="my-window">
20
+ <Row selected title="Polaris Studio" subtitle="Scale · 24 seats">
21
+ {#snippet lead()}<Avatar name="Polaris Studio" size={30} tone="accent" />{/snippet}
22
+ {#snippet meta()}$12,400.00{/snippet}
23
+ </Row>
24
+ </Window>
25
+ {/snippet}
26
+ </Stage>
27
+ ```
28
+
29
+ The full README in the repository has the prop mapping (72 React prop names,
30
+ 0 missing), the diff table, the deterministic `runFrames()` contract and the
31
+ verification record (`REHEARSAL.md`).
32
+
33
+ - Intended repository: `maven-ammar/svelte-stageshow` (private, not pushed yet)
34
+ - Licence: MIT, © 2026 Benji Taylor; Svelte port by Ammar (maven-ammar), 2026
35
+ - Peer: `svelte` ^5. No React, react-dom or icon dependency.
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+ import { getLook } from './look.js'
4
+
5
+ type Tone = 'accent' | 'ink' | 'success' | 'person' | 'ios'
6
+ type Ring = 'pending' | 'waiting' | 'confirmed' | 'declined'
7
+
8
+ interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, 'class'> {
9
+ /** Its initial. Leave it out (or set `plain`) for a plain disc. */
10
+ name?: string
11
+ /** A plain disc in any fidelity: filler people, so they don't compete with the named one. */
12
+ plain?: boolean
13
+ size?: number
14
+ tone?: Tone
15
+ src?: string
16
+ ring?: Ring
17
+ className?: string
18
+ /** Svelte's own class attribute; merged after className. */
19
+ class?: string
20
+ style?: string
21
+ }
22
+
23
+ let { name = '', plain = false, size = 24, tone, src, ring, className = '', class: klass = '', style = '', ...rest }: Props = $props()
24
+
25
+ // Six muted, opaque fills that read on light and dark grounds, one per person
26
+ const PEOPLE = ['#c98f6e', '#6f9b7d', '#7b86c7', '#c47a9e', '#8c9ab0', '#c2a35a']
27
+ const hue = (value: string) => {
28
+ let h = 0
29
+ for (const c of value) h = (h * 31 + c.charCodeAt(0)) % 9973
30
+ return PEOPLE[h % PEOPLE.length]
31
+ }
32
+
33
+ const look = getLook()
34
+ const disc = $derived(plain || look.fidelity === 'minimal')
35
+ const toneValue: Tone = $derived(tone ?? (name ? 'person' : 'ink'))
36
+ const person = $derived(toneValue === 'person' && !disc ? `background:${hue(name)};color:#fff;` : '')
37
+ const words = $derived(name.trim().split(/\s+/).filter(Boolean))
38
+ const initials = $derived(
39
+ toneValue === 'ios' ? (words[0]?.[0] ?? '') + (words.length > 1 ? words[words.length - 1]![0] : '') : (name.trim()[0] ?? ''),
40
+ )
41
+ const fontSize = $derived(Math.round(size * (toneValue === 'ios' ? 0.4 : size < 24 ? 0.5 : 0.42)))
42
+ const css = $derived(`width:${size}px;height:${size}px;font-size:${fontSize}px;${person}${style}`)
43
+ </script>
44
+
45
+ <span
46
+ {...rest}
47
+ class={`ss-avatar${className ? ` ${className}` : ''}${klass ? ` ${klass}` : ''}`}
48
+ data-tone={toneValue}
49
+ data-ring={ring}
50
+ style={css}
51
+ >
52
+ {#if src && !disc}
53
+ <img {src} alt="" />
54
+ {:else if !disc}
55
+ <span class="ss-avatar__initial">{initials.toUpperCase()}</span>
56
+ {/if}
57
+ </span>
@@ -0,0 +1,20 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type Tone = 'accent' | 'ink' | 'success' | 'person' | 'ios';
3
+ type Ring = 'pending' | 'waiting' | 'confirmed' | 'declined';
4
+ interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, 'class'> {
5
+ /** Its initial. Leave it out (or set `plain`) for a plain disc. */
6
+ name?: string;
7
+ /** A plain disc in any fidelity: filler people, so they don't compete with the named one. */
8
+ plain?: boolean;
9
+ size?: number;
10
+ tone?: Tone;
11
+ src?: string;
12
+ ring?: Ring;
13
+ className?: string;
14
+ /** Svelte's own class attribute; merged after className. */
15
+ class?: string;
16
+ style?: string;
17
+ }
18
+ declare const Avatar: import("svelte").Component<Props, {}, "">;
19
+ type Avatar = ReturnType<typeof Avatar>;
20
+ export default Avatar;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements'
3
+
4
+ interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, 'class'> {
5
+ /** Width in px, or any CSS length. */
6
+ w: number | string
7
+ h?: number
8
+ /** 1 (faintest) to 5. */
9
+ tone?: 1 | 2 | 3 | 4 | 5
10
+ r?: number
11
+ /** Let it narrow in a row too tight for it. On by default for a line. */
12
+ shrink?: boolean
13
+ className?: string
14
+ /** Svelte's own class attribute; merged after className. */
15
+ class?: string
16
+ style?: string
17
+ }
18
+
19
+ let { w, h = 6, tone = 3, r = 2, shrink, className = '', class: klass = '', style = '', ...rest }: Props = $props()
20
+
21
+ const INK = ['var(--ss-ink-1)', 'var(--ss-ink-2)', 'var(--ss-ink-3)', 'var(--ss-ink-4)', 'var(--ss-ink-5)'] as const
22
+ const gives = $derived(shrink ?? (typeof w === 'number' && w > h * 3))
23
+ const width = $derived(typeof w === 'number' ? `${w}px` : w)
24
+ const css = $derived(`width:${width};height:${h}px;border-radius:${r}px;background:${INK[tone - 1]};${style}`)
25
+ </script>
26
+
27
+ <span {...rest} class={`ss-bar${className ? ` ${className}` : ''}${klass ? ` ${klass}` : ''}`} data-shrink={gives || undefined} style={css}></span>
@@ -0,0 +1,18 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ interface Props extends Omit<HTMLAttributes<HTMLSpanElement>, 'class'> {
3
+ /** Width in px, or any CSS length. */
4
+ w: number | string;
5
+ h?: number;
6
+ /** 1 (faintest) to 5. */
7
+ tone?: 1 | 2 | 3 | 4 | 5;
8
+ r?: number;
9
+ /** Let it narrow in a row too tight for it. On by default for a line. */
10
+ shrink?: boolean;
11
+ className?: string;
12
+ /** Svelte's own class attribute; merged after className. */
13
+ class?: string;
14
+ style?: string;
15
+ }
16
+ declare const Bar: import("svelte").Component<Props, {}, "">;
17
+ type Bar = ReturnType<typeof Bar>;
18
+ export default Bar;
@@ -0,0 +1,255 @@
1
+ <script lang="ts">
2
+ import { getContext, onDestroy, onMount, untrack } from 'svelte'
3
+ import type { Snippet } from 'svelte'
4
+ import type { HTMLAttributes } from 'svelte/elements'
5
+ import { getLook } from './look.js'
6
+ import { prefersReducedMotion, reducedMotion } from './reduced-motion.js'
7
+ import { THREAD_KEY, type ThreadContext } from './Thread.svelte'
8
+
9
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> {
10
+ /** me sits on the right in the accent bubble; them on the left. */
11
+ from: 'me' | 'them'
12
+ /** Three bouncing dots in place of the text. Turning it off grows the bubble into the message. */
13
+ typing?: boolean
14
+ /** Set by Thread: true on the last bubble of a run. */
15
+ tail?: boolean
16
+ /**
17
+ * Where the message comes from, as iMessage sends: a target name (or a
18
+ * CSS selector) for the composer's text. The bubble forms out of it, the
19
+ * words lifting from the field into place while the bubble fills in around
20
+ * them. Clear the field in the same update that adds the bubble.
21
+ */
22
+ source?: string
23
+ /**
24
+ * A tapback on this bubble: the emoji, and `show` to pop it in and out.
25
+ * It sits in a white circle on the bubble's bottom corner, and the bubble
26
+ * makes room below so the emoji never covers the words.
27
+ */
28
+ tapback?: { emoji: Snippet | string; show?: boolean }
29
+ /** A small `Avatar` beside a received bubble, as a group chat shows who is speaking. */
30
+ avatar?: Snippet
31
+ className?: string
32
+ /** Svelte's own class attribute; merged after className. */
33
+ class?: string
34
+ style?: string
35
+ /** Text, or a snippet for rich content. A string can also be passed to `children` directly. */
36
+ children?: Snippet | string
37
+ }
38
+
39
+ let {
40
+ avatar,
41
+ from,
42
+ typing = false,
43
+ tail,
44
+ source,
45
+ tapback,
46
+ className = '',
47
+ class: klass = '',
48
+ style = '',
49
+ children,
50
+ ...rest
51
+ }: Props = $props()
52
+
53
+ const { fidelity } = getLook()
54
+ const thread = getContext<ThreadContext | undefined>(THREAD_KEY)
55
+ // Bubbles in the thread on its first render are the opening picture and
56
+ // don't animate; only later arrivals rise in.
57
+ const arrived = thread ? thread.ready : true
58
+
59
+ let rowEl = $state<HTMLDivElement | null>(null)
60
+ let bubbleEl = $state<HTMLDivElement | null>(null)
61
+ let autoTail = $state<boolean | undefined>(undefined)
62
+ const effectiveTail = $derived(tail !== undefined ? tail : autoTail)
63
+
64
+ onMount(() => {
65
+ if (!thread) return
66
+ return thread.register({
67
+ row: () => rowEl,
68
+ from: () => from,
69
+ explicit: () => tail !== undefined,
70
+ setTail: (value) => {
71
+ autoTail = value
72
+ },
73
+ })
74
+ })
75
+
76
+ // Whether this bubble grew out of the typing dots (its words fade in as it
77
+ // grows). The flag latches on, as the React ref did.
78
+ let lastTyping = $state(untrack(() => typing))
79
+ let grew = $state(false)
80
+ $effect.pre(() => {
81
+ if (lastTyping && !typing) grew = true
82
+ lastTyping = typing
83
+ })
84
+
85
+ // Morph: measure the bubble before and after the content changes, then ease
86
+ // its box from the old size to the new.
87
+ let size: { w: number; h: number } | null = null
88
+ let animating = false
89
+ let morphTimer: ReturnType<typeof setTimeout> | null = null
90
+
91
+ function measure(node: HTMLElement) {
92
+ const measured = getComputedStyle(node)
93
+ return { w: parseFloat(measured.width), h: parseFloat(measured.height) }
94
+ }
95
+
96
+ function morph() {
97
+ const el = bubbleEl
98
+ if (!el || animating) return
99
+ const was = size
100
+ el.style.width = ''
101
+ el.style.height = ''
102
+ // Resolved CSS dimensions retain the fractions offsetWidth/Height round
103
+ // away, so releasing the measured box doesn't resize it at the endpoint.
104
+ const now = measure(el)
105
+ size = now
106
+ if (!was || (was.w === now.w && was.h === now.h)) return
107
+ if (reducedMotion(el)) return
108
+ animating = true
109
+ // The words keep their final line breaks while the box grows around them
110
+ const text = el.querySelector<HTMLElement>('.ss-bubble__text')
111
+ if (text) text.style.width = getComputedStyle(text).width
112
+ el.style.transition = 'none'
113
+ el.style.width = `${was.w}px`
114
+ el.style.height = `${was.h}px`
115
+ void el.offsetWidth
116
+ el.style.transition = ''
117
+ el.style.width = `${now.w}px`
118
+ el.style.height = `${now.h}px`
119
+ const end = () => {
120
+ if (morphTimer) {
121
+ clearTimeout(morphTimer)
122
+ morphTimer = null
123
+ }
124
+ el.removeEventListener('transitionend', end)
125
+ el.style.width = ''
126
+ el.style.height = ''
127
+ if (text) text.style.width = ''
128
+ animating = false
129
+ size = measure(el)
130
+ }
131
+ el.addEventListener('transitionend', end, { once: true })
132
+ // Reduced motion or a stage still can remove the transition entirely, so a
133
+ // timer releases the measured box even if transitionend never arrives.
134
+ morphTimer = setTimeout(end, 600)
135
+ }
136
+
137
+ $effect(() => {
138
+ void typing
139
+ void children
140
+ morph()
141
+ })
142
+
143
+ $effect(() => {
144
+ const el = bubbleEl
145
+ if (!el) return
146
+ size = measure(el)
147
+ const ro = new ResizeObserver(() => morph())
148
+ ro.observe(el)
149
+ return () => ro.disconnect()
150
+ })
151
+
152
+ onDestroy(() => {
153
+ if (morphTimer) clearTimeout(morphTimer)
154
+ })
155
+
156
+ // Form out of the composer: start where the field's words were, in the
157
+ // field's color with no fill, and travel into place as the fill comes in.
158
+ $effect(() => {
159
+ const el = bubbleEl
160
+ if (!el || !arrived || !source) return
161
+ const stage = el.closest('.ss-stage') ?? document
162
+ const src =
163
+ stage.querySelector<HTMLElement>(`[data-target="${source}"]`) ?? stage.querySelector<HTMLElement>(source)
164
+ const text = el.querySelector<HTMLElement>('.ss-bubble__text')
165
+ if (!src || !text) return
166
+ if (prefersReducedMotion() || el.closest('[data-still]')) return
167
+ // Measure where it rests, not where an earlier run already moved it
168
+ el.style.transition = 'none'
169
+ el.style.left = el.style.top = '0px'
170
+ const k = el.getBoundingClientRect().width / el.offsetWidth || 1
171
+ const o = src.getBoundingClientRect()
172
+ const t = text.getBoundingClientRect()
173
+ const dx = (o.left - t.left) / k
174
+ const dy = (o.top + o.height / 2 - (t.top + t.height / 2)) / k
175
+ const ease = 'cubic-bezier(0.22, 1, 0.36, 1)'
176
+ // Travels by left and top, so the words land without a sub-pixel hop
177
+ el.style.transition = 'none'
178
+ el.style.left = `${dx}px`
179
+ el.style.top = `${dy}px`
180
+ el.setAttribute('data-flying', '')
181
+ el.style.color = getComputedStyle(src).color
182
+ void el.offsetWidth
183
+ el.style.transition = `left 0.5s ${ease}, top 0.5s ${ease}, color 0.3s ease 0.06s`
184
+ el.style.left = el.style.top = ''
185
+ el.removeAttribute('data-flying')
186
+ el.style.color = ''
187
+ const end = (e: TransitionEvent) => {
188
+ if (e.target !== el || e.propertyName !== 'top') return
189
+ el.style.transition = ''
190
+ el.removeEventListener('transitionend', end)
191
+ }
192
+ el.addEventListener('transitionend', end)
193
+ return () => el.removeEventListener('transitionend', end)
194
+ })
195
+
196
+ const isBar = $derived(fidelity === 'minimal' && typeof children === 'string')
197
+ const barWidth = $derived(typeof children === 'string' ? Math.min(180, children.length * 5.5) : 0)
198
+ </script>
199
+
200
+ <div
201
+ class={`ss-bubble-row${className ? ` ${className}` : ''}${klass ? ` ${klass}` : ''}`}
202
+ data-from={from}
203
+ data-avatar={avatar ? '' : undefined}
204
+ style={style || undefined}
205
+ bind:this={rowEl}
206
+ >
207
+ {#if avatar && from !== 'me'}
208
+ <span class="ss-bubble__avatar" aria-hidden="true">{@render avatar()}</span>
209
+ {/if}
210
+ <div
211
+ {...rest}
212
+ bind:this={bubbleEl}
213
+ class="ss-bubble"
214
+ data-arrived={arrived || undefined}
215
+ data-source={source ? '' : undefined}
216
+ data-tail={effectiveTail || undefined}
217
+ data-typing={typing || undefined}
218
+ data-grew={grew || undefined}
219
+ data-tapback={tapback && tapback.show !== false ? '' : undefined}
220
+ >
221
+ {#if tapback}
222
+ <span class="ss-tapback" data-show={tapback.show !== false || undefined} aria-hidden="true">
223
+ <span class="ss-tapback__emoji">
224
+ {#if typeof tapback.emoji === 'string'}{tapback.emoji}{:else}{@render tapback.emoji()}{/if}
225
+ </span>
226
+ </span>
227
+ {/if}
228
+ {#if typing}
229
+ <span class="ss-bubble__dots" aria-label="typing">
230
+ <i></i>
231
+ <i></i>
232
+ <i></i>
233
+ </span>
234
+ {:else}
235
+ <span class="ss-bubble__text">
236
+ {#if isBar}
237
+ <span class="ss-bar ss-bubble__bar" style={`width:${barWidth}px`}></span>
238
+ {:else if typeof children === 'string'}
239
+ {children}
240
+ {:else if children}
241
+ {@render children()}
242
+ {/if}
243
+ </span>
244
+ {/if}
245
+ <!-- Always drawn, so a tail grows out of the corner when its bubble
246
+ becomes the last of a run, and retracts when it stops being. -->
247
+ <svg class="ss-bubble__tail" viewBox="0 0 18 16" aria-hidden="true">
248
+ <path
249
+ d={from === 'me'
250
+ ? 'M0 0H12V8.5C12 12.6 14 15 17.5 16C15 16.7 12.2 16.5 9.8 15.3L0 15Z'
251
+ : 'M18 0H6V8.5C6 12.6 4 15 0.5 16C3 16.7 5.8 16.5 8.2 15.3L18 15Z'}
252
+ />
253
+ </svg>
254
+ </div>
255
+ </div>
@@ -0,0 +1,37 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props extends Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> {
4
+ /** me sits on the right in the accent bubble; them on the left. */
5
+ from: 'me' | 'them';
6
+ /** Three bouncing dots in place of the text. Turning it off grows the bubble into the message. */
7
+ typing?: boolean;
8
+ /** Set by Thread: true on the last bubble of a run. */
9
+ tail?: boolean;
10
+ /**
11
+ * Where the message comes from, as iMessage sends: a target name (or a
12
+ * CSS selector) for the composer's text. The bubble forms out of it, the
13
+ * words lifting from the field into place while the bubble fills in around
14
+ * them. Clear the field in the same update that adds the bubble.
15
+ */
16
+ source?: string;
17
+ /**
18
+ * A tapback on this bubble: the emoji, and `show` to pop it in and out.
19
+ * It sits in a white circle on the bubble's bottom corner, and the bubble
20
+ * makes room below so the emoji never covers the words.
21
+ */
22
+ tapback?: {
23
+ emoji: Snippet | string;
24
+ show?: boolean;
25
+ };
26
+ /** A small `Avatar` beside a received bubble, as a group chat shows who is speaking. */
27
+ avatar?: Snippet;
28
+ className?: string;
29
+ /** Svelte's own class attribute; merged after className. */
30
+ class?: string;
31
+ style?: string;
32
+ /** Text, or a snippet for rich content. A string can also be passed to `children` directly. */
33
+ children?: Snippet | string;
34
+ }
35
+ declare const Bubble: import("svelte").Component<Props, {}, "">;
36
+ type Bubble = ReturnType<typeof Bubble>;
37
+ export default Bubble;
@@ -0,0 +1,10 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** While typing, a real terminal holds the cursor solid. */
4
+ still?: boolean
5
+ }
6
+
7
+ let { still = false }: Props = $props()
8
+ </script>
9
+
10
+ <span class="ss-term__caret" data-still={still || undefined} aria-hidden="true"></span>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ /** While typing, a real terminal holds the cursor solid. */
3
+ still?: boolean;
4
+ }
5
+ declare const Caret: import("svelte").Component<Props, {}, "">;
6
+ type Caret = ReturnType<typeof Caret>;
7
+ export default Caret;