== Changelog ==

Full version history for BlueBook Feed Sync. The most recent releases are also listed in readme.txt.

= 3.16.0 =
* New: Text header layout "Inline" — icon and title on one line, the way a simple feed title usually looks. The title is the Header Text, or the page name when Header Text is empty; the bio, when shown, goes underneath. New feeds start inline; feeds that existed before this update are kept on the previous "Stacked" layout (icon row on top, page name and bio in a block below), so nothing changes on a live site until you choose Inline under Style → Header. "Show Name on Mobile" no longer applies to the inline title, which is the header itself.
* New: Text header alignment — Left (as before), Center or Right, under Style → Header. Applies to the icon/title row and, in the stacked layout, to the page name and bio block.
* New: Text header icon size — Default (24 px, as before) or a custom pixel size, under Style → Header.
* New: Text header icon library — besides the three Facebook marks, fourteen BlueBook line icons (camera, people, thumbs up, speech bubble, calendar, ticket, news list, document, video, YouTube, Vimeo, star, megaphone, RSS). The admin picker now shows the icons themselves. Existing headers keep their Facebook icon unchanged.
* New: Card style for the post cards (Layout tab, every layout) — "Outline" (thin border, no shadow: the look until now and the default), "Elevated" (no border, a soft layered shadow in three strengths, a 1 px lift on hover unless the visitor prefers reduced motion) and "Flat" (no border, no shadow). Plus a card corner radius (default 8 px). The card background still follows the colour scheme on the Style tab.
* Fix: The text header now honours "Name Size", "Name Colour", "Bio Size" and "Bio Colour" (Style → Typography). Before, those four settings only applied to the visual header, so a text header kept the theme's dark title however the colour was set.

= 3.15.2 =
* Fix: On tablets the video player in the lightbox fills the viewer's height instead of sitting in a frame with a black margin all round.
* Fix: "Clear Cache" and saving settings now take effect at once on hosts with a persistent object cache (Redis, Memcached). The plugin keeps a list of what it has cached and deletes each entry through WordPress, which reaches the object cache; before, only the database rows were deleted and visitors kept seeing the old posts until the cache duration ran out.
* Fix: "Load More" now renders the next posts with the same shortcode or block overrides as the page (layout, lightbox, columns and so on). Before, the next posts came with the feed's saved settings, so a carousel set up in a shortcode got ordinary cards after "Load More". The overrides travel with a signature, so visitors cannot change settings through the request.
* Fix: Carousel dots are now buttons labelled "Slide 2 of 5" with the current slide marked, instead of unnamed buttons a screen reader read as "button, button, button".
* Fix: Lightbox accessibility. While the viewer is open the rest of the page is marked inert, so screen readers and the Tab key stay inside the dialog (the keyboard trap remains as a fallback for older browsers). The large image now has an alt text taken from the post text, or "Photo from <Page>" / "Video from <Page>" when the post has none. Gallery thumbnails are buttons labelled "Image 2 of 5", reachable with Tab and activated with Enter or Space, with the current one marked.
* Fix: A translation of the plugin (a language pack from translate.wordpress.org) did not apply to the feed labels on sites that had saved the Components tab before the translation existed: the English default texts had been stored as if they were the administrator's own wording, so a Norwegian site kept "6 dager ago", "View on Facebook · Share" and "shared a post". A stored label that still equals its English default now follows the translation, exactly as on a fresh install. Labels you have changed or emptied are kept as they are.

= 3.15.1 =
* Fix: On desktop screens the lightbox no longer shows a video in a near-square black area with thick bars above and below. For a landscape video post the viewer is now 16:9 and the lightbox takes its height from the video, so the thumbnail and the player fill it edge to edge; on a low screen the player shrinks and keeps 16:9. Portrait videos, photos and text-only posts are unchanged.
* Fix: In the lightbox, YouTube videos now show YouTube's large 1280×720 thumbnail instead of the small 480×360 one, which was upscaled and carried its own black bars inside the picture. Videos without a large thumbnail keep the small one.

