liqgui 0.1.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 (51) hide show
  1. package/README.md +190 -0
  2. package/dist/components/glass-accordion.d.ts +15 -0
  3. package/dist/components/glass-accordion.js +173 -0
  4. package/dist/components/glass-avatar.d.ts +9 -0
  5. package/dist/components/glass-avatar.js +98 -0
  6. package/dist/components/glass-badge.d.ts +10 -0
  7. package/dist/components/glass-badge.js +151 -0
  8. package/dist/components/glass-button.d.ts +6 -0
  9. package/dist/components/glass-button.js +124 -0
  10. package/dist/components/glass-card.d.ts +8 -0
  11. package/dist/components/glass-card.js +102 -0
  12. package/dist/components/glass-dropdown.d.ts +12 -0
  13. package/dist/components/glass-dropdown.js +182 -0
  14. package/dist/components/glass-input.d.ts +8 -0
  15. package/dist/components/glass-input.js +151 -0
  16. package/dist/components/glass-modal.d.ts +11 -0
  17. package/dist/components/glass-modal.js +128 -0
  18. package/dist/components/glass-navbar.d.ts +6 -0
  19. package/dist/components/glass-navbar.js +84 -0
  20. package/dist/components/glass-progress.d.ts +12 -0
  21. package/dist/components/glass-progress.js +159 -0
  22. package/dist/components/glass-slider.d.ts +17 -0
  23. package/dist/components/glass-slider.js +168 -0
  24. package/dist/components/glass-tabs.d.ts +7 -0
  25. package/dist/components/glass-tabs.js +102 -0
  26. package/dist/components/glass-toast.d.ts +8 -0
  27. package/dist/components/glass-toast.js +128 -0
  28. package/dist/components/glass-toggle.d.ts +9 -0
  29. package/dist/components/glass-toggle.js +112 -0
  30. package/dist/components/glass-tooltip.d.ts +14 -0
  31. package/dist/components/glass-tooltip.js +214 -0
  32. package/dist/core/base-element.d.ts +4 -0
  33. package/dist/core/base-element.js +9 -0
  34. package/dist/core/curves.d.ts +22 -0
  35. package/dist/core/curves.js +32 -0
  36. package/dist/core/focus-trap.d.ts +1 -0
  37. package/dist/core/focus-trap.js +19 -0
  38. package/dist/core/glow.d.ts +3 -0
  39. package/dist/core/glow.js +57 -0
  40. package/dist/core/motion.d.ts +12 -0
  41. package/dist/core/motion.js +54 -0
  42. package/dist/core/spring-engine.d.ts +12 -0
  43. package/dist/core/spring-engine.js +90 -0
  44. package/dist/core/supports.d.ts +1 -0
  45. package/dist/core/supports.js +1 -0
  46. package/dist/core/theme.d.ts +2 -0
  47. package/dist/core/theme.js +1 -0
  48. package/dist/index.d.ts +39 -0
  49. package/dist/index.js +40 -0
  50. package/package.json +47 -0
  51. package/src/styles/tokens.css +140 -0
