@contentrain/astro-kit 0.38.0 → 0.40.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 +2 -2
- package/catalog.json +51 -12
- package/components/_shared/BehaviorScript.astro +11 -0
- package/components/_shared/behaviors.ts +58 -7
- package/components/_shared/embed-frame.ts +22 -0
- package/components/{embed/embed.ts → _shared/embed-target.ts} +30 -0
- package/components/behavior/Behavior.astro +11 -0
- package/components/behavior/fixtures.json +3 -1
- package/components/behavior/meta.json +2 -0
- package/components/dialog/Dialog.astro +31 -7
- package/components/dialog/fixtures.json +19 -0
- package/components/dialog/meta.json +3 -2
- package/components/embed/Embed.astro +7 -10
- package/components/embed/meta.json +1 -1
- package/components/header/Header.astro +9 -2
- package/components/header/header.ts +13 -0
- package/components/header/meta.json +1 -0
- package/components/slider/Slider.astro +9 -3
- package/components/slider/SliderTrack.astro +97 -21
- package/components/slider/fixtures.json +5 -1
- package/components/slider/meta.json +5 -4
- package/components/stats/Stats.astro +15 -6
- package/components/stats/fixtures.json +25 -0
- package/components/stats/meta.json +14 -2
- package/mapping/classic.json +14 -7
- package/mapping/divi.json +8 -4
- package/mapping/elementor.json +16 -8
- package/mapping/gutenberg.json +2 -1
- package/package.json +3 -2
|
@@ -8,6 +8,7 @@ import type { ActionInput, ImageInput, NavItem } from '../_shared/types'
|
|
|
8
8
|
import Nav from '../nav/Nav.astro'
|
|
9
9
|
import SearchBox from '../search-box/SearchBox.astro'
|
|
10
10
|
import { safeHref } from '../_shared/safe-url'
|
|
11
|
+
import { brandColorValue } from './header'
|
|
11
12
|
|
|
12
13
|
const header = tv({
|
|
13
14
|
slots: {
|
|
@@ -50,6 +51,11 @@ interface Props {
|
|
|
50
51
|
padBottom?: string | undefined
|
|
51
52
|
/** The navigation in the site's link colour, as themes that colour every link do (Hello Elementor). */
|
|
52
53
|
linkNav?: boolean | undefined
|
|
54
|
+
/**
|
|
55
|
+
* The site name's colour where the source theme's is not the header's text colour: `link` for the site's link colour
|
|
56
|
+
* (a title that is a link styled like every other), or a colour (`#hex`, `rgb()`, `hsl()`; anything else is ignored).
|
|
57
|
+
*/
|
|
58
|
+
brandColor?: string | undefined
|
|
53
59
|
logo?: ImageInput | undefined
|
|
54
60
|
/** Show the site name next to the logo. */
|
|
55
61
|
showName?: boolean | undefined
|
|
@@ -68,11 +74,12 @@ interface Props {
|
|
|
68
74
|
menu?: 'flyout' | 'mega' | undefined
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
const { siteName, tagline, brandSize, brandWeight, brandTracking, bordered, offsetTop, padTop, padBottom, linkNav, logo, showName = true, homeHref = '/', items, action, search, navLabel, menuLabel, closeLabel, layout, tone, position, menu } = Astro.props
|
|
77
|
+
const { siteName, tagline, brandSize, brandWeight, brandTracking, bordered, offsetTop, padTop, padBottom, linkNav, brandColor, logo, showName = true, homeHref = '/', items, action, search, navLabel, menuLabel, closeLabel, layout, tone, position, menu } = Astro.props
|
|
72
78
|
const { root, inner, brand } = header({ layout, tone, position, bordered })
|
|
73
79
|
const rootStyle = offsetTop ? `margin-top: ${offsetTop}` : undefined
|
|
74
80
|
const innerStyle = [padTop && `padding-top: ${padTop}`, padBottom && `padding-bottom: ${padBottom}`].filter(Boolean).join('; ') || undefined
|
|
75
|
-
const
|
|
81
|
+
const brandColour = brandColorValue(brandColor)
|
|
82
|
+
const brandStyle = [brandSize && `font-size: ${brandSize}`, brandWeight && `font-weight: ${brandWeight}`, brandTracking && `letter-spacing: ${brandTracking}`, brandColour && `color: ${brandColour}`].filter(Boolean).join('; ') || undefined
|
|
76
83
|
// A tagline under the name keeps its line on a phone, as themes print it: the navigation wraps below.
|
|
77
84
|
// The search field sits in the bar from lg; at md the bar already holds the navigation, so the field keeps its own row.
|
|
78
85
|
const wrap = search ? 'flex-wrap lg:flex-nowrap' : tagline ? 'flex-wrap md:flex-nowrap' : undefined
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The header's site-name colour: the site's link colour, a colour the source theme gives the name, or nothing.
|
|
2
|
+
|
|
3
|
+
/** A colour that may go into the brand's style attribute: hex, `rgb()`/`rgba()` or `hsl()`/`hsla()` of numbers. */
|
|
4
|
+
const COLOR = /^(?:#[0-9a-f]{3,8}|(?:rgb|hsl)a?\([\d\s.,%/deg]+\))$/i
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `color` value for the brand: `link` → the site's link colour (the accent where none is set), a colour as it is,
|
|
8
|
+
* anything else (a `;`, a `url()`, a name the plan never writes) → none, so the header keeps its text colour.
|
|
9
|
+
*/
|
|
10
|
+
export function brandColorValue(brandColor: string | undefined): string | undefined {
|
|
11
|
+
if (brandColor === 'link') return 'var(--color-link, var(--color-accent))'
|
|
12
|
+
return brandColor && COLOR.test(brandColor) ? brandColor : undefined
|
|
13
|
+
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"padTop": { "type": "string", "description": "Space inside the bar above its content where the source's header has its own (a CSS length)." },
|
|
23
23
|
"padBottom": { "type": "string", "description": "Space inside the bar below its content where the source's header has its own (a CSS length)." },
|
|
24
24
|
"linkNav": { "type": "boolean", "default": false, "description": "The navigation in the site's link colour, as themes that colour every link do." },
|
|
25
|
+
"brandColor": { "type": "string", "description": "The site name's colour where the source theme's is not the header's text colour: link for the site's link colour, or a colour (#hex, rgb(), hsl())." },
|
|
25
26
|
"logo": { "type": "object", "fields": { "src": { "type": "image", "required": true }, "alt": { "type": "string", "required": true }, "width": { "type": "integer" }, "height": { "type": "integer" } }, "description": "Logo image (site.logo)." },
|
|
26
27
|
"showName": { "type": "boolean", "default": true, "description": "Show the site name next to the logo." },
|
|
27
28
|
"homeHref": { "type": "url", "default": "/", "description": "Where the brand links." },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
// A carousel of images with optional captions. Without JavaScript it is a
|
|
2
|
+
// A carousel of images, videos (a file, or a YouTube/Vimeo one loaded only on click) or text cards, with optional captions. Without JavaScript it is a
|
|
3
3
|
// horizontally scrolling, snapping row — every slide is reachable. With it,
|
|
4
4
|
// Embla turns the row into a paged carousel with previous/next buttons and
|
|
5
5
|
// slide dots. The markup and the script are in SliderTrack.astro; given a
|
|
@@ -10,9 +10,13 @@ import type { ActionInput, MeasuredStyle } from '../_shared/types'
|
|
|
10
10
|
import SliderTrack from './SliderTrack.astro'
|
|
11
11
|
|
|
12
12
|
interface Slide {
|
|
13
|
-
/** The image's address (or, until 0.7, an `ImageInput`). */
|
|
14
|
-
image
|
|
13
|
+
/** The image's address (or, until 0.7, an `ImageInput`). Optional: a text card (a quote, a testimonial) is a slide too. */
|
|
14
|
+
image?: ImageSource | undefined
|
|
15
15
|
imageAlt?: string | undefined
|
|
16
|
+
/** A video file's address (mp4, webm): drawn in place of the image (its poster), played only when the reader asks. */
|
|
17
|
+
video?: string | undefined
|
|
18
|
+
/** A video on another site (its YouTube/Vimeo/Loom/Wistia page, or `youtube:<id>` / `vimeo:<id>`): a poster until the reader clicks it. */
|
|
19
|
+
embed?: string | undefined
|
|
16
20
|
title?: string | undefined
|
|
17
21
|
text?: string | undefined
|
|
18
22
|
/** The slide's link, flat so a page stays within Contentrain's nesting depth. */
|
|
@@ -37,6 +41,8 @@ interface Props {
|
|
|
37
41
|
slideLabel?: string | undefined
|
|
38
42
|
/** Word between a slide number and the count ("of"). */
|
|
39
43
|
ofLabel?: string | undefined
|
|
44
|
+
/** An embedded video's play button, before its name ("Play"). */
|
|
45
|
+
playLabel?: string | undefined
|
|
40
46
|
loop?: boolean | undefined
|
|
41
47
|
perView?: '1' | '2' | '3' | undefined
|
|
42
48
|
aspect?: 'wide' | 'standard' | 'square' | undefined
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
// A carousel of images with optional captions
|
|
2
|
+
// A carousel of images with optional captions, of videos (played only when the
|
|
3
|
+
// reader asks; a YouTube or Vimeo one only once the reader clicks its poster), or of text cards (a slide with no image: quotes, testimonials). Without JavaScript it is a
|
|
3
4
|
// horizontally scrolling, snapping row — every slide is reachable. With it,
|
|
4
5
|
// Embla turns the row into a paged carousel with previous/next buttons and
|
|
5
6
|
// slide dots. Slider.astro frames it (the section's own look, when a measured
|
|
@@ -9,6 +10,8 @@ import Button from '../_shared/Button.astro'
|
|
|
9
10
|
import KitImage from '../_shared/KitImage.astro'
|
|
10
11
|
import SectionHeader from '../_shared/SectionHeader.astro'
|
|
11
12
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
13
|
+
import { safeSrc } from '../_shared/safe-url'
|
|
14
|
+
import { onVideoHost, videoTarget, type EmbedTarget } from '../_shared/embed-target'
|
|
12
15
|
import { measuredSlides } from '../_shared/measured'
|
|
13
16
|
import { type ActionInput, type ImageInput, type MeasuredStyle } from '../_shared/types'
|
|
14
17
|
|
|
@@ -19,6 +22,8 @@ const slider = tv({
|
|
|
19
22
|
slide: 'relative min-w-0 shrink-0 snap-start overflow-hidden',
|
|
20
23
|
image: 'w-full object-cover',
|
|
21
24
|
caption: 'flex flex-col items-start gap-2',
|
|
25
|
+
// A slide with no image: its text as a card the height of the row, never laid over an image that is not there.
|
|
26
|
+
card: 'flex h-full flex-col items-start gap-2 rounded-[var(--radius-card)] bg-surface p-6 text-ink ring-1 ring-line/60',
|
|
22
27
|
},
|
|
23
28
|
variants: {
|
|
24
29
|
perView: {
|
|
@@ -41,9 +46,21 @@ const slider = tv({
|
|
|
41
46
|
})
|
|
42
47
|
|
|
43
48
|
interface Slide {
|
|
44
|
-
/** The image's address (or, until 0.7, an `ImageInput`). */
|
|
45
|
-
image
|
|
49
|
+
/** The image's address (or, until 0.7, an `ImageInput`). Optional: a text card (a quote, a testimonial) is a slide too. */
|
|
50
|
+
image?: ImageSource | undefined
|
|
46
51
|
imageAlt?: string | undefined
|
|
52
|
+
/**
|
|
53
|
+
* A video file's address (mp4, webm). Drawn in place of the image, which becomes its poster; it loads nothing until
|
|
54
|
+
* the reader plays it, never plays by itself, and pauses when its slide is paged away. The caption sits under it.
|
|
55
|
+
*/
|
|
56
|
+
video?: string | undefined
|
|
57
|
+
/**
|
|
58
|
+
* A video on another site: its page (YouTube, Vimeo, Loom, Wistia) or `youtube:<id>` / `vimeo:<id>`. Drawn as its
|
|
59
|
+
* poster (the image, else a dark panel) with a play button; nothing from the provider loads until the reader clicks,
|
|
60
|
+
* and then the player takes the poster's place (YouTube from youtube-nocookie.com, Vimeo with dnt). Paged away, it
|
|
61
|
+
* goes back to its poster. Wins over `video`; an address that is not one of these videos is not drawn.
|
|
62
|
+
*/
|
|
63
|
+
embed?: string | undefined
|
|
47
64
|
title?: string | undefined
|
|
48
65
|
text?: string | undefined
|
|
49
66
|
/** The slide's link, flat so a page stays within Contentrain's nesting depth. */
|
|
@@ -68,6 +85,8 @@ interface Props {
|
|
|
68
85
|
slideLabel?: string | undefined
|
|
69
86
|
/** Word between a slide number and the count ("of"). */
|
|
70
87
|
ofLabel?: string | undefined
|
|
88
|
+
/** An embedded video's play button, before its name ("Play"). */
|
|
89
|
+
playLabel?: string | undefined
|
|
71
90
|
loop?: boolean | undefined
|
|
72
91
|
perView?: '1' | '2' | '3' | undefined
|
|
73
92
|
aspect?: 'wide' | 'standard' | 'square' | undefined
|
|
@@ -80,20 +99,35 @@ interface Props {
|
|
|
80
99
|
framed?: boolean | undefined
|
|
81
100
|
}
|
|
82
101
|
|
|
83
|
-
const { slides: given, label, prevLabel = 'Previous slide', nextLabel = 'Next slide', slideLabel = 'Slide', ofLabel = 'of', loop = false, perView = '1', aspect, caption, heading, intro, eyebrow, actions, measured, id = 'slider', framed = true } = Astro.props
|
|
102
|
+
const { slides: given, label, prevLabel = 'Previous slide', nextLabel = 'Next slide', slideLabel = 'Slide', ofLabel = 'of', playLabel = 'Play', loop = false, perView = '1', aspect, caption, heading, intro, eyebrow, actions, measured, id = 'slider', framed = true } = Astro.props
|
|
103
|
+
// A slide is drawn when it has something to show: an image, a video (a file or another site's), or a text card (a title, a text or a link) where
|
|
104
|
+
// the source carousel moved quotes or testimonials. A slide with none of them is dropped.
|
|
84
105
|
const slides = given.flatMap((slide) => {
|
|
85
|
-
const
|
|
106
|
+
const player = videoTarget(slide.embed) ?? undefined
|
|
107
|
+
// A video's poster is the site's own image: one on the provider's hosts (its thumbnail) would be a request to it
|
|
108
|
+
// before the reader asked, so the poster is a plain dark panel instead.
|
|
109
|
+
const picture = slide.image === undefined ? undefined : imageOf(slide.image, slide.imageAlt)
|
|
110
|
+
const image = player && onVideoHost(picture?.src) ? undefined : picture
|
|
111
|
+
const video = player ? undefined : safeSrc(slide.video) || undefined
|
|
86
112
|
const action = slide.action ?? (slide.ctaLabel && slide.ctaHref ? { label: slide.ctaLabel, href: slide.ctaHref } : undefined)
|
|
87
|
-
return image ? [{ ...slide, image, action }] : []
|
|
88
|
-
}) satisfies Array<{ image: ImageInput, action?: ActionInput | undefined }>
|
|
113
|
+
return image || video || player || slide.title || slide.text || action ? [{ ...slide, image, video, player, action }] : []
|
|
114
|
+
}) satisfies Array<{ image: ImageInput | undefined, video: string | undefined, player: EmbedTarget | undefined, action?: ActionInput | undefined }>
|
|
89
115
|
const measuredRow = measuredSlides(measured)
|
|
90
116
|
// A measured row sets the slides in view itself, so the variant's own widths step aside.
|
|
91
117
|
const slots = slider({ perView: measuredRow ? '1' : perView, aspect, caption })
|
|
118
|
+
// A video's caption is never laid over it: it would cover the player's controls.
|
|
119
|
+
const below = slider({ perView: measuredRow ? '1' : perView, aspect, caption: 'below' })
|
|
92
120
|
const trackId = `${id}-track`
|
|
93
121
|
const sizes = measuredRow ? '(min-width: 1216px) 1152px, 100vw' : perView === '1' ? '(min-width: 1216px) 1152px, 100vw' : perView === '2' ? '(min-width: 640px) 50vw, 85vw' : '(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 85vw'
|
|
94
122
|
// Arrows from sm up; on phones the carousel is swiped and the dots show the position.
|
|
95
123
|
const Root = framed ? 'section' : 'div'
|
|
96
124
|
const arrow = 'absolute top-1/2 z-10 hidden size-11 -translate-y-1/2 items-center justify-center rounded-full bg-surface/90 text-ink shadow-md ring-1 ring-line hover:bg-surface disabled:opacity-40 sm:group-data-[ready]:flex'
|
|
125
|
+
// A text card has no picture for the arrows to sit on: over it they would cover its words. A carousel with any text
|
|
126
|
+
// card (all of them, or mixed with pictures) has its arrows under the row, either side of the dots.
|
|
127
|
+
const cards = slides.some(slide => !slide.image && !slide.video && !slide.player)
|
|
128
|
+
// A video from another site is a link to its page, so without JavaScript it opens there; its frame keeps the image's
|
|
129
|
+
// ratio, so the player that replaces the link moves nothing.
|
|
130
|
+
const under = 'flex size-11 shrink-0 items-center justify-center rounded-full bg-surface text-ink ring-1 ring-line hover:bg-surface disabled:opacity-40'
|
|
97
131
|
---
|
|
98
132
|
<Root class:list={['group relative', framed && 'container-page py-8']} role={framed ? undefined : 'region'} aria-roledescription="carousel" aria-label={label} data-kit-slider data-loop={loop ? '' : undefined} data-slide-label={slideLabel}>
|
|
99
133
|
<SectionHeader heading={heading} intro={intro} eyebrow={eyebrow} actions={actions} />
|
|
@@ -101,9 +135,22 @@ const arrow = 'absolute top-1/2 z-10 hidden size-11 -translate-y-1/2 items-cente
|
|
|
101
135
|
<div class:list={['flex gap-4 [&>*]:shrink-0 group-data-[ready]:[&>*]:snap-align-none', measuredRow?.track]} id={trackId} data-container>
|
|
102
136
|
{slides.map((slide, index) => (
|
|
103
137
|
<div class={slots.slide({ class: measuredRow?.slide })} role="group" aria-roledescription="slide" aria-label={`${slideLabel} ${index + 1} ${ofLabel} ${slides.length}`}>
|
|
104
|
-
|
|
138
|
+
{slide.player
|
|
139
|
+
? (
|
|
140
|
+
<div class={below.image({ class: 'relative overflow-hidden bg-ink' })} data-slide-player>
|
|
141
|
+
<a href={slide.player.href} class="group/play absolute inset-0 block focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-accent" data-embed-src={slide.player.src} data-embed-title={slide.title || slide.image?.alt || `${slideLabel} ${index + 1}`} aria-label={`${playLabel}: ${slide.title || slide.image?.alt || `${slideLabel} ${index + 1}`}`} rel="noopener">
|
|
142
|
+
{slide.image && <KitImage image={{ ...slide.image, alt: '' }} sizes={sizes} priority={index === 0} class="absolute inset-0 h-full w-full object-cover opacity-85 transition-opacity group-hover/play:opacity-70" />}
|
|
143
|
+
<span class="absolute top-1/2 left-1/2 grid size-16 -translate-x-1/2 -translate-y-1/2 place-items-center rounded-full bg-accent text-accent-ink shadow-lg transition-transform group-hover/play:scale-105 group-focus-visible/play:scale-105" aria-hidden="true">
|
|
144
|
+
<svg viewBox="0 0 24 24" class="ms-1 size-7" fill="currentColor"><path d="M7 4.5v15a.5.5 0 0 0 .77.42l11.5-7.5a.5.5 0 0 0 0-.84L7.77 4.08A.5.5 0 0 0 7 4.5Z" /></svg>
|
|
145
|
+
</span>
|
|
146
|
+
</a>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
: slide.video
|
|
150
|
+
? <video class={below.image({ class: 'bg-ink' })} src={safeSrc(slide.video)} poster={safeSrc(slide.image?.src)} controls preload="none" playsinline aria-label={slide.title ? undefined : slide.image?.alt || `${slideLabel} ${index + 1}`} />
|
|
151
|
+
: slide.image && <KitImage image={slide.image} sizes={sizes} priority={index === 0} class={slots.image()} />}
|
|
105
152
|
{(slide.title || slide.text || slide.action) && (
|
|
106
|
-
<div class={slots.caption()}>
|
|
153
|
+
<div class={slide.video || slide.player ? below.caption() : slide.image ? slots.caption() : slots.card()}>
|
|
107
154
|
{slide.title && <p class="text-xl [font-weight:var(--font-weight-heading,600)] text-balance">{slide.title}</p>}
|
|
108
155
|
{slide.text && <p class="opacity-85">{slide.text}</p>}
|
|
109
156
|
{slide.action && <Button action={slide.action} size="sm" class="mt-2" />}
|
|
@@ -113,21 +160,26 @@ const arrow = 'absolute top-1/2 z-10 hidden size-11 -translate-y-1/2 items-cente
|
|
|
113
160
|
))}
|
|
114
161
|
</div>
|
|
115
162
|
</div>
|
|
116
|
-
{slides.length > 1 && (
|
|
117
|
-
|
|
118
|
-
<
|
|
119
|
-
<svg aria-hidden="true" viewBox="0 0 24 24" class="size-5 rtl:-scale-x-100" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6" /></svg>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
163
|
+
{slides.length > 1 && (cards
|
|
164
|
+
? (
|
|
165
|
+
<div class="mt-4 hidden items-center justify-center gap-3 group-data-[ready]:flex">
|
|
166
|
+
<button type="button" class={under} aria-label={prevLabel} aria-controls={trackId} data-prev><svg aria-hidden="true" viewBox="0 0 24 24" class="size-5 rtl:-scale-x-100" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6" /></svg></button>
|
|
167
|
+
<div class="flex justify-center" data-dots></div>
|
|
168
|
+
<button type="button" class={under} aria-label={nextLabel} aria-controls={trackId} data-next><svg aria-hidden="true" viewBox="0 0 24 24" class="size-5 rtl:-scale-x-100" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" /></svg></button>
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
: (
|
|
172
|
+
<>
|
|
173
|
+
<button type="button" class:list={[arrow, 'start-6 md:start-10']} aria-label={prevLabel} aria-controls={trackId} data-prev><svg aria-hidden="true" viewBox="0 0 24 24" class="size-5 rtl:-scale-x-100" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6" /></svg></button>
|
|
174
|
+
<button type="button" class:list={[arrow, 'end-6 md:end-10']} aria-label={nextLabel} aria-controls={trackId} data-next><svg aria-hidden="true" viewBox="0 0 24 24" class="size-5 rtl:-scale-x-100" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6" /></svg></button>
|
|
175
|
+
<div class="mt-4 hidden justify-center group-data-[ready]:flex" data-dots></div>
|
|
176
|
+
</>
|
|
177
|
+
))}
|
|
127
178
|
</Root>
|
|
128
179
|
|
|
129
180
|
<script>
|
|
130
181
|
import EmblaCarousel from 'embla-carousel'
|
|
182
|
+
import { playerFrame, plainClick } from '../_shared/embed-frame'
|
|
131
183
|
|
|
132
184
|
for (const root of document.querySelectorAll<HTMLElement>('[data-kit-slider]')) {
|
|
133
185
|
const viewport = root.querySelector<HTMLElement>('[data-viewport]')
|
|
@@ -178,7 +230,31 @@ const arrow = 'absolute top-1/2 z-10 hidden size-11 -translate-y-1/2 items-cente
|
|
|
178
230
|
if (prev) prev.disabled = !embla.canScrollPrev()
|
|
179
231
|
if (next) next.disabled = !embla.canScrollNext()
|
|
180
232
|
}
|
|
181
|
-
|
|
233
|
+
// A video paged out of view stops: nothing plays where the reader cannot see it.
|
|
234
|
+
const pause = () => { for (const video of root.querySelectorAll('video')) if (!video.paused) video.pause() }
|
|
235
|
+
// Another site's video: its poster link becomes the player on a plain click (a swipe that ends on it is not one:
|
|
236
|
+
// Embla cancels that click), and goes back to its poster when its slide is paged out of view, which stops it and
|
|
237
|
+
// ends the provider's session there.
|
|
238
|
+
const posters = new Map<HTMLIFrameElement, HTMLAnchorElement>()
|
|
239
|
+
viewport.addEventListener('click', (event) => {
|
|
240
|
+
if (!plainClick(event)) return
|
|
241
|
+
const link = (event.target as Element | null)?.closest<HTMLAnchorElement>('[data-slide-player] a[data-embed-src]')
|
|
242
|
+
const frame = link && playerFrame(link.dataset.embedSrc, link.dataset.embedTitle ?? '')
|
|
243
|
+
if (!link || !frame) return
|
|
244
|
+
event.preventDefault()
|
|
245
|
+
posters.set(frame, link)
|
|
246
|
+
link.replaceWith(frame)
|
|
247
|
+
frame.focus()
|
|
248
|
+
})
|
|
249
|
+
const unplay = () => {
|
|
250
|
+
const inView = new Set(embla.slidesInView().map(index => embla.slideNodes()[index]))
|
|
251
|
+
for (const [frame, link] of posters) {
|
|
252
|
+
if (inView.has(frame.closest<HTMLElement>('[aria-roledescription="slide"]') ?? undefined)) continue
|
|
253
|
+
frame.replaceWith(link)
|
|
254
|
+
posters.delete(frame)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
embla.on('select', () => { pause(); sync() }).on('settle', unplay).on('reInit', () => { drawDots(); sync() })
|
|
182
258
|
sync()
|
|
183
259
|
// Set up before the layout was final, Embla measures once more when it is: the page loaded, its fonts in, the tab in
|
|
184
260
|
// view. A background tab lays nothing out (ctrl/cmd-click), so its carousel had one snap and a dead "next" until then;
|
|
@@ -2,5 +2,9 @@
|
|
|
2
2
|
{ "name": "single-wide-overlay", "props": { "label": "Featured work", "slides": [{ "image": { "src": "/kit-fixtures/photo-1.svg", "alt": "Blue gradient", "width": 1200, "height": 800 }, "title": "Northwind Journal", "text": "A WordPress blog moved to Astro in an afternoon.", "action": { "label": "Read the case", "href": "/work/northwind/" } }, { "image": { "src": "/kit-fixtures/photo-2.svg", "alt": "Green gradient", "width": 1200, "height": 800 }, "title": "Harbor & Co" }, { "image": { "src": "/kit-fixtures/photo-3.svg", "alt": "Orange gradient", "width": 1200, "height": 800 }, "title": "Field Notes" }] } },
|
|
3
3
|
{ "name": "two-standard-below", "props": { "label": "Gallery", "perView": "2", "aspect": "standard", "caption": "below", "loop": true, "slides": [{ "image": { "src": "/kit-fixtures/photo-4.svg", "alt": "", "width": 1200, "height": 800 }, "title": "Studio" }, { "image": { "src": "/kit-fixtures/photo-5.svg", "alt": "", "width": 1200, "height": 800 }, "title": "Workshop" }, { "image": { "src": "/kit-fixtures/photo-6.svg", "alt": "", "width": 1200, "height": 800 }, "title": "Garden" }] } },
|
|
4
4
|
{ "name": "three-square", "props": { "label": "Team", "perView": "3", "aspect": "square", "slides": [{ "image": { "src": "/kit-fixtures/avatar-1.svg", "alt": "Mira", "width": 96, "height": 96 } }, { "image": { "src": "/kit-fixtures/avatar-2.svg", "alt": "Jon", "width": 96, "height": 96 } }, { "image": { "src": "/kit-fixtures/avatar-3.svg", "alt": "Lea", "width": 96, "height": 96 } }, { "image": { "src": "/kit-fixtures/photo-1.svg", "alt": "Studio", "width": 1200, "height": 800 } }] } },
|
|
5
|
-
{ "name": "five-measured", "props": { "label": "Five slides", "id": "five", "eyebrow": "Recent work", "heading": "Five projects", "intro": "A few of the sites we moved this year.", "actions": [{ "label": "See all work", "href": "/work/" }], "slides": [{ "image": { "src": "/kit-fixtures/photo-1.svg", "alt": "View 1", "width": 1200, "height": 800 }, "title": "Slide 1 title" }, { "image": { "src": "/kit-fixtures/photo-2.svg", "alt": "View 2", "width": 1200, "height": 800 }, "title": "Slide 2 title" }, { "image": { "src": "/kit-fixtures/photo-3.svg", "alt": "View 3", "width": 1200, "height": 800 }, "title": "Slide 3 title" }, { "image": { "src": "/kit-fixtures/photo-4.svg", "alt": "View 4", "width": 1200, "height": 800 }, "title": "Slide 4 title" }, { "image": { "src": "/kit-fixtures/photo-5.svg", "alt": "View 5", "width": 1200, "height": 800 }, "title": "Slide 5 title" }], "measured": { "padY": 24, "columns": { "390": 1, "768": 2, "1280": 3 }, "gap": 20 } } }
|
|
5
|
+
{ "name": "five-measured", "props": { "label": "Five slides", "id": "five", "eyebrow": "Recent work", "heading": "Five projects", "intro": "A few of the sites we moved this year.", "actions": [{ "label": "See all work", "href": "/work/" }], "slides": [{ "image": { "src": "/kit-fixtures/photo-1.svg", "alt": "View 1", "width": 1200, "height": 800 }, "title": "Slide 1 title" }, { "image": { "src": "/kit-fixtures/photo-2.svg", "alt": "View 2", "width": 1200, "height": 800 }, "title": "Slide 2 title" }, { "image": { "src": "/kit-fixtures/photo-3.svg", "alt": "View 3", "width": 1200, "height": 800 }, "title": "Slide 3 title" }, { "image": { "src": "/kit-fixtures/photo-4.svg", "alt": "View 4", "width": 1200, "height": 800 }, "title": "Slide 4 title" }, { "image": { "src": "/kit-fixtures/photo-5.svg", "alt": "View 5", "width": 1200, "height": 800 }, "title": "Slide 5 title" }], "measured": { "padY": 24, "columns": { "390": 1, "768": 2, "1280": 3 }, "gap": 20 } } },
|
|
6
|
+
{ "name": "text-cards", "props": { "label": "What clients say", "perView": "3", "heading": "Testimonials", "slides": [{ "title": "Ada Lovelace", "text": "They moved our whole site in a week, and nothing broke." }, { "title": "Grace Hopper", "text": "Every page is ours to edit now, without a developer." }, { "title": "Alan Turing", "text": "The new site is faster, and it looks the same.", "ctaLabel": "Read the story", "ctaHref": "/stories/" }] } },
|
|
7
|
+
{ "name": "video-slides", "props": { "label": "Feature tour", "heading": "See it work", "slides": [{ "video": "/kit-fixtures/clip-1.mp4", "image": "/kit-fixtures/photo-1.svg", "imageAlt": "", "title": "Import" }, { "video": "/kit-fixtures/clip-2.mp4", "title": "Edit" }, { "video": "/kit-fixtures/clip-3.mp4", "image": "/kit-fixtures/photo-3.svg", "imageAlt": "", "title": "Publish", "text": "One click, and the site is live." }] } },
|
|
8
|
+
{ "name": "mixed-cards", "props": { "label": "Stories", "slides": [{ "title": "Ada Lovelace", "text": "They moved our whole site in a week, and nothing broke." }, { "image": "/kit-fixtures/photo-2.svg", "imageAlt": "Green gradient", "title": "Harbor & Co" }, { "title": "Grace Hopper", "text": "Every page is ours to edit now, without a developer." }] } },
|
|
9
|
+
{"name": "video-embed", "props": {"label": "Studio videos", "id": "videos", "aspect": "standard", "perView": "2", "slides": [{"embed": "youtube:aqz-KE-bpKQ", "image": {"src": "/kit-fixtures/photo-1.svg", "alt": "Blue gradient", "width": 1200, "height": 800}, "title": "Studio tour"}, {"embed": "https://vimeo.com/76979871", "title": "Behind the scenes"}, {"embed": "youtube:aqz-KE-bpKQ", "image": "https://i.ytimg.com/vi/aqz-KE-bpKQ/hqdefault.jpg", "title": "A provider thumbnail is never loaded"}, {"image": {"src": "/kit-fixtures/photo-3.svg", "alt": "Orange gradient", "width": 1200, "height": 800}, "title": "Field Notes"}]}}
|
|
6
10
|
]
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "slider",
|
|
3
3
|
"name": "Slider",
|
|
4
|
-
"description": "A carousel of images with optional title, text and button per slide. Without JavaScript it is a snapping horizontal scroller; Embla adds paging, arrows and dots.",
|
|
4
|
+
"description": "A carousel of images, videos (a file, or a YouTube/Vimeo link loaded only on click) or text cards, with optional title, text and button per slide. Without JavaScript it is a snapping horizontal scroller; Embla adds paging, arrows and dots.",
|
|
5
5
|
"category": "section",
|
|
6
6
|
"uses": [],
|
|
7
|
-
"shared": ["Button.astro", "KitImage.astro", "Section.astro", "SectionHeader.astro", "image.ts", "measured.ts", "safe-url.ts", "types.ts"],
|
|
7
|
+
"shared": ["Button.astro", "KitImage.astro", "Section.astro", "SectionHeader.astro", "embed-frame.ts", "embed-target.ts", "image.ts", "measured.ts", "safe-url.ts", "types.ts"],
|
|
8
8
|
"variants": {
|
|
9
9
|
"perView": { "options": ["1", "2", "3"], "default": "1", "description": "Slides visible at once on wide screens." },
|
|
10
10
|
"aspect": { "options": ["wide", "standard", "square"], "default": "wide", "description": "Image aspect ratio: 21:9, 3:2 or 1:1." },
|
|
11
11
|
"caption": { "options": ["below", "overlay"], "default": "overlay", "description": "Caption under the image, or over its lower edge on a gradient." }
|
|
12
12
|
},
|
|
13
13
|
"props": {
|
|
14
|
-
"slides": { "type": "array", "label": "Slides", "required": true, "items": { "type": "object", "fields": { "image": { "type": "image", "label": "Image", "
|
|
14
|
+
"slides": { "type": "array", "label": "Slides", "required": true, "items": { "type": "object", "fields": { "image": { "type": "image", "label": "Image", "description": "The slide image; a slide without one is a text card (a quote, a testimonial)." }, "imageAlt": { "type": "string", "label": "Image description", "description": "What the image shows, for readers who cannot see it." }, "video": { "type": "video", "label": "Video", "description": "A video file (mp4, webm) shown in place of the image, which is its poster. It plays only when the reader starts it." }, "embed": { "type": "url", "label": "Video link", "description": "A video on another site: its YouTube, Vimeo, Loom or Wistia page (or youtube:<id> / vimeo:<id>). Shown as the image with a play button; nothing from that site loads until the reader clicks it. Wins over Video." }, "title": { "type": "string", "label": "Title", "description": "Heading over the slide." }, "text": { "type": "text", "label": "Text", "description": "A line or two under the heading." }, "ctaLabel": { "type": "string", "label": "Button label", "description": "Button text; the slide has no button without it." }, "ctaHref": { "type": "url", "label": "Button link", "description": "Where the button goes." } } }, "description": "Slides: the image's address and text, an optional video file or video link (the image is its poster), and an optional link (ctaLabel + ctaHref). A slide with no image or video is a text card; one with none of them is not drawn." },
|
|
15
15
|
"label": { "type": "string", "required": true, "content": false, "description": "Accessible name of the carousel." },
|
|
16
16
|
"prevLabel": { "type": "string", "content": false, "description": "ui-strings slider.prev." },
|
|
17
17
|
"nextLabel": { "type": "string", "content": false, "description": "ui-strings slider.next." },
|
|
18
18
|
"slideLabel": { "type": "string", "content": false, "description": "ui-strings slider.slide." },
|
|
19
19
|
"ofLabel": { "type": "string", "content": false, "description": "ui-strings slider.of." },
|
|
20
|
+
"playLabel": { "type": "string", "content": false, "description": "ui-strings embed.play." },
|
|
20
21
|
"loop": { "type": "boolean", "default": false, "content": false, "description": "Wrap from the last slide to the first." },
|
|
21
22
|
"id": { "type": "string", "default": "slider", "content": false, "description": "Unique on the page: names the track that the previous/next buttons control (aria-controls)." },
|
|
22
23
|
"heading": {"type": "string", "label": "Heading", "description": "The section heading."},
|
|
@@ -34,5 +35,5 @@
|
|
|
34
35
|
"divi": ["divi/et_pb_slider", "divi/et_pb_fullwidth_slider", "divi/et_pb_post_slider"],
|
|
35
36
|
"classic": ["classic/revslider", "classic/metaslider", "classic/smart-slider", "classic/swiper", "classic/slick", "classic/splide", "classic/owl"]
|
|
36
37
|
},
|
|
37
|
-
"a11y": "WAI-ARIA carousel pattern: a labelled region with aria-roledescription=carousel, slides as labelled groups ('Slide 2 of 5'), labelled previous/next buttons disabled at the ends, and dot buttons marking the current slide. The track and the buttons are tied by aria-controls, the row takes focus (arrow keys page it, polite live announcement). There is no autoplay, so nothing moves unasked; with prefers-reduced-motion slides change without animation. Without JavaScript every slide is reachable by scrolling."
|
|
38
|
+
"a11y": "WAI-ARIA carousel pattern: a labelled region with aria-roledescription=carousel, slides as labelled groups ('Slide 2 of 5'), labelled previous/next buttons disabled at the ends, and dot buttons marking the current slide. The track and the buttons are tied by aria-controls, the row takes focus (arrow keys page it, polite live announcement). There is no autoplay, so nothing moves unasked: a video slide loads nothing until the reader plays it, never starts by itself, keeps its native controls with its caption under them, and pauses when its slide is paged away; a video link is a labelled poster link ('Play: title') to the video's page that becomes the player only on a click (no request to the provider before it; YouTube from youtube-nocookie.com, Vimeo with dnt), keeps the poster's box so nothing moves, takes focus, and goes back to its poster when its slide is paged away; with prefers-reduced-motion slides change without animation. Without JavaScript every slide is reachable by scrolling."
|
|
38
39
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
// A row of figures, each with a short label, under an optional heading and
|
|
3
|
-
// intro. Figures are
|
|
3
|
+
// intro. Figures are text: the final number is in the HTML. `motion: 'count'` (a source counter that counted up) makes
|
|
4
|
+
// the list a count-up behaviour island: once in view each figure counts from 0 to it, never under reduced motion or
|
|
5
|
+
// without IntersectionObserver, and only the figure's own digits change.
|
|
4
6
|
import { tv } from 'tailwind-variants'
|
|
7
|
+
import BehaviorScript from '../_shared/BehaviorScript.astro'
|
|
5
8
|
import KitImage from '../_shared/KitImage.astro'
|
|
6
9
|
import Section, { type Tone } from '../_shared/Section.astro'
|
|
7
10
|
import { imageOf, type ImageSource } from '../_shared/image'
|
|
@@ -84,13 +87,18 @@ interface Props {
|
|
|
84
87
|
valueSize?: 'md' | 'lg' | 'xl' | 'display' | undefined
|
|
85
88
|
labelSize?: 'sm' | 'base' | 'lg' | undefined
|
|
86
89
|
order?: 'label-first' | 'value-first' | undefined
|
|
90
|
+
/** static: the figures as printed. count: they count up once in view, as the source's counter did. */
|
|
91
|
+
motion?: 'static' | 'count' | undefined
|
|
87
92
|
tone?: Tone | undefined
|
|
88
93
|
/** The source section's measured style (`MeasuredStyle`): frame, padding, fill, background image, columns per width, gap. Absent, the site's tokens; present, it wins over tone and columns. */
|
|
89
94
|
measured?: MeasuredStyle | undefined
|
|
90
95
|
id?: string | undefined
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
const { heading, intro, items, layout, columns, style, align, valueSize, labelSize, order, tone, measured, id } = Astro.props
|
|
98
|
+
const { heading, intro, items, layout, columns, style, align, valueSize, labelSize, order, motion, tone, measured, id } = Astro.props
|
|
99
|
+
const count = motion === 'count'
|
|
100
|
+
// A count-up island (behaviors.ts `countup`): the root names the kind, each figure's digits are their own span.
|
|
101
|
+
const island = count ? { 'data-cr-behavior': 'countup', 'data-cr-items': '[data-kit-count]' } : {}
|
|
94
102
|
const s = stats({ layout, columns, style, align, valueSize, labelSize, order, tone })
|
|
95
103
|
const grid = (layout ?? 'grid') === 'grid' ? measuredGrid(measured) : undefined
|
|
96
104
|
const headingId = heading && id ? `${id}-title` : undefined
|
|
@@ -107,22 +115,23 @@ const widthOf = (item: Item) => `--stat-chars:${(item.prefix ?? '').length + ite
|
|
|
107
115
|
</header>
|
|
108
116
|
)}
|
|
109
117
|
{order === 'value-first' ? (
|
|
110
|
-
<ul class={s.list({ class: grid })} role="list">
|
|
118
|
+
<ul class={s.list({ class: grid })} role="list" {...island}>
|
|
111
119
|
{items.map(item => (
|
|
112
120
|
<li class={s.item()}>
|
|
113
|
-
<p class={s.value()} style={widthOf(item)}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{item.value}{item.suffix}</p>
|
|
121
|
+
<p class={s.value()} style={widthOf(item)}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{count ? <span data-kit-count>{item.value}</span> : item.value}{item.suffix}</p>
|
|
114
122
|
<p class={s.label()}>{item.label}</p>
|
|
115
123
|
</li>
|
|
116
124
|
))}
|
|
117
125
|
</ul>
|
|
118
126
|
) : (
|
|
119
|
-
<dl class={s.list({ class: grid })}>
|
|
127
|
+
<dl class={s.list({ class: grid })} {...island}>
|
|
120
128
|
{items.map(item => (
|
|
121
129
|
<div class={s.item()}>
|
|
122
130
|
<dt class={s.label()}>{item.label}</dt>
|
|
123
|
-
<dd class={s.value()} style={widthOf(item)}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{item.value}{item.suffix}</dd>
|
|
131
|
+
<dd class={s.value()} style={widthOf(item)}>{iconOf(item) && <KitImage image={iconOf(item)!} sizes="40px" class={s.icon()} />}{item.prefix}{count ? <span data-kit-count>{item.value}</span> : item.value}{item.suffix}</dd>
|
|
124
132
|
</div>
|
|
125
133
|
))}
|
|
126
134
|
</dl>
|
|
127
135
|
)}
|
|
136
|
+
{count && <BehaviorScript />}
|
|
128
137
|
</Section>
|
|
@@ -416,5 +416,30 @@
|
|
|
416
416
|
"valueSize": "display",
|
|
417
417
|
"align": "center"
|
|
418
418
|
}
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"name": "count-label-lg",
|
|
422
|
+
"props": {
|
|
423
|
+
"motion": "count",
|
|
424
|
+
"labelSize": "lg",
|
|
425
|
+
"heading": "Since 2019",
|
|
426
|
+
"items": [
|
|
427
|
+
{
|
|
428
|
+
"value": "1,200",
|
|
429
|
+
"prefix": "$",
|
|
430
|
+
"suffix": "+",
|
|
431
|
+
"label": "Shops launched"
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"value": "99.9",
|
|
435
|
+
"suffix": "%",
|
|
436
|
+
"label": "Uptime"
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"value": "11",
|
|
440
|
+
"label": "Countries shipped to"
|
|
441
|
+
}
|
|
442
|
+
]
|
|
443
|
+
}
|
|
419
444
|
}
|
|
420
445
|
]
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "stats",
|
|
3
3
|
"name": "Stats",
|
|
4
|
-
"description": "A row of figures with a short label each (years in business, clients, uptime), with an optional heading and intro. The counter/number section of a landing page; numbers are
|
|
4
|
+
"description": "A row of figures with a short label each (years in business, clients, uptime), with an optional heading and intro. The counter/number section of a landing page; numbers are text (the final value in the HTML); motion \"count\" counts them up once in view, as a source counter did.",
|
|
5
5
|
"category": "section",
|
|
6
6
|
"uses": [],
|
|
7
7
|
"shared": [
|
|
8
|
+
"BehaviorScript.astro",
|
|
8
9
|
"KitImage.astro",
|
|
9
10
|
"Section.astro",
|
|
11
|
+
"behaviors.ts",
|
|
12
|
+
"embed-frame.ts",
|
|
13
|
+
"embed-target.ts",
|
|
10
14
|
"image.ts",
|
|
11
15
|
"measured.ts",
|
|
12
16
|
"safe-url.ts",
|
|
@@ -85,6 +89,14 @@
|
|
|
85
89
|
],
|
|
86
90
|
"default": "label-first",
|
|
87
91
|
"description": "Which the page's text reads first, as the source does: label-first (an Elementor counter) or value-first (a Gutenberg figure heading over its label). The figure is drawn on top either way."
|
|
92
|
+
},
|
|
93
|
+
"motion": {
|
|
94
|
+
"options": [
|
|
95
|
+
"static",
|
|
96
|
+
"count"
|
|
97
|
+
],
|
|
98
|
+
"default": "static",
|
|
99
|
+
"description": "static: the figures as printed. count: each counts up from 0 to its value once in view (a source counter that animated); the final number is in the HTML, nothing moves under reduced motion or without IntersectionObserver."
|
|
88
100
|
}
|
|
89
101
|
},
|
|
90
102
|
"props": {
|
|
@@ -253,7 +265,7 @@
|
|
|
253
265
|
}
|
|
254
266
|
},
|
|
255
267
|
"slots": {},
|
|
256
|
-
"js": "
|
|
268
|
+
"js": "vanilla",
|
|
257
269
|
"dependencies": {},
|
|
258
270
|
"sources": {
|
|
259
271
|
"gutenberg": [
|
package/mapping/classic.json
CHANGED
|
@@ -102,7 +102,8 @@
|
|
|
102
102
|
"prevLabel": "ui:slider.prev",
|
|
103
103
|
"nextLabel": "ui:slider.next",
|
|
104
104
|
"slideLabel": "ui:slider.slide",
|
|
105
|
-
"ofLabel": "ui:slider.of"
|
|
105
|
+
"ofLabel": "ui:slider.of",
|
|
106
|
+
"playLabel": "ui:embed.play"
|
|
106
107
|
},
|
|
107
108
|
"into": "slides",
|
|
108
109
|
"each": "rs-slide, .tp-revslider-slidesli",
|
|
@@ -120,7 +121,8 @@
|
|
|
120
121
|
"prevLabel": "ui:slider.prev",
|
|
121
122
|
"nextLabel": "ui:slider.next",
|
|
122
123
|
"slideLabel": "ui:slider.slide",
|
|
123
|
-
"ofLabel": "ui:slider.of"
|
|
124
|
+
"ofLabel": "ui:slider.of",
|
|
125
|
+
"playLabel": "ui:embed.play"
|
|
124
126
|
},
|
|
125
127
|
"into": "slides",
|
|
126
128
|
"each": ".slides > li",
|
|
@@ -138,7 +140,8 @@
|
|
|
138
140
|
"prevLabel": "ui:slider.prev",
|
|
139
141
|
"nextLabel": "ui:slider.next",
|
|
140
142
|
"slideLabel": "ui:slider.slide",
|
|
141
|
-
"ofLabel": "ui:slider.of"
|
|
143
|
+
"ofLabel": "ui:slider.of",
|
|
144
|
+
"playLabel": "ui:embed.play"
|
|
142
145
|
},
|
|
143
146
|
"into": "slides",
|
|
144
147
|
"each": ".n2-ss-slide",
|
|
@@ -431,7 +434,8 @@
|
|
|
431
434
|
"prevLabel": "ui:slider.prev",
|
|
432
435
|
"nextLabel": "ui:slider.next",
|
|
433
436
|
"slideLabel": "ui:slider.slide",
|
|
434
|
-
"ofLabel": "ui:slider.of"
|
|
437
|
+
"ofLabel": "ui:slider.of",
|
|
438
|
+
"playLabel": "ui:embed.play"
|
|
435
439
|
},
|
|
436
440
|
"into": "slides",
|
|
437
441
|
"each": ".swiper-slide:not(.swiper-slide-duplicate)",
|
|
@@ -450,7 +454,8 @@
|
|
|
450
454
|
"prevLabel": "ui:slider.prev",
|
|
451
455
|
"nextLabel": "ui:slider.next",
|
|
452
456
|
"slideLabel": "ui:slider.slide",
|
|
453
|
-
"ofLabel": "ui:slider.of"
|
|
457
|
+
"ofLabel": "ui:slider.of",
|
|
458
|
+
"playLabel": "ui:embed.play"
|
|
454
459
|
},
|
|
455
460
|
"into": "slides",
|
|
456
461
|
"each": ".slick-slide:not(.slick-cloned)",
|
|
@@ -469,7 +474,8 @@
|
|
|
469
474
|
"prevLabel": "ui:slider.prev",
|
|
470
475
|
"nextLabel": "ui:slider.next",
|
|
471
476
|
"slideLabel": "ui:slider.slide",
|
|
472
|
-
"ofLabel": "ui:slider.of"
|
|
477
|
+
"ofLabel": "ui:slider.of",
|
|
478
|
+
"playLabel": "ui:embed.play"
|
|
473
479
|
},
|
|
474
480
|
"into": "slides",
|
|
475
481
|
"each": ".splide__slide:not(.splide__slide--clone)",
|
|
@@ -488,7 +494,8 @@
|
|
|
488
494
|
"prevLabel": "ui:slider.prev",
|
|
489
495
|
"nextLabel": "ui:slider.next",
|
|
490
496
|
"slideLabel": "ui:slider.slide",
|
|
491
|
-
"ofLabel": "ui:slider.of"
|
|
497
|
+
"ofLabel": "ui:slider.of",
|
|
498
|
+
"playLabel": "ui:embed.play"
|
|
492
499
|
},
|
|
493
500
|
"into": "slides",
|
|
494
501
|
"each": ".owl-item:not(.cloned)",
|
package/mapping/divi.json
CHANGED
|
@@ -124,7 +124,8 @@
|
|
|
124
124
|
"prevLabel": "ui:slider.prev",
|
|
125
125
|
"nextLabel": "ui:slider.next",
|
|
126
126
|
"slideLabel": "ui:slider.slide",
|
|
127
|
-
"ofLabel": "ui:slider.of"
|
|
127
|
+
"ofLabel": "ui:slider.of",
|
|
128
|
+
"playLabel": "ui:embed.play"
|
|
128
129
|
},
|
|
129
130
|
"into": "slides",
|
|
130
131
|
"each": ".et_pb_slide",
|
|
@@ -145,7 +146,8 @@
|
|
|
145
146
|
"prevLabel": "ui:slider.prev",
|
|
146
147
|
"nextLabel": "ui:slider.next",
|
|
147
148
|
"slideLabel": "ui:slider.slide",
|
|
148
|
-
"ofLabel": "ui:slider.of"
|
|
149
|
+
"ofLabel": "ui:slider.of",
|
|
150
|
+
"playLabel": "ui:embed.play"
|
|
149
151
|
},
|
|
150
152
|
"into": "slides",
|
|
151
153
|
"each": ".et_pb_slide",
|
|
@@ -167,7 +169,8 @@
|
|
|
167
169
|
"prevLabel": "ui:slider.prev",
|
|
168
170
|
"nextLabel": "ui:slider.next",
|
|
169
171
|
"slideLabel": "ui:slider.slide",
|
|
170
|
-
"ofLabel": "ui:slider.of"
|
|
172
|
+
"ofLabel": "ui:slider.of",
|
|
173
|
+
"playLabel": "ui:embed.play"
|
|
171
174
|
},
|
|
172
175
|
"into": "slides",
|
|
173
176
|
"item": {
|
|
@@ -423,7 +426,8 @@
|
|
|
423
426
|
"id": "stats.counter",
|
|
424
427
|
"component": "stats",
|
|
425
428
|
"variant": {
|
|
426
|
-
"order": "value-first"
|
|
429
|
+
"order": "value-first",
|
|
430
|
+
"motion": "count"
|
|
427
431
|
},
|
|
428
432
|
"slots": {
|
|
429
433
|
"intro": {
|