=== HomeRunner Conversion Kit ===
Contributors: spearsgroupcreative
Tags: homerunner, booking, conversion, rate-comparison, ota
Requires at least: 5.8
Tested up to: 6.4
Requires PHP: 7.4
Stable tag: 1.81.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Conversion optimization suite for HomeRunner booking sites. Includes Rate Comparison Tool and future conversion enhancements.

== Description ==

HomeRunner Conversion Kit extends the HomeRunner plugin with powerful conversion optimization tools designed to increase direct bookings.

**Current Features:**

* **Rate Comparison Tool** - Displays OTA price comparison widget showing savings from booking direct
* **Contact Form** - "Have a Question?" CTA with modal contact form, configurable fields, email routing, and spam protection
* **Activity Counter** - Social proof with live viewer counts
* **Split Cost Calculator** - Per-person and per-family cost breakdowns
* **Price Alerts** - Email notifications when prices drop
* **Wishlist** - Save favorite properties
* **Cart Abandonment** - Recovery emails for abandoned bookings
* **Promo Bar** - Scheduled site-wide announcement bar with coupon-validated CTAs
* **Emails Hub** - Centralized branding, copy editing, and live preview for all plugin emails
* **Review Insights** - AI-generated review summaries
* **Vibe Profile** - Property personality tagging
* Event-driven architecture (no DOM scraping)
* Responsive design (desktop & mobile)
* Customizable multipliers for each OTA platform
* Themeable via CSS variables

**Supported OTA Platforms:**

* Airbnb
* Vrbo
* Booking.com
* Expedia

**Requirements:**

* HomeRunner plugin version 2.1.0 or higher
* PHP 7.4 or higher
* WordPress 5.8 or higher

== Installation ==

1. Upload the plugin files to `/wp-content/plugins/homerunner-conversion-kit/`
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Ensure HomeRunner plugin is active (required dependency)
4. Configure settings under Settings → Conversion Kit

== Frequently Asked Questions ==

= Does this plugin work without HomeRunner? =

No, this plugin requires HomeRunner plugin version 2.1.0 or higher to function.

= Can I customize the OTA multipliers? =

Yes! Go to Settings → Conversion Kit to configure multipliers for each platform.

= Does it work on mobile? =

Yes, the widget includes a mobile drawer interface optimized for touchscreens.

= How does it get pricing data? =

The plugin hooks into HomeRunner's quote data filter for reliable, real-time pricing without DOM scraping.

== Changelog ==

= 1.163.0 =
* New: cart recovery reports under the industry definition. Recovered now means the guest we emailed clicked the reminder link and booked the same stay within 7 days. Every reminder link carries a signed click credential and UTM tags (`utm_source=cart_recovery`); clicks are recorded as immutable rows; the Insights page shows reminder emails sent, clicked, booked within 7 days of a click (count and gross booking value per currency), assisted by the on-site reminder, emailed with no click, and tracked bookings that returned on their own, each with its definition. Rows written under the older rule keep their labels on one "before" line.
* Changed: the Central reconcile sweep now walks every confirmed reservation created in the last 90 days (created_at order, persisted page cursor) and credits each to at most one cart by guest identity (reminder click, reminder recipient, or verified guest email), never by newest cart. Browser-side completion signals stay provisional until the server-side reservation confirms them in place. On-site bookings are credited the moment Central confirms them.
* Changed: cart-recovery and AI Concierge links no longer count as a marketing touch, so a partner's last touch survives a later reminder click; both are also excluded from commission reports.
* Internal: three new tables (`hrck_cart_notifications`, `hrck_cart_clicks`, `hrck_reservation_ingest`), new ledger columns (`booked_at`, `confirmed_at`, evidence ids, `definition_version`), `hrck_visits.cart_notification_id`, `hrck_cart_events.dismissed_reservation_id`. The `hrck_reservation_ingest_consumers` filter lets an add-on register as a consumer of every confirmed reservation.

= 1.160.1 =
* Improvement: the cart abandonment webhook's market field now reads the home's PMS account first (`platform_market_map`, account id to market name), then the destination term, then the state map. On a site that runs one PMS account per market this makes the market exact for every home whether or not it was tagged. A new `account` source can send the PMS account id itself under a partner-named key. Nothing changes for sites without these settings.

= 1.160.0 =
* New: the cart abandonment webhook can carry property context on each cart: market, city, state and the listing tags, under key names the receiving partner chooses (`extra_fields` in the webhook settings, plus a `state_market_map` so a home with a state but no location still resolves to a market). Off by default: a site that has not opted in sends exactly the same payload as before.

= 1.159.17 =
* New: Compare Homes offers a Share button beside Copy link on phones and tablets. It opens the device's share sheet with the link to the open comparison and the names of the picked homes, so the table goes straight into iMessage, WhatsApp or wherever the group is deciding. Closing the sheet changes nothing; if the device refuses the share, the link is copied instead. Desktop browsers keep Copy link alone.

= 1.159.16 =
* New: Compare Homes has a shareable link. Opening the table puts the picked homes in the page address (compare=...), a Copy link button in the table header copies it, and a pasted link opens the same comparison on the same search, dates included. Closing the table clears the address again.

