=== Sonic Pixel Product Spin for WooCommerce ===
Contributors: maikunari
Donate link: https://ko-fi.com/maikunari
Tags: woocommerce, 360, product spin, product viewer, media library
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.2.5
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
WC requires at least: 7.0
WC tested up to: 11.1

Self-hosted 360° product spin viewer. Attach ordered photos from the media library; the WooCommerce gallery is never replaced.

== Description ==

**Sonic Pixel Product Spin for WooCommerce** lets a shop manager attach an ordered set of photos to a product and show them as a 360° spin on the product page. Frames live in the WordPress media library and are served by your own site. There is no third-party CDN, no subscription, and no remote service that can shut down.

The plugin never replaces the WooCommerce product gallery. You can show the spin via a `[sppfw_product_spin]` shortcode, an extra product tab, a section below the product summary, or as the **first gallery slide** (the theme keeps thumbnails, zoom, and lightbox). Shop and category pages can show a small 360° badge on products that have frames — the viewer itself is not loaded on archives.

= Key Features =

* **Self-hosted frames** — ordinary media-library attachments, stored under `wp-content/uploads/`
* **Does not replace the gallery** — optional first-slide injection; thumbnails, lightbox, zoom, and variation images stay with the theme
* **Placements** — shortcode, extra tab, below-summary, or first gallery slide (falls back to below-summary if the theme gallery is not recognised)
* **Catalog 360° badge** — presentational badge on shop/category thumbnails; opt out under WooCommerce → Settings → Products
* **Drag, touch, and keyboard** — Pointer Events for mouse and touch, inertia after a flick, arrow keys for accessibility, optional autoplay
* **Lazy loading** — remaining frames load on first interaction or when the viewer scrolls into view
* **Simple product editor** — media-library multi-select, drag-to-reorder thumbnails, placement radios, autoplay checkbox
* **Library hygiene** — spin frames are tagged and hidden from the general media library by default, with a “Spin frames” filter to reach them
* **Organized uploads** — new frames uploaded from the product metabox go to `uploads/product-spins/product-{id}/`
* **Conditional assets** — viewer CSS and JS enqueue only on pages that actually render a spin; archives never load the viewer

= How to use =

1. Edit a WooCommerce product.
2. In the **360° Spin** box, click **Add / Edit frames** and select 24–72 photos shot in order around the product. Frame 1 is the resting view.
3. Drag thumbnails to reorder if needed.
4. Choose a placement (None / Extra tab / Below product summary / First gallery slide) and optionally enable autoplay.
5. Update the product. You can also paste the shortcode `[sppfw_product_spin]` into any page or product description.
6. Optionally turn off the catalog 360° badge under WooCommerce → Settings → Products.

= How to shoot frames =

* Mount the camera (or the product) so the framing stays consistent.
* Rotate the product in even steps. **24 frames (15°)** is the practical minimum; **36 frames (10°)** is the usual sweet spot; **72 frames (5°)** is very smooth but heavier.
* Keep lighting and background constant. Export JPEGs; WordPress will create intermediate sizes.

= Requirements =

* WordPress 6.0 or higher
* WooCommerce 7.0 or higher
* PHP 7.4 or higher

== Installation ==

= Automatic Installation =

1. Log in to your WordPress dashboard.
2. Navigate to Plugins → Add New.
3. Search for “Sonic Pixel Product Spin for WooCommerce”.
4. Click Install Now, then Activate.
5. Edit a product and use the **360° Spin** box.

= Manual Installation =

1. Upload the `sonicpixel-product-spin` folder to `/wp-content/plugins/`.
2. Activate the plugin through the Plugins menu.
3. Edit a product and use the **360° Spin** box.

== Frequently Asked Questions ==

= Does this replace my product gallery? =

No. The gallery is never swapped out for a custom viewer. **First gallery slide** inserts the spin as an extra first slide and leaves the theme’s thumbnails, lightbox, zoom, and variation-image swapping in place. If the theme does not use the standard WooCommerce gallery container, that placement falls back to below the product summary instead of rendering nothing.

= Will shop pages load the 360 viewer? =

No. Archives show a small 360° badge on products that have frames. The viewer CSS and JavaScript are not enqueued on shop or category pages. Turn the badge off under WooCommerce → Settings → Products if every product has a spin.

= Where are the frames stored? =

In your site’s media library, as normal attachments. The plugin stores attachment IDs in product meta and resolves URLs at render time. Nothing is hosted by a third party.

= How many frames do I need for a full 360°? =

Any count of two or more will spin fully — one drag across the viewer maps to the whole set. 24 frames is the practical minimum so motion does not feel steppy; 36 is the industry-standard sweet spot; 72 is very smooth but doubles the payload.

= Will this work with my custom theme? =

Tab, below-summary, and shortcode work as long as the theme does not strip shortcodes or WooCommerce tab filters. First-gallery-slide placement targets `.woocommerce-product-gallery`. Storefront and Astra keep the classic FlexSlider wrapper, so the spin is a real first slide with a 360° thumbnail badge. Blocksy keeps the gallery container but rebuilds slides in its own Flexy slider from attachment IDs — the spin is shown above that slider in the gallery column rather than as a Flexy slide. A bespoke product template that does not output the container, or that does not run `woocommerce_single_product_image_thumbnail_html`, gets the below-summary fallback.

= Are viewer assets loaded on every product? =

No. Viewer CSS and JavaScript enqueue only when a spin is actually rendered (product with a placement, or a page that contains the shortcode). Catalog pages load only a tiny badge stylesheet, and only when the badge is enabled.

