Files
flatrender/services/remotion/src/compositions/IGProfileMock.tsx
T
soroush.asadi 7ed2ccc414 feat(remotion): Instagram channel-promo template + taste system + design-quality kit
The reference-round workflow, run end to end for a real template:

Taste system (how we learn the user's taste, persisted):
- references/TASTE_PROFILE.md (living design contract) + references/README.md (the
  daily loop) + a "reference round" stage in docs/TEMPLATE_BRIEF.md (provide refs or
  I suggest+mock directions).

Design-quality before/after:
- HeroDemo — the fix recipe vs the faint default: layered-depth background, a proper
  big video type scale, and a bold composed focal object. (Backgrounds were naked,
  text too small, scenes had no objects.)
- YaldaSofreh3D + IGPromoDirections + IGProfileMock — reference-match proofs
  (low-poly 3D, 3 IG-promo style directions, the realistic IG-light page).

Instagram channel-promo template (the deliverable — a flexible 5-scene FlexStory):
- igkit + 5 blocks: IGIntro, IGProfile (realistic IG-light profile, scales to all
  aspects), IGFeed (post grid), IGStats (animated count-up), IGFollowCTA (Follow taps
  to "Following").
- FlexStory gains a `finish` toggle so the IG-light scenes render clean (no brand
  grade). INSTAGRAM_PROMO preset + 3 aspect comps in Root.

Verified: a still of every scene at 9:16 renders clean; full preview MP4 rendering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:16:31 +03:30

118 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from "react";
import { AbsoluteFill } from "remotion";
import { FONT } from "../lib/fonts";
import { hexToRgba } from "../lib/anim";
/**
* IGProfileMock — an authentic Instagram profile page (LIGHT theme) inside a phone,
* as the centrepiece of a "follow our page" promo. Real IG chrome: status bar, the
* username header, avatar + stats, bio, Follow/Message buttons, story highlights,
* the grid tabs and the posts grid. Everything here is an editable field later.
*/
const IGLOGO = "linear-gradient(45deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)";
const BLUE = "#0095f6";
const HANDLE = "flat.studio";
const NAME = "استودیو فلت";
const CAT = "هنر و طراحی";
const BIO = ["هر روز یک طرح تازه ✨", "آموزش، قالب و الهام برای طراحان", "سفارش و دانلود 👇"];
const LINK = "flat.studio/shop";
const HILITES = ["جدید", "قالب‌ها", "آموزش", "نمونه‌کار"];
const POSTS = ["#ff5a3c", "#7c5cff", "#16b5a0", "#ffb23c", "#ef5da8", "#3aa0ff", "#ff7a59", "#4cd4b0", "#a06bff", "#ff5a3c", "#3aa0ff", "#ffb23c"];
const IgCamera: React.FC<{ s: number }> = ({ s }) => (
<svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="url(#ig)" strokeWidth="2">
<defs><linearGradient id="ig" x1="0" y1="1" x2="1" y2="0"><stop offset="0" stopColor="#f09433" /><stop offset="0.5" stopColor="#dc2743" /><stop offset="1" stopColor="#bc1888" /></linearGradient></defs>
<rect x="2" y="2" width="20" height="20" rx="6" /><circle cx="12" cy="12" r="5" /><circle cx="17.5" cy="6.5" r="1.2" fill="url(#ig)" stroke="none" />
</svg>
);
const Stat: React.FC<{ n: string; l: string }> = ({ n, l }) => (
<div style={{ textAlign: "center" }}>
<div style={{ fontWeight: 800, fontSize: 38, color: "#000" }}>{n}</div>
<div style={{ fontWeight: 400, fontSize: 27, color: "#262626" }}>{l}</div>
</div>
);
export const IGProfileMock: React.FC = () => {
const S = 770; // phone screen inner width
const cell = (S - 12) / 3;
return (
<AbsoluteFill style={{ fontFamily: FONT, background: "linear-gradient(165deg,#fdfcfb,#f3edf5)" }}>
<div style={{ position: "absolute", left: "-12%", top: "-6%", width: "55%", height: "34%", borderRadius: "50%", background: hexToRgba("#dc2743", 0.16), filter: "blur(150px)" }} />
<div style={{ position: "absolute", right: "-14%", bottom: "2%", width: "58%", height: "36%", borderRadius: "50%", background: hexToRgba("#7c5cff", 0.16), filter: "blur(160px)" }} />
{/* promo header: IG logo + headline */}
<div style={{ position: "absolute", top: 96, left: 0, right: 0, display: "flex", flexDirection: "column", alignItems: "center", gap: 18 }}>
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
<IgCamera s={64} />
<div style={{ fontWeight: 800, fontSize: 52, background: IGLOGO, WebkitBackgroundClip: "text", backgroundClip: "text", color: "transparent" }}>Instagram</div>
</div>
<div style={{ direction: "rtl", fontWeight: 900, fontSize: 64, color: "#15151a" }}>صفحهٔ ما را دنبال کنید</div>
</div>
{/* phone */}
<div style={{ position: "absolute", left: "50%", top: 300, transform: "translateX(-50%)", width: S + 36, borderRadius: 64, background: "#0c0c0f", padding: 18, boxShadow: "0 50px 100px rgba(20,12,30,0.4)" }}>
<div style={{ width: S, borderRadius: 48, background: "#fff", overflow: "hidden" }}>
{/* status bar */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "16px 34px 6px", fontSize: 26, fontWeight: 700, color: "#000" }}>
<span>۹:۴۱</span><span style={{ display: "flex", gap: 10, alignItems: "center", fontSize: 24 }}> <span style={{ fontSize: 22 }}>WiFi</span> <span style={{ border: "2px solid #000", borderRadius: 5, padding: "2px 6px", fontSize: 18 }}>۸۴٪</span></span>
</div>
{/* username header */}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 28px" }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, direction: "ltr" }}>
<span style={{ fontSize: 26 }}>🔒</span><span style={{ fontWeight: 800, fontSize: 36, color: "#000" }}>{HANDLE}</span><span style={{ fontSize: 24, color: "#000" }}></span>
</div>
<div style={{ display: "flex", gap: 26, fontSize: 42, color: "#000" }}><span></span><span></span></div>
</div>
{/* profile: avatar + stats */}
<div style={{ display: "flex", alignItems: "center", gap: 30, padding: "12px 34px" }}>
<div style={{ width: 176, height: 176, borderRadius: "50%", background: IGLOGO, padding: 6 }}>
<div style={{ width: "100%", height: "100%", borderRadius: "50%", background: "#eee", border: "5px solid #fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 78 }}>🎨</div>
</div>
<div style={{ flex: 1, display: "flex", justifyContent: "space-around" }}>
<Stat n="۳۲۰" l="پست" /><Stat n="۲۴٫۸ هزار" l="دنبال‌کننده" /><Stat n="۱۸۰" l="دنبال‌شده" />
</div>
</div>
{/* name + bio */}
<div style={{ direction: "rtl", padding: "6px 34px 0", color: "#000" }}>
<div style={{ fontWeight: 800, fontSize: 32 }}>{NAME}</div>
<div style={{ fontSize: 27, color: "#737373", marginTop: 2 }}>{CAT}</div>
{BIO.map((b, i) => <div key={i} style={{ fontSize: 28, marginTop: 4 }}>{b}</div>)}
<div style={{ fontSize: 28, color: "#00376b", fontWeight: 600, marginTop: 4, direction: "ltr", textAlign: "right" }}>{LINK}</div>
</div>
{/* buttons */}
<div style={{ display: "flex", gap: 12, padding: "20px 34px 8px" }}>
<div style={{ flex: 1, height: 76, borderRadius: 12, background: BLUE, color: "#fff", fontWeight: 800, fontSize: 30, display: "flex", alignItems: "center", justifyContent: "center" }}>دنبال کردن</div>
<div style={{ flex: 1, height: 76, borderRadius: 12, background: "#efefef", color: "#000", fontWeight: 700, fontSize: 30, display: "flex", alignItems: "center", justifyContent: "center" }}>پیام</div>
<div style={{ width: 76, height: 76, borderRadius: 12, background: "#efefef", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 34 }}>👤</div>
</div>
{/* highlights */}
<div style={{ display: "flex", gap: 28, padding: "16px 34px", direction: "rtl" }}>
{HILITES.map((h, i) => (
<div key={i} style={{ textAlign: "center" }}>
<div style={{ width: 120, height: 120, borderRadius: "50%", border: "2px solid #dbdbdb", background: i % 2 ? "#f3e9df" : "#e9eef5", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 46 }}>{["✨", "🗂️", "🎓", "🖼️"][i]}</div>
<div style={{ fontSize: 24, color: "#000", marginTop: 8 }}>{h}</div>
</div>
))}
</div>
{/* tabs */}
<div style={{ display: "flex", borderTop: "1px solid #dbdbdb", marginTop: 6 }}>
{["▦", "▶", "𓏬"].map((t, i) => (
<div key={i} style={{ flex: 1, textAlign: "center", padding: "18px 0", fontSize: 38, color: i === 0 ? "#000" : "#b3b3b3", borderTop: i === 0 ? "2px solid #000" : "none", marginTop: -1 }}>{t}</div>
))}
</div>
{/* posts grid */}
<div style={{ display: "grid", gridTemplateColumns: `repeat(3, ${cell}px)`, gap: 6 }}>
{POSTS.map((c, i) => (
<div key={i} style={{ width: cell, height: cell, background: c, display: "flex", alignItems: "center", justifyContent: "center", position: "relative" }}>
<div style={{ width: cell * 0.36, height: cell * 0.36, borderRadius: 12, background: hexToRgba("#fff", 0.4) }} />
{i % 4 === 0 ? <span style={{ position: "absolute", top: 10, right: 12, color: "#fff", fontSize: 30 }}></span> : i % 4 === 1 ? <span style={{ position: "absolute", top: 10, right: 12, color: "#fff", fontSize: 26 }}></span> : null}
</div>
))}
</div>
</div>
</div>
</AbsoluteFill>
);
};