= 3.15.0 =
* New: Post card parts (Components tab). The page avatar, page name, date, likes/shares/comments, "View on Facebook" and "Share" can each be switched off per feed and per block; parts that are off are left out of the page. #Hashtags in post text now link to the hashtag on Facebook (can be switched off).
* New: Load more while scrolling (Style tab → Load More Button). The next posts load by themselves when the visitor reaches the end of the feed, up to three times, after which the button takes over. After every "Load More", screen readers hear how many posts arrived instead of every new post being read out.
* New: WPBakery Page Builder element and Elementor widget "BlueBook Feed" with feed, layout, posts, columns and colour scheme controls. Nothing changes on sites without those builders.
* Fix: In the lightbox, a video's thumbnail now fills the viewer instead of sitting small in a black area, the YouTube player sits in a centred 16:9 frame like the Facebook player, and portrait Facebook videos are recognised before their thumbnail has loaded.
* New: Content filters (Layout tab). Show only posts containing certain words or #hashtags, hide posts containing others, hide whole kinds of posts (shared, video, text-only, link) and hide single posts by pasting their Facebook link. When filters hide posts, the plugin fetches up to three further pages so the configured number of posts is still shown, and "Load More" continues where it left off. The Layout tab shows how many posts the last fetch hid. Developers: filter `bbfsync_filter_max_pages`.
* New: Privacy & Consent (Components tab). "Follow the site's cookie banner" waits for the marketing consent in CookieYes, Complianz, Cookiebot or the WP Consent API before loading images and videos from Facebook and YouTube, showing a short notice with a button where the visitor has not agreed; "Always ask in the feed first" shows that notice regardless of any banner. Notice and button texts are editable. With image copies stored on the site, only video playback asks.
* Change: The YouTube player script is now loaded the first time a YouTube video is played instead of on every page that shows a feed.
* New: Image copies on your own site. With "Store image copies on this site" on (Connect → Advanced; on for new feeds, off for existing ones until you switch it on), post photos, video thumbnails, avatars and the cover photo are downloaded by the server in the background, resized to two widths and served from the uploads folder with srcset. Visitors make no request to Facebook when the page loads, and the feed keeps its pictures during a Facebook outage — Facebook's own image links expire after a few days, which until now left the backup copy with broken images. A weekly clean-up removes copies not used for 60 days and keeps a feed under 150 MB. Developers: filter `bbfsync_image_widths`.
* New: Background refresh. A WP-Cron job refreshes every feed a few minutes before its cache expires, with the same code the page uses, so visitors no longer wait for Facebook after the cache has run out. A "Refresh now" button on the Connect tab (Advanced) fetches at once, and the tab tells you when the next refresh is due or if WP-Cron is not running on your host. A failed background refresh never replaces what visitors are currently served. Developers: filter `bbfsync_background_refresh` (false switches it off), action `bbfsync_after_refresh`.
* Fix: A shared link whose website blocks Facebook's link scraper was shown with the website's error page as its preview ("403 Forbidden", "Please be advised that LiteSpeed Technologies Inc. is not a web hosting company…"), the card linked to Facebook's redirect page and its domain label said "facebook.com". The plugin now follows Facebook's link wrapper to the real page, fetches the preview from there (asking once more as an ordinary request when the site refuses Facebook's scraper), never uses an HTTP error page as a preview, and labels the card with the real domain.
* Fix: On sites with a page cache (LiteSpeed Cache, WP Rocket, WP Super Cache, Cloudflare and similar), "Load More" stopped working for visitors once the cached page was older than the security token printed into it (12–24 hours) and showed "Error - Try Again" until the cache was purged. The token is now only required for logged-in users; for visitors the request is public and read-only, as it always was.
* Fix: Posts older than a week showed the date in UTC instead of the site's timezone, so a post published shortly after midnight (before about 02:00 in Norway) was dated the day before.
* Fix: When a "Load More" request failed (a Facebook error, or the plugin's pause after an outage), the button said "No more posts" and disappeared, as if the feed had ended. It now shows the "Error" text and stays, so the visitor can try again. Administrators get Facebook's message in the browser console.
* Fix: Photos in multi-photo posts are now printed with their real width and height, like single photos have been since 3.13.4, so the page does not shift while they load.
* Maintenance: Removed two leftover calls to the Facebook JavaScript SDK (not loaded since 3.9.23) and a stylesheet rule for a button class that no longer exists. No visible change.
* Fix: A web address at the end of a sentence in a post took the full stop, comma or closing parenthesis into the link, which could lead to a "page not found". The punctuation now stays outside the link.
* Fix: On phones, a YouTube video (or a landscape Facebook video) playing in the lightbox was shown in a box taller than the area holding it, so the bottom of the player — progress bar and fullscreen button — was cut off, with large black bars above and below the picture. The player is now a 16:9 frame centred in the area, and the plugin's own Close, Previous/Next and mute buttons sit in the black bands above and below it instead of on top of the player's controls.
* Fix: A "Load More" request with an invalid page cursor made the server ask Facebook up to ten times (every field set on both endpoints) before giving up, and anyone could send such a request. "Load More" now reuses the exact request that worked for the first page, so it makes one call, and an invalid cursor is reported as such instead of as a permission error.

= 3.14.1 =
* Fix: A percent sign in the "time ago" text (Components → Post Labels, or the `time_ago_format` shortcode attribute) — for example "%s ago (100%)" — caused a fatal error on PHP 8 on every page showing a post less than a week old. Such texts are now simply shown as written; ordinary texts like "%s ago" give exactly the same result as before.
* Accessibility: With the default button style, the lightbox's Close, Previous and Next buttons were invisible and had no focus ring for keyboard users unless the mouse pointer happened to be over the lightbox. When the keyboard is used they are now shown, and the focused button gets a ring. Nothing changes for mouse and touch.
* Accessibility: After moving to another post inside the lightbox, closing it lost the keyboard focus instead of returning it to the post that was opened. Focus now goes back to that post.
* Accessibility: On WordPress versions older than 7.1, post cards could not be reached with the Tab key on the first page load (posts added by "Load More" could). WordPress 7.1 and newer were not affected.

= 3.14.0 =
* New: The Connect tab now shows the real connection status — when Facebook last answered successfully, or why it is failing (token rejected, missing permission, wrong Page ID, rate limit, network error) together with Facebook's own message. Before, it only said "credentials configured".
* New: When Facebook rejects the Page Access Token (expired or revoked), the site administrator gets one email explaining what to do, at most once per day per feed, and the plugin's other settings tabs show a notice. Developers can change or disable the recipient with the `bbfsync_token_alert_recipient` filter.
* Fix: If your server could not reach Facebook (timeout, DNS or network failure), every uncached page view repeated up to ten 15-second attempts before showing the saved posts. A network failure now stops after the first attempt, and the plugin waits a few minutes before calling Facebook again; visitors see the saved posts immediately in the meantime.
* Fix: A broken response from Facebook (an error page instead of data) was stored as an empty feed and written over the backup copy. It is now treated as a failure, and an empty answer never replaces a backup that contains posts.
* Fix: "Posts on mobile" had no effect — every device showed the larger of the desktop and mobile numbers. Phones (up to 768px wide) now show the mobile number; "Load More" reveals the rest. If your mobile number is lower than your desktop number, phones will show fewer posts after this update, as the setting always described.
* Fix: Switching off "Image & Video Viewer" did not stop posts from opening in the lightbox. With the viewer off, photos now link to the post on Facebook, text-only posts expand in place, and videos play inline (Facebook videos open on Facebook on touch devices).
* Fix: Clicking the 2nd, 3rd… photo of a multi-photo post always opened the lightbox on the first photo. It now opens on the photo you clicked.
* Fix: The carousel layout only worked with the default 3 / 2 / 1 columns; any other column setting made the slides drift out of step. Card width now follows the column settings.
* Fix: "Load More" on feed #2 and higher used feed #1's cache slot.
* Developer: new action `bbfsync_api_fetch_failed` and filter `bbfsync_failure_cooldown`; the `bbfsync_cache_duration` filter now also applies to the page-info request, as documented. See docs/hooks.md.

= 3.13.15 =
* Fix: When Facebook returned a rate-limit or temporary service error, the feed went blank for visitors even though a backup copy of the last successful fetch existed. Those errors now fall through to the same stale-cache fallback that already covered field/permission errors, so visitors keep seeing the last good posts until the next successful refresh. Expired/invalid-token errors (code 190) are unchanged and still fail fast so admins see the message.
* Fix: The Facebook icon in the "Text" header mode was silently removed by WordPress's HTML sanitiser (which has no allowance for inline SVG), so the header showed only your text. It now renders as the setting always intended, in your chosen icon colour. Sites using the default "Visual" header are unaffected.
* Fix: A shortcode override with posts_desktop / posts_mobile above 100 asked Facebook for more posts than the Graph API permits per request and the feed failed to load. The request is now clamped to Facebook's hard limit of 100 (the admin field already stopped at 100), and the settings sanitiser caps the value there too.
* Fix: The admin footer showed the Graph API version as v22.0; it now reads the live constant (currently v26.0).
* Fix: The "show more text" caret on grid layouts now carries its aria-expanded state on the initial page render too, not only on posts loaded via "Load More". Screen-reader users get the correct collapsed/expanded announcement in both cases.
* Fix: "Verify Connection" now reports a clear message instead of a PHP notice if Facebook ever returns an empty or non-JSON body.
* Docs: The setup guide now also asks you to add the pages_read_user_content permission, matching Meta's Page feed documentation. Existing tokens keep working; this only affects how the guide describes generating a new one.
* Compatibility: Tested up to WordPress 7.1.

= 3.13.14 =
* Tweak: Updated the Facebook Graph API version used for all requests from v22.0 to v26.0. This is a maintenance update to stay current with Meta's supported API versions — v22.0 was still valid (through May 2027), but moving to v26.0 now avoids a future forced migration. No fields, endpoints, or behaviour changed; verified against Meta's official changelogs for v23.0–v26.0 that nothing this plugin uses (post fields, Page endpoints, permissions) was altered across those versions.

= 3.13.13 =
* New: "Show Load More button" toggle under Style → Load More Button lets you hide the pagination button entirely, so a feed shows just your configured number of posts as a fixed showcase. Default is on (unchanged behaviour). Also renamed the "Lightbox & Video" settings card to "Image & Video Viewer".
* New: "Image size on tablet" setting under the media viewer card gives the image a larger share of the lightbox on tablets/iPads (portrait and landscape). Choose Standard, Large, or Extra large. The post text stays fully scrollable — larger presets simply show more of the image and less text before you scroll, and the frame stays fixed while swiping between images. The new default is Extra large, so existing feeds get the roomiest image automatically on update; set a feed to Large for a middle ground, or Standard for the previous look. Phones and desktop are unaffected.

= 3.13.12 =
* Fixed: In multi-column layouts (Masonry, Boxed, Carousel), post text was still clipped to roughly 14 lines even when Text Clamp was turned off — so setting "Post text length" to 0 (show full text) together with Text Clamp off did not actually show the full text. Turning Text Clamp off now removes the grid text limit as intended. CSS only; no settings or data changes.

= 3.13.11 =
* New: "Post text length" setting under Feed Layout controls how many characters of a post's text are shown before a "See more" link — and **0 shows the full post text**. Previously post text was always hard-truncated at 200 characters with no way to show it in full. The new default for fresh installs is 0 (show everything); existing installs are automatically pinned to the previous 200-character behaviour on update, so no live feed changes unexpectedly. Works in every layout and as a shortcode/block override (`text_char_limit`). In multi-column layouts the separate Text Clamp height limit still applies — set text length to 0 and turn Text Clamp off for fully unlimited text there.

= 3.13.10 =
* Improved: The "How to configure Meta / Facebook" setup guide has been rewritten based on real-world token generation. It drops the App Secret entirely (extend the token with the Access Token Debugger's "Extend Access Token" button instead), skips the business portfolio, and adds a critical checkpoint — confirm you granted the *right* Facebook Page via /me/accounts *before* extending the token, since Facebook's authorization pop-up silently reuses your previous choice and is the most common cause of the wrong Page ending up connected. A new "Common pitfalls" section collects the failure modes (token in the wrong box, short-lived Page token, leftover Debugger tabs, #100 "Object does not exist"). Guide content only — no change to how the plugin fetches or stores anything.

= 3.13.9 =
* Improved: The "How to configure Meta / Facebook" setup guide has been updated to match Meta's current app-creation flow. It now uses the "Content management → Manage everything on your Page" use case, explains adding the pages_read_engagement permission under Use cases → Customize → Manage Pages, and retrieves the Page ID and permanent Page Access Token together via the /me/accounts endpoint — the reliable method for a new app still in development mode. Token verification now also checks the granted scopes.

= 3.13.8 =
* New: Reshared posts now use a redesigned, Facebook-style share card — a source row (brand glyph, domain, open-on-Facebook affordance) above the shared post's text, a subtle hover lift and image zoom, and full Light / Dark / Inherit / Custom colour-scheme support via the existing CSS variables.
* New: A detected reshare's header now shows a proper "shared a post" label (customisable under Components, and translatable) instead of Facebook's misleading "updated their status" story text.
* Fix: A reshare card is no longer exposed to screen readers as a button that opens the lightbox — it is a link to the original post on Facebook and is announced as such.
* Tweak: Share-card titles that merely repeat the page name are suppressed, and the source domain is shown in normal case instead of all-caps.

= 3.13.7 =
* Fix: Reshared posts where Facebook returns its "content isn't available" placeholder as the attachment title/description (common for cross-page reshares, even when the preview image resolves) no longer show that misleading text. Open Graph recovery now also reads og:title and og:description from the shared link, and the share card prefers those; when no meaningful title/description can be recovered, the card shows the image and link without the placeholder.
* Dev: New filter `bbfsync_unavailable_markers` to customize the localized substrings used to detect Facebook's unavailable-content placeholder.
* Internal: OG recovery now fetches image + title + description in one request (`fetch_og_meta`); `fetch_og_image` is retained as a compatibility wrapper, and legacy single-value OG cache entries are normalized on read.

= 3.13.6 =
* Fix: Reshared posts that were forwarded without an added caption rendered as an empty card (author + engagement bar, no body or media). Facebook's Graph API frequently returns these without status_type 'shared_story' or attachment type 'share', and the localized story text ("har oppdatert statusen sin") isn't a share keyword, so share detection missed them and they fell through to an empty text-only card. Share detection now also recognizes a caption-less reshare structurally — empty message, no own media, but attachment title/description/target present — and routes it to the existing Smart Share Card (image, title, description, source link).
* Fix: A post with no caption, no media, and no recoverable shared-content metadata is now skipped entirely instead of rendering as a hollow card.

= 3.13.5 =
* Fix: The page could become unscrollable ("frozen") after opening a post in the lightbox, navigating between posts, and then closing it. The background scroll lock introduced in 3.13.4 is reference-counted, but the lock was re-applied on every prev/next/swipe/keyboard navigation while the close only released it once, so the count never returned to zero and the position-fixed body lock stayed engaged. The page scroll is now locked once per lightbox session and released exactly once on close, regardless of how many posts you navigate through.

= 3.13.4 =
* Fix: Mobile — the page behind the post lightbox could scroll ("the posts behind slide up") while the lightbox was open. Background scroll-locking relied on `overflow:hidden`, which iOS Safari ignores for touch scrolling. Scroll locking now uses a position-fixed body lock with scroll-position restoration, reference-counted across the post lightbox, the video lightbox, and fullscreen video, so the page stays put on all devices and returns to the exact scroll position on close.
* Compliance: Added the direct-file-access guard (`if ( ! defined( 'ABSPATH' ) ) exit;`) to the block's `index.asset.php`, resolving a Plugin Check error. No functional change.
* Docs: Clarified the YouTube disclosure (privacy policy and readme) to state accurately that the YouTube iframe API is loaded on any page that displays the feed, while thumbnails are fetched only when a post actually contains a YouTube video. Wording only; no behaviour change.
* Cleanup: Removed dead code — an unused, unreachable "standalone video lightbox" subsystem (markup, scripts, and styles) that no code path ever triggered, and a deactivation handler that cleared a cron event the plugin never scheduled. No user-facing change.
* Performance: Post images, Facebook video thumbnails, recovered share-card images, and the header and follow-card avatar and cover photo now carry width/height attributes — using the Graph API's real pixel dimensions where available (post and video images, and the square page avatar), and a standard ratio where the API provides none (1.91:1 for Open Graph share images, the Facebook cover ratio for the header cover) — so the browser reserves layout space before they load, reducing Cumulative Layout Shift (CLS). Gallery and carousel images were already stable via fixed grid rows / heights.

= 3.13.3 =
* Compatibility: Tested with WordPress 7.0.
* Fix: Carousel layout — the "Load more" button was absolutely positioned at the bottom-right of the carousel wrapper, overlapping the dots and the bottom-right of the last visible post card. The button now sits in the standard sticky-footer bar below the carousel, matching the grid / masonry / list / boxed layouts.

= 3.13.2 =
* **Admin UX:** Stale-token notice on the plugin page when the stored access token is encrypted but cannot be decrypted (typically after a host migration where `wp_salt` keys were not carried over). Today the symptom is a silent empty feed; this release surfaces the cause and tells the admin to re-paste the token.
* **Admin UX:** Stale-data warning when the last successful API fetch is more than 30 days old. Helps owners notice forgotten plugins quietly serving cached content from a long-expired token.
* **Defensive:** Settings sanitization now caps `columns_*`, `posts_*`, and `text_lines_grid` at sensible upper bounds (12 / 8 / 4 / 500 / 100 respectively). Reasonable values are unchanged; absurd values from a typo no longer render thousands of hairline columns.
* **Defensive:** Inline CSS for the Custom colour scheme and header backgrounds now passes hex values through `sanitize_hex_color()` at output time as a defence-in-depth layer (the field-level sanitiser already runs at save time). No visible change for any valid setting.
* **Cleanup:** The one-time token-encryption migration flag (`bbfsync_tokens_encrypted`) is now removed on uninstall, leaving no orphan rows in `wp_options`.

= 3.13.1 =
* **Admin UX:** Inline notice next to the Page Access Token field on the Connect tab explains that the token is encrypted at rest, and that re-pasting the token will be needed if the site's WordPress salts (in `wp-config.php`) are ever rotated or the site is migrated to a new host without them. Saves a future support ticket. The encryption-at-rest behaviour itself was introduced in 3.13.0 and is unchanged here.
* **Repo hygiene:** Removed two stray cloud-sync `# Name clash` backup files from the source tree. They were already excluded from the distributable zip and never shipped to anyone — purely a dev-side cleanup.

= 3.13.0 =
* **Security:** Facebook Page Access Tokens are now encrypted at rest in `wp_options` (AES-256-CBC, key derived from `wp_salt('auth')`, random per-encryption IV under the `enc2:` envelope). A one-shot, idempotent migration runs at activation and on every `init` until complete, so sites updated via WP auto-update are also covered. Tokens decrypt transparently for the API client; no admin action required. Feed exports now strip the token field — site secrets do not travel between sites.
* **Accessibility:** Post cards are now keyboard-reachable (`tabindex="0"` + `role="button"` + `aria-label`). Enter and Space open the lightbox or expand text-only posts; visible focus ring via `:focus-visible`. WCAG 2.1.1 fix.
* **Accessibility:** Lightbox now has a focus trap (Tab/Shift+Tab wraps inside the dialog) and returns focus to the element that opened it on close. WCAG 2.4.3.
* **Accessibility:** "Show more text" button gains `aria-expanded` state, `aria-hidden` on the decorative chevron, and an explicit `type="button"`. Post-expandable cards toggle `aria-expanded` on expand/collapse.
* **Accessibility:** Default link colour bumped from `#1877F2` (4.25:1 on white — fails AA) to `#1665D8` (5.43:1 — passes AA). Visual difference is imperceptible. Affects the "Inherit", "Light", and freshly-installed "Custom" colour schemes.
* **Accessibility:** Dark scheme date colour bumped from `#6b7280` (3.84:1 on `#121212` — fails AA) to `#9ca3af` (~7.3:1 — passes AAA).
* **Performance:** OG image recovery now uses a 3-second timeout (was 8 s) and is capped at 2 cold-cache HTTP fetches per render. Cache hits — including cached failures — are always honoured for free. New filter `bbfsync_og_fetch_budget_per_render` lets developers tune or disable. Worst-case cold-cache render time for a feed full of shared-without-media posts drops from ~40 s to ~6 s.
* **Performance:** OG image regex now also matches `og:image:secure_url` and `og:image:url` variants — many modern share pages emit only the variants and skip the bare `og:image`.
* **Admin UX:** Text-Only Posts setting now lives in its own admin card so it stays reachable when the Lightbox & Video card is collapsed (its "Expand in feed" mode does not require the lightbox). The "Open in lightbox" option is automatically disabled at the markup level when the lightbox feature is off.
* **Admin UX:** Setup-guide Step 1 wording fixed — previously told admins to "paste them into the fields above" referring to App ID *and* App Secret, but only App ID has a destination field on the Connect tab.
* **Developers:** New filter `bbfsync_share_keywords` lets you extend the share-detection word list when Graph API doesn't include `status_type` (your audience speaks Spanish/Dutch/Swedish/etc.). Documented in `docs/hooks.md` with an example.
* **Developers:** New filter `bbfsync_og_fetch_budget_per_render` (see Performance above). Documented in `docs/hooks.md`.

= 3.12.17 =
* Compatibility: Verified compatibility with PHP 8.5.

= 3.12.16 =
* New: Controls Style setting — choose between iOS (liquid glass) and Classic button styles for lightbox navigation, close buttons, and video play overlays. iOS is the default.
* New: Lightbox buttons (prev, next, close) now use SVG stroke icons instead of text characters for crisp rendering at all sizes.
* New: iOS liquid glass buttons use layered glassmorphism with SVG turbulence distortion filter, specular highlights, and backdrop-filter blur.
* Improved: Lightbox buttons repositioned outside the modal content area for cleaner layout — prev on the left, next and close on the right of the full-screen overlay.
* Improved: Buttons fade in on hover (desktop) and are always visible on touch devices.
* Accessibility: Added aria-hidden="true" to decorative SVG icons inside buttons that already have aria-label.
* Accessibility: Glass-layer DOM elements only render when iOS style is active, avoiding unnecessary markup in Classic mode.
* Code: New play_button_style setting with enum validation (ios/classic), registered in Components tab group. Also available as shortcode attribute.

= 3.11.0 =
* New: Smart Share Cards — shared posts that previously appeared as plain text-only cards now display with a visually distinct link-preview style. Three-tier fallback: (1) recovered OG image with title/description, (2) styled metadata card with share icon, (3) minimal dashed-border CTA linking to Facebook.
* New: OG image recovery — when the Graph API cannot serve media for shared/cross-page posts, the plugin fetches the Open Graph image from the permalink server-side. Results are cached to avoid repeated requests.
* New: Added `status_type` and attachment `type`/`target` to Graph API field requests, enabling reliable detection of shared posts vs. genuinely text-only posts.
* Improved: Attachment `title` and `description` fields (already requested but previously unused) are now rendered inside share cards, providing context for shared content.
* Improved: Dark mode and custom color scheme support for share cards.

= 3.10.0 =
* Developers: Introduced 6 filter hooks and 3 action hooks for extending the plugin without forking. See docs/hooks.md inside the plugin folder for full documentation and examples.
* Developers: New filter bbfsync_field_sets lets you customize which Graph API fields are requested in each fallback variant.
* Developers: New filter bbfsync_posts_after_fetch lets you modify the result array (posts + cursor) before it is written to cache.
* Developers: New filter bbfsync_cache_duration lets you override the per-request cache TTL.
* Developers: New filter bbfsync_post_data lets you modify a single post's data before any rendering happens.
* Developers: New filter bbfsync_post_html lets you wrap, augment, or replace per-post markup. Fires on both initial render and AJAX Load More.
* Developers: New filter bbfsync_feed_html gives you the final feed HTML before it is returned from render().
* Developers: New action bbfsync_before_api_fetch fires before each Graph API call.
* Developers: New action bbfsync_after_api_fetch fires after a successful fetch is cached — useful for cache busting and notifications.
* Developers: New action bbfsync_cache_flushed fires after BBFSYNC_Cache::flush() completes.
* Developers: New FAQ entry "Can I extend the plugin from my theme or another plugin?" pointing at the hooks documentation.
* i18n: The "%s ago" relative-time format on post timestamps is now admin-customizable from Components > Post Labels, alongside the other label customizations. The %s placeholder is replaced with a human-readable duration; if the placeholder is removed by mistake, the format falls back to the translated default.
* No user-visible behavior change unless you customize the new label — the plugin renders identically to 3.9.33 by default.

= 3.9.33 =
* Performance: Upgraded resource hint for scontent.xx.fbcdn.net from dns-prefetch to preconnect on pages that use the plugin. Preconnect performs the full TCP and TLS handshake ahead of time (not just the DNS lookup), saving an additional ~50-150ms on first thumbnail load. Browser support is universal since 2018.
* Performance: Removed the resource hint for graph.facebook.com — the Graph API is called server-side only, so the visitor's browser never connects to it directly. Under dns-prefetch this was a tiny cost; under preconnect it would tie up a connection slot that nothing uses.

= 3.9.32 =
* Accessibility: Lightbox now exposes proper dialog semantics — role="dialog", aria-modal, aria-label, and a screen-reader hint describing the keyboard shortcuts (Escape, arrow keys)
* Accessibility: Lightbox close, previous, and next buttons now have explicit aria-label attributes
* Accessibility: Honors prefers-reduced-motion — animations, transitions and smooth-scroll are disabled inside plugin elements when the visitor's OS requests reduced motion (WCAG 2.3.3)
* Accessibility: Added :focus-visible outlines for keyboard users on feed links/buttons, lightbox controls, carousel arrows, and the Load More button
* Performance: Added explicit width and height attributes to author avatars and YouTube thumbnails so browsers reserve layout space before the image loads (improves Cumulative Layout Shift)
* Performance: Added DNS-prefetch hints for graph.facebook.com and scontent.xx.fbcdn.net on pages that use the plugin, shaving the connection setup off the first interaction
* Privacy: Privacy policy content now also discloses that profile pictures, cover photos, and post images are loaded directly from Facebook's CDN (scontent.xx.fbcdn.net) on every page render
* Security: AJAX load_more error responses no longer leak Graph API error details to anonymous visitors — admins still see the full error for debugging
* Improved: BBFSYNC_VERSION constant now reads dynamically from the plugin header via get_file_data() — single source of truth, eliminates version drift
* Improved: Build script now auto-syncs block.json and index.asset.php version metadata from the plugin header at build time
* Cleanup: Removed stale "Facebook SDK (JavaScript)" entry from readme.txt Third-Party Services — the SDK was removed in 3.9.23 but the disclosure was left behind

= 3.9.31 =
* Improved: Full internationalization audit — all frontend labels now admin-customizable from Components > Labels & Text
* Improved: Added "See more", "View on Facebook", "Share", "No more posts", and "Error" as customizable post labels
* Improved: All setting defaults wrapped in translation functions for proper localization on fresh installs
* Improved: Proper plural support for "follower/followers" using _n()
* Fixed: 12 placeholder attributes in admin panel now properly translatable
* Fixed: Visual style preset labels (Classic, Compact, Banner, Clean) now translatable
* Fixed: Regenerated POT file with correct line references and all missing entries

= 3.9.30 =
* New: Text-Only Posts setting — choose between "Expand in feed" (default) or "Open in lightbox" for posts without images or video
* Fixed: Lightbox no longer shows a dark strip at the top when viewing text-only posts
* Improved: Text-only posts expand and collapse in place with smooth animation when "Expand in feed" is selected

= 3.9.29 =
* Fixed: Text-only posts now display content in the mobile lightbox instead of showing a black screen.
* Fixed: Swipe navigation now works on text-only posts in the lightbox on all touch devices (mobile and iPad).
* Fixed: Vertical text scrolling in the lightbox sidebar no longer accidentally closes the lightbox.

= 3.9.28 =
* New: Text Preview Lines setting — limits post text in grid layouts (masonry, boxed, carousel) to keep cards compact
* New: Expand indicator (line with caret) appears below clamped text — click to expand inline
* New: Setting configurable per feed (0–20 lines, default 14, 0 = unlimited), also available as shortcode attribute and Gutenberg block control
* Improved: Mobile single-column layouts lift the text clamp for full readability
* Improved: "See more" link hidden in grid layouts — lightbox and expand indicator handle full text

= 3.9.27 =
* Improved: Setup guide Step 3 now clarifies that a User token (not Page token) must be selected, and that your own app must be chosen
* Improved: Setup guide Step 4 adds easier recommended method via Access Token Debugger "Extend Access Token" button
* Improved: Setup guide Step 5 now uses direct Page ID URL instead of /me/accounts for more reliable token retrieval
* Improved: Added troubleshooting tip for "Error validating client secret" in Step 4
* Fixed: Header followers label now uses the customizable "Followers label" setting from Components tab

= 3.9.26 =
* Improved: Simplified "Find your Page ID" step — now uses Graph API Explorer for quick lookup

= 3.9.25 =
* Improved: Setup guide now correctly explains the 3-step token process (short-lived → long-lived User → permanent Page)
* Improved: Added link to Access Token Debugger for verifying token permanence
* Improved: Each setup step links to the exact tool or page where it is performed

= 3.9.24 =
* Removed debug HTML comment from follow card output
* Fixed double-escaping in follow card CSS class builder
* Fixed stale version fallback in block asset file

= 3.9.23 =
* New: Like Box replaced with custom follow card — no longer depends on Facebook's deprecated Page Plugin SDK
* The follow card uses Graph API data (avatar, page name, follower count) and renders reliably at all sizes
* Cover photo shown in Large mode, all existing settings (position, size, display mode, width) still apply
* Removed Facebook JS SDK dependency — one fewer external script loaded on the frontend

= 3.9.22 =
* Fixed: Lightbox prev/next navigation arrows now visible on text-only posts
* Fixed: Text-only lightbox no longer stretches to full viewport height — shrinks to fit content on all screen sizes

= 3.9.21 =
* New: Infinite lightbox browsing — next batch of posts loads automatically when you reach the last post
* New: Text-only posts display in a clean full-width lightbox layout instead of an empty black media panel
* New: Customisable Mute, Unmute, and Fullscreen button labels in Features → Labels & Text
* Fixed: Per-feed CSS scoping — multiple feeds with different custom colours on the same page no longer conflict
* Fixed: Reset palette defaults now match the settings defaults exactly
* Fixed: Version consistency across .pot file and all plugin surfaces
* Changed: README.md excluded from distribution zip

= 3.9.20 =
* Fixed: Page name now displays correctly when entered manually in text header mode
* Fixed: Bio display logic made consistent across all header styles
* Fixed: Lightbox now renders above sticky headers and page builder containers
* Improved: Facebook Graph API version centralised as a single constant for easier updates
* Improved: API retry logic now logs failures and successes when WP_DEBUG_LOG is enabled
* Improved: Dark color scheme error message styling
* Changed: Legacy migration now runs only on plugin activation instead of every page load

= 3.9.18 =
* New: Like Box display mode setting — Always visible or Reveal on scroll
* Reveal on scroll: Like Box is hidden initially and fades in when the user scrolls near the bottom of the feed
* Display mode option only shown when Like Box position is set to Bottom
* Admin UI shows contextual description for each display mode

= 3.9.17 =
* Improved: Load More button now meets WCAG AAA touch target (44px minimum height)
* Improved: Button has larger padding, font size, and subtle shadow for better visibility
* Changed: Like Box and Load More button no longer share the same footer bar — Like Box renders independently above the sticky Load More bar for clearer visual separation

= 3.9.16 =
* Fixed: Video playback now works on iPad and mobile devices
* Touch devices open lightbox for video instead of broken inline embed
* All Facebook videos (landscape + portrait) show play button on touch devices
* Safari/WebKit: Facebook videos open directly on Facebook (iOS ITP workaround)
* Customizable Safari video hint text in admin settings
* Stale-cache fallback: feed remains visible when API is temporarily unavailable
* "Last successful fetch" timestamp shown in admin cache settings

= 3.7.2 =
* Carousel video no longer cropped by 220px height limit — shows full frame in natural proportions
* Portrait and landscape videos both display uncropped in carousel

= 3.7.1 =
* Video thumbnails now display full frame without cropping (object-fit: contain)
* Removed fixed height constraint on carousel video — shows natural proportions
* Container dimensions locked from thumbnail before iframe replacement

= 3.7.0 =
* One-click video playback on all browsers — autoplay with mute for reliable instant play
* Facebook video in lightbox now fills entire media area for immersive viewing
* Applies to both feed/carousel and lightbox video embeds

= 3.6.3 =
* Lightbox video loads iframe directly (no thumbnail step) — eliminates double-click on mobile
* Portrait video in lightbox now centered horizontally instead of left-aligned
* Removed "Watch on Facebook" overlay from lightbox video embeds

= 3.6.1 =
* Tightened gallery gap from 4px to 2px for cleaner, more professional image grids

= 3.6.0 =
* Pro gallery grid: 6-column CSS Grid matching Facebook/Smash Balloon layout patterns
* 2 images: equal side-by-side; 3: large left + 2 stacked right; 4: 2×2; 5+: 2 top + 3 bottom
* Up to 5 visible images per gallery with +N overlay on last
* Subtle inset border on gallery cells (Facebook-style depth)
* All images use object-fit: cover for consistent look with mixed portrait/landscape

= 3.5.9 =
* Redesigned gallery grid: 2 side-by-side, 3 L-shape, 4 as 2×2, 5+ as 2-top/3-bottom (Facebook pattern)
* Gallery now shows up to 5 visible images with +N overlay for remaining
* Improved gallery proportions and spacing across all layouts

= 3.5.8 =
* Fixed multi-image posts: explicit subattachment fields in API request and fallback for multiple attachments
* Increased subattachment limit to 25 images per post

= 3.5.7 =
* Facebook-inspired gallery grid: 2 images side-by-side, 3 images L-shaped, 4+ with top hero and thumbnail row
* Gallery uses CSS Grid with center-crop for consistent layout across all image orientations
* +N overlay on last visible image when posts contain more than 4 photos
* Carousel layout now accommodates gallery grids with adaptive height
* Portrait video thumbnails use 4:5 aspect ratio matching Facebook feed standard
* Cleaned up duplicate play button CSS and legacy portrait video placeholder rules

= 3.5.6 =
* Gallery main image capped at 400px height for consistent layout with mixed portrait/landscape images
* Gallery thumbnails increased from 100px to 120px height

= 3.5.5 =
* Fixed portrait video playback: container height locked before iframe replaces thumbnail
* Portrait videos in lightbox now render in correct aspect ratio
* Portrait video detection propagated to lightbox for consistent display

= 3.5.4 =
* Portrait video thumbnails now display in natural aspect ratio instead of being cropped to 16:9

= 3.5.3 =
* Added "Loading Text" admin setting for Load More button loading state

= 3.5.2 =
* Added "Watch on Facebook" fallback link for copyright-blocked video embeds
* Fallback appears on hover in feed, always visible in lightbox

= 3.5.1 =
* Added esc_attr() wrappers on assembled style attributes for Plugin Check compliance
* Added esc_url() in linkify() for proper URL escaping in href attributes
* Added phpcs:ignore comments for pre-escaped concatenated output
* Added "No posts found" admin-only message for empty feeds
* Prefixed uninstall.php variables for codesniffer compliance

= 3.5.0 =
* Admin panel redesign: reduced from 4 tabs to 3 (Connect, Appearance, Features)
* Header settings moved into Appearance tab as a section (no longer a separate tab)
* Content settings (page name, bio, avatar, cover) shown directly without accordion
* Mobile header toggles shown inline (removed Mobile accordion)
* Backwards-compatible URL mapping for old tab names (layout-style, header)

= 3.4.3 =
* Added constant definition guards to prevent redefinition errors
* Fixed unprepared DB query in legacy migration (now uses $wpdb->prepare)
* Added privacy policy suggestion via wp_add_privacy_policy_content()
* Removed hardcoded BBFSYNC_LAST_UPDATED constant

= 3.4.2 =
* Whitespace cleanup across PHP, CSS, and JS files

= 3.4.1 =
* Fixed unprefixed variables in uninstall.php
* Added missing contributor to readme.txt

= 3.4.0 =
* Admin panel UX redesign: reorganised from 6 tabs to 4 (Connect, Layout & Style, Header, Features)
* Client-side tab switching — no page reloads when navigating between settings tabs
* Header tab now uses collapsible accordion sections (Content, Background, Typography, Mobile)
* Features tab merges Sections and Lightbox into card-based toggles with enable/disable switches
* Layout & Style tab merges Layout and Colours into one tab with clear visual sections
* Connect tab: collapsible Advanced section for cache settings, auto-expanding setup guide
* Two-row feed management bar with shortcode badge and copy button
* Sticky save button with unsaved changes indicator
* Connection status banner shows setup progress at a glance
* Unsaved changes warning when navigating away from modified settings
* Backwards-compatible URL mapping for old tab names

= 3.3.0 =
* Fixed Facebook video playback (black screen on play) with direct iframe embed
* Added feed Export/Import functionality in admin panel
* Added header text color option for text-style headers
* Fixed inline SVG icons being stripped by output escaping

= 3.2.0 =
* Added lightbox image counter for multi-photo posts
* Security hardening: feed management actions use POST requests
* Mobile carousel improvements (auto-height images, swipe navigation)
* Added mobile-specific toggles for header page name and bio visibility
* Two-level lightbox navigation: images within posts, then between posts
* WordPress.org audit compliance improvements

= 3.0.0 =
* Multi-Feed Manager — Create unlimited feeds with individual settings
* Feed selector dropdown with create, duplicate, rename, delete
* Per-feed shortcodes with ID parameter
* Shortcode attribute overrides for any setting
* New feeds inherit API connection from primary feed

= 2.9.0 =
* Header redesign with 4 visual styles (Classic, Compact, Banner, Clean)
* Header backgrounds: Theme, Solid color, Gradient, Transparent
* Visual style picker with mini preview cards in admin
* Show Bio toggle shared across header types

= 2.8.0 =
* Header style picker with visual preview cards
* Text header: transparent background option, custom text size
* Visual header: reorganised settings with size/color controls

= 2.7.0 =
* Dark color scheme with OLED-friendly pure grey palette
* Reset to Default button for custom color palette

= 2.6.0 =
* Sticky footer bar combining Like Box and Load More

= 2.5.0 =
* Carousel swipe with snap-based passive touch navigation
* List layout width increased to 960px

= 2.4.0 =
* Multi-card carousel with responsive columns
* Dot pagination and arrow navigation

= 2.0.0 =
* Touch swipe in lightbox
* Facebook video iframe embed
* Video touch zones for mobile

= 1.9.0 =
* YouTube auto-detection and click-to-play

= 1.8.0 =
* Universal post lightbox with split layout

= 1.7.0 =
* 9-color customization system

= 1.6.0 =
* Boxed layout, author links, image galleries, engagement bar

= 1.5.0 =
* Carousel slider, video lightbox, cursor-based pagination

= 1.0.0 =
* Initial release with List and Masonry layouts
