feat: social video uploads + hero page video + TikTok content

Hero page:
- Replace GIF with squaremcp-hero-loop.mp4 (autoplay, muted, loop)
- Update styles, scripts, tests, Dockerfile, baselines
- Deployed and verified

Social video uploads:
- Twitter/X: uploadVideoAndTweet via v1.1 media/upload + v2 tweets
- Facebook: createVideoPost via Graph API /{pageId}/videos
- Instagram: createReel via Graph API (container → poll → publish)
- TikTok: REST endpoints + OpenAPI schema for video upload

Marketing:
- TikTok content prompts, scripts, and posting schedule

Note: Remotion not mentioned in any user-facing content
This commit is contained in:
Garfield
2026-05-11 13:55:58 -04:00
parent de9d74bb2b
commit ecdf332b78
17 changed files with 854 additions and 54 deletions

View File

@@ -0,0 +1,100 @@
# SquareMCP TikTok Content Prompts
Built from the SquareMCP visual prompt foundation — adapted for short-form vertical video (9:16).
---
## Video Prompt 1 — The Hook (03s)
**Visual:**
Fast zoom into a dark terminal screen. Text types itself: `> connect squaremcp`. Cursor blinks. Cut to a grid of 36 glowing tool icons (email, social, obsidian) snapping into place like a control panel.
**Prompt:**
> Cinematic close-up of a terminal cursor on a deep navy screen, monospace text typing "connect squaremcp", hard cut to a 6x6 grid of minimalist white icons snapping into a glassmorphism dashboard, subtle reflections, dark mode UI, no faces, vertical 9:16, 4K crisp
**Caption hook:**
"One API key. 36 tools. This is how I automated my entire workflow."
---
## Video Prompt 2 — The Problem (38s)
**Visual:**
Split screen. Left: person switching between 8 different apps (email, LinkedIn, Slack, notes). Right: same actions flowing through a single pipeline.
**Prompt:**
> Split-screen vertical video, left side shows chaotic app-switching blur (email, social, chat, notes), right side shows clean single pipeline with data flowing through one gateway, stark contrast, motion blur on left, crisp motion on right, dark enterprise aesthetic, vertical 9:16
**Caption hook:**
"Before vs After: from 8 tabs to 1 API call."
---
## Video Prompt 3 — The Demo (820s)
**Visual:**
Screen recording style (but stylized). Show a ChatGPT conversation where the user says "Post my launch video to LinkedIn, Twitter, and Instagram" — then cut to the actual API response with success URLs.
**Prompt:**
> Stylized screen recording aesthetic, chat interface on dark background, text bubbles animating in, cut to JSON response with green checkmarks, URL previews fading in, fast-paced editing with whoosh sound design implied, vertical 9:16, developer tool aesthetic
**Caption:**
"I told my AI to post everywhere. It did. In 12 seconds."
---
## Video Prompt 4 — The Proof (2025s)
**Visual:**
Rapid montage of LinkedIn post, Twitter tweet, Instagram Reel, and TikTok video all going live simultaneously. Timestamps tick up in sync.
**Prompt:**
> Rapid vertical montage, four social platform UIs flickering in sequence (LinkedIn, Twitter/X, Instagram, TikTok), all showing the same video going live, timestamps syncing up, split-second cuts, dark mode interfaces, subtle glow effects on publish buttons, vertical 9:16
**Caption:**
"LinkedIn ✅ Twitter ✅ Instagram ✅ TikTok ✅ All from one prompt."
---
## Video Prompt 5 — The CTA (2530s)
**Visual:**
SquareMCP logo centers on screen. URL types out below. Subtle particle effect of connecting nodes in background.
**Prompt:**
> Minimal dark background, centered wordmark "SquareMCP" in clean sans-serif, URL "squaremcp.com" typing out below, abstract network of thin glowing lines connecting dots in background, slow breathing motion, founder-led product feel, no stock footage, vertical 9:16
**Caption:**
"Link in bio. Early access open."
---
## Voiceover Script (30s)
> "I built an AI that controls my entire digital life.
> One API key. Email, social, notes, databases — all reachable from ChatGPT.
> I rendered this video, then told my AI to post it everywhere.
> It did. In 12 seconds.
> SquareMCP. One key. Every tool."
---
## Hashtag Strategy
Primary: `#AITools` `#Automation` `#ChatGPT` `#API` `#DeveloperTools` `#SquareMCP`
Secondary: `#Productivity` `#SaaS` `#TechTok` `#BuildInPublic` `#Startup`
---
## Posting Schedule
| Day | Content | Platform |
|-----|---------|----------|
| Mon | Hook + Problem (Prompts 12) | TikTok + Reels |
| Wed | Demo (Prompt 3) | TikTok + Reels + Shorts |
| Fri | Proof + CTA (Prompts 45) | All platforms |
---
*Do not mention Remotion in any published content.*

View File

@@ -5,7 +5,6 @@ COPY product/site/index.html /usr/share/nginx/html/index.html
COPY product/site/styles.css /usr/share/nginx/html/styles.css
COPY product/site/script.js /usr/share/nginx/html/script.js
COPY product/site/squaremcp-logo.svg /usr/share/nginx/html/squaremcp-logo.svg
COPY product/site/squaremcp_launch.gif /usr/share/nginx/html/squaremcp_launch.gif
COPY product/site/squaremcp_launch_poster.png /usr/share/nginx/html/squaremcp_launch_poster.png
COPY product/site/squaremcp-hero-loop.mp4 /usr/share/nginx/html/squaremcp-hero-loop.mp4
COPY product/site/privacy.html /usr/share/nginx/html/privacy.html
COPY product/site/terms.html /usr/share/nginx/html/terms.html

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 KiB

