fancy-ui-svelte 0.5.0 → 0.6.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.
- package/README.md +22 -3
- package/dist/fancy-ui/dock/index.d.ts +1 -1
- package/dist/fancy-ui/dock/index.js +0 -1
- package/dist/fancy-ui/editorial-engine/EditorialEngine.svelte +181 -0
- package/dist/fancy-ui/editorial-engine/EditorialEngine.svelte.d.ts +26 -0
- package/dist/fancy-ui/editorial-engine/README.md +56 -0
- package/dist/fancy-ui/editorial-engine/engine.d.ts +7 -0
- package/dist/fancy-ui/editorial-engine/engine.js +581 -0
- package/dist/fancy-ui/editorial-engine/index.d.ts +2 -0
- package/dist/fancy-ui/editorial-engine/index.js +1 -0
- package/dist/fancy-ui/index.d.ts +4 -0
- package/dist/fancy-ui/index.js +4 -0
- package/dist/fancy-ui/line-reveal/LineReveal.svelte +168 -0
- package/dist/fancy-ui/line-reveal/LineReveal.svelte.d.ts +33 -0
- package/dist/fancy-ui/line-reveal/README.md +42 -0
- package/dist/fancy-ui/line-reveal/index.d.ts +2 -0
- package/dist/fancy-ui/line-reveal/index.js +1 -0
- package/dist/fancy-ui/noise-reveal/NoiseReveal.svelte +323 -0
- package/dist/fancy-ui/noise-reveal/NoiseReveal.svelte.d.ts +19 -0
- package/dist/fancy-ui/noise-reveal/NoiseReveal.test.d.ts +1 -0
- package/dist/fancy-ui/noise-reveal/NoiseReveal.test.js +123 -0
- package/dist/fancy-ui/noise-reveal/index.d.ts +2 -0
- package/dist/fancy-ui/noise-reveal/index.js +1 -0
- package/dist/fancy-ui/registry.js +128 -11
- package/dist/types.d.ts +2 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Beautiful animation and UI components for **Svelte 5**.
|
|
|
5
5
|

|
|
6
6
|

|
|
7
7
|

|
|
8
|
-

|
|
9
9
|

