shopify-drag-carousel 1.0.0 → 1.0.4

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 CHANGED
@@ -18,28 +18,84 @@ A tiny, zero-dependency behavior layer for smooth mouse and touch drag scrolling
18
18
  npm install shopify-drag-carousel
19
19
  ```
20
20
 
21
- ## CDN (no build step)
21
+ ```js
22
+ const DragCarousel = require("shopify-drag-carousel");
23
+ ```
22
24
 
23
- Script (global `DragCarousel` on `window`):
25
+ ---
24
26
 
25
- ```html
26
- <script src="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js"></script>
27
- ```
27
+ ## Quick start (browser)
28
28
 
29
- Optional CSS for the dragging cursor:
29
+ 1. Add a horizontal row of children (your own layout: flex, grid, etc.).
30
+ 2. Put the library on the page, then add class **`drag-carousel`** to the scroll container.
30
31
 
31
32
  ```html
32
33
  <link
33
34
  rel="stylesheet"
34
35
  href="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/style.css"
35
36
  />
37
+ <div class="drag-carousel">
38
+ <div>Slide A</div>
39
+ <div>Slide B</div>
40
+ <div>Slide C</div>
41
+ </div>
42
+ <script src="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js"></script>
43
+ ```
44
+
45
+ After load, `window.DragCarousel` is available. Auto-init runs on `DOMContentLoaded` for every `.drag-carousel`.
46
+
47
+ **Manual init** (e.g. custom speed or arrows):
48
+
49
+ ```html
50
+ <div id="strip"></div>
51
+ <button type="button" id="prev" aria-label="Previous">←</button>
52
+ <button type="button" id="next" aria-label="Next">→</button>
53
+ <script src="https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js"></script>
54
+ <script>
55
+ new DragCarousel("#strip", {
56
+ speed: 1.5,
57
+ buttons: { prev: "#prev", next: "#next" },
58
+ });
59
+ </script>
36
60
  ```
37
61
 
38
- Pin a major version (`@1`) or an exact version in production.
62
+ Do **not** put `class="drag-carousel"` on the same node if you also call `new DragCarousel` on it with options (avoid double init).
63
+
64
+ ---
65
+
66
+ ## CDN URLs
67
+
68
+ Use a **major** tag (`@1`) or pin an exact version (`@1.0.0`) in production.
69
+
70
+ | Asset | jsDelivr | unpkg |
71
+ | ----- | -------- | ----- |
72
+ | Script | [cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js](https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/index.js) | [unpkg.com/shopify-drag-carousel@1/index.js](https://unpkg.com/shopify-drag-carousel@1/index.js) |
73
+ | Styles (optional) | [cdn.jsdelivr.net/npm/shopify-drag-carousel@1/style.css](https://cdn.jsdelivr.net/npm/shopify-drag-carousel@1/style.css) | [unpkg.com/shopify-drag-carousel@1/style.css](https://unpkg.com/shopify-drag-carousel@1/style.css) |
74
+
75
+ ---
76
+
77
+ ## Live example (bundled demo)
78
+
79
+ The package includes HTML examples under **`examples/`**:
80
+
81
+ | Demo | What it is |
82
+ | ---- | ----------- |
83
+ | **[examples/demo-cdn.html](https://unpkg.com/shopify-drag-carousel@1/examples/demo-cdn.html)** | Full page on **unpkg** — loads the library from the CDN, fetches sample products from [api.escuelajs.co](https://api.escuelajs.co/docs), drag + prev/next. Requires the version to exist on [npm](https://www.npmjs.com/package/shopify-drag-carousel). |
84
+ | **`examples/demo.html`** | Same UI with **relative** script paths — run a static server from the repo root (see below). |
85
+
86
+ **Run the local demo** (clone or `npm install shopify-drag-carousel`, then serve the **package root** over `http://`, not `file://`):
87
+
88
+ ```bash
89
+ npm run demo
90
+ ```
91
+
92
+ Open [http://localhost:5173/examples/demo.html](http://localhost:5173/examples/demo.html) — or [http://localhost:5173/demo.html](http://localhost:5173/demo.html), which redirects to the example folder.
93
+
94
+ ---
39
95
 
40
96
  ## Shopify (Liquid)
41
97
 
42
- Drop the script (and optional stylesheet) in your theme—**theme.liquid** asset tags, or a section’s `{% schema %}` and `{% javascript %}` / `{% stylesheet %}` as you prefer.
98
+ Load the script and optional CSS in your theme (e.g. `theme.liquid` or section assets).
43
99
 
44
100
  ```liquid
45
101
  <div class="drag-carousel">
@@ -49,61 +105,39 @@ Drop the script (and optional stylesheet) in your theme—**theme.liquid** asset
49
105
  </div>
50
106
  ```
51
107
 
52
- Give children whatever layout you use (flex row, grid, inline blocks, etc.). The library only enables horizontal scrolling and drag behavior.
53
-
54
- After **dynamic** updates (e.g. section AJAX), call:
108
+ Style the row/cards yourself (flex, grid, etc.). After **dynamic** HTML (e.g. section AJAX), call:
55
109
 
56
110
  ```js
57
111
  DragCarousel.initAll();
58
112
  ```
59
113
 
60
- ## Vanilla JavaScript
61
-
62
- **Automatic:** Add class `drag-carousel` to your container; initialization runs when the DOM is ready.
63
-
64
- **Manual:**
65
-
66
- ```js
67
- const carousel = new DragCarousel('#my-row', {
68
- speed: 1.5,
69
- buttons: {
70
- prev: '.prev-btn',
71
- next: '.next-btn',
72
- },
73
- });
74
- ```
75
-
76
- **Node / CommonJS:**
77
-
78
- ```js
79
- const DragCarousel = require('shopify-drag-carousel');
80
- new DragCarousel('.drag-carousel');
81
- ```
114
+ ---
82
115
 
83
116
  ## API
84
117
 
85
118
  ### `new DragCarousel(selectorOrElement, options?)`
86
119
 
87
- | Option | Type | Default | Description |
88
- | --------- | -------- | ------- | ------------------------------------------------ |
89
- | `speed` | `number` | `1.5` | Multiplier applied to pointer movement vs scroll |
90
- | `buttons` | `object` | `null` | `{ prev: string, next: string }` CSS selectors |
120
+ | Option | Type | Default | Description |
121
+ | ------ | ---- | ------- | ----------- |
122
+ | `speed` | `number` | `1.5` | Multiplier for pointer movement vs. scroll |
123
+ | `buttons` | `object` | `null` | `{ prev: string, next: string }` any valid `querySelector` string |
91
124
 
92
- String selectors use `document.querySelector` (first match). For multiple carousels, create one instance per element or rely on **auto-init** + class `drag-carousel`.
125
+ Selectors resolve with `document.querySelector` (first match).
93
126
 
94
127
  ### `DragCarousel.initAll()`
95
128
 
96
- Finds all `.drag-carousel` elements that are not yet initialized and attaches behavior. Use after injecting new HTML.
129
+ Initializes every `.drag-carousel` in the document that is not already initialized. Use after injecting new markup.
97
130
 
98
131
  ### `DragCarousel.isInitialized(element)`
99
132
 
100
- Returns whether the given element already has carousel behavior.
133
+ Returns whether the element already has carousel behavior.
134
+
135
+ ---
101
136
 
102
137
  ## Constraints
103
138
 
104
- - No React or other UI framework required
105
- - No bundler required for browser usage
106
- - Intended to run directly in the browser via script tag or small bundles
139
+ - No React required; no bundler required for browser usage
140
+ - Intended for direct use via `<script>` or small bundles
107
141
 
108
142
  ## License
109
143
 
@@ -0,0 +1,447 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>shopify-drag-carousel — example (CDN)</title>
7
+ <!-- Library from unpkg — no clone or npm install required to try the demo -->
8
+ <link
9
+ rel="stylesheet"
10
+ href="https://unpkg.com/shopify-drag-carousel@1/style.css"
11
+ />
12
+ <style>
13
+ :root {
14
+ --bg: #f4f2ef;
15
+ --surface: #ffffff;
16
+ --text: #1c1917;
17
+ --muted: #78716c;
18
+ --accent: #0d9488;
19
+ --accent-soft: rgba(13, 148, 136, 0.12);
20
+ --radius: 14px;
21
+ --shadow: 0 4px 20px rgba(28, 25, 23, 0.08);
22
+ --shadow-hover: 0 12px 32px rgba(28, 25, 23, 0.12);
23
+ }
24
+
25
+ * {
26
+ box-sizing: border-box;
27
+ }
28
+
29
+ body {
30
+ margin: 0;
31
+ min-height: 100vh;
32
+ font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
33
+ color: var(--text);
34
+ background: radial-gradient(1200px 600px at 10% -10%, #e0f2f1 0%, transparent 55%),
35
+ radial-gradient(900px 500px at 100% 0%, #fef3c7 0%, transparent 50%), var(--bg);
36
+ }
37
+
38
+ .page {
39
+ max-width: 68rem;
40
+ margin: 0 auto;
41
+ padding: 2.5rem 1.25rem 4rem;
42
+ }
43
+
44
+ .hero {
45
+ text-align: center;
46
+ margin-bottom: 2.5rem;
47
+ }
48
+
49
+ .hero__eyebrow {
50
+ display: inline-block;
51
+ font-size: 0.75rem;
52
+ font-weight: 600;
53
+ letter-spacing: 0.12em;
54
+ text-transform: uppercase;
55
+ color: var(--accent);
56
+ background: var(--accent-soft);
57
+ padding: 0.35rem 0.75rem;
58
+ border-radius: 999px;
59
+ margin-bottom: 0.75rem;
60
+ }
61
+
62
+ .hero h1 {
63
+ font-size: clamp(1.5rem, 4vw, 2rem);
64
+ font-weight: 700;
65
+ letter-spacing: -0.02em;
66
+ margin: 0 0 0.5rem;
67
+ }
68
+
69
+ .hero p {
70
+ margin: 0 auto;
71
+ max-width: 36rem;
72
+ color: var(--muted);
73
+ line-height: 1.55;
74
+ font-size: 0.95rem;
75
+ }
76
+
77
+ .hero code {
78
+ font-size: 0.85em;
79
+ background: rgba(0, 0, 0, 0.06);
80
+ padding: 0.15rem 0.4rem;
81
+ border-radius: 6px;
82
+ }
83
+
84
+ .section {
85
+ margin-bottom: 2.75rem;
86
+ }
87
+
88
+ .section__head {
89
+ display: flex;
90
+ align-items: flex-end;
91
+ justify-content: space-between;
92
+ gap: 1rem;
93
+ margin-bottom: 1rem;
94
+ flex-wrap: wrap;
95
+ }
96
+
97
+ .section__title {
98
+ font-size: 1.125rem;
99
+ font-weight: 600;
100
+ margin: 0;
101
+ }
102
+
103
+ .section__hint {
104
+ font-size: 0.8rem;
105
+ color: var(--muted);
106
+ margin: 0.25rem 0 0;
107
+ }
108
+
109
+ .toolbar {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 0.5rem;
113
+ }
114
+
115
+ .toolbar button {
116
+ display: inline-flex;
117
+ align-items: center;
118
+ justify-content: center;
119
+ width: 2.5rem;
120
+ height: 2.5rem;
121
+ border: 1px solid rgba(28, 25, 23, 0.12);
122
+ border-radius: 10px;
123
+ background: var(--surface);
124
+ cursor: pointer;
125
+ color: var(--text);
126
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
127
+ transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
128
+ }
129
+
130
+ .toolbar button:hover:not(:disabled) {
131
+ background: #fafaf9;
132
+ box-shadow: var(--shadow);
133
+ }
134
+
135
+ .toolbar button:active:not(:disabled) {
136
+ transform: scale(0.96);
137
+ }
138
+
139
+ .toolbar button:disabled {
140
+ opacity: 0.35;
141
+ cursor: not-allowed;
142
+ }
143
+
144
+ /* Scroll track: flex row; library adds overflow + drag */
145
+ .scroller {
146
+ display: flex;
147
+ gap: 1rem;
148
+ padding: 0.5rem 0.25rem 1rem;
149
+ scroll-padding-inline: 0.25rem;
150
+ }
151
+
152
+ .scroller-wrap {
153
+ position: relative;
154
+ border-radius: var(--radius);
155
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.72));
156
+ border: 1px solid rgba(28, 25, 23, 0.06);
157
+ box-shadow: var(--shadow);
158
+ padding: 0.75rem 0.5rem;
159
+ }
160
+
161
+ .product-card {
162
+ flex: 0 0 15rem;
163
+ min-width: 15rem;
164
+ background: var(--surface);
165
+ border-radius: var(--radius);
166
+ overflow: hidden;
167
+ border: 1px solid rgba(28, 25, 23, 0.06);
168
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
169
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
170
+ }
171
+
172
+ .product-card:hover {
173
+ transform: translateY(-3px);
174
+ box-shadow: var(--shadow-hover);
175
+ }
176
+
177
+ .product-card__media {
178
+ position: relative;
179
+ aspect-ratio: 4 / 3;
180
+ background: linear-gradient(145deg, #e7e5e4, #d6d3d1);
181
+ }
182
+
183
+ .product-card__media img {
184
+ width: 100%;
185
+ height: 100%;
186
+ object-fit: cover;
187
+ display: block;
188
+ }
189
+
190
+ .product-card__badge {
191
+ position: absolute;
192
+ top: 0.65rem;
193
+ left: 0.65rem;
194
+ font-size: 0.65rem;
195
+ font-weight: 600;
196
+ text-transform: uppercase;
197
+ letter-spacing: 0.06em;
198
+ color: #fff;
199
+ background: rgba(28, 25, 23, 0.72);
200
+ backdrop-filter: blur(6px);
201
+ padding: 0.25rem 0.5rem;
202
+ border-radius: 6px;
203
+ max-width: calc(100% - 1.3rem);
204
+ white-space: nowrap;
205
+ overflow: hidden;
206
+ text-overflow: ellipsis;
207
+ }
208
+
209
+ .product-card__body {
210
+ padding: 0.9rem 1rem 1.1rem;
211
+ }
212
+
213
+ .product-card__title {
214
+ margin: 0 0 0.35rem;
215
+ font-size: 0.9rem;
216
+ font-weight: 600;
217
+ line-height: 1.35;
218
+ display: -webkit-box;
219
+ -webkit-line-clamp: 2;
220
+ -webkit-box-orient: vertical;
221
+ overflow: hidden;
222
+ min-height: 2.4em;
223
+ }
224
+
225
+ .product-card__desc {
226
+ margin: 0 0 0.65rem;
227
+ font-size: 0.72rem;
228
+ line-height: 1.45;
229
+ color: var(--muted);
230
+ display: -webkit-box;
231
+ -webkit-line-clamp: 2;
232
+ -webkit-box-orient: vertical;
233
+ overflow: hidden;
234
+ }
235
+
236
+ .product-card__row {
237
+ display: flex;
238
+ align-items: baseline;
239
+ justify-content: space-between;
240
+ gap: 0.5rem;
241
+ }
242
+
243
+ .product-card__price {
244
+ font-size: 1.05rem;
245
+ font-weight: 700;
246
+ color: var(--accent);
247
+ letter-spacing: -0.02em;
248
+ }
249
+
250
+ .product-card__id {
251
+ font-size: 0.65rem;
252
+ color: #a8a29e;
253
+ font-variant-numeric: tabular-nums;
254
+ }
255
+
256
+ #status {
257
+ margin-top: 1.5rem;
258
+ padding: 0.75rem 1rem;
259
+ border-radius: 10px;
260
+ font-size: 0.85rem;
261
+ background: rgba(255, 255, 255, 0.65);
262
+ border: 1px solid rgba(28, 25, 23, 0.08);
263
+ color: var(--muted);
264
+ }
265
+
266
+ #status.is-error {
267
+ color: #b91c1c;
268
+ border-color: rgba(185, 28, 28, 0.25);
269
+ background: #fef2f2;
270
+ }
271
+
272
+ #status.is-ok {
273
+ color: #0f766e;
274
+ border-color: rgba(13, 148, 136, 0.25);
275
+ background: #f0fdfa;
276
+ }
277
+ </style>
278
+ </head>
279
+ <body>
280
+ <div class="page">
281
+ <header class="hero">
282
+ <span class="hero__eyebrow">Example</span>
283
+ <h1>shopify-drag-carousel</h1>
284
+ <p>
285
+ Products load from
286
+ <code>GET https://api.escuelajs.co/api/v1/products</code>
287
+ — drag the row or use arrows. The library loads from
288
+ <strong>unpkg</strong> (<code>shopify-drag-carousel@1</code>). For local
289
+ dev, use <code>examples/demo.html</code> in the repo instead.
290
+ </p>
291
+ </header>
292
+
293
+ <section class="section" aria-labelledby="sec-manual">
294
+ <div class="section__head">
295
+ <div>
296
+ <h2 class="section__title" id="sec-manual">Manual init + prev / next</h2>
297
+ <p class="section__hint">
298
+ <code>new DragCarousel('#strip-main', { buttons: { … } })</code>
299
+ </p>
300
+ </div>
301
+ <div class="toolbar">
302
+ <button type="button" id="btn-prev" aria-label="Previous products">←</button>
303
+ <button type="button" id="btn-next" aria-label="Next products">→</button>
304
+ </div>
305
+ </div>
306
+ <div class="scroller-wrap">
307
+ <div id="strip-main" class="scroller"></div>
308
+ </div>
309
+ </section>
310
+
311
+ <section class="section" aria-labelledby="sec-auto">
312
+ <div class="section__head">
313
+ <div>
314
+ <h2 class="section__title" id="sec-auto">Auto-init</h2>
315
+ <p class="section__hint">
316
+ Class <code>.drag-carousel</code> — initialized on
317
+ <code>DOMContentLoaded</code>
318
+ </p>
319
+ </div>
320
+ </div>
321
+ <div class="scroller-wrap">
322
+ <div id="strip-auto" class="drag-carousel scroller"></div>
323
+ </div>
324
+ </section>
325
+
326
+ <p id="status" role="status"></p>
327
+ </div>
328
+
329
+ <script src="https://unpkg.com/shopify-drag-carousel@1/index.js"></script>
330
+ <script>
331
+ const API = "https://api.escuelajs.co/api/v1/products";
332
+
333
+ function truncate(s, n) {
334
+ const t = String(s || "");
335
+ return t.length <= n ? t : t.slice(0, n - 1) + "…";
336
+ }
337
+
338
+ function productCard(p) {
339
+ const el = document.createElement("article");
340
+ el.className = "product-card";
341
+
342
+ const media = document.createElement("div");
343
+ media.className = "product-card__media";
344
+
345
+ const img = document.createElement("img");
346
+ img.loading = "lazy";
347
+ const src =
348
+ Array.isArray(p.images) && p.images.length
349
+ ? p.images[0]
350
+ : "";
351
+ img.src =
352
+ src ||
353
+ "data:image/svg+xml," +
354
+ encodeURIComponent(
355
+ '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300"><rect fill="#d6d3d1" width="100%" height="100%"/></svg>'
356
+ );
357
+ const title = String(p.title || "");
358
+ img.alt = title;
359
+
360
+ const badge = document.createElement("span");
361
+ badge.className = "product-card__badge";
362
+ badge.textContent = p.category && p.category.name ? String(p.category.name) : "Product";
363
+
364
+ media.append(img, badge);
365
+
366
+ const body = document.createElement("div");
367
+ body.className = "product-card__body";
368
+
369
+ const h = document.createElement("h3");
370
+ h.className = "product-card__title";
371
+ h.textContent = title;
372
+
373
+ const desc = document.createElement("p");
374
+ desc.className = "product-card__desc";
375
+ desc.textContent = truncate(p.description || "", 120);
376
+
377
+ const row = document.createElement("div");
378
+ row.className = "product-card__row";
379
+
380
+ const price = document.createElement("span");
381
+ price.className = "product-card__price";
382
+ const n = Number(p.price);
383
+ price.textContent = Number.isFinite(n) ? "$" + n.toFixed(2) : "";
384
+
385
+ const id = document.createElement("span");
386
+ id.className = "product-card__id";
387
+ id.textContent = "#" + String(p.id != null ? p.id : "");
388
+
389
+ row.append(price, id);
390
+ body.append(h, desc, row);
391
+ el.append(media, body);
392
+ return el;
393
+ }
394
+
395
+ function fill(el, list) {
396
+ el.replaceChildren();
397
+ list.forEach((p) => el.appendChild(productCard(p)));
398
+ }
399
+
400
+ (async () => {
401
+ const status = document.getElementById("status");
402
+
403
+ if (typeof DragCarousel === "undefined") {
404
+ status.className = "is-error";
405
+ status.textContent =
406
+ "DragCarousel not found. Check that unpkg could load shopify-drag-carousel@1.";
407
+ return;
408
+ }
409
+
410
+ status.textContent = "Loading products…";
411
+
412
+ try {
413
+ const res = await fetch(API);
414
+ if (!res.ok) throw new Error("HTTP " + res.status);
415
+ const all = await res.json();
416
+ if (!Array.isArray(all)) throw new Error("Unexpected JSON");
417
+
418
+ const a = all.slice(0, 12);
419
+ const b = all.slice(12, 24);
420
+
421
+ fill(document.getElementById("strip-main"), a);
422
+ fill(document.getElementById("strip-auto"), b);
423
+
424
+ void new DragCarousel("#strip-main", {
425
+ speed: 1.5,
426
+ buttons: { prev: "#btn-prev", next: "#btn-next" },
427
+ });
428
+
429
+ status.className = "is-ok";
430
+ status.textContent =
431
+ "Loaded " +
432
+ all.length +
433
+ " products (showing " +
434
+ a.length +
435
+ " + " +
436
+ b.length +
437
+ "). Demo loads the package from unpkg — no clone required.";
438
+ } catch (e) {
439
+ status.className = "is-error";
440
+ status.textContent =
441
+ "Could not load the API. Use http:// (e.g. npm run demo), not file://. " +
442
+ (e && e.message ? "(" + e.message + ")" : "");
443
+ }
444
+ })();
445
+ </script>
446
+ </body>
447
+ </html>
@@ -0,0 +1,442 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>shopify-drag-carousel — example</title>
7
+ <!-- Library optional CSS (grabbing cursor); behavior also injects base styles -->
8
+ <link rel="stylesheet" href="../style.css" />
9
+ <style>
10
+ :root {
11
+ --bg: #f4f2ef;
12
+ --surface: #ffffff;
13
+ --text: #1c1917;
14
+ --muted: #78716c;
15
+ --accent: #0d9488;
16
+ --accent-soft: rgba(13, 148, 136, 0.12);
17
+ --radius: 14px;
18
+ --shadow: 0 4px 20px rgba(28, 25, 23, 0.08);
19
+ --shadow-hover: 0 12px 32px rgba(28, 25, 23, 0.12);
20
+ }
21
+
22
+ * {
23
+ box-sizing: border-box;
24
+ }
25
+
26
+ body {
27
+ margin: 0;
28
+ min-height: 100vh;
29
+ font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
30
+ color: var(--text);
31
+ background: radial-gradient(1200px 600px at 10% -10%, #e0f2f1 0%, transparent 55%),
32
+ radial-gradient(900px 500px at 100% 0%, #fef3c7 0%, transparent 50%), var(--bg);
33
+ }
34
+
35
+ .page {
36
+ max-width: 68rem;
37
+ margin: 0 auto;
38
+ padding: 2.5rem 1.25rem 4rem;
39
+ }
40
+
41
+ .hero {
42
+ text-align: center;
43
+ margin-bottom: 2.5rem;
44
+ }
45
+
46
+ .hero__eyebrow {
47
+ display: inline-block;
48
+ font-size: 0.75rem;
49
+ font-weight: 600;
50
+ letter-spacing: 0.12em;
51
+ text-transform: uppercase;
52
+ color: var(--accent);
53
+ background: var(--accent-soft);
54
+ padding: 0.35rem 0.75rem;
55
+ border-radius: 999px;
56
+ margin-bottom: 0.75rem;
57
+ }
58
+
59
+ .hero h1 {
60
+ font-size: clamp(1.5rem, 4vw, 2rem);
61
+ font-weight: 700;
62
+ letter-spacing: -0.02em;
63
+ margin: 0 0 0.5rem;
64
+ }
65
+
66
+ .hero p {
67
+ margin: 0 auto;
68
+ max-width: 36rem;
69
+ color: var(--muted);
70
+ line-height: 1.55;
71
+ font-size: 0.95rem;
72
+ }
73
+
74
+ .hero code {
75
+ font-size: 0.85em;
76
+ background: rgba(0, 0, 0, 0.06);
77
+ padding: 0.15rem 0.4rem;
78
+ border-radius: 6px;
79
+ }
80
+
81
+ .section {
82
+ margin-bottom: 2.75rem;
83
+ }
84
+
85
+ .section__head {
86
+ display: flex;
87
+ align-items: flex-end;
88
+ justify-content: space-between;
89
+ gap: 1rem;
90
+ margin-bottom: 1rem;
91
+ flex-wrap: wrap;
92
+ }
93
+
94
+ .section__title {
95
+ font-size: 1.125rem;
96
+ font-weight: 600;
97
+ margin: 0;
98
+ }
99
+
100
+ .section__hint {
101
+ font-size: 0.8rem;
102
+ color: var(--muted);
103
+ margin: 0.25rem 0 0;
104
+ }
105
+
106
+ .toolbar {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 0.5rem;
110
+ }
111
+
112
+ .toolbar button {
113
+ display: inline-flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ width: 2.5rem;
117
+ height: 2.5rem;
118
+ border: 1px solid rgba(28, 25, 23, 0.12);
119
+ border-radius: 10px;
120
+ background: var(--surface);
121
+ cursor: pointer;
122
+ color: var(--text);
123
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
124
+ transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
125
+ }
126
+
127
+ .toolbar button:hover:not(:disabled) {
128
+ background: #fafaf9;
129
+ box-shadow: var(--shadow);
130
+ }
131
+
132
+ .toolbar button:active:not(:disabled) {
133
+ transform: scale(0.96);
134
+ }
135
+
136
+ .toolbar button:disabled {
137
+ opacity: 0.35;
138
+ cursor: not-allowed;
139
+ }
140
+
141
+ /* Scroll track: flex row; library adds overflow + drag */
142
+ .scroller {
143
+ display: flex;
144
+ gap: 1rem;
145
+ padding: 0.5rem 0.25rem 1rem;
146
+ scroll-padding-inline: 0.25rem;
147
+ }
148
+
149
+ .scroller-wrap {
150
+ position: relative;
151
+ border-radius: var(--radius);
152
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.72));
153
+ border: 1px solid rgba(28, 25, 23, 0.06);
154
+ box-shadow: var(--shadow);
155
+ padding: 0.75rem 0.5rem;
156
+ }
157
+
158
+ .product-card {
159
+ flex: 0 0 15rem;
160
+ min-width: 15rem;
161
+ background: var(--surface);
162
+ border-radius: var(--radius);
163
+ overflow: hidden;
164
+ border: 1px solid rgba(28, 25, 23, 0.06);
165
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
166
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
167
+ }
168
+
169
+ .product-card:hover {
170
+ transform: translateY(-3px);
171
+ box-shadow: var(--shadow-hover);
172
+ }
173
+
174
+ .product-card__media {
175
+ position: relative;
176
+ aspect-ratio: 4 / 3;
177
+ background: linear-gradient(145deg, #e7e5e4, #d6d3d1);
178
+ }
179
+
180
+ .product-card__media img {
181
+ width: 100%;
182
+ height: 100%;
183
+ object-fit: cover;
184
+ display: block;
185
+ }
186
+
187
+ .product-card__badge {
188
+ position: absolute;
189
+ top: 0.65rem;
190
+ left: 0.65rem;
191
+ font-size: 0.65rem;
192
+ font-weight: 600;
193
+ text-transform: uppercase;
194
+ letter-spacing: 0.06em;
195
+ color: #fff;
196
+ background: rgba(28, 25, 23, 0.72);
197
+ backdrop-filter: blur(6px);
198
+ padding: 0.25rem 0.5rem;
199
+ border-radius: 6px;
200
+ max-width: calc(100% - 1.3rem);
201
+ white-space: nowrap;
202
+ overflow: hidden;
203
+ text-overflow: ellipsis;
204
+ }
205
+
206
+ .product-card__body {
207
+ padding: 0.9rem 1rem 1.1rem;
208
+ }
209
+
210
+ .product-card__title {
211
+ margin: 0 0 0.35rem;
212
+ font-size: 0.9rem;
213
+ font-weight: 600;
214
+ line-height: 1.35;
215
+ display: -webkit-box;
216
+ -webkit-line-clamp: 2;
217
+ -webkit-box-orient: vertical;
218
+ overflow: hidden;
219
+ min-height: 2.4em;
220
+ }
221
+
222
+ .product-card__desc {
223
+ margin: 0 0 0.65rem;
224
+ font-size: 0.72rem;
225
+ line-height: 1.45;
226
+ color: var(--muted);
227
+ display: -webkit-box;
228
+ -webkit-line-clamp: 2;
229
+ -webkit-box-orient: vertical;
230
+ overflow: hidden;
231
+ }
232
+
233
+ .product-card__row {
234
+ display: flex;
235
+ align-items: baseline;
236
+ justify-content: space-between;
237
+ gap: 0.5rem;
238
+ }
239
+
240
+ .product-card__price {
241
+ font-size: 1.05rem;
242
+ font-weight: 700;
243
+ color: var(--accent);
244
+ letter-spacing: -0.02em;
245
+ }
246
+
247
+ .product-card__id {
248
+ font-size: 0.65rem;
249
+ color: #a8a29e;
250
+ font-variant-numeric: tabular-nums;
251
+ }
252
+
253
+ #status {
254
+ margin-top: 1.5rem;
255
+ padding: 0.75rem 1rem;
256
+ border-radius: 10px;
257
+ font-size: 0.85rem;
258
+ background: rgba(255, 255, 255, 0.65);
259
+ border: 1px solid rgba(28, 25, 23, 0.08);
260
+ color: var(--muted);
261
+ }
262
+
263
+ #status.is-error {
264
+ color: #b91c1c;
265
+ border-color: rgba(185, 28, 28, 0.25);
266
+ background: #fef2f2;
267
+ }
268
+
269
+ #status.is-ok {
270
+ color: #0f766e;
271
+ border-color: rgba(13, 148, 136, 0.25);
272
+ background: #f0fdfa;
273
+ }
274
+ </style>
275
+ </head>
276
+ <body>
277
+ <div class="page">
278
+ <header class="hero">
279
+ <span class="hero__eyebrow">Example</span>
280
+ <h1>shopify-drag-carousel</h1>
281
+ <p>
282
+ Products load from
283
+ <code>GET https://api.escuelajs.co/api/v1/products</code>
284
+ — drag the row or use arrows. This file ships in
285
+ <code>examples/demo.html</code> for developers.
286
+ </p>
287
+ </header>
288
+
289
+ <section class="section" aria-labelledby="sec-manual">
290
+ <div class="section__head">
291
+ <div>
292
+ <h2 class="section__title" id="sec-manual">Manual init + prev / next</h2>
293
+ <p class="section__hint">
294
+ <code>new DragCarousel('#strip-main', { buttons: { … } })</code>
295
+ </p>
296
+ </div>
297
+ <div class="toolbar">
298
+ <button type="button" id="btn-prev" aria-label="Previous products">←</button>
299
+ <button type="button" id="btn-next" aria-label="Next products">→</button>
300
+ </div>
301
+ </div>
302
+ <div class="scroller-wrap">
303
+ <div id="strip-main" class="scroller"></div>
304
+ </div>
305
+ </section>
306
+
307
+ <section class="section" aria-labelledby="sec-auto">
308
+ <div class="section__head">
309
+ <div>
310
+ <h2 class="section__title" id="sec-auto">Auto-init</h2>
311
+ <p class="section__hint">
312
+ Class <code>.drag-carousel</code> — initialized on
313
+ <code>DOMContentLoaded</code>
314
+ </p>
315
+ </div>
316
+ </div>
317
+ <div class="scroller-wrap">
318
+ <div id="strip-auto" class="drag-carousel scroller"></div>
319
+ </div>
320
+ </section>
321
+
322
+ <p id="status" role="status"></p>
323
+ </div>
324
+
325
+ <script src="../index.js"></script>
326
+ <script>
327
+ const API = "https://api.escuelajs.co/api/v1/products";
328
+
329
+ function truncate(s, n) {
330
+ const t = String(s || "");
331
+ return t.length <= n ? t : t.slice(0, n - 1) + "…";
332
+ }
333
+
334
+ function productCard(p) {
335
+ const el = document.createElement("article");
336
+ el.className = "product-card";
337
+
338
+ const media = document.createElement("div");
339
+ media.className = "product-card__media";
340
+
341
+ const img = document.createElement("img");
342
+ img.loading = "lazy";
343
+ const src =
344
+ Array.isArray(p.images) && p.images.length
345
+ ? p.images[0]
346
+ : "";
347
+ img.src =
348
+ src ||
349
+ "data:image/svg+xml," +
350
+ encodeURIComponent(
351
+ '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300"><rect fill="#d6d3d1" width="100%" height="100%"/></svg>'
352
+ );
353
+ const title = String(p.title || "");
354
+ img.alt = title;
355
+
356
+ const badge = document.createElement("span");
357
+ badge.className = "product-card__badge";
358
+ badge.textContent = p.category && p.category.name ? String(p.category.name) : "Product";
359
+
360
+ media.append(img, badge);
361
+
362
+ const body = document.createElement("div");
363
+ body.className = "product-card__body";
364
+
365
+ const h = document.createElement("h3");
366
+ h.className = "product-card__title";
367
+ h.textContent = title;
368
+
369
+ const desc = document.createElement("p");
370
+ desc.className = "product-card__desc";
371
+ desc.textContent = truncate(p.description || "", 120);
372
+
373
+ const row = document.createElement("div");
374
+ row.className = "product-card__row";
375
+
376
+ const price = document.createElement("span");
377
+ price.className = "product-card__price";
378
+ const n = Number(p.price);
379
+ price.textContent = Number.isFinite(n) ? "$" + n.toFixed(2) : "";
380
+
381
+ const id = document.createElement("span");
382
+ id.className = "product-card__id";
383
+ id.textContent = "#" + String(p.id != null ? p.id : "");
384
+
385
+ row.append(price, id);
386
+ body.append(h, desc, row);
387
+ el.append(media, body);
388
+ return el;
389
+ }
390
+
391
+ function fill(el, list) {
392
+ el.replaceChildren();
393
+ list.forEach((p) => el.appendChild(productCard(p)));
394
+ }
395
+
396
+ (async () => {
397
+ const status = document.getElementById("status");
398
+
399
+ if (typeof DragCarousel === "undefined") {
400
+ status.className = "is-error";
401
+ status.textContent = "DragCarousel not found. Check script path to ../index.js";
402
+ return;
403
+ }
404
+
405
+ status.textContent = "Loading products…";
406
+
407
+ try {
408
+ const res = await fetch(API);
409
+ if (!res.ok) throw new Error("HTTP " + res.status);
410
+ const all = await res.json();
411
+ if (!Array.isArray(all)) throw new Error("Unexpected JSON");
412
+
413
+ const a = all.slice(0, 12);
414
+ const b = all.slice(12, 24);
415
+
416
+ fill(document.getElementById("strip-main"), a);
417
+ fill(document.getElementById("strip-auto"), b);
418
+
419
+ void new DragCarousel("#strip-main", {
420
+ speed: 1.5,
421
+ buttons: { prev: "#btn-prev", next: "#btn-next" },
422
+ });
423
+
424
+ status.className = "is-ok";
425
+ status.textContent =
426
+ "Loaded " +
427
+ all.length +
428
+ " products (showing " +
429
+ a.length +
430
+ " + " +
431
+ b.length +
432
+ "). Serve this folder from the repo root: npm run demo → /examples/demo.html";
433
+ } catch (e) {
434
+ status.className = "is-error";
435
+ status.textContent =
436
+ "Could not load the API. Use http:// (e.g. npm run demo), not file://. " +
437
+ (e && e.message ? "(" + e.message + ")" : "");
438
+ }
439
+ })();
440
+ </script>
441
+ </body>
442
+ </html>
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "shopify-drag-carousel",
3
- "version": "1.0.0",
3
+ "version": "1.0.4",
4
4
  "description": "Lightweight drag-scroll for horizontal containers—one class, zero dependencies. Shopify & vanilla JS friendly.",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
8
8
  "style.css",
9
- "README.md"
9
+ "README.md",
10
+ "examples/demo.html",
11
+ "examples/demo-cdn.html"
10
12
  ],
11
13
  "scripts": {
12
- "test": "echo \"Error: no test specified\" && exit 1"
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "demo": "python3 -m http.server 5173"
13
16
  },
14
17
  "repository": {
15
18
  "type": "git",