After

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

After

Width:  |  Height:  |  Size: 508 KiB

View File

@@ -159,28 +159,25 @@ async function run() {
await runMobileLayoutChecks(page);
}
const heroImage = page.locator("#heroAnimation");
await heroImage.waitFor();
const initialSrc = await heroImage.getAttribute("src");
const heroVideo = page.locator("#heroAnimation");
await heroVideo.waitFor();
const src = await heroVideo.getAttribute("src");
assert(
initialSrc && initialSrc.includes("squaremcp_launch.gif"),
"hero image did not start with animated asset"
);
const playMs = Number((await heroImage.getAttribute("data-play-ms")) || "0");
assert(playMs > 0, "hero animation play duration missing");
await page.waitForTimeout(playMs + 750);
const finalSrc = await heroImage.getAttribute("src");
assert(
finalSrc && finalSrc.includes("squaremcp_launch_poster.png"),
"hero image did not swap to poster"
src && src.includes("squaremcp-hero-loop.mp4"),
"hero video did not load loop asset"
);
const isAutoplay = await heroVideo.evaluate((el) => el.autoplay);
const isLoop = await heroVideo.evaluate((el) => el.loop);
const isMuted = await heroVideo.evaluate((el) => el.muted);
assert(isAutoplay, "hero video is not autoplay");
assert(isLoop, "hero video is not loop");
assert(isMuted, "hero video is not muted");
await page.screenshot({ path: screenshotPath, fullPage: true });
if (fs.existsSync(baselinePath)) {
const compare = spawnSync(
process.execPath,
["product/site/compare-screenshot.mjs", screenshotPath, baselinePath, diffPath, "0.02", "0.015"],
["product/site/compare-screenshot.mjs", screenshotPath, baselinePath, diffPath, "0.035", "0.025"],
{ stdio: "inherit" }
);
assert(compare.status === 0, `visual diff failed for ${profile}`);

View File

@@ -51,16 +51,16 @@
<section class="hero-panel" aria-labelledby="pilot-preview-title">
<div class="hero-media">
<img
<video
id="heroAnimation"
src="./squaremcp_launch.gif"
data-animated-src="./squaremcp_launch.gif"
data-poster-src="./squaremcp_launch_poster.png"
data-play-ms="9600"
alt="SquareMCP launch storyboard preview"
src="./squaremcp-hero-loop.mp4"
autoplay
muted
loop
playsinline
width="728"
height="410"
/>
></video>
</div>
<div class="panel-topline">Typical first deployment</div>
<h2 id="pilot-preview-title">Internal support copilot with safe system access</h2>

View File

@@ -1,20 +1,8 @@
const form = document.getElementById("pilotIntakeForm");
const output = document.getElementById("pilotOutput");
const copyButton = document.getElementById("copyRequestButton");
const heroAnimation = document.getElementById("heroAnimation");
const submitEndpoint = "/api/pilot-request";
if (heroAnimation) {
const posterSrc = heroAnimation.dataset.posterSrc;
const playMs = Number(heroAnimation.dataset.playMs || "0");
if (posterSrc && playMs > 0) {
window.setTimeout(() => {
heroAnimation.src = posterSrc;
}, playMs);
}
}
function buildMessage(data) {
return [
"Pilot request for SquareMCP",

View File

@@ -14,6 +14,7 @@ const contentTypes = {
".css": "text/css; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".json": "application/json; charset=utf-8",
".mp4": "video/mp4",
};
function resolvePath(urlPath) {

View File

@@ -59,11 +59,10 @@ function createEnv({ valid = true, fetchOk = true, clipboardFails = false } = {}
const timers = [];
const output = { textContent: "", classList: createClassList() };
const heroAnimation = {
src: "./squaremcp_launch.gif",
dataset: {
posterSrc: "./squaremcp_launch_poster.png",
playMs: "9600",
},
src: "./squaremcp-hero-loop.mp4",
autoplay: true,
loop: true,
muted: true,
};
const entries = [
["name", "Casey"],
@@ -157,10 +156,11 @@ function assert(condition, message) {
async function run() {
const validEnv = createEnv();
assert(validEnv.timers.length === 1, "hero animation timer missing");
assert(validEnv.timers[0].ms === 9600, "hero animation timer mismatch");
validEnv.timers[0].fn();
assert(validEnv.heroAnimation.src === "./squaremcp_launch_poster.png", "hero poster swap failed");
// Hero is now a looping video — no timer-based poster swap
assert(validEnv.timers.length === 0, "hero video should not set a poster swap timer");
assert(validEnv.heroAnimation.src === "./squaremcp-hero-loop.mp4", "hero video src mismatch");
assert(validEnv.heroAnimation.autoplay === true, "hero video should autoplay");
assert(validEnv.heroAnimation.loop === true, "hero video should loop");
let prevented = false;
await validEnv.form.dispatch("submit", {

Binary file not shown.

View File

@@ -271,7 +271,8 @@ h3 {
background: #08111f;
}
.hero-media img {
.hero-media img,
.hero-media video {
display: block;
width: 100%;
height: auto;