=== Australcode Uniform Images – Product Image Resize Without Cropping for WooCommerce ===
Contributors: australcode
Tags: image resize without crop, uniform images, same size images, product image resize, avif
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 8.2
Requires Plugins: woocommerce
Stable tag: 1.0.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Make WooCommerce product images the same size without cropping. Unlimited bulk, free AVIF + WebP, and cache-safe delivery. No catalog limit.

== Description ==

**Australcode Uniform Images** solves the classic WooCommerce catalog problem: product photos with different proportions that the theme crops brutally to align the grid. This plugin makes them uniform without cropping — it adds smart padding over a colored canvas (white by default), optionally trims the uniform background first to normalize inconsistent margins, and delivers each thumbnail in AVIF + WebP + JPEG using the `<picture>` element. The result: **same size product images** across your whole shop — resize product images without cropping and regenerate product thumbnails in bulk, with no catalog limit.

= Key differentiators =

* **No accidental crops** — the entire product fits inside the thumbnail, always. The plugin pads to a uniform canvas instead of guessing where to crop.
* **Your whole catalog, on the free version** — no image cap, no monthly credits, no quota to run out of. Ten products or thirty thousand, the bulk run processes all of them.
* **AVIF and WebP are free too** — both formats are part of the free core, not an upgrade.
* **Real `<picture>` markup** — multi-source with AVIF + WebP + JPEG fallback. **Survives any page cache** (LiteSpeed, WP Rocket, Cloudflare APO, W3 Total Cache) because it does not use `Vary: Accept`. The browser picks the optimal format; the cache serves HTML without negotiation.
* **Alt text from the product, not the filename** — your uploads are called `IMG_4832.jpg`. Image SEO reads the WooCommerce product title, category and brand and writes real alt text: "Vape Pod XYZ — Pods, Brand Z". Automatic on every new upload, free, no AI and no API calls. It never overwrites alt text you wrote yourself.
* **4-tier quality preset** — Maximum / High / Standard / Economy with per-format values calibrated (e.g. Standard = AVIF Q60, WebP Q82, JPEG Q85 — visually indistinguishable from the original at half the size).
* **HPOS-ready from day one** — declares `custom_order_tables` and `cart_checkout_blocks` compatibility without any setup.
* **Cloudflare Image Transformations (CIT)** — optional integration to serve via Cloudflare edge (`/cdn-cgi/image/format=auto`) with automatic availability detection on your zone. If CIT is not enabled on your CF plan, the plugin keeps serving local derivatives without breaking anything.

= Free (no license required) =

