=== AddOnn Shoppable Video for WooCommerce ===
Contributors: zain111
Tags: woocommerce, video, shoppable video, reels, whatsapp
Requires at least: 5.8
Tested up to: 6.8
Requires PHP: 7.4
Stable tag: 1.5.8
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Turn your existing video content into a shoppable, TikTok-style feed for WooCommerce — with WhatsApp share-to-buy and combo/bundle support built in.

== Description ==

AddOnn Shoppable Video for WooCommerce lets you upload video content (UGC, reels, product demos) and turn it into a tappable, shoppable feed on your store — customers tap a video, see the tagged product(s), and add to cart without leaving the player.

**Free version features:**

* Self-hosted video upload via the WordPress Media Library — no third-party CDN dependency
* Tag one or more products per video (native combo/bundle video support)
* Add to cart directly from the video, no page reload
* **WhatsApp "Order on WhatsApp" share-to-buy button** — great for stores where customers prefer to finalize orders over WhatsApp
* Two layouts: Carousel (horizontal scroll) and Inline (vertical TikTok-style feed)
* Works via shortcode `[svp_feed]`, a native **Elementor widget**, and a native **Gutenberg block** — no other free shoppable-video plugin covers all three
* Basic per-video analytics: views and add-to-cart counts, visible right in the video list
* **Front-end "Add Video"** — store admins/shop managers can upload a video, tag products, and set a cover image without ever going into wp-admin, right from the feed itself (shortcode, Elementor, or Gutenberg). Never shown to regular shoppers.
* **Build your video list right inside the Elementor editor** — the Elementor widget's "Add & manage videos right here" mode lets you upload videos, tag products, and set WhatsApp options directly in the Content panel, with a live preview, no trip to wp-admin needed.
* **Carousel navigation dots, autoplay, and style controls** — swap the browser scrollbar for clickable dots, turn on auto-slide with adjustable speed and continuous looping, and (in Elementor) style the arrows and dots' colors and size from the widget's Style tab.

== Installation ==

1. Upload the plugin files to `/wp-content/plugins/addonn-shoppable-video`, or install directly through the WordPress Plugins screen.
2. Activate the plugin. Requires WooCommerce to be active.
3. Go to **Shoppable Videos → Settings** and set your default WhatsApp number (digits only, with country code, e.g. 919876543210).
4. Go to **Shoppable Videos → Add New**, upload a video, tag one or more products, and publish.
5. Display your feed:
   * Shortcode: `[svp_feed layout="carousel"]`
   * Elementor: search for the "Shoppable Video" widget
   * Gutenberg: add the "Shoppable Video Feed" block

== Frequently Asked Questions ==

= Does this require WooCommerce? =
Yes, WooCommerce must be installed and active.

= Does this require Elementor? =
No — the Elementor widget only registers if Elementor is active. The shortcode and Gutenberg block work without it.

= Will videos be hosted on your servers? =
No. This is fully self-hosted — videos are stored in your own WordPress Media Library.

= Can I tag more than one product on a single video? =
Yes — this is built specifically to support combo/bundle-style selling.

= Does this work with my theme? =
Yes — every visual element the plugin renders (cards, arrows, dots, play/shop buttons, modal) carries its own styling independent of the active theme, so it looks the same on Astra, Storefront, OceanWP, WoodMart, and other popular themes.

= Can shoppers check out without adding to cart? =
Yes — turn on WhatsApp Share-to-Buy (globally in Settings, or per-video) and an "Order on WhatsApp" button appears next to Add to Cart, prefilled with the product name, price, and page link.

== Screenshots ==

1. The shoppable video feed as a horizontal carousel — price badges, a "COMBO" tag for multi-product videos, and navigation arrows, embedded right on a shop page.
2. Tap a video to open the product panel — tagged product with price, one-tap Add to Cart, and the "Order on WhatsApp" share-to-buy button.
3. wp-admin video editor — upload a video, tag one or more products (combo/bundle support), and optionally override the WhatsApp number for that video.
4. The Shoppable Videos list in wp-admin, with per-video view and add-to-cart counts.
5. Build your video feed without leaving the page — the Elementor widget's "Add & manage videos right here" mode with live preview.