= Can I keep using the shortcode if I choose “None” for placement? =

Yes. Placement only controls the automatic product-page output. `[sppfw_product_spin]` always works when the product has frames.

= Do spin frames clutter the media library? =

By default they are hidden from the general library and from the media modal, except inside this plugin’s own frame picker. A **Spin frames** filter on the Media Library lists them on purpose. This can be disabled with the `psfw_hide_frames_from_library` filter.

= Does this work with variable products? =

v1 attaches one spin per product, not per variation.

= Is this compatible with High-Performance Order Storage (HPOS)? =

Yes. The plugin declares HPOS compatibility. It does not read or write orders.

== Screenshots ==

1. Product editor — 360° Spin metabox with frame thumbnails, placement, and shortcode.
2. Frontend spin on a product page (below-summary or tab), gallery otherwise unchanged.
3. Extra “360° View” product tab.
4. First gallery slide with a 360° thumbnail badge.
5. Catalog 360° badge on shop thumbnails.
6. Media Library “Spin frames” filter.

== Changelog ==

Older entries live in `changelog.txt`.

= 1.2.5 =
* Renamed to "Sonic Pixel Product Spin for WooCommerce" (wordpress.org slug `sonicpixel-product-spin`) at the plugin review team's request. The plugin folder and main file are now `sonicpixel-product-spin/sonicpixel-product-spin.php` and the text domain is `sonicpixel-product-spin`. Sites running the old Product Spin for WooCommerce: deactivate it, activate the renamed plugin (both cannot be active together), then remove the old `product-spin-for-woocommerce` folder with FTP or a file manager. Do not use Plugins → Delete on the old plugin: its uninstall deletes the spin frames and settings the renamed plugin uses. Frame data and settings otherwise carry over.
* The shortcode is now `[sppfw_product_spin]`; the unprefixed `[product_spin]` is no longer registered. Replace it in any page or product description that used it.
* Prefixed the script and style handles (`sppfw-product-spin`, `sppfw-product-spin-archive`).

= 1.2.4 =
* Bumped `Tested up to: 7.1` and `WC tested up to: 11.1` — re-verified with the repo's own 57-check Playwright suite on WP 7.1.2 + WooCommerce 11.1.2, WP_DEBUG on, zero PHP notices.
* Removed the `Plugin URI` header. It duplicated the `Author URI`, and wordpress.org requires a URI unique to the plugin.
* Removed the unused `Domain Path` header and `load_plugin_textdomain()` call; translations load automatically for wordpress.org-hosted plugins since WP 4.6.
* Prefixed the uninstall script's file-scope globals (`$psfw_site_ids`, `$psfw_site_id`) and swapped its direct `$wpdb->delete()` call for core's `delete_post_meta_by_key()`.
* Dropped a `post__not_in` query arg in favor of skipping the excepted product inside the results loop.
* Corrected the GPL license boilerplate to include "or (at your option) any later version", matching the GPLv2-or-later header.
* Added `icon-128x128.png` / `icon-256x256.png`, derived from the existing banner ribbon art.
* Fixed stale `{product-slug}` wording in the readme and an admin docblock; uploads have gone to `product-{id}` since 1.1.1.

= 1.2.3 =
* Fixed: on a variable product with more than one attribute, WooCommerce triggers its own "change" on the attribute dropdowns while it resolves the form on page load. 1.2.2 counted that as the shopper picking a variation, so the gallery could still move off the spin on load; only a real interaction counts now.
* Hardened: the poster's extra attributes are restricted to `data-*`, so a theme calling `PSFW_Render::markup()` with caller-controlled arguments cannot inject an event handler attribute.

= 1.2.2 =
* Fixed: on a variable product, gallery placement showed the spin and then immediately slid to the featured image. WooCommerce runs its variation image code once at page load, and the plugin was treating that as a shopper picking a variation. The gallery now only leaves the spin when a shopper actually chooses a variation that has its own image.
* Fixed: clicking the zoom / magnifier while the spin slide was showing opened the lightbox on a black slide. WooCommerce reads the large-image data from the first image in a slide, which was the spin poster; the data lived on a hidden image after it and was never read. The poster now carries it, and the hidden hook image is gone.
* The spin slide is now protected by a MutationObserver as well as the jQuery patch, so a theme or optimisation plugin that loads WooCommerce's variation script out of order can no longer rewrite the spin's poster, its large-image data, or its thumbnail.

== Upgrade Notice ==

= 1.2.5 =
Renamed. Deactivate the old Product Spin for WooCommerce, activate this one, then remove the old folder by FTP or file manager, not Plugins → Delete (its uninstall deletes your spin data). Replace [product_spin] shortcodes with [sppfw_product_spin].

= 1.2.4 =
Readiness fixes for wordpress.org (Tested up to 7.1, dropped Plugin URI, added an icon) plus small internal cleanups. No behavior change for existing sites.

= 1.2.3 =
Follow-up to 1.2.2: on a multi-attribute variable product the gallery could still slide off the spin on page load. Recommended for anyone running 1.2.2 with gallery placement.

= 1.2.2 =
Fixes gallery placement on variable products (the spin no longer flips to the featured image on load) and the black lightbox when zooming from the spin slide.

== Additional Information ==

= Privacy =

This plugin does not collect, store, or transmit personal data. It stores product meta (frame attachment IDs, placement, autoplay) on your site.

= Credits =

Developed by Mike Sewell at [SonicPixel](https://sonicpixel.io/).

= License =

This plugin is licensed under the GPL v2 or later.