@@ -0,0 +1,8 @@
1
+ import { BaseElement } from "../core/base-element.js";
2
+ export declare class GlassToast extends BaseElement {
3
+ static get observedAttributes(): string[];
4
+ connectedCallback(): void;
5
+ private getIcon;
6
+ private dismiss;
7
+ static show(message: string, variant?: "success" | "error" | "warning" | "info"): HTMLElement;
8
+ }
@@ -0,0 +1,128 @@
1
+ import { BaseElement } from "../core/base-element.js";
2
+ import { motion } from "../core/motion.js";
3
+ export class GlassToast extends BaseElement {
4
+ static get observedAttributes() {
5
+ return ["variant", "duration"];
6
+ }
7
+ connectedCallback() {
8
+ var _a;
9
+ const variant = this.getAttribute("variant") || "info";
10
+ const duration = parseInt(this.getAttribute("duration") || "4000");
11
+ this.mount(`
12
+ <div class="toast ${variant}">
13
+ <span class="icon">${this.getIcon(variant)}</span>
14
+ <div class="content">
15
+ <slot></slot>
16
+ </div>
17
+ <button class="close" aria-label="Dismiss">×</button>
18
+ <div class="progress"></div>
19
+ </div>
20
+ `, `
21
+ :host {
22
+ position: fixed;
23
+ bottom: 1.5rem;
24
+ right: 1.5rem;
25
+ z-index: 10000;
26
+ }
27
+ .toast {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 0.75rem;
31
+ padding: 1rem 1.25rem;
32
+ background: var(--lg-bg);
33
+ backdrop-filter: blur(var(--lg-blur));
34
+ -webkit-backdrop-filter: blur(var(--lg-blur));
35
+ border-radius: var(--lg-radius);
36
+ border: 1px solid var(--lg-border);
37
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
38
+ min-width: 280px;
39
+ max-width: 400px;
40
+ overflow: hidden;
41
+ }
42
+ .icon {
43
+ font-size: 1.25rem;
44
+ flex-shrink: 0;
45
+ }
46
+ .content {
47
+ flex: 1;
48
+ line-height: 1.4;
49
+ }
50
+ .close {
51
+ background: none;
52
+ border: none;
53
+ color: inherit;
54
+ font-size: 1.5rem;
55
+ cursor: pointer;
56
+ opacity: 0.5;
57
+ padding: 0;
58
+ line-height: 1;
59
+ transition: opacity 0.2s;
60
+ }
61
+ .close:hover {
62
+ opacity: 1;
63
+ }
64
+ .progress {
65
+ position: absolute;
66
+ bottom: 0;
67
+ left: 0;
68
+ height: 3px;
69
+ background: var(--accent-color, rgba(255, 255, 255, 0.5));
70
+ animation: progress ${duration}ms linear forwards;
71
+ }
72
+
73
+ /* Variants */
74
+ .toast.success { --accent-color: #30d158; }
75
+ .toast.success .icon { color: #30d158; }
76
+
77
+ .toast.error { --accent-color: #ff453a; }
78
+ .toast.error .icon { color: #ff453a; }
79
+
80
+ .toast.warning { --accent-color: #ffd60a; }
81
+ .toast.warning .icon { color: #ffd60a; }
82
+
83
+ .toast.info { --accent-color: #5ac8fa; }
84
+ .toast.info .icon { color: #5ac8fa; }
85
+
86
+ @keyframes progress {
87
+ from { width: 100%; }
88
+ to { width: 0%; }
89
+ }
90
+ `);
91
+ // Animate in
92
+ const toast = this.root.querySelector(".toast");
93
+ motion.slideInUp(toast, 300);
94
+ // Close button
95
+ (_a = this.root.querySelector(".close")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => this.dismiss());
96
+ // Auto dismiss
97
+ if (duration > 0) {
98
+ setTimeout(() => this.dismiss(), duration);
99
+ }
100
+ }
101
+ getIcon(variant) {
102
+ const icons = {
103
+ success: "✓",
104
+ error: "✕",
105
+ warning: "⚠",
106
+ info: "ℹ"
107
+ };
108
+ return icons[variant] || icons.info;
109
+ }
110
+ dismiss() {
111
+ const toast = this.root.querySelector(".toast");
112
+ if (toast) {
113
+ motion.slideOutDown(toast, 200).finished.then(() => this.remove());
114
+ }
115
+ else {
116
+ this.remove();
117
+ }
118
+ }
119
+ // Static helper for creating toasts
120
+ static show(message, variant = "info") {
121
+ const toast = document.createElement("glass-toast");
122
+ toast.setAttribute("variant", variant);
123
+ toast.textContent = message;
124
+ document.body.appendChild(toast);
125
+ return toast;
126
+ }
127
+ }
128
+ customElements.define("glass-toast", GlassToast);
@@ -0,0 +1,9 @@
1
+ import { BaseElement } from "../core/base-element.js";
2
+ export declare class GlassToggle extends BaseElement {
3
+ static get observedAttributes(): string[];
4
+ connectedCallback(): void;
5
+ private updateThumb;
6
+ attributeChangedCallback(name: string): void;
7
+ get checked(): boolean;
8
+ set checked(value: boolean);
9
+ }
@@ -0,0 +1,112 @@
1
+ import { BaseElement } from "../core/base-element.js";
2
+ import { springAnimate, bouncySpring } from "../core/spring-engine.js";
3
+ export class GlassToggle extends BaseElement {
4
+ static get observedAttributes() {
5
+ return ["checked", "disabled"];
6
+ }
7
+ connectedCallback() {
8
+ this.mount(`
9
+ <button role="switch" aria-checked="false">
10
+ <span class="track">
11
+ <span class="thumb"></span>
12
+ </span>
13
+ <span class="label"><slot></slot></span>
14
+ </button>
15
+ `, `
16
+ :host {
17
+ display: inline-block;
18
+ }
19
+ button {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 0.75rem;
23
+ background: none;
24
+ border: none;
25
+ color: inherit;
26
+ font: inherit;
27
+ cursor: pointer;
28
+ padding: 0;
29
+ }
30
+ button:focus-visible .track {
31
+ outline: 2px solid var(--lg-accent-focus, #5ac8fa);
32
+ outline-offset: 2px;
33
+ }
34
+ button:disabled {
35
+ opacity: 0.5;
36
+ cursor: not-allowed;
37
+ }
38
+ .track {
39
+ position: relative;
40
+ width: 52px;
41
+ height: 32px;
42
+ background: rgba(255, 255, 255, 0.15);
43
+ border-radius: 999px;
44
+ border: 1px solid var(--lg-border);
45
+ transition: background 0.3s ease;
46
+ }
47
+ .thumb {
48
+ position: absolute;
49
+ top: 3px;
50
+ left: 3px;
51
+ width: 24px;
52
+ height: 24px;
53
+ background: white;
54
+ border-radius: 50%;
55
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
56
+ }
57
+ :host([checked]) .track {
58
+ background: var(--lg-accent);
59
+ }
60
+ .label {
61
+ user-select: none;
62
+ }
63
+ `);
64
+ const button = this.root.querySelector("button");
65
+ const thumb = this.root.querySelector(".thumb");
66
+ button.addEventListener("click", () => {
67
+ if (this.hasAttribute("disabled"))
68
+ return;
69
+ this.toggleAttribute("checked");
70
+ });
71
+ // Spring animation for thumb
72
+ this.updateThumb(thumb, false);
73
+ }
74
+ updateThumb(thumb, animate = true) {
75
+ const checked = this.hasAttribute("checked");
76
+ const target = checked ? 21 : 3;
77
+ if (animate) {
78
+ const current = parseFloat(thumb.style.left) || 3;
79
+ springAnimate(current, target, v => {
80
+ thumb.style.left = `${v}px`;
81
+ }, bouncySpring);
82
+ }
83
+ else {
84
+ thumb.style.left = `${target}px`;
85
+ }
86
+ const button = this.root.querySelector("button");
87
+ button === null || button === void 0 ? void 0 : button.setAttribute("aria-checked", String(checked));
88
+ }
89
+ attributeChangedCallback(name) {
90
+ if (name === "checked") {
91
+ const thumb = this.root.querySelector(".thumb");
92
+ if (thumb)
93
+ this.updateThumb(thumb);
94
+ this.dispatchEvent(new CustomEvent("change", {
95
+ detail: { checked: this.hasAttribute("checked") },
96
+ bubbles: true
97
+ }));
98
+ }
99
+ if (name === "disabled") {
100
+ const button = this.root.querySelector("button");
101
+ if (button)
102
+ button.disabled = this.hasAttribute("disabled");
103
+ }
104
+ }
105
+ get checked() {
106
+ return this.hasAttribute("checked");
107
+ }
108
+ set checked(value) {
109
+ this.toggleAttribute("checked", value);
110
+ }
111
+ }
112
+ customElements.define("glass-toggle", GlassToggle);
@@ -0,0 +1,14 @@
1
+ import { BaseElement } from "../core/base-element.js";
2
+ export declare class GlassTooltip extends BaseElement {
3
+ private tooltipEl?;
4
+ static get observedAttributes(): string[];
5
+ connectedCallback(): void;
6
+ }
7
+ export declare class GlassPopover extends BaseElement {
8
+ private isOpen;
9
+ static get observedAttributes(): string[];
10
+ connectedCallback(): void;
11
+ toggle(): void;
12
+ open(): void;
13
+ close(): void;
14
+ }
@@ -0,0 +1,214 @@
1
+ import { BaseElement } from "../core/base-element.js";
2
+ export class GlassTooltip extends BaseElement {
3
+ static get observedAttributes() {
4
+ return ["position", "delay"];
5
+ }
6
+ connectedCallback() {
7
+ const position = this.getAttribute("position") || "top";
8
+ this.mount(`
9
+ <div class="wrapper">
10
+ <slot></slot>
11
+ <div class="tooltip ${position}" role="tooltip">
12
+ <slot name="content"></slot>
13
+ </div>
14
+ </div>
15
+ `, `
16
+ :host {
17
+ display: inline-block;
18
+ position: relative;
19
+ }
20
+ .wrapper {
21
+ position: relative;
22
+ display: inline-block;
23
+ }
24
+ .tooltip {
25
+ position: absolute;
26
+ padding: 0.5rem 0.75rem;
27
+ background: var(--lg-bg);
28
+ backdrop-filter: blur(var(--lg-blur));
29
+ border: 1px solid var(--lg-border);
30
+ border-radius: calc(var(--lg-radius) / 2);
31
+ box-shadow: var(--lg-shadow-sm);
32
+ font-size: 0.875rem;
33
+ white-space: nowrap;
34
+ opacity: 0;
35
+ visibility: hidden;
36
+ transform: scale(0.9);
37
+ transition: all 0.15s cubic-bezier(0.16, 1, 0.3, 1);
38
+ z-index: 10000;
39
+ pointer-events: none;
40
+ }
41
+ .wrapper:hover .tooltip,
42
+ .wrapper:focus-within .tooltip {
43
+ opacity: 1;
44
+ visibility: visible;
45
+ transform: scale(1);
46
+ }
47
+
48
+ /* Positions */
49
+ .tooltip.top {
50
+ bottom: calc(100% + 8px);
51
+ left: 50%;
52
+ transform-origin: bottom center;
53
+ }
54
+ .wrapper:hover .tooltip.top,
55
+ .wrapper:focus-within .tooltip.top {
56
+ transform: translateX(-50%) scale(1);
57
+ }
58
+ .tooltip.top:not(:hover) {
59
+ transform: translateX(-50%) scale(0.9);
60
+ }
61
+
62
+ .tooltip.bottom {
63
+ top: calc(100% + 8px);
64
+ left: 50%;
65
+ transform-origin: top center;
66
+ }
67
+ .wrapper:hover .tooltip.bottom,
68
+ .wrapper:focus-within .tooltip.bottom {
69
+ transform: translateX(-50%) scale(1);
70
+ }
71
+ .tooltip.bottom:not(:hover) {
72
+ transform: translateX(-50%) scale(0.9);
73
+ }
74
+
75
+ .tooltip.left {
76
+ right: calc(100% + 8px);
77
+ top: 50%;
78
+ transform-origin: right center;
79
+ }
80
+ .wrapper:hover .tooltip.left,
81
+ .wrapper:focus-within .tooltip.left {
82
+ transform: translateY(-50%) scale(1);
83
+ }
84
+ .tooltip.left:not(:hover) {
85
+ transform: translateY(-50%) scale(0.9);
86
+ }
87
+
88
+ .tooltip.right {
89
+ left: calc(100% + 8px);
90
+ top: 50%;
91
+ transform-origin: left center;
92
+ }
93
+ .wrapper:hover .tooltip.right,
94
+ .wrapper:focus-within .tooltip.right {
95
+ transform: translateY(-50%) scale(1);
96
+ }
97
+ .tooltip.right:not(:hover) {
98
+ transform: translateY(-50%) scale(0.9);
99
+ }
100
+ `);
101
+ }
102
+ }
103
+ customElements.define("glass-tooltip", GlassTooltip);
104
+ // Popover with click trigger
105
+ export class GlassPopover extends BaseElement {
106
+ constructor() {
107
+ super(...arguments);
108
+ this.isOpen = false;
109
+ }
110
+ static get observedAttributes() {
111
+ return ["position", "open"];
112
+ }
113
+ connectedCallback() {
114
+ var _a;
115
+ const position = this.getAttribute("position") || "bottom";
116
+ this.mount(`
117
+ <div class="wrapper">
118
+ <div class="trigger">
119
+ <slot name="trigger"></slot>
120
+ </div>
121
+ <div class="popover ${position}" role="dialog">
122
+ <slot></slot>
123
+ </div>
124
+ </div>
125
+ `, `
126
+ :host {
127
+ display: inline-block;
128
+ position: relative;
129
+ }
130
+ .wrapper {
131
+ position: relative;
132
+ }
133
+ .trigger {
134
+ cursor: pointer;
135
+ }
136
+ .popover {
137
+ position: absolute;
138
+ min-width: 200px;
139
+ padding: 1rem;
140
+ background: var(--lg-bg);
141
+ backdrop-filter: blur(var(--lg-blur));
142
+ border: 1px solid var(--lg-border);
143
+ border-radius: var(--lg-radius);
144
+ box-shadow: var(--lg-shadow);
145
+ opacity: 0;
146
+ visibility: hidden;
147
+ transform: scale(0.95);
148
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
149
+ z-index: 10000;
150
+ }
151
+ :host([open]) .popover {
152
+ opacity: 1;
153
+ visibility: visible;
154
+ transform: scale(1);
155
+ }
156
+
157
+ .popover.top {
158
+ bottom: calc(100% + 8px);
159
+ left: 50%;
160
+ transform-origin: bottom center;
161
+ }
162
+ :host([open]) .popover.top { transform: translateX(-50%) scale(1); }
163
+ .popover.top { transform: translateX(-50%) scale(0.95); }
164
+
165
+ .popover.bottom {
166
+ top: calc(100% + 8px);
167
+ left: 50%;
168
+ transform-origin: top center;
169
+ }
170
+ :host([open]) .popover.bottom { transform: translateX(-50%) scale(1); }
171
+ .popover.bottom { transform: translateX(-50%) scale(0.95); }
172
+
173
+ .popover.left {
174
+ right: calc(100% + 8px);
175
+ top: 50%;
176
+ transform-origin: right center;
177
+ }
178
+ :host([open]) .popover.left { transform: translateY(-50%) scale(1); }
179
+ .popover.left { transform: translateY(-50%) scale(0.95); }
180
+
181
+ .popover.right {
182
+ left: calc(100% + 8px);
183
+ top: 50%;
184
+ transform-origin: left center;
185
+ }
186
+ :host([open]) .popover.right { transform: translateY(-50%) scale(1); }
187
+ .popover.right { transform: translateY(-50%) scale(0.95); }
188
+ `);
189
+ (_a = this.root.querySelector(".trigger")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", () => this.toggle());
190
+ // Close on outside click
191
+ document.addEventListener("click", (e) => {
192
+ if (!this.contains(e.target) && this.isOpen) {
193
+ this.close();
194
+ }
195
+ });
196
+ // Close on Escape
197
+ this.addEventListener("keydown", (e) => {
198
+ if (e.key === "Escape")
199
+ this.close();
200
+ });
201
+ }
202
+ toggle() {
203
+ this.isOpen ? this.close() : this.open();
204
+ }
205
+ open() {
206
+ this.isOpen = true;
207
+ this.setAttribute("open", "");
208
+ }
209
+ close() {
210
+ this.isOpen = false;
211
+ this.removeAttribute("open");
212
+ }
213
+ }
214
+ customElements.define("glass-popover", GlassPopover);
@@ -0,0 +1,4 @@
1
+ export declare abstract class BaseElement extends HTMLElement {
2
+ protected root: ShadowRoot;
3
+ protected mount(html: string, css: string): void;
4
+ }
@@ -0,0 +1,9 @@
1
+ export class BaseElement extends HTMLElement {
2
+ constructor() {
3
+ super(...arguments);
4
+ this.root = this.attachShadow({ mode: "open" });
5
+ }
6
+ mount(html, css) {
7
+ this.root.innerHTML = `<style>${css}</style>${html}`;
8
+ }
9
+ }
@@ -0,0 +1,22 @@
1
+ export declare const liquidCurves: {
2
+ standard: string;
3
+ emphasized: string;
4
+ decelerate: string;
5
+ accelerate: string;
6
+ glassIn: string;
7
+ glassOut: string;
8
+ glassInOut: string;
9
+ elasticOut: string;
10
+ bounceOut: string;
11
+ smooth: string;
12
+ smoothIn: string;
13
+ smoothOut: string;
14
+ };
15
+ export declare const durations: {
16
+ instant: number;
17
+ fast: number;
18
+ normal: number;
19
+ slow: number;
20
+ reveal: number;
21
+ };
22
+ export declare function createTransition(properties: string | string[], duration?: number, curve?: string): string;
@@ -0,0 +1,32 @@
1
+ // Easing curves
2
+ export const liquidCurves = {
3
+ // Standard curves
4
+ standard: "cubic-bezier(0.4, 0, 0.2, 1)",
5
+ emphasized: "cubic-bezier(0.2, 0, 0, 1)",
6
+ decelerate: "cubic-bezier(0, 0, 0.2, 1)",
7
+ accelerate: "cubic-bezier(0.4, 0, 1, 1)",
8
+ // Glass curves
9
+ glassIn: "cubic-bezier(0.16, 1, 0.3, 1)",
10
+ glassOut: "cubic-bezier(0.7, 0, 0.84, 0)",
11
+ glassInOut: "cubic-bezier(0.87, 0, 0.13, 1)",
12
+ // Elastic curves
13
+ elasticOut: "cubic-bezier(0.34, 1.56, 0.64, 1)",
14
+ bounceOut: "cubic-bezier(0.34, 1.3, 0.64, 1)",
15
+ // Smooth
16
+ smooth: "cubic-bezier(0.25, 0.1, 0.25, 1)",
17
+ smoothIn: "cubic-bezier(0.42, 0, 1, 1)",
18
+ smoothOut: "cubic-bezier(0, 0, 0.58, 1)",
19
+ };
20
+ // Duration presets
21
+ export const durations = {
22
+ instant: 100,
23
+ fast: 150,
24
+ normal: 250,
25
+ slow: 400,
26
+ reveal: 600,
27
+ };
28
+ // Helper to create transition strings
29
+ export function createTransition(properties, duration = durations.normal, curve = liquidCurves.standard) {
30
+ const props = Array.isArray(properties) ? properties : [properties];
31
+ return props.map(p => `${p} ${duration}ms ${curve}`).join(", ");
32
+ }
@@ -0,0 +1 @@
1
+ export declare function trapFocus(el: HTMLElement): () => void;
@@ -0,0 +1,19 @@
1
+ export function trapFocus(el) {
2
+ const items = el.querySelectorAll("button,input,[tabindex]:not([tabindex='-1'])");
3
+ const first = items[0], last = items[items.length - 1];
4
+ function handler(e) {
5
+ if (e.key !== "Tab")
6
+ return;
7
+ if (e.shiftKey && document.activeElement === first) {
8
+ e.preventDefault();
9
+ last.focus();
10
+ }
11
+ else if (!e.shiftKey && document.activeElement === last) {
12
+ e.preventDefault();
13
+ first.focus();
14
+ }
15
+ }
16
+ el.addEventListener("keydown", handler);
17
+ first === null || first === void 0 ? void 0 : first.focus();
18
+ return () => el.removeEventListener("keydown", handler);
19
+ }
@@ -0,0 +1,3 @@
1
+ export declare const glowCSS: (accent: string) => string;
2
+ export declare function createRipple(event: MouseEvent, element: HTMLElement, color?: string): void;
3
+ export declare function injectRippleStyles(): void;
@@ -0,0 +1,57 @@
1
+ // Glow effect CSS
2
+ export const glowCSS = (accent) => `
3
+ position: absolute;
4
+ inset: -2px;
5
+ background: ${accent};
6
+ filter: blur(12px);
7
+ opacity: 0;
8
+ transition: opacity 0.3s ease;
9
+ pointer-events: none;
10
+ z-index: -1;
11
+ border-radius: inherit;
12
+ `;
13
+ // Ripple effect
14
+ export function createRipple(event, element, color = "rgba(255, 255, 255, 0.4)") {
15
+ const rect = element.getBoundingClientRect();
16
+ const size = Math.max(rect.width, rect.height) * 2;
17
+ const x = event.clientX - rect.left - size / 2;
18
+ const y = event.clientY - rect.top - size / 2;
19
+ const ripple = document.createElement("span");
20
+ ripple.style.cssText = `
21
+ position: absolute;
22
+ width: ${size}px;
23
+ height: ${size}px;
24
+ left: ${x}px;
25
+ top: ${y}px;
26
+ background: ${color};
27
+ border-radius: 50%;
28
+ transform: scale(0);
29
+ opacity: 1;
30
+ pointer-events: none;
31
+ animation: ripple-expand 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
32
+ `;
33
+ element.style.position = "relative";
34
+ element.style.overflow = "hidden";
35
+ element.appendChild(ripple);
36
+ ripple.addEventListener("animationend", () => ripple.remove());
37
+ }
38
+ // Inject ripple keyframes
39
+ export function injectRippleStyles() {
40
+ if (document.getElementById("lg-ripple-styles"))
41
+ return;
42
+ const style = document.createElement("style");
43
+ style.id = "lg-ripple-styles";
44
+ style.textContent = `
45
+ @keyframes ripple-expand {
46
+ to {
47
+ transform: scale(1);
48
+ opacity: 0;
49
+ }
50
+ }
51
+ `;
52
+ document.head.appendChild(style);
53
+ }
54
+ // Init on load
55
+ if (typeof document !== "undefined") {
56
+ injectRippleStyles();
57
+ }
@@ -0,0 +1,12 @@
1
+ export declare const motion: {
2
+ animate(element: HTMLElement, keyframes: Keyframe[], options: KeyframeAnimationOptions): Animation;
3
+ fadeIn(element: HTMLElement, duration?: number): Animation;
4
+ fadeOut(element: HTMLElement, duration?: number): Animation;
5
+ scaleIn(element: HTMLElement, duration?: number): Animation;
6
+ scaleOut(element: HTMLElement, duration?: number): Animation;
7
+ slideInUp(element: HTMLElement, duration?: number): Animation;
8
+ slideOutDown(element: HTMLElement, duration?: number): Animation;
9
+ blurIn(element: HTMLElement, duration?: number): Animation;
10
+ shimmer(element: HTMLElement): Animation;
11
+ pulseGlow(element: HTMLElement): Animation;
12
+ };