paraframe 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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -2
  3. package/dist/Canvas.svelte +701 -0
  4. package/dist/Canvas.svelte.d.ts +9 -0
  5. package/dist/Comments.svelte +48 -0
  6. package/dist/Comments.svelte.d.ts +13 -0
  7. package/dist/FrameHost.svelte +135 -0
  8. package/dist/FrameHost.svelte.d.ts +8 -0
  9. package/dist/FrameLink.svelte +17 -0
  10. package/dist/FrameLink.svelte.d.ts +7 -0
  11. package/dist/Player.svelte +371 -0
  12. package/dist/Player.svelte.d.ts +8 -0
  13. package/dist/comments.d.ts +4 -0
  14. package/dist/comments.js +7 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.js +6 -0
  17. package/dist/internal/CanvasPreview.svelte +153 -0
  18. package/dist/internal/CanvasPreview.svelte.d.ts +15 -0
  19. package/dist/internal/Icon.svelte +44 -0
  20. package/dist/internal/Icon.svelte.d.ts +29 -0
  21. package/dist/internal/Menu.svelte +138 -0
  22. package/dist/internal/Menu.svelte.d.ts +15 -0
  23. package/dist/internal/MenuItem.svelte +40 -0
  24. package/dist/internal/MenuItem.svelte.d.ts +12 -0
  25. package/dist/internal/canvas-frame.d.ts +2 -0
  26. package/dist/internal/canvas-frame.js +88 -0
  27. package/dist/internal/canvas-load-queue.d.ts +7 -0
  28. package/dist/internal/canvas-load-queue.js +69 -0
  29. package/dist/internal/comments.d.ts +36 -0
  30. package/dist/internal/comments.js +14 -0
  31. package/dist/internal/storage.d.ts +3 -0
  32. package/dist/internal/storage.js +22 -0
  33. package/dist/messages.d.ts +26 -0
  34. package/dist/messages.js +27 -0
  35. package/dist/nav.d.ts +18 -0
  36. package/dist/nav.js +15 -0
  37. package/dist/prototype.d.ts +10 -0
  38. package/dist/prototype.js +78 -0
  39. package/dist/styles.css +2 -0
  40. package/dist/types.d.ts +100 -0
  41. package/dist/types.js +1 -0
  42. package/package.json +97 -5
