@ev-ry/fx 0.1.0-rc.2 → 0.1.0-rc.3
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/QUICKSTART.fa.md +99 -87
- package/README.md +6 -2
- package/build-report.json +36 -31
- package/docs/GUIDE.md +8 -2
- package/docs/RELEASE-NOTES.md +23 -0
- package/examples/evry-website.md +58 -0
- package/package.json +1 -1
- package/src/dom-attachment.d.ts +1 -1
- package/src/dom-free.d.ts +14 -14
- package/src/dom-free.js +61 -38
- package/src/dom-image-surface.js +150 -103
- package/src/dom-image-swap.js +5 -2
- package/src/dom-once.js +39 -34
- package/src/dom-reveal.js +83 -73
- package/src/dom-svg-surface.js +41 -39
- package/src/dom-text-paint-mask.js +24 -0
- package/src/dom-text-surface.js +63 -32
- package/src/image-surface.js +161 -157
- package/src/render-owner.js +17 -12
- package/src/text-motion-primitives.js +7 -0
- package/src/triangle-effect.js +2 -1
- package/src/viewport-render-owner.js +322 -316
package/QUICKSTART.fa.md
CHANGED
|
@@ -1,94 +1,106 @@
|
|
|
1
|
-
# EV-RY FX Free 0.1.0-rc.
|
|
2
|
-
|
|
3
|
-
این نسخهٔ آزمایشی عمومی با مجوز MIT ارائه میشود. نام بسته در npm برابر @ev-ry/fx است.
|
|
4
|
-
|
|
5
|
-
## روش script
|
|
6
|
-
|
|
7
|
-
آرشیو را استخراج کنید و پوشه package را کامل در مسیر عمومی `/thd/` سایت قرار دهید. این خط را اضافه کنید:
|
|
8
|
-
|
|
9
|
-
```html
|
|
10
|
-
<script src="/thd/src/dom-free-script.js" data-thd-auto defer></script>
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
H1/H2 خودکار انتخاب میشوند. برای تصویر `data-thd-image` و برای متن دیگر `data-thd-text` بگذارید. برای استثناکردن یک بخش، `data-thd-ignore` روی آن یا والدش قرار دهید. فایل examples/script.html نمونهٔ آماده است؛ آن را از HTTP باز کنید، نه file://.
|
|
14
|
-
|
|
15
|
-
### جلوگیری از چشمک در اولین نمایش صفحه
|
|
16
|
-
|
|
17
|
-
این اسکریپت کوچک را در `head`، پیش از محتوای صفحه و **بدون defer یا async** قرار دهید:
|
|
18
|
-
|
|
19
|
-
```html
|
|
20
|
-
<script src="/thd/src/dom-reveal-boot.js"></script>
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
روی آیتمی که منتظر افکت ورود است `data-thd-pending` بگذارید؛ این نشانگر جایگزین انتخابگر اتصال نیست:
|
|
24
|
-
|
|
25
|
-
```html
|
|
26
|
-
<img data-thd-image data-thd-pending src="photo.jpg" width="640" height="400" alt="Photo">
|
|
27
|
-
<h1 data-thd-pending>Hello</h1>
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
فضا حفظ میشود و ماسک اولیه در همان لحظهٔ اتصال به ماسک افکت تحویل داده میشود. اگر موتور بارگیری نشود، ماسک اولیه حداکثر پس از ۸ ثانیه برداشته میشود؛ بدون JavaScript محتوا بومی و قابل مشاهده است. آمادهسازی افکت پس از اتصال، مراقبت خطای مستقل خود را دارد. برای تصویر ابعاد یا aspect-ratio تعیین کنید تا بارگیری آن چیدمان را جابهجا نکند.
|
|
31
|
-
|
|
32
|
-
نشانگر `data-thd-pending` فقط برای مخفیسازی اولیه است و روی آیتمهای خارج از انتخابگر یا مستثناشده نگذارید. ویژگی `data-thd-reveal` متعلق به موتور است؛ آن را دستی تغییر ندهید. مهلت ۸ ثانیه از اجرای اسکریپت اولیه محاسبه میشود و تأخیر افکت نیست. اگر موتور پس از این مهلت آماده شود، جلوگیری از چشمک تضمین نمیشود. در سایت دارای CSP محدود، اجرای اسکریپت و style تزریقشده باید مجاز باشد.
|
|
33
|
-
|
|
34
|
-
## نصب با npm
|
|
35
|
-
|
|
36
|
-
```sh
|
|
37
|
-
npm install ./thd-free-0.1.0-rc.
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
برای import از `@ev-ry/fx`، namespace سازگار THREE را خودتان ارائه کنید؛ نسخهٔ همراه script همان r158 است. راهنمای کامل API در docs/GUIDE.md قرار دارد.
|
|
41
|
-
|
|
42
|
-
برای شروع صریح از داخل ماژول نیز میتوانید از loader استفاده کنید:
|
|
43
|
-
|
|
44
|
-
```js
|
|
45
|
-
import * as THREE from 'three';
|
|
46
|
-
import {loadFree} from '@ev-ry/fx/script';
|
|
47
|
-
const api = await loadFree({THREE, auto: true});
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
خود import روی صفحه اثری ندارد و هنگام رندر سمت سرور هم قابل import است؛ `loadFree()` را فقط در مرورگر و پس از وجود صفحه اجرا کنید. نسخهٔ آزمایششدهٔ Three.js همان r158 است. در صورت ندادن THREE، loader از نسخهٔ موجود در window یا فایل همراه استفاده میکند؛ مسیر فایل همراه باید پس از بستهبندی قابل دسترس بماند. اولین فراخوانی حالت خودکار را تعیین میکند و فراخوانیهای بعدی همان آمادهسازی را به اشتراک میگذارند. برای مدیریت دستی از `auto:false` و سپس `api.create()` استفاده کنید.
|
|
51
|
-
|
|
52
|
-
آدرس قبلی `src/dom-free-script.js` برای تگ script معمولی حفظ شده است. روی آن `type="module"` نگذارید. مسیر `@ev-ry/fx/classic-script` فقط برای یافتن فایل script معمولی است؛ ورودی import همان `@ev-ry/fx/script` و تابع `loadFree` است.
|
|
53
|
-
|
|
54
|
-
## React
|
|
55
|
-
|
|
56
|
-
مثال `examples/AnimatedTitle.jsx` عمداً از همان script استفاده میکند تا نیازی به تنظیم بستهبندی فایلهای همراه موتور نباشد. script را بدون data-thd-auto پیش از اجرای React بارگیری کنید و سپس AnimatedTitle را در برنامه استفاده کنید. روی همان عنصر اتصال خودکار و دستی را همزمان فعال نکنید.
|
|
57
|
-
|
|
58
|
-
این مثال cleanup دارد و در StrictMode و تغییر متن باید اتصال قبلی را آزاد کند. children در این مثال یک رشته است؛ این یک پک کامپوننت React مستقل نیست.
|
|
59
|
-
|
|
60
|
-
## رفتار و محدودیتها
|
|
61
|
-
|
|
62
|
-
### ورود به دید و تکرار
|
|
63
|
-
|
|
64
|
-
پیشفرض `once:true` یعنی تنها اولین ورود. با `once:false`، خروج کامل آیتم آن را فوراً مخفی و برای ورود مجدد آماده میکند؛ این خروج افکت محو اجرا نمیکند. حرکت کوچک داخل محدوده نباید افکت را تکرار کند. ورود مجدد در حین افکت ناتمام، همان زمانبندی را ادامه میدهد؛ پس از پایان، ورود جدید افکت تازه میسازد. `play('enter')` درخواست بازپخش صریح و `play('exit')` درخواست محو متحرک است.
|
|
65
|
-
|
|
1
|
+
# EV-RY FX Free 0.1.0-rc.3 — نصب آزمایشی
|
|
2
|
+
|
|
3
|
+
این نسخهٔ آزمایشی عمومی با مجوز MIT ارائه میشود. نام بسته در npm برابر @ev-ry/fx است.
|
|
4
|
+
|
|
5
|
+
## روش script
|
|
6
|
+
|
|
7
|
+
آرشیو را استخراج کنید و پوشه package را کامل در مسیر عمومی `/thd/` سایت قرار دهید. این خط را اضافه کنید:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<script src="/thd/src/dom-free-script.js" data-thd-auto defer></script>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
H1/H2 خودکار انتخاب میشوند. برای تصویر `data-thd-image` و برای متن دیگر `data-thd-text` بگذارید. برای استثناکردن یک بخش، `data-thd-ignore` روی آن یا والدش قرار دهید. فایل examples/script.html نمونهٔ آماده است؛ آن را از HTTP باز کنید، نه file://.
|
|
14
|
+
|
|
15
|
+
### جلوگیری از چشمک در اولین نمایش صفحه
|
|
16
|
+
|
|
17
|
+
این اسکریپت کوچک را در `head`، پیش از محتوای صفحه و **بدون defer یا async** قرار دهید:
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<script src="/thd/src/dom-reveal-boot.js"></script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
روی آیتمی که منتظر افکت ورود است `data-thd-pending` بگذارید؛ این نشانگر جایگزین انتخابگر اتصال نیست:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<img data-thd-image data-thd-pending src="photo.jpg" width="640" height="400" alt="Photo">
|
|
27
|
+
<h1 data-thd-pending>Hello</h1>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
فضا حفظ میشود و ماسک اولیه در همان لحظهٔ اتصال به ماسک افکت تحویل داده میشود. اگر موتور بارگیری نشود، ماسک اولیه حداکثر پس از ۸ ثانیه برداشته میشود؛ بدون JavaScript محتوا بومی و قابل مشاهده است. آمادهسازی افکت پس از اتصال، مراقبت خطای مستقل خود را دارد. برای تصویر ابعاد یا aspect-ratio تعیین کنید تا بارگیری آن چیدمان را جابهجا نکند.
|
|
31
|
+
|
|
32
|
+
نشانگر `data-thd-pending` فقط برای مخفیسازی اولیه است و روی آیتمهای خارج از انتخابگر یا مستثناشده نگذارید. ویژگی `data-thd-reveal` متعلق به موتور است؛ آن را دستی تغییر ندهید. مهلت ۸ ثانیه از اجرای اسکریپت اولیه محاسبه میشود و تأخیر افکت نیست. اگر موتور پس از این مهلت آماده شود، جلوگیری از چشمک تضمین نمیشود. در سایت دارای CSP محدود، اجرای اسکریپت و style تزریقشده باید مجاز باشد.
|
|
33
|
+
|
|
34
|
+
## نصب با npm
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install ./thd-free-0.1.0-rc.3.tgz
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
برای import از `@ev-ry/fx`، namespace سازگار THREE را خودتان ارائه کنید؛ نسخهٔ همراه script همان r158 است. راهنمای کامل API در docs/GUIDE.md قرار دارد.
|
|
41
|
+
|
|
42
|
+
برای شروع صریح از داخل ماژول نیز میتوانید از loader استفاده کنید:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import * as THREE from 'three';
|
|
46
|
+
import {loadFree} from '@ev-ry/fx/script';
|
|
47
|
+
const api = await loadFree({THREE, auto: true});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
خود import روی صفحه اثری ندارد و هنگام رندر سمت سرور هم قابل import است؛ `loadFree()` را فقط در مرورگر و پس از وجود صفحه اجرا کنید. نسخهٔ آزمایششدهٔ Three.js همان r158 است. در صورت ندادن THREE، loader از نسخهٔ موجود در window یا فایل همراه استفاده میکند؛ مسیر فایل همراه باید پس از بستهبندی قابل دسترس بماند. اولین فراخوانی حالت خودکار را تعیین میکند و فراخوانیهای بعدی همان آمادهسازی را به اشتراک میگذارند. برای مدیریت دستی از `auto:false` و سپس `api.create()` استفاده کنید.
|
|
51
|
+
|
|
52
|
+
آدرس قبلی `src/dom-free-script.js` برای تگ script معمولی حفظ شده است. روی آن `type="module"` نگذارید. مسیر `@ev-ry/fx/classic-script` فقط برای یافتن فایل script معمولی است؛ ورودی import همان `@ev-ry/fx/script` و تابع `loadFree` است.
|
|
53
|
+
|
|
54
|
+
## React
|
|
55
|
+
|
|
56
|
+
مثال `examples/AnimatedTitle.jsx` عمداً از همان script استفاده میکند تا نیازی به تنظیم بستهبندی فایلهای همراه موتور نباشد. script را بدون data-thd-auto پیش از اجرای React بارگیری کنید و سپس AnimatedTitle را در برنامه استفاده کنید. روی همان عنصر اتصال خودکار و دستی را همزمان فعال نکنید.
|
|
57
|
+
|
|
58
|
+
این مثال cleanup دارد و در StrictMode و تغییر متن باید اتصال قبلی را آزاد کند. children در این مثال یک رشته است؛ این یک پک کامپوننت React مستقل نیست.
|
|
59
|
+
|
|
60
|
+
## رفتار و محدودیتها
|
|
61
|
+
|
|
62
|
+
### ورود به دید و تکرار
|
|
63
|
+
|
|
64
|
+
پیشفرض `once:true` یعنی تنها اولین ورود. با `once:false`، خروج کامل آیتم آن را فوراً مخفی و برای ورود مجدد آماده میکند؛ این خروج افکت محو اجرا نمیکند. حرکت کوچک داخل محدوده نباید افکت را تکرار کند. ورود مجدد در حین افکت ناتمام، همان زمانبندی را ادامه میدهد؛ پس از پایان، ورود جدید افکت تازه میسازد. `play('enter')` درخواست بازپخش صریح و `play('exit')` درخواست محو متحرک است.
|
|
65
|
+
|
|
66
66
|
`threshold` نسبت مساحت قابل مشاهده است: صفر برای اولین تقاطع مثبت، `0.5` برای نصف و `1` برای تمام آیتم. آیتم بزرگتر از محدودهٔ دید ممکن است هرگز به مقدار یک نرسد.
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
اگر بعد از اتصال دارای `revealOnView`، صریحاً `play()` را فراخوانی کنید، زمانبندی دستی جای ورود خودکار را میگیرد؛ ماسک اولیه تا اولین فریم حفظ میشود، اما ورود به دید دیگر افکت را از نو شروع نمیکند. برای متن اسلایدر که باید همزمان با اسلاید شروع شود، همین روش مناسب است. متن خارج از دید فقط زمان شروع را نگه میدارد و رسم و محاسبات ذرات متوقف میشوند؛ با برگشت، وضعیت متناسب با زمان سپریشده نمایش داده میشود. اگر زمان تمام شده باشد، افکت دوباره اجرا نمیشود.
|
|
69
|
+
|
|
70
|
+
برای پایان افکت از `await surface.whenFinished()` استفاده کنید؛ پایان گذار به متن بومی را هم در نظر میگیرد و خارج از دید نیز تکمیل میشود. نتیجه یکی از وضعیتهای `completed`، `cancelled` و `unsupported` است. برای تشخیص پایان، تایمر ثابت یا آمار رندر را در کد سایت بررسی نکنید. لغو یا تخریب اتصال، انتظار جاری را با وضعیت `cancelled` آزاد میکند.
|
|
71
|
+
|
|
72
|
+
اگر `play('exit')` را خارج از دید فراخوانی کنید، نمایش بومی همان لحظه مخفی میشود؛ اندازه، جای عنصر و محتوای DOM حذف نمیشوند. اگر حذف در حال اجرا از دید خارج شود نیز نمایش بومی مخفی میماند. هنگام برگشت، فقط ذرات متناسب با زمان سپریشده رسم میشوند و اگر حذف تمام شده باشد، آیتم مخفی میماند. `play('enter')` آن را دوباره تشکیل میدهد؛ `cancel()` یا `destroy()` نمایش و سبکهای بومی را بازمیگردانند. این رفتار در خود موتور برای متن، تصویر و SVG اعمال میشود.
|
|
73
|
+
|
|
74
|
+
### محل canvas و لایهها
|
|
75
|
+
|
|
76
|
+
پیشفرض `auto` با `documentCanvas:true` است؛ canvas مشترک همراه سند حرکت میکند. محدودههای خاص ممکن است به حالت محلی بروند. برای اجبار به حالت محلی از `presentation:'local'` استفاده کنید. canvas مشترک تمام ترکیبهای لایهبندی CSS را بازسازی نمیکند؛ برای تداخل با هدر، zIndex و حالت نمایش را بررسی کنید.
|
|
77
|
+
|
|
78
|
+
### اتصال و پاکسازی
|
|
79
|
+
|
|
80
|
+
عنصرِ خالی که دستی متصل شده، پس از دریافت متن میتواند افکت ورود را اجرا کند. اما عنصر خالیای که اسکن خودکار آن را انتخاب نکرده، برای کشفشدن به `free.refresh()` نیاز دارد. اگر سبک پشتیبانینشده را اصلاح کردید، refresh امکان اجرای اولین ورود را بازمیگرداند؛ ورودِ یکبارهای که قبلاً کامل شده تکرار نمیشود. فاصلههای معمولی HTML طبق CSS جمع میشوند؛ فاصلهٔ نشکن و فاصلههای پیشقالببندیشده حذف سراسری نمیشوند.
|
|
81
|
+
|
|
82
|
+
اتصال خودکار و دستی را روی یک آیتم همزمان انجام ندهید. اسکن تغییرات DOM خودکار و دائمی نیست؛ پس از افزودن یا حذف آیتمها `free.refresh()` را اجرا کنید. هنگام unmount، اتصال یا مالک آن را `destroy()` کنید. تصویر خارجی بدون مجوز CORS یا CSS/SVG پشتیبانینشده ممکن است بومی باقی بماند؛ نبود افکت همیشه خطای بارگذاری موتور نیست.
|
|
83
|
+
|
|
84
|
+
loader خودکار هنگام رفتن صفحه به حافظهٔ Back/Forward مرورگر، اتصال را نگه میدارد و هنگام بازگشت موقعیتش را تازه میکند؛ ورودهای یکباره دوباره ساخته نمیشوند. خروج واقعی صفحه منابع را آزاد میکند. اگر اتصال را خودتان با `api.create()` یا `createFree()` ساختهاید، مدیریت عمر آن هم با شماست: روی `pagehide` فقط در صورت `event.persisted === false` آن را آزاد کنید؛ در بازگشتِ persisted، `refresh()` کافی است. اتصال عمداً destroyشده خودکار زنده نمیشود. پاکسازی هنگام unmount واقعی همچنان لازم است.
|
|
85
|
+
|
|
80
86
|
### آمادهسازی و مصرف پردازش
|
|
81
87
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
`surface.ready` نتیجهٔ آمادهسازی است، نه پایان نصب موتور؛ برای متن یا SVG پایین صفحه ممکن است تا نزدیکشدن به دید منتظر بماند. فعالشدن دکمههای کل صفحه را به `ready` همهٔ آیتمها وابسته نکنید. پس از ساخت اتصال میتوانید `play()` را صدا بزنید و پایان اجرای درخواستشده را با `whenFinished()` بگیرید.
|
|
89
|
+
|
|
90
|
+
برای تصویر خارج از دید یا با اندازهٔ صفر، `ready` ممکن است با وضعیت موقت بومی و دلیل `Image not visible` تمام شود؛ این به معنی آمادهبودن مش روی GPU نیست. آمادهسازی تصویر از نزدیکی صفحه، حدود نصف ارتفاع viewport جلوتر، آغاز میشود. بارگیری شبکهٔ خود `img` همچنان تابع مرورگر است. هنگام resize سریع، مش قبلی برای مدت کوتاه با کادر حرکت میکند و برش و گوشههای نهایی پس از تجمیع تغییرات بازسازی میشوند؛ ساعت افکت از ابتدا شروع نمیشود.
|
|
91
|
+
|
|
92
|
+
متن ثابت از چیدمان معتبر و پیکسلهای کششده استفاده میکند. سقف کش مشترک پیکسلها ۱۶MiB است؛ این سقف کل حافظهٔ صفحه یا GPU نیست. بارگیری فونت کش مرتبط را تازه میکند. متن پشتیبانینشده بومی نمایش داده میشود و موتور روی همان خطای معلوم مدام تلاش نمیکند؛ پس از اصلاح سبک، `refresh()` امکان تلاش مجدد را میدهد.
|
|
93
|
+
|
|
86
94
|
### مشخصات نسخه
|
|
87
95
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
96
|
+
برای پایان افکت از `await surface.whenFinished()` استفاده کنید؛ نتیجه `completed`، `cancelled` یا `unsupported` است و پایان تبدیل مش به نمایش بومی را هم لحاظ میکند. چند فراخوانی همزمان یک انتظار مشترک دارند. اجرای جدید، انتظار اجرای قبلی را لغو میکند؛ `cancel()`، `destroy()` و جداشدن عنصر از سند نیز انتظار را آزاد میکنند. انتظار ورود خودکار تا اولین intersection محدود به زمان آمادهسازی نیست.
|
|
97
|
+
|
|
98
|
+
متن، تصویر و SVG خارج دید ساعت افکت را با پردازش کم حفظ میکنند؛ ورود مجدد افکت را از ابتدا شروع نمیکند. هنگام درخواست محو خارج دید، نمایش بومی همان لحظه مخفی میشود. در اسلایدر، متن و تصویر بعدی را تنها پس از موفقیت تعویض نهایی کنید. برای متن بعدی عنصر تازه بسازید؛ عنصر متصل به موتور ممکن است سبک یا ویژگی موقت داشته باشد و نباید همان حالت زنده را clone کرد.
|
|
99
|
+
|
|
100
|
+
- متن: ورود باد، خروج دود. تصویر/SVG: ورود برف، خروج ذوب.
|
|
101
|
+
- مدت۲ ثانیه، ذرات مثلثی، سکون بومی، شکلگیری دیرتر برای نمایشدهندهها.
|
|
102
|
+
- canvas متصل به سند پیشفرض است؛ حالت خودکار برای بعضی محدودهها محلی میشود.
|
|
103
|
+
- قبل از جداکردن بخش صفحه owner.destroy() را صدا بزنید. برای اسکن عناصر جدید owner.refresh().
|
|
104
|
+
- چهار افکت ثابت؛ ویرایشگر، افکت سفارشی و پشتیبانی تمام CSS/SVG در این بسته نیستند.
|
|
105
|
+
- یک script با فایلهای همراه است، نه یک فایل یکپارچه. ساختار پوشهها را حفظ کنید.
|
|
94
106
|
- تستهای واقعی آیفون و Android توسط کاربر موفق گزارش شدهاند؛ این تضمین تمام دستگاهها و سایتها نیست.
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<p align="center"><strong>Your HTML. Four particle effects. Native when still.</strong></p>
|
|
6
6
|
|
|
7
|
-
<p align="center">Free edition · 0.1.0-rc.
|
|
7
|
+
<p align="center">Free edition · 0.1.0-rc.3 preview · <a href="https://github.com/kbaghini/evry-fx/blob/main/LICENSE">MIT licensed</a> · JavaScript + TypeScript declarations</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
10
|
<a href="#quick-start">Quick start</a> ·
|
|
@@ -38,7 +38,7 @@ The README shows an animated preview. Open the [live interactive demo](https://k
|
|
|
38
38
|
|
|
39
39
|
## Quick start
|
|
40
40
|
|
|
41
|
-
**Public preview: 0.1.0-rc.
|
|
41
|
+
**Public preview: 0.1.0-rc.3.** Validate the supported content and layouts in your project before production use.
|
|
42
42
|
|
|
43
43
|
Install from your project folder:
|
|
44
44
|
|
|
@@ -114,3 +114,7 @@ For direct ownership with injected Three.js, `createFree` is also exported from
|
|
|
114
114
|
- This Free build does not include editable inputs, arbitrary effect customization or a dedicated React/Vue component pack.
|
|
115
115
|
|
|
116
116
|
EV-RY FX Free is [MIT licensed](https://github.com/kbaghini/evry-fx/blob/main/LICENSE). Bundled dependencies retain their own licenses and [notices](https://github.com/kbaghini/evry-fx/blob/main/NOTICE.md). This license covers the Free distribution; other editions are separate.
|
|
117
|
+
|
|
118
|
+
## Real integration: EV-RY website
|
|
119
|
+
|
|
120
|
+
[See the EV-RY product-showcase integration](examples/evry-website.md): alternating snow/melt image transitions, animated captions and persistent-engine navigation. Includes initial-reveal and lifecycle guidance; the full website is currently a local integration.
|
package/build-report.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.0-rc.
|
|
2
|
+
"version": "0.1.0-rc.3",
|
|
3
3
|
"effects": [
|
|
4
4
|
"dust-wind",
|
|
5
5
|
"smoke",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"file": "src/dom-free.js",
|
|
17
|
-
"bytes":
|
|
18
|
-
"gzip":
|
|
17
|
+
"bytes": 5191,
|
|
18
|
+
"gzip": 1910
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"file": "src/dom-attachment.js",
|
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"file": "src/dom-reveal.js",
|
|
27
|
-
"bytes":
|
|
28
|
-
"gzip":
|
|
27
|
+
"bytes": 6790,
|
|
28
|
+
"gzip": 2528
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"file": "src/dom-svg-surface.js",
|
|
32
|
-
"bytes":
|
|
33
|
-
"gzip":
|
|
32
|
+
"bytes": 5096,
|
|
33
|
+
"gzip": 2098
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"file": "src/dom-image-surface.js",
|
|
37
|
-
"bytes":
|
|
38
|
-
"gzip":
|
|
37
|
+
"bytes": 14775,
|
|
38
|
+
"gzip": 4711
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"file": "src/image-surface.js",
|
|
42
|
-
"bytes":
|
|
43
|
-
"gzip":
|
|
42
|
+
"bytes": 12425,
|
|
43
|
+
"gzip": 4356
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"file": "src/motion-envelope.js",
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
"file": "src/triangle-effect.js",
|
|
97
|
-
"bytes":
|
|
98
|
-
"gzip":
|
|
97
|
+
"bytes": 13758,
|
|
98
|
+
"gzip": 4313
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
"file": "src/particle-centers.js",
|
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
131
|
"file": "src/text-motion-primitives.js",
|
|
132
|
-
"bytes":
|
|
133
|
-
"gzip":
|
|
132
|
+
"bytes": 4764,
|
|
133
|
+
"gzip": 2022
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
"file": "src/text-motion-contour.js",
|
|
@@ -154,23 +154,23 @@
|
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
"file": "src/dom-image-swap.js",
|
|
157
|
-
"bytes":
|
|
158
|
-
"gzip":
|
|
157
|
+
"bytes": 3310,
|
|
158
|
+
"gzip": 1316
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
"file": "src/dom-once.js",
|
|
162
|
-
"bytes":
|
|
163
|
-
"gzip":
|
|
162
|
+
"bytes": 3463,
|
|
163
|
+
"gzip": 1357
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
"file": "src/render-owner.js",
|
|
167
|
-
"bytes":
|
|
168
|
-
"gzip":
|
|
167
|
+
"bytes": 4268,
|
|
168
|
+
"gzip": 1583
|
|
169
169
|
},
|
|
170
170
|
{
|
|
171
171
|
"file": "src/viewport-render-owner.js",
|
|
172
|
-
"bytes":
|
|
173
|
-
"gzip":
|
|
172
|
+
"bytes": 25171,
|
|
173
|
+
"gzip": 7607
|
|
174
174
|
},
|
|
175
175
|
{
|
|
176
176
|
"file": "src/viewport-clip.js",
|
|
@@ -179,8 +179,8 @@
|
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"file": "src/dom-text-surface.js",
|
|
182
|
-
"bytes":
|
|
183
|
-
"gzip":
|
|
182
|
+
"bytes": 22552,
|
|
183
|
+
"gzip": 7092
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
"file": "src/hybrid-text-flow.js",
|
|
@@ -257,6 +257,11 @@
|
|
|
257
257
|
"bytes": 2103,
|
|
258
258
|
"gzip": 1021
|
|
259
259
|
},
|
|
260
|
+
{
|
|
261
|
+
"file": "src/dom-text-paint-mask.js",
|
|
262
|
+
"bytes": 1469,
|
|
263
|
+
"gzip": 644
|
|
264
|
+
},
|
|
260
265
|
{
|
|
261
266
|
"file": "src/dom-auto-route.js",
|
|
262
267
|
"bytes": 1884,
|
|
@@ -294,8 +299,8 @@
|
|
|
294
299
|
},
|
|
295
300
|
{
|
|
296
301
|
"file": "src/dom-free.d.ts",
|
|
297
|
-
"bytes":
|
|
298
|
-
"gzip":
|
|
302
|
+
"bytes": 1387,
|
|
303
|
+
"gzip": 639
|
|
299
304
|
},
|
|
300
305
|
{
|
|
301
306
|
"file": "src/dom-auto-reveal.d.ts",
|
|
@@ -304,8 +309,8 @@
|
|
|
304
309
|
},
|
|
305
310
|
{
|
|
306
311
|
"file": "src/dom-attachment.d.ts",
|
|
307
|
-
"bytes":
|
|
308
|
-
"gzip":
|
|
312
|
+
"bytes": 4537,
|
|
313
|
+
"gzip": 1404
|
|
309
314
|
},
|
|
310
315
|
{
|
|
311
316
|
"file": "assets/vendor/three-LICENSE.txt",
|
|
@@ -323,6 +328,6 @@
|
|
|
323
328
|
"gzip": 1971
|
|
324
329
|
}
|
|
325
330
|
],
|
|
326
|
-
"bytes":
|
|
327
|
-
"gzipSum":
|
|
331
|
+
"bytes": 974292,
|
|
332
|
+
"gzipSum": 280473
|
|
328
333
|
}
|
package/docs/GUIDE.md
CHANGED
|
@@ -39,9 +39,15 @@ Importing this entry is inert, including during server-side rendering. Call `loa
|
|
|
39
39
|
|
|
40
40
|
The classic `<script src="…/src/dom-free-script.js">` URL remains unchanged. `@ev-ry/fx/classic-script` resolves that classic asset; it is not an ES-module import entry. Do not use `type="module"` with the classic asset: use `loadFree` instead.
|
|
41
41
|
|
|
42
|
-
Auto selects h1/h2/[data-thd-text] and img[data-thd-image]; data-thd-ignore and interactive/navigation/dialog regions are excluded. Options: auto, textSelector, imageSelector, presentation, threshold, once, intersectionRoot. Call refresh after route/DOM changes. Manual attachment accepts presentation and revealOnView. play/cancel/refresh/stats/destroy are exposed; arbitrary effects are not. Scroll exit hides repeated targets rather than running an exit animation.
|
|
42
|
+
Auto selects h1/h2/[data-thd-text] and img[data-thd-image]; data-thd-ignore and interactive/navigation/dialog regions are excluded. Options: auto, textSelector, imageSelector, presentation, threshold, once, intersectionRoot. Call refresh after route/DOM changes. Manual attachment accepts presentation and revealOnView. play/cancel/refresh/stats/destroy are exposed; arbitrary effects are not. Scroll exit hides repeated targets rather than running an exit animation.
|
|
43
|
+
|
|
44
|
+
An explicit `surface.play()` takes ownership from automatic `revealOnView` for that attachment, retaining the initial mask until rendering starts. Offscreen text, images and SVG keep their start timestamp without particle updates or drawing, and resume at the elapsed position instead of restarting. Use `await surface.whenFinished()` for cleanup, including native handoff and offscreen completion; it returns `{status: 'completed' | 'cancelled' | 'unsupported'}`. See `examples/evry-website.md` for captions driven by a slider clock.
|
|
45
|
+
|
|
46
|
+
Concurrent `whenFinished()` calls share one pending completion and polling loop. A new successful `play()` cancels the previous pending wait; an invalid phase does not interrupt it. Cancellation, destruction of the attachment or its installation, and detaching the target resolve outstanding waits as `cancelled`. An untouched automatic reveal can wait for its first intersection without a preparation timeout. The 30-second watchdog only bounds an actual play/preparation attempt. Suspended effects are checked infrequently; waiting does not require a per-frame render loop.
|
|
43
47
|
|
|
44
|
-
Preparation is lazy for offscreen targets. Native content may paint before a late script initializes; use the optional early mask below for initial-page entry effects. Unsupported content falls back to native presentation. Cross-origin image canvas restrictions and unsupported rich CSS still apply. This does not claim general CSS reproduction or new physical-device validation.
|
|
48
|
+
Preparation is lazy for offscreen targets. Native content may paint before a late script initializes; use the optional early mask below for initial-page entry effects. Unsupported content falls back to native presentation. Cross-origin image canvas restrictions and unsupported rich CSS still apply. This does not claim general CSS reproduction or new physical-device validation.
|
|
49
|
+
|
|
50
|
+
`surface.ready` describes the first preparation result, not installation readiness. A text/SVG target below the viewport can remain unprepared until it approaches view. Enable ordinary controls after attachment creation rather than waiting for every page target's `ready`. `play()` can queue a request before preparation; use `whenFinished()` for the end of that requested effect. Targets without an entry request keep their native presentation.
|
|
45
51
|
|
|
46
52
|
For an offscreen or zero-area image, `ready` can resolve with temporary native reason `Image not visible`; it does not promise a prepared GPU mesh outside the viewport. Image preparation begins near the viewport (roughly half a viewport height ahead). The native image's network loading policy is unchanged. Resize work is coalesced: the current raster follows the new box briefly, then the final crop and rounded corners are rebuilt after about 50ms of quiet, with a roughly 150ms bound during continuous resizing. Existing motion keeps its timeline.
|
|
47
53
|
|
package/docs/RELEASE-NOTES.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
# EV-RY FX Free 0.1.0-rc.3
|
|
2
|
+
|
|
3
|
+
- Cancel/update no longer starts a fresh text handoff; invalid play phases leave automatic reveal intact, and offscreen no-effect calls complete immediately.
|
|
4
|
+
- Image/SVG source refresh preserves the active effect clock, seed and settled exit mask. SVG snapshot errors retain native fallback diagnostics.
|
|
5
|
+
- SVG native pixels now appear below the settled mesh during the same 250ms handoff as images.
|
|
6
|
+
- Cancelling an image swap restores the incoming image's original location, including removal when it was initially detached.
|
|
7
|
+
- Completion waits share one pending poller, cancel promptly on superseding play or owner disposal, and do not time out untouched intersection reveals.
|
|
8
|
+
- Public whenFinished() for completion, cancellation and unsupported rendering.
|
|
9
|
+
- Offscreen text preserves its effect clock without particle updates or drawing; plain/rich text resumes at elapsed time and completion no longer waits for viewport entry.
|
|
10
|
+
- Explicit play takes ownership from automatic reveal without dropping the initial mask or restarting on intersection. Existing automatic reveal behavior remains in effect until explicit play.
|
|
11
|
+
- Offscreen exits suppress native text/image/SVG paint immediately and retain it through reentry and completion; cancellation and disposal restore original styles. Regression covers actual departure shader clocks, seeds, coordinates and rendered smoke pixels.
|
|
12
|
+
- Transient effects and image swaps complete on their original offscreen timeline without waiting for visual readiness; hidden transient polling is infrequent and cancellation still restores native content.
|
|
13
|
+
- Images reveal native pixels at assembly completion, then fade the mesh for 250ms, including swaps.
|
|
14
|
+
|
|
15
|
+
- Reuse local rendering buffers across differently sized surfaces, avoiding repeated GPU buffer allocation.
|
|
16
|
+
- Clear retained scratch pixels before each local surface copy.
|
|
17
|
+
- Image motion preserves source colors by default (recipe glow defaults to zero).
|
|
18
|
+
- Gradual particle appearance on the existing effect clock.
|
|
19
|
+
- Free image swaps support `enter` and `topImage`, including outgoing-only melt over the next native image.
|
|
20
|
+
- Include the EV-RY website integration recipe and initial-reveal lifecycle guidance.
|
|
21
|
+
|
|
22
|
+
Website artwork and SPA changes are not package runtime exports. Physical-device performance and the reported first-image brightness difference are not certified by this release.
|
|
23
|
+
|
|
1
24
|
# EV-RY FX Free 0.1.0-rc.2
|
|
2
25
|
|
|
3
26
|
- Fix collapsed whitespace at wrapped line ends triggering native fallback, including Android headings with negative letter spacing.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# EV-RY website: product showcase
|
|
2
|
+
|
|
3
|
+
The EV-RY marketing website is a real integration case: a four-product showcase, animated captions, one shared FX installation across client-side navigation, and native content at rest.
|
|
4
|
+
|
|
5
|
+
The website is currently a local integration at `http://127.0.0.1:8780/`, not a hosted demo included in this package. The public runnable examples are linked from the package README. The website backend, forms and artwork are not dependencies of FX.
|
|
6
|
+
|
|
7
|
+
## Initial reveal without a flash
|
|
8
|
+
|
|
9
|
+
Place `data-thd-pending` on the image and caption elements in the HTML, use the documented boot mask, then let `revealOnView` transfer the mask to the first rendered frame. Keep each returned handle until cleanup. Do not remove the mask manually on attachment readiness.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const imageSurface = engine.attachImage(image, {
|
|
13
|
+
presentation: 'global',
|
|
14
|
+
revealOnView: { threshold: 0, once: true }
|
|
15
|
+
});
|
|
16
|
+
const captionSurfaces = captionLines.map(line => {
|
|
17
|
+
const surface = engine.attachText(line, {
|
|
18
|
+
presentation: 'local',
|
|
19
|
+
revealOnView: { threshold: 0, once: true }
|
|
20
|
+
});
|
|
21
|
+
surface.play('enter');
|
|
22
|
+
return surface;
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The captions explicitly start with the slide, even when below the viewport. `play()` takes control from automatic intersection reveal while retaining its initial paint mask until a rendered frame. It does not wait for visibility or restart on reentry. Without explicit `play()`, `revealOnView` continues to wait for the configured intersection and follows its `once` setting.
|
|
27
|
+
|
|
28
|
+
## Alternate two image transitions
|
|
29
|
+
|
|
30
|
+
After decoding the next mounted image, alternate between incoming snow over the previous image and outgoing melt above the already visible next image:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
const transition = engine.swapImage(previous, next, {
|
|
34
|
+
enter: !melt,
|
|
35
|
+
exit: melt,
|
|
36
|
+
topImage: melt ? 'previous' : 'next',
|
|
37
|
+
waitForExit: false,
|
|
38
|
+
presentation: 'global'
|
|
39
|
+
});
|
|
40
|
+
const result = await transition.finished;
|
|
41
|
+
if (result.status === 'completed') previous.remove();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use a busy flag to prevent overlapping swaps. Pause scheduling while the document is hidden. On navigation, cancel an outstanding transition, clear the scheduled timeout and destroy page handles; retain the shared engine and refresh its automatic attachments after inserting the new page. Install the boot mask before the new DOM can paint. Destroy the engine only when the application is disposed.
|
|
45
|
+
|
|
46
|
+
The five-second scheduling interval, caption layout, routing and product artwork belong to the website, not to the FX API. Local caption surfaces share rendering infrastructure; the retained scratch buffer avoids resizing the GPU buffer for each caption line.
|
|
47
|
+
|
|
48
|
+
Create incoming captions from fresh authored markup rather than cloning a currently attached element: a live attachment can carry temporary engine-owned visibility attributes and styles. Commit the incoming image and caption together only after `transition.finished` reports `completed`. If image decoding, attachment or the swap fails, destroy incoming caption handles, remove their layer, retain the previous caption and image, then retry. Track each acquired handle immediately so partial attachment failures can also be cleaned up.
|
|
49
|
+
|
|
50
|
+
Schedule one deadline five seconds from each transition start. After completion, wait only for the remaining time. If the transition itself overruns that deadline, start one next transition when available and establish a new deadline; do not discard interval ticks or queue catch-up transitions. Returning from a hidden tab and retrying a failed transition each establish a fresh five-second wait. This keeps start-to-start cadence stable without overlapping jobs.
|
|
51
|
+
|
|
52
|
+
## Completion
|
|
53
|
+
|
|
54
|
+
After `surface.play()`, await `surface.whenFinished()`. It returns a status of `completed`, `cancelled`, or `unsupported`. Completion includes the native handoff; avoid fixed cleanup timers or renderer statistics. Destroy page surfaces when navigating away.
|
|
55
|
+
|
|
56
|
+
Offscreen text and media retain their start time without running per-particle updates or drawing frames. Returning during the effect evaluates its current elapsed time; returning after it ends shows the final state without replay. `whenFinished()` also completes when content remains offscreen, so slider cleanup never depends on scrolling captions into view. It checks the suspended clock infrequently instead of maintaining a render loop. Concurrent waits share a pending promise; starting a new play cancels the previous wait. Cancel or destroy the handle to release an outstanding wait.
|
|
57
|
+
|
|
58
|
+
When an exit is requested offscreen, the engine immediately suppresses native paint while retaining layout and the DOM content. Native paint also remains hidden if an active departure leaves view. Reentry renders only the remaining departure particles; an expired exit stays hidden. Text, image and SVG attachments own this masking, so the site should not toggle native opacity or visibility to emulate it. Cancel/destroy restores the original presentation.
|
package/package.json
CHANGED
package/src/dom-attachment.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface SurfaceOptions {
|
|
2
|
-
/** Text/image/SVG only. Re-arm after complete exit. */
|
|
2
|
+
/** Text/image/SVG only. Re-arm after complete exit when once is false. Explicit play takes over automatic triggering. */
|
|
3
3
|
revealOnView?: false | {threshold?: number; once?: boolean; root?: Element | null};
|
|
4
4
|
resting?: 'mesh' | 'native';
|
|
5
5
|
inputEffect?: string;
|
package/src/dom-free.d.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {AutoRevealOptions} from './dom-auto-reveal.js';
|
|
2
|
-
import type {DOMPresentation, SurfaceOptions, TextSurface, createDOMRenderer} from './dom-attachment.js';
|
|
3
|
-
export const FREE_EFFECTS: Readonly<{textEnter:'dust-wind';textExit:'smoke';imageEnter:'drifting-snow';imageExit:'melt'}>;
|
|
4
|
-
export type FreeSurface = Pick<TextSurface,'ready'|'play'|'refresh'|'stats'|'destroy'> & {cancel():void};
|
|
5
|
-
export interface FreeAttachOptions {revealOnView?: SurfaceOptions['revealOnView'];presentation?: DOMPresentation;}
|
|
6
|
-
export function createFree(THREE: Parameters<typeof createDOMRenderer>[0],root?: Document|Element,options?: AutoRevealOptions & {auto?:boolean;zIndex?:number;documentCanvas?:boolean;experimentalDocumentCanvas?:boolean}): {
|
|
7
|
-
attachText(element:HTMLElement,options?:FreeAttachOptions):FreeSurface;
|
|
8
|
-
attachImage(element:HTMLImageElement,options?:FreeAttachOptions):FreeSurface;
|
|
9
|
-
attachSVG(element:SVGSVGElement,options?:FreeAttachOptions):FreeSurface;
|
|
10
|
-
swapImage(previous:HTMLImageElement,next:HTMLImageElement,options?:{exit?:boolean;waitForExit?:boolean;presentation?:DOMPresentation}):{finished:Promise<{status:string}>;cancel():void};
|
|
11
|
-
refresh():void;
|
|
12
|
-
stats():{disposed:boolean;automatic:unknown;renderer:unknown};
|
|
13
|
-
destroy():void;
|
|
14
|
-
};
|
|
1
|
+
import type {AutoRevealOptions} from './dom-auto-reveal.js';
|
|
2
|
+
import type {DOMPresentation, SurfaceOptions, TextSurface, createDOMRenderer} from './dom-attachment.js';
|
|
3
|
+
export const FREE_EFFECTS: Readonly<{textEnter:'dust-wind';textExit:'smoke';imageEnter:'drifting-snow';imageExit:'melt'}>;
|
|
4
|
+
export type FreeSurface = Pick<TextSurface,'ready'|'play'|'refresh'|'stats'|'destroy'> & {cancel():void;whenFinished():Promise<{status:'completed'|'cancelled'|'unsupported'}>};
|
|
5
|
+
export interface FreeAttachOptions {revealOnView?: SurfaceOptions['revealOnView'];presentation?: DOMPresentation;}
|
|
6
|
+
export function createFree(THREE: Parameters<typeof createDOMRenderer>[0],root?: Document|Element,options?: AutoRevealOptions & {auto?:boolean;zIndex?:number;documentCanvas?:boolean;experimentalDocumentCanvas?:boolean}): {
|
|
7
|
+
attachText(element:HTMLElement,options?:FreeAttachOptions):FreeSurface;
|
|
8
|
+
attachImage(element:HTMLImageElement,options?:FreeAttachOptions):FreeSurface;
|
|
9
|
+
attachSVG(element:SVGSVGElement,options?:FreeAttachOptions):FreeSurface;
|
|
10
|
+
swapImage(previous:HTMLImageElement,next:HTMLImageElement,options?:{exit?:boolean;enter?:boolean;topImage?:'previous'|'next';waitForExit?:boolean;presentation?:DOMPresentation}):{finished:Promise<{status:string}>;cancel():void};
|
|
11
|
+
refresh():void;
|
|
12
|
+
stats():{disposed:boolean;automatic:unknown;renderer:unknown};
|
|
13
|
+
destroy():void;
|
|
14
|
+
};
|
|
15
15
|
|
|
16
16
|
|