WP Manifestindependent plugin directory
manifest / media / sample-reel-delivery

Marketing Video Reels

A WordPress plugin I'm building to deliver sample reels to prospective clients

by John Kakuk · github.com/johnkakuk/sample-reel-delivery

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/johnkakuk/sample-reel-delivery/archive/refs/heads/main.zip

Readme

Marketing Video Plugin

A WordPress plugin for delivering a free personalized reel to a prospect through a phone-friendly, swipe-through landing page — instead of just texting them a video file.

The visitor lands on a reels custom post (slug configurable) and swipes through four full-screen, portrait (9:16) slides:

  1. Personalized video — a short "Hey NAME, I shot this for you" intro. Requires a tap to start (browsers block autoplay-with-sound on the very first interaction).
  2. Deliverable video — the actual free reel. Autoplays, has a download button, and once it finishes shows a thumbs up/down reaction overlay (up advances to the VSL, down shows a "thanks — want changes? email me" note) with a "Watch again" replay option.
  3. VSL — a reusable pitch video, autoplays, picked from a dropdown of past VSLs rather than re-uploaded per post since one may get reused for months.
  4. Booking — a Calendly embed, loaded immediately in the background so it's ready by the time the visitor scrolls there.

Every video's progress bar is scrubbable (click/drag to seek), shows a buffering spinner when it genuinely has to wait on data, and videos load one at a time in priority order (the current one first, the next one once the current one starts playing) rather than all three competing for bandwidth at once — self-hosted video has no adaptive streaming or CDN to fall back on, so this matters more here than it would embedding a YouTube player.

Status: Alpha

Feature-complete and tested end-to-end — built and iterated against a local Docker WordPress sandbox throughout, and validated on the author's real production site (WordPress + Elementor) across desktop and iOS (Safari and Chrome, both WebKit under the hood).

Packaged as marketing-video-plugin-0.1.0-alpha.zip in the project root — upload via Plugins → Add New → Upload Plugin, or extract into wp-content/plugins/marketing-video-plugin/. Re-run the packaging steps below after any further change; the zip is gitignored (a generated deliverable, not source) and needs re-exporting by hand.