@@ -0,0 +1,22 @@
1
+ // Per-viewer conveniences only. Storage can be unavailable (private windows, blocked
2
+ // site data), so every access is guarded and failures fall back to the default.
3
+ export function readStored(store, key, fallback) {
4
+ try {
5
+ const raw = (store === 'local' ? localStorage : sessionStorage).getItem(key);
6
+ return raw === null ? fallback : JSON.parse(raw);
7
+ }
8
+ catch {
9
+ return fallback;
10
+ }
11
+ }
12
+ export function writeStored(store, key, value) {
13
+ try {
14
+ (store === 'local' ? localStorage : sessionStorage).setItem(key, JSON.stringify(value));
15
+ }
16
+ catch {
17
+ // Not persisting is fine.
18
+ }
19
+ }
20
+ export function storageKey(prototypeTitle, name) {
21
+ return `paraframe:${prototypeTitle}:${name}`;
22
+ }
@@ -0,0 +1,26 @@
1
+ export type FrameMessage =
2
+ /** The frame has rendered and its fonts are loaded. */
3
+ {
4
+ type: 'paraframe:ready';
5
+ frame: string;
6
+ }
7
+ /** A link in the frame was followed. */
8
+ | {
9
+ type: 'paraframe:navigate';
10
+ to: string;
11
+ }
12
+ /** A navigation key was pressed while the frame had focus. */
13
+ | {
14
+ type: 'paraframe:key';
15
+ key: string;
16
+ };
17
+ export declare function isFrameMessage(data: unknown): data is FrameMessage;
18
+ export declare function postToHost(message: FrameMessage): void;
19
+ /**
20
+ * Keys the player handles. Frames forward them so shortcuts work while a
21
+ * frame has focus. Comments listen in the frame themselves for C and Escape.
22
+ */
23
+ export declare const PLAYER_KEYS: Set<string>;
24
+ export declare function isEditable(target: EventTarget | null): boolean;
25
+ /** Whether Enter on this target belongs to a focused control rather than to a global shortcut. */
26
+ export declare function activatesOnEnter(target: EventTarget | null): boolean;
@@ -0,0 +1,27 @@
1
+ // The protocol between a frame (inside an iframe) and the player around it.
2
+ const TYPES = new Set(['paraframe:ready', 'paraframe:navigate', 'paraframe:key']);
3
+ export function isFrameMessage(data) {
4
+ return (typeof data === 'object' &&
5
+ data !== null &&
6
+ TYPES.has(data.type));
7
+ }
8
+ export function postToHost(message) {
9
+ if (window.parent !== window)
10
+ window.parent.postMessage(message, location.origin);
11
+ }
12
+ /**
13
+ * Keys the player handles. Frames forward them so shortcuts work while a
14
+ * frame has focus. Comments listen in the frame themselves for C and Escape.
15
+ */
16
+ export const PLAYER_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'r', 'R']);
17
+ export function isEditable(target) {
18
+ return (target instanceof HTMLElement &&
19
+ (target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)));
20
+ }
21
+ // Controls that act on Enter themselves.
22
+ const ACTIVATES_ON_ENTER = 'a[href],button,summary,[role=button],[role=link],[role=tab],[role=checkbox],[role=switch],' +
23
+ '[role=radio],[role=option],[role=menuitem],[role=menuitemradio],[role=menuitemcheckbox]';
24
+ /** Whether Enter on this target belongs to a focused control rather than to a global shortcut. */
25
+ export function activatesOnEnter(target) {
26
+ return target instanceof Element && target.closest(ACTIVATES_ON_ENTER) !== null;
27
+ }
package/dist/nav.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { Attachment } from 'svelte/attachments';
2
+ /** Navigation between frames, available to any component inside a frame. */
3
+ export interface FrameNav {
4
+ /** Id of the frame being shown. */
5
+ readonly current: string;
6
+ /** URL that opens a frame in the player. */
7
+ href(frameId: string): string;
8
+ /** Shows another frame. */
9
+ go(frameId: string): void;
10
+ /**
11
+ * Turns any element into a link to another frame, e.g. a table row:
12
+ * `<tr {@attach nav.hotspot('detail')}>`.
13
+ */
14
+ hotspot(frameId: string): Attachment<HTMLElement>;
15
+ }
16
+ export declare function setFrameNav(nav: FrameNav): void;
17
+ /** Returns frame navigation. Outside a frame host, links do nothing. */
18
+ export declare function useFrameNav(): FrameNav;
package/dist/nav.js ADDED
@@ -0,0 +1,15 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const KEY = Symbol('paraframe.nav');
3
+ const detached = {
4
+ current: '',
5
+ href: () => '#',
6
+ go: () => { },
7
+ hotspot: () => () => { }
8
+ };
9
+ export function setFrameNav(nav) {
10
+ setContext(KEY, nav);
11
+ }
12
+ /** Returns frame navigation. Outside a frame host, links do nothing. */
13
+ export function useFrameNav() {
14
+ return getContext(KEY) ?? detached;
15
+ }
@@ -0,0 +1,10 @@
1
+ import type { FrameSize, Prototype, PrototypeDefinition, ResolvedFrame } from './types.js';
2
+ export declare const DEFAULT_SIZE: FrameSize;
3
+ /** Validates a prototype definition and indexes its frames. Throws on bad or duplicate ids. */
4
+ export declare function definePrototype(definition: PrototypeDefinition): Prototype;
5
+ /** Prerender entries for a `[frame]` route parameter. */
6
+ export declare function frameEntries(prototype: Prototype): {
7
+ frame: string;
8
+ }[];
9
+ /** Imports a frame's component. Use it in the frame route's `load`. Responds 404 for unknown ids. */
10
+ export declare function resolveFrame(prototype: Prototype, frameId: string): Promise<ResolvedFrame>;
@@ -0,0 +1,78 @@
1
+ import { error } from '@sveltejs/kit';
2
+ export const DEFAULT_SIZE = { width: 1440, height: 900 };
3
+ const ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
4
+ function assertId(kind, id) {
5
+ if (!ID.test(id)) {
6
+ throw new Error(`[paraframe] ${kind} id "${id}" must be lowercase letters, digits and dashes`);
7
+ }
8
+ }
9
+ /** Validates a prototype definition and indexes its frames. Throws on bad or duplicate ids. */
10
+ export function definePrototype(definition) {
11
+ const size = { ...DEFAULT_SIZE, ...definition.size };
12
+ const frames = [];
13
+ const byId = new Map();
14
+ const sectionIds = new Set();
15
+ const sections = definition.sections.map((def) => {
16
+ assertId('Section', def.id);
17
+ if (sectionIds.has(def.id))
18
+ throw new Error(`[paraframe] Duplicate section id "${def.id}"`);
19
+ sectionIds.add(def.id);
20
+ const section = { id: def.id, title: def.title, columns: def.columns, frames: [] };
21
+ for (const f of def.frames) {
22
+ assertId('Frame', f.id);
23
+ if (byId.has(f.id))
24
+ throw new Error(`[paraframe] Duplicate frame id "${f.id}"`);
25
+ const frame = {
26
+ id: f.id,
27
+ title: f.title,
28
+ path: f.path,
29
+ load: f.load,
30
+ props: f.props ?? {},
31
+ size: { ...size, ...f.size },
32
+ section,
33
+ index: frames.length
34
+ };
35
+ frames.push(frame);
36
+ byId.set(f.id, frame);
37
+ section.frames.push(frame);
38
+ }
39
+ return section;
40
+ });
41
+ const flows = (definition.flows ?? []).map((flow) => {
42
+ assertId('Flow', flow.id);
43
+ if (!byId.has(flow.start)) {
44
+ throw new Error(`[paraframe] Flow "${flow.id}" starts on unknown frame "${flow.start}"`);
45
+ }
46
+ return { ...flow };
47
+ });
48
+ return {
49
+ title: definition.title,
50
+ description: definition.description,
51
+ size,
52
+ sections,
53
+ frames,
54
+ flows,
55
+ paths: definition.paths,
56
+ get: (id) => byId.get(id),
57
+ next: (id) => {
58
+ const frame = byId.get(id);
59
+ return frame ? frames[frame.index + 1] : undefined;
60
+ },
61
+ prev: (id) => {
62
+ const frame = byId.get(id);
63
+ return frame && frame.index > 0 ? frames[frame.index - 1] : undefined;
64
+ }
65
+ };
66
+ }
67
+ /** Prerender entries for a `[frame]` route parameter. */
68
+ export function frameEntries(prototype) {
69
+ return prototype.frames.map((f) => ({ frame: f.id }));
70
+ }
71
+ /** Imports a frame's component. Use it in the frame route's `load`. Responds 404 for unknown ids. */
72
+ export async function resolveFrame(prototype, frameId) {
73
+ const frame = prototype.get(frameId);
74
+ if (!frame)
75
+ error(404, `No frame "${frameId}" in this prototype`);
76
+ const module = await frame.load();
77
+ return { frame, component: module.default };
78
+ }
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-semibold:600;--leading-relaxed:1.625;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.pf-toolbar{pointer-events:none;flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:.5rem;display:flex}.pf-island{background:var(--pf-island);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);min-width:0;max-width:100%;color:var(--pf-island-ink);pointer-events:auto;border-radius:9999px;align-items:center;gap:.125rem;padding:.25rem;display:flex}.pf-tool{min-width:2rem;height:2rem;color:var(--pf-island-ink);border-radius:9999px;outline:none;justify-content:center;align-items:center;display:inline-flex}.pf-tool:hover,.pf-tool[aria-expanded=true]{background:var(--pf-island-hover)}.pf-tool:disabled{opacity:.35;pointer-events:none}.pf-tool[aria-pressed=true],.pf-tool-primary{background:var(--pf-island-ink);color:var(--pf-ink)}.pf-tool-primary{gap:.375rem;padding-inline:.875rem 1rem;font-weight:500}.pf-tool-primary:hover{background:#ffffffd9}.pf-tool:focus-visible{box-shadow:0 0 0 2px var(--pf-island-muted)}.pf-tool-light{border-radius:.125rem;outline:none;justify-content:center;align-items:center;min-width:1.75rem;height:1.75rem;display:inline-flex}.pf-tool-light:hover{background:var(--pf-wash)}.pf-tool-light:focus-visible{box-shadow:0 0 0 2px var(--pf-accent)}}@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:0}.inset-auto{inset:auto}.inset-x-3{inset-inline:calc(var(--spacing) * 3)}.top-0{top:0}.top-1{top:var(--spacing)}.top-3{top:calc(var(--spacing) * 3)}.top-\[60px\]{top:60px}.right-1{right:var(--spacing)}.right-3{right:calc(var(--spacing) * 3)}.bottom-3{bottom:calc(var(--spacing) * 3)}.left-0{left:0}.z-10{z-index:10}.z-20{z-index:20}.m-0{margin:0}.m-3{margin:calc(var(--spacing) * 3)}.m-auto{margin:auto}.my-3{margin-block:calc(var(--spacing) * 3)}.mr-3{margin-right:calc(var(--spacing) * 3)}.mb-0{margin-bottom:0}.ml-auto{margin-left:auto}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-full{width:100%;height:100%}.h-5{height:calc(var(--spacing) * 5)}.h-dvh{height:100dvh}.max-h-\[min\(70vh\,560px\)\]{max-height:min(70vh,560px)}.min-h-0{min-height:0}.min-h-10{min-height:calc(var(--spacing) * 10)}.w-4{width:calc(var(--spacing) * 4)}.w-20{width:calc(var(--spacing) * 20)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.w-max{width:max-content}.max-w-48{max-width:calc(var(--spacing) * 48)}.max-w-60{max-width:calc(var(--spacing) * 60)}.max-w-\[calc\(100vw-24px\)\]{max-width:calc(100vw - 24px)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-16{min-width:calc(var(--spacing) * 16)}.min-w-52{min-width:calc(var(--spacing) * 52)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.origin-top-left{transform-origin:0 0}.cursor-grabbing{cursor:grabbing}.touch-none{touch-action:none}.resize{resize:both}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[16px\]{border-radius:16px}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-pf-line{border-color:var(--pf-line)}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.bg-pf-accent{background-color:var(--pf-accent)}.bg-pf-canvas{background-color:var(--pf-canvas)}.bg-pf-comment{background-color:var(--pf-comment)}.bg-pf-ink{background-color:var(--pf-ink)}.bg-pf-section{background-color:var(--pf-section)}.bg-pf-stage{background-color:var(--pf-stage)}.bg-pf-surface{background-color:var(--pf-surface)}.bg-white{background-color:var(--color-white)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-5{padding-right:calc(var(--spacing) * 5)}.pb-1{padding-bottom:var(--spacing)}.pl-1{padding-left:var(--spacing)}.pl-1\.5{padding-left:calc(var(--spacing) * 1.5)}.pl-3\.5{padding-left:calc(var(--spacing) * 3.5)}.text-center{text-align:center}.text-left{text-align:left}.font-pf{font-family:var(--pf-font)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-pf-accent{color:var(--pf-accent)}.text-pf-canvas-ink{color:var(--pf-canvas-ink)}.text-pf-canvas-muted{color:var(--pf-canvas-muted)}.text-pf-ink{color:var(--pf-ink)}.text-pf-island-muted{color:var(--pf-island-muted)}.text-pf-muted{color:var(--pf-muted)}.text-pf-selection{color:var(--pf-selection)}.text-pf-surface{color:var(--pf-surface)}.text-white\/90{color:#ffffffe6}@supports (color:color-mix(in lab, red, red)){.text-white\/90{color:color-mix(in oklab, var(--color-white) 90%, transparent)}}.lowercase{text-transform:lowercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.opacity-50{opacity:.5}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-pf-selection,.ring-pf-selection\/60{--tw-ring-color:var(--pf-selection)}@supports (color:color-mix(in lab, red, red)){.ring-pf-selection\/60{--tw-ring-color:color-mix(in oklab, var(--pf-selection) 60%, transparent)}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:bg-pf-selection\/10:hover{background-color:var(--pf-selection)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-pf-selection\/10:hover{background-color:color-mix(in oklab, var(--pf-selection) 10%, transparent)}}.hover\:bg-pf-wash:hover{background-color:var(--pf-wash)}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.hover\:text-pf-canvas-ink:hover{color:var(--pf-canvas-ink)}.hover\:brightness-110:hover{--tw-brightness:brightness(110%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus-visible\:bg-pf-wash:focus-visible{background-color:var(--pf-wash)}.focus-visible\:bg-white\/10:focus-visible{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.focus-visible\:bg-white\/10:focus-visible{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.data-\[pf-hover\=true\]\:pointer-events-auto[data-pf-hover=true]{pointer-events:auto}@media (min-width:40rem){.sm\:inline{display:inline}}@media (min-width:48rem){.md\:inline{display:inline}}}:root{--pf-font:ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--pf-ink:#292524;--pf-muted:#78716c;--pf-line:#d6d3d1;--pf-wash:#e7e5e4;--pf-surface:#f5f5f4;--pf-canvas:#1c1917;--pf-canvas-ink:#e7e5e4;--pf-canvas-muted:#a8a29e;--pf-selection:#f5f5f4;--pf-section:#292524;--pf-grid:#a8a29e33;--pf-stage:#1c1917;--pf-accent:#57534e;--pf-avatar:#009aaa;--pf-comment:#0d99ff;--pf-comment-soft:#e5f4ff;--vc-comment:var(--pf-comment);--vc-comment-soft:var(--pf-comment-soft);--vc-avatar:var(--pf-avatar);--vc-font:var(--pf-font);--pf-island:#32323291;--pf-island-ink:#fff;--pf-island-muted:#fff9;--pf-island-hover:#ffffff1f}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -0,0 +1,100 @@
1
+ import type { Component } from 'svelte';
2
+ export type FrameComponent = Component<any>;
3
+ /** A frame's viewport in CSS pixels. */
4
+ export interface FrameSize {
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface FrameDefinition {
9
+ /** URL-safe identifier: lowercase letters, digits and single dashes. */
10
+ id: string;
11
+ title: string;
12
+ /** Where the screen lives in the product, e.g. `/{account}/projects`. Shown beside the title. */
13
+ path?: string;
14
+ /** Imports the frame's component, e.g. `() => import('./Overview.svelte')`. */
15
+ load: () => Promise<{
16
+ default: FrameComponent;
17
+ }>;
18
+ /** Props passed to the component, so one component can back several frames. */
19
+ props?: Record<string, unknown>;
20
+ /** Overrides the prototype's default size. */
21
+ size?: Partial<FrameSize>;
22
+ }
23
+ export interface SectionDefinition {
24
+ id: string;
25
+ title: string;
26
+ frames: FrameDefinition[];
27
+ /** Wrap frames onto a new canvas row after this many. Default: one row. */
28
+ columns?: number;
29
+ }
30
+ /** A named starting point for clicking through the prototype. */
31
+ export interface FlowDefinition {
32
+ id: string;
33
+ title: string;
34
+ /** Id of the frame the flow starts on. */
35
+ start: string;
36
+ }
37
+ /**
38
+ * Where the host app mounts paraframe's three views. Build these with
39
+ * `resolve()` from `$app/paths` so base paths are respected, e.g.
40
+ * `play: (frame) => resolve(`play/${frame}`)`.
41
+ */
42
+ export interface PrototypePaths {
43
+ /** The canvas with every frame. */
44
+ canvas: () => string;
45
+ /** The player, showing one frame. */
46
+ play: (frameId: string) => string;
47
+ /** A frame rendered on its own, without paraframe's chrome. Loaded inside iframes. */
48
+ frame: (frameId: string) => string;
49
+ }
50
+ export interface PrototypeDefinition {
51
+ title: string;
52
+ description?: string;
53
+ /** Default frame size. Default: 1440 × 900. */
54
+ size?: Partial<FrameSize>;
55
+ sections: SectionDefinition[];
56
+ flows?: FlowDefinition[];
57
+ paths: PrototypePaths;
58
+ }
59
+ export interface Frame {
60
+ id: string;
61
+ title: string;
62
+ path: string | undefined;
63
+ load: FrameDefinition['load'];
64
+ props: Record<string, unknown>;
65
+ size: FrameSize;
66
+ section: Section;
67
+ /** Position in reading order across all sections. */
68
+ index: number;
69
+ }
70
+ export interface Section {
71
+ id: string;
72
+ title: string;
73
+ columns: number | undefined;
74
+ frames: Frame[];
75
+ }
76
+ export interface Flow {
77
+ id: string;
78
+ title: string;
79
+ start: string;
80
+ }
81
+ export interface Prototype {
82
+ title: string;
83
+ description: string | undefined;
84
+ size: FrameSize;
85
+ sections: Section[];
86
+ /** Every frame, in reading order. */
87
+ frames: Frame[];
88
+ flows: Flow[];
89
+ paths: PrototypePaths;
90
+ get(frameId: string): Frame | undefined;
91
+ /** The frame after this one in reading order, if any. */
92
+ next(frameId: string): Frame | undefined;
93
+ /** The frame before this one in reading order, if any. */
94
+ prev(frameId: string): Frame | undefined;
95
+ }
96
+ /** A frame with its component imported, ready to render. */
97
+ export interface ResolvedFrame {
98
+ frame: Frame;
99
+ component: FrameComponent;
100
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,98 @@
1
1
  {
2
- "name": "paraframe",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "paraframe",
3
+ "version": "0.2.0",
4
+ "description": "Click through Svelte prototypes like a Figma file: real Svelte components as frames, on a zoomable canvas and in a flow player.",
5
+ "keywords": [
6
+ "svelte",
7
+ "sveltekit",
8
+ "svelte5",
9
+ "prototype",
10
+ "prototyping",
11
+ "prototype-viewer",
12
+ "canvas",
13
+ "infinite-canvas",
14
+ "design-tools",
15
+ "figma",
16
+ "frames"
17
+ ],
18
+ "homepage": "https://paraframe.convex.works",
19
+ "bugs": "https://github.com/Convex-Works/paraframe/issues",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Convex-Works/paraframe.git"
23
+ },
24
+ "license": "MIT",
25
+ "author": "Convex Works",
26
+ "type": "module",
27
+ "packageManager": "pnpm@12.4.1",
28
+ "engines": {
29
+ "node": ">=22.17"
30
+ },
31
+ "scripts": {
32
+ "dev": "svelte-kit sync && svelte-package && pnpm css && (svelte-package --watch --preserve-output & pnpm css --watch)",
33
+ "package": "svelte-kit sync && svelte-package && pnpm css && publint",
34
+ "css": "tailwindcss -i src/styles.css -o dist/styles.css --minify",
35
+ "showcase": "vite dev",
36
+ "build": "vite build && pnpm package",
37
+ "preview": "vite preview",
38
+ "prepare": "svelte-kit sync || echo ''",
39
+ "prepack": "pnpm package",
40
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --tsgo",
41
+ "test": "vitest run"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "!dist/**/*.test.*",
46
+ "!dist/**/*.spec.*"
47
+ ],
48
+ "sideEffects": [
49
+ "**/*.css"
50
+ ],
51
+ "svelte": "./dist/index.js",
52
+ "types": "./dist/index.d.ts",
53
+ "exports": {
54
+ ".": {
55
+ "types": "./dist/index.d.ts",
56
+ "svelte": "./dist/index.js"
57
+ },
58
+ "./comments": {
59
+ "types": "./dist/comments.d.ts",
60
+ "svelte": "./dist/comments.js"
61
+ },
62
+ "./styles.css": "./dist/styles.css"
63
+ },
64
+ "peerDependencies": {
65
+ "@sveltejs/kit": "^3.0.0",
66
+ "svelte": "^5.57.1",
67
+ "visual-comments": "^0.1.0"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "visual-comments": {
71
+ "optional": true
72
+ }
73
+ },
74
+ "devDependencies": {
75
+ "@convex-works/sign-in": "link:../sign-in",
76
+ "@sveltejs/adapter-static": "^4.0.0",
77
+ "@sveltejs/kit": "^3.0.0",
78
+ "@sveltejs/package": "^3.0.0",
79
+ "@sveltejs/vite-plugin-svelte": "^7.2.0",
80
+ "@tailwindcss/cli": "^4.3.3",
81
+ "@tailwindcss/vite": "^4.3.0",
82
+ "@types/node": "^26.6.5",
83
+ "@typescript/native": "npm:typescript@^7.0.2",
84
+ "happy-dom": "^21.0.3",
85
+ "publint": "^0.3.21",
86
+ "svelte": "^5.57.1",
87
+ "svelte-check": "^4.7.6",
88
+ "tailwindcss": "^4.3.0",
89
+ "typescript": "~6.0.3",
90
+ "visual-comments": "^0.1.0",
91
+ "vite": "^8.3.0",
92
+ "vitest": "^5.0.5"
93
+ },
94
+ "imports": {
95
+ "#lib": "./src/lib/index.js",
96
+ "#lib/*": "./src/lib/*"
97
+ }
98
+ }