Skip to content

Commit 8478823

Browse files
feat(remotion): unlock remaining brand comps (iMessage, Instagram DMs, popup, lock screen)
MessageBubbles, MessagePopup, InstagramMessages, LockScreenMessage now accept the universal Background/Text/Font/Accent controls; curated iMessage/glass themes and authentic chrome preserved.
1 parent 7934a6d commit 8478823

9 files changed

Lines changed: 134 additions & 37 deletions

File tree

apps/remotion/src/compositions/InstagramMessages/InstagramMessages.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
staticFile,
88
useVideoConfig,
99
} from "remotion";
10+
import { type ClipStyle, resolveClipStyle } from "../../clip-style";
1011
import type { ChatMessage } from "../../editors/types";
1112
import { proxyExternalImg } from "../../proxy-image";
1213
import { useSafeArea } from "../../safe-area";
@@ -20,6 +21,8 @@ export type InstagramMessagesProps = {
2021
messages: ChatMessage[];
2122
theme: "light" | "dark";
2223
orientation?: "landscape" | "portrait";
24+
/** Universal Style — background, received-bubble text, font, sent accent. */
25+
clipStyle?: ClipStyle;
2326
};
2427

2528
// 9:19.5 — modern iPhone screen aspect ratio.
@@ -35,6 +38,8 @@ const SENT_BG = "#A23CF8";
3538