== Changelog ==

= 1.5.8 =
* Fix: on carousels with autoplay enabled, videos would repeatedly flicker play/pause every few hundred milliseconds after autoplay advanced the carousel — worse on some themes (confirmed on WoodMart) than others. Root cause: the infinite-loop carousel's boundary math (used to silently detect and correct when the scroll position drifts into clone-padding territory) compared the browser's whole-pixel scroll position against a boundary computed from card widths that some themes render at fractional/sub-pixel widths (percentage-based layouts). The tiny mismatch made the carousel think a perfectly settled position was still inside clone territory, jump-correct it, immediately re-trigger its own scroll listener from that jump, and repeat — each cycle toggling the video's visibility enough to pause and replay it. Card-width measurements are now rounded to whole pixels, plus a small tolerance was added around the boundary check so a sub-pixel settle is recognized as settled instead of re-triggering a correction.

= 1.5.7 =
* Fix: on some pages, the video previews for cards visible on initial load/reload would appear to play forward for about a second and then jump back, repeating until the carousel was scrolled. Root cause: the lazy-load/autoplay `IntersectionObserver` was watching cards before the infinite-loop carousel's clone padding had settled into its final scroll position, so the settle jump plus normal above-the-fold layout shift (banner images, price badges finishing load) repeatedly toggled cards in and out of "intersecting" — each toggle called `play()`/`pause()` again before the video had buffered its first frame, restarting playback from the beginning each time. The clone padding now settles into position before the observer starts watching, and rapid intersection flips within ~150ms are now debounced to the last state instead of each being acted on.

= 1.5.6 =
* Fix: the carousel's clone-based infinite-loop reset jump (used to silently snap from a cloned slide back to the matching real slide) is supposed to be instant/invisible, but the 1.5.4 hardening pass made `scroll-behavior: smooth` on `.svp-carousel-track` `!important`, which beat the JS's inline override and made that reset jump animate — visible as the carousel appearing to reverse and scroll backward to slide 1, on both arrow-click and swipe/touch. `scroll-behavior` on the track is no longer `!important`; every other property on it still is.

= 1.5.5 =
* Fix (hotfix): the 1.5.4 hardening pass added `!important` to `.svp-modal-overlay`'s `display: flex`, but the product modal and the front-end "Add Video" editor modal are both opened/closed by toggling that same element's *inline* `display` style (jQuery fadeIn()/fadeOut()) -- a stylesheet `!important` beats a non-!important inline style, so both modals ignored fadeOut() and rendered open on every page load instead of staying hidden until a video/trigger was clicked. `display` on `.svp-modal-overlay` is no longer `!important`; every other property on it (position, background, z-index, alignment) still is.

= 1.5.4 =
* Hardening: the last three releases (1.5.2, 1.5.3) each fixed a theme/page-builder CSS conflict one property at a time, as they were found — color, then position/min-height, on a WoodMart site. Rather than wait for the next theme to expose the next gap, every plugin-owned visual element (arrows, dots, play button, hotspot button, video card/wrapper, modal, buttons, front-end editor UI) now forces its full set of layout and appearance properties with `!important`, so the plugin's own look no longer depends on which theme or page builder is active. The feed heading/subheading text is intentionally left alone so it continues to inherit the site's own heading typography by default (use the Heading/Subheading Style controls in Elementor to customize it) — everything else is now the plugin's own, by default, on any theme.

