=== AlpineConsent ===
Contributors: alpineconsent
Tags: cookie consent, gdpr, privacy, consent management, cookie banner
Requires at least: 6.0
Tested up to: 6.6
Requires PHP: 8.0
Stable tag: 0.6.72
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Lean, EU-native cookie consent and compliance widget for WordPress.

== Description ==

AlpineConsent is a lightweight cookie consent management platform (CMP), built and hosted in Austria/Germany. It helps WordPress sites comply with the GDPR, the Austrian TKG 2021, and the German TDDDG, without the weight of typical enterprise consent plugins.

The plugin enqueues the real AlpineConsent banner — granular consent categories (necessary/preferences/statistics/marketing), prior-blocking of scripts and iframes, Google Consent Mode v2 — and holds any script you assign a category to under its own AlpineConsent menu until a visitor grants it. A script left unassigned loads normally, so activating the plugin never breaks a site by default.

Scripts are found for you automatically — no need to dig through theme or plugin code for a WordPress "handle." As visitors load your pages (or immediately, via the "Scan site now" button), every tracked script is listed with a suggested category already filled in, matched against a real, curated database of thousands of known cookies and providers. You just confirm or change it. That same scan also reads the cookies your pages actually set, showing each one's real description, retention period, and data controller on a separate Cookies tab.

The banner's layout (bar top/bottom, floating, or modal), title/description text, and button set are all configurable on the Banner tab — buttons can be shown as buttons or plain links, reordered, and you can add your own Privacy Policy/Cookie Policy/Imprint links (with a one-click autofill for WordPress's own Privacy Policy page). A small "Powered by AlpineConsent" badge shows on the free plan. The banner's colors (background, text, accent, accept button), corner roundness, and font are also configurable on the Banner tab, with a live preview of every change before you save — free on every plan.

The banner's language auto-detects: if you run WPML, Polylang, WP Multilang, TranslatePress, or Weglot, it follows that visitor's own detected language dynamically — whatever languages your site is actually configured for, not a fixed list — otherwise it follows your site's own WordPress language setting. Every button, category label, and badge, in every one of your site's languages, is fully editable on the Banner tab's "Button & banner text" section — pre-filled with a suggested translation for 42 languages (all 24 official EU languages, the rest of the EEA, and major world languages), or plain English as a starting point for anything else. Right-to-left languages (Arabic, Hebrew, Persian, and others) render with a correctly mirrored layout, both in the banner itself and while editing their text.

== External services ==

This plugin connects to the following external services, both operated by AlpineConsent itself:

1) **Automatic update checks** (always, no action needed from you)
This plugin isn't distributed through the WordPress.org Plugin Directory yet, so it checks for new versions using AlpineConsent's own self-hosted release server rather than wordpress.org's. On a normal update-check cycle, a request is sent to:
`https://releases.alpineconsent.com/wordpress-plugin/latest-release`
When an update is actually installed, the plugin also downloads the update package and its cryptographic signature from that same release server and verifies the signature before installing — an unsigned or tampered package is refused. No personal or visitor data is sent — only what any plain HTTP request implicitly includes (server IP, user agent). See [AlpineConsent's Privacy Policy](https://alpineconsent.com/privacy).

2) **Activate license** (only if you click "Activate license" under Settings)
Links this WordPress site to your AlpineConsent account so the plugin knows your real plan — this only ever happens if you start the flow yourself, and it has nothing to do with your company/legal details (see below). Your site's domain and a one-time authorization code are sent to:
`https://api.alpineconsent.com/connect/exchange`
See [AlpineConsent's Privacy Policy](https://alpineconsent.com/privacy) and [Terms of Service](https://alpineconsent.com/terms).

3) **"View details" popup images** (only if you click "View details" on this plugin in your Plugins list)
The popup's banner image is loaded directly by your own browser from AlpineConsent's public documentation site:
`https://docs.alpineconsent.com/docs/assets/wordpress-plugin/`
No data about your site is sent — this is a plain image request, the same as any `<img>` tag on any web page.

4) **Consent decisions** (only once this site is Connected to an AlpineConsent account)
A visitor's own browser sends which categories they granted or denied to power your dashboard's Consent data chart — no cookies, page content, or anything else about that visitor. A site you never connect sends nothing here at all; the banner and script-blocking still run entirely in the visitor's own browser either way. Sent from the visitor's browser directly to:
`https://api.alpineconsent.com/consent`
See [AlpineConsent's Privacy Policy](https://alpineconsent.com/privacy).

5) **Provider logo icons** (shown on the Scripts & Categories tab, admin-only)
When a discovered script's provider is recognized, your own browser loads that provider's logo directly from:
`https://api.alpineconsent.com/logos/`
This is a plain image request, the same as any `<img>` tag on any web page — no data about your site is sent. Only your own browser, while you're viewing the Scripts & Categories tab, requests these images; nothing is fetched or sent on a visitor's behalf.

