@magic-spells/cart-panel 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.
package/README.md ADDED
@@ -0,0 +1,537 @@
1
+ # Cart Panel Web Component
2
+
3
+ A professional, highly-customizable modal shopping cart dialog built with Web Components. Features accessible modal interactions, smooth slide-in animations, real-time cart synchronization, and seamless integration with Shopify and other e-commerce platforms.
4
+
5
+ [**Live Demo**](https://magic-spells.github.io/cart-panel/demo/)
6
+
7
+ ## Features
8
+
9
+ - 🛒 **Complete cart modal** - Slide-in panel with overlay and focus management
10
+ - ♿ **Accessibility-first** - ARIA attributes, focus trapping, and keyboard navigation
11
+ - 🔄 **Real-time sync** - Automatic cart updates via `/cart.json` and `/cart/change.json` APIs
12
+ - 📡 **Event-driven architecture** - Rich event system with custom event emitter
13
+ - 🎬 **Smooth animations** - CSS transitions with customizable timing and effects
14
+ - 🔒 **Body scroll locking** - Prevents background scrolling when modal is open
15
+ - 🎛️ **Highly customizable** - CSS custom properties and SCSS variables
16
+ - 📱 **Framework agnostic** - Pure Web Components work with any framework
17
+ - 🛒 **Shopify-ready** - Built specifically for Shopify cart integrations
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @magic-spells/cart-panel
23
+ ```
24
+
25
+ ```javascript
26
+ // Import the component (includes cart-item automatically)
27
+ import '@magic-spells/cart-panel';
28
+
29
+ // Import styles (includes cart-item styles automatically)
30
+ import '@magic-spells/cart-panel/css';
31
+ ```
32
+
33
+ Or include directly in your HTML:
34
+
35
+ ```html
36
+ <script src="https://unpkg.com/@magic-spells/cart-panel"></script>
37
+ <link rel="stylesheet" href="https://unpkg.com/@magic-spells/cart-panel/dist/cart-panel.css" />
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```html
43
+ <!-- Trigger button -->
44
+ <button aria-haspopup="dialog" aria-controls="my-cart" aria-expanded="false">
45
+ Open Cart (3 items)
46
+ </button>
47
+
48
+ <!-- Cart modal dialog -->
49
+ <cart-dialog id="my-cart" aria-labelledby="cart-title">
50
+ <cart-panel>
51
+ <div class="cart-header">
52
+ <h2 id="cart-title">Shopping Cart</h2>
53
+ <button data-action="hide-cart" aria-label="Close cart">&times;</button>
54
+ </div>
55
+
56
+ <div class="cart-body">
57
+ <!-- Cart items using @magic-spells/cart-item -->
58
+ <cart-item data-key="shopify-line-item-123">
59
+ <cart-item-content>
60
+ <div class="product-info">
61
+ <img src="product.jpg" alt="Product" />
62
+ <div>
63
+ <h4>Awesome T-Shirt</h4>
64
+ <div class="price">$29.99</div>
65
+ </div>
66
+ </div>
67
+ <div class="quantity-controls">
68
+ <input type="number" data-cart-quantity value="1" min="1" />
69
+ <button data-action="remove">Remove</button>
70
+ </div>
71
+ </cart-item-content>
72
+ <cart-item-processing>
73
+ <div>Processing...</div>
74
+ </cart-item-processing>
75
+ </cart-item>
76
+ </div>
77
+
78
+ <div class="cart-footer">
79
+ <div class="cart-total">Total: $29.99</div>
80
+ <button class="checkout-btn">Checkout</button>
81
+ </div>
82
+ </cart-panel>
83
+ </cart-dialog>
84
+ ```
85
+
86
+ ## How It Works
87
+
88
+ The cart panel component creates a complete modal cart experience with three main elements:
89
+
90
+ - **cart-dialog**: Main container managing modal state, focus trapping, and scroll locking
91
+ - **cart-overlay**: Clickable backdrop that closes the modal when clicked
92
+ - **cart-panel**: Sliding content area that contains the actual cart items and controls
93
+
94
+ The component automatically handles:
95
+
96
+ - Opening when trigger buttons with `aria-controls` are clicked
97
+ - Closing via close buttons, escape key, or overlay clicks
98
+ - Fetching cart data from `/cart.json` on show
99
+ - Updating cart items via `/cart/change.json` API calls
100
+ - Managing cart item states and animations through integrated `@magic-spells/cart-item`
101
+ - Emitting events for cart updates and state changes
102
+
103
+ ## Configuration
104
+
105
+ ### Cart Dialog Attributes
106
+
107
+ | Attribute | Description | Required |
108
+ | ----------------- | ----------------------------------------------- | ----------- |
109
+ | `id` | Unique identifier referenced by trigger buttons | Yes |
110
+ | `aria-labelledby` | References the cart title element | Recommended |
111
+ | `aria-modal` | Set to "true" for proper modal semantics | Recommended |
112
+
113
+ ### Required HTML Structure
114
+
115
+ | Element | Description | Required |
116
+ | ---------------- | -------------------------------------------- | -------- |
117
+ | `<cart-dialog>` | Main modal container | Yes |
118
+ | `<cart-panel>` | Sliding content area | Yes |
119
+ | `<cart-overlay>` | Background overlay (auto-created if missing) | No |
120
+
121
+ ### Interactive Elements
122
+
123
+ | Selector | Description | Event Triggered |
124
+ | --------------------------- | ----------------------------------- | --------------------------- |
125
+ | `[aria-controls="cart-id"]` | Trigger buttons to open cart | Opens modal |
126
+ | `[data-action="hide-cart"]` | Close buttons inside modal | Closes modal |
127
+ | `[data-action="remove"]` | Remove item buttons (via cart-item) | `cart-item:remove` |
128
+ | `[data-cart-quantity]` | Quantity inputs (via cart-item) | `cart-item:quantity-change` |
129
+
130
+ Example:
131
+
132
+ ```html
133
+ <!-- Minimal cart modal -->
134
+ <cart-dialog id="simple-cart">
135
+ <cart-panel>
136
+ <h2>Cart</h2>
137
+ <button data-action="hide-cart">Close</button>
138
+ <!-- Cart content here -->
139
+ </cart-panel>
140
+ </cart-dialog>
141
+
142
+ <!-- Complete cart with all features -->
143
+ <cart-dialog id="full-cart" aria-modal="true" aria-labelledby="cart-heading">
144
+ <cart-overlay></cart-overlay>
145
+ <cart-panel>
146
+ <header class="cart-header">
147
+ <h2 id="cart-heading">Shopping Cart</h2>
148
+ <button data-action="hide-cart" aria-label="Close cart">×</button>
149
+ </header>
150
+ <div class="cart-content">
151
+ <!-- Cart items will be rendered here -->
152
+ </div>
153
+ <footer class="cart-footer">
154
+ <button class="checkout-btn">Checkout</button>
155
+ </footer>
156
+ </cart-panel>
157
+ </cart-dialog>
158
+ ```
159
+
160
+ ## Customization
161
+
162
+ ### Styling
163
+
164
+ The component provides complete styling control through CSS custom properties and SCSS variables. Customize the modal appearance to match your design:
165
+
166
+ ```css
167
+ /* Customize modal positioning and sizing */
168
+ cart-dialog {
169
+ --cart-panel-width: min(500px, 95vw);
170
+ --cart-panel-z-index: 9999;
171
+ --cart-overlay-z-index: 9998;
172
+ }
173
+
174
+ /* Customize overlay appearance */
175
+ cart-overlay {
176
+ --cart-overlay-background: rgba(0, 0, 0, 0.3);
177
+ --cart-overlay-backdrop-filter: blur(8px);
178
+ }
179
+
180
+ /* Customize panel styling */
181
+ cart-panel {
182
+ --cart-panel-background: #ffffff;
183
+ --cart-panel-shadow: -10px 0 30px rgba(0, 0, 0, 0.2);
184
+ --cart-panel-border-radius: 12px 0 0 12px;
185
+ }
186
+
187
+ /* Customize animations */
188
+ cart-dialog {
189
+ --cart-transition-duration: 400ms;
190
+ --cart-transition-timing: cubic-bezier(0.25, 0.8, 0.25, 1);
191
+ }
192
+
193
+ /* Style your cart content layout */
194
+ cart-panel {
195
+ display: flex;
196
+ flex-direction: column;
197
+ }
198
+
199
+ .cart-header {
200
+ padding: 1.5rem;
201
+ border-bottom: 1px solid #eee;
202
+ background: #f8f9fa;
203
+ }
204
+
205
+ .cart-content {
206
+ flex: 1;
207
+ overflow-y: auto;
208
+ padding: 1rem;
209
+ }
210
+
211
+ .cart-footer {
212
+ padding: 1.5rem;
213
+ border-top: 1px solid #eee;
214
+ background: #f8f9fa;
215
+ }
216
+ ```
217
+
218
+ ### CSS Variables & SCSS Variables
219
+
220
+ The component supports both CSS custom properties and SCSS variables for maximum flexibility:
221
+
222
+ | CSS Variable | SCSS Variable | Description | Default |
223
+ | -------------------------------- | ------------------------------- | ---------------------------- | ---------------------------- |
224
+ | `--cart-dialog-z-index` | `$cart-dialog-z-index` | Base z-index for modal | 1000 |
225
+ | `--cart-overlay-z-index` | `$cart-overlay-z-index` | Overlay layer z-index | 1000 |
226
+ | `--cart-panel-z-index` | `$cart-panel-z-index` | Panel layer z-index | 1001 |
227
+ | `--cart-panel-width` | `$cart-panel-width` | Width of the sliding panel | min(400px, 90vw) |
228
+ | `--cart-overlay-background` | `$cart-overlay-background` | Overlay background color | rgba(0, 0, 0, 0.15) |
229
+ | `--cart-overlay-backdrop-filter` | `$cart-overlay-backdrop-filter` | Overlay backdrop blur effect | blur(4px) |
230
+ | `--cart-panel-background` | `$cart-panel-background` | Panel background color | #ffffff |
231
+ | `--cart-panel-shadow` | `$cart-panel-shadow` | Panel box shadow | -5px 0 25px rgba(0,0,0,0.15) |
232
+ | `--cart-panel-border-radius` | `$cart-panel-border-radius` | Panel border radius | 0 |
233
+ | `--cart-transition-duration` | `$cart-transition-duration` | Animation duration | 350ms |
234
+ | `--cart-transition-timing` | `$cart-transition-timing` | Animation timing function | cubic-bezier(0.4, 0, 0.2, 1) |
235
+
236
+ #### CSS Override Examples:
237
+
238
+ ```css
239
+ /* Dramatic slide-in effect */
240
+ .dramatic-cart {
241
+ --cart-transition-duration: 600ms;
242
+ --cart-transition-timing: cubic-bezier(0.68, -0.55, 0.265, 1.55);
243
+ --cart-overlay-background: rgba(0, 0, 0, 0.4);
244
+ --cart-overlay-backdrop-filter: blur(10px);
245
+ }
246
+
247
+ /* Subtle minimal styling */
248
+ .minimal-cart {
249
+ --cart-panel-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
250
+ --cart-panel-border-radius: 8px;
251
+ --cart-transition-duration: 200ms;
252
+ --cart-overlay-background: rgba(0, 0, 0, 0.05);
253
+ }
254
+
255
+ /* Mobile-optimized full-width */
256
+ @media (max-width: 768px) {
257
+ .mobile-cart {
258
+ --cart-panel-width: 100vw;
259
+ --cart-panel-border-radius: 0;
260
+ }
261
+ }
262
+ ```
263
+
264
+ #### SCSS Override Examples:
265
+
266
+ ```scss
267
+ // Override SCSS variables before importing
268
+ $cart-panel-width: min(500px, 95vw);
269
+ $cart-transition-duration: 400ms;
270
+ $cart-overlay-background: rgba(0, 0, 0, 0.25);
271
+
272
+ // Import the component styles
273
+ @import '@magic-spells/cart-panel/scss';
274
+
275
+ // Or import the CSS and override with CSS custom properties
276
+ @import '@magic-spells/cart-panel/css';
277
+
278
+ .my-store cart-dialog {
279
+ --cart-transition-duration: 400ms;
280
+ --cart-panel-background: #f8f9fa;
281
+ }
282
+ ```
283
+
284
+ ### JavaScript API
285
+
286
+ #### Methods
287
+
288
+ - `show(triggerElement)`: Open the cart modal and focus the first interactive element
289
+ - `hide()`: Close the cart modal and restore focus to trigger element
290
+ - `getCart()`: Fetch current cart data from `/cart.json`
291
+ - `updateCartItem(key, quantity)`: Update cart item quantity via `/cart/change.json`
292
+ - `refreshCart()`: Refresh cart data and update UI components
293
+ - `on(eventName, callback)`: Add event listener using the event emitter
294
+ - `off(eventName, callback)`: Remove event listener
295
+
296
+ #### Events
297
+
298
+ The component emits custom events for cart state changes and data updates:
299
+
300
+ **Modal Events:**
301
+
302
+ - `cart-dialog:show` - Modal has opened
303
+ - `cart-dialog:hide` - Modal has started closing
304
+ - `cart-dialog:afterHide` - Modal has finished closing animation
305
+
306
+ **Cart Data Events:**
307
+
308
+ - `cart-dialog:updated` - Cart data updated after item change
309
+ - `cart-dialog:refreshed` - Cart data refreshed from server
310
+ - `cart-dialog:data-changed` - Any cart data change (unified event)
311
+
312
+ **Cart Item Events (bubbled from cart-item components):**
313
+
314
+ - `cart-item:remove` - Remove button clicked: `{ cartKey, element }`
315
+ - `cart-item:quantity-change` - Quantity changed: `{ cartKey, quantity, element }`
316
+
317
+ #### Programmatic Control
318
+
319
+ ```javascript
320
+ const cartDialog = document.querySelector('cart-dialog');
321
+
322
+ // Open/close cart
323
+ cartDialog.show(); // Open modal
324
+ cartDialog.hide(); // Close modal
325
+
326
+ // Cart data operations
327
+ const cartData = await cartDialog.getCart();
328
+ const updatedCart = await cartDialog.updateCartItem('item-key', 2);
329
+ await cartDialog.refreshCart();
330
+
331
+ // Event emitter pattern (recommended)
332
+ cartDialog
333
+ .on('cart-dialog:show', (e) => {
334
+ console.log('Cart opened by:', e.detail.triggerElement);
335
+ })
336
+ .on('cart-dialog:data-changed', (cartData) => {
337
+ console.log('Cart updated:', cartData);
338
+ // Update header cart count, etc.
339
+ });
340
+
341
+ // Traditional event listeners (also supported)
342
+ cartDialog.addEventListener('cart-item:remove', (e) => {
343
+ console.log('Remove requested:', e.detail.cartKey);
344
+
345
+ // The component handles the API calls automatically
346
+ // Just listen for the data changes
347
+ });
348
+
349
+ cartDialog.addEventListener('cart-item:quantity-change', (e) => {
350
+ console.log('Quantity changed:', e.detail.quantity);
351
+ // Component automatically syncs with Shopify
352
+ });
353
+
354
+ // Listen for all cart changes
355
+ cartDialog.on('cart-dialog:data-changed', (cartData) => {
356
+ // Update your UI when cart changes
357
+ updateCartBadge(cartData.item_count);
358
+ updateCartTotal(cartData.total_price);
359
+ });
360
+ ```
361
+
362
+ #### Performance & Architecture
363
+
364
+ The component is optimized for:
365
+
366
+ - **Smooth animations**: CSS transforms and transitions for slide-in effects
367
+ - **Focus management**: Automatic focus trapping with `@magic-spells/focus-trap`
368
+ - **Memory management**: Proper event listener cleanup on disconnect
369
+ - **Scroll lock**: Body scroll prevention with position restoration
370
+ - **API efficiency**: Smart cart data fetching and caching
371
+ - **Event system**: Centralized event handling with custom event emitter
372
+ - **Accessibility**: Full ARIA support and keyboard navigation
373
+
374
+ ## Integration Examples
375
+
376
+ ### Shopify Integration
377
+
378
+ The cart panel automatically integrates with Shopify's AJAX Cart API. Simply add the component to your theme and it handles all cart operations:
379
+
380
+ ```liquid
381
+ <!-- In your Shopify theme layout -->
382
+ <button
383
+ aria-haspopup="dialog"
384
+ aria-controls="shopify-cart"
385
+ aria-expanded="false"
386
+ class="cart-trigger">
387
+ Cart ({{ cart.item_count }})
388
+ </button>
389
+
390
+ <cart-dialog id="shopify-cart" aria-labelledby="cart-heading">
391
+ <cart-panel>
392
+ <header class="cart-header">
393
+ <h2 id="cart-heading">{{ 'cart.general.title' | t }}</h2>
394
+ <button data-action="hide-cart" aria-label="{{ 'cart.general.close' | t }}">
395
+ {% render 'icon-close' %}
396
+ </button>
397
+ </header>
398
+
399
+ <div class="cart-content">
400
+ <!-- Cart items will be populated automatically -->
401
+ {% for item in cart.items %}
402
+ <cart-item data-key="{{ item.key }}">
403
+ <cart-item-content>
404
+ <div class="cart-item-layout">
405
+ <img src="{{ item.image | img_url: '100x100' }}" alt="{{ item.title | escape }}">
406
+ <div class="item-details">
407
+ <h4>{{ item.product.title }}</h4>
408
+ {% unless item.variant.title == 'Default Title' %}
409
+ <p class="variant">{{ item.variant.title }}</p>
410
+ {% endunless %}
411
+ <p class="price">{{ item.final_price | money }}</p>
412
+ </div>
413
+ <div class="item-controls">
414
+ <input
415
+ type="number"
416
+ data-cart-quantity
417
+ value="{{ item.quantity }}"
418
+ min="0">
419
+ <button data-action="remove">{{ 'cart.general.remove' | t }}</button>
420
+ </div>
421
+ </div>
422
+ </cart-item-content>
423
+ <cart-item-processing>
424
+ <div class="spinner"></div>
425
+ <span>{{ 'cart.general.updating' | t }}</span>
426
+ </cart-item-processing>
427
+ </cart-item>
428
+ {% endfor %}
429
+ </div>
430
+
431
+ <footer class="cart-footer">
432
+ <div class="cart-total">
433
+ {{ 'cart.general.total' | t }}: <span data-cart-total>{{ cart.total_price | money }}</span>
434
+ </div>
435
+ <a href="/checkout" class="checkout-btn">
436
+ {{ 'cart.general.checkout' | t }}
437
+ </a>
438
+ </footer>
439
+ </cart-panel>
440
+ </cart-dialog>
441
+
442
+ <script>
443
+ // Optional: Listen for cart updates to sync with other UI elements
444
+ document.querySelector('cart-dialog').on('cart-dialog:data-changed', (cartData) => {
445
+ // Update cart count in header
446
+ document.querySelector('.cart-trigger').textContent = `Cart (${cartData.item_count})`;
447
+
448
+ // Update cart total
449
+ document.querySelector('[data-cart-total]').textContent =
450
+ new Intl.NumberFormat('en-US', {
451
+ style: 'currency',
452
+ currency: 'USD'
453
+ }).format(cartData.total_price / 100);
454
+ });
455
+ </script>
456
+ ```
457
+
458
+ ### Vanilla JavaScript Integration
459
+
460
+ ```javascript
461
+ // Example for non-Shopify platforms
462
+ class CustomCartManager {
463
+ constructor() {
464
+ this.cartDialog = document.querySelector('cart-dialog');
465
+ this.setupEventListeners();
466
+ }
467
+
468
+ setupEventListeners() {
469
+ // Listen for cart data changes
470
+ this.cartDialog.on('cart-dialog:data-changed', (cartData) => {
471
+ this.updateCartUI(cartData);
472
+ });
473
+
474
+ // Override default cart operations for custom API
475
+ this.cartDialog.getCart = this.customGetCart.bind(this);
476
+ this.cartDialog.updateCartItem = this.customUpdateCartItem.bind(this);
477
+ }
478
+
479
+ async customGetCart() {
480
+ try {
481
+ const response = await fetch('/api/cart');
482
+ return await response.json();
483
+ } catch (error) {
484
+ console.error('Failed to fetch cart:', error);
485
+ return { error: true, message: error.message };
486
+ }
487
+ }
488
+
489
+ async customUpdateCartItem(itemId, quantity) {
490
+ try {
491
+ const response = await fetch('/api/cart/update', {
492
+ method: 'POST',
493
+ headers: { 'Content-Type': 'application/json' },
494
+ body: JSON.stringify({ itemId, quantity }),
495
+ });
496
+
497
+ if (!response.ok) throw new Error(response.statusText);
498
+
499
+ // Return updated cart data
500
+ return this.customGetCart();
501
+ } catch (error) {
502
+ console.error('Failed to update cart:', error);
503
+ return { error: true, message: error.message };
504
+ }
505
+ }
506
+
507
+ updateCartUI(cartData) {
508
+ // Update cart count in navigation
509
+ const cartCount = document.querySelector('.cart-count');
510
+ if (cartCount) {
511
+ cartCount.textContent = cartData.items?.length || 0;
512
+ }
513
+
514
+ // Update cart total display
515
+ const cartTotal = document.querySelector('.cart-total-display');
516
+ if (cartTotal && cartData.total) {
517
+ cartTotal.textContent = cartData.total;
518
+ }
519
+ }
520
+ }
521
+
522
+ // Initialize
523
+ new CustomCartManager();
524
+ ```
525
+
526
+ ## Browser Support
527
+
528
+ - Chrome 54+
529
+ - Firefox 63+
530
+ - Safari 10.1+
531
+ - Edge 79+
532
+
533
+ All modern browsers with Web Components support.
534
+
535
+ ## License
536
+
537
+ MIT
@@ -0,0 +1,171 @@
1
+ cart-item {
2
+ --cart-item-processing-duration: 250ms;
3
+ --cart-item-destroying-duration: 600ms;
4
+ --cart-item-shadow-color: rgba(0, 0, 0, 0.15);
5
+ --cart-item-shadow-color-strong: rgba(0, 0, 0, 0.5);
6
+ --cart-item-processing-bg: rgba(100, 100, 100, 0.2);
7
+ --cart-item-destroying-bg: rgba(0, 0, 0, 0.1);
8
+ --cart-item-processing-scale: 0.98;
9
+ --cart-item-destroying-scale: 0.85;
10
+ --cart-item-processing-blur: 1px;
11
+ --cart-item-destroying-blur: 10px;
12
+ --cart-item-destroying-opacity: 0.2;
13
+ --cart-item-destroying-brightness: 0.6;
14
+ --cart-item-destroying-saturate: 0.3;
15
+ display: block;
16
+ position: relative;
17
+ overflow: hidden;
18
+ padding: 0px;
19
+ box-shadow: inset 0px 0px 0px rgba(0, 0, 0, 0);
20
+ transition: filter var(--cart-item-processing-duration) ease-out, background-color var(--cart-item-processing-duration) ease-out, box-shadow var(--cart-item-processing-duration) ease-out;
21
+ }
22
+ cart-item::after {
23
+ content: "";
24
+ display: block;
25
+ position: absolute;
26
+ background: rgba(0, 0, 0, 0);
27
+ width: 100%;
28
+ pointer-events: none;
29
+ height: 100%;
30
+ top: 0px;
31
+ left: 0px;
32
+ transition: background-color var(--cart-item-processing-duration) ease;
33
+ }
34
+ cart-item[data-state=ready] cart-item-content {
35
+ transform: scale(1);
36
+ filter: blur(0px);
37
+ opacity: 1;
38
+ }
39
+ cart-item[data-state=ready] cart-item-processing {
40
+ opacity: 0;
41
+ visibility: hidden;
42
+ }
43
+ cart-item[data-state=processing] {
44
+ box-shadow: inset 0px 2px 10px var(--cart-item-shadow-color);
45
+ }
46
+ cart-item[data-state=processing]::after {
47
+ background: rgba(0, 0, 0, 0.15);
48
+ }
49
+ cart-item[data-state=processing] cart-item-content {
50
+ transform: scale(var(--cart-item-processing-scale));
51
+ filter: blur(var(--cart-item-processing-blur));
52
+ opacity: 0.9;
53
+ pointer-events: none;
54
+ }
55
+ cart-item[data-state=processing] cart-item-processing {
56
+ opacity: 1;
57
+ visibility: visible;
58
+ }
59
+ cart-item[data-state=destroying] {
60
+ background-color: var(--cart-item-destroying-bg);
61
+ box-shadow: inset 0px 4px 20px var(--cart-item-shadow-color-strong);
62
+ margin-top: 0px;
63
+ margin-bottom: 0px;
64
+ transition: filter var(--cart-item-destroying-duration) ease, background-color var(--cart-item-destroying-duration) ease, box-shadow var(--cart-item-destroying-duration) ease, margin var(--cart-item-destroying-duration) ease;
65
+ }
66
+ cart-item[data-state=destroying]::after {
67
+ background: rgba(0, 0, 0, 0.9);
68
+ transition: background-color var(--cart-item-destroying-duration) ease;
69
+ }
70
+ cart-item[data-state=destroying] cart-item-content {
71
+ transition: transform var(--cart-item-destroying-duration) ease, filter var(--cart-item-destroying-duration) ease, opacity var(--cart-item-destroying-duration) ease;
72
+ transform: scale(var(--cart-item-destroying-scale));
73
+ filter: blur(var(--cart-item-destroying-blur)) saturate(var(--cart-item-destroying-saturate));
74
+ opacity: var(--cart-item-destroying-opacity);
75
+ pointer-events: none;
76
+ }
77
+ cart-item[data-state=destroying] cart-item-processing {
78
+ opacity: 0;
79
+ transition: opacity var(--cart-item-processing-duration) ease;
80
+ }
81
+
82
+ cart-item-content {
83
+ display: block;
84
+ transition: transform var(--cart-item-processing-duration) ease-out, filter var(--cart-item-processing-duration) ease-out, opacity var(--cart-item-processing-duration) ease-out;
85
+ }
86
+
87
+ cart-item-processing {
88
+ position: absolute;
89
+ top: 0;
90
+ left: 0;
91
+ right: 0;
92
+ bottom: 0;
93
+ display: flex;
94
+ align-items: center;
95
+ justify-content: center;
96
+ background: transparent;
97
+ opacity: 0;
98
+ visibility: hidden;
99
+ transition: opacity var(--cart-item-processing-duration) ease-out, visibility var(--cart-item-processing-duration) ease-out;
100
+ z-index: 10;
101
+ }
102
+
103
+ :root {
104
+ --cart-dialog-z-index: 1000;
105
+ --cart-overlay-z-index: 1000;
106
+ --cart-panel-z-index: 1001;
107
+ --cart-panel-width: min(400px, 90vw);
108
+ --cart-overlay-background: rgba(0, 0, 0, 0.15);
109
+ --cart-overlay-backdrop-filter: blur(4px);
110
+ --cart-panel-background: #ffffff;
111
+ --cart-panel-shadow: -5px 0 25px rgba(0, 0, 0, 0.15);
112
+ --cart-panel-border-radius: 0;
113
+ --cart-transition-duration: 350ms;
114
+ --cart-transition-timing: cubic-bezier(0.4, 0, 0.2, 1);
115
+ }
116
+
117
+ cart-dialog {
118
+ display: contents;
119
+ }
120
+ cart-dialog[aria-hidden=false] cart-overlay,
121
+ cart-dialog[aria-hidden=false] cart-panel {
122
+ pointer-events: auto;
123
+ opacity: 1;
124
+ }
125
+ cart-dialog[aria-hidden=false] cart-panel {
126
+ transform: translateX(0);
127
+ }
128
+
129
+ cart-overlay {
130
+ position: fixed;
131
+ top: 0;
132
+ left: 0;
133
+ width: 100vw;
134
+ height: 100vh;
135
+ opacity: 0;
136
+ pointer-events: none;
137
+ z-index: var(--cart-overlay-z-index);
138
+ background-color: var(--cart-overlay-background);
139
+ backdrop-filter: var(--cart-overlay-backdrop-filter);
140
+ transition: opacity var(--cart-transition-duration) var(--cart-transition-timing), backdrop-filter var(--cart-transition-duration) var(--cart-transition-timing);
141
+ }
142
+
143
+ cart-panel {
144
+ position: fixed;
145
+ top: 0;
146
+ right: 0;
147
+ width: var(--cart-panel-width);
148
+ height: 100vh;
149
+ opacity: 0;
150
+ transform: translateX(100%);
151
+ pointer-events: none;
152
+ z-index: var(--cart-panel-z-index);
153
+ background: var(--cart-panel-background);
154
+ box-shadow: var(--cart-panel-shadow);
155
+ border-radius: var(--cart-panel-border-radius);
156
+ overflow: hidden;
157
+ transition: opacity var(--cart-transition-duration) var(--cart-transition-timing), transform var(--cart-transition-duration) var(--cart-transition-timing);
158
+ }
159
+ cart-panel.hidden {
160
+ display: none;
161
+ }
162
+
163
+ body.overflow-hidden {
164
+ overflow: hidden;
165
+ position: fixed;
166
+ width: 100%;
167
+ height: 100%;
168
+ left: 0;
169
+ right: 0;
170
+ margin: 0;
171
+ }