Appearance
SliderV2 — Implementation Progress & Status
Branch: add-slider-v2 (both Application-Frontend and Creative-Engine repos) Last updated: 2026-03-10
Overall Status
| Phase | Status |
|---|---|
| Phase 1: Core engine | COMPLETE |
| Phase 2 Round 1: Styling controls | COMPLETE |
| Phase 2 Round 2: Indicators (dots, counter, progress) | COMPLETE |
| Phase 2 Polish: Feedback fixes | COMPLETE |
| Video in Slider | CODE COMPLETE — awaiting BunnyCDN keys |
| Smart Video Autoplay | COMPLETE |
| Thumbnail Navigation | COMPLETE |
| Template Presets | COMPLETE (4 templates, visual polish needed) |
| Phase 3: Bug fixes (A1-A13) | COMPLETE |
| Phase 3: New features (B1-B3) | COMPLETE |
| Phase 4: New transitions + config reorg | COMPLETE |
Phase 1 — Core Engine (COMPLETE)
What was built:
CreativeSliderBlockV2.vue— full slider runtime component (~2080 lines) with multiple transition modes, navigation, autoplay, tease, feed support, and per-slide videoSliderV2Configuration.vue— config panel (~1500 lines) for all slider settings, organized into 4 tabs- Block registration in Application-Frontend: types (
SliderV2Properties), defaults (sliderV2Defaults), and block registry SliderV2Propertiestype definition with ~90+ typed properties covering layout, navigation, indicators, behavior, feed, and standard block properties
Key features:
- Scroll-snap based navigation with native rubber-band feel on mobile
- Fade and crossfade transitions with opacity-based slide switching
- Sub-block architecture: each slide can contain text, graphic, button, and HTML blocks as children
- Per-slide overrides: each slide entry in the
slidesarray can override any sub-block property - Feed support via proxy URL with filter rules, custom params, sort key/order, and live override from flow operators
- Flow operator support: ShowHide and TargetOpSelector recognize the
sl2prefix - Keyboard navigation (arrow keys) with focus management
- Touch/swipe support for all JS-navigated modes (fade, slide, cube, carousel)
- Slide click-through: each slide can have a URL that opens on click, with feed data interpolation
- Lazy rendering via
canShowSlide()— only renders slides within a 3-slide buffer in scroll/fade modes - Editing sync: listens for
slider-editing-slideCustomEvent from the builder to preview specific slides and pause autoplay - Analytics: swipe events sent via debounced
sendSwipeAnalytics(), first-event tracking on wrap click
Phase 2 — Styling & UI (COMPLETE)
Round 1: Styling Controls
Arrow styling:
- Style options: Default SVG chevron / Custom image upload
- Custom image modes: Single (right arrow mirrored via
scaleX(-1)for left) / Separate (individual uploads for left and right) - Size: 16-80px (default 32px), configurable via slider
- Color: any color with alpha (default
#55555599), only shown for default SVG style - Background: any color with alpha (default
rgba(255,255,255,0.7)) - Border-radius: configurable (default
50%= circle) - Position: Inside / Outside / Free
- Inside: offset from track edges by
arrowOffsetpx - Outside: positioned outside track by
-(arrowSize + arrowOffset)px - Free: full 4-sided position inputs (
arrowFreeLeftPosition,arrowFreeRightPosition) with smarttranslateY(-50%)centering that detects whether top or bottom is active
- Inside: offset from track edges by
- Offset X and Offset Y controls for inside/outside modes
- SVG scales with button size (60% of container)
- Arrow z-index: 10 (above dots/thumbnails at z-index 2)
Dot styling:
- 2 shapes: Dot (1:1 aspect ratio, round by default) and Line (active dot = 3x wider bar)
- Shape change auto-sets border-radius: dot ->
50%, line ->2px BorderRadiusSectionfor custom rounding of both shapes- Color: inactive color + active color, both with alpha support
- Size: 4-24px (default 8px)
- Gap: 0-20px (default 6px)
- Position: Overlay (absolute at bottom) / Above (absolute at -24px top) / Below (absolute at -24px bottom) / Free (full position input with z-index 3)
- Active line dot border-radius clamped to
min(dotBorderRadius, dotSize/2)pxto prevent overflow
Layout:
- Flex alignment section: horizontal + vertical alignment of the block itself
- Size section: width and height with unit support
- Position section: margin/offset for all 4 sides
- Padding section: slide padding (inner padding within each slide), 4-sided with px/% units
Effects:
- Edge fade: CSS mask gradient (5% transparent edges) — disabled for fade/cube/carousel modes
- Box shadow: full shadow settings (x, y, blur, spread, color, inset)
- Rotation: block rotation in degrees
Round 2: Indicators
Slide counter (1/N):
- Toggle on/off
- Placement: Overlay (positioned over the slider) or Per-Slide (inside each slide, top-right)
- Color: configurable (default
#ffffff) - Font size: 8-32px (default 14px)
- Position (overlay mode): 4 corners (top-left, top-right, bottom-left, bottom-right) + free position
- Text shadow for readability:
0 1px 3px rgba(0,0,0,0.3)
Progress bar:
- Toggle on/off
- Color: configurable (default
#ffffff) - Height: 1-12px (default 3px)
- Position: Top / Bottom / Free
- Fill width:
(activeIndex + 1) / totalSlides * 100%for slides, orvideoProgress * 100%for video slides - Smooth width transition (300ms ease) for slides, no transition for video (real-time)
- Background track:
rgba(255,255,255,0.2)
Autoplay:
- Toggle on/off
- Interval: 1-60 seconds (default 5s, step 0.5s)
- Loop video toggle (only visible when autoplay is on)
- Loop toggle (independent of autoplay, always visible)
Polish (completed feedback fixes)
- 4-tab config organization: Style | Navigation | Behavior | Slides
- Numeric input
??fallbacks instead of||to correctly handle zero values - Slide pagination click fix for navigating between slides in the editor
- New slides clone the last slide via
cloneDeep(preserving sub-block overrides) - Arrow SVG scales with button size (60% width/height)
- All labels shortened and improved for clarity
Video in Slider (CODE COMPLETE)
Design: Per-slide video field. Each slide object can have optional videoStreamId, videoFileName, and videoUrl fields.
Video URL resolution (priority order):
slide.videoUrl— direct URLslide.videoStreamId— BunnyCDN delivery URL:https://delivery-6.cavai.com/{streamId}/original- Feed data
videoUrl— from feed data, interpolated with feed variables
Engine: <video> element rendered per slide, absolute positioned over the slide content, object-fit: cover, muted, playsinline, z-index 1. When a video is playing on the active slide, all sub-blocks within that slide are hidden (v-if guard on video existence + active state).
Playback control:
- Active slide auto-plays video (resets
currentTime = 0on activation) - Inactive slide video is paused via
el.pause() - Video progress tracked via
onVideoTimeUpdate— updatesvideoProgressfor the progress bar - On
onVideoEnded: ifloopVideois off, advances to next slide (treats as autoplay-driven advance)
Smart Video Autoplay interaction matrix:
| Autoplay | Loop video | Behavior |
|---|---|---|
| Off | - | Video loops indefinitely, manual navigation only |
| On | Off | Video plays to completion, then auto-advances to next slide |
| On | On | Video loops indefinitely on its slide, autoplay timer skips it |
Key implementation detail: The autoplay timer (setInterval) checks activeSlideHasVideo each tick — if the active slide has a video, the tick is skipped entirely. Video-driven advancement happens via the @ended event handler instead.
Thumbnail Navigation (COMPLETE)
Core features:
- Scrollable thumbnail strip rendered as a row of
<button>elements - Each thumbnail uses
background-imagefrom the slide's graphic block image - Active thumbnail has distinct border color and full opacity (inactive: 0.6 opacity)
- Auto-scroll: active thumbnail scrolled into center of strip via
container.scrollTo({ left, behavior: 'smooth' }) - Click on thumbnail navigates to that slide
Source block detection:
- Auto mode (default): iterates all graphic sub-blocks, returns first one with an actual image URL
- Manual override:
thumbnailSourceBlockcan specify a specific graphic block name - Fallback: uses feed data
imageUrlif no graphic block image found
Config options (16 total):
thumbnailAlignment: left / center / right (maps toflex-start/center/flex-endviajustifyContent)thumbnailSourceBlock: auto / specific graphic block namethumbnailSize: 24-96px (default 48px) — sets both width and heightthumbnailGap: 0-16px (default 4px)thumbnailFit: cover / contain (background-size on each thumbnail)thumbnailMargin: 0-32px (default 0) — horizontal padding on the stripthumbnailPadding: 0-16px (default 0) — inner padding within each thumbnail (usesbackground-origin: content-box)thumbnailBorderRadius: configurable (default4px)thumbnailBgColor: background color behind the image (defaulttransparent)thumbnailBorderColor: border color (defaulttransparent)thumbnailActiveColor: border color for active thumbnail (default#ffffff)thumbnailBorderWidth: 0-8px (default 2px)thumbnailPosition: Overlay / Above / Below / Free (same position system as dots)
Scrollbar: Hidden via scrollbarWidth: none, -webkit-scrollbar: none, and msOverflowStyle: none
Template Presets (COMPLETE — needs visual polish)
Infrastructure:
SliderV2Templateenum intypes.tswith 4 values- Template functions in
src/templates/sliderV2/— each returns a partialSliderV2Propertiesobject updateSliderV2Template()insrc/templates/index.ts— merges template data while preserving identity fields (order,parent,blockName,blockType,displayName,hidden), content fields (slides,feed*), and structural fields (allowedSubBlocks, etc.)TemplateButtoncomponent in the Style tab of the config paneltemplateModifiedtracking referenced in confirm dialog but not yet implemented
1. Product Carousel
- Purpose: E-commerce product browsing
- Transition: Scroll (native snap)
- Slide width: 80% (peek effect showing adjacent slides)
- Gap: 8px between slides
- Navigation: Arrows (inside, small 28px, white bg with 85% opacity), dots (below, small 7px, dark colors
#444444) - Behavior: Loop on, tease on (0.5s delay, 0.8s duration, 1 iteration), no autoplay
- Style: Light background (
#F5F5F5), no border radius, 100% width x 90% height
2. Image Gallery
- Purpose: Photography/image showcase with thumbnails
- Transition: Crossfade (overlapping opacity transition)
- Slide width: 100%
- Navigation: Arrows (inside, 30px, dark bg
rgba(0,0,0,0.3), white arrows), slide counter (top-right, 12px, white), thumbnails (below, 44px, 4px gap, 2px padding, cover fit, white active border) - Behavior: Loop on, no autoplay, no tease
- Style: Dark background (
#1A1A1A), no border radius, 100% width x 90% height
3. Testimonial
- Purpose: Quote/review carousel with autoplay
- Transition: Fade (opacity with brief blackout delay)
- Slide width: 100%
- Navigation: Arrows (inside, small 24px, subtle bg
rgba(255,255,255,0.1), white arrows), dots (below, line shape 20px,rgba(255,255,255,0.25)/#ffffffCC) - Behavior: Autoplay on (6s interval), loop on, no tease
- Style: Dark blue-grey background (
#2B2D42), 12px border radius, 90% width x 85% height, 12px slide padding all sides
4. Story
- Purpose: Instagram-stories style full-screen content
- Transition: Scroll (fast 250ms)
- Slide width: 100%
- Navigation: No arrows, no dots, no counter, no thumbnails. Only progress bar (top, 2px, white
#ffffffCC) - Behavior: Autoplay on (4s interval), loop on, no tease
- Style: Near-black background (
#111111), no border radius, 100% width x 100% height
Phase 3 — Bug Fixes + Features (COMPLETE)
Bug Fixes (A1-A13)
| ID | Issue | Fix |
|---|---|---|
| A1 | Thumbnail alignment | justifyContent maps left/center/right to flex-start/center/flex-end |
| A2 | Thumbnail graphic block selection | Smart auto-search through all graphic blocks + manual override via thumbnailSourceBlock |
| A3 | Cube scaling | Perspective set to container size; compensating translateZ(-halfSize) so front face lands at Z=0 with scale=1.0 |
| A4 | Cube loop backward direction | Proper forward/backward distance calculation using (target - active + total) % total vs (active - target + total) % total |
| A5 | Tease for all modes | Per-mode implementations: scroll (scrollLeft wiggle), slide (translateX offset), fade (opacity flash to 0.3), carousel (viewport shift -5%), cube (rotation wobble 15deg) |
| A6 | Counter card padding | Proper padding values on counter overlay |
| A7 | Overlay 50% centering | translateX(-50%) and translateY(-50%) applied in resolveOverlayPosition() when left/top are set |
| A8 | Thumbnail transparent colors | Use #ffffff00 instead of 'transparent' for consistent color picker behavior |
| A9 | Center slides -> "Peek" | Renamed, uses scroll-snap-align: center + scroll-padding: 0 ${(100-slideWidth)/2}% |
| A10 | Timer dots | Line shape (HTML div width animation) + dot shape (SVG circle arc), rAF-based progress animation |
| A11 | Free-position z-index | z-index 3 for all free-positioned overlays via resolveOverlayPosition() |
| A12 | Feed section title | Proper title for the feed configuration card |
| A13 | Scroll vs Slide distinction | Scroll = native CSS scroll-snap; Slide = JS-driven translateX with CSS transition |
New Features (B1-B3)
| ID | Feature | Implementation |
|---|---|---|
| B1 | Per-slide counter | slideCounterPlacement: 'per-slide' renders {i+1}/{total} inside each slide (top-right, absolute positioned) |
| B2 | Slide editing preview sync | Builder dispatches slider-editing-slide CustomEvent with editingSlideIndex; engine pauses autoplay/tease and navigates to that slide |
| B3 | 3D Perspective Carousel | New carousel transition mode with depth-based positioning, scaling, rotation, and opacity |
Phase 4 — New Transitions + Carousel Improvements (COMPLETE)
New Transition Modes (COMPLETE)
6 transition modes total, presented in a 3+3 grid in the config panel:
| Mode | Type | Description |
|---|---|---|
| Scroll | Native CSS | CSS scroll-snap with rubber-band feel on mobile. flex container, scroll-snap-type: x mandatory, scroll-snap-align: start (or center for peek). Smooth scrollTo() for programmatic navigation. |
| Slide | JS-driven | track-inner wrapper with translateX(calc(-index * (slideWidth% + gap px))). CSS transition: transform {duration}ms cubic-bezier(0.25, 0.1, 0.25, 1). Supports gap between slides. |
| Fade | JS-driven | All slides absolute positioned, opacity 0. Active slide opacity 1. Non-crossfade: transitionDelay on active slide (40% of duration) creates brief blackout between slides. |
| Crossfade | JS-driven | Same as fade but without delay — slides cross-fade simultaneously, no blackout gap. Full transitionDuration on both in and out. |
| Cube | 3D CSS | CSS 3D cube rotation with perspective, preserve-3d, backfaceVisibility: hidden. Only 2 faces rendered at once (active + target). |
| Carousel | 3D perspective | Perspective-based card stack with depth layering. All slides rendered simultaneously with computed transform, opacity, and filter. Two layout modes: Stack and Ring. |
Cube Transition — How It Works
Math:
- Perspective set to
containerSize(1x container width or height) - Scene pulled back by
translateZ(-halfSize)so the active face (attranslateZ(+halfSize)) lands at Z=0 - This gives
scale = perspective / (perspective - 0) = 1.0— no magnification on the front face
Navigation flow:
navigateCube(index)setscubeTargetIndex, enablescubeAnimating(CSS transition)cubeRotationincrements by +/-90 degrees depending on direction- After
transitionDurationms, animation state resets:cubeAnimating = false,activeIndex = index,cubeRotation = 0 - Only 2 slides rendered via
cubeFaceIndices: active + target
Face positioning:
- Active face:
transform: translateZ(halfSize px)— sits at scene origin - Target face:
rotate{axis}(+-90deg) translateZ(halfSize px)— positioned on the adjacent cube face - Direction determined by shortest-path calculation:
forwardDist = (target - active + total) % totalvsbackwardDist
Vertical mode:
- Uses
rotateXinstead ofrotateY - Sign flip (
isVertical ? 1 : -1) for correct rotation direction
Background:
- Cube scene element receives
backgroundPropertiesfrom the block to prevent parent background from showing through during rotation (flash fix)
Carousel Transition — How It Works
Two layout modes: Stack and Ring.
Stack Layout (default)
Linear depth layering with perspective transform.
Positioning:
- Each slide gets
width: slideWidth%,left: centerOffset%(wherecenterOffset = (100 - slideWidth) / 2) - Active slide:
translateX(0) scale(1) rotateY(0deg), opacity 1, zIndex 10 - Adjacent slides: offset by
spacing * distance %, scaled byMath.pow(scale, distance). Distance-1 slides (directly beside active) are fully opaque; distance-2+ fade byMath.pow(opacity, distance) - Up to 5 visible slides (2 per side via
carouselVisibleIndices), slides beyondmaxVisiblegetopacity: 0 - Z-index decreases with distance:
10 - distance * 2 - Rotation applied per distance:
rotSign * (isPrev ? rotation : -rotation) * distance
Config options:
- Scale: 0.3-1.0 (default 0.8) — how much each depth level shrinks
- Rotation: 0-30 degrees (default 5) — Y-axis tilt per depth level
- Opacity: 0-1.0 (default 0.6) — how much each depth level fades
- Blur: 0-10px (default 0) — Gaussian blur per depth level
- Spacing: 10-80% (default 40) — horizontal offset between depth levels
- Perspective: 200-3000px (default 1200) — only for stack layout
Ring Layout (NEW)
Circular distribution with JS-driven animation instead of CSS transitions.
Animation mechanism:
ringAnglestate tracks continuous, unbounded rotation angle in degrees (not clamped to 360)animateRing()method usesrequestAnimationFramewith ease-out cubic interpolation- Animates from current
ringAngleto target angle overtransitionDurationms - All ring slides have
transition: 'none'— JS controls the animation
Math:
angleStep = (2 * PI) / totalSlides- Slide angle:
(slideIndex * angleStep - ringAngle * π/180)— continuous, no wrapping- As
ringAngleincreases, slides rotate backward (counterclockwise appearance) - Using radians internally:
ringAngle * π/180converts degrees to radians
- As
- X position:
sin(angle) * spacing— horizontal offset from center - Depth:
cos(angle)— ranges from -1 (back of ring) to 1 (front) - Normalized depth:
norm = (depth + 1) / 2— maps to 0 (back) to 1 (front)
Visual properties interpolated from norm:
- Scale:
minScale + (1 - minScale) * norm— back slides small, front slides full size - Opacity: Front-half slides (
depth >= 0) are fully opaque (1.0). Back-half slides (depth < 0) interpolate fromminOpacityto 1.0. This prevents seeing through foreground slides. - Blur: Front-half slides (
depth >= 0) have no blur. Back-half slides interpolate frommaxBlurto 0. - Z-index:
round(norm * 10)— proper layering - Tangential tilt:
rotSign * -rotation * sin(angle)— side slides face inward
Shortest-path navigation:
- Forward distance:
(target - active + total) % total - Backward distance:
(active - target + total) % total - Choose direction (forward/backward) based on which is shorter
- Ring rotates continuously in that direction (no wrapping detection needed)
Rapid-click handling:
animateRing()starts from currentringAngle(which may be mid-animation)- Previous animation frame is cancelled before starting a new one
ringAnimationFrameID tracked and cleaned up inbeforeUnmount
Carousel Wrapping System
When looping, slides that cross from one side to the other would normally animate through the center, creating a visual "jump" glitch. Solutions differ by layout:
Stack mode — departing approach:
prepareCarouselTransition(newIndex)detects slides that change sides (oldIsPrev !== newIsPrev)- Wrapping slides are added to
carouselDepartingMap with their current direction and distance + 1 - These slides animate off-screen in their current direction (prevents empty edge during animation)
- After
transitionDurationms: slides added tocarouselNoTransitionSet (disables CSS transition),carouselDepartingcleared - Next frame:
carouselNoTransitioncleared, slides snap to new positions without animation
Ring mode — no wrapping needed: Since ringAngle is continuous and unbounded, slides rotate naturally without wrapping artifacts. No prepareRingTransition logic required — the ring simply rotates to the shortest-path angle.
Container Background in Carousel
In carousel mode, the container background is hidden (backgroundProperties not applied to main container). Without this, the background color would show between the spaced-out slides, which looks wrong for carousel layouts. Instead, each slide gets backgroundProperties individually.
Autoplay System — How It Works
Timer:
setIntervalwith interval =autoplayInterval * 1000 + transitionDuration- The extra
transitionDurationensures the previous transition completes before the next one starts
Skip conditions (checked each tick):
isPaused— mouse hovering over track (mouseenter/mouseleave)!inView— component not in viewport (fromDataStore.inView)autoplayStopped— user has manually navigatedactiveSlideHasVideo— video slide controls its own timing
User interaction stops autoplay:
stopAutoplayOnInteraction()called from:scrollPrev(),scrollNext()(arrow clicks),scrollToSlide()(dot/thumbnail clicks, non-autoplay),onScroll()(manual scroll detection),onTouchEnd()(swipe)- Sets
autoplayStopped = true, clears interval, stops timer animation
Scroll mode autoplay guard:
isAutoplayScrollingflag set totruebefore autoplay-triggeredscrollTo()onScroll()handler checks this flag — if true, does NOT callstopAutoplayOnInteraction()- Flag reset to
falseafter scroll detection processes
Timer Dots — How It Works
Condition: showTimerDots = dots enabled AND autoplay enabled AND autoplayIndicatorStyle === 'timer'
When timer dots are active, regular dots are NOT shown (mutually exclusive via v-if).
Two shapes determined by dotShape:
- Legacy mapping:
circle/square->'dot',pill->'line'
Dot shape (SVG circle arc):
- SVG
viewBox="0 0 36 36"withr=15.9 - Background circle: filled with
dotColor - Progress arc:
stroke-dasharray = circumference,stroke-dashoffset = circumference * (1 - progress) - Arc rotated -90deg to start from top (
transform="rotate(-90 18 18)") - Only rendered on the active dot when autoplay is not stopped
Line shape (HTML div width animation):
- Active timer dot: 3x wider than inactive (same as regular line dots)
- Progress fill div: absolute positioned,
width: (timerProgress * 100)% - Background color:
dotActiveColor,border-radius: inherit - Only rendered on the active dot when autoplay is not stopped
Animation system:
requestAnimationFrameloop started byrestartTimerAnimation()- Measures
elapsed = performance.now() - timerStartTime - Progress =
min(1, elapsed / (autoplayInterval * 1000)) - Restarts per slide change via
activeIndexwatcher - Restarts on
inViewbecoming true - Stopped on
inViewbecoming false, onstopAutoplayOnInteraction()
On stop: Timer dots remain visible (showing which slide is active) but progress animation freezes. Active dot switches from dotColor (progress background) to dotActiveColor (highlight color) so the active slide is still clearly indicated. Regular dots do NOT appear as a fallback.
Tease System — How It Works
Trigger: Runs once on first inView change to true, controlled by teased boolean flag.
Configuration:
teaseConfig.delay: seconds before tease starts (default 0.5s)teaseConfig.duration: duration of each tease cycle (default 0.8s)teaseConfig.iterations: number of tease cycles (default 1)
Easing function (mapAdvancementRatio):
- Complex component:
((1 - cos(2PI * ratio^0.7)) / 2)^2— weighted 95% - Parabolic component:
4*ratio - 4*ratio^2— weighted 5% - Creates a smooth wiggle-and-return effect
Per-mode implementations:
| Mode | Tease behavior |
|---|---|
| Scroll | Temporarily disables scroll-snap, wiggles track.scrollLeft/scrollTop by up to 20% of container width |
| Slide | Offsets track-inner translateX/Y by -15% using inline style override |
| Fade | Flashes next slide opacity to 0.3 briefly |
| Carousel | Shifts carousel viewport by -5% along primary axis |
| Cube | Wobbles cube scene rotation by 15 degrees |
Cancellation: stopTease() called by scrollToSlide() — cancels teasingAnimationFrame, resets all inline styles. For cube mode, restores the base transform (translateZ(-hs)px rotateAxis(0deg)) instead of clearing to empty string (prevents Vue :style binding from not re-applying).
Peek Mode (Center Slides) — How It Works
- Only active when
centerSlides === trueANDslideWidth < 100 - Scroll mode only
- Sets
scroll-snap-align: 'center'on all slides - Sets
scroll-padding: 0 ${(100 - slideWidth) / 2}%on track - Effect: first slide is flush-left, last slide is flush-right, middle slides are centered with adjacent slides peeking on both sides
- Config UI: toggle with info text explaining the slideWidth requirement
Vertical Mode
All transitions support vertical via the isVertical computed property (direction === 'vertical').
Axis-dependent property swaps:
- Transform axis:
translateX->translateY,rotateY->rotateX - Container dimension:
clientWidth->clientHeight - Scroll position:
scrollLeft->scrollTop - Scroll-snap type:
x mandatory->y mandatory - Flex direction:
row->column - Arrow rotation: 90deg CSS rotation in vertical mode
- Touch handling:
dybecomes primary axis delta - Cube: rotateX with sign flip for correct direction
Config Panel Organization
4 tabs: Style | Nav (Navigation) | Behavior | Slides
Style tab:
- Template selector (TemplateButton)
- Block properties card: background (color/gradient/image), border-radius, border (width/style/color)
- Layout card: flex alignment, size (width/height), position (margin), slide padding (4-sided)
- Effects card: edge fade, box shadow, rotation
Navigation tab:
- Arrows card (toggle-gated): style, custom image (single/separate), size, color, background, border-radius, position (inside/outside/free), offset X/Y, free position inputs
- Dots card (toggle-gated): colors (inactive/active), size, gap, shape (dot/line), border-radius, position (overlay/above/below/free), autoplay indicator style (none/timer)
- Slide counter card (toggle-gated): placement (overlay/per-slide), color, font size, position (4 corners + free)
- Progress bar card (toggle-gated): color, height, position (top/bottom/free)
- Thumbnails card (toggle-gated): alignment, source block, size, gap, fit, margin, padding, border-radius, bg color, border color, active color, border width, position
Behavior tab:
- Behavior card: direction (horizontal/vertical), transition type (3+3 grid), transition duration (100-2000ms), carousel-specific options (layout, scale, rotation, opacity, blur, spacing, perspective)
- Slide Layout card: slide width (%), gap (px), peek toggle
- Autoplay card (toggle-gated): interval (1-60s), loop video toggle
- Loop toggle (independent of autoplay)
- Tease card (toggle-gated): delay, duration, iterations
Slides tab:
- Feed card: URL, filter rules, custom params, sort key/order
- Child creative redirect (link to master for editing)
- Slide pagination dots (navigating between slides in the editor)
- Slide editor: per-slide config via
SlideSectioncomponent with add/remove slide controls - Maximum 25 slides
Recent Bug Fixes (Phase 4)
| # | Fix | Details |
|---|---|---|
| 1 | Transition layout padding | 3+3 grid via transitionOptionGroups computed, full-width OptionList, proper padding |
| 2 | Autoplay waits for transition | Interval = autoplayInterval + transitionDuration (replaced duration cap approach) |
| 3 | Timer dots line shape | HTML div with animated width (not SVG rect), inherits dot border-radius |
| 4 | Loop independent of autoplay | Moved loop toggle outside v-if="blockData.autoplay" so it's always visible |
| 5 | Cube 3D math | Scene translateZ(-halfSize), active face at translateZ(+halfSize) lands at Z=0, scale=1.0 |
| 6 | Carousel perspective config | carouselPerspective property (200-3000px, default 1200), only shown for stack layout |
| 7 | Carousel slides use slideWidth | Inline width and left on each slide, overriding class-based 100% width |
| 8 | Timer dots in scroll mode | isAutoplayScrolling flag prevents onScroll() from killing autoplay during autoplay-triggered scrolls |
| 9 | Click overrides tease | stopTease() cancels animation + resets inline styles before any navigation |
| 10 | Cube tease scale-up fix | Tease end handler restores translateZ(-hs) rotateAxis(0deg) instead of clearing to empty string |
| 11 | Cube background flash | Cube scene element gets backgroundProperties to prevent parent from showing through |
| 12 | Arrow z-index | Increased from 2 to 10 (above dots/thumbnails at z-index 2) |
| 13 | Default arrow free position | Changed from 4px to 2% for responsive defaults |
| 14 | Button templates in sub-blocks | Nested lookup in updateButtonTemplate() — searches inside parent blocks for sub-blocks |
| 15 | Carousel ghost effect | Removed opacity from carousel slide transitions (was causing mid-slide transparency) |
| 16 | Carousel "jump to 1" bug | Wrapping detection + departing system: wrapping slides animate off-screen instead of snapping |
| 17 | Carousel container background hidden | Background not applied to main container in carousel mode (shows between slides otherwise) |
| 18 | Carousel ring layout | New carouselLayout: 'ring' option with circular 2D projection |
| 19 | Carousel opacity removed from transitions | Only transform and filter in CSS transition, no opacity — prevents mid-animation transparency |
| 20 | Ring carousel JS-driven animation | Replaces CSS transitions with requestAnimationFrame + ease-out cubic easing. Uses continuous ringAngle state instead of activeIndex for positioning. Prevents wrapping artifacts where slides would fade/disappear. |
| 21 | Timer dot active color when stopped | When autoplay is stopped (user interacted), active timer dot now uses dotActiveColor instead of dotColor. Line-shape timer dots check autoplayStopped to determine which color to use. |
| 22 | Carousel foreground slides fully opaque | Ring: front-half slides (depth >= 0) get opacity: 1 and blur: 0. Stack: distance-1 slides (directly beside active) are fully opaque with no blur. Only back-half/distance-2+ slides fade. Prevents seeing through foreground slides. |
Known Issues / TODO
Needs Testing
- Video upload E2E (awaiting BunnyCDN env keys for full pipeline testing)
- Cube transition in ad iframes (3D CSS transforms can be unstable in some iframe contexts)
- Carousel ring layout with various slide counts (especially edge cases: 2 slides, 3 slides, large numbers)
- Timer dots with all transition modes (verify animation restarts correctly for each)
- Carousel departing system edge cases (rapid navigation, very fast transition durations)
Not Yet Implemented
templateModifiedtracking — the confirm dialog inhandleTemplateSelect()referencesblockData.templateModifiedbut no Vuex mutation ever sets it. Currently, template switching always applies immediately without a confirmation warning.- Easing functions for transitions —
TransitionTimingtype exists in the type system with easing options (ease,ease-in,ease-out,ease-in-out,linear) but these are not wired to the slider config. Currently hardcoded toeasefor carousel,cubic-bezier(0.25, 0.1, 0.25, 1)for slide. - Thumbnails free mode: use alignment icons (like TextConfiguration) instead of text labels "Left" / "Center" / "Right"
- Default thumbnail positioning to alignment-based (left bottom) instead of center
slideWidthBreakpoints— the runtime engine supports responsive breakpoints (effectiveSlideWidthcomputed checks aslideWidthBreakpointsobject against container width), but the config panel has no UI to configure breakpoints
Config UI Improvements
- Template visuals need polish — templates are functionally correct but could benefit from more distinctive visual differentiation
- Consider adding more template presets beyond the current 4
- Thumbnails free mode may have cropping issues at extreme positions
Key Files
Application-Frontend
| File | Purpose |
|---|---|
src/pages/Chatbots/components/BuilderVisuals/Blocks/data/types.ts | SliderV2Properties type definition (~100 typed properties), SliderV2Template enum, OverlayPosition type |
src/pages/Chatbots/components/BuilderVisuals/Blocks/data/defaults.ts | Default values for all properties (sliderV2Defaults object) |
src/pages/Chatbots/components/BuilderVisuals/Configuration/configs/SliderV2Configuration.vue | Main config panel (~1500 lines, 4 tabs, template selection, slide editor with pagination) |
src/pages/Chatbots/components/BuilderVisuals/Configuration/components/SlideSection.vue | Per-slide config (image overrides, video upload, link URL, sub-block overrides) |
src/pages/Chatbots/components/BuilderVisuals/Configuration/components/SlideSectionsPagination.vue | Slide pagination dots for navigating between slides in the editor |
src/pages/Chatbots/components/BuilderVisuals/Configuration/components/OverlayPositionSection.vue | Shared position picker component (preset buttons + free mode with 4-sided inputs) |
src/templates/sliderV2/index.ts | Barrel exports for template functions |
src/templates/sliderV2/productCarousel.ts | Product Carousel template preset |
src/templates/sliderV2/imageGallery.ts | Image Gallery template preset |
src/templates/sliderV2/testimonial.ts | Testimonial template preset |
src/templates/sliderV2/story.ts | Story template preset |
src/templates/index.ts | Template registry, updateSliderV2Template(), updateButtonTemplate() (with nested sub-block lookup) |
Creative-Engine
| File | Purpose |
|---|---|
src/components/creative/CreativeSliderBlockV2/CreativeSliderBlockV2.vue | Runtime component (~2080 lines): template, all computed styles, navigation methods, autoplay, tease, timer dots, carousel wrapping, cube navigation, feed loading, video control, touch handling, analytics |
Architecture
Application-Frontend (Builder / Config)
=========================================
SliderV2Configuration.vue (config panel, ~1500 lines)
|-- 4 tabs: Style | Navigation | Behavior | Slides
|-- ~90 config options spread across tabs
|-- Uses shared components:
| Card, OptionRow, OptionList, OptionButton, DelaySection,
| DurationSection, IterationsSection, ColorInput, ImageInput,
| InputField, BorderRadiusSection, BorderStyleSection,
| BackgroundStyleSection, BoxShadowSection, FlexAlignmentSection,
| SizeSection, PositionSection, PaddingSection, RotationSection,
| SliderEdgeFadeSection, OverlayPositionSection, TabNavigation,
| TemplateButton, SlideSection, SlideSectionsPagination,
| SliderFeedSection, DeleteButtonWithExpandingConfirmation
|-- Vuex store: configurationLogic mixin for property updates
|-- sliderHelpers mixin: getFuzzyIndex, pagination math
|-- masterUrl mixin: link to master creative for child creatives
|-- ScrollingAnimator class: eased scroll animation for slide editor
types.ts
|-- SliderV2Properties (BlockBase & {...})
|-- SliderV2Template enum
|-- OverlayPosition type
defaults.ts
|-- sliderV2Defaults: all default values
|-- Registered in blockDefaults map
templates/
|-- sliderV2/productCarousel.ts
|-- sliderV2/imageGallery.ts
|-- sliderV2/testimonial.ts
|-- sliderV2/story.ts
|-- index.ts: updateSliderV2Template(), registry
Creative-Engine (Runtime)
=========================================
CreativeSliderBlockV2.vue (runtime engine, ~2080 lines)
|
|-- Template Layer (HTML)
| |-- Arrows (left/right buttons, SVG or custom image)
| |-- Track container (event listeners: scroll, keydown, mouse, touch)
| | |-- [Cube mode] cube-viewport > cube-scene > 2 cube-faces
| | |-- [Carousel mode] carousel-viewport > all slides with computed transforms
| | |-- [Slide mode] track-inner > all slides with translateX
| | |-- [Scroll/Fade/Crossfade] slides directly in track
| | |-- Each slide renders:
| | |-- <video> element (if slide has video)
| | |-- Sub-block components (text, graphic, button, HTML)
| | |-- Per-slide counter (if enabled)
| |-- Dots (regular pagination dots)
| |-- Timer Dots (SVG arc or HTML progress bar)
| |-- Thumbnails (scrollable strip with background-image)
| |-- Slide Counter (overlay 1/N display)
| |-- Progress Bar (fill width)
|
|-- Computed Layer
| |-- Mode detection: isFadeMode, isSlideMode, isCubeMode, isCarouselMode
| |-- isJsNavigated: all modes except scroll
| |-- effectiveSlideWidth: supports responsive breakpoints
| |-- styles: generates ALL CSS as JavaScript objects (~500 lines)
| | |-- Wrap, container, track, arrows, dots, timer dots,
| | thumbnails, counter, progress bar, slide video,
| | slide styles (per-mode: scroll/fade/slide/cube/carousel)
| |-- Carousel computeds: carouselVisibleIndices, cubeSceneStyle, trackInnerStyle
| |-- Timer computeds: showTimerDots, timerCircumference, timerDashOffset
|
|-- Methods Layer
| |-- Navigation: scrollToSlide, scrollPrev, scrollNext, navigateCube
| |-- Carousel: carouselSlideStyle, carouselRingSlideStyle,
| | prepareCarouselTransition, prepareRingTransition
| |-- Cube: cubeFaceStyle, navigateCube
| |-- Autoplay: startAutoplay, stopAutoplay, stopAutoplayOnInteraction
| |-- Timer: restartTimerAnimation, stopTimerAnimation
| |-- Tease: tease (per-mode), stopTease
| |-- Touch: onTouchStart, onTouchMove, onTouchEnd
| |-- Video: slideVideoUrl, playSlideVideo, pauseSlideVideo,
| | onVideoTimeUpdate, onVideoEnded
| |-- Thumbnails: getSlideThumbnail, scrollThumbnailIntoView
| |-- Feed: loadFeed (proxy URL construction, JSON fetch)
| |-- Analytics: sendSwipeAnalytics, sendFirstAnalyticsEvent
| |-- Helpers: resolveOverlayPosition, canShowSlide, slideClasses,
| | dotClasses, timerDotClasses, slideInlineStyle
|
|-- Watchers
| |-- activeIndex: video control, thumbnail scroll, timer restart
| |-- autoplay/autoplayInterval: start/restart autoplay
| |-- inView: autoplay, tease, video, timer
| |-- feedOverridesFromDataStore: debounced feed reload
|
|-- Lifecycle
|-- mounted: tease trigger, autoplay start, feed load, editing listener
|-- beforeUnmount: cleanup (autoplay, timer, tease, editing listener)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
Property Reference
Layout Properties
| Property | Type | Default | Description |
|---|---|---|---|
slideWidth | number | 100 | Percentage width of each slide (100 = full width) |
gap | number | 0 | Pixel gap between slides |
slidePadding | object | all 0px | Inner padding within each slide (4-sided) |
direction | string | 'horizontal' | 'horizontal' or 'vertical' |
centerSlides | boolean | false | Peek mode — center slides with adjacent peek |
Arrow Properties
| Property | Type | Default | Description |
|---|---|---|---|
arrows | boolean | true | Show navigation arrows |
arrowColor | string | '#55555599' | SVG arrow color |
arrowStyle | string | 'default' | 'default' (SVG) or 'custom' (image) |
arrowImageMode | string | 'single' | 'single' (mirrored) or 'separate' |
arrowImageUrl | string | '' | Custom arrow image URL (single mode) |
arrowImageUrlLeft | string | '' | Left arrow image (separate mode) |
arrowImageUrlRight | string | '' | Right arrow image (separate mode) |
arrowSize | number | 32 | Arrow button size in px |
arrowBackground | string | 'rgba(255,255,255,0.7)' | Arrow button background |
arrowBorderRadius | string | '50%' | Arrow button border radius |
arrowPosition | string | 'inside' | 'inside', 'outside', or 'free' |
arrowOffset | number | 4 | X-axis offset in px (inside/outside) |
arrowOffsetY | number | 0 | Y-axis offset in px (inside/outside) |
arrowFreeLeftPosition | object | top:50%, left:2% | Free position for left arrow |
arrowFreeRightPosition | object | top:50%, right:2% | Free position for right arrow |
Dot Properties
| Property | Type | Default | Description |
|---|---|---|---|
dots | boolean | false | Show pagination dots |
dotColor | string | 'rgba(255,255,255,0.5)' | Inactive dot color |
dotActiveColor | string | '#ffffff' | Active dot color |
dotSize | number | 8 | Dot size in px |
dotShape | string | 'dot' | 'dot' or 'line' |
dotGap | number | 6 | Gap between dots in px |
dotBorderRadius | string | '50%' | Dot border radius |
dotPosition | OverlayPosition | overlay | Position (overlay/above/below/free) |
Indicator Properties
| Property | Type | Default | Description |
|---|---|---|---|
progressBar | boolean | false | Show progress bar |
progressBarColor | string | '#ffffff' | Progress bar fill color |
progressBarHeight | number | 3 | Progress bar height in px |
progressBarPosition | OverlayPosition | bottom | Position (top/bottom/free) |
slideCounter | boolean | false | Show slide counter |
slideCounterColor | string | '#ffffff' | Counter text color |
slideCounterFontSize | number | 14 | Counter font size in px |
slideCounterPlacement | string | 'overlay' | 'overlay' or 'per-slide' |
slideCounterPosition | OverlayPosition | top-right | Position (4 corners + free) |
autoplayIndicatorStyle | string | 'none' | 'none' or 'timer' (timer dots) |
Behavior Properties
| Property | Type | Default | Description |
|---|---|---|---|
transition | string | 'scroll' | Transition mode (scroll/fade/crossfade/slide/cube/carousel) |
transitionDuration | number | 400 | Transition duration in ms |
autoplay | boolean | false | Enable autoplay |
autoplayInterval | number | 5 | Seconds per slide |
loop | boolean | false | Loop navigation |
tease | boolean | true | Enable tease animation |
teaseConfig | object | Tease settings | |
edgeFade | boolean | false | Edge gradient mask |
Carousel Properties
| Property | Type | Default | Description |
|---|---|---|---|
carouselLayout | string | 'stack' | 'stack' or 'ring' |
carouselScale | number | 0.8 | Depth scale factor (0.3-1.0) |
carouselRotation | number | 5 | Y-axis rotation per level (0-30 deg) |
carouselOpacity | number | 0.6 | Depth opacity factor (0-1.0) |
carouselBlur | number | 0 | Blur per depth level (0-10 px) |
carouselSpacing | number | 40 | Horizontal spacing (10-80%) |
carouselPerspective | number | 1200 | CSS perspective (200-3000 px, stack only) |
Feed Properties
| Property | Type | Default | Description |
|---|---|---|---|
feed | boolean | false | Enable feed data |
feedUrl | string | '' | Feed URL |
feedFilterRules | array | [] | Filter rules (key/value/type) |
feedCustomParams | array | [] | Custom URL params (key/value) |
feedSortKey | string | '' | Sort key |
feedSortDescending | boolean | false | Sort descending |