What's in the box

  • marketing-video-plugin.php + includes/ — the plugin itself
  • templates/single-mvp_reel.php — the full-bleed reel template (bypasses the theme's header/footer entirely, since the swipe experience needs the whole viewport)
  • assets/dist/ — the built front-end bundle the plugin enqueues (built from /frontend, committed since WordPress doesn't run a build step)
  • assets/admin/ — the meta box JS/CSS (media picker)
  • assets/images/ — static plugin assets (the thumbs-up/down icon)
  • /frontend — the front-end source; required for making any future change to the reel experience — assets/dist/reel.js is generated, minified output, not something anyone would hand-edit
  • docker-compose.yml — a local WordPress + MariaDB sandbox for testing (see below)

Known gaps / things worth knowing before relying on this further

  • No uninstall cleanup — deactivating/deleting the plugin leaves reel data in the database.
  • No admin UI for poster/thumbnail images on videos.
  • Video compression is a manual step in whatever export tool is used (guidance on bitrate/ffmpeg settings came up during development, not automated in the plugin — see conversation history if picking this back up, or just ask again).
  • The WordPress admin bar isn't suppressed on the reel template for logged-in viewers — it'll show at the top of the full-bleed layout.
  • Tested against one theme (a WordPress default block theme) and one real-world stack (Elementor); other page builders/themes are unverified.

Plugin architecture

Two custom post types:

  • mvp_reel (menu: "Demo Reels", public, rewrite slug configurable via Settings, default reels) — one per prospect. Meta boxes:
    • Personalization — Recipient Name, Company Name. Not rendered as on-page text (the personalized video carries the message) — instead used to build the page's SEO title / link-preview text, e.g. "Video for Mike, Anderson Roofing" (what shows up when the link is texted).
    • Videos — personalized + deliverable video pickers (media library), download filename + toggle, VSL dropdown.
    • Booking — Calendly link; falls back to the site-wide default in Settings if left blank.
    • Notes — a private WYSIWYG field, admin-only, never sent to the front end.
    • Analytics (sidebar) — read-only counters: Loads, Intro Views, Reel Views, Likes, Dislikes, Downloads, VSL Views, Calendar Views. No booking count — Calendly already tracks that. See Analytics below.
  • mvp_vsl ("VSL Library", admin-only, nested under the Reels menu) — named video entries (title + one media-library video) that feed the VSL dropdown on a reel. Since a VSL often gets reused for months, this avoids re-picking a media library file on every single reel while still keeping every video backed by the media library.

Settings (Reels → Settings): reel URL slug, and a default Calendly link used whenever a reel doesn't set its own.

MVP_Frontend reads a reel's meta into the exact shape of the front end's ReelData TypeScript interface, injects it as window.REEL_DATA before the bundle runs, swaps in the plugin's own template via template_include (a theme can override this by providing its own single-mvp_reel.php), builds the SEO title/Open Graph tags, and — critically for page-builder compatibility — strips every other enqueued style/script from the reel page (strip_other_assets(), running dead last on wp_enqueue_scripts), since page builders like Elementor otherwise load their global CSS/JS on every page by default and visibly clash with this one's full-bleed design.

MVP_Analytics owns the whole analytics pipeline:

  • A public REST route (POST /wp-json/mvp/v1/track) the front end beacons events to for anything play/interaction-driven (video plays, likes, downloads, slide views).
  • Loads is tracked separately, server-side, on template_redirect — not a beaconed event — so it counts every qualifying page render even if the JS bundle never executes. Intro Views is deliberately not the same number: it's driven by the front end's video_play event, so it only counts when the first video is actually played, not just scrolled to (which happens automatically on page load, before any interaction).
  • Every counting path excludes logged-in visitors — the reel owner's own testing/preview visits never inflate the numbers. Worth noting: this required wp_validate_auth_cookie( '', 'logged_in' ) rather than the more obvious is_user_logged_in() inside the REST callback specifically — WordPress's default current-user resolution checks the admin-scoped auth cookie, which browsers never send to a non-/wp-admin URL, so is_user_logged_in() reads as logged-out there even for an authenticated admin. (template_redirect, used for Loads, doesn't have this problem — regular page requests resolve the current user normally.)
  • The first time a reel gets a non-logged-in load, wp_mail() notifies the site admin email.

Frontend

Framework-free Vite + TypeScript. Slide paging uses native CSS scroll-snap (no swipe library). npm run build outputs straight into assets/dist/ with fixed filenames (reel.js / reel.css) so the PHP side can enqueue them without a manifest.

cd frontend
npm install
npm run dev     # standalone dev server, mock data
npm run build   # builds into ../assets/dist for the plugin to enqueue

Key files:

  • src/types.ts — the ReelData shape WordPress injects as window.REEL_DATA
  • src/mock-data.ts — stand-in data for standalone dev (swap for real footage/links as needed)
  • src/modules/video-controller.ts — per-slide play/pause/scrub, progress bar, staggered-loading hooks, near-end swipe-hint trigger
  • src/modules/analytics.ts — fires the interaction-driven events; posts to ReelData.analyticsEndpoint (the plugin's REST route) when set
  • src/modules/calendly.ts — lazy-loads the Calendly inline widget
  • src/modules/device.ts — touch vs. pointer detection (swipe vs. scroll hint wording)

A couple of non-obvious things worth knowing if picking this back up:

  • The deliverable video's <video> src gets #t=0.001 appended (main.ts) — a documented WebKit workaround for a real bug where iOS Safari shows a plain black box instead of a video's first frame unless it's autoplaying. Don't remove it without re-testing on an actual iPhone.
  • Interactive elements' :hover CSS rules are wrapped in @media (hover: hover) (style.css) — iOS otherwise needs two taps on anything with a bare :hover rule (first tap simulates hover, second actually clicks).

Note: frontend/public/demo/ holds large sample video files (~100MB total) used only for local testing — gitignored, not shipped.

Local WordPress sandbox (Docker)

docker compose up -d
docker compose run --rm wpcli wp <command>   # one-off WP-CLI commands

WordPress at http://localhost:8080, admin login admin / admin. The whole project directory is bind-mounted into wp-content/plugins/marketing-video-plugin, so PHP edits are live immediately; frontend changes need npm run build first. Note the wordpress:cli image defaults to a different www-data UID (Alpine, 82) than the wordpress image (Debian, 33) — the wpcli service in docker-compose.yml is pinned to user: "33:33" to avoid file-ownership mismatches on uploads.

Packaging a release zip

STAGE=/tmp/marketing-video-plugin   # or any scratch dir
rm -rf "$STAGE" && mkdir -p "$STAGE"
cp marketing-video-plugin.php "$STAGE/"
cp -R includes templates "$STAGE/"
mkdir -p "$STAGE/assets/admin" "$STAGE/assets/dist" "$STAGE/assets/images"
cp assets/admin/* "$STAGE/assets/admin/"
cp assets/dist/reel.js assets/dist/reel.css "$STAGE/assets/dist/"
cp assets/images/thumb.png "$STAGE/assets/images/"
cd "$(dirname "$STAGE")" && zip -r -X marketing-video-plugin.zip "$(basename "$STAGE")"

(Rebuild the frontend first with npm run build if anything in frontend/ changed.)

Read the full README on GitHub →