flowcss-style 1.0.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/dist/flow.js ADDED
@@ -0,0 +1,264 @@
1
+ /*!
2
+ * FlowCSS v1.0.0 — JavaScript companion
3
+ * Handles: scroll reveals, accordion, tabs, ripple, typewriter, toast, theme
4
+ */
5
+ (function (global) {
6
+ 'use strict';
7
+
8
+ const Flow = {};
9
+
10
+ /* ─── Scroll Reveal ──────────────────────────────────────── */
11
+ Flow.initReveal = function (options = {}) {
12
+ const threshold = options.threshold ?? 0.15;
13
+ const rootMargin = options.rootMargin ?? '0px 0px -60px 0px';
14
+
15
+ const observer = new IntersectionObserver((entries) => {
16
+ entries.forEach(entry => {
17
+ if (entry.isIntersecting) {
18
+ entry.target.classList.add('is-visible');
19
+ if (!options.repeat) observer.unobserve(entry.target);
20
+ } else if (options.repeat) {
21
+ entry.target.classList.remove('is-visible');
22
+ }
23
+ });
24
+ }, { threshold, rootMargin });
25
+
26
+ document.querySelectorAll('.flow-reveal').forEach(el => observer.observe(el));
27
+ return observer;
28
+ };
29
+
30
+ /* ─── Stagger animation trigger ─────────────────────────── */
31
+ Flow.initStagger = function () {
32
+ const observer = new IntersectionObserver((entries) => {
33
+ entries.forEach(entry => {
34
+ if (entry.isIntersecting) {
35
+ entry.target.querySelectorAll(':scope > *').forEach((child, i) => {
36
+ child.style.animationDelay = `${i * 80}ms`;
37
+ child.style.opacity = '';
38
+ });
39
+ observer.unobserve(entry.target);
40
+ }
41
+ });
42
+ }, { threshold: 0.1 });
43
+ document.querySelectorAll('.flow-stagger').forEach(el => observer.observe(el));
44
+ };
45
+
46
+ /* ─── Accordion ──────────────────────────────────────────── */
47
+ Flow.initAccordion = function (selector = '.flow-accordion') {
48
+ document.querySelectorAll(selector).forEach(accordion => {
49
+ accordion.querySelectorAll('.flow-accordion__trigger').forEach(trigger => {
50
+ trigger.addEventListener('click', () => {
51
+ const item = trigger.closest('.flow-accordion__item');
52
+ const isOpen = item.classList.contains('is-open');
53
+ // Close siblings if data-single
54
+ if (accordion.dataset.single !== undefined) {
55
+ accordion.querySelectorAll('.flow-accordion__item.is-open').forEach(el => el.classList.remove('is-open'));
56
+ }
57
+ item.classList.toggle('is-open', !isOpen);
58
+ });
59
+ });
60
+ });
61
+ };
62
+
63
+ /* ─── Tabs ───────────────────────────────────────────────── */
64
+ Flow.initTabs = function () {
65
+ document.querySelectorAll('.flow-tabs').forEach(tabBar => {
66
+ const panelsId = tabBar.dataset.panels;
67
+ const panels = panelsId ? document.getElementById(panelsId) : null;
68
+ tabBar.querySelectorAll('.flow-tab').forEach((tab, i) => {
69
+ tab.addEventListener('click', () => {
70
+ tabBar.querySelectorAll('.flow-tab').forEach(t => t.classList.remove('is-active'));
71
+ tab.classList.add('is-active');
72
+ if (panels) {
73
+ panels.querySelectorAll('[data-panel]').forEach((p, j) => {
74
+ p.style.display = j === i ? '' : 'none';
75
+ });
76
+ }
77
+ tabBar.dispatchEvent(new CustomEvent('flow:tab-change', { detail: { index: i, tab } }));
78
+ });
79
+ });
80
+ });
81
+ };
82
+
83
+ /* ─── Ripple Effect ──────────────────────────────────────── */
84
+ Flow.initRipple = function (selector = '.flow-btn--ripple') {
85
+ document.querySelectorAll(selector).forEach(btn => {
86
+ btn.addEventListener('click', function (e) {
87
+ const rect = btn.getBoundingClientRect();
88
+ const size = Math.max(rect.width, rect.height) * 2;
89
+ const x = e.clientX - rect.left - size / 2;
90
+ const y = e.clientY - rect.top - size / 2;
91
+ const ripple = document.createElement('span');
92
+ ripple.className = 'flow-ripple-wave';
93
+ ripple.style.cssText = `width:${size}px;height:${size}px;left:${x}px;top:${y}px;`;
94
+ btn.appendChild(ripple);
95
+ ripple.addEventListener('animationend', () => ripple.remove());
96
+ });
97
+ });
98
+ };
99
+
100
+ /* ─── Typewriter ─────────────────────────────────────────── */
101
+ Flow.typewriter = function (el, options = {}) {
102
+ const text = options.text || el.textContent;
103
+ const speed = options.speed || 50;
104
+ const loop = options.loop || false;
105
+ el.textContent = '';
106
+ el.classList.remove('flow-typewriter'); // remove CSS fallback
107
+ el.style.borderRight = '2px solid currentColor';
108
+ el.style.whiteSpace = 'nowrap';
109
+ el.style.overflow = 'hidden';
110
+
111
+ let i = 0;
112
+ function type() {
113
+ if (i <= text.length) {
114
+ el.textContent = text.slice(0, i);
115
+ i++;
116
+ setTimeout(type, speed);
117
+ } else if (loop) {
118
+ setTimeout(() => {
119
+ i = 0;
120
+ type();
121
+ }, 2000);
122
+ }
123
+ }
124
+ type();
125
+ };
126
+
127
+ /* ─── Toast Notifications ────────────────────────────────── */
128
+ Flow.toast = function (message, options = {}) {
129
+ let container = document.querySelector('.flow-toast-container');
130
+ if (!container) {
131
+ container = document.createElement('div');
132
+ container.className = 'flow-toast-container';
133
+ document.body.appendChild(container);
134
+ }
135
+
136
+ const toast = document.createElement('div');
137
+ const type = options.type || '';
138
+ toast.className = `flow-toast${type ? ` flow-toast--${type}` : ''}`;
139
+ toast.innerHTML = `
140
+ ${options.icon ? `<span>${options.icon}</span>` : ''}
141
+ <span style="flex:1">${message}</span>
142
+ <button onclick="this.parentElement.remove()" style="background:none;border:none;color:inherit;cursor:pointer;opacity:0.6;font-size:1rem">✕</button>
143
+ `;
144
+ container.appendChild(toast);
145
+
146
+ const duration = options.duration ?? 4000;
147
+ if (duration > 0) {
148
+ setTimeout(() => {
149
+ toast.style.opacity = '0';
150
+ toast.style.transform = 'translateX(110%)';
151
+ toast.style.transition = 'all 0.3s ease';
152
+ setTimeout(() => toast.remove(), 300);
153
+ }, duration);
154
+ }
155
+ return toast;
156
+ };
157
+
158
+ /* ─── Theme Toggle ───────────────────────────────────────── */
159
+ Flow.initTheme = function () {
160
+ const saved = localStorage.getItem('flow-theme') || 'light';
161
+ document.documentElement.dataset.theme = saved;
162
+
163
+ document.querySelectorAll('[data-flow-theme-toggle]').forEach(btn => {
164
+ btn.addEventListener('click', () => {
165
+ const current = document.documentElement.dataset.theme;
166
+ const next = current === 'dark' ? 'light' : 'dark';
167
+ document.documentElement.dataset.theme = next;
168
+ localStorage.setItem('flow-theme', next);
169
+ btn.dispatchEvent(new CustomEvent('flow:theme-change', { detail: { theme: next }, bubbles: true }));
170
+ });
171
+ });
172
+ };
173
+
174
+ /* ─── Counter Animation ──────────────────────────────────── */
175
+ Flow.animateCounter = function (el, options = {}) {
176
+ const target = parseFloat(options.target ?? el.dataset.count ?? 100);
177
+ const duration = options.duration ?? 1500;
178
+ const prefix = options.prefix ?? el.dataset.prefix ?? '';
179
+ const suffix = options.suffix ?? el.dataset.suffix ?? '';
180
+ const decimals = options.decimals ?? 0;
181
+ const start = performance.now();
182
+ const from = options.from ?? 0;
183
+
184
+ function update(now) {
185
+ const elapsed = now - start;
186
+ const progress = Math.min(elapsed / duration, 1);
187
+ const ease = 1 - Math.pow(1 - progress, 3);
188
+ const value = from + (target - from) * ease;
189
+ el.textContent = prefix + value.toFixed(decimals) + suffix;
190
+ if (progress < 1) requestAnimationFrame(update);
191
+ }
192
+ requestAnimationFrame(update);
193
+ };
194
+
195
+ Flow.initCounters = function () {
196
+ const observer = new IntersectionObserver(entries => {
197
+ entries.forEach(entry => {
198
+ if (entry.isIntersecting) {
199
+ Flow.animateCounter(entry.target);
200
+ observer.unobserve(entry.target);
201
+ }
202
+ });
203
+ }, { threshold: 0.5 });
204
+ document.querySelectorAll('[data-flow-counter]').forEach(el => observer.observe(el));
205
+ };
206
+
207
+ /* ─── Smooth scroll for anchors ──────────────────────────── */
208
+ Flow.initSmoothScroll = function () {
209
+ document.querySelectorAll('a[href^="#"]').forEach(a => {
210
+ a.addEventListener('click', e => {
211
+ const target = document.querySelector(a.getAttribute('href'));
212
+ if (target) {
213
+ e.preventDefault();
214
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
215
+ }
216
+ });
217
+ });
218
+ };
219
+
220
+ /* ─── Parallax ───────────────────────────────────────────── */
221
+ Flow.initParallax = function (selector = '[data-parallax]') {
222
+ const els = document.querySelectorAll(selector);
223
+ if (!els.length) return;
224
+ window.addEventListener('scroll', () => {
225
+ const scrollY = window.scrollY;
226
+ els.forEach(el => {
227
+ const speed = parseFloat(el.dataset.parallax ?? 0.3);
228
+ el.style.transform = `translateY(${scrollY * speed}px)`;
229
+ });
230
+ }, { passive: true });
231
+ };
232
+
233
+ /* ─── Magnetic button ────────────────────────────────────── */
234
+ Flow.initMagnetic = function (selector = '.flow-magnetic') {
235
+ document.querySelectorAll(selector).forEach(el => {
236
+ el.addEventListener('mousemove', e => {
237
+ const rect = el.getBoundingClientRect();
238
+ const x = e.clientX - rect.left - rect.width / 2;
239
+ const y = e.clientY - rect.top - rect.height / 2;
240
+ el.style.transform = `translate(${x * 0.25}px, ${y * 0.25}px)`;
241
+ });
242
+ el.addEventListener('mouseleave', () => {
243
+ el.style.transform = '';
244
+ el.style.transition = 'transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)';
245
+ });
246
+ });
247
+ };
248
+
249
+ /* ─── Auto-init on DOMContentLoaded ─────────────────────── */
250
+ document.addEventListener('DOMContentLoaded', () => {
251
+ Flow.initReveal();
252
+ Flow.initStagger();
253
+ Flow.initAccordion();
254
+ Flow.initTabs();
255
+ Flow.initRipple();
256
+ Flow.initTheme();
257
+ Flow.initCounters();
258
+ Flow.initSmoothScroll();
259
+ Flow.initParallax();
260
+ Flow.initMagnetic();
261
+ });
262
+
263
+ global.Flow = Flow;
264
+ })(window);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "flowcss-style",
3
+ "version": "1.0.0",
4
+ "description": "A modern CSS framework with 40+ animations, components, glassmorphism, and dark mode.",
5
+ "main": "dist/flow.css",
6
+ "style": "dist/flow.css",
7
+ "files": [
8
+ "dist/",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "keywords": [
13
+ "css",
14
+ "framework",
15
+ "animations",
16
+ "utility",
17
+ "glassmorphism",
18
+ "dark-mode",
19
+ "tailwind-alternative"
20
+ ],
21
+ "author": "Your Name <you@example.com>",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/yourusername/flowcss.git"
26
+ },
27
+ "homepage": "https://flowcss.dev",
28
+ "scripts": {
29
+ "prepublishOnly": "echo Build and copy dist files here"
30
+ }
31
+ }