|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -23,7 +23,7 @@ Beautiful animation and UI components for **Svelte 5**.
|
|
|
23
23
|
- **Svelte 5 Runes** — Built with `$state`, `$derived`, `$effect`, and `$props`
|
|
24
24
|
- **Tailwind CSS 4** — Utility-first styling with theme tokens
|
|
25
25
|
- **TypeScript** — Fully typed props and events
|
|
26
|
-
- **
|
|
26
|
+
- **56 Components** — Buttons, text animations, backgrounds, effects, and more
|
|
27
27
|
- **Dark Mode** — All components support light and dark themes
|
|
28
28
|
- **Tested** — Component tests with Vitest and Testing Library
|
|
29
29
|
|
|
@@ -162,6 +162,25 @@ pnpm install
|
|
|
162
162
|
pnpm dev
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
## Using with AI agents (Claude Code, Cursor, Copilot)
|
|
166
|
+
|
|
167
|
+
The docs site serves LLM-friendly documentation following the [llms.txt](https://llmstxt.org) convention:
|
|
168
|
+
|
|
169
|
+
- [fancy-ui.rama.app/llms.txt](https://fancy-ui.rama.app/llms.txt) — setup guide, usage rules, and component index
|
|
170
|
+
- [fancy-ui.rama.app/llms-full.txt](https://fancy-ui.rama.app/llms-full.txt) — full reference with every component's props
|
|
171
|
+
|
|
172
|
+
To make your coding agent use fancy-ui correctly, add this to your project's `CLAUDE.md`, `AGENTS.md`, or `.cursorrules`:
|
|
173
|
+
|
|
174
|
+
```markdown
|
|
175
|
+
## UI components
|
|
176
|
+
|
|
177
|
+
Use fancy-ui-svelte (Svelte 5 + Tailwind CSS v4) for animated UI components.
|
|
178
|
+
Full component and props reference: https://fancy-ui.rama.app/llms-full.txt
|
|
179
|
+
Key rules: overlay/effect components (BorderBeam, GlowBorder, backgrounds, ...)
|
|
180
|
+
need a parent with `relative overflow-hidden`; cursor effects (FluidCursor,
|
|
181
|
+
SmoothCursor) mount once in the root +layout.svelte; Svelte 5 syntax only.
|
|
182
|
+
```
|
|
183
|
+
|
|
165
184
|
## Development
|
|
166
185
|
|
|
167
186
|
```bash
|
|
@@ -195,7 +214,7 @@ src/
|
|
|
195
214
|
|
|
196
215
|
## Contributing
|
|
197
216
|
|
|
198
|
-
Contributions are welcome!
|
|
217
|
+
Contributions are welcome! 56 components and counting — PRs for new components, bug fixes, and improvements are appreciated.
|
|
199
218
|
|
|
200
219
|
### Adding a new component
|
|
201
220
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default as Dock } from "./Dock.svelte";
|
|
2
2
|
export { default as DockIcon } from "./DockIcon.svelte";
|
|
3
3
|
export { default as DockSeparator } from "./DockSeparator.svelte";
|
|
4
|
-
export
|
|
4
|
+
export type { DataOrientation, Direction, DockContext } from "./types.js";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
/**
|
|
3
|
+
* Props for EditorialEngine
|
|
4
|
+
*
|
|
5
|
+
* Live magazine layout: multi-column text flow with cursor handoff,
|
|
6
|
+
* auto-fitted headline, drop cap, pullquotes, and body text reflowing in
|
|
7
|
+
* real time around draggable animated orbs. All line breaking is computed
|
|
8
|
+
* by @chenglou/pretext — the render loop performs zero DOM reads.
|
|
9
|
+
*
|
|
10
|
+
* Inspired by the pretext "editorial engine" demo
|
|
11
|
+
* (https://chenglou.me/pretext/editorial-engine/).
|
|
12
|
+
*/
|
|
13
|
+
export interface EditorialEngineProps {
|
|
14
|
+
/** Headline, auto-sized to the widest font that breaks no word */
|
|
15
|
+
headline?: string;
|
|
16
|
+
/** Body copy (plain text, paragraphs separated by blank lines) */
|
|
17
|
+
body?: string;
|
|
18
|
+
/** Up to two pullquotes embedded in the columns */
|
|
19
|
+
pullquotes?: string[];
|
|
20
|
+
/** Font family stack used for all text */
|
|
21
|
+
fontFamily?: string;
|
|
22
|
+
/** Additional CSS classes for the stage */
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<script lang="ts">
|
|
28
|
+
import { cn } from "../../utils.js";
|
|
29
|
+
import { createEditorialEngine } from "./engine.js";
|
|
30
|
+
|
|
31
|
+
const DEFAULT_HEADLINE = "TEXT THAT FLOWS AROUND ANYTHING";
|
|
32
|
+
|
|
33
|
+
const DEFAULT_BODY = `Print designers have flowed text around images, illustrations, and odd-shaped figures for more than a century. The web never caught up. CSS gives us float, and more recently shape-outside, but both only push text away from one side of one static box. Flowing text through multiple columns, around several moving obstacles at once, filling the gaps on both sides — the browser has no answer for that.
|
|
34
|
+
|
|
35
|
+
The reason is not a lack of imagination. It is the cost of asking. Every question about text — how wide is this word, where does this line break, how tall is this paragraph — must go through the browser's layout engine, and the layout engine answers by reflowing the document. Ask too often and the interface stutters. So we learned not to ask, and our text stayed in rigid rectangles.
|
|
36
|
+
|
|
37
|
+
This page asks hundreds of times per frame. Every line of text you are reading was positioned by arithmetic, not by the browser. The pretext library measured every word once, up front, against a canvas font engine. After that, layout is pure math: given a width, it returns line breaks, line widths, and a cursor pointing at exactly where the text stopped.
|
|
38
|
+
|
|
39
|
+
That cursor is what makes columns work. The first column consumes text until it runs out of vertical space, then hands its cursor to the second column, which continues mid-sentence without duplication or gaps. Newspapers have done this on paper forever; on the web it has always required hidden elements and fragile content splitting.
|
|
40
|
+
|
|
41
|
+
The orbs drifting across the page are obstacles, not decoration. Each frame, the engine intersects every text line's vertical band with every orb, subtracts the blocked intervals from the column width, and fills every remaining slot — text flows on both sides of an orb at once, something shape-outside cannot express at all.
|
|
42
|
+
|
|
43
|
+
Drag an orb and the paragraphs part around it in real time. Click one and it pauses. Resize the window and the headline re-fits itself, searching for the largest font size that breaks no word across lines — a binary search that costs microseconds because each probe is arithmetic, not reflow.
|
|
44
|
+
|
|
45
|
+
The drop cap at the top of this article is another obstacle: a rectangle carved out of the first column's first three lines. The pullquotes are the same trick at a larger scale. Everything you see is one mechanism — carve the blocked intervals out of each line's band, lay out text in whatever space remains.
|
|
46
|
+
|
|
47
|
+
When the engine finishes a frame, it writes positions and text content to a pool of absolutely positioned elements, and nothing else. The browser never computes layout for this page's body text, because every coordinate is already known. A full reflow of every line around five moving obstacles typically costs a fraction of a millisecond.
|
|
48
|
+
|
|
49
|
+
Cheap text measurement changes what interfaces can be. Virtualized lists can know every item's exact height before rendering it. Chat bubbles can shrink-wrap to their tightest width. Labels can dodge chart elements as you zoom. Documents can flow around widgets that other people are dragging in real time.
|
|
50
|
+
|
|
51
|
+
None of this needs a new browser API or a standards process. It needs cached font metrics, a line breaker, and the willingness to stop asking the DOM questions it answers slowly. The text on this page stopped asking. And so it flows.`;
|
|
52
|
+
|
|
53
|
+
const DEFAULT_PULLQUOTES = [
|
|
54
|
+
"“Text flows on both sides of a moving obstacle at once — something shape-outside cannot express at all.”",
|
|
55
|
+
"“Every coordinate is already known. The browser never computes layout for this text.”",
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const DEFAULT_FONT_FAMILY =
|
|
59
|
+
'"Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serif';
|
|
60
|
+
|
|
61
|
+
let {
|
|
62
|
+
headline = DEFAULT_HEADLINE,
|
|
63
|
+
body = DEFAULT_BODY,
|
|
64
|
+
pullquotes = DEFAULT_PULLQUOTES,
|
|
65
|
+
fontFamily = DEFAULT_FONT_FAMILY,
|
|
66
|
+
class: className,
|
|
67
|
+
}: EditorialEngineProps = $props();
|
|
68
|
+
|
|
69
|
+
let stageRef: HTMLDivElement;
|
|
70
|
+
let ready = $state(false);
|
|
71
|
+
|
|
72
|
+
// Re-created whenever the text props change, so the prepared measurements
|
|
73
|
+
// always match the rendered content. Effects never run during SSR.
|
|
74
|
+
$effect(() => {
|
|
75
|
+
const options = { headline, body, pullquotes, fontFamily };
|
|
76
|
+
let destroy: (() => void) | null = null;
|
|
77
|
+
let cancelled = false;
|
|
78
|
+
|
|
79
|
+
// pretext measures against the canvas font engine — fonts must be
|
|
80
|
+
// loaded first or widths come from the fallback font.
|
|
81
|
+
document.fonts.ready.then(() => {
|
|
82
|
+
if (cancelled) return;
|
|
83
|
+
destroy = createEditorialEngine(stageRef, options);
|
|
84
|
+
ready = true;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
cancelled = true;
|
|
89
|
+
destroy?.();
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<div
|
|
95
|
+
bind:this={stageRef}
|
|
96
|
+
class={cn("ee-stage", className)}
|
|
97
|
+
style:font-family={fontFamily}
|
|
98
|
+
class:ready
|
|
99
|
+
>
|
|
100
|
+
{#if !ready}
|
|
101
|
+
<!-- SSR / pre-engine fallback: keeps the content in the HTML -->
|
|
102
|
+
<div class="ee-fallback" aria-hidden="false">
|
|
103
|
+
<h2>{headline}</h2>
|
|
104
|
+
<p>{body}</p>
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<style>
|
|
110
|
+
.ee-stage {
|
|
111
|
+
position: relative;
|
|
112
|
+
width: 100%;
|
|
113
|
+
height: 100%;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
background: radial-gradient(ellipse at 50% 40%, #0f0f14 0%, #0a0a0c 100%);
|
|
116
|
+
color: #e8e4dc;
|
|
117
|
+
touch-action: none;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ee-fallback {
|
|
121
|
+
position: absolute;
|
|
122
|
+
inset: 0;
|
|
123
|
+
padding: 48px;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
opacity: 0.4;
|
|
126
|
+
font-size: 14px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.ee-stage :global(.ee-line) {
|
|
130
|
+
position: absolute;
|
|
131
|
+
white-space: pre;
|
|
132
|
+
z-index: 1;
|
|
133
|
+
color: #e8e4dc;
|
|
134
|
+
user-select: text;
|
|
135
|
+
-webkit-user-select: text;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.ee-stage :global(.ee-headline-line) {
|
|
139
|
+
position: absolute;
|
|
140
|
+
white-space: pre;
|
|
141
|
+
font-weight: 700;
|
|
142
|
+
color: #fff;
|
|
143
|
+
letter-spacing: -0.5px;
|
|
144
|
+
z-index: 2;
|
|
145
|
+
user-select: text;
|
|
146
|
+
-webkit-user-select: text;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.ee-stage :global(.ee-drop-cap) {
|
|
150
|
+
position: absolute;
|
|
151
|
+
pointer-events: none;
|
|
152
|
+
z-index: 2;
|
|
153
|
+
font-weight: 700;
|
|
154
|
+
color: #c4a35a;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.ee-stage :global(.ee-orb) {
|
|
158
|
+
position: absolute;
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
pointer-events: none;
|
|
161
|
+
z-index: 10;
|
|
162
|
+
will-change: transform;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.ee-stage :global(.ee-pullquote-box) {
|
|
166
|
+
position: absolute;
|
|
167
|
+
pointer-events: none;
|
|
168
|
+
z-index: 3;
|
|
169
|
+
border-left: 3px solid #6b5a3d;
|
|
170
|
+
padding-left: 14px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ee-stage :global(.ee-pullquote-line) {
|
|
174
|
+
position: absolute;
|
|
175
|
+
white-space: pre;
|
|
176
|
+
font-style: italic;
|
|
177
|
+
color: #b8a070;
|
|
178
|
+
user-select: text;
|
|
179
|
+
-webkit-user-select: text;
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for EditorialEngine
|
|
3
|
+
*
|
|
4
|
+
* Live magazine layout: multi-column text flow with cursor handoff,
|
|
5
|
+
* auto-fitted headline, drop cap, pullquotes, and body text reflowing in
|
|
6
|
+
* real time around draggable animated orbs. All line breaking is computed
|
|
7
|
+
* by @chenglou/pretext — the render loop performs zero DOM reads.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by the pretext "editorial engine" demo
|
|
10
|
+
* (https://chenglou.me/pretext/editorial-engine/).
|
|
11
|
+
*/
|
|
12
|
+
export interface EditorialEngineProps {
|
|
13
|
+
/** Headline, auto-sized to the widest font that breaks no word */
|
|
14
|
+
headline?: string;
|
|
15
|
+
/** Body copy (plain text, paragraphs separated by blank lines) */
|
|
16
|
+
body?: string;
|
|
17
|
+
/** Up to two pullquotes embedded in the columns */
|
|
18
|
+
pullquotes?: string[];
|
|
19
|
+
/** Font family stack used for all text */
|
|
20
|
+
fontFamily?: string;
|
|
21
|
+
/** Additional CSS classes for the stage */
|
|
22
|
+
class?: string;
|
|
23
|
+
}
|
|
24
|
+
declare const EditorialEngine: import("svelte").Component<EditorialEngineProps, {}, "">;
|
|
25
|
+
type EditorialEngine = ReturnType<typeof EditorialEngine>;
|
|
26
|
+
export default EditorialEngine;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# EditorialEngine
|
|
2
|
+
|
|
3
|
+
Live magazine layout powered by [@chenglou/pretext](https://github.com/chenglou/pretext):
|
|
4
|
+
multi-column text flow with cursor handoff, auto-fitted headline, drop cap,
|
|
5
|
+
pullquotes, and body text reflowing in real time around draggable animated
|
|
6
|
+
orbs. The render loop performs **zero DOM reads** — every line position is
|
|
7
|
+
computed arithmetically from cached font metrics, and the only DOM operations
|
|
8
|
+
are writes (`left`, `top`, `textContent`).
|
|
9
|
+
|
|
10
|
+
Inspired by the pretext
|
|
11
|
+
[editorial engine demo](https://chenglou.me/pretext/editorial-engine/).
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
- `prepareWithSegments(body, font)` measures the text once against the canvas
|
|
16
|
+
font engine.
|
|
17
|
+
- Each frame, every text line's vertical band is intersected with each orb;
|
|
18
|
+
blocked horizontal intervals are subtracted from the column width and
|
|
19
|
+
`layoutNextLine` fills every remaining slot — text flows on **both sides**
|
|
20
|
+
of an obstacle, which CSS `shape-outside` cannot do.
|
|
21
|
+
- Columns hand a `LayoutCursor` to one another, so text continues
|
|
22
|
+
mid-sentence across column boundaries like print.
|
|
23
|
+
- The headline binary-searches the largest font size that breaks no word
|
|
24
|
+
(`walkLineRanges` to detect intra-word breaks).
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```svelte
|
|
29
|
+
<script lang="ts">
|
|
30
|
+
import { EditorialEngine } from "$lib/fancy-ui/editorial-engine";
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div class="h-[85vh]">
|
|
34
|
+
<EditorialEngine />
|
|
35
|
+
</div>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The stage fills its parent — give the parent an explicit height.
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
| ------------ | ---------- | -------------------- | ------------------------------------------------- |
|
|
44
|
+
| `headline` | `string` | built-in copy | Auto-sized headline (largest size, no word break) |
|
|
45
|
+
| `body` | `string` | built-in copy | Body text, paragraphs separated by blank lines |
|
|
46
|
+
| `pullquotes` | `string[]` | built-in quotes | Up to two pullquotes embedded in the columns |
|
|
47
|
+
| `fontFamily` | `string` | Palatino serif stack | Font stack for all text |
|
|
48
|
+
| `class` | `string` | — | Additional CSS classes for the stage |
|
|
49
|
+
|
|
50
|
+
## Interactions
|
|
51
|
+
|
|
52
|
+
- **Drag** an orb — text reparts around it every frame.
|
|
53
|
+
- **Click** an orb — pauses/resumes its drift.
|
|
54
|
+
- **Resize** — headline re-fits, column count adapts (1/2/3), text reflows.
|
|
55
|
+
- Text stays selectable; rendering pauses while a selection is active.
|
|
56
|
+
- `prefers-reduced-motion` starts all orbs paused (still draggable).
|