astro-component-kit 0.4.0 → 0.4.1
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 +105 -27
- package/package.json +16 -3
- package/src/components/index.ts +2 -0
- package/src/components/lib/Galaxy3D.astro +249 -137
- package/src/components/lib/app/AppSidebar.astro +249 -0
- package/src/components/lib/app/AppTopbar.astro +284 -0
- package/src/components/lib/app/ArrivalsBoard.astro +176 -0
- package/src/components/lib/app/AuthCard.astro +213 -0
- package/src/components/lib/app/DataTable.astro +346 -0
- package/src/components/lib/app/GuestProfileCard.astro +133 -0
- package/src/components/lib/app/HousekeepingBoard.astro +189 -0
- package/src/components/lib/app/PageHeader.astro +157 -0
- package/src/components/lib/app/PlanUsageCard.astro +112 -0
- package/src/components/lib/app/RoomRack.astro +207 -0
- package/src/components/lib/app/SettingsSection.astro +96 -0
- package/src/components/lib/app/SwitchRow.astro +71 -0
- package/src/components/lib/app/TeamMembers.astro +190 -0
- package/src/components/lib/app/app-icons.ts +52 -0
- package/src/components/lib/app/index.ts +19 -0
- package/src/components/lib/charts/AreaChart.astro +4 -4
- package/src/components/lib/charts/BarChart.astro +3 -3
- package/src/components/lib/charts/ChartFrame.astro +10 -10
- package/src/components/lib/charts/DonutChart.astro +2 -2
- package/src/components/lib/charts/DumbbellChart.astro +1 -1
- package/src/components/lib/charts/ForecastLineChart.astro +1 -1
- package/src/components/lib/charts/GaugeChart.astro +1 -1
- package/src/components/lib/charts/HeatmapChart.astro +1 -1
- package/src/components/lib/charts/HorizontalBarChart.astro +2 -2
- package/src/components/lib/charts/KpiStatTile.astro +4 -4
- package/src/components/lib/charts/LinearProgressGauge.astro +4 -4
- package/src/components/lib/charts/MetricDeltaTable.astro +3 -3
- package/src/components/lib/charts/RadarMetricChart.astro +4 -4
- package/src/components/lib/charts/RadialBarChart.astro +1 -1
- package/src/components/lib/charts/RetentionCohortTable.astro +2 -2
- package/src/components/lib/charts/TreemapChart.astro +1 -1
- package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
- package/src/components/lib/ecommerce/CartPageList.astro +290 -0
- package/src/components/lib/ecommerce/CartToast.astro +276 -0
- package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
- package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
- package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
- package/src/components/lib/ecommerce/FilterBar.astro +278 -0
- package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
- package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
- package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
- package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
- package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
- package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
- package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
- package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
- package/src/components/lib/ecommerce/ProductRating.astro +78 -0
- package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
- package/src/components/lib/ecommerce/PromoBar.astro +145 -0
- package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
- package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
- package/src/components/lib/ecommerce/StoreHero.astro +260 -0
- package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
- package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
- package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
- package/src/components/lib/ecommerce/cart-store.ts +151 -0
- package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
- package/src/components/lib/ecommerce/index.ts +34 -0
- package/src/components/lib/ecommerce/product-icons.ts +28 -0
- package/src/components/lib/ecommerce/types.ts +70 -0
- package/src/components/lib/forms/AnimatedInput.astro +4 -2
- package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
- package/src/components/lib/index.ts +2 -0
- package/src/components/lib/layout/BentoGrid3.astro +6 -4
- package/src/components/lib/layout/CenterFixedBox.astro +2 -2
- package/src/components/lib/layout/CenteredContainer.astro +4 -2
- package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
- package/src/components/lib/layout/FeatureHero.astro +4 -2
- package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
- package/src/components/lib/layout/FullBleedSection.astro +2 -2
- package/src/components/lib/layout/GalleryGrid4.astro +2 -2
- package/src/components/lib/layout/HolyGrailGrid.astro +11 -9
- package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
- package/src/components/lib/layout/MasonryGrid.astro +2 -2
- package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
- package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
- package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
- package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
- package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
- package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
- package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
- package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
- package/src/components/lib/sections/AgencyHeader.astro +161 -0
- package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
- package/src/components/lib/sections/AgencyServicesList.astro +113 -0
- package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
- package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
- package/src/components/lib/sections/BlogGridSection.astro +9 -7
- package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
- package/src/components/lib/sections/CardsShowcase.astro +28 -26
- package/src/components/lib/sections/CareersOpenings.astro +10 -8
- package/src/components/lib/sections/ChangelogSection.astro +14 -7
- package/src/components/lib/sections/ContactGlassForm.astro +10 -8
- package/src/components/lib/sections/ContactSplitMap.astro +11 -9
- package/src/components/lib/sections/CtaAppDownload.astro +13 -11
- package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
- package/src/components/lib/sections/CtaNewsletterSection.astro +13 -11
- package/src/components/lib/sections/CtaSplitImage.astro +13 -11
- package/src/components/lib/sections/DataShowcase.astro +23 -21
- package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
- package/src/components/lib/sections/FaqTwoColumn.astro +9 -7
- package/src/components/lib/sections/FeatureAlternatingRows.astro +10 -8
- package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
- package/src/components/lib/sections/FeatureIconGrid.astro +6 -6
- package/src/components/lib/sections/FeatureTabsShowcase.astro +13 -11
- package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
- package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
- package/src/components/lib/sections/FooterMegaSection.astro +13 -11
- package/src/components/lib/sections/FormsShowcase.astro +22 -20
- package/src/components/lib/sections/HeroDashboardPreview.astro +11 -9
- package/src/components/lib/sections/HeroGlass.astro +22 -20
- package/src/components/lib/sections/HeroGradient.astro +15 -13
- package/src/components/lib/sections/HeroMinimalCentered.astro +10 -10
- package/src/components/lib/sections/HeroShowcaseSplit.astro +18 -16
- package/src/components/lib/sections/HeroSplit.astro +13 -11
- package/src/components/lib/sections/HeroTerminal.astro +14 -12
- package/src/components/lib/sections/HeroWaitlist.astro +10 -8
- package/src/components/lib/sections/HowItWorksSteps.astro +12 -10
- package/src/components/lib/sections/IntegrationsGrid.astro +7 -7
- package/src/components/lib/sections/LayoutShowcase.astro +21 -19
- package/src/components/lib/sections/LogoCloudSection.astro +4 -4
- package/src/components/lib/sections/LogoWallGrid.astro +6 -4
- package/src/components/lib/sections/NavigationShowcase.astro +15 -13
- package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
- package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
- package/src/components/lib/sections/PricingSinglePlan.astro +13 -11
- package/src/components/lib/sections/PricingTierCards.astro +11 -9
- package/src/components/lib/sections/PricingToggleCards.astro +13 -13
- package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
- package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
- package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
- package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
- package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
- package/src/components/lib/sections/ShopLookbook.astro +250 -0
- package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
- package/src/components/lib/sections/ShopProductRail.astro +150 -0
- package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
- package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
- package/src/components/lib/sections/StatsCounterBand.astro +9 -7
- package/src/components/lib/sections/StatsDashboardSection.astro +4 -2
- package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
- package/src/components/lib/sections/TeamGridSection.astro +12 -10
- package/src/components/lib/sections/TestimonialCarousel.astro +10 -8
- package/src/components/lib/sections/TestimonialFeatured.astro +7 -5
- package/src/components/lib/sections/TestimonialGridSection.astro +7 -7
- package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
- package/src/components/lib/sections/TypographyShowcase.astro +10 -8
- package/src/components/lib/sections/index.ts +10 -0
- package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
- package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
- package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
- package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
- package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
- package/src/components/lib/templates/ecommerce/data.ts +187 -0
- package/src/components/lib/templates/ecommerce/index.ts +14 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
- package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
- package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
- package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
- package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
- package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
- package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
- package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
- package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
- package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
- package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
- package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
- package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
- package/src/components/lib/templates/saas-hotel/data.ts +229 -0
- package/src/components/lib/templates/saas-hotel/index.ts +19 -0
- package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
- package/src/styles/global.css +95 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* FilterSidebar — Catalog filters (category, max price, colour, rating, stock) that filter
|
|
4
|
+
* and sort a ProductGrid instantly in the browser. Collapses behind a "Filters" button on mobile.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} target - id of the ProductGrid to filter. Default 'product-grid'.
|
|
7
|
+
* @param {string} id - Form id; ProductToolbar's sort select uses `${id}-sort`. Default 'catalog-filters'.
|
|
8
|
+
* @param {Array<{id: string, name: string, count?: number}>} categories - Category checkboxes.
|
|
9
|
+
* @param {string[]} selected - Pre-checked category ids (also read from `?category=` in the URL).
|
|
10
|
+
* @param {number} maxPrice - Upper bound of the price slider. Default 300.
|
|
11
|
+
* @param {Array<{name: string, hex: string}>} colors - Colour swatches.
|
|
12
|
+
* @param {string} currency - ISO currency. Default 'USD'.
|
|
13
|
+
* @param {string} locale - Number format locale. Default 'en-US'.
|
|
14
|
+
* @param {string} variant - Style (glass|plain). Default 'glass'.
|
|
15
|
+
* @param {string} size - Density (sm|md). Default 'md'.
|
|
16
|
+
* @param {string} className - Extra class.
|
|
17
|
+
*
|
|
18
|
+
* Accessibility: native form controls in fieldsets with legends; result count is announced
|
|
19
|
+
* by ProductToolbar's live region; the mobile toggle is a disclosure button.
|
|
20
|
+
*/
|
|
21
|
+
import { formatMoney } from './cart-store';
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
target?: string;
|
|
25
|
+
id?: string;
|
|
26
|
+
categories?: Array<{ id: string; name: string; count?: number }>;
|
|
27
|
+
selected?: string[];
|
|
28
|
+
maxPrice?: number;
|
|
29
|
+
colors?: Array<{ name: string; hex: string }>;
|
|
30
|
+
currency?: string;
|
|
31
|
+
locale?: string;
|
|
32
|
+
variant?: 'glass' | 'plain';
|
|
33
|
+
size?: 'sm' | 'md';
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
target = 'product-grid',
|
|
39
|
+
id = 'catalog-filters',
|
|
40
|
+
categories = [
|
|
41
|
+
{ id: 'audio', name: 'Audio', count: 3 },
|
|
42
|
+
{ id: 'wearables', name: 'Wearables', count: 2 },
|
|
43
|
+
{ id: 'bags', name: 'Bags', count: 2 },
|
|
44
|
+
{ id: 'home', name: 'Home', count: 3 },
|
|
45
|
+
],
|
|
46
|
+
selected = [],
|
|
47
|
+
maxPrice = 300,
|
|
48
|
+
colors = [
|
|
49
|
+
{ name: 'Graphite', hex: '#2b2b2f' },
|
|
50
|
+
{ name: 'Sand', hex: '#d8c3a5' },
|
|
51
|
+
{ name: 'Ocean', hex: '#3d6f9e' },
|
|
52
|
+
{ name: 'Moss', hex: '#5f7a4e' },
|
|
53
|
+
{ name: 'Clay', hex: '#b5654a' },
|
|
54
|
+
],
|
|
55
|
+
currency = 'USD',
|
|
56
|
+
locale = 'en-US',
|
|
57
|
+
variant = 'glass',
|
|
58
|
+
size = 'md',
|
|
59
|
+
className = '',
|
|
60
|
+
} = Astro.props;
|
|
61
|
+
|
|
62
|
+
const money = { currency, locale };
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
<div class:list={['filters', `filters--${variant}`, `filters--${size}`, className]} data-filters-root>
|
|
66
|
+
<button class="filters__toggle" type="button" aria-expanded="false" aria-controls={id} data-filters-toggle>
|
|
67
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M7 12h10M10 18h4"/></svg>
|
|
68
|
+
Filters
|
|
69
|
+
<span class="filters__active" data-active-count hidden></span>
|
|
70
|
+
</button>
|
|
71
|
+
|
|
72
|
+
<form class="filters__form" id={id} data-filters={target} data-currency={currency} data-locale={locale} aria-label="Product filters">
|
|
73
|
+
<fieldset class="filters__group">
|
|
74
|
+
<legend>Category</legend>
|
|
75
|
+
{categories.map((c) => (
|
|
76
|
+
<label class="filters__check">
|
|
77
|
+
<input type="checkbox" name="category" value={c.id} checked={selected.includes(c.id)} />
|
|
78
|
+
<span class="filters__box" aria-hidden="true"></span>
|
|
79
|
+
<span class="filters__label">{c.name}</span>
|
|
80
|
+
{c.count !== undefined && <span class="filters__count">{c.count}</span>}
|
|
81
|
+
</label>
|
|
82
|
+
))}
|
|
83
|
+
</fieldset>
|
|
84
|
+
|
|
85
|
+
<fieldset class="filters__group">
|
|
86
|
+
<legend>Price</legend>
|
|
87
|
+
<label class="filters__range">
|
|
88
|
+
<span class="filters__range-head">
|
|
89
|
+
<span>Up to</span>
|
|
90
|
+
<output data-price-out>{formatMoney(maxPrice, money)}</output>
|
|
91
|
+
</span>
|
|
92
|
+
<input type="range" name="max" min="0" max={maxPrice} step="5" value={maxPrice} aria-label="Maximum price" />
|
|
93
|
+
<span class="filters__range-scale" aria-hidden="true"><span>{formatMoney(0, money)}</span><span>{formatMoney(maxPrice, money)}</span></span>
|
|
94
|
+
</label>
|
|
95
|
+
</fieldset>
|
|
96
|
+
|
|
97
|
+
{colors.length > 0 && (
|
|
98
|
+
<fieldset class="filters__group">
|
|
99
|
+
<legend>Colour</legend>
|
|
100
|
+
<div class="filters__swatches">
|
|
101
|
+
{colors.map((c) => (
|
|
102
|
+
<label class="filters__swatch" style={`--sw: ${c.hex}`}>
|
|
103
|
+
<input type="checkbox" name="color" value={c.name.toLowerCase()} />
|
|
104
|
+
<span class="filters__swatch-dot" aria-hidden="true"></span>
|
|
105
|
+
<span class="sr-only">{c.name}</span>
|
|
106
|
+
</label>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</fieldset>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
<fieldset class="filters__group">
|
|
113
|
+
<legend>Rating</legend>
|
|
114
|
+
{[['0', 'Any rating'], ['4.5', '4.5 ★ & up'], ['4', '4 ★ & up']].map(([v, l], i) => (
|
|
115
|
+
<label class="filters__check filters__check--radio">
|
|
116
|
+
<input type="radio" name="rating" value={v} checked={i === 0} />
|
|
117
|
+
<span class="filters__box" aria-hidden="true"></span>
|
|
118
|
+
<span class="filters__label">{l}</span>
|
|
119
|
+
</label>
|
|
120
|
+
))}
|
|
121
|
+
</fieldset>
|
|
122
|
+
|
|
123
|
+
<fieldset class="filters__group">
|
|
124
|
+
<legend class="sr-only">Availability</legend>
|
|
125
|
+
<label class="filters__switch">
|
|
126
|
+
<span>In stock only</span>
|
|
127
|
+
<input type="checkbox" name="stock" value="in" role="switch" />
|
|
128
|
+
<span class="filters__track" aria-hidden="true"></span>
|
|
129
|
+
</label>
|
|
130
|
+
</fieldset>
|
|
131
|
+
|
|
132
|
+
<button class="filters__reset" type="reset">Clear all filters</button>
|
|
133
|
+
</form>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
import { formatMoney } from './cart-store';
|
|
138
|
+
|
|
139
|
+
document.querySelectorAll<HTMLFormElement>('form[data-filters]:not([data-bound])').forEach((form) => {
|
|
140
|
+
form.dataset.bound = '';
|
|
141
|
+
const root = form.closest<HTMLElement>('[data-filters-root]')!;
|
|
142
|
+
const target = document.getElementById(form.dataset.filters || '');
|
|
143
|
+
if (!target) return;
|
|
144
|
+
const grid: HTMLElement = target;
|
|
145
|
+
const wrap = grid.parentElement;
|
|
146
|
+
const empty = wrap?.querySelector<HTMLElement>('[data-product-empty]');
|
|
147
|
+
const cards = Array.from(grid.querySelectorAll<HTMLElement>('[data-product]'));
|
|
148
|
+
cards.forEach((c, i) => { c.dataset.index = String(i); });
|
|
149
|
+
const sort = document.getElementById(`${form.id}-sort`) as HTMLSelectElement | null;
|
|
150
|
+
const priceOut = form.querySelector<HTMLOutputElement>('[data-price-out]');
|
|
151
|
+
const toggle = root.querySelector<HTMLButtonElement>('[data-filters-toggle]');
|
|
152
|
+
const activeBadge = root.querySelector<HTMLElement>('[data-active-count]');
|
|
153
|
+
const money = { currency: form.dataset.currency, locale: form.dataset.locale };
|
|
154
|
+
const priceInput = form.elements.namedItem('max') as HTMLInputElement;
|
|
155
|
+
const ceiling = Number(priceInput.max);
|
|
156
|
+
|
|
157
|
+
// Pre-select categories from ?category=a,b
|
|
158
|
+
const params = new URLSearchParams(window.location.search);
|
|
159
|
+
const fromUrl = (params.get('category') || '').split(',').filter(Boolean);
|
|
160
|
+
if (fromUrl.length) {
|
|
161
|
+
form.querySelectorAll<HTMLInputElement>('input[name="category"]').forEach((i) => { i.checked = fromUrl.includes(i.value); });
|
|
162
|
+
}
|
|
163
|
+
if (sort && params.get('sort')) sort.value = params.get('sort')!;
|
|
164
|
+
|
|
165
|
+
function apply() {
|
|
166
|
+
const data = new FormData(form);
|
|
167
|
+
const cats = data.getAll('category') as string[];
|
|
168
|
+
const colors = data.getAll('color') as string[];
|
|
169
|
+
const max = Number(data.get('max'));
|
|
170
|
+
const minRating = Number(data.get('rating') || 0);
|
|
171
|
+
const stockOnly = data.get('stock') === 'in';
|
|
172
|
+
let visible = 0;
|
|
173
|
+
|
|
174
|
+
cards.forEach((card) => {
|
|
175
|
+
const d = card.dataset;
|
|
176
|
+
const cardColors = (d.colors || '').split(',').filter(Boolean);
|
|
177
|
+
const ok =
|
|
178
|
+
(!cats.length || cats.includes(d.category || '')) &&
|
|
179
|
+
Number(d.price) <= max &&
|
|
180
|
+
Number(d.rating) >= minRating &&
|
|
181
|
+
(!stockOnly || d.stock === 'in') &&
|
|
182
|
+
(!colors.length || colors.some((c) => cardColors.includes(c)));
|
|
183
|
+
card.hidden = !ok;
|
|
184
|
+
if (ok) visible++;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const mode = sort?.value || 'featured';
|
|
188
|
+
const sorted = [...cards].sort((a, b) => {
|
|
189
|
+
const A = a.dataset, B = b.dataset;
|
|
190
|
+
if (mode === 'price-asc') return Number(A.price) - Number(B.price);
|
|
191
|
+
if (mode === 'price-desc') return Number(B.price) - Number(A.price);
|
|
192
|
+
if (mode === 'rating') return Number(B.rating) - Number(A.rating);
|
|
193
|
+
if (mode === 'name') return (A.name || '').localeCompare(B.name || '');
|
|
194
|
+
return Number(A.index) - Number(B.index);
|
|
195
|
+
});
|
|
196
|
+
grid.append(...sorted);
|
|
197
|
+
|
|
198
|
+
if (empty) empty.hidden = visible > 0;
|
|
199
|
+
if (priceOut) priceOut.textContent = formatMoney(max, money);
|
|
200
|
+
document.querySelectorAll<HTMLElement>(`[data-result-count="${grid.id}"]`).forEach((n) => { n.textContent = String(visible); });
|
|
201
|
+
|
|
202
|
+
const active = cats.length + colors.length + (max < ceiling ? 1 : 0) + (minRating > 0 ? 1 : 0) + (stockOnly ? 1 : 0);
|
|
203
|
+
if (activeBadge) { activeBadge.hidden = active === 0; activeBadge.textContent = String(active); }
|
|
204
|
+
|
|
205
|
+
const url = new URL(window.location.href);
|
|
206
|
+
if (cats.length) url.searchParams.set('category', cats.join(','));
|
|
207
|
+
else url.searchParams.delete('category');
|
|
208
|
+
if (mode !== 'featured') url.searchParams.set('sort', mode);
|
|
209
|
+
else url.searchParams.delete('sort');
|
|
210
|
+
window.history.replaceState(null, '', url);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
form.addEventListener('input', apply);
|
|
214
|
+
form.addEventListener('change', apply);
|
|
215
|
+
form.addEventListener('submit', (e) => { e.preventDefault(); apply(); });
|
|
216
|
+
form.addEventListener('reset', () => window.setTimeout(apply, 0));
|
|
217
|
+
sort?.addEventListener('change', apply);
|
|
218
|
+
|
|
219
|
+
toggle?.addEventListener('click', () => {
|
|
220
|
+
const open = toggle.getAttribute('aria-expanded') !== 'true';
|
|
221
|
+
toggle.setAttribute('aria-expanded', String(open));
|
|
222
|
+
root.classList.toggle('is-open', open);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
apply();
|
|
226
|
+
});
|
|
227
|
+
</script>
|
|
228
|
+
|
|
229
|
+
<style>
|
|
230
|
+
.filters { container-type: inline-size; position: relative; }
|
|
231
|
+
.filters__form {
|
|
232
|
+
display: grid;
|
|
233
|
+
gap: var(--sp-2, 0.5rem);
|
|
234
|
+
}
|
|
235
|
+
.filters--glass .filters__form {
|
|
236
|
+
padding: var(--sp-5, 1.25rem);
|
|
237
|
+
border-radius: var(--r-lg, 20px);
|
|
238
|
+
border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
|
|
239
|
+
background: var(--c-surface, rgb(var(--ink-rgb, 255 255 255) / 0.02));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.filters__group {
|
|
243
|
+
display: grid;
|
|
244
|
+
gap: 2px;
|
|
245
|
+
margin: 0;
|
|
246
|
+
padding: 0 0 var(--sp-4, 1rem);
|
|
247
|
+
border: 0;
|
|
248
|
+
border-bottom: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
|
|
249
|
+
}
|
|
250
|
+
.filters__group:last-of-type { border-bottom: 0; }
|
|
251
|
+
.filters__group legend {
|
|
252
|
+
padding: var(--sp-2, 0.5rem) 0;
|
|
253
|
+
font-size: 0.72rem;
|
|
254
|
+
font-weight: 800;
|
|
255
|
+
letter-spacing: 0.1em;
|
|
256
|
+
text-transform: uppercase;
|
|
257
|
+
color: var(--c-text-1, #fff);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.filters__check {
|
|
261
|
+
position: relative;
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
gap: var(--sp-3, 0.75rem);
|
|
265
|
+
min-height: 40px;
|
|
266
|
+
padding: 0 var(--sp-2, 0.5rem);
|
|
267
|
+
margin: 0 calc(var(--sp-2, 0.5rem) * -1);
|
|
268
|
+
border-radius: var(--r-sm, 8px);
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
color: var(--c-text-2, #a3a3a3);
|
|
271
|
+
font-size: 0.9rem;
|
|
272
|
+
}
|
|
273
|
+
.filters--sm .filters__check { min-height: 34px; font-size: 0.85rem; }
|
|
274
|
+
.filters__check:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.03); color: var(--c-text-1, #fff); }
|
|
275
|
+
.filters__check input,
|
|
276
|
+
.filters__swatch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
|
|
277
|
+
.filters__box {
|
|
278
|
+
display: grid;
|
|
279
|
+
place-items: center;
|
|
280
|
+
flex-shrink: 0;
|
|
281
|
+
width: 18px;
|
|
282
|
+
height: 18px;
|
|
283
|
+
border-radius: 5px;
|
|
284
|
+
border: 1.5px solid rgb(var(--ink-rgb, 255 255 255) / 0.25);
|
|
285
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
286
|
+
}
|
|
287
|
+
.filters__check--radio .filters__box { border-radius: 50%; }
|
|
288
|
+
.filters__check input:checked + .filters__box {
|
|
289
|
+
border-color: var(--c-primary-light, #d4a017);
|
|
290
|
+
background: var(--c-primary-light, #d4a017);
|
|
291
|
+
box-shadow: inset 0 0 0 3px var(--c-bg-1, #0a0a0a);
|
|
292
|
+
}
|
|
293
|
+
.filters__check input:checked ~ .filters__label { color: var(--c-text-1, #fff); font-weight: 600; }
|
|
294
|
+
.filters__check input:focus-visible + .filters__box,
|
|
295
|
+
.filters__swatch input:focus-visible + .filters__swatch-dot,
|
|
296
|
+
.filters__switch input:focus-visible + .filters__track { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
297
|
+
.filters__label { flex: 1; }
|
|
298
|
+
.filters__count { font-size: 0.75rem; color: var(--c-text-2, #a3a3a3); }
|
|
299
|
+
|
|
300
|
+
.filters__range { display: grid; gap: var(--sp-2, 0.5rem); }
|
|
301
|
+
.filters__range-head { display: flex; justify-content: space-between; font-size: 0.85rem; }
|
|
302
|
+
.filters__range-head output { color: var(--c-text-1, #fff); font-weight: 700; }
|
|
303
|
+
.filters__range input { width: 100%; accent-color: var(--c-primary-light, #d4a017); height: 24px; cursor: pointer; }
|
|
304
|
+
.filters__range-scale { display: flex; justify-content: space-between; font-size: 0.72rem; color: var(--c-text-2, #a3a3a3); }
|
|
305
|
+
|
|
306
|
+
.filters__swatches { display: flex; flex-wrap: wrap; gap: var(--sp-2, 0.5rem); padding-top: 2px; }
|
|
307
|
+
.filters__swatch { position: relative; display: grid; place-items: center; width: 40px; height: 40px; cursor: pointer; }
|
|
308
|
+
.filters__swatch-dot {
|
|
309
|
+
width: 28px;
|
|
310
|
+
height: 28px;
|
|
311
|
+
border-radius: 50%;
|
|
312
|
+
background: var(--sw);
|
|
313
|
+
box-shadow: 0 0 0 1px rgb(var(--ink-rgb, 255 255 255) / 0.2);
|
|
314
|
+
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
|
315
|
+
}
|
|
316
|
+
.filters__swatch:hover .filters__swatch-dot { transform: scale(1.08); }
|
|
317
|
+
.filters__swatch input:checked + .filters__swatch-dot { box-shadow: 0 0 0 2px var(--c-bg-1, #0a0a0a), 0 0 0 4px var(--c-primary-light, #d4a017); }
|
|
318
|
+
|
|
319
|
+
.filters__switch {
|
|
320
|
+
position: relative;
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
justify-content: space-between;
|
|
324
|
+
min-height: 44px;
|
|
325
|
+
font-size: 0.9rem;
|
|
326
|
+
color: var(--c-text-1, #fff);
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
}
|
|
329
|
+
.filters__switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
|
|
330
|
+
.filters__track {
|
|
331
|
+
position: relative;
|
|
332
|
+
width: 42px;
|
|
333
|
+
height: 24px;
|
|
334
|
+
border-radius: 999px;
|
|
335
|
+
background: rgb(var(--ink-rgb, 255 255 255) / 0.12);
|
|
336
|
+
transition: background 0.2s ease;
|
|
337
|
+
}
|
|
338
|
+
.filters__track::after {
|
|
339
|
+
content: '';
|
|
340
|
+
position: absolute;
|
|
341
|
+
top: 3px;
|
|
342
|
+
left: 3px;
|
|
343
|
+
width: 18px;
|
|
344
|
+
height: 18px;
|
|
345
|
+
border-radius: 50%;
|
|
346
|
+
background: var(--c-text-1, #fff);
|
|
347
|
+
transition: transform 0.2s ease;
|
|
348
|
+
}
|
|
349
|
+
.filters__switch input:checked + .filters__track { background: var(--c-primary-light, #d4a017); }
|
|
350
|
+
.filters__switch input:checked + .filters__track::after { transform: translateX(18px); }
|
|
351
|
+
|
|
352
|
+
.filters__reset {
|
|
353
|
+
min-height: 44px;
|
|
354
|
+
margin-top: var(--sp-2, 0.5rem);
|
|
355
|
+
border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12);
|
|
356
|
+
border-radius: var(--r-full, 9999px);
|
|
357
|
+
background: transparent;
|
|
358
|
+
color: var(--c-text-1, #fff);
|
|
359
|
+
font: inherit;
|
|
360
|
+
font-size: 0.85rem;
|
|
361
|
+
font-weight: 600;
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
}
|
|
364
|
+
.filters__reset:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.05); }
|
|
365
|
+
.filters__reset:focus-visible, .filters__toggle:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
366
|
+
|
|
367
|
+
.filters__toggle {
|
|
368
|
+
display: none;
|
|
369
|
+
align-items: center;
|
|
370
|
+
gap: var(--sp-2, 0.5rem);
|
|
371
|
+
min-height: 44px;
|
|
372
|
+
padding: 0 var(--sp-4, 1rem);
|
|
373
|
+
border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.12);
|
|
374
|
+
border-radius: var(--r-full, 9999px);
|
|
375
|
+
background: rgb(var(--ink-rgb, 255 255 255) / 0.03);
|
|
376
|
+
color: var(--c-text-1, #fff);
|
|
377
|
+
font: inherit;
|
|
378
|
+
font-size: 0.9rem;
|
|
379
|
+
font-weight: 600;
|
|
380
|
+
cursor: pointer;
|
|
381
|
+
}
|
|
382
|
+
.filters__active {
|
|
383
|
+
min-width: 20px;
|
|
384
|
+
height: 20px;
|
|
385
|
+
padding: 0 6px;
|
|
386
|
+
border-radius: 999px;
|
|
387
|
+
background: var(--c-primary-light, #d4a017);
|
|
388
|
+
color: var(--c-on-primary, #050505);
|
|
389
|
+
font-size: 0.72rem;
|
|
390
|
+
font-weight: 800;
|
|
391
|
+
line-height: 20px;
|
|
392
|
+
}
|
|
393
|
+
.filters__active[hidden] { display: none; }
|
|
394
|
+
|
|
395
|
+
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
396
|
+
|
|
397
|
+
@container (max-width: 900px) {
|
|
398
|
+
.filters__toggle { display: inline-flex; }
|
|
399
|
+
.filters__form { display: none; margin-top: var(--sp-3, 0.75rem); }
|
|
400
|
+
.filters.is-open .filters__form { display: grid; }
|
|
401
|
+
}
|
|
402
|
+
@media (prefers-reduced-motion: reduce) {
|
|
403
|
+
.filters__track, .filters__track::after, .filters__swatch-dot { transition: none; }
|
|
404
|
+
}
|
|
405
|
+
</style>
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* LiveActivityToast — Rotating social-proof notice ("Lucía in Madrid bought Aurora Headphones
|
|
4
|
+
* · 3 min ago") that slides in, waits and hides, cycling through `items`.
|
|
5
|
+
*
|
|
6
|
+
* @param {Array} items - `{ name, city, product, href?, icon?, hue?, ago }` entries to cycle through.
|
|
7
|
+
* @param {string} verb - Action text between person and product. Default 'just bought'.
|
|
8
|
+
* @param {number} interval - Time between notices in ms. Default 9000.
|
|
9
|
+
* @param {number} visibleFor - How long each notice stays in ms. Default 5000.
|
|
10
|
+
* @param {number} delay - Wait before the first notice in ms. Default 4000.
|
|
11
|
+
* @param {string} position - Corner (bottom-left|bottom-right). Default 'bottom-left'.
|
|
12
|
+
* @param {string} storageKey - sessionStorage key that remembers "stop showing". Default 'ack-activity-off'.
|
|
13
|
+
* @param {boolean} demo - Render statically with the first item (docs/previews). Default false.
|
|
14
|
+
* @param {string} variant - Style (glass|solid). Default 'glass'.
|
|
15
|
+
* @param {string} className - Extra class.
|
|
16
|
+
*
|
|
17
|
+
* Accessibility: not announced by screen readers (it is promotional, aria-live="off"), never
|
|
18
|
+
* steals focus, has a close button that stops it for the session, pauses on hover/focus and
|
|
19
|
+
* when the tab is hidden; respects prefers-reduced-motion.
|
|
20
|
+
*/
|
|
21
|
+
import { productIcon } from './product-icons';
|
|
22
|
+
|
|
23
|
+
interface Activity { name: string; city: string; product: string; href?: string; icon?: string; hue?: number; ago: string }
|
|
24
|
+
interface Props {
|
|
25
|
+
items?: Activity[];
|
|
26
|
+
verb?: string;
|
|
27
|
+
interval?: number;
|
|
28
|
+
visibleFor?: number;
|
|
29
|
+
delay?: number;
|
|
30
|
+
position?: 'bottom-left' | 'bottom-right';
|
|
31
|
+
storageKey?: string;
|
|
32
|
+
demo?: boolean;
|
|
33
|
+
variant?: 'glass' | 'solid';
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
items = [
|
|
39
|
+
{ name: 'Lucía', city: 'Madrid', product: 'Aurora Wireless Headphones', icon: 'headphones', hue: 38, ago: '2 min ago' },
|
|
40
|
+
{ name: 'Tom', city: 'Berlin', product: 'Trail 24L Backpack', icon: 'backpack', hue: 150, ago: '6 min ago' },
|
|
41
|
+
{ name: 'Aiko', city: 'Osaka', product: 'Orbit Smart Watch', icon: 'watch', hue: 205, ago: '11 min ago' },
|
|
42
|
+
],
|
|
43
|
+
verb = 'just bought',
|
|
44
|
+
interval = 9000,
|
|
45
|
+
visibleFor = 5000,
|
|
46
|
+
delay = 4000,
|
|
47
|
+
position = 'bottom-left',
|
|
48
|
+
storageKey = 'ack-activity-off',
|
|
49
|
+
demo = false,
|
|
50
|
+
variant = 'glass',
|
|
51
|
+
className = '',
|
|
52
|
+
} = Astro.props;
|
|
53
|
+
|
|
54
|
+
const first = items[0];
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<div
|
|
58
|
+
class:list={['live-activity', `live-activity--${position}`, `live-activity--${variant}`, { 'live-activity--demo is-visible': demo }, className]}
|
|
59
|
+
aria-live="off"
|
|
60
|
+
data-live-activity={demo ? undefined : JSON.stringify(items)}
|
|
61
|
+
data-verb={verb}
|
|
62
|
+
data-interval={interval}
|
|
63
|
+
data-visible={visibleFor}
|
|
64
|
+
data-delay={delay}
|
|
65
|
+
data-key={storageKey}
|
|
66
|
+
hidden={!demo}
|
|
67
|
+
>
|
|
68
|
+
{first && (
|
|
69
|
+
<>
|
|
70
|
+
<span class="live-activity__media" style={`--h: ${first.hue ?? 40}`} aria-hidden="true" data-media>
|
|
71
|
+
<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"><Fragment set:html={productIcon(first.icon)} /></svg>
|
|
72
|
+
</span>
|
|
73
|
+
<a class="live-activity__body" href={first.href ?? '#'} data-link>
|
|
74
|
+
<span class="live-activity__line"><strong data-who>{first.name}</strong> <span data-city>in {first.city}</span> {verb}</span>
|
|
75
|
+
<span class="live-activity__product" data-product>{first.product}</span>
|
|
76
|
+
<span class="live-activity__ago"><span class="live-activity__dot" aria-hidden="true"></span><span data-ago>{first.ago}</span> · Verified</span>
|
|
77
|
+
</a>
|
|
78
|
+
<button class="live-activity__close" type="button" aria-label="Stop showing purchase notifications" data-close>×</button>
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
import { productIcon } from './product-icons';
|
|
85
|
+
|
|
86
|
+
document.querySelectorAll<HTMLElement>('[data-live-activity]:not([data-bound])').forEach((el) => {
|
|
87
|
+
el.dataset.bound = '';
|
|
88
|
+
const key = el.dataset.key || 'ack-activity-off';
|
|
89
|
+
try { if (window.sessionStorage.getItem(key)) return; } catch { /* storage blocked */ }
|
|
90
|
+
let items: Array<Record<string, any>> = [];
|
|
91
|
+
try { items = JSON.parse(el.dataset.liveActivity || '[]'); } catch { return; }
|
|
92
|
+
if (!items.length) return;
|
|
93
|
+
|
|
94
|
+
const q = (s: string) => el.querySelector<HTMLElement>(s);
|
|
95
|
+
const media = q('[data-media]');
|
|
96
|
+
const link = q('[data-link]') as HTMLAnchorElement | null;
|
|
97
|
+
const verb = el.dataset.verb || 'just bought';
|
|
98
|
+
const interval = Number(el.dataset.interval) || 9000;
|
|
99
|
+
const visibleFor = Number(el.dataset.visible) || 5000;
|
|
100
|
+
let index = 0;
|
|
101
|
+
let paused = false;
|
|
102
|
+
let stopped = false;
|
|
103
|
+
let hideTimer = 0;
|
|
104
|
+
|
|
105
|
+
function fill(item: Record<string, any>) {
|
|
106
|
+
q('[data-who]')!.textContent = item.name;
|
|
107
|
+
q('[data-city]')!.textContent = `in ${item.city}`;
|
|
108
|
+
q('[data-product]')!.textContent = item.product;
|
|
109
|
+
q('[data-ago]')!.textContent = item.ago;
|
|
110
|
+
if (link) link.href = item.href || '#';
|
|
111
|
+
if (media) {
|
|
112
|
+
media.style.setProperty('--h', String(item.hue ?? 40));
|
|
113
|
+
media.innerHTML = `<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round">${productIcon(item.icon)}</svg>`;
|
|
114
|
+
}
|
|
115
|
+
const line = q('.live-activity__line');
|
|
116
|
+
if (line && line.lastChild) line.lastChild.textContent = ` ${verb}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function cycle() {
|
|
120
|
+
if (stopped) return;
|
|
121
|
+
if (paused || document.hidden) { window.setTimeout(cycle, 1500); return; }
|
|
122
|
+
fill(items[index % items.length]);
|
|
123
|
+
index++;
|
|
124
|
+
el.hidden = false;
|
|
125
|
+
requestAnimationFrame(() => el.classList.add('is-visible'));
|
|
126
|
+
hideTimer = window.setTimeout(hide, visibleFor);
|
|
127
|
+
}
|
|
128
|
+
function hide() {
|
|
129
|
+
if (paused) { hideTimer = window.setTimeout(hide, 1000); return; }
|
|
130
|
+
el.classList.remove('is-visible');
|
|
131
|
+
window.setTimeout(() => { el.hidden = true; }, 300);
|
|
132
|
+
window.setTimeout(cycle, Math.max(1000, interval - visibleFor));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
el.addEventListener('mouseenter', () => { paused = true; });
|
|
136
|
+
el.addEventListener('mouseleave', () => { paused = false; });
|
|
137
|
+
el.addEventListener('focusin', () => { paused = true; });
|
|
138
|
+
el.addEventListener('focusout', () => { paused = false; });
|
|
139
|
+
q('[data-close]')?.addEventListener('click', () => {
|
|
140
|
+
stopped = true;
|
|
141
|
+
window.clearTimeout(hideTimer);
|
|
142
|
+
el.classList.remove('is-visible');
|
|
143
|
+
el.hidden = true;
|
|
144
|
+
try { window.sessionStorage.setItem(key, '1'); } catch { /* ignore */ }
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
window.setTimeout(cycle, Number(el.dataset.delay) || 4000);
|
|
148
|
+
});
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<style>
|
|
152
|
+
.live-activity {
|
|
153
|
+
position: fixed;
|
|
154
|
+
bottom: 1rem;
|
|
155
|
+
z-index: 1050;
|
|
156
|
+
display: grid;
|
|
157
|
+
grid-template-columns: 52px 1fr auto;
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: var(--sp-3, 0.75rem);
|
|
160
|
+
width: min(360px, calc(100vw - 2rem));
|
|
161
|
+
padding: var(--sp-3, 0.75rem);
|
|
162
|
+
border-radius: var(--r-lg, 20px);
|
|
163
|
+
border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
|
|
164
|
+
background: rgb(var(--paper-rgb, 5 5 5) / 0.86);
|
|
165
|
+
backdrop-filter: blur(16px) saturate(1.4);
|
|
166
|
+
box-shadow: 0 24px 60px -24px rgba(0, 0, 0, 0.6);
|
|
167
|
+
opacity: 0;
|
|
168
|
+
transform: translateY(16px);
|
|
169
|
+
transition: opacity 0.3s ease, transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
|
|
170
|
+
}
|
|
171
|
+
.live-activity[hidden] { display: none; }
|
|
172
|
+
.live-activity.is-visible { opacity: 1; transform: none; }
|
|
173
|
+
.live-activity--bottom-left { left: 1rem; }
|
|
174
|
+
.live-activity--bottom-right { right: 1rem; }
|
|
175
|
+
.live-activity--solid { background: var(--c-bg-1, #0a0a0a); backdrop-filter: none; }
|
|
176
|
+
.live-activity--demo { position: relative; inset: auto; margin: 0 auto; }
|
|
177
|
+
|
|
178
|
+
.live-activity__media {
|
|
179
|
+
display: grid;
|
|
180
|
+
place-items: center;
|
|
181
|
+
width: 52px;
|
|
182
|
+
height: 52px;
|
|
183
|
+
border-radius: var(--r-md, 12px);
|
|
184
|
+
color: hsl(var(--h) var(--pm-ink-s, 80%) var(--pm-ink-l, 80%));
|
|
185
|
+
background: radial-gradient(circle at 50% 40%, hsl(var(--h) 70% 50% / 0.3), transparent 70%), var(--c-bg-2, #111);
|
|
186
|
+
}
|
|
187
|
+
.live-activity__media :global(svg) { width: 60%; }
|
|
188
|
+
.live-activity__body { display: grid; gap: 1px; min-width: 0; color: inherit; text-decoration: none; border-radius: var(--r-sm, 8px); }
|
|
189
|
+
.live-activity__body:hover .live-activity__product { text-decoration: underline; }
|
|
190
|
+
.live-activity__body:focus-visible, .live-activity__close:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
|
|
191
|
+
.live-activity__line { font-size: 0.8rem; color: var(--c-text-2, #a3a3a3); }
|
|
192
|
+
.live-activity__line strong { color: var(--c-text-1, #fff); }
|
|
193
|
+
.live-activity__product { font-size: 0.88rem; font-weight: 700; color: var(--c-text-1, #fff); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
194
|
+
.live-activity__ago { display: inline-flex; align-items: center; gap: 6px; font-size: 0.72rem; color: var(--c-text-2, #a3a3a3); }
|
|
195
|
+
.live-activity__dot { width: 6px; height: 6px; border-radius: 50%; background: var(--c-success, #34d399); box-shadow: 0 0 8px var(--c-success, #34d399); }
|
|
196
|
+
.live-activity__close {
|
|
197
|
+
align-self: start;
|
|
198
|
+
width: 32px;
|
|
199
|
+
height: 32px;
|
|
200
|
+
border: 0;
|
|
201
|
+
border-radius: 50%;
|
|
202
|
+
background: transparent;
|
|
203
|
+
color: var(--c-text-2, #a3a3a3);
|
|
204
|
+
font-size: 1.25rem;
|
|
205
|
+
line-height: 1;
|
|
206
|
+
cursor: pointer;
|
|
207
|
+
}
|
|
208
|
+
.live-activity__close:hover { color: var(--c-text-1, #fff); background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
|
|
209
|
+
|
|
210
|
+
@media (prefers-reduced-motion: reduce) {
|
|
211
|
+
.live-activity { transition: none; transform: none; }
|
|
212
|
+
}
|
|
213
|
+
</style>
|