6) **"Suggest translation (AI)" button** (only if you click it — requires a paid plan, the feature enabled, and its notice accepted under Settings)
Your banner's current English title, description, and button/category labels are sent to AlpineConsent's own backend, which asks an AI model to suggest a translation into the language you clicked — nothing about your site's visitors is included. The suggestion fills that language's fields for you to review; it is never saved automatically. Sent to:
`https://api.alpineconsent.com/translate`
See [AlpineConsent's Privacy Policy](https://alpineconsent.com/privacy) and [Terms of Service](https://alpineconsent.com/terms).

7) **"Back up now" under Settings** (only if you click it, after entering a passphrase)
Encrypts your company/legal details, using a passphrase only you know, entirely in your own browser (Web Crypto, never sent anywhere) before anything leaves this site. AlpineConsent only ever receives and stores the resulting encrypted blob — it cannot decrypt it, has no way to recover a lost passphrase, and never sees your company details in plain text. Sent to:
`https://api.alpineconsent.com/sites/{siteKey}/legal-backup`
See [AlpineConsent's Privacy Policy](https://alpineconsent.com/privacy) and [Terms of Service](https://alpineconsent.com/terms).

Company/legal details you enter under Settings (name, address, VAT ID, and similar) are stored locally on this site only, in your own WordPress database — never sent to AlpineConsent, regardless of whether you've activated a license, except as an encrypted backup if you explicitly click "Back up now" and provide a passphrase (see #7 above), which AlpineConsent can never decrypt. "Scan site now" fetches your own site's own pages locally (via `wp_remote_get()`) — nothing is sent to AlpineConsent for that either.

== Frequently Asked Questions ==

= Does this plugin send my visitors' data to AlpineConsent? =

Only once this site has an activated license, and only which categories a visitor granted or denied — never a cookie value, page content, or anything else about them. Script-blocking itself always runs entirely in the visitor's own browser regardless. See "External services" above for everything this plugin does talk to AlpineConsent for.

= Will this break my site if I just activate it? =

No — a script only gets held for consent once you explicitly assign it a category under the AlpineConsent menu. An unassigned script keeps loading exactly as it did before you installed the plugin.

= Do I need an AlpineConsent account to use this? =

No. The plugin is fully functional standalone — activating a license (Settings -> License) is optional and only relevant if you're on a paid plan. It has no bearing on your company/legal details, which always stay local to this site.

= Is my company information ever stored on AlpineConsent's servers? =

Not by default. Your company/legal details are stored only in your own WordPress database and used only to generate this site's own Cookie Policy, Legal Notice, and Privacy Policy cookies section. The one exception: if you explicitly click "Back up now" under Settings and provide a passphrase, an encrypted copy is stored on AlpineConsent as a backup — encrypted in your own browser before it's sent, using a passphrase only you know, which AlpineConsent can never decrypt or use to recover it if lost.

= What happens on the free plan? =

The banner, automatic script/cookie discovery, and manual categorization all work fully. A small "Powered by AlpineConsent" badge shows on the banner; it's removed once your site has an activated license on a paid plan. The static translation dictionary (42 languages) still pre-fills your banner text either way — only the "Suggest translation (AI)" button itself is a paid-plan feature.

= What is the "Consent setup score"? =

A plain completion score (0-100) shown at the top of the AlpineConsent menu, based only on this plugin's own settings: is the banner on, are your detected scripts/cookies categorized, is your company/legal info filled in, and have you published a Cookie Policy and Legal Notice page. **It is not a legal compliance score** — we only see this plugin's own configuration, not your whole site, and a perfect 100 is not a guarantee you meet the GDPR, TKG, or TDDDG. It's a checklist, not a certificate. It never looks at whether your license is activated — a free-plan site can score 100 just as easily as a paid one.

== Installation ==

1. Upload the `wordpress-plugin` directory to `/wp-content/plugins/alpineconsent`, or install via the WordPress plugin screen once packaged.
2. Activate the plugin through the 'Plugins' screen in WordPress.
3. Go to the AlpineConsent menu item — assign a category to any discovered script you want held until consent.

== Changelog ==

= 0.6.72 =
* Fixed: clearing a title/description field for a non-English language (e.g. Persian) showed the English default instead of that language's own — in both the live preview and the real banner.

= 0.6.71 =
* Banner tab: new "Banner theme" section — background/text/accent/accept-button colors, corner roundness, and a font choice, with the live preview updating instantly. Free on every plan.

= 0.6.70 =
* Scripts & Categories / Cookies: saving a category change (or deleting an entry) now updates the table's grouping immediately — a script moved to "Uncategorized" used to stay visually under its old group until you refreshed the page.

= 0.6.69 =
* Recognizes any script served from WordPress core's own `/wp-admin/js/` or `/wp-includes/js/` directory as "WordPress Core" now — not a tracker — even when it's not one of the specific bundled libraries already named (jQuery, MediaElement.js, ...). Real files like `wp-tooltip.js`/`wp-util.min.js` used to show up unrecognized.

= 0.6.68 =
* Scripts & Categories: the "Other" group (a real, assigned or suggested category) now renders before "Uncategorized" (no category at all) — the more complete group first, the least informative one last.

= 0.6.67 =
* Scripts & Categories / Cookies: you can now delete a stale discovered entry, one at a time or in bulk — "Select entries not seen in the last N days" pre-checks the likely candidates for you, based on how long it's actually been since a "Scan site now" re-confirmed each one. Nothing is ever deleted automatically.

= 0.6.66 =
* Cookie Policy page: scripts with no identifiable provider at all now collapse behind one "Other" disclosure too, instead of one bare, unidentified row per script — matches how the Scripts & Categories admin table already groups them.

= 0.6.65 =
* Cookie Policy page: the "Not yet categorized" section now groups scripts by provider and collapses multiple scripts from the same one behind a disclosure, the same way every real category above it already does — it was still a bare, ungrouped list.

= 0.6.64 =
* Legal pages per-language editor: "Load current English text" and "Load default translation" are now real buttons with visible spacing between them, instead of two link-styled buttons crammed together inline.

= 0.6.63 =
* The Legal pages "Per-language translation" checkbox now also saves in place instead of reloading the page — the last remaining full-page reload in the settings screen.

= 0.6.62 =
* Banner tab: "Fill empty fields from dictionary" is now also available for English — if you clear or break your own English banner text, you can bring the built-in default text back in, the same way every other language already could.

= 0.6.61 =
* Saving Scripts & Categories, Cookies, Banner, or Settings no longer reloads the page — it saves in place, updates the setup score and category counts right away, and shows a "Saved." message next to the button.
* Saving a Legal pages translation for one language now also saves in place instead of reloading.

= 0.6.60 =
* Fixed a real bug where a right-to-left language's own per-language text field (Legal pages, Banner text) genuinely had `dir="rtl"` in the page but still displayed and typed left-to-right — some other CSS loaded on the same admin screen was overriding it. Forced it back for both directions, regardless of what else is loaded on the page.
* The installed plugin version is now shown right in the settings page header — no more needing to check Plugins → Installed Plugins just to confirm which version is live on a site.

= 0.6.59 =
* Legal pages "Preview" subtab: added a language selector — it used to only ever show one fixed language (whichever your visitor-language detection resolves to), with no way to check how a real translation actually reads. Also fixed a right-to-left language's own preview never being shown right-to-left, unlike its own editor field.

= 0.6.58 =
* Fixed a real bug where clearing a script's category back to "—" and saving looked like it silently failed — for any script with a real suggested category next to it (e.g. Contact Form 7), the dropdown re-selected that suggestion on the next page load instead of staying blank, even though the "no category" choice had actually been saved correctly.

= 0.6.57 =
* Scripts & Categories: Yoast SEO and Google Site Kit are now recognized as real providers — a real scan report showed roughly 35 files from these two extremely widely-installed plugins with no provider identity at all, each its own separate, unrelated-looking row.
* Fixed a real bug where a script's URL could get silently corrupted if it contained an HTML-encoded "&" (`&#038;`, how WordPress itself commonly writes multi-parameter URLs) — the encoded text was never decoded back to a real URL, which could truncate it entirely (a real Google Maps embed URL was affected on a live site).
* "Uncategorized" and "Other" now start collapsed, same as every other provider group — they used to start expanded so previously-always-visible rows wouldn't suddenly need an extra click, but once every script on a real site is actually categorized, leaving just those two open while everything else collapses read as inconsistent.
* Scripts & Categories: replaced the small left indent under a group's own rows (added in 0.6.53) with a clearer separator line above each group instead — the indent left the row-select checkbox (deliberately fixed in one column for select-all to work) looking disconnected from the now-shifted text next to it.
* Fixed a real bug where saving any change (a script's category, a banner setting, and more) while on a different tab than the one the page first loaded on could redirect you back to that first tab instead of the one you were actually working in — switching tabs is instant and never reloads the page, so nothing about it looked like it should matter.

= 0.6.56 =
* Legal pages (Cookie Policy / Legal Notice / Privacy Policy per-language translation): new "Load default translation" button, next to "Load current English text" — brings in a built-in translated draft for the fixed legal boilerplate (headings, GDPR/liability/copyright clauses) while leaving your own company details, dates, and the live cookie/script table exactly as they are, untranslated. Covers German, Persian, and Italian for now, more languages to follow; only appears once we have a built-in translation for a given language, and is clearly marked as machine-generated and not yet reviewed by a native or legal speaker — check it carefully before relying on it for a real, compliance-facing page.

= 0.6.55 =
* Legal pages (Cookie Policy / Legal Notice / Privacy Policy per-language translation): the textarea for a right-to-left language (Persian, Arabic, Hebrew) now actually edits right-to-left — the Banner tab's own per-language fields already did this, but this one never had.

= 0.6.54 =
* Banner tab, Title & description: fixed a real bug where a language with no saved text pre-filled with your current ENGLISH text instead of that language's own built-in translation — the on-page text already promised this but it wasn't actually true.
* Banner tab: new "Fill empty fields from dictionary" button per language (next to "Suggest translation (AI)") — only fills in whatever's currently empty on that language's tab (title, description, buttons, category labels) from our own built-in translation; never touches anything already typed, and does nothing if we don't have a built-in translation for that language yet. Clearing a field and saving now leaves it genuinely empty instead of silently doing nothing.
* Legal pages (Cookie Policy / Legal Notice / Privacy Policy per-language translation): a language with no saved translation now shows a genuinely empty box instead of silently pre-filling with the live English draft — a real point of confusion reported directly (clearing a translation and saving made English text quietly reappear with no explanation). A clear note now explains that an empty language falls back to the English draft for visitors; "Load current English text" brings that draft in on demand to translate from.

= 0.6.53 =
* Scripts & Categories follow-up, all reported directly off v0.6.52: the un-grouped "Other" section now splits into "Uncategorized" (nothing assigned or suggested yet) and "Other" (already has a category, just no named provider), so a script still needing a decision is never sitting unmarked next to one that's already done. Fixed a real bug where a group's arrow needed two clicks to actually expand it (and pointed the wrong direction in between) instead of one. Column labels (Script/Suggested/Category/Provider/Description) now appear right above each group's own rows instead of once in a fixed header at the very top of the whole table, most of which starts collapsed.

= 0.6.52 =
* New "Suggest translation (AI)" button (paid plans) on the Banner tab — translates your banner's CURRENT text (title, description, every button/category label, even unsaved edits) into another language via AI, proxied through AlpineConsent's own backend so this plugin never holds its own AI provider key. Closes a real gap the built-in 42-language starting dictionary can't: it only ever suggests one fixed wording per language, so it can't help once you've already edited away from that default text.
* New optional "Back up now" button under Settings — encrypts your company/legal details with a passphrase only you know, entirely in your own browser, before anything is sent to AlpineConsent. AlpineConsent only ever stores the resulting encrypted blob and can never decrypt it or recover a lost passphrase. Purely additive: your Cookie Policy, Legal Notice, and Privacy Policy section still render locally from this site's own data, exactly as before.
* Scripts & Categories: a sub-group's own rows now show a small left indent under their group header, and every script without a real, named provider group now sits under its own "Other" header instead of floating with no visual grouping at all. Also added an "Uncategorized" filter pill next to the existing category pills, for quickly finding everything still needing a category decision.
* Provider-logo images in this table now declare their own width/height, closing a real Lighthouse "explicit width and height" finding — avoids a layout shift while a logo is still loading.

= 0.6.51 =
* Scripts & Categories: WordPress's own dozens of internal `@wordpress/*` JavaScript modules (Gutenberg's own building blocks) now group together under one "WordPress Core" entry instead of each one showing up as its own separate, uncollapsible row — a site with many of these loaded no longer buries the table in a wall of near-identical singleton rows.
* Fixed the Consent setup score's "Enable the consent banner" checklist item — it linked to the Banner tab (layout/text styling) instead of Settings, where the actual "Show the consent banner to visitors" checkbox lives.
* Visual fixes reported directly: a two-word suggested-category label ("Statistics / Analytics") no longer wraps and distorts its own badge; the setup score's circular badge no longer overflows its own border; the setup score's checklist is now a real vertical list instead of everything running together on one line; provider logos (shrunk in v0.6.47's own cleanup pass) are back to a readable size.

= 0.6.50 =
* "Scan site now" (Scripts & Categories tab) now shows real feedback while it runs — the button disables and a spinner/"Scanning…" message appears immediately on click — instead of giving no indication at all that anything was happening for what can genuinely take several seconds.

= 0.6.49 =
* The suggested-translation dictionary for banner text now covers 42 languages — all 24 official EU languages, the rest of the EEA (Norwegian, Icelandic), major regional languages with real EU business presence (Turkish, Russian, Ukrainian, Albanian, Serbian, Bosnian), and major world languages by speaker count (Persian, Arabic, Hebrew, Chinese, Hindi, Japanese, Korean, Indonesian, Vietnamese, Thai) — up from just English/German/Italian. Machine-assisted, like the existing entries — not yet reviewed by a native speaker for any language, including the ones that were already there; always worth checking the exact wording before relying on it for a real compliance-facing banner. This is only ever the starting pre-fill an admin sees the first time they open a language's row — fully editable and saved independently either way.
* Right-to-left languages (Arabic, Hebrew, Persian, and others) now render with a correctly mirrored layout — both the banner itself (button order, text alignment) and the Banner tab's own per-language editing fields (title, description, button text), which previously always rendered left-to-right regardless of the language being edited.

= 0.6.48 =
* Added a "Consent setup score" at the top of the AlpineConsent menu — a plain 0-100 completion score based only on this plugin's own settings (banner on, scripts/cookies categorized, company info filled in, Cookie Policy and Legal Notice pages published), with a checklist linking straight to whatever isn't finished yet. Deliberately not a legal compliance score, and doesn't factor in whether your license is activated — see the new FAQ entry above.

= 0.6.47 =
* Scripts & Categories: a provider with more than one script now actually collapses (click to expand) — it only ever showed a header with no way to hide the rows underneath before. A provider with just one script skips the header/click entirely and shows its own icon directly, and now sorts after the real (collapsible) groups instead of mixed in among them, alongside scripts with no recognized provider. Also tightened up the header row's own layout — it used to read as heavier and less consistent than the rest of the table.

= 0.6.46 =
* Breaking change, for the better: your company/legal details (name, address, VAT ID, and similar) are local-only again, exactly like before v0.6.40 — the brief period where they were shared live with your AlpineConsent account, once connected, is retired. That data has been deleted from AlpineConsent's servers. Also renamed "Connect to AlpineConsent" to "Activate license" and moved it to the bottom of the Settings tab, since it only ever activated your plan — it never had anything to do with your company details, and the earlier naming made that easy to assume otherwise.

= 0.6.45 =
* The Settings tab now has two separate sections — "Behavior" (Show banner, Run blocker, Block raw scripts too) and "Company & legal details" (the 7 fields used to generate your legal pages) — instead of one shared "Behavior" heading covering both. Purely a display change: everything still saves together on one "Save changes" click.

= 0.6.44 =
* The Scripts & Categories table now groups a provider's scripts together with a real logo and a count, instead of one identical-looking row per script — a real scan turned up a page-builder add-on alone accounting for 5 rows. Each script still keeps its own independent category — the grouping is purely visual. The generated Cookie Policy does the same for a provider with more than one script in the same category, collapsing them behind a "Show N scripts" toggle.

= 0.6.43 =
* Visual polish: the AlpineConsent settings pages used seven different corner-rounding values across buttons, cards, tables, and form fields, added ad hoc as each was built over time. Consolidated to one consistent, intentional scale — no functional change.

= 0.6.42 =
* Added WP Multilang support: the banner's language now also follows WP Multilang's own per-visitor detection (alongside the existing WPML, Polylang, TranslatePress, and Weglot support), and Settings -> AlpineConsent's per-language rows pick up every language WP Multilang has enabled.

= 0.6.41 =
* Your dashboard's Consent data chart can now actually show real data for this site: once connected to your AlpineConsent account, a visitor's own browser reports which categories they granted or denied (never a cookie value or anything else about them). A site you never connect sends nothing here, exactly as before — the consent banner and script-blocking always run entirely in the visitor's own browser regardless.

= 0.6.40 =
* Once connected to your AlpineConsent account (Settings → Connect to AlpineConsent), your company/legal details (name, address, email, phone, managing director, commercial register, VAT ID) are now shared live with your account — the same details shown on your dashboard's Sites → Legal pages panel. Edit them from either place; there is nothing to keep in sync, since there's now only one real copy. A site you never connect keeps working exactly as before, fully local.

= 0.6.39 =
* Fixed a real bug: Scripts & Categories could pick up scripts that only ever load in your own wp-admin (Dashboard, Plugins, Site Health, and similar) while you're logged in managing the site — never something a real visitor sees. Beyond cluttering the table, this closed a narrow but real risk: if one of those admin-only scripts ever got assigned a blockable category, it could get silently disabled inside wp-admin itself, with no consent banner there to ever turn it back on.

= 0.6.38 =
* Fixed a real, long-standing bug: the vendored cookie database that powers the Cookies tab's Provider/Description matches and the "Known cookies for detected providers" section never actually shipped in the release zip. Every site that went through a real automatic update since this plugin's very first release lost this data silently (WordPress replaces the whole plugin folder on update) — those two features kept working right up until a site's first auto-update, then went quietly empty from then on with no visible error.

= 0.6.37 =
* Legal pages tab: the per-language translation textareas now show plain, readable text (with light `## heading`/`**bold**`/`[link](url)` formatting) instead of raw HTML source — reported directly as unusable and risky to hand-edit.
* The auto-generated cookie/script table inside the Cookie Policy and Privacy Policy cookies section is no longer something to translate by hand: it's represented by a single placeholder you can leave in place, move, or remove, and it always expands into the current, live table at render time — even inside an otherwise-saved translation.

= 0.6.36 =
* Fixed a real bug where "Scan site now" could never actually save a newly-discovered raw/hardcoded script or cookie, no matter how many times it was run — a WordPress core mechanism was silently reverting each scan's own new discoveries back to what was already saved before the scan. Scripts and cookies discovered passively (as real visitors load your pages) were never affected.

= 0.6.35 =
* New, experimental "Block raw scripts too" setting (off by default) — holds a hardcoded `<script>` (one your theme or a page builder writes directly into the page, not one WordPress itself loads) until the visitor consents, once you've categorized it on the Scripts & Categories tab. Previously such a script could be discovered and categorized but never actually held.

= 0.6.34 =
* Cookie Policy page: scripts within each category now show in a real table (Script/Provider/Purpose) instead of a bare list of URLs with no context.
* Cookies tab and Cookie Policy page now also list cookies a detected script's own provider is publicly documented to use, even when this scan's Set-Cookie-header check finds nothing — most analytics/marketing cookies are set by client-side JavaScript, invisible to that check.

= 0.6.33 =
* Legal pages tab: self-service per-language translation for the Cookie Policy, Legal Notice and Privacy Policy cookies section, gated behind a liability-acceptance checkbox — AlpineConsent drafts English text only, translating and keeping it legally correct in every other language is your own responsibility.
* All 3 legal-content shortcodes now accept an optional `lang` attribute (e.g. `[alpineconsent_cookie_policy lang="de"]`) to force one fixed language regardless of the visitor's own detected language.

= 0.6.32 =
* "Button & banner text" (Banner tab) now shows one sub-tab per language instead of stacking every language's fields in one long scroll.

= 0.6.31 =
* Fixed the admin menu icon showing at its full original size instead of a normal small menu icon.

= 0.6.30 =
* Fixed the "Show with our default theme" preview checkbox on the Legal Notice and Cookie Policy panels (Legal pages tab) — checking it did nothing. The Privacy Policy cookies-section panel was never affected.

= 0.6.29 =
* The plugin's own admin settings screens (not the visitor-facing banner) can now follow your WordPress admin language — German, Italian, and Persian are included as a starting point (machine-assisted, not yet reviewed by a native speaker). Any other language, or any string not yet covered, shows in English as before.

= 0.6.28 =
* The "View details" popup (from your Plugins list) now shows a real Description, Installation, FAQ, and Changelog — pulled straight from this same readme — plus a proper banner image, instead of just a changelog snippet.

= 0.6.27 =
* The admin menu icon is now your site's real logo instead of an abstract monochrome silhouette.
* "Scan site now" now checks your homepage plus a few of your other pages and posts, instead of only the homepage.

= 0.6.26 =
* "Scan homepage now" now also discovers a script hardcoded directly into a page (for example, pasted into a page-builder module) instead of loaded the normal WordPress way — it's listed and can be categorized like any other script, though it can't yet be held until consent the way an ordinary script can (a separate improvement, tracked for later).

= 0.6.25 =
* Rotated the cryptographic key this plugin uses to verify update signatures, as a precaution. This has no visible effect — updates continue to be verified before installing, exactly as before.

= 0.6.24 =
* Polylang detection (used to pre-populate its known cookie on the Cookies tab) now checks the `POLYLANG_VERSION` constant Polylang always defines, instead of a function name — no behavior change for a real site, just a more robust detection primitive.

= 0.6.23 =
* One "Save changes" button instead of two — the settings screen used to show a second copy right under the tab nav, which looked redundant on a short tab. It's now a single button that stays visible at the bottom of your screen as you scroll, so it's never far away even on a long tab like Scripts & Categories.

= 0.6.22 =
* Removed the "Force a fixed language" override on the Banner tab — AlpineConsent never forces a language of its own; the banner's language always follows real detection (WPML/Polylang, or your site's own WordPress language).
* New "Button & banner text" section on the Banner tab: every button (Accept, Reject, Manage preferences, Save preferences), every category label, and the "Powered by AlpineConsent" badge text are now editable per language, for every language your site actually has — pre-filled with a suggested translation (German/Italian) or plain English as a starting point.
* If you're running a German or Italian site today: your button text keeps working exactly as before, but it now lives in these new editable fields instead of being hardcoded — visit the Banner tab and click "Save changes" once after updating, so your existing translation is carried over as a real, editable setting.

= 0.6.21 =
* Reorganized the admin screen: tabs are now Scripts & Categories, Banner, Cookies, **Legal pages** (new — the Cookie Policy, Legal Notice, and Privacy Policy cookies-section generators, previously scattered across other tabs, now live together), and Settings.
* The Banner tab now has its own Settings/Preview sub-tabs. The live preview loads your **real homepage** with the banner shown on top of your site's own actual look, instead of a plain blank page.
* The live preview's language switcher now shows exactly your own site's real languages (from WPML/Polylang, or just your default if none) — never a language your site doesn't actually have.
* The Cookie Policy / Legal Notice / Privacy Policy cookies-section previews each gained a "Show with our default theme" checkbox — off by default (these pages are intentionally unstyled, so they inherit your site's real theme), lets you preview roughly how it would look with none of your own styling applied.
* Fixed the AlpineConsent menu icon for real this time: WordPress recolors every menu icon's fill to white, so the earlier navy-background design became an invisible white-on-white square. It's now a single white silhouette, the same convention every other plugin's monochrome menu icon uses.
* The "Save changes" buttons no longer float loose on the page — each now sits inside its own card, matching the rest of the screen.

= 0.6.20 =
* Clarified the Banner tab's "Force a fixed language" setting (previously just "Language"). Reported live by a site running WPML with 2 languages: that dropdown only ever offers the (unrelated, small) list of languages this plugin has full built-in translations for, and was easy to mistake for a broken version of the site's own detected-language list shown just below it — the description text now explains this is a separate, independent setting, and mentions Italian (previously missing from the text even though it's a real option in the list). Superseded by 0.6.22 above, which removes this setting entirely.

= 0.6.19 =
* "Scan homepage now" now also recognizes the well-known cookies of a handful of active plugins (currently Polylang, WPML, WooCommerce) and lists them on the Cookies tab — most of these are set by JavaScript in the visitor's browser, not in the page response this scan reads, so they were invisible before.

= 0.6.18 =
* Fixed the AlpineConsent menu icon not showing in the WordPress admin sidebar on some sites (the icon SVG was missing explicit dimensions).
* Fixed the Cookie Policy / Legal Notice previews on the settings screen rendering as unreadable white-on-white text.
* Clearer message on the Cookies tab when a homepage scan finds no cookies in the response headers — this is normal (most tracking cookies are set later by JavaScript in the visitor's browser); the guidance now explains what to do instead of implying the scan failed.

= 0.6.17 =
* Plugin updates are now cryptographically signed and verified. Before an update is installed, the plugin downloads its package and a detached signature and checks the signature against a key built into the plugin; an unsigned, altered, or unverifiable package is refused rather than installed. This protects the update channel even if AlpineConsent's own release server were compromised — the signing key is held offline, never on that server. (This also switches the automatic update check to `releases.alpineconsent.com`, an AlpineConsent-owned indirection layer, so the release hosting can move in future without another plugin update.)

= 0.6.16 =
* New live banner preview on the Banner tab. The real consent banner renders right there in the settings screen and updates as you edit — layout, per-language title/description, buttons, custom links, logo — before you save anything. A "Preview language" switcher shows how each of your configured languages looks. The preview runs the exact same widget your visitors get, isolated in a frame so it never covers the settings page.

= 0.6.15 =
* New Legal Notice / Impressum generator. On the Settings tab: a live preview of an Impressum built from your company details (name, address, managing director, commercial register, VAT ID, phone, email — new fields on the same tab), with the § 5 DDG / § 5 ECG identification block, the EU online-dispute-resolution pointer, and the standard short liability clauses. Same behaviour as the Cookie Policy page — one-click "Create & publish" / "Create as a draft", or paste the `[alpineconsent_legal_notice]` shortcode into a page you already have. It never creates a page on its own.
* New "Privacy Policy — cookies section". AlpineConsent does not generate a whole Privacy Policy (it can't see your contact forms, shop, newsletter, hosting, or payment providers). This is only the cookies / tracking part — controller identity, the discovered-cookie table with legal basis, and your data-subject rights — as a `[alpineconsent_privacy_cookies_section]` shortcode to paste as one section into your own Privacy Policy. The Settings tab lists what the rest of your policy still has to cover.

= 0.6.14 =
* New Cookie Policy page generator. On the Cookies tab: a live preview of the page, and — if you don't have a policy page yet — a one-click "Create & publish" or "Create as a draft". It never creates a page on its own. The generated page contains just an `[alpineconsent_cookie_policy]` shortcode, so it stays current on every re-scan with nothing to regenerate; you can also drop that shortcode into a page you already have.
* The generated page is a full Cookie Policy, not just a table: it explains what cookies and similar technologies are, the consent/legal basis (GDPR and ePrivacy), a per-category description with the discovered cookies (provider, purpose, retention), a third-party-cookies note, how to control or delete cookies, and your GDPR rights — linking your site's own Privacy Policy page when one is set. The company name, address and email from Settings → AlpineConsent are used for a "Questions or complaints" contact section, and the page reads as your own document (no "generated by" attribution). It carries no styling of its own, so it inherits your theme's fonts and colours.

= 0.6.13 =
* The banner Description field now supports simple **bold** and [link text](https://example.com) formatting — plain text otherwise, no other HTML.
* New optional banner Logo: pick an image from your Media Library, or paste a direct URL. Shown above the title in every language; a broken image URL just removes itself instead of leaving a broken-image icon.

= 0.6.12 =
* Real per-language banner title/description: Settings -> AlpineConsent now shows one Title/Description field per language your site actually has active (detected from WPML, Polylang, TranslatePress, or Weglot — the page also tells you which one it found), pre-filled with your English text as a starting point to translate. Leave a language's field blank to use its own built-in default text instead.
* The bundled widget script (also used by the Universal Script Tag on non-WordPress sites) now auto-detects the visitor's browser language when nothing else says otherwise, instead of always defaulting to English.

= 0.6.11 =
* Added the "External services" and "Frequently Asked Questions" sections to this readme — real disclosure of the two things this plugin actually talks to AlpineConsent for (automatic update checks, and the optional Connect flow), ahead of a future WordPress.org Plugin Directory submission.

= 0.6.10 =
* Internal-only hotfix: the previous release's badge-removal fix pushed the widget's own script over its hard 5 KB size budget, caught by our own CI. No behavior change for site owners — the badge removal from 0.6.8 works exactly the same, just implemented a few bytes leaner.

= 0.6.8 =
* Fixed a real bug: the "Powered by AlpineConsent" badge on the consent banner rendered for every plan, including paid ones — despite Watermark Removal being an advertised Pro/Business/Enterprise feature. The badge is now actually removed once your site is connected to a paid AlpineConsent plan (Settings -> Connect to AlpineConsent).

= 0.6.7 =
* Fixed the new Settings tab's own section headings ("AlpineConsent account", "Behavior") rendering in a near-invisible dark color against the dark card background — found live, right after 0.6.6 shipped.
* Fixed a real testing gap in the Connect flow: connecting a local WordPress test site (e.g. wp-env/Local/Valet, all plain http) always failed with a confusing "returnUrl must be https" error, even though local testing was always meant to work — localhost/127.0.0.1 is now correctly exempted, and the error message for a real non-local, non-https site is now a clear, specific explanation instead of a bare technical string.

= 0.6.6 =
* New "Connect to AlpineConsent" button on a new Settings tab — links this site to your AlpineConsent account (sign in on alpineconsent.com, confirm, and you're brought straight back) so the plugin knows your real plan, without ever copy-pasting a key by hand. A site you never connect keeps working exactly as before, fully standalone.
* New Settings tab also adds two toggles — show/hide the banner and turn script-blocking on/off — plus company details (used for automatic legal-text generation in a future release).

= 0.6.5 =
* Fixed "Scan homepage now" only ever adding brand-new scripts/cookies to the Suggested column — an already-discovered script or cookie kept its very first suggestion forever, even after later plugin updates taught the classifier to recognize it correctly. Rescanning now refreshes the suggestion for already-known entries too, without ever touching a category you've already assigned yourself.

= 0.6.4 =
* Category suggestions synced against the public scanner's own classifier, closing a real gap between the two: recognizes jQuery and other WordPress-core-bundled libraries, WordPress core's own @wordpress/* packages, plugins that serve their tracker from /wp-content/uploads/ (Burst Statistics and others), must-use plugins (/wp-content/mu-plugins/ and VIP's /wp-content/client-mu-plugins/), and Cloudflare's own /cdn-cgi/ path (email obfuscation and similar built-in features, recognized on any Cloudflare-proxied site). Also adds around two dozen real hosts and several WordPress plugin slugs found scanning live sites this session (Osano, DataDome, NoBid, Optimizely, Sailthru, Complianz, and others). A handful of already-recognized plugins/themes (Divi, Elementor, Contact Form 7, and others) now get a real category suggestion instead of none at all.

= 0.6.3 =
* Fixed "Check Again" (Dashboard -> Updates) not actually re-checking for a new version — this plugin's own update cache wasn't being cleared alongside WordPress's own, so a manual check could still show stale results.
* Fixed the dropdown arrow rendering oversized and overlapping option text; the custom-links table now actually reserves the URL column its own width instead of squeezing it back down.
* Category suggestions now also recognize well-known plugins (WooCommerce and similar) from their /wp-content/plugins/ URL path, for scripts served from the site's own domain where the host alone gives no clue.

= 0.6.2 =
* Fixed the branded admin theme: buttons and dropdowns could become unreadable on hover/focus (text same color as background), the masthead now shows the real site logo, and the custom-links table is no longer capped to a fixed width.
* Category suggestions now also check the script's WordPress handle name (e.g. "csrf", "analytics", "pixel") when its source URL alone doesn't match a known provider.

= 0.6.1 =
* The settings page is now visually branded to match alpineconsent.com — navy background, Consent Green accent, card-based sections — instead of bare, unstyled Settings API chrome.

= 0.6.0 =
* Settings moved out from under Settings into their own top-level AlpineConsent menu item, reorganized into Banner / Scripts & Categories tabs, with a Save button at both top and bottom and "Scan homepage now" moved directly above the scripts table.
* Category labels updated to Functional (Essential) / Preferences / Statistics / Analytics / Marketing / Tracking; auto-suggestion matching extended to a few more well-known providers.
* The banner description now preserves line breaks instead of collapsing multi-line text to one line; the "Manage preferences" panel's Save button now sits directly with the checkboxes it saves.
* The plugin now checks for and can install updates the normal WordPress way (Plugins list "Update now"), without needing WordPress.org.

= 0.5.0 =
* Automatic banner language detection: follows WPML or Polylang's own per-visitor language when either is active, otherwise your site's WordPress language setting. A new "Language" dropdown under Settings -> AlpineConsent lets you fix it to German or English instead, overriding both.

= 0.4.0 =
* Banner customization: layout (modal, bar top/bottom, floating), title/description text, and a configurable button set (show/hide, reorder, button-or-link style) for Accept/Reject/Manage, plus custom link buttons (e.g. Privacy Policy, Cookie Policy, Imprint) with a one-click autofill from WordPress's own Privacy Policy page.
* The banner's language now follows the site's own WordPress language setting (German or English).
* A "Powered by AlpineConsent" badge is now shown on the banner (free plan).

= 0.3.0 =
* Automatic script discovery: every tracked script is found as visitors load your pages (or on demand via a "Scan homepage now" button), with a suggested category pre-filled for well-known trackers. Replaces the old manual-handle-entry textareas with a single table.

= 0.2.0 =
* Real prior-blocking: a `script_loader_tag` filter holds any script assigned a category (via the new Settings -> AlpineConsent page) until a visitor consents to that category.
* The loader now enqueues the real AlpineConsent widget (granular categories, blocking engine, Consent Mode v2) instead of a placeholder stub.

= 0.1.0 =
* Initial development skeleton: plugin bootstrap and stub loader enqueue.