3639
type Palette = {
3740
bg: string;
41+
/** Outgoing (sent) bubble background — the universal accent. */
42+
sentBg: string;
3843
headerBg: string;
3944
headerBorder: string;
4045
headerText: string;
@@ -55,6 +60,7 @@ function getPalette(theme: "light" | "dark"): Palette {
5560
if (theme === "dark") {
5661
return {
5762
bg: "#000000",
63+
sentBg: SENT_BG,
5864
headerBg: "#000000",
5965
headerBorder: "rgba(255,255,255,0.1)",
6066
headerText: "#ffffff",
@@ -73,6 +79,7 @@ function getPalette(theme: "light" | "dark"): Palette {
7379
}
7480
return {
7581
bg: "#ffffff",
82+
sentBg: SENT_BG,
7683
headerBg: "#ffffff",
7784
headerBorder: "rgba(0,0,0,0.08)",
7885
headerText: "#0f1014",
@@ -96,10 +103,29 @@ export const InstagramMessages: React.FC<InstagramMessagesProps> = ({
96103
messages,
97104
theme,
98105
orientation = "landscape",
106+
clipStyle,
99107
}) => {
100108
const frame = useDesignFrame();
101109
const { fps } = useVideoConfig();
102-
const palette = getPalette(theme);
110+
const basePalette = getPalette(theme);
111+
const s = resolveClipStyle(clipStyle, {
112+
background: basePalette.bg,
113+
color: basePalette.bubbleReceivedText,
114+
fontFamily:
115+
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
116+
accent: basePalette.sentBg,
117+
});
118+
// Universal Style on top of the theme palette: chat-sheet background,
119+
// received-bubble text, and the sent bubble accent. Header/composer chrome
120+
// and the IG gradient + button keep their authentic per-theme look.
121+
const palette: Palette = {
122+
...basePalette,
123+
bg: s.background,
124+
headerBg: s.background,
125+
composerBg: s.background,
126+
bubbleReceivedText: s.color,
127+
sentBg: s.accent,
128+
};
103129
const safe = useSafeArea();
104130
// Auto-portrait inside a device frame (see ChatFill for the same logic).
105131
const inDeviceFrame = safe.top > 0 || safe.bottom > 0;
@@ -113,8 +139,7 @@ export const InstagramMessages: React.FC<InstagramMessagesProps> = ({
113139
inset: 0,
114140
background: palette.bg,
115141
color: palette.bubbleReceivedText,
116-
fontFamily:
117-
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
142+
fontFamily: s.fontFamily,
118143
overflow: "hidden",
119144
display: "flex",
120145
flexDirection: "column",
@@ -648,7 +673,7 @@ function TypingBubble({
648673
return (
649674
<div
650675
style={{
651-
background: isRight ? SENT_BG : palette.receivedBg,
676+
background: isRight ? palette.sentBg : palette.receivedBg,
652677
padding: "22px 28px",
653678
borderRadius: 36,
654679
display: "flex",
@@ -704,7 +729,7 @@ function MessageBubble({
704729
return (
705730
<div
706731
style={{
707-
background: isRight ? SENT_BG : palette.receivedBg,
732+
background: isRight ? palette.sentBg : palette.receivedBg,
708733
color: isRight ? palette.bubbleSentText : palette.bubbleReceivedText,
709734
padding: "18px 26px",
710735
borderRadius: 36,

apps/remotion/src/compositions/InstagramMessages/meta.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export const instagramMessagesInfo: CompositionInfo<InstagramMessagesProps> = {
3838
width: INSTAGRAM_MESSAGES_WIDTH,
3939
height: INSTAGRAM_MESSAGES_HEIGHT,
4040
defaultProps: instagramMessagesDefaultProps,
41-
brandMode: "locked",
4241
phoneFitMode: "cover",
4342
fields: [
4443
{ kind: "text", key: "contactName", label: "Contact name" },

apps/remotion/src/compositions/LockScreenMessage/LockScreenMessage.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
staticFile,
88
useVideoConfig,
99
} from "remotion";
10+
import { type ClipStyle, resolveClipStyle } from "../../clip-style";
1011
import { proxyExternalImg } from "../../proxy-image";
1112
import { snap } from "../../snap";
1213
import { useDesignFrame } from "../../use-design-frame";
@@ -55,6 +56,9 @@ export type LockScreenMessageProps = {
5556
n3Body?: string;
5657
n3Time?: string;
5758
n3Avatar?: string;
59+
60+
/** Universal Style — base background, clock text color, font. */
61+
clipStyle?: ClipStyle;
5862
};
5963

6064
type Notif = {
@@ -103,10 +107,18 @@ function collectNotifs(p: LockScreenMessageProps): Notif[] {
103107
}
104108

105109
export const LockScreenMessage: React.FC<LockScreenMessageProps> = (props) => {
106-
const { time, date, wallpaper } = props;
110+
const { time, date, wallpaper, clipStyle } = props;
107111
const frame = useDesignFrame();
108112
const { fps } = useVideoConfig();
109113

114+
const s = resolveClipStyle(clipStyle, {
115+
background: "#0c1018",
116+
color: "#ffffff",
117+
fontFamily:
118+
"-apple-system, BlinkMacSystemFont, 'SF Pro Display', Inter, sans-serif",
119+
accent: "#ffffff",
120+
});
121+
110122
const wallpaperSrc =
111123
resolveAsset(wallpaper) ?? staticFile(DEFAULT_WALLPAPER_SRC);
112124
const notifs = collectNotifs(props);
@@ -121,9 +133,8 @@ export const LockScreenMessage: React.FC<LockScreenMessageProps> = (props) => {
121133
return (
122134
<AbsoluteFill
123135
style={{
124-
background: "#0c1018",
125-
fontFamily:
126-
"-apple-system, BlinkMacSystemFont, 'SF Pro Display', Inter, sans-serif",
136+
background: s.background,
137+
fontFamily: s.fontFamily,
127138
overflow: "hidden",
128139
}}
129140
>
@@ -161,7 +172,7 @@ export const LockScreenMessage: React.FC<LockScreenMessageProps> = (props) => {
161172
left: 0,
162173
right: 0,
163174
textAlign: "center",
164-
color: "#fff",
175+
color: s.color,
165176
opacity: clockIn,
166177
transform: `translate3d(0, ${snap(clockY)}px, 0)`,
167178
}}

apps/remotion/src/compositions/LockScreenMessage/meta.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export const lockScreenMessageInfo: CompositionInfo<LockScreenMessageProps> = {
4242
width: LOCK_SCREEN_MESSAGE_WIDTH,
4343
height: LOCK_SCREEN_MESSAGE_HEIGHT,
4444
defaultProps: lockScreenMessageDefaultProps,
45-
brandMode: "locked",
4645
agentNotes:
4746
"Use for a 'phone buzzes' beat — messages landing on a locked iPhone over a wallpaper and clock. Great cold open or punchline. Keep each body short (one or two lines reads best). Leave n2/n3 empty for a single notification.",
4847
fields: [

apps/remotion/src/compositions/MessageBubbles/IMessageChat.tsx

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,24 @@ export type IMessageChatProps = {
101101
designWidth?: number;
102102
/** Extra gallery photos for the attachment picker (besides the sent one). */
103103
galleryImages?: { name: string; url: string }[];
104+
/**
105+
* Universal Style overrides forwarded from the (now unlocked) MessageBubbles
106+
* composition. Each maps to the one clean slot in the iMessage layout:
107+
* `clipBackground` → chat sheet, `clipColor` → received-bubble text,
108+
* `clipFontFamily` → root font, `clipAccent` → the sent (blue) bubble +
109+
* tail. Unset (empty) means keep the authentic default.
110+
*/
111+
clipBackground?: string;
112+
clipColor?: string;
113+
clipFontFamily?: string;
114+
clipAccent?: string;
104115
};
105116

117+
/** Use the override if set (non-empty), otherwise the authentic default. */
118+
function ov(value: string | undefined, fallback: string): string {
119+
return value && value.trim() !== "" ? value : fallback;
120+
}
121+
106122
export function IMessageChat({
107123
messages,
108124
title,
@@ -122,6 +138,10 @@ export function IMessageChat({
122138
keyboardOpen = 1,
123139
designWidth,
124140
galleryImages,
141+
clipBackground,
142+
clipColor,
143+
clipFontFamily,
144+
clipAccent,
125145
}: IMessageChatProps) {
126146
const grouped = groupThread(messages);
127147
// Caret blink for the idle (placeholder) composer — a ~1s cycle: on, quick
@@ -201,14 +221,20 @@ export function IMessageChat({
201221
const photoY = riseIn;
202222
const photoOpacity = fadeIn * (1 - sendT);
203223

204-
// The chat sheet (when there's no wallpaper) follows the appearance.
205-
const sheetBg = dark ? "#000000" : "#ffffff";
224+
// The chat sheet (when there's no wallpaper) follows the appearance — or the
225+
// universal background override when one is set.
226+
const sheetBg = ov(clipBackground, dark ? "#000000" : "#ffffff");
206227
// Chrome text/icons go light over a wallpaper OR in dark mode.
207228
const lightUI = hasBg || dark;
208229
const headerText = lightUI ? "#ffffff" : "#000000";
209-
// Received bubbles use Apple's exact grays per appearance; sent stays blue.
210-
const themText = dark ? "#ffffff" : "#000000";
230+
// Received bubbles use Apple's exact grays per appearance; their text follows
231+
// the universal text override. Sent bubbles use the universal accent (default
232+
// iMessage blue) for both the fill and the tail.
233+
const themText = ov(clipColor, dark ? "#ffffff" : "#000000");
211234
const themBubbleBg = dark ? IMESSAGE_THEM_BG_DARK : IMESSAGE_THEM_BG_LIGHT;
235+
const sentBg = ov(clipAccent, IMESSAGE_GRADIENT);
236+
const sentTail = ov(clipAccent, IMESSAGE_TAIL_ME_COLOR);
237+
const fontStack = ov(clipFontFamily, SF_PRO_STACK);
212238
// The header/composer strips must NOT paint an opaque sheet color over the
213239
// WebGL glass canvas, or the glass chrome (buttons, name chip, composer pill)
214240
// is buried and only the bare icons show. Whenever glass is on we keep the
@@ -270,7 +296,7 @@ export function IMessageChat({
270296
bgImage={backgroundImage}
271297
bgColor={sheetBg}
272298
className={cn("h-full", className)}
273-
style={{ fontFamily: SF_PRO_STACK }}
299+
style={{ fontFamily: fontStack }}
274300
>
275301
<div className="relative flex h-full flex-col">
276302
{/* Subtle top darkness — a soft top-down gradient sitting ABOVE the
@@ -582,12 +608,8 @@ export function IMessageChat({
582608
{m.typing ? (
583609
<TypingBubble
584610
from={group.from}
585-
background={
586-
isMe ? IMESSAGE_GRADIENT : themBubbleBg
587-
}
588-
tailColor={
589-
isMe ? IMESSAGE_TAIL_ME_COLOR : themBubbleBg
590-
}
611+
background={isMe ? sentBg : themBubbleBg}
612+
tailColor={isMe ? sentTail : themBubbleBg}
591613
color={isMe ? "#fff" : themText}
592614
dotsColor={
593615
isMe ? "rgba(255,255,255,0.9)" : "#8e8e93"
@@ -605,10 +627,8 @@ export function IMessageChat({
605627
<DotsToMessage
606628
from={group.from}
607629
tail={isLast}
608-
background={isMe ? IMESSAGE_GRADIENT : themBubbleBg}
609-
tailColor={
610-
isMe ? IMESSAGE_TAIL_ME_COLOR : themBubbleBg
611-
}
630+
background={isMe ? sentBg : themBubbleBg}
631+
tailColor={isMe ? sentTail : themBubbleBg}
612632
color={isMe ? "#fff" : themText}
613633
dotsColor={
614634
isMe ? "rgba(255,255,255,0.9)" : "#8e8e93"

apps/remotion/src/compositions/MessageBubbles/MessageBubbles.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import {
88
staticFile,
99
useVideoConfig,
1010
} from "remotion";
11+
import { type ClipStyle, resolveClipStyle } from "../../clip-style";
1112
import type { ChatMessage } from "../../editors/types";
1213
import { SmartAudio } from "../../smart-audio";
1314
import { DESIGN_FPS, useDesignFrame } from "../../use-design-frame";
14-
import type { ChatMessageItem } from "../_chat-demo/ChatDemo";
15+
import {
16+
type ChatMessageItem,
17+
IMESSAGE_GRADIENT,
18+
} from "../_chat-demo/ChatDemo";
1519
import { ChatFill } from "../_chat-demo/ChatFill";
1620
import { KEYBOARD_BG } from "../_chat-demo/Keyboard";
1721
import { useSFProDisplay } from "../_chat-demo/sf-pro";
@@ -117,6 +121,8 @@ export type MessageBubblesProps = {
117121
* gradient placeholders. Each is `{ name, url }` (static path or http URL).
118122
*/
119123
galleryImages?: { name: string; url: string }[];
124+
/** Universal Style — sheet background, received text, font, sent accent. */
125+
clipStyle?: ClipStyle;
120126
};
121127

122128
/** Pop balloon hold + rise timings (frames) for the keyboard key press. */
@@ -362,6 +368,7 @@ export const MessageBubbles: React.FC<MessageBubblesProps> = ({
362368
theme = "dark",
363369
showKeyboard = false,
364370
galleryImages,
371+
clipStyle,
365372
}) => {
366373
// Load Apple's SF Pro Display so the chat renders in the real iMessage font
367374
// in headless exports too (blocks the render until decoded; never fails it).
@@ -484,7 +491,20 @@ export const MessageBubbles: React.FC<MessageBubblesProps> = ({
484491
})
485492
: 1;
486493

487-
const backdrop = backgroundImage || theme === "dark" ? "#000000" : "#ffffff";
494+
// Universal Style. Authentic iMessage defaults per appearance: sheet bg
495+
// (black/white), received-bubble text, SF Pro font, and the sent-bubble
496+
// blue as the accent. Overrides forwarded into the iMessage renderer.
497+
const s = resolveClipStyle(clipStyle, {
498+
background: theme === "dark" ? "#000000" : "#ffffff",
499+
color: theme === "dark" ? "#ffffff" : "#000000",
500+
fontFamily:
501+
"-apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', sans-serif",
502+
accent: IMESSAGE_GRADIENT,
503+
});
504+
// With a wallpaper the sheet stays the authentic black so the chrome reads;
505+
// otherwise the screen fill follows the universal background (transparent
506+
// when a Background Scene is chosen).
507+
const backdrop = backgroundImage ? "#000000" : s.background;
488508

489509
return (
490510
<>
@@ -553,6 +573,10 @@ export const MessageBubbles: React.FC<MessageBubblesProps> = ({
553573
keyboardOpen={keyboardOpen}
554574
designWidth={PHONE_DESIGN_WIDTH}
555575
galleryImages={galleryImages}
576+
clipBackground={s.background}
577+
clipColor={s.color}
578+
clipFontFamily={s.fontFamily}
579+
clipAccent={s.accent}
556580
/>
557581
</ChatFill>
558582
</>

apps/remotion/src/compositions/MessageBubbles/meta.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,8 @@ export const messageBubblesInfo: CompositionInfo<MessageBubblesProps> = {
139139
width: MESSAGE_BUBBLES_WIDTH,
140140
height: MESSAGE_BUBBLES_HEIGHT,
141141
defaultProps: messageBubblesDefaultProps,
142-
brandMode: "locked",
143142
phoneFitMode: "cover",
144-
// Curated skins — first entry is the default look. Locked compositions
145-
// still get the Theme picker (themes are hand-built, not free recoloring).
143+
// Curated skins — first entry is the default look.
146144
themes: [{ id: "imessage", label: "iMessage" }],
147145
fields: [
148146
{ kind: "text", key: "contactName", label: "Contact name" },

0 commit comments

Comments
 (0)