= 1.159.15 =
* Fix: Compare Homes keeps the picked homes across a page load in the same tab. Paging that reloads the page, or opening a home and coming back, returns to the same picks and the same tray. A home that has left the results keeps its name and photo; its dated total and link are dropped when the search changed, as before. Cancel clears the stored picks, nothing is stored until a home is picked, and nothing is restored on a page without the full Explorer view.

= 1.159.14 =
* Change: Compare Homes attaches only to the full Explorer view, the results grid with its filter bar. The "Show Compare" setting is gone: a filter-less embed such as a featured-homes slider on the home page, or a search bar on its own, never gets the tool. A saved "every grid" choice is ignored.

= 1.159.13 =
* Fix: Compare Homes follow-ups from the post-release review. The label gutter now takes the wider of the two chip faces and is measured again when the first read is zero; a card that loses both controls in a re-render drops its compare flag; the Label Designer's moved overlay keeps its full canvas; a chip's accessible name carries the button text; a notice in the tray survives background re-syncs until the next pick; hover no longer hides the keyboard focus halo.

= 1.159.12 =
* New: Compare Homes tool (ckit#217). A Compare toggle above HomeRunner Explorer results (or a floating button where there is no results header) switches the list into compare mode: each card's favorite heart becomes a "+ Compare" chip (it reads "Added" once picked) and a tap on the chip or anywhere on the card picks the home, two to four per comparison. Cards show nothing from this tool until the toggle is on; picked homes collect in a tray at the bottom of the page (thumbnails, how many more can be added, a Compare button); Compare opens a side-by-side table: photo, price, sleeps, bedrooms, bathrooms, location and an amenities check list, with a "Show only differences" switch, a sticky header row and a sticky label column. Settings: show on every Explorer grid or only the search page, homes per comparison (2 to 4), button text, accent color. The card control is added from JS over Explorer's own markup (no template changes); facts come from a new POST /hrck/v1/compare-homes route; the price row shows the card's live total (with its taxes-and-fees note) when the guest searched with dates. Styled to the Trip Planner language. The Group Stays compare is unchanged.

= 1.153.0 =
* New: Trip Planner optional AI composer (setting "composer", default "deterministic": nothing changes until a site opts in). At unlock, an OpenRouter model personalizes the prose of the deterministically built plan and may reference operator-authored Recommendations. Matching, availability, quoting, and booking stay deterministic. Any validation failure ships the operator-authored plan unchanged.
* New: Recommendations post type (hrck_recommendation) with a single category, optional coordinates or an area-wide flag, and interest tags. Plans show distance chips computed on the server.
* New (AI mode only): a wizard "Pace" choice and a short free-text note. The note never enters share links, webhooks, or public payloads. The email gate carries an AI disclosure.
* New: day swaps on AI-composed plans re-personalize the swapped day.
* New: settings for a write-only OpenRouter key (or the HRCK_OPENROUTER_KEY constant), a model allowlist (default anthropic/claude-haiku-4.5), a 200-calls-per-day cap, and distance radius and unit. The Plans tab and the health block report composer outcomes. Privacy export and disclosures updated.
* Change: the plans table gains composer status columns (Trip Planner schema 1.1.4).

= 1.152.0 =
* New: Trip Planner "Show me another option". A guest holding the plan's access link can swap any eligible day of an unlocked itinerary for the next-best authored day. Swaps cycle through the ranked alternatives and are recorded on the plan.
* Change: the plans table gains a swap_count column (Trip Planner schema 1.1.3). Pre-upgrade plans keep their dedupe key and do not show the control.
* Change: the Plans tab shows a Swaps column and states that a resend sends the plan as it is now. The health block reports plans with swaps and swap counts for the last 30 days. The privacy export lists plan changes.

= 1.151.0 =
* New: Trip Planner step 1 takes exact check-in and check-out dates. The server rejects stays outside the lead window, booking horizon, or minimum and maximum nights instead of clamping them. Older month-based share links and stored plans keep working in month mode.
* New: Trip Planner availability-aware matching. Capacity-fit candidates are ranked and quoted in order until one prices for the dates, and the plan's property becomes the unit that priced.
* New: Trip Planner "Price before email" setting (off by default; requires "Price at Unlock"). When on, the gated preview fetches the matched property's live quote through a rate-limited, reCAPTCHA-protected endpoint before the guest enters an email.
* Change: Trip Planner entries, inquiry webhook payloads, operator notices, guest emails, the privacy export, and the Plans tab carry the exact dates.

= 1.150.0 =
* New: Trip Planner "Skin" setting. Classic (default) blends into the theme; Editorial adds a display serif, a dark results band with the property card overlapping it and an italic accent subtitle. Same markup and script, token overrides only.
* New: Trip Planner "Display Font" setting, a CSS font-family for headings and titles (blank uses the skin's default). The plugin never loads fonts: name a face the theme already loads.
* Change: Trip Planner UI redesign. Wizard card with a step eyebrow, display title, 1 / 2 / 3 counter and progress bar, a grouped travelers panel with a live guest total, a segmented first-visit control, indicator style cards, a 3-up budget row and icon chips; results with a header band and meta chips, a highlights grid, a property card with the price hero, and a timeline rail with morning / afternoon / evening icons; the email gate now follows one locked teaser with a compact "N more days" stack and a mobile sticky unlock bar instead of six blurred cards; the standalone plan document gains a top bar, a "Prepared by" footer and a print stylesheet; the plan email carries the same hierarchy.

= 1.149.0 =
* New: Trip Planner tool (premium, #422). A three-step itinerary wizard with a deterministic, admin-editable day-by-day plan, a matched property, an email gate for days 2+, plan emails, ckit Entries, optional forwarding to the Contact Form outbound webhook, print-to-PDF and share links. Content is a validated JSON document under HomeRunner → Trip Planner Content.
* New: Trip Planner booking bridge. Matched-property links open the booking widget with the plan's dates and guests already filled in, and bookings that follow are credited back to the plan through booking attribution (trip_planner/booked insight events with the reservation value; per-plan totals on the Plans tab).
* New: Trip Planner "Price at Unlock" setting (off by default). When on and a property matches, the unlock fetches the property's live Central quote for the plan's dates and party server-side, stores it on the plan, and shows "from $X for your dates" on the property card and in the plan email; the entry, operator notice and webhook carry the quote, and the Plans tab reports quoted vs booked. Quotes stop showing after 30 days (filter hrck_trip_planner_quote_max_age_days).
* Change: the Trip Planner installer creates its plans table as InnoDB and converts it — and a legacy MyISAM hrck_events — to InnoDB when a host's default engine created them (the booked-event + plan-stamp transaction needs both).
* Change: hrck_visits gains a nullable trip_plan_id column (additive; guest identity schema 1.6.0). The visitor tracker also captures an hrck_plan URL parameter into a first-party cookie and strips it from the address bar; utm_* handling is unchanged.
* Change (Contact Form): reCAPTCHA v3 verification now also checks the token's action and hostname (site host plus aliases from the hrck_recaptcha_hostnames filter). Tokens minted for another site or action are rejected; transport failures keep the fail-open default and filter.
* Change (Contact Form): reCAPTCHA is saved as enabled only when BOTH the site key and the secret key are present; saving with one key blank disables it with a settings notice instead of silently half-configuring it.
* Change: InquiryPayload gains a `source` value per producing tool (Contact Form stays `have_a_question`) and an optional `trip` block; inquiries without a property now send empty property fields instead of resolving the global post. Every existing key keeps its name and type.

= 1.148.0 =
* Change: the Review Insights "What Guests Are Saying" card and "Frequently Asked by Guests" accordion now land after the property description on YOOtheme and other page-builder layouts. Those layouts carry none of the description containers the automatic placement looked for, so both blocks were appended to the very end of the page content (lakefront.luxury: description at the top of the page, summary card 6,300 pixels further down, below the AI-Ready block). Automatic placement now also finds the description by its text and places both blocks right after it. Two new settings give you direct control: a Placement Mode (Automatic, CSS selector, or Manual through shortcodes) with a Placement Target selector and a Placement Position (inside the target or after it). A new [hrck_review_faq] shortcode places the FAQ accordion by hand, the way [hrck_review_summary] already does for the card, and a block placed by shortcode is never printed a second time. When both blocks and the AI-Ready block share the end of the page, the review blocks come first.

= 1.147.5 =
* Fix: the session request the Conversion Kit sends on every page view is no longer sent from pages viewed under another address, such as a Google Translate view, a staging domain, or a preview embed. Those views could never keep the site's session cookie, and the request only produced a 403 in the host's access log (luxelodgebr.com: 104 visitors). Visitors on the site's own address are unchanged. The visit tracker also stops answering a reload of the same page inside a minute with a 429; the first visit was always kept, and the repeat is now acknowledged as a duplicate instead of logged as an error. A single address sending more than 120 visits in five minutes is still refused.

= 1.145.2 =
* Fix: the Review Insights trust badge now counts and averages every published review of a property. Its review count and average rating were computed from the same short list of reviews that goes to the AI for summarizing, which stops at the "max reviews per prompt" setting and always keeps every 5-star and 1-star review. On any property with more reviews than that cap the badge showed a smaller count and a rating pulled toward the extremes (hostandkeep.com: 83 reviews at 4.95 on a property with 108 rated reviews averaging 4.69). The AI summary still reads the same selection as before. Only the badge numbers change, and they now match the property review stats HomeRunner itself keeps: reviews without a rating are left out of both the count and the average. The badge is also updated when a property's last review is removed, or when every review sits under the minimum star rating set for the AI summary; before, those cases left the previous numbers in place.

= 1.145.0 =
* New: the commission report now credits a referring website even when the partner never tagged its links. When the credited touch has no utm_source but the visit came from another site, the booking is credited to that site's host name as a slug, so hi-tide-resort.com shows up as hi-tide-resort-com, in both first- and last-touch modes. utm_source wins whenever it is present, and Google Ads and Meta click-throughs are never treated as a partner. Search engines, social networks, OTAs, AI assistants, webmail, payment redirects, and your own site never count as a partner either; the hrck_attribution_referrer_source_denylist filter adds any other host you never want to pay commission on. New slugs appear under Unregistered slugs on the Attribution screen until you give them a display name. Bookings recorded before this update keep the credit they already have (recreationstays.com).

= 1.144.0 =
* Fix: inquiry notification emails no longer claim to be From the WP admin email address. When a site's admin email is a free-mail address (a leftover builder gmail.com account, for example), mail sent from the site's own server fails SPF/DKIM alignment for that domain, and DMARC-aware receivers, Google Workspace above all, drop the notification while the entries screen shows every send green (hosthouserentals.com). The Have a Question form, Group Stays inquiries, and every emails-hub send now use the From address configured under Emails > Sender, and when none is saved they default to wordpress@your-site-domain, the same SPF-aligned pattern WP core uses. Sites that already configured a From address are unchanged.
* Fix: the Group Stays date picker is now immune to theme button styles. Themes and page builders style every button on the page (Hello Elementor's reset adds padding and a border, Elementor kits add the accent background), and those rules outranked the picker's own: each day cell picked up 16px of side padding, the two month grids grew wider than the panel and overlapped each other, and the Done, Clear dates, and Find group stays buttons came out in the theme's button look with a stray border (palmbeachvacationrentals.net). The picker's day cells, month arrows, and every wizard button now pin their own box and paint, and the calendar columns are floored so a cell can never widen its grid.
* Fix: on themes whose header is fixed to the top of the viewport at load (Elementor sticky headers, for one), the Group Stays page title sat underneath the header because the wizard page has no theme section above it to absorb the header's height. The wizard now measures how far a fixed header reaches past the top of the page and pads itself by that amount, re-measuring on resize. Themes that already clear their header, and [hrck_group_stays] embeds, are unchanged.
* Fix: in the phone-width date picker sheet, the sticky Clear dates / Done footer now paints above the day numbers that scroll underneath it.

= 1.143.0 =
* New: a Group Stays inquiry sent from the nearest-fit screen now carries the combinations the guest was looking at. The operator email lists each option (up to 3) as a bold "3 homes · sleeps 8 of 10" line followed by the home names, linked to their listing pages, with each home's sleeping capacity; the Leads entry stores the same options as a readable "Options the guest saw" field, and a resend from the Leads screen reproduces the email. The homes are resolved on the server from the search's own bundle hashes (the same short-lived records a save uses), never from the browser, so the request cannot name a home the search did not produce; options whose records have expired are simply left out and the "Open this search" link remains the fallback. Inquiries from the no-results screen are unchanged.

= 1.140.1 =
* Fix: the Group Stays wizard page (/group-stays/) now hands its template to WordPress through template_include instead of rendering and exiting during template_redirect. On sites whose theme wraps pages through that filter (Divi Theme Builder with a header or footer layout, for example) the old path skipped the theme's footer renderer, so the page ended without wp_footer(): no scripts, a dead search, a dead inquiry form, and a blank screen behind theme preloaders (heavenlyvacations.com). Sites without such wrappers render exactly as before.

= 1.140.0 =
* New: the Group Stays wizard's "Email us your dates" handoff is now "Let us put something together", a real inquiry form instead of a mailto: link. When no combination fits, the guest fills in name, email, phone, dates, group size, and a note in a modal (dates and group size are prefilled from their search). Every inquiry is saved under Leads (form "Trip Builder (group stay inquiry)"), linked to the visitor's guest identity, and emailed to the operator with Reply-To set to the guest, the site name in the subject, and a link that re-runs the guest's search. The recipient is the Trip Builder "Lead & inquiry recipient" setting (previously "Lead notice recipient"), falling back to the site admin email; before this the link always used the admin email with no way to change it. The mailto: link remains as the no-JS fallback and now honors the same setting.

= 1.139.6 =
* Fix: Template Customizer edits to archive-property.php now load on the property archive. The template_include override only ran on `is_singular( 'property' )`, so an archive override was written to disk, reported as saved, and then silently ignored - the page kept rendering the bundled template. Single-property behavior is unchanged.

= 1.139.0 =
* Fix: the visitor tracker no longer removes utm_*/gclid/fbclid parameters from the address bar after capturing them (#407). The cleanup raced Google Analytics, Google Ads, and Meta tags that read the page URL after load, silently dropping campaign attribution in those tools. Attribution capture inside Conversion Kit is unchanged. Sites that prefer tidy URLs and accept the tradeoff can opt back in with the hrck_clean_url option or filter.

= 1.118.0 =
* New: Recovery coupon for Cart Abandonment (#339). Enter a coupon code you created in your PMS (e.g. Guesty) and every recovery email shows it prominently and pre-applies it on the booking link. When a confirmed reservation comes back through Central booked with that code, the recovery is recorded as coupon-verified — redemption-proof attribution, the strongest causal signal available — and surfaces under "Recovered via Email" on the Insights dashboard. Off by default; leave the setting empty to keep current behavior.

= 1.98.0 =
* New (CK Premium): Availability Watchers — guests on a sold-out property page can ask to be emailed the moment their dates open up. Double opt-in confirmation that survives email security scanners, one coalesced email per opening (never one per property), automatic expiry when dates pass, and unsubscribe in every send. Availability is rechecked a few times a day against HomeRunner Central with strict fail-safe handling: an outage or partial response can never trigger a false "your dates are open" email.
* New: Watchers Health admin view — sweep freshness, Central failures, notification backlog, paused-by-license counts.
* Improvement: commission reports now exclude the site's own re-engagement traffic (availability watcher emails) from partner referral attribution.

= 1.95.1 =
* Security fix: guest REST endpoints no longer mint the session cookie on GET requests, and all hrck/v1 GET responses now carry explicit no-store cache headers. On hosts whose page cache keys on URL alone and ignores cookies, a session cookie minted on a stable REST URL could be cached and replayed to other visitors, making unrelated visitors share one guest session. Guest portal and cart requests also add a cache-busting parameter so per-guest data is never served from a shared cache.

= 1.82.0 =
* Fix: The contact form's hidden anti-spam (honeypot) field is no longer tripped by browser autofill or password managers. It was named "website", which some password managers filled automatically; the server read that as a bot and silently dropped the inquiry (the guest saw a success screen, but no email was sent and no entry was recorded). The field is renamed to a neutral name with autocomplete and 1Password/LastPass/Dashlane ignore hints so autofill leaves it alone. Bot detection is otherwise unchanged.

= 1.81.0 =
* New: "Sync All Sections" button on the Template Customizer Overview activates every templatable section for customization in one click. Templates you have already customized are retained untouched; only sections not yet synced are activated. This also makes the Create Backup ZIP a comprehensive full-template backup.
* New: Per-section results panel after Sync All showing activated / retained / failed counts (with error detail) per section, so a partial failure is visible instead of silent.

= 1.80.0 =
* New: Trip Builder saved trips with a guest email are now stored as Form Entries; the guest's trip-link email is logged on the entry and can be re-sent from the Entries screen.
* New: Cart abandonment entries — when a browsing session is identified by email, its abandoned carts are recorded as Form Entries (one per property, never for anonymous sessions).
* New: Failed-delivery alarm — when a form's notification emails fail repeatedly (default 3 in a row, configurable on the Entries settings screen), a dismissible admin notice links to the affected entries and clears on the next successful send.
* New: Optional guest receipt for the contact form — email the guest an automatic copy of their inquiry, rendered from the stored entry (off by default).
* New: "Delete all entries on uninstall" opt-in (off by default). The shared entries data is only removed when the HomeRunner Toolkit is not installed either.

= 1.79.0 =
* New: Form Entries — every "Have a Question?" submission is now stored locally with a per-entry delivery log (admin screen under HomeRunner → Entries), one-click email/webhook resend, CSV/JSON export, retention control, and WP privacy (export/erase) integration. Submissions are recorded before the notification email sends, so a mail failure no longer loses the lead.
* Changed: The contact form notification email now includes the phone number and marketing-consent answer when those fields are enabled.

= 1.64.0 =
* Fix: The bundle search could report "No combination sleeps your whole group" while a covering combination existed. A coverage rescue pass now anchors on every candidate home (not just the largest) and fills largest-first before giving up on a search radius.
* Changed: The maximum homes per combination setting is now a preference, not a hard wall. When covering the whole group needs more homes, the search grows the combination up to 8 homes ("suggest as many listings as it takes").
* Changed: Nearest-fit results are ordered by smallest capacity gap first, and the guest view shows at most the 3 closest fits.
* New: The nearest-fit message now says how close the best option came (for example "sleeps 40 of your 42 guests") and offers recovery actions: reopen the date picker, or email the property manager with the dates and group size prefilled.
* New: Compare combinations side by side. Guests tick 2-3 options and get a column view of total price, sleeps, home count, spread, and the per-home lineup, with the same choose action.
* New: Check-in and check-out collapse into a single "Dates" field showing the range (for example "Jul 15 - Jul 18") when the range picker is active. Native date inputs keep two fields.
* Changed: Distances read in miles for guests: preset labels ("About 1 mile - walking distance"), the per-combination spread line, and the widened-search notice. The API keeps meters.
* Changed: Trip page polish. Total and booking progress share one summary card with a proper progress bar and an all-booked state, listing cards get sleeps/bedrooms/baths, larger photos, and a clickable image, and statuses read more clearly.

= 1.63.0 =
* New: Trip Builder results show real property cards with a photo, sleeps, bedrooms, baths, and a link for every home in a combination, in the results list and the swap picker.
* New: Each multi-home combination gets a mini-map with numbered pins (reuses the HomeRunner plugin's bundled Leaflet; skips gracefully when it isn't available). Pin coordinates are rounded to about 110 m so exact addresses stay private.
* New: Check-in and check-out get a dual-month range picker. Sites running Searchbar Pro see its look (rebuilt with Trip Builder's own class names); other HomeRunner sites reuse the Explorer date widget; plain sites keep native date inputs.
* New: Guests can choose how close together the homes should be: walking distance, a short drive, or any distance.
* New: Skeleton loading placeholders while a search runs, while trip-page prices hydrate, and while a swap updates.
* New: "Start over" clears the wizard, results, and URL in one click. Trip pages get a "Copy trip link" button for organizers.

= 1.62.1 =
* Fix: Per-home prices on trip pages no longer disappear on sites running HomeRunner Toolkit skins — the trip page previously borrowed Explorer's price CSS classes, so skin flags (like the bottom-right price position) pulled the price out of the card. Trip pages now use their own class names.

= 1.62.0 =
* New: Trip Builder guest search now checks real availability — only homes Central confirms are open for the searched dates are offered in combinations. One batched, cached availability call per search (the same source Explorer's date filter uses); if the check itself fails the search proceeds unfiltered rather than going dark.
* New: Swapping a home also honors availability, and saved trips can no longer contain homes that are blocked for the trip dates.

= 1.61.2 =
* Fix: Trip pages now explain when live pricing can't be confirmed (blocked dates or rate restrictions upstream) instead of silently showing no prices. The exact totals still appear at checkout.

= 1.61.1 =
* Fix: The group-stays wizard page no longer renders with a "Page not found" title and a noindex robots meta — the wizard now claims its URL before WordPress (and SEO plugins) flag the request as a 404.

= 1.61.0 =
* New: Trip Builder guest UI — a public group-stays wizard at /group-stays/ (dates + group size) that renders ranked home combinations with per-slot swap alternatives, list-price totals, nearest-fit warnings, and a contact fallback when nothing fits. Only appears when "Guest self-serve search" is enabled.
* New: Email gate + save — guests save a chosen combination as a real trip (POST /trip-builder/save, rate-limited, validated against the 15-minute search hash so trips can't be fabricated), get the trip link by email, and land on the existing /trip/<hash> page to book each home. The operator gets a lead notification with the guest's email.
* New: Explorer banner — when a guest searches Explorer for a group of 10 or more (threshold configurable), a dismissible banner offers the group-stays search with their dates and party size carried over. Fails silent if Explorer markup is absent.
* New: Swap now re-prices — swapping a home re-shapes the exact combination server-side so prices, scores, and the save hash stay correct.
* Under the hood: guest_email column on the trips table (explicit migration), swap candidates now carry title/URL/price for the modal, and the search endpoint accepts an exact property set for the swap flow.

= 1.60.0 =
* New: Trip Builder guest self-serve search engine (dark launch) — a pairing algorithm that bundles nearby properties to sleep large groups: greedy anchor matching, capacity and proximity scoring, automatic radius widening, and ranked results with swap alternatives per slot.
* New: REST endpoints — public POST /trip-builder/search (rate-limited, off until the new "Guest self-serve search" setting is enabled) and admin GET /trip-builder/inventory for data-quality diagnostics.
* New: "Group-stay eligible" checkbox on each property plus Trip Builder settings for tag mode, pairing radius, anchors, and bundle size. In the default "optional" tag mode all properties are eligible until you tag at least one.
* Note: No guest-facing UI yet — this release ships the engine and API only; nothing changes on any site until the guest flow is switched on.

= 1.59.1 =
* Fix: Trip Builder property picker no longer uses WordPress content search, which site search stacks can silently filter — it now matches every published property directly by title, internal address name, or post ID. Searching "263" or "14813" finds the unit even when site search can't.

= 1.59.0 =
* New: Trip Builder admin now manages trips end to end — an All Trips table with booked progress, copy-link, edit, and delete. No more one-off snippets for trip cleanup.
* New: Editing a trip keeps its share link and preserves booked listings; dates lock once a guest has booked, and booked listings can't be removed.
* New: Property picker results show each listing's internal address name and sleeps count, and flag combined (A+B) listings so pairings are built from individual units.
* New: REST endpoints (admin-only) to list, update, and delete trips.

= 1.58.0 =
* New: Trip Builder pages now show live pricing — each open listing displays its stay total and per-night rate (taxes and fees included), quoted through the HomeRunner plugin's quotes endpoint with the guest count capped at each home's occupancy.
* New: Combined package total on the trip page once every open listing has priced; the label switches to a remaining-homes total after bookings begin.
* Fix: The shareable trip link no longer disappears from the admin form right after creating a trip.

= 1.55.1 =
* Fix: Global Marketing Cards color swatches now use the same swatch layout and styling as per-card overrides; tightened chip sizing and added a hairline border so white swatches read as filled.

= 1.55.0 =
* New: Wishlist share email leads with a collage of up to 4 listing cover images (uniform rounded tiles, each linking to the wishlist), with a switch to turn the collage off.
* New: Email cover images are now clickable, linking to the same destination as the email's CTA.
* Enhancement: More breathing room throughout the Emails hub layouts.

= 1.54.0 =
* New: Public REST endpoint (hrck/v1/checkout-capture) that receives guest details from the Toolkit Checkout Guest Step — IP rate-limited, enumeration-safe responses, takeover-safe email identification, and idempotent per email + session + property.
* New: hrck_checkout_guest_captured action fires as a server-side integration seam; guest PII stays server-side.

= 1.53.0 =
* New: Every single-listing email (cart recovery, price drop, booking reminder, price verify, booking confirmation) offers a cover image with a per-email layout choice — full-width image on top (default) or a 25%-width thumbnail beside the heading that stacks on mobile.
* New: Per-email switch to turn the cover image off entirely.

= 1.52.0 =
* New: Emails hub Phase B — price drop, booking reminder, wishlist share, price verify, booking confirmation, and magic link emails now render through the shared branded renderer with operator-editable copy, global branding, and live preview, completing the centralized Emails hub.

= 1.51.4 =
* Fix: Emails live preview no longer clips off the right edge — removed the settings-shell width cap for the Emails panel and let the preview iframe scale down fluidly inside its grid column.

= 1.51.3 =
* Enhancement: Redesigned Emails settings UI — card-based design-system layout with a section rail (Global + per-email), grouped form subheaders, consistent inputs, and helper text.

= 1.51.2 =
* New: Emails panel reorganized into sections — Global (brand kit) and per-email lists, each saving only its own store.
* New: Live preview renders the property hero image; added a show-total toggle for the cart email.

= 1.51.1 =
* New: Cart recovery email resolves the property cover photo via the PMS-synced photo pipeline, so PMS properties without a WordPress thumbnail show their real cover image.
* New: Brand-kit logo max-width setting (px, capped at 600).
* Enhancement: Compliant unsubscribe opt-out in recovery emails.

= 1.51.0 =
* New: Centralized Emails hub (Phase A) — dedicated admin section that customizes the wording and look of plugin emails through a shared branded renderer with a live visual preview; cart recovery ported first.
* New: Core email primitives — per-email copy store, sparse global brand kit with header-safe sanitization, email type registry, and shared renderer with color precedence (content, then branding, then profile).

= 1.50.0 =
* New: Customizable cart abandonment email — operator-editable subject, headline, intro, CTA label, urgency line, and footer with {property}/{site} merge tokens.
* New: Look controls for the recovery email — heading and background color, optional header logo, and a toggle for the guest-review social-proof block.
* New: Send a real test copy of the recovery template to any address, with per-user throttle, domain-only audit log, and surfaced delivery-failure reasons.
* Enhancement: Mailer self-defaults email keys and guards against a blank booking URL or missing guest name.

= 1.49.0 =
* New: Promo Bar — a scheduler-driven site-wide announcement bar (top or bottom) with calendar-aware, timezone-correct display windows and an honest server-clock-corrected countdown.
* New: Cache-safe delivery — the show/dismiss decision is made pre-paint from an inline manifest with zero layout shift (CLS 0 by construction); the bar shares one arbitrated slot with Cart Abandonment so only one bar shows at a time.
* New: Deep-linked CTAs into a pre-filtered search; an advertised discount must bind a coupon validated against Central on a single property (awareness-only otherwise — no bait-and-switch).
* New: Campaign manager — list with effective-status badges and awareness columns (impressions, CTR, dismissals, assisted bookings), create / pause / duplicate / archive, inline validation, and custom colors checked for WCAG AA contrast before they can be saved.
* New: Accessibility baseline — correct reading order, forced-colors support, reduced motion, focus return on dismiss, and a polite screen-reader announcement on reveal.
* New: Privacy — an essential dismissal record that always works versus a consent-gated optimization record (fail-closed; honors GPC), an operator "what this stores" disclosure, and a clear-storage action.
* New: Pause/kill propagates to already-open tabs within about a minute via a no-store, rate-limited manifest-revision endpoint with conditional requests.

= 1.38.19 =
* Security: ContactForm mailer now strips CR/LF from the visitor-supplied Reply-To address (SMTP header-injection guard) and filters recipient addresses through is_email() before wp_mail().
* Security: Price Alerts subscribe endpoint now has a honeypot + per-IP rate limit (3 verification emails / 15 min), closing an inbox-bomb / SMTP-quota abuse vector. Matches the existing Booking Reminder protection.

= 1.38.18 =
* Security: Removed two unauthenticated diagnostic scripts (check-activity-counter.php, enable-activity-counter.php) that shipped in the plugin root. enable-activity-counter.php allowed any anonymous visitor to toggle the Activity Counter tool and run its installer; check-activity-counter.php disclosed active plugins, enabled tools, and property data. Neither is needed at runtime.

= 1.38.17 =
* Fix: Cart recovery emails were never sent — RecoveryEngine looked up guests via a non-existent GuestManager::get_guest(); added get_guest_by_id() and repointed the call
* Fix: Orphaned carts were never linked to identified guests — the guest.identified handler passed the session_ids JSON array as one string; now decodes and links each session
* Enhancement: Honest three-tier booking attribution on the Insights hero — Direct Bookings (tracked, no nudge), Recovered via Email (recovery email then booked), Assisted by Reminder (on-site banner shown then booked). Mutually exclusive; "Assisted" now requires a real banner_shown event instead of defaulting every reconciled cart

= 1.16.3 =
* Fix: Modal uses class-driven show/hide (display:none in CSS, --open class adds display:flex) matching Split Cost pattern
* Fix: Modal max-width reduced to 415px on desktop
* Enhancement: Check-in, Check-out, and Guests fields on single row for compact layout
* Enhancement: Animate-in/out with scale+opacity transitions matching other CKit modals
* Enhancement: Lighter backdrop on desktop, @starting-style for smooth enter animation

= 1.16.2 =
* Fix: JS init deferred to DOMContentLoaded — footer scripts (priority 20) were executing before trigger/modal HTML (priority 21/25)
* Fix: Contact form trigger now positions below booking widget instead of page bottom
* JS relocates trigger after Split Cost (if present) or after booking widget container

= 1.16.0 =
* New: Contact Form module — "Have a Question?" CTA on property pages
* Modal contact form with configurable fields (name, email, dates, guests, message)
* Per-field visibility: required, optional, or hidden for dates and guest count
* Configurable recipient email addresses and subject line with template variables
* Spam protection: honeypot field + optional Google reCAPTCHA v3 + IP rate limiting
* Custom CSS field for button styling to match site theme
* Auto-close confirmation with progress bar countdown
* Shortcode support: [hrck_contact_form] for manual placement on any page
* Proxy-aware rate limiting (Cloudflare, load balancers)
* HTML email with Reply-To header for easy guest replies

= 1.0.13 =
* Complete layout refinement for premium, cohesive look
* Removed dotted leaders, replaced with subtle border dividers
* Lightened gray background from #E5E7EB to #F8F9FA
* Reduced logo size from 40px to 32px for better balance
* Refined typography hierarchy across all sections
* Direct price: Reduced weight from 900 to 600 (semi-bold)
* "Best Price" badge: Changed from yellow to light green pill with uppercase text
* OTA prices: Normalized to 16px with refined layout
* Difference text: Reduced to 12px, positioned inline with price
* Tightened all padding and spacing for cohesive feel
* Updated red color to #D32F2F for professional validation style
* Check icon now matches brand green exactly
* Created clean 3-column grid structure for OTA list

= 1.0.12 =
* Fixed z-index layering so expanded content stays behind green header
* Added animated border-radius transition on green header
* Bottom corners now round (8px) when collapsed and square when expanded
* Smoother visual continuity during expand/collapse animations

= 1.0.11 =
* Enhanced desktop expand/collapse animation with smooth scroll and fade effect
* Added upward slide transition (translateY) for content reveal
* Improved hover interaction on collapsed preview with subtle lift effect
* Enhanced box shadow on expanded state for better depth perception
* Refined animation easing curves for more natural motion

= 1.0.10 =
* Added chevron icon to "Compare Rates" button on both desktop and mobile
* Chevron now rotates 180 degrees on expand/collapse for visual feedback
* Desktop collapsed preview stays visible when widget is expanded
* Improved UI consistency with persistent savings indicator

= 1.0.9 =
* Removed dark backdrop on mobile expanded widget for cleaner look
* Removed gray "Compare Rates" header on desktop
* Desktop widget now only shows green badge (collapsed state)
* Clicking outside mobile drawer closes it (without backdrop)
* Streamlined desktop UX with single green badge entry point

= 1.0.8 =
* Normalized collapsed view design across mobile and desktop
* Added green badge design with checkmark icon for both platforms
* Mobile drawer tab now includes "You Are Saving $XXX" with "Compare Rates" button
* Desktop collapsed preview matches mobile design aesthetic
* Fixed mobile drawer to stay firmly attached to top of sticky booking widget
* Tab now moves with drawer when opening (stays at top)
* Backdrop click properly closes expanded mobile drawer
* Improved transform animations for smoother mobile UX

= 1.0.7 =
* Fixed mobile drawer positioning to appear above sticky booking widget
* Restored green tab design that protrudes from top of widget
* Drawer now calculates position dynamically based on sticky widget height
* Improved z-index layering for proper display on mobile
* Tab positioned at correct location regardless of widget size

= 1.0.6 =
* Reverted incorrect mobile integration approach

= 1.0.5 =
* Renamed SVG logo files to force cache refresh (airbnb-logo.svg, vrbo-logo.svg, etc.)
* Fixed persistent caching issue preventing updated logos from displaying
* Updated logo references in Renderer class

= 1.0.4 =
* Tightened overall spacing throughout widget for cleaner appearance
* Reduced padding and margins in collapsed preview, expanded sections, and disclaimer
* Improved visual density while maintaining readability
* Refined spacing between elements for better flow

= 1.0.3 =
* Updated expanded widget to match design specifications
* Replaced OTA SVG logos with correct branded versions
* Added gray rounded box container for OTA comparisons
* Updated direct price section with larger font and green checkmark
* Changed "Book Direct for Best Price." with yellow highlight
* Increased logo size to 40px circular display
* Improved typography and spacing throughout
* Updated disclaimer text with complete legal language
* Enhanced visual hierarchy in expanded state

= 1.0.2 =
* Updated desktop collapsed view to match design specifications
* Simplified header to show only "Compare Rates" with chevron
* Added collapsed preview section showing savings information
* Adjusted text order: "No Platform Fees. Same Stay. Lower Cost."
* Improved visual hierarchy and spacing

= 1.0.1 =
* Added full internationalization support with .pot file
* Fixed widget positioning to appear adjacent to booking widget instead of footer
* All user-facing strings now translatable
* Improved conversion optimization with proper placement

= 1.0.0 =
* Initial release
* Rate Comparison Tool with Airbnb, Vrbo, Booking.com, and Expedia
* Extensible framework for future conversion tools
* Event-driven architecture
* Responsive mobile drawer

== Upgrade Notice ==

= 1.0.0 =
Initial release of HomeRunner Conversion Kit.