= 1.5.3 =
* Fix: the 1.5.2 WoodMart/Elementor fix only covered *colors* (arrows/dots picking up the theme's button accent color). The same theme also forces `position: relative` and a `min-height: 42px` on every `<button>` element site-wide, which was quietly overriding this plugin's `position: absolute` on the play button, hotspot button, and carousel arrows — knocking them out of their floating position and adding up to 42px of stray empty space under each video and inside the carousel viewport. Pagination dots were also rendering oversized (up to 42px instead of 8px) for the same reason. All affected elements now force `position` and `min-height` the same way color/border-radius already were.

= 1.5.2 =
* Fix: on pages built with Elementor's flexbox Container layout (e.g. a video feed sharing a row with a taller widget), the feed's wrapper was being stretched to match the row height, which pushed each video card taller than its own 9:16 video — leaving a dark gap under the video with the shop icon floating in it instead of sitting on the video. Found by a client testing the plugin on a WoodMart theme site.
* Fix: prev/next arrows and pagination dots could pick up the theme's own button accent color (and lose their rounded shape) on themes/page builders that style `<button>` elements globally with higher CSS specificity than the plugin's own rules — most visible as flat blue squares instead of white circular arrows. Same underlying cause as the 1.3.9 hotspot-button fix, now applied to the carousel arrows and dots too.

= 1.5.1 =
* Fix: found during a final audit — the (currently disabled) front-end "Add Video" feature inserts a newly saved card by prepending it directly into the carousel track. On an infinite-loop carousel, that would land the new card ahead of the clone padding added in 1.5.0, throwing off the loop's wrap math the moment that card scrolled into view. The carousel now rebuilds its clone padding whenever a card is inserted this way, so it stays correct regardless of whether/when that feature is re-enabled.

= 1.5.0 =
* Redesigned "Continue Scrolling" (infinite loop) to be genuinely seamless: the carousel now clones the real cards once before and once after themselves in the track, so scrolling past the last card (or before the first) always shows real-looking cards, with silent, invisible repositioning happening only once the scroll settles inside a clone region. Previously the loop worked by detecting "we're at the true end" and cutting back to card 1 — reliable in principle, but every place that could trigger it (arrow clicks, autoplay, swipe) had to independently guess whether a scroll was genuinely "at the end," which is what caused the earlier reverse-scrub and premature-reset bugs. The new approach needs no such guessing: arrow clicks, autoplay, dot clicks, and swipes all just scroll normally, and a single shared watcher handles the invisible wrap-around regardless of what caused the scroll.

= 1.4.9 =
* Fix: after the 1.4.8 fix, the loop would sometimes reset one card early, with no extra click — this was a second, separate bug, not a leftover of the same one. The arrow-click loop jump was working correctly, but a second independent watcher (added in 1.3.6 to make swipe/touch scrolling loop too) was also seeing that same scroll settle at the last card and firing its own extra loop-jump moments later, since it had no way to tell "arrow click just correctly reached the end" apart from "user actually swiped to the end." Arrow clicks, dot clicks, and autoplay now flag their own scrolls so that watcher only ever reacts to genuine swipes.

= 1.4.8 =
* Fix: the 1.4.7 loop fix still didn't fully work — turns out a plain `element.scrollLeft = 0` assignment *also* honors the CSS `scroll-behavior: smooth` on the carousel track (this is a common misconception; only `scrollTo()`/`scrollBy()` calls with an explicit `behavior` option are guaranteed to override it). The jump back to card 1 now temporarily forces `scroll-behavior: auto` via an inline style right before the jump (and restores it a frame later), which is the only way to force a truly instant scroll regardless of CSS. Verified with an instrumented before/after test measuring actual scroll position frame-by-frame, not just visually.

= 1.4.7 =
* Fix: the 1.4.6 loop fix didn't actually take effect — `scrollTo({behavior:'auto'})` defers to the carousel track's CSS `scroll-behavior: smooth`, so the jump back to card 1 was still animating through every card in between. Now uses a direct `scrollLeft` assignment, which is always instant regardless of CSS, so the loop actually cuts straight to card 1 as intended.

= 1.4.6 =
* Fix: "Continue Scrolling" (infinite loop) on the carousel was visually rewinding backward through every card (5,4,3,2,1) to get back to the first one instead of cutting straight to it — so it looked like the carousel reversed direction instead of looping. The jump back to the first card is now instant, so the sequence reads as 1,2,3,4,5,1,2,3,4,5... like a proper loop. Affects arrow clicks, swipe, and autoplay.

= 1.4.5 =
* Security/hardening pass covering analytics, AJAX, admin, and front-end editor code paths:
* Fix: the view/add-to-cart analytics counter now increments atomically (was read-then-write, which could lose counts under concurrent traffic), and only counts events against real, published videos.
* Fix: add-to-cart now verifies the product is actually tagged to the given video before adding it, closing a gap where any published product ID could be added via the endpoint.
* Removed a dead, unauthenticated admin-only product lookup endpoint that was no longer used anywhere.
* Fix: product names are now inserted as text (not raw HTML) when rendering search results and tagged-product lists in both the wp-admin video editor and the front-end "Add Video" editor, closing a stored-XSS risk from product titles containing markup.
* Fix: WhatsApp numbers are now normalized to digits-only everywhere they're saved (wp-admin, front-end editor, Elementor sync), matching the existing settings-page behavior, so a stray character can't break the wa.me link.
* Fix: front-end video/product save now validates the uploaded file is actually a video and rejects saves with no valid, purchasable tagged product.
* Fix: the Elementor widget's live front-end now correctly resolves each curated video to its synced post, instead of silently falling back to showing the site's latest videos — a regression introduced in 1.4.2's curate-only simplification.
* Fix: the `[svp_feed]` shortcode's `ids` filter no longer silently ignores an explicit "match nothing" value due to a PHP empty-string quirk.
* Fix: the front-end "Add Video" flow now reloads the feed when adding the very first video to an empty feed, instead of silently leaving the "no videos yet" message on screen.

= 1.4.4 =
* Change: removed the "Add All to Cart" button from the video modal — it could silently skip products (e.g. anything out of stock or a variable product needing options picked) and only add the rest, so shoppers weren't always getting what they saw on screen. Products still add to cart individually, one tap each, exactly as before.

= 1.4.3 =
* Fix: the "Order on WhatsApp" button could stay visible (and clickable) even for videos where it's turned off, sometimes still carrying the WhatsApp link from whichever video was last opened instead of doing nothing. Same underlying issue could make "Add All to Cart" show up for a single-product video. Caused by the buttons' own hard CSS reset (`display: inline-flex !important`, added back in 1.0.5 for cross-theme consistency) silently overriding the show/hide logic. Both buttons now hide correctly, and the WhatsApp link is cleared whenever it's hidden so it can never carry over stale product info.

= 1.4.2 =
* Change: simplified the Elementor widget's "Which videos?" dropdown away entirely — the widget now always works in "Add & manage videos right here" mode, no choice to make. Picking from the full wp-admin-managed video library (what "All published videos" / "Choose specific videos" used to do) is now what the `[svp_feed]` shortcode is for, since that's a wp-admin-driven workflow to begin with. Widgets already saved with the old dropdown's selection keep working exactly as before — nothing changes for pages that were already using it.

= 1.4.1 =
* New: the Heading and Subheading style controls now use Elementor's full Typography group (font family, size, weight, transform, style, decoration, line-height, letter-spacing) and a Text Shadow control, replacing the earlier simple font-size slider — matching the level of control Elementor's own Heading widget offers.

= 1.4.0 =
* New: optional Heading and Subheading text above the video feed — e.g. "Shop the Look" with a short line underneath. Available in the shortcode (`heading` / `subheading` attributes), the Elementor widget (Content tab fields + a new Style tab "Heading" section for color, font size, and alignment), and the Gutenberg block. Leaves everything unchanged if left blank.

= 1.3.9 =
* Fix: the carousel arrows (and the shop-button icon) reverted to the theme's default blue as soon as you clicked them, even with a custom color set in Elementor — clicking leaves the button focused, and plain ":focus" isn't always treated as ":focus-visible" by the browser, so the styled-hover rule (which only covered ":hover"/":focus-visible") didn't apply after a click and the theme's own focused-button color showed through. Both buttons now keep their styled/hover color through a click, not just while hovering.

= 1.3.8 =
* Fix: the round "shop this video" bag-icon button could turn blue on hover/tap — it had no explicit hover state, so it fell back to the active theme's own default button hover color instead of staying consistent. It now keeps its own colors on hover too.
* New: Elementor Style tab now has a "Shop Button" section — Icon Color, Background Color, and their hover variants — for that same bag-icon button, on both Carousel and Inline layouts.

= 1.3.7 =
* Fix: the real reason autoplay (and its loop) could stop working on mobile entirely — "Pause on Hover" was listening for mouseenter/mouseleave unconditionally. Mobile browsers synthesize a mouseenter on the first tap for compatibility, but never fire the matching mouseleave (there's no cursor to "leave"), so a single tap anywhere on the carousel permanently paused autoplay for the rest of the visit with no way to resume. "Pause on Hover" now only activates on devices with a real mouse/trackpad; touch devices keep autoplaying (and looping) regardless of taps.

= 1.3.6 =
* Fix: "Continue Scrolling" (loop) still did nothing if you scrolled the carousel by swiping instead of tapping an arrow — native touch scrolling has no concept of looping on its own, it just stops at the last card. The carousel now watches for the swipe settling at the end and automatically loops back to the first video, matching what tapping an arrow (or autoplay) already did.

= 1.3.5 =
* Fix: the real reason the carousel loop kept not working on mobile — the prev/next arrows were set to display:none below 600px width (to "rely on native swipe"), so the loop-click fix shipped in 1.3.2 was never actually reachable there. Native swipe alone has no concept of looping; it just stops at the last card. Arrows are now visible on mobile too (smaller, tucked closer to the edge) so tapping them loops correctly, same as desktop.

= 1.3.4 =
* Fix: on mobile, tapping the video to open the product modal could make the video expand to fill (or exceed) the whole screen, hiding the close button and the product list underneath. Caused by the video panel's height being calculated as a percentage of an auto-sized parent, which isn't reliable in CSS. The video panel and product list now each get a fixed, predictable height on mobile, and the close button stays visible and properly centered.

= 1.3.3 =
* New: Elementor Style tab now has "Icon Hover Color" and "Background Hover Color" controls for the carousel's prev/next arrows, in addition to the existing resting-state Icon Color and Background Color.
* Fix: bumped asset version to force a cache refresh for the above.

= 1.3.2 =
* Fix: "Continue Scrolling" (loop) had no effect on the prev/next arrow buttons — clicking next at the last video just stopped there instead of wrapping to the first. The loop check only existed in the autoplay timer, not in the manual arrow-click handler. Arrows now wrap in both directions when Continue Scrolling is on.
* Fix: bumped asset version again to force a cache refresh for the above.

= 1.3.1 =
* Fix: the active navigation dot never updated while scrolling or after clicking a dot — the listener that syncs it was bound via delegated `scroll` events on `document`, but `scroll` events don't bubble, so it silently never fired. Dot clicks still scrolled the carousel correctly; only the visual "active dot" indicator was stuck. Now bound directly on each carousel's track.
* Fix: bumped asset version to force a cache refresh (browser + host static-file caching serves JS with a 7-day max-age here), so the fix above actually reaches visitors instead of being served a stale cached copy.

= 1.3.0 =
* New: carousel pagination dots — replaces the browser's native scrollbar with clickable navigation dots below the feed. Available as a shortcode attribute (`dots`), Elementor widget control, and Gutenberg block toggle.
* New: carousel autoplay — auto-advance the carousel on a timer, with a configurable speed (`autoplay_speed`, ms between slides), "Continue Scrolling" loop (jumps back to the first video instead of stopping at the end), and pause-on-hover. Available in the shortcode, Elementor widget, and Gutenberg block.
* New: Elementor Style tab controls for the carousel's arrows and navigation dots — icon/background color for arrows, dot color, active-dot color, and dot size, all live-previewed in the editor.

= 1.2.0 =
* New: "Add & manage videos right here" mode for the Elementor widget — build your video list directly inside the Elementor panel (Content tab): click "+ Add Video", upload the video, optional cover image, tag products with a type-to-filter search, and set WhatsApp options, all without leaving the page editor. See a live preview of the real card layout as you build it. Hit Update/Publish and each video is automatically created (or updated) as a real Shoppable Video — removing a row from the list trashes its video too. The existing "All published videos" and "Choose specific videos" modes are unchanged.

= 1.1.1 =
* Fix: bumped asset version to force a cache refresh on hosts with aggressive static-file caching (e.g. LiteSpeed Cache), after the front-end "Add Video" JS wasn't picking up on some setups because the browser/host served a stale cached `frontend.js`.

= 1.1.0 =
* New: front-end "Add Video" — store admins and shop managers (`manage_woocommerce` capability) can now upload a video, choose an optional cover image, tag one or more products, and set WhatsApp share-to-buy — all from a "+ Add Video" button that appears right in the feed (shortcode, Elementor widget, or Gutenberg block), no wp-admin trip needed. Regular shoppers never see this button. The new video appears in the feed instantly on save, no page reload.

= 1.0.8 =
* Fix: the "Cards Visible" carousel setting had no visible effect — the active theme's own WooCommerce grid CSS loaded after the plugin's stylesheet and won the specificity tie on the card's flex-basis. Forced with `!important`, consistent with the other button/icon fixes on this theme.

= 1.0.7 =
* New: full control over the carousel layout — set how many video cards are visible at once, independently for desktop/tablet/mobile ("Cards Visible"), how many cards the prev/next arrows move per click ("Slides to Scroll"), and whether to show the nav arrows at all. Available as shortcode attributes (`columns`, `columns_tablet`, `columns_mobile`, `scroll_by`, `arrows`), Elementor widget controls, and Gutenberg block settings.

= 1.0.6 =
* Fix: widget appeared unstyled (raw blue buttons, no card layout) inside the Elementor editor canvas. The plugin's CSS/JS were only registered on page load and actually enqueued lazily inside the widget's own render — fine on normal pages, but Elementor's editor rendering pipeline can render widget content after `wp_head` has already printed, so the late-enqueued stylesheet never made it onto the page. CSS/JS are now enqueued unconditionally on every frontend load, so they're always present regardless of which context (shortcode, block, Elementor widget, or the Elementor editor canvas) renders the feed.

= 1.0.5 =
* Fix: "Add to Cart" buttons for simple vs. variable products (button vs. link) rendered at different sizes/casing because the active theme's default button/link styles leaked through. All plugin buttons now use a hard CSS reset (`all: unset` + explicit `!important` values) so they're visually identical regardless of theme.

= 1.0.4 =
* Fix: hotspot/play button icons (shopping bag and play triangle) were invisible — replaced font emoji with inline SVG, then fixed the SVG rendering at zero width and white-on-white color caused by the active theme's global button/SVG resets overriding our styles. Icon color and size are now forced with explicit values so they render consistently regardless of theme.

= 1.0.1 =
* Fix: "Add to Cart" / "Add All to Cart" buttons in the video modal were invisible (white text on transparent background) because the shared modal is printed once per page as a sibling of the video feed, so it fell outside the CSS custom-property scope. Colors are now defined at :root with fallback values.
* Fix: bumped Gutenberg block apiVersion to 3 to remove an editor deprecation warning.

= 1.0.0 =
* Initial release: video CPT, product tagging, AJAX add-to-cart, WhatsApp share-to-buy, carousel/inline layouts, Elementor widget, Gutenberg block, basic analytics.