* **Bulk regenerate with Action Scheduler** — async processing resumable after worker death, error classification (`oom`, `missing_original`, `engine_failed`, `fs_permission`), scope filters (`auto` = products only based on Settings; `all-used` = every image referenced in posts/products/Bricks templates/terms).
* **Embedded sample test** — processes 1-5 images on demand and shows the before/after grid with bytes and % savings. Available on the **Settings** page ("Quick preview") and on **Bulk regenerate** ("Generate sample").
* **Health page with CDN detection** — engine diagnostics, encoders, filesystem permissions, detected competing plugins (Smush/EWWW/ShortPixel/Imagify), Cloudflare Polish status (alerts if active — incompatible with the plugin's AVIF/WebP), Image Prioritizer (Performance Lab) status with automatic detection.
* **Image SEO on every upload** — new product images get alt text, title and caption built from the product title, category and brand (caption off by default — WordPress fills it from the photo's IPTC data, and that text is usually the photographer's). Deterministic, no AI, no API calls. Existing images can be filled one at a time from the Media Library; filling the whole library in one pass is Pro.
* **Extreme-padding detection** — the Dashboard flags product images that get squared with a lot of empty canvas (panoramic or very tall photos) and a gentle, dismissible notice points you to how each aspect is handled. Detection is free; the per-aspect fix is a Pro upgrade.
* **WP-CLI — 9 commands** — 7 free: `wp acimg health`, `stats`, `regenerate`, `derivatives <id>`, `purge-cache` (orphans; `--stale`/`--all` require Pro), `restore-originals`, `migrate-from-sir`. 2 Pro: `wp acimg doctor` (extended diagnostics) and `wp acimg reset` (full state reset).

= Pro (license required) =

* **Cloudflare Image Transformations (CIT) delivery** — emits `<img>` with CIT URLs (`/cdn-cgi/image/format=auto`) instead of local derivatives when CIT is enabled on your zone. Defensive automatic fallback to the normal path if CIT is unavailable.
* **Media Library audit + cleanup** — orphan image detector that understands **Bricks Builder** (PHP serialize), `wp_termmeta`, WooCommerce shortcodes, custom meta. Trash mode with typed confirmation, immutable audit log, and an `untrash` endpoint for rollback.
* **Image Health Monitor** — weekly cron that scans for missing derivatives, stale derivatives, Cloudflare Polish conflicts, and storage usage. Email digest to the admin only when there are actionable findings.
* **Image SEO bulk backfill** — fill the alt text, title and caption your library is missing in one pass, with a dry-run preview before anything is written. It only writes where the field is empty and never touches text you wrote yourself. (This is the *fix at scale* for the backlog the free Dashboard counts.)
* **AI Alt Text BYOK** — alt text generation per attachment via OpenAI Vision (`gpt-4o-mini`) or Anthropic Vision (`claude-haiku-4-5`). Bring Your Own Key (no Merchant of Record over AI costs). Context enrichment with WooCommerce product title + category + brand. Complements Image SEO rather than replacing it: use it on the photos where the product name doesn't describe the picture.
* **Catalog Watch + Auto-Heal** — daily cron that detects ghost derivatives (DB rows without files), orphan files (files without DB rows), products with broken thumbnails. Auto-heal with dry-run preview + strict path validation. Persistent admin notice when there are actionable issues.
* **CIT Cost Analyzer** — daily pull from Cloudflare GraphQL Analytics API. MTD cost + monthly projection + peak day to identify spikes. BYO API token (CF authenticates directly).
* **Per-aspect-ratio override** — within a single WordPress size, process each aspect ratio (1:1, 4:3, 16:9, vertical, horizontal) differently: uniform with canvas, proportional without cropping, or skip. Keep square photos proportional while the rest follow the base mode. Wired into upload + bulk regeneration + derivative invalidation. (This is the *fix* for the extreme-padding the free Dashboard flags below.)

= Integration with other plugins =

* **Image Prioritizer / Performance Lab** — Australcode Uniform Images detects `fetchpriority="high"` and propagates correct loading/decoding to the final `<picture>`. Native LCP optimization.
* **Bricks Builder** — supports `wp_get_attachment_image` and optional output buffer rewriter for themes/page builders that bypass that filter.
* **WP Rocket / a3 Lazy Load** — compatible with `data-src`/`data-sizes` lazy loaders (reads both if present).

= v1.0 non-goals =

* **Smart crop** — would break the "no cropping" promise. If you need that, this plugin is not for you.
* **SaaS-only / phone home** — the plugin is 100% local. All optimization happens on your own server.
* **JPEG XL** — format still immature in browsers. We will reevaluate in 2027.

= Why this approach =

* **Padded canvas, never cropped** — every thumbnail pads the product onto a uniform canvas, so the full product is always visible. No subject-guessing, no accidental crops.
* **`<picture>` element, cache-safe by design** — AVIF + WebP + JPEG fallback served without `Vary: Accept`, so it survives any HTTP page cache (LiteSpeed, WP Rocket, Cloudflare APO, W3 Total Cache). The browser picks the format; the cache serves one HTML.
* **AVIF + WebP + JPEG, all free** — modern formats are part of the free core, not a paid add-on.
* **Unlimited bulk on Free** — regenerate your whole catalog with no image cap and no monthly credit quota.
* **100% local** — all processing happens on your own server. No SaaS, no phone-home, your images never leave the site.
* **Built for WooCommerce grids** — reads `wp_get_registered_image_subsizes()` and respects the WC thumbnail-cropping setting, so it targets exactly the product sizes your theme uses.

Australcode Uniform Images does one job: uniform product thumbnails for WooCommerce grids, served cache-safe. It only touches images on registered WooCommerce sizes, so it coexists with any general image optimizer you already run.

Docs, before/after gallery and Pro plans: https://uniform-images.australcode.io

== Installation ==

= Requirements =

* **WooCommerce** — required. This plugin works on WooCommerce product images, so WordPress will not let you activate it unless WooCommerce is installed and active.
* WordPress 6.5+ · PHP 8.2+
* **Imagick** (preferred, with `libheif` for AVIF) or **GD** as fallback.

= Via WordPress Admin (recommended) =

1. Go to **Plugins → Add new** in your WP admin, search for "Australcode Uniform Images" and click Install + Activate.
2. Navigate to the new top-level menu **Australcode Uniform Images → Settings** and choose your quality preset (default Standard works well for most cases).
3. Go to **Australcode Uniform Images → Bulk regenerate**, run a Sample Test with one product to validate visually, then click "Start bulk" to process the full catalog.

Full setup guide, the free-vs-Pro comparison and the Pro license: https://uniform-images.australcode.io

= Via WP-CLI =

~~~
wp plugin install australcode-uniform-images --activate
wp acimg health           # verify everything is OK
wp acimg regenerate       # bulk dry-run
wp acimg regenerate --start --yes   # starts bulk in the background
wp acimg purge-cache --orphans --yes  # clean up derivatives whose image is gone
~~~

Pro adds two more commands — `wp acimg doctor` (extended diagnostics) and `wp acimg reset` (wipes every persisted artifact: the 6 custom tables, options, post meta, transients and derivative directories, without deactivating the plugin). They ship only in the Pro build, so in the free version they are not registered at all. Pro also unlocks the `--stale` and `--all` scopes of `purge-cache`; the free version runs `--orphans`. The destructive ones default to a dry-run that only lists what they would change; pass `--yes` to apply it.

== Frequently Asked Questions ==

= Does Image SEO rename my image files? =

No. Image SEO writes the alt text, title and caption attributes — it does not touch filenames. Renaming a file after upload means rewriting every URL that already points at it, and getting that wrong breaks live product pages. If file renaming is what you're after, this is not the plugin for it.

= What's the difference between Image SEO and the AI alt text feature? =

Image SEO reads your WooCommerce product data — title, category, brand — and builds the alt text from a template. It's instant, free, deterministic, and runs on every upload. AI alt text (Pro) looks at the image itself and describes what it sees, using your own OpenAI or Anthropic key at a fraction of a cent per image.

Most stores run Image SEO on everything and AI only on the images where the product name doesn't describe the picture — a lifestyle shot, a detail close-up, a size comparison. They write the same field, so neither runs automatically on top of the other, and neither overwrites alt text you wrote yourself.

= Will it overwrite the alt text I already wrote? =

No. The automatic path only writes where the field is empty. The plugin also records what it generated, so if you edit an alt text by hand afterwards it's locked — no automatic pass will touch it again. The only way anything gets overwritten is if you click the button on that specific image yourself, or explicitly pick the "replace existing" option on a bulk run — and even then only the alt text is replaced. Titles and captions you wrote are never touched.

= Why is the alt text empty on some images? =

Because they aren't attached to a product. Image SEO builds the text from the product title, category and brand — a logo, a banner or a blog image has no product to read from, so the plugin writes nothing rather than inventing something. The Dashboard counter reflects this: it reports how many images it can actually fill, not how many are missing alt text.

= Do I need WooCommerce? =

Yes. Australcode Uniform Images makes **WooCommerce product images** uniform, so WooCommerce is a hard requirement: the plugin declares `Requires Plugins: woocommerce` and WordPress blocks activation if WooCommerce is not installed and active. It is not a general-purpose image optimizer.

= Is there a limit on how many images I can process? =

No. Not on the free version, not on Pro. There is no image cap, no monthly credit quota and no per-site allowance to run out of — the bulk run walks your entire catalog, ten products or thirty thousand. It processes through Action Scheduler in batches, so it survives a worker being killed mid-run and picks up where it stopped instead of starting over.

What the size of your catalog does cost you is **disk**: roughly 700 KB of derivatives per original at the Standard preset. A thousand products with one image each is about 700 MB. That is the real constraint, and it is your server's, not a licence's.

= Do I have to pay to get WebP or AVIF? =

No. AVIF, WebP and JPEG are generated by the free version and served together in a real `<picture>` element, with the browser choosing. Modern formats are part of the core of this plugin, not an upgrade — if AVIF were behind a licence, the free version would be shipping the heaviest possible image, which defeats the point.

What Pro adds on top of delivery is different in kind: serving through the Cloudflare edge, the library audit, the weekly health monitor, AI alt text and the per-aspect-ratio override. The formats themselves are never gated.

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

Yes. Compatibility with `custom_order_tables` and `cart_checkout_blocks` is declared from day one via `before_woocommerce_init`. No manual configuration required.

= Does it work with Cloudflare? =

Yes, and well. The plugin emits `<picture>` with `<source>` per MIME type — **it survives Cloudflare APO** and any page cache perfectly because it does NOT use `Vary: Accept` (which would break caching).

If you have **Cloudflare Pro+**, you can enable **Image Transformations** from Settings → CDN edge. The plugin automatically detects whether CIT is available on your zone and emits `/cdn-cgi/image/` URLs that CF transforms on-demand with `format=auto`.

**Caution with Cloudflare Polish**: if active, it re-compresses the AVIF/WebP that the plugin already optimized — double compression equals visual artifacts. The plugin detects this on the Health page and alerts you with an admin notice. Recommendation: disable Polish, keep APO.

= Which image engine does it use? =

**Imagick** (preferred) with `libheif` for AVIF. **GD** as fallback. **libvips** is wired in the code but requires bundled binaries (not included in v0.x — future sprint). The active engine and its capabilities (read/write per format) appear on **Health → Image engines**.

= Do I need anything special for AVIF? =

Imagick compiled with `libheif` (common on modern hosts: Kinsta, WP Engine, SiteGround, recent Cloudways). If Imagick does not support AVIF, the plugin still emits WebP + JPEG and the Health page reports the limitation. **You do not get stuck without anything** — you just lose the most efficient format.

= How much storage does it use? =

Estimate: ~700 KB of derivatives on average per original image at Standard preset (varies a lot by content type — product photos with a uniform background compress better). For a catalog of 1,000 products with one image each, that's ~700 MB of derivatives. By default the plugin **never modifies your originals** — derivatives are separate files, so you need space for original + derivatives. The only exception is the optional "apply trim to original" setting (off by default), which backs up the pristine file first and is reversible with `wp acimg restore-originals`.

= How do I regenerate after changing settings? =

Change settings on **Australcode Uniform Images → Settings**, save (the plugin shows a modal warning you that existing derivatives are invalidated), and then go to **Bulk regenerate** or run `wp acimg regenerate --start --yes`. The plugin keeps serving the old derivatives until regeneration completes — **zero visual downtime**.

= Conflicts with Smush / EWWW / ShortPixel / Imagify? =

Yes, **disable those plugins** before installing Australcode Uniform Images. The Health → Doctor page detects them and alerts you. The typical conflict is that those plugins also hook into `wp_get_attachment_image` and rewrite the HTML — the output ends up inconsistent.

= Does it have WP-CLI? =

Yes, 9 commands under `wp acimg <subcommand>`. Seven ship in the free version: health (CI smoke test), stats, regenerate, derivatives <id>, purge-cache, restore-originals, and migrate-from-sir. Two more ship only with Pro: doctor (extended diagnostics) and reset (resets all persisted state without deactivating). Each command supports `--format=json`, and `wp help acimg` lists the commands available in your build.

= How do I report a bug or request a feature? =

Open a support thread in the WordPress.org plugin support forum — the plugin menu links straight to it. For Pro license-related issues, use the "Contact Us" screen in the plugin menu, or email info@australcode.io.

== External services ==

This plugin connects to external services **only when you explicitly enable or
use the corresponding optional feature**. The free, core functionality (uniform
images, AVIF/WebP/JPEG `<picture>`, bulk regenerate, health) runs **100% on your
own server and contacts no external service**.

= Freemius (account, licensing & secure checkout) =

Australcode uses Freemius as its billing and licensing platform (Freemius is the
Merchant of Record for Pro purchases). Connecting to Freemius is **optional**: on
activation you can click **"Skip"** and the plugin stays fully functional without
ever contacting Freemius.

* If you **opt in** (free or Pro), the plugin sends your **admin email**, **site
  URL** and basic **environment data** (WordPress, PHP and plugin versions) to the
  Freemius API (host `api.freemius.com`, path `/v1/`) to create your account and
  check for updates.
* If you **activate a Pro license**, the plugin sends your **license key** and
  **site domain** to Freemius to validate the license and enforce the per-plan
  site limit. The license is re-validated periodically.
* Purchases and license management happen on Freemius-hosted pages
  (hosts `freemius.com`, `users.freemius.com`).
* The **"Support Forum"** menu entry is a plain link to this plugin's forum on
  WordPress.org (`wordpress.org/support/plugin/australcode-uniform-images`). It
  opens in a new tab and sends nothing to us or to Freemius.
* The **"Contact Us"** menu entry opens Freemius' contact form. In the free build
  it is an external link, and opening it sends — **in the URL itself, at click
  time, before you type anything** — your **site URL**, your **login URL**, the
  **plugin ID, public key and version**, and, if you opted in, your Freemius
  install identifier. Whatever you then write in the form (your **name**, **email
  address** and **message**) is sent when you submit it. Nothing is sent if you
  never open it.

Apart from opening "Contact Us", no data is sent to Freemius if you skip the
opt-in and do not enter a license key.

* Terms of Service: https://freemius.com/terms/
* Privacy Policy: https://freemius.com/privacy/

= OpenAI / Anthropic (AI Alt Text — Pro, Bring Your Own Key) =

If you enable **AI Alt Text** and provide your own API key, the plugin sends a
**copy of the image** (a JPEG of at most 1024 px, as a base64 data URI, with its
metadata removed — camera data and GPS location are not sent) plus the related
**WooCommerce product context** (title, category, brand) to the provider you choose,
to generate alt text. API use is governed by each provider's business terms:

* OpenAI Vision (`gpt-4o-mini`) — host `api.openai.com`, path `/v1/chat/completions`
  * Terms: https://openai.com/policies/services-agreement/
  * Privacy: https://openai.com/policies/privacy-policy
* Anthropic Vision (`claude-haiku-4-5`) — host `api.anthropic.com`, path `/v1/messages`
  * Terms: https://www.anthropic.com/legal/commercial-terms
  * Privacy: https://www.anthropic.com/legal/privacy

This runs only when you trigger alt text generation and only with the key you
supply. The plugin is not a Merchant of Record for these AI costs (BYOK).

= Cloudflare (Image Transformations + Cost Analyzer — Pro) =

If you enable **Cloudflare Image Transformations (CIT)**, image URLs of your own
site are served through Cloudflare's edge (`/cdn-cgi/image/...`) so Cloudflare can
transform them on-demand. If you enable the **CIT Cost Analyzer**, the plugin
queries the Cloudflare GraphQL Analytics API
(host `api.cloudflare.com`, path `/client/v4/graphql`) with the API token **you provide**
and your zone identifier, to report usage and cost. The plugin also performs a
`HEAD` request to one of **your own** derivative URLs to detect whether CIT and
Cloudflare Polish are active on your zone (no third-party data is sent in that
detection).

* Terms of Service: https://www.cloudflare.com/terms/
* Privacy Policy: https://www.cloudflare.com/privacypolicy/

== Screenshots ==

1. **Before / After** — the theme crops product photos with mismatched proportions to a square and cuts off part of each product: the bottle's cap, the arms of the sunglasses, the backpack's handle (left). Australcode pads them onto a uniform canvas instead, so the full product shows (right).
2. **Dashboard** — catalog metrics: processed images, generated derivatives, storage used, % savings when serving AVIF vs original, active quality preset, current bulk run, plus at-a-glance counts of images missing alt text, heavily padded, and full-bleed.
3. **Settings** — every option on one page, with section navigation: general, processing, formats & quality, sizes, context filter, frontend delivery, original file, CDN edge, preview, Image SEO and AI alt text. Includes whether uninstalling keeps or deletes the plugin's data.
4. **Bulk regenerate** — test a sample of 1–5 images first, then process the whole catalog in the background, in small batches you can cancel at any time.
5. **Health** — a one-line verdict, then the environment (PHP, WordPress, WooCommerce, image engine, memory, theme) and the plugin's database tables with their row counts. Further down: codec support per engine, migrations and CDN edge detection.
6. **Clean library** — orphan image audit with Bricks Builder + wp_termmeta + WooCommerce shortcodes detection. Trash mode with typed confirmation and audit log. *This screen ships with the Pro license; it is not part of the free download.*
7. **Catalog Watch** — daily cron that detects ghost derivatives, orphan files, and products with broken images. Auto-heal with dry-run preview before applying. *This screen ships with the Pro license; it is not part of the free download.*
8. **AI Alt Text** (Pro, BYOK) — generate accessible alt text per image via OpenAI or Anthropic Vision, enriched with the WooCommerce product title, category and brand. Your own key, no markup.

== Changelog ==

= 1.0.4 =

* **Fix: "Keep full-bleed photos" now also works with the content size at 100% and no trim padding.** In that setup the check that tells a full-bleed photo from a cutout never ran, so full-bleed photos still got white bands: an 800×800 photo came out as 1024×1024 with 112 px bands per side. They now keep their own shape, as in 1.0.3. Nothing is cropped.
* The help text of "Keep full-bleed photos" now says that those photos keep their own shape, so a portrait photo stays portrait in a square size.
* Every site with "Keep full-bleed photos" enabled sees its derivatives marked outdated; regenerate to apply. Sites without it need nothing.

= 1.0.3 =

* **Fix: "Keep full-bleed photos" no longer pads them to a larger or differently shaped canvas.** A full-bleed photo smaller than a size, or with another shape, was centered on that size's canvas: an 800×800 photo came out as 1024×1024 with 112 px white bands, and a portrait photo got bands on both sides. Full-bleed photos now keep their own proportions and are never made larger than the original, so no white bands and no oversized candidates in the srcset. Nothing is cropped. In a product grid without `object-fit`, a portrait full-bleed photo now stands taller than its neighbors. Only sites with the setting enabled see their derivatives marked outdated; regenerate to apply.

= 1.0.2 =

* **Fix: the `<picture>` wrapper no longer shifts theme layouts.** Since 1.0.0 the wrapper uses `display: contents`, which also turned its `<source>` elements into flex and grid items: in a grid they took a cell, and in a flex container with a gap they added space before the photo. They are now hidden, still at zero specificity so any theme rule wins, and the browser keeps choosing the same AVIF or WebP file. Measured on a live store: a mini-cart row squeezed the product name into a 0 px column, and a category card photo dropped 30 px.
* **Fix: "Keep full-bleed photos" no longer frames them with the trim padding.** The trim added its padding even when there was nothing to crop, so a photo that fills the frame came out with a white border — 5 px per side at 768 px with the default 8 px padding. Full-bleed photos now get no padding, also with the content size at 100% and with "Apply trim to the original" on. Only sites with "Keep full-bleed photos" enabled see their derivatives marked outdated; regenerate to apply.

= 1.0.1 =

* **Fix: a variation picked in a quick view now shows its own photo.** Page builders that open products in a popup (Bricks, for one) load WooCommerce's variation script after the plugin had already checked for it, so its companion script never loaded and the popup kept the main product photo. The check now runs at the last moment before the footer scripts print. Measured on a live store: 9 of 81 quick-view tests failed before, across search, shop, home and cart.
* **Fix: the `acimg_should_wrap_in_picture` filter now also covers variation data.** Turning it off kept the gallery on the original photo, but picking a variation still switched to the optimized file — a different geometry, or a 404 where those files were not on disk. Both now follow the filter.

= 1.0.0 =

**Breaking change — Multi-Site Manager is removed and the paid plans collapse to a single Pro tier.** The Agency plan is gone; every paid feature now lives in Pro. If you held an Agency licence it has been migrated to Pro, and everything it unlocked except the client-site dashboard is unchanged.

**Nothing that touches your images moves.** Same settings, same derivatives, same pipeline — no regeneration is triggered by this upgrade.

**New name in the directory: "Australcode Uniform Images – Product Image Resize Without Cropping for WooCommerce".** Same plugin, same slug and same menu entry — the longer name just says what it does.

* **Images centred with `margin: auto` inside a flex or grid container are no longer pushed off-centre.** Wrapping the image in `<picture>` used to change which element the layout sees, so the theme's centring stopped applying. Measured on a live site: an image sat 61 px to the left of where it belonged, on six pages. The wrapper is now transparent to layout, and the images land exactly where they do without the plugin.
* **Photos placed through a page builder now get processed.** Images that Bricks, Elementor, Beaver or Oxygen reference in a template were being skipped, so they never received AVIF or WebP — while the bulk planner counted them as in use. The two now agree.
* **New: `wp acimg regenerate --ids=12,34,56`.** Reprocess a specific handful of images without planning a full catalog run. It deliberately skips the context filter, which is the point: it is for the images the filter would otherwise skip.
* **Picking a variation now shows that variation's photo.** Choosing "Blue" swapped the image file underneath, but the browser kept painting the AVIF of the main product photo — so the shopper picked one colour and saw another. The product gallery also stopped sliding to the variation's photo. Both are fixed, and the variations data now serves your optimized files, including the full-size image the lightbox opens.
* **Fix (data): "Move to trash" in Media Cleanup now really uses the trash.** On a standard WordPress install it deleted the images and their files permanently, and the rollback had nothing to restore. It now moves them to the trash for as many days as your site keeps it, and the link on the results screen opens the trash view that actually lists them. On a site with the trash turned off, it refuses instead of deleting.
* **Fix (data): replacing alt text no longer replaces your titles and captions.** Ticking "Also replace alt text I wrote myself" on a bulk Image SEO run — or clicking "Fill from product data" on one image — also overwrote hand-written attachment titles and captions. Only the alt text is replaced now.
* **Fix: no more black frame on trimmed JPEGs.** Trimming a JPEG could leave an 8 px black border, on the derivatives and on the original when "apply trim to original" is on. The padding now takes your background colour.
* **"Prevent upscaling" now does what it says.** It was on by default and had no effect: small images were enlarged anyway, with a blocky resize. With it on, a photo smaller than the target keeps its size and is centred on the canvas; turn it off to enlarge, now with a smooth (Lanczos) resize. Regenerate to apply.
* **The AVIF quality setting now takes effect.** Every quality preset produced byte-identical AVIF files. Regenerate to apply your preset to existing images.
* **Servers that list AVIF but cannot write it are detected.** Some ImageMagick installs advertise AVIF with no encoder behind it, so every image attempted an AVIF that came out empty. The plugin now test-encodes once and skips AVIF there.
* **Modern formats now reach images outside the product catalog.** Delivery was limited to three WooCommerce sizes, so a photo in a blog post stayed unoptimized even when its derivatives existed. Any size with a derivative is now served properly.
* **New: choose uniform or proportional per content type.** A landscape blog photo and a landscape packshot want opposite treatment, and until now the mode could only be set per size for the whole site. It reaches featured images uploaded from the Media Library too, and the sample test shows the mode each size actually gets. Pro.
* **When the image engine cannot trim, it says so.** On a server without ImageMagick the trim silently did not happen and products came out at the wrong scale with no warning anywhere. It is now reported on the Health screen.
* **Fix (data): uninstalling or resetting no longer deletes the backups of your trimmed originals.** Those backups are the only way back to the untrimmed photo; they are now kept (`wp acimg reset` deletes them only with `--delete-backups`), so `wp acimg restore-originals` still works after a reinstall.
* **Fix (data): `wp acimg purge-cache` only ever deletes generated files.** A stale database row could point it at an original upload; it now refuses anything outside the plugin's derivatives folder. `--stale` also stopped deleting files a current derivative still used.
* **Uninstalling now removes everything the plugin created** — settings, generated images, tables, logs and scheduled tasks; before, eleven kinds of leftovers stayed behind. New in Settings: **Keep the plugin's data**, for when you plan to reinstall.
* **Privacy: photos sent to the AI alt-text provider no longer carry their metadata.** The plugin sends a small copy with EXIF stripped, so a phone photo's GPS location stays on your server. Pro.
* **Variation images and large products now get alt text.** Images of product variations were skipped, and products with more than 30 images stopped at the thirtieth.
* **Before a bulk AI alt-text run, you see how many paid requests it will make** and which provider, and confirm. Pro.
* **A bulk run no longer freezes if the background worker dies.** Stuck items are retried and then reported as failed, and two runs started at the same moment no longer both start.
* **Your storefront stops paying for the plugin's schedules.** The recurring checks used to query the task scheduler on every page view; they now run only in background requests.
* **Photos taken sideways come out upright on servers using GD**, and two images could no longer overwrite each other's derivative folder.
* **Health counts outdated images correctly.** It reported every derivative as pending regeneration — and running the regeneration could not clear the warning. It now counts only the ones made with older settings.
* **Health shows the plugin's background events** (an image processed from a smaller copy for lack of memory, a partial failure) instead of logging them where nobody could see them.
* **Cloudflare Image Transformations delivery now applies the uniform canvas and sets width and height.** Pro.
* **The plugin's notices no longer appear on the license and account screens.**
* **WP-CLI:** every message is in English, and `--yes` is honoured when there is no terminal to confirm in.
* Housekeeping: the deprecation notices emitted on PHP 8.5 are gone, and static analysis is back to zero.

= 0.45.0 =
* **The paid features now leave the menu when a license expires.** Clean library, Catalog Watch, Multi-Site Manager and CIT Cost stayed in the sidebar after a license lapsed, and each one opened onto a screen that only offered to sell you the plugin again. They are now hidden while the license is inactive and come straight back when it is renewed. The pages themselves still answer if you have one bookmarked, so no link you saved breaks.
* **An expired license is asked to renew, not to buy.** The license screen could not tell "never paid" apart from "paid and expired", so a customer whose subscription had lapsed was shown the same pitch as a brand-new visitor — offering to sell what they already own. It now recognizes the expired state and points at renewal.
* **The license screen lists everything the paid plans include.** It listed 8 of the 11 paid capabilities, quietly leaving out the weekly Image Health Monitor, the Pro WP-CLI commands and priority email support. All eleven are listed now.
* **The Pro WP-CLI commands are no longer part of the free download.** `wp acimg doctor` and `wp acimg reset` used to ship inside the free version and refuse to run without a license. They now ship only in the Pro build, so on the free version they are simply not registered — `wp acimg doctor` reports an unknown command instead of a license error. The advanced `purge-cache` scopes (`--stale` / `--all`) still appear in the free command's help and point you to Pro when used; only `--orphans` runs there, as before. Nothing changes for Pro users, and every free command (`health`, `stats`, `regenerate`, `derivatives`, `purge-cache --orphans`, `restore-originals`, `migrate-from-sir`) works exactly as before.
* **WooCommerce is now stated as a requirement everywhere.** The plugin has always required WooCommerce — WordPress blocks activation without it — but the readme and the website never said so. They do now.

= 0.44.2 =
* **Fix: the Clean library audit could report more images in use than you have.** The counter behind "In use" was built from every reference the plugin finds, including numbers that are not images at all — a stored timestamp could be counted as an image in use. On a library of 8 images it reported 10 in use, which is impossible and made the whole panel hard to trust. The count is now restricted to your actual images, so "in use" plus "orphaned" always adds up to your total. This only affected the number shown: the list of orphaned images was always correct, and nothing was ever deleted because of it.

= 0.44.1 =
* **Fix: product images could still render stretched — this is the root cause.** Earlier releases fixed two contributing causes; this one fixes the origin. The `<img>` the plugin serves was not declaring its width and height, so the browser had no shape to reserve and themes that impose no aspect ratio of their own drew the image at whatever height the slot allowed — a square product image could render as a tall strip. Themes that set their own aspect ratio were masking it, which is why it only showed on some sites. The image now declares the dimensions of the file it actually serves. Safe in-place upgrade — no regeneration, no settings change.
* **Fix: the fallback image could point at the wrong shape.** Its `srcset` still listed WordPress' original crops instead of the uniform ones the plugin generates, so a browser could pick a file whose proportions contradicted the image it belonged to.
* **Fix: deleting a product image now cleans up after itself.** Nothing was listening for deletions, so the generated files and their database rows stayed behind forever — the cleanup tool existed, but nothing ever triggered it. Shared files are ref-counted and preserved: a file used by another size is never removed.
* **Fix: an image that fails to process no longer fails silently.** Until now a corrupt or unreadable image produced no derivatives and no trace anywhere — not in the Errors page, not in the health log. You saw an image that never got uniformed with no way to find out why. These failures are now recorded with a reason, and show up in the Errors page labelled "On upload".
* **Fix (accessibility): the setup dialog is usable with a keyboard.** It had no dialog semantics, did not move focus, could not be closed with Escape, and let Tab move focus onto controls hidden behind the overlay. It now announces itself as a dialog, traps focus, closes with Escape and returns focus where it was.

= 0.44.0 =
* **Fix: product images no longer break your theme's layout.** This closes the second — and last — cause of the oversized images that 0.43.0 started fixing. The `<picture>` wrapper the plugin adds was invisible to your theme's CSS, so the height your theme sets on an image was measured against the wrapper instead of the card it sits in. The wrapper now behaves like the image it replaces.
* **New: the wrapper carries a class (`acimg-picture`)**, so your theme can style it directly. If your theme already worked around this, your own styling still wins over the plugin's.
* Admin polish: on narrow screens the dashboard cards no longer force sideways scrolling, and the Clean library steps wrap instead of running off the edge.

= 0.43.0 =
* **Fix (accessibility): images no longer lose `alt=""`, `aria-hidden` or `role` when served as `<picture>`.** The rewrite kept only a fixed list of attributes and dropped empty values, so a decorative image marked `alt=""` reached the browser without it — screen readers announced the filename instead of skipping the image. Every attribute your theme sets is now passed through untouched.
* **Fix: images could render stretched inside sliders and carousels on WordPress 6.7+.** A square 768px photo was being drawn as 500×1500 on a live store. WordPress 6.7 adds an `auto` token to the `sizes` of lazy images that is meant for the `<img>` tag only; the plugin was copying it into the `<source>` elements of the `<picture>`, where it made the browser resolve the wrong shape. The `<img>` keeps the token, the `<source>` copies no longer do — no settings change and no regeneration needed.
* **Fix (licensing): the Pro screens no longer light up before the Pro build is installed.** Right after buying, the license is valid but the site is still running the free build — the plugin now waits until the Pro code is actually installed, instead of showing Pro screens backed by nothing.
* **Improved: the licensing screen speaks to the person reading it.** If you already own Pro, the menu entry reads "License" instead of "Upgrade" and the page confirms what you have running, rather than pitching what you already paid for.
* **Improved: support is one click away.** The plugin menu now links straight to this plugin's support forum on WordPress.org, instead of leaving you to find it.
* Admin polish: the license box no longer squashes on narrow screens, long tab bars scroll instead of overflowing, and keyboard focus is visible again on every control.

= 0.42.0 =
* **New: Image SEO (free).** Alt text, title and caption built from your WooCommerce product data — title, category and brand — instead of from the filename. Runs on every new upload, deterministic, no AI and no API calls. Off by default: nothing is written until you turn it on.
* **New: "Missing alt text" on the Dashboard (free).** Counts how many product images Image SEO can actually fill, and says plainly how many aren't attached to a product — because those have nothing to read from and no upgrade fixes that.
* **New: Image SEO bulk backfill (Pro).** Fills the alt text your library is missing in one pass, with a dry-run preview of real rows before anything is written.
* **Fix (data): AI alt text no longer overwrites alt text you wrote yourself.** The bulk action applied the generated text unconditionally, replacing hand-written alt text. It now skips those images unless you explicitly pick the new "replace existing alt text" action — and it skips them *before* calling the provider, so no tokens are spent on a result that gets discarded.
* **Improved: AI alt text now gets product context on gallery and variation images.** It previously only recognized featured images; the rest reached the model with no context at all.
* The media modal now has a single "Image SEO" field with both buttons ("Fill from product data" and, with a key configured, "Generate with AI") instead of two separate rows writing the same field.

= 0.41.0 =
* **New: full-bleed photo detection (free).** The plugin now recognizes photos that already fill the frame — lifestyle shots, banners — as opposed to product cutouts on a plain background. The Dashboard and Health page flag how many are being shrunk and framed in white, with an honest "N of M analyzed" count.
* **New: "Keep full-bleed photos" setting (opt-in).** Turn it on and those photos are served edge to edge instead of scaled down to the content size; real cutouts are still scaled as before. Off by default — your existing derivatives are untouched until you enable it.
* **New: the "Content size (%)" is now editable in Settings.** It controls how much of the canvas the subject fills in Uniform sizes (85% leaves a margin, 100% fills edge to edge). It was previously fixed and only reachable via WP-CLI.
* **Fix (data): saving Settings no longer resets the content size.** Sites that had customized it — for example after migrating from Perfect Image Resize, where it defaulted to 100% — silently lost the value on every save and needlessly regenerated their whole catalog. Fixed.
* **Fix: with "Apply trim to the original" enabled, full-bleed photos are no longer re-encoded.** When the trim finds nothing to crop, the original is now left byte-for-byte untouched instead of being re-saved at a lower quality with no benefit.
* Internal: static-analysis cleanups. No breaking changes — safe in-place upgrade.

= 0.40.1 =
* Maintenance: removed the "More by Australcode" cross-promotion section from the Upgrade screen.

= 0.40.0 =
* **New: "Heavily padded" detection (free).** The Dashboard now flags product photos that get squared with a lot of empty canvas — panoramic or very tall images — and a gentle, dismissible notice points you to how each aspect ratio is handled. Detection is free; the per-aspect fix is a Pro upgrade.
* **Admin theme now fully matches the Australcode family.** Every page header shows the product app-icon and the sidebar menu icon recolors like a native WordPress icon (de-forked onto the shared Admin Kit) — consistent chrome across every Australcode plugin.
* **Polish:** the Upgrade page header gained the product icon + a one-line intro, the Catalog Watch and Multi-Site tables now scroll cleanly on mobile instead of squashing, smoother transitions throughout, and the Freemius account screen picks up the green accent.
* No data or settings changes — safe in-place upgrade.

= 0.39.1 =
* **New "Upgrade" page** replaces the separate "Family" submenu — it bundles the Pro feature pitch and the "More by Australcode" cross-sell into a single, less intrusive growth surface (the family catalog still lives there, ad-free).
* **Refined menu icon** — the sidebar now shows the product glyph (the 2×2 uniform-grid mark), matching the plugin icon for a consistent identity.
* **Completed Spanish translations** for the Pro weekly health digest email, the health severity labels, and the Cloudflare cost analyzer placeholders.
* **Housekeeping:** SPDX license identifier in the headers, refreshed static-analysis tooling (PHPStan 2.x), and documentation tidy-up. No behavior changes for the free version.

= 0.39.0 =
* **New look: the Australcode admin theme.** Every plugin page now ships the Australcode Admin Kit — same typography, spacing and the new green accent across the whole plugin family. No settings change; purely visual.
* **English-first interface.** The admin UI source language is now English, with complete Spanish translations bundled (`es_ES` and `es_CL`, 100% coverage). WordPress picks your site language automatically.
* **New "Family" page** — discover the other Australcode plugins, see which ones you already run, and install the free ones in one click. No ads, no admin notices — it lives quietly as the last submenu.
* **Fixed:** PHP notices when the premium and free builds are swapped in place (constant guards).
* **Fixed:** the WooCommerce cropping how-to path in Settings was untranslatable.

= 0.38.0 =
* **Licensing and checkout moved to Freemius.** Pro licensing, purchases and the in-plugin account now run through Freemius — our new billing platform and Merchant of Record — replacing the previous provider. Image processing, bulk regeneration and delivery are unchanged; this release only swaps the billing/licensing layer.
* **Privacy-first, optional opt-in.** The connection screen is fully WordPress.org-compliant: clicking "Skip" keeps every free feature working and sends no data to any external service.
* **The free version stays 100% functional** with no account required.
* **Updated the "External services" disclosure** in this readme to reflect the move from Lemon Squeezy to Freemius.

= 0.37.0 =
* **`wp acimg migrate-from-sir` is now free** — import your Smart Image Resize settings (background color, trim, sizes) into Australcode with one command. Makes switching effortless.
* **New output-format controls (free)** — force JPEG-only or PNG-only output per your needs, plus an optional delivery filesystem check on the Health page. Off by default; existing sites unaffected.
* **Catalog Watch repair now runs in the background** — on large catalogs the repair (and its preview) no longer time out behind Cloudflare. Progress is shown and you can close the page; the job continues. (Pro)
* **Cleanup, Multi-Site and CIT Cost pages redesigned** — Cleanup is now a 3-step wizard (Audit → Review → Move), Multi-Site has a guided empty state, and CIT Cost leads with a budget KPI + projection.
* **Fixed** — Settings page polish: the AI Alt Text and aspect-ratio override sections now align with the settings column, and their "Pro" badges correctly disappear once a Pro license is active.

= 0.36.1 =
* Page-builder coverage extended to Beaver Builder (`_fl_builder_data`) and Oxygen (`ct_builder_shortcodes`). The detection now combines two strategies per builder: structured ID extraction (precise for Bricks/Elementor) and a universal URL scan of the builder's stored markup (covers Beaver, Oxygen, and any builder that embeds the image URL). Divi/WPBakery remain covered via `post_content`.

= 0.36.0 =
* Page-builder coverage: Bulk Regenerate ("All used images" scope) now detects images placed directly in Bricks and Elementor templates — they live in the builder's own storage (`_bricks_page_content`, `_elementor_data`), outside `post_content`, so previous versions never found or optimized them. Divi was already covered (it stores in `post_content`). The frontend delivery already handled any `<img>` from any builder; this closes the generation side.
* Bulk runs now process exactly what the chosen scope selected: the "All used images" scope optimizes every used image (products and non-products alike), instead of silently skipping images whose post type isn't in the processing filter. The per-type filter still governs automatic processing on upload.

= 0.35.0 =
* New (opt-in): auto-detect transparency. When enabled, images whose original has significant transparency (logos, cut-out products with shadows) keep their alpha channel — the plugin uses a transparent canvas and a PNG raster fallback instead of flattening onto the canvas color. WebP and AVIF already preserve transparency. Off by default, so existing sites are unaffected until you turn it on (which regenerates derivatives). Set it in Settings → Formats & quality → Transparency.

= 0.34.1 =
* Internationalization: the admin JavaScript layer is now fully translatable. All dynamic UI text in Bulk Regenerate and Clean Library (counters, confirmations, progress, summaries, error messages) — plus the previously localized Catalog Watch, Multi-Site, CIT Cost Analyzer and settings preview — now routes through WordPress translations instead of hardcoded strings.
* The English (en_US) translation bundle is now 100% complete.
* No behavior changes: every string keeps its original fallback until the site's active language provides a translation.

= 0.34.0 =
* Pro: new per-aspect-ratio mode overrides. For each main WooCommerce size you can choose how each aspect ratio is processed (square 1:1, 4:3, 16:9, portrait, etc.): uniform with canvas, proportional without cropping, or skip. For example, keep square images on "proportional" while the rest of the size follows its base mode. Available on all paid plans.
* The per-aspect override is wired into the generation pipeline (upload and bulk regeneration) and into derivative invalidation, so changing it regenerates only what is needed.
* Visibility: the Dashboard now shows how many images were skipped by the context filter (not attached to a processable post type), with a shortcut to adjust the filter. Avoids the "I uploaded an image and nothing happened" confusion.

= 0.33.2 =
* Security: REST endpoints that operate on attachments now verify the edit/delete capability per attachment (current_user_can edit_post/delete_post), in addition to the general manage_woocommerce gate.

= 0.33.1 =

WP.org re-submission compliance + listing polish. No user-facing behavior changes — safe in-place upgrade.

* **External services URLs no longer pingable as 404** — the readme's "External services" section referenced API endpoints (`api.lemonsqueezy.com`, `api.openai.com`, `api.anthropic.com`, `api.cloudflare.com`) as full URLs. WP.org's review scanner pings every URL in the readme and flagged the Lemon Squeezy endpoint as a broken Terms/Privacy URL. Endpoints are now declared as host + path (not full clickable URLs); Terms/Privacy links remain and resolve.
* **Explicit output-buffer close** — `OutputBufferHook` (opt-in feature, off by default) now closes its `ob_start()` buffer explicitly on `shutdown`, guarded by `ob_get_level()`. Same rewritten output, paired open/close as WP.org guidelines expect. WP 6.5+ compatible.
* **Banners + branding** — re-generated WP.org banners with the current name (Australcode Uniform Images) and version; renamed the last legacy global variable in the bootstrap file.
* **Listing polish** — sharper short description and tags for the target niche, added Upgrade Notice, fixed screenshot #8 caption, aligned WP-CLI command count between Description and FAQ.

Internal: 296/296 unit tests passing.

Full history of older versions (0.33.0 and earlier) is preserved in the project's archive — contact info@australcode.io if you need detail on a specific legacy version.

== Upgrade Notice ==

= 1.0.4 =
With Keep full-bleed photos on, those photos now keep their own shape also at a 100% content size with no trim padding. Regenerate after updating if you use that setting; nothing else needs regeneration.

= 1.0.3 =
With Keep full-bleed photos on, those photos now keep their own shape and size instead of getting white bands on a larger canvas. Regenerate after updating if you use that setting; nothing else needs regeneration.

= 1.0.2 =
Fixes a layout shift from the `<picture>` wrapper in flex and grid themes, and removes the white frame around full-bleed photos. With Keep full-bleed photos on, regenerate after updating; nothing else needs regeneration.

= 1.0.1 =
Fixes variation photos in page-builder quick views (Bricks and others), and makes the `acimg_should_wrap_in_picture` filter cover variation data too. Safe in-place upgrade, no regeneration.

= 1.0.0 =
Breaking change: Multi-Site Manager is removed and the paid plans collapse to a single Pro tier. Everything that touches your images is unchanged — same settings, same derivatives, no regeneration. Only the client-site monitoring dashboard is gone; nothing else in the plugin moves.

= 0.45.0 =
Housekeeping for paid plans: Pro sections now leave the menu when a license expires, and an expired license is asked to renew instead of to buy. The Pro-only WP-CLI commands moved out of the free build. Every free command is unchanged. Safe in-place upgrade, no regeneration.

= 0.44.2 =
Fixes the Clean library audit reporting more images in use than exist in your library, which made the counts impossible to reconcile. Only the displayed number was affected: the orphaned list was always correct and nothing was deleted because of it. Safe in-place upgrade, no regeneration.

= 0.44.1 =
Fixes stretched product images at the root: the image now declares its own width and height, so themes without aspect-ratio rules draw it correctly. Also cleans up files when an image is deleted, and logs images that fail to process. Safe in-place upgrade, no regeneration.

= 0.44.0 =
Fixes product images breaking theme layouts: the `<picture>` wrapper the plugin adds was invisible to theme CSS, so image sizing resolved against it instead of the surrounding slot. Safe in-place upgrade — no regeneration, no settings change.

= 0.43.0 =
Accessibility fix: decorative images lost their empty alt on the way to the browser, so screen readers read the filename instead of skipping the image. Also fixes images rendering stretched in sliders on WordPress 6.7+. Safe in-place upgrade.

= 0.42.0 =
New free Image SEO: alt text, title and caption from your product data on every upload. Fixes a bug where the AI alt text bulk overwrote alt text you had written by hand. Image SEO ships off by default — nothing is written until you turn it on.

= 0.41.0 =
New free detection for full-bleed photos being shrunk and framed in white, plus an opt-in setting to serve them edge to edge. Fixes a bug where saving Settings silently reset the content size and regenerated the catalog. Safe in-place upgrade — nothing changes until you opt in.

= 0.40.1 =
Maintenance: removed the "More by Australcode" cross-promotion section from the Upgrade screen. Safe in-place upgrade — no settings or data changes.

= 0.40.0 =
New free "heavily padded" detection on the Dashboard flags panoramic or very tall product photos that get squared with empty canvas. The admin theme now fully matches the Australcode family. No data or settings changes — safe in-place upgrade.

= 0.39.1 =
Polish release: the "Family" submenu is now the unified "Upgrade" page (Pro pitch + "More by Australcode" in one place), a refined menu icon, and completed Spanish translations for the Pro weekly digest. Safe in-place upgrade — no settings or data changes.

= 0.39.0 =
Visual refresh: the new Australcode admin theme (green accent) + English-first UI with full Spanish translations included. No data or settings changes — safe in-place upgrade.

= 0.38.0 =
Pro licensing and checkout now run through Freemius (our new billing platform). The free version is unchanged and 100% functional — the connection screen is optional, so you can safely click "Skip". Safe in-place upgrade.

= 0.37.0 =
Catalog Watch repair now runs in the background — no more timeouts on large catalogs. New free output-format controls and a free Smart Image Resize import command. Safe in-place upgrade.

= 0.36.1 =
Page-builder detection now also covers Beaver Builder and Oxygen. Run a Bulk Regenerate ("All used images" scope) to optimize images placed in their templates.

= 0.36.0 =
Bulk regeneration now detects images placed in Bricks and Elementor templates (page-builder content). Run a Bulk Regenerate ("All used images" scope) to optimize them.

= 0.35.0 =
New opt-in setting: auto-detect transparency. Off by default — existing sites are unaffected. Enabling it regenerates derivatives.

= 0.34.1 =
The admin UI is now fully translatable (English bundle 100%). No behavior changes — safe in-place upgrade.

= 0.33.1 =
WordPress.org compliance and listing fixes. No user-facing behavior changes. Safe in-place upgrade.
