@fluritheme/grinch 0.0.0-stage → 0.1.1
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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +188 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +508 -0
- package/dist/index.js.map +1 -0
- package/dist/layers.d.ts +12 -0
- package/dist/layers.d.ts.map +1 -0
- package/dist/layers.js +58 -0
- package/dist/layers.js.map +1 -0
- package/dist/model.d.ts +67 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +404 -0
- package/dist/model.js.map +1 -0
- package/dist/options.d.ts +26 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +89 -0
- package/dist/options.js.map +1 -0
- package/dist/paint.d.ts +9 -0
- package/dist/paint.d.ts.map +1 -0
- package/dist/paint.js +355 -0
- package/dist/paint.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +81 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1 — 2026-11-14
|
|
4
|
+
|
|
5
|
+
- Point the npm Homepage link to https://fluri.xyz/. The Grinch playground and documentation remain at grinch.fluri.xyz.
|
|
6
|
+
- Keep the animation runtime unchanged.
|
|
7
|
+
|
|
8
|
+
## 0.1.0 — 2026-11-14
|
|
9
|
+
|
|
10
|
+
- Introduce an original animated green holiday visitor with articulated limbs, blinking, a gift sack and three entrance styles.
|
|
11
|
+
- Add gift heists with approach, climb, reach, lift, stash and celebration phases.
|
|
12
|
+
- Add object-aware hiding, peeking, pointer reactions, tap-to-catch, animated returns and a kindness mode.
|
|
13
|
+
- Anchor gifts to document elements and support root clipping, nested scrolling and live layout changes.
|
|
14
|
+
- Provide a typed controller, events, live tuning, reduced-motion support, pause, SSR-safe imports and complete cleanup.
|
|
15
|
+
- Include a responsive playground, real runtime previews and framework integration examples.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fluri contributors
|
|
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/README.md
CHANGED
|
@@ -1,3 +1,189 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Grinch by Fluri
|
|
4
|
+
|
|
5
|
+
**A little green. A little greed. A lot of personality.**
|
|
6
|
+
|
|
7
|
+
A mischievous holiday visitor who sneaks around your website, steals presents, hides behind cards, and discovers a softer side of Christmas.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@fluritheme/grinch)
|
|
10
|
+
[](https://www.npmjs.com/package/@fluritheme/grinch)
|
|
11
|
+
[](https://github.com/fluritheme/grinch/blob/main/LICENSE)
|
|
12
|
+
|
|
13
|
+
[Live playground](https://grinch.fluri.xyz/) · [All Fluri themes](https://fluri.xyz/) · [Source](https://github.com/fluritheme/grinch)
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
## Meet your website's least innocent resident
|
|
20
|
+
|
|
21
|
+
- **An actual little heist.** He approaches a present, reaches for it, lifts it, and puts it in his sack. Presents stay on their ledges until he picks them up.
|
|
22
|
+
- **Real places to hide.** He ducks behind page objects, peeks out, then climbs back onto a ledge. The sack gets fuller with every successful theft.
|
|
23
|
+
- **Catch him in the act.** Move the pointer close or tap him to startle him. A stolen present flies back to its original element.
|
|
24
|
+
- **A change of heart.** Kindness mode warms his heart, returns the entire sack, and turns him into a friendly visitor.
|
|
25
|
+
- **A proper entrance.** Sneak in from the side, descend on a white rope, or appear immediately.
|
|
26
|
+
- **Your content stays yours.** Presents are illustrated props. Buttons, images, text and links are never stolen or rearranged.
|
|
27
|
+
- **Small and considerate.** Zero runtime dependencies, no runtime network requests, bounded gift count, one Canvas, reduced-motion support, pause/resume and complete cleanup.
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
These previews come from the running package. The character and presents are original procedural vector artwork, rendered in Canvas.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Add a little holiday mischief to any website. No CSS import, API key or runtime network request. Try every behavior in the [playground](https://grinch.fluri.xyz/).
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm i @fluritheme/grinch
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createGrinch } from '@fluritheme/grinch';
|
|
43
|
+
|
|
44
|
+
const visitor = createGrinch({
|
|
45
|
+
targets: '[data-grinch-gift]',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// When the page unmounts:
|
|
49
|
+
visitor.destroy();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Mark a few elements with room above them for a tiny visitor and a present:
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
<article data-grinch-gift data-grinch-object>A little room for holiday mischief.</article>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Without `targets`, the package uses `[data-grinch-gift]` elements first, then common cards and buttons. Use `[data-grinch-ignore]` on an element or ancestor to exclude it. `data-grinch-object` marks objects he can hide behind; the default object selector also recognizes cards, images, articles and buttons.
|
|
59
|
+
|
|
60
|
+
## Try a different ending
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
visitor.steal(); // Start a heist when he is ready
|
|
64
|
+
visitor.steal('#special-card'); // Choose a tracked gift
|
|
65
|
+
visitor.shoo(); // Catch him and return one present
|
|
66
|
+
visitor.returnGifts(); // Return the sack along animated arcs
|
|
67
|
+
visitor.redeem(); // Return everything; switch to friendly
|
|
68
|
+
visitor.wave(); // Say hello
|
|
69
|
+
|
|
70
|
+
visitor.update({ size: 1.2, speed: 0.8, fearRadius: 120 });
|
|
71
|
+
visitor.update({ mood: 'mischievous' }); // Allow mischief again
|
|
72
|
+
visitor.pause();
|
|
73
|
+
visitor.resume();
|
|
74
|
+
visitor.refresh(); // Request a layout measurement
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`steal()` returns `false` while he is busy, paused, in friendly mode, or respecting reduced motion. A selector must match a gift already tracked by this instance. Calls do not queue extra thefts.
|
|
78
|
+
|
|
79
|
+
## React / Next.js
|
|
80
|
+
|
|
81
|
+
Create the visitor after mounting, and return its cleanup. For Next.js, use a client component.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
'use client';
|
|
85
|
+
|
|
86
|
+
import { useEffect } from 'react';
|
|
87
|
+
import { createGrinch } from '@fluritheme/grinch';
|
|
88
|
+
|
|
89
|
+
export function HolidayVisitor() {
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const visitor = createGrinch({ targets: '[data-grinch-gift]' });
|
|
92
|
+
return () => visitor.destroy();
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
return <article data-grinch-gift>Your holiday card</article>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Vue
|
|
100
|
+
|
|
101
|
+
```vue
|
|
102
|
+
<script setup lang="ts">
|
|
103
|
+
import { onMounted, onUnmounted } from 'vue';
|
|
104
|
+
import { createGrinch, type GrinchController } from '@fluritheme/grinch';
|
|
105
|
+
|
|
106
|
+
let visitor: GrinchController | undefined;
|
|
107
|
+
onMounted(() => {
|
|
108
|
+
visitor = createGrinch();
|
|
109
|
+
});
|
|
110
|
+
onUnmounted(() => visitor?.destroy());
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<article data-grinch-gift>Your holiday card</article>
|
|
115
|
+
</template>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Keep him inside a section
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const root = document.querySelector<HTMLElement>('#holiday-section')!;
|
|
122
|
+
const visitor = createGrinch({ root, territory: 'root' });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This clips the scene to the root's rectangle and selects gifts from inside it. The default `territory: 'page'` allows movement around the document. Coordinates are anchored to the document, so scrolling does not attach the visitor to your viewport. Nested scrolling and moving gift anchors are measured too.
|
|
126
|
+
|
|
127
|
+
## Options
|
|
128
|
+
|
|
129
|
+
| Option | Default | Purpose |
|
|
130
|
+
| ------------- | --------------------- | ---------------------------------------- |
|
|
131
|
+
| `root` | `document` | Element to search for gifts |
|
|
132
|
+
| `targets` | Automatic | CSS selector or array of elements |
|
|
133
|
+
| `territory` | `'page'` | `'page'` or `'root'` |
|
|
134
|
+
| `objects` | Common cards/elements | Selector for objects he can hide behind |
|
|
135
|
+
| `maxGifts` | `5` | Maximum tracked presents, 1–12 |
|
|
136
|
+
| `size` | `1` | Character and present scale, 0.6–1.8 |
|
|
137
|
+
| `mobileScale` | `0.85` | Additional scale below 600px, 0.6–1 |
|
|
138
|
+
| `speed` | `1` | Movement multiplier, 0.25–2 |
|
|
139
|
+
| `cooldown` | `10` | Seconds between completed heists, 3–120 |
|
|
140
|
+
| `fearRadius` | `90` | Pointer reaction radius; `0` disables it |
|
|
141
|
+
| `mood` | `'mischievous'` | Or `'friendly'` for a gentler visitor |
|
|
142
|
+
| `autoSteal` | `true` | Start heists automatically |
|
|
143
|
+
| `entrance` | `'sneak'` | `'sneak'`, `'rope'`, or `'appear'` |
|
|
144
|
+
| `motion` | `'system'` | `'system'`, `'full'`, or `'none'` |
|
|
145
|
+
| `observe` | `true` | Observe layout and content changes |
|
|
146
|
+
| `zIndex` | `140` | Canvas stacking position |
|
|
147
|
+
| `furColor` | `'#79ad30'` | Six-digit hex color |
|
|
148
|
+
| `coatColor` | `'#c92c42'` | Six-digit hex color |
|
|
149
|
+
| `controls` | `false` | Add an accessible visitor pause button |
|
|
150
|
+
|
|
151
|
+
`update()` accepts size, mobileScale, speed, cooldown, fearRadius, mood, autoSteal, furColor and coatColor. Configuration is validated before mounting; invalid values throw. The palette stays stable when he crosses dark or light content.
|
|
152
|
+
|
|
153
|
+
## Events and state
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const stop = visitor.on('gift:stolen', ({ giftId, totalStolen }) => {
|
|
157
|
+
console.log(giftId, totalStolen);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
visitor.on('redeemed', () => console.log('A little kindness wins.'));
|
|
161
|
+
const snapshot = visitor.getState();
|
|
162
|
+
console.log(snapshot.phase, snapshot.stolen, snapshot.gifts);
|
|
163
|
+
stop();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Events: `ready`, `phase`, `theft:start`, `gift:stolen`, `gift:returned`, `caught`, `redeemed`, `error`. `getState()` returns a copy with lifecycle, mood, phase, character coordinates, gift states, current stolen count, lifetime total and caught count. Coordinates are CSS pixels in the document; character coordinates describe his feet.
|
|
167
|
+
|
|
168
|
+
## Motion, rendering and layout
|
|
169
|
+
|
|
170
|
+
The default respects `prefers-reduced-motion`: a still resident, no automatic thefts. Hidden tabs stop animation. `pause()` freezes the routine while retaining layout measurements; `destroy()` removes the Canvas, controls, observers, event listeners and animation loop. Importing on the server is safe and does not create a browser instance.
|
|
171
|
+
|
|
172
|
+
The Canvas ignores pointer events, so website controls remain clickable. Hosts can provide keyboard-accessible buttons calling `shoo()`, `redeem()` and `pause()`, as the playground does. Hiding uses the visible rectangles, rounded corners and 2D transforms of matching objects. It approximates browser stacking rather than reproducing arbitrary CSS masks, 3D transforms, shadow DOM or every stacking context. Give the character roughly 145 × `size` pixels of room above a ledge. Targets need a visible box at least 48px wide.
|
|
173
|
+
|
|
174
|
+
Grinch can coexist with [Winter](https://winter.fluri.xyz/) or [Spider](https://spider.fluri.xyz/); each has its own controller. Keep active effects and gift counts modest on smaller devices.
|
|
175
|
+
|
|
176
|
+
## Develop
|
|
177
|
+
|
|
178
|
+
Node.js 22.12+ and pnpm 10.
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
pnpm install --frozen-lockfile
|
|
182
|
+
pnpm exec playwright install --with-deps
|
|
183
|
+
pnpm check
|
|
184
|
+
pnpm dev
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The suite covers the heist and return state machine, configuration, SSR, cleanup, scrolling, mobile layout, host interactions and reduced motion across Chromium, Firefox, WebKit and iPhone-sized WebKit.
|
|
188
|
+
|
|
189
|
+
MIT. Built by [František Gejdoš](https://fluri.xyz/founder/), founder of [Fluri](https://fluri.xyz/).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GrinchController, GrinchOptions } from './types.js';
|
|
2
|
+
export type { Box, GiftState, GrinchController, GrinchEvent, GrinchEventData, GrinchMood, GrinchOptions, GrinchPhase, GrinchSnapshot, GrinchTuning, Point, } from './types.js';
|
|
3
|
+
export { defaults } from './options.js';
|
|
4
|
+
/** A decorative, page-aware visitor. The runtime performs no network requests and never moves host content. */
|
|
5
|
+
export declare function createGrinch(input?: GrinchOptions): GrinchController;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,gBAAgB,EAGhB,aAAa,EAId,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,GAAG,EACH,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,UAAU,EACV,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,KAAK,GACN,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAWxC,+GAA+G;AAC/G,wBAAgB,YAAY,CAAC,KAAK,GAAE,aAAkB,GAAG,gBAAgB,CAsfxE"}
|