=== Upbinger Blog ===
Contributors: upbinger
Tags: blog, embed, seo, server-side-rendering, content
Requires at least: 5.6
Tested up to: 6.9
Requires PHP: 7.4
Stable tag: 2.1.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Embed your Upbinger-hosted blog into any WordPress page — server-side rendered for full SEO, with theme-safe CSS isolation and anonymous visit analytics.

== Description ==

**Upbinger Blog** lets you seamlessly embed your blog content — published through [Upbinger](https://upbinger.com) — into your WordPress site. As of version 2.1 the blog is **rendered on the server**: the post content, title, meta description, canonical link, Open Graph/Twitter tags and JSON-LD structured data are all present in the first HTTP response, so search engines and social scrapers index your posts reliably — no JavaScript required. The blog stylesheet is **scoped to the blog container**, so blog styles never leak into (or break) your theme.

= Key Features =

* **True Server-Side SEO** — Post content, `<title>`, meta description, canonical, Open Graph/Twitter tags, and JSON-LD are rendered server-side into the first HTTP response. Search engines and social scrapers index your posts without executing JavaScript.
* **Real 404s** — Unknown post slugs return a genuine HTTP 404 instead of a soft-404 "200 with no content", which search engines penalise.
* **Theme-Safe CSS Isolation** — The blog stylesheet is automatically scoped to the blog container (and emitted in the page head), so blog CSS cannot leak out and break your theme. No JavaScript required.
* **rem → em Conversion** — Automatically converts CSS `rem` units to `em` so blog typography renders correctly regardless of your theme's root font-size.
* **Clean URLs** — Internal blog links are rewritten to clean WordPress paths (`/blog/`, `/blog/page-2/`, `/blog/your-post/`) — no `.html` suffixes, no duplicate canonicals.
* **Edge Caching** — Fetched blog HTML is cached in a WordPress transient (5 minutes) so pages stay fast and the CDN isn't hit on every view.
* **Google Fonts** — Font stylesheets are loaded in the page `<head>` so blog typography renders correctly across all browsers.
* **Anonymous Analytics** — Visit counts are sent via `sendBeacon` with session deduplication. No cookies, no PII.

= How It Works =

1. Your blog HTML is published to the Upbinger CDN.
2. When a visitor (or crawler) requests a blog route, the plugin fetches the matching page from the CDN **on the server** and caches it briefly.
3. The post content is rendered into the page, and its SEO meta/canonical/JSON-LD plus a container-scoped copy of the blog stylesheet are emitted into the `<head>` — all in the first response.
4. Visitors see your blog styled exactly as designed with no CSS side-effects on your theme; search engines receive fully-indexable HTML.

= Requirements =

* An active [Upbinger](https://upbinger.com) account with a published blog.
* WordPress 5.0 or higher.
* PHP 7.4 or higher.

== Installation ==

1. Upload the `upbinger-blog` folder to `/wp-content/plugins/`.
2. Activate the plugin through the **Plugins** menu in WordPress. A `/blog` page is created automatically.
3. Navigate to **Settings → Upbinger Blog** to confirm your detected domain matches the domain your blog is published under in Upbinger.
4. (Optional) Disable anonymous analytics or enable debug logging.
5. Visit `/blog` to verify your posts render correctly.

== Frequently Asked Questions ==

= Do I need an Upbinger account? =

Yes. The plugin loads blog content from Upbinger's CDN, so you need a published blog on the platform.

= Will this break my theme's styles? =

No. The blog stylesheet is automatically scoped to the blog container (`#upbinger-blog`), so the blog's CSS cannot leak out and affect your theme.

= Why are font sizes different on my site? =

Some themes set `html { font-size: 62.5%; }` which changes the meaning of CSS `rem` units. This plugin automatically converts `rem` to `em` and sets a 16px base so blog text renders at the intended size.

= Does this affect my SEO? =

Positively, and significantly as of 2.1.0. Blog content is now rendered **server-side**, so the full article, title, meta description, canonical link, Open Graph/Twitter tags and JSON-LD structured data are all present in the first HTTP response — no JavaScript execution required for search engines to index your posts. Unknown post URLs also return a real 404 instead of a soft-404.

= Will my blog look different after updating to 2.1.0? =

No. Your blog renders with the exact same styling, isolated from your theme. The change is under the hood: content is produced on the server instead of being fetched by the browser, which is what makes it reliably indexable.

= Are analytics GDPR compliant? =

Yes. The plugin sends anonymous page-view events only (no cookies, no personal data). You can also disable analytics entirely from the settings page.

== Screenshots ==

1. Settings page with domain, base path, and container configuration.

== Changelog ==

= 2.1.1 =
* **Fix: Blank blog area on some sites.** 2.1.0 isolated styling with a Declarative Shadow DOM `<template>`, which did not render in every environment. Rendering is now plain light-DOM HTML that always displays.
* **Change: CSS isolation via scoped stylesheet.** The blog's CSS is automatically scoped to `#upbinger-blog` and emitted in the page `<head>`, keeping blog styles from leaking into your theme — without relying on shadow DOM or JavaScript.
* All 2.1.0 SEO behaviour (server-side content, meta, canonical, JSON-LD, real 404s, clean URLs) is unchanged.

= 2.1.0 =
* **Major: Server-side rendering for full SEO.** Blog content, `<title>`, meta description, canonical, Open Graph/Twitter tags and JSON-LD are now rendered into the first HTTP response on the server — no JavaScript needed for search engines to index posts. Replaces the previous browser-side fetch + JS meta injection.
* **Fix: Real 404s.** Unknown post slugs now return a genuine HTTP 404 + `noindex` instead of a soft-404 (200 with empty content).
* **Add: Declarative Shadow DOM.** CSS isolation is now serialized in the server HTML (`<template shadowrootmode>`), preserving the exact styling while making content crawlable. A tiny polyfill attaches it on older browsers.
* **Add: Clean internal URLs.** CDN `.html` links are rewritten to `/blog/`, `/blog/page-N/`, `/blog/your-post/`, and `og:url`/canonical are normalised — preventing duplicate-URL/canonical conflicts.
* **Add: Transient caching** of fetched CDN HTML (5 min) for fast page loads.
* **Change: Much smaller front-end script** — only a Declarative Shadow DOM polyfill and anonymous analytics remain, since rendering now happens server-side.

= 2.0.2 =
* Fix: Remove ALL duplicate canonical tags (not just first one) to prevent SEO conflicts.
* Fix: Strip .html suffix from canonical and og:url for clean public-facing URLs.
* Fix: Deduplicate meta tags by property/name before injecting.
* Add: Light DOM H1 tag for SEO crawlers that cannot read Shadow DOM content.

= 2.0.1 =
* Fix duplicate JSON-LD structured data (BlogPosting + FAQPage) appearing in both document head and Shadow DOM.

= 2.0.0 =
* Complete rewrite for WordPress.org coding standards.
* Shadow DOM isolation for CSS conflict prevention.
* rem → em conversion for theme compatibility.
* SEO meta injection (OG, Twitter, canonical, JSON-LD).
* SPA-style internal navigation.
* Anonymous analytics with session deduplication.
* Debug logging gated behind an admin setting.
* Proper escaping, sanitization, and i18n throughout.

= 1.0.0 =
* Initial release.

== External services ==

This plugin relies on two third-party services provided by Upbinger.

= Upbinger CDN (cdn.upbinger.com) =

Blog HTML, CSS, images, and font references are fetched from the Upbinger content-delivery network every time a visitor loads a blog page on your site.

* **What is sent:** an HTTPS GET request, made **from your WordPress server**, containing only the URL path of the blog page being rendered (e.g. `/blogs/example.com/index.html`). No cookies or personal data are transmitted.
* **When:** when a blog route is rendered. Responses are cached in a WordPress transient for 5 minutes, so the CDN is not contacted on every page view.
* **Service provider:** Upbinger — [Terms of Service](https://upbinger.com/terms) | [Privacy Policy](https://upbinger.com/privacy)

= Upbinger Analytics API =

Anonymous page-view events are sent so site owners can see which posts are popular. No cookies are set and no personally-identifiable information is collected.

* **What is sent:** the page path, a session-scoped deduplication flag (stored in `sessionStorage`), and the site domain. The request is made via `navigator.sendBeacon`.
* **When:** once per unique page view per browser session (deduplicated). Can be disabled entirely from **Settings → Upbinger Blog → Disable Analytics**.
* **Endpoint:** `https://m745tnh6jg.execute-api.us-east-1.amazonaws.com/api/trackVisit`
* **Service provider:** Upbinger — [Terms of Service](https://upbinger.com/terms) | [Privacy Policy](https://upbinger.com/privacy)

== Upgrade Notice ==

= 2.1.1 =
Fixes a blank blog area that could occur on 2.1.0. Update strongly recommended for anyone on 2.1.0.

= 2.1.0 =
Major SEO upgrade: blog content and meta are now rendered server-side (indexable without JavaScript), with real 404s and clean URLs. Styling is unchanged. Strongly recommended for all users.

= 2.0.0 =
Major rewrite with full Shadow DOM isolation, SEO enhancements, and WordPress.org coding standards compliance. Recommended for all users.
