mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(videos): enhance ambient compositions visibility and atmosphere
- FloatingTools: increase layer opacities (back 0.12, mid 0.22, front 0.35), add depth-of-field blur (1px back, 0.5px mid), radial vignette overlay, increase X oscillation amplitude from 3px to 8px - BrandGradient: add center SnapOtter logo with subtle scale pulse, scatter faint tool names at 5% opacity for texture, add radial vignette - FormatUniverse: replace buggy ghost badge duplicates with proper CSS opacity trail (3 copies at decreasing opacity: 1.0, 0.3, 0.1), increase logo pulse from 3% to 5% scale oscillation, add faint elliptical SVG orbit ring lines at 5% opacity behind each ring
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { AbsoluteFill } from "remotion";
|
import { AbsoluteFill, Img, staticFile, useCurrentFrame } from "remotion";
|
||||||
import { GradientBlob } from "@/components/GradientBlob";
|
import { GradientBlob } from "@/components/GradientBlob";
|
||||||
import { GrainOverlay } from "@/components/GrainOverlay";
|
import { GrainOverlay } from "@/components/GrainOverlay";
|
||||||
import { COLOR } from "@/lib/colors";
|
import { COLOR } from "@/lib/colors";
|
||||||
|
import { FONT } from "@/lib/fonts";
|
||||||
|
import { TOOLS } from "@/lib/tools";
|
||||||
|
|
||||||
const DURATION = 150;
|
const DURATION = 150;
|
||||||
|
|
||||||
@@ -69,11 +71,70 @@ const BLOBS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const BrandGradient: React.FC = () => (
|
/** Pre-computed scattered tool name positions (frozen, for texture) */
|
||||||
<AbsoluteFill style={{ backgroundColor: COLOR.dark, overflow: "hidden" }}>
|
const SCATTERED_TOOLS = TOOLS.slice(0, 15).map((tool, i) => ({
|
||||||
{BLOBS.map((blob) => (
|
name: tool.name,
|
||||||
<GradientBlob key={blob.color} config={blob} duration={DURATION} />
|
x: ((i * 137 + 47) % 750) + 25,
|
||||||
))}
|
y: ((i * 89 + 23) % 550) + 25,
|
||||||
<GrainOverlay opacity={0.04} />
|
fontSize: 11 + (i % 3) * 2,
|
||||||
</AbsoluteFill>
|
}));
|
||||||
);
|
|
||||||
|
export const BrandGradient: React.FC = () => {
|
||||||
|
const frame = useCurrentFrame();
|
||||||
|
const logoScale = 1 + Math.sin(frame * 0.08) * 0.03;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AbsoluteFill style={{ backgroundColor: COLOR.dark, overflow: "hidden" }}>
|
||||||
|
{/* Scattered tool names for texture (frozen, 5% opacity) */}
|
||||||
|
{SCATTERED_TOOLS.map((tool) => (
|
||||||
|
<span
|
||||||
|
key={tool.name}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: tool.x,
|
||||||
|
top: tool.y,
|
||||||
|
fontFamily: FONT.body,
|
||||||
|
fontSize: tool.fontSize,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: "rgba(255,255,255,0.05)",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tool.name}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{BLOBS.map((blob) => (
|
||||||
|
<GradientBlob key={blob.color} config={blob} duration={DURATION} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Center logo with subtle scale pulse */}
|
||||||
|
<Img
|
||||||
|
src={staticFile("logo.png")}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
left: 400 - 40,
|
||||||
|
top: 300 - 40,
|
||||||
|
transform: `scale(${logoScale})`,
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Radial vignette */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
background:
|
||||||
|
"radial-gradient(ellipse 60% 55% at 50% 50%, transparent 0%, rgba(0,0,0,0.5) 100%)",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<GrainOverlay opacity={0.04} />
|
||||||
|
</AbsoluteFill>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { FONT } from "@/lib/fonts";
|
|||||||
import { TOOLS } from "@/lib/tools";
|
import { TOOLS } from "@/lib/tools";
|
||||||
|
|
||||||
const LAYERS = [
|
const LAYERS = [
|
||||||
{ speed: 0.8, fontSize: 14, opacity: 0.06, count: 10 },
|
{ speed: 0.8, fontSize: 14, opacity: 0.12, count: 10, blur: 1 },
|
||||||
{ speed: 1.5, fontSize: 18, opacity: 0.12, count: 9 },
|
{ speed: 1.5, fontSize: 18, opacity: 0.22, count: 9, blur: 0.5 },
|
||||||
{ speed: 2.5, fontSize: 24, opacity: 0.2, count: 6 },
|
{ speed: 2.5, fontSize: 24, opacity: 0.35, count: 6, blur: 0 },
|
||||||
];
|
];
|
||||||
|
|
||||||
const X_FREQ = (2 * Math.PI) / 120;
|
const X_FREQ = (2 * Math.PI) / 120;
|
||||||
@@ -41,7 +41,7 @@ export const FloatingTools: React.FC = () => {
|
|||||||
{items.map((item, idx) => {
|
{items.map((item, idx) => {
|
||||||
const rawY = item.startY - frame * item.layer.speed;
|
const rawY = item.startY - frame * item.layer.speed;
|
||||||
const y = (((rawY % wrapH) + wrapH) % wrapH) - 50;
|
const y = (((rawY % wrapH) + wrapH) % wrapH) - 50;
|
||||||
const xOffset = Math.sin(frame * X_FREQ + item.phase) * 3;
|
const xOffset = Math.sin(frame * X_FREQ + item.phase) * 8;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -56,12 +56,23 @@ export const FloatingTools: React.FC = () => {
|
|||||||
color: COLOR.category[item.tool.category] ?? COLOR.accent,
|
color: COLOR.category[item.tool.category] ?? COLOR.accent,
|
||||||
opacity: item.layer.opacity,
|
opacity: item.layer.opacity,
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
|
filter: item.layer.blur > 0 ? `blur(${item.layer.blur}px)` : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.tool.name}
|
{item.tool.name}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{/* Radial vignette */}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
inset: 0,
|
||||||
|
background:
|
||||||
|
"radial-gradient(ellipse 60% 55% at 50% 50%, transparent 0%, rgba(0,0,0,0.6) 100%)",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<GrainOverlay opacity={0.02} />
|
<GrainOverlay opacity={0.02} />
|
||||||
</AbsoluteFill>
|
</AbsoluteFill>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -67,12 +67,14 @@ const Badge: React.FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const GhostBadge: React.FC<{
|
/** Trail badge - renders a badge at a past frame's position with reduced opacity */
|
||||||
|
const TrailBadge: React.FC<{
|
||||||
label: string;
|
label: string;
|
||||||
color: string;
|
color: string;
|
||||||
angle: number;
|
angle: number;
|
||||||
radius: number;
|
radius: number;
|
||||||
}> = ({ label, color, angle, radius }) => {
|
trailOpacity: number;
|
||||||
|
}> = ({ label, color, angle, radius, trailOpacity }) => {
|
||||||
const x = Math.cos(angle) * radius;
|
const x = Math.cos(angle) * radius;
|
||||||
const y = Math.sin(angle) * radius * 0.55;
|
const y = Math.sin(angle) * radius * 0.55;
|
||||||
const z = Math.sin(angle);
|
const z = Math.sin(angle);
|
||||||
@@ -86,12 +88,12 @@ const GhostBadge: React.FC<{
|
|||||||
left: 400 + x,
|
left: 400 + x,
|
||||||
top: 280 + y,
|
top: 280 + y,
|
||||||
transform: `translate(-50%, -50%) scale(${scale})`,
|
transform: `translate(-50%, -50%) scale(${scale})`,
|
||||||
opacity: 0.2,
|
opacity: trailOpacity,
|
||||||
zIndex,
|
zIndex,
|
||||||
padding: "6px 14px",
|
padding: "6px 14px",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
backgroundColor: `${color}26`,
|
backgroundColor: `${color}26`,
|
||||||
border: `1px solid ${color}66`,
|
border: `1px solid ${color}44`,
|
||||||
fontFamily: FONT.body,
|
fontFamily: FONT.body,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -107,26 +109,61 @@ const GhostBadge: React.FC<{
|
|||||||
|
|
||||||
export const FormatUniverse: React.FC = () => {
|
export const FormatUniverse: React.FC = () => {
|
||||||
const frame = useCurrentFrame();
|
const frame = useCurrentFrame();
|
||||||
const logoScale = Math.sin(frame * 0.06) * 0.03 + 1;
|
const logoScale = Math.sin(frame * 0.06) * 0.05 + 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AbsoluteFill style={{ backgroundColor: COLOR.dark, overflow: "hidden" }}>
|
<AbsoluteFill style={{ backgroundColor: COLOR.dark, overflow: "hidden" }}>
|
||||||
|
{/* Orbit ring lines (faint elliptical paths behind each ring) */}
|
||||||
|
<svg
|
||||||
|
style={{ position: "absolute", inset: 0 }}
|
||||||
|
width={800}
|
||||||
|
height={600}
|
||||||
|
role="img"
|
||||||
|
aria-label="Orbit rings"
|
||||||
|
>
|
||||||
|
{RINGS.map((ring) => (
|
||||||
|
<ellipse
|
||||||
|
key={`orbit-${ring.radius}`}
|
||||||
|
cx={400}
|
||||||
|
cy={280}
|
||||||
|
rx={ring.radius}
|
||||||
|
ry={ring.radius * 0.55}
|
||||||
|
fill="none"
|
||||||
|
stroke={ring.color}
|
||||||
|
strokeWidth={0.5}
|
||||||
|
opacity={0.05}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Opacity trail: render each badge at frame-1 and frame-2 positions */}
|
||||||
{RINGS.map((ring) =>
|
{RINGS.map((ring) =>
|
||||||
ring.formats.map((fmt, fi) => {
|
ring.formats.map((fmt, fi) => {
|
||||||
const angleOffset = (fi / ring.formats.length) * Math.PI * 2;
|
const angleOffset = (fi / ring.formats.length) * Math.PI * 2;
|
||||||
const ghostAngle = angleOffset + (frame - 2) * SPEED * ring.direction;
|
const trail1Angle = angleOffset + (frame - 1) * SPEED * ring.direction;
|
||||||
return (
|
const trail2Angle = angleOffset + (frame - 2) * SPEED * ring.direction;
|
||||||
<GhostBadge
|
return [
|
||||||
key={`ghost-${ring.radius}-${fmt}`}
|
<TrailBadge
|
||||||
|
key={`trail2-${ring.radius}-${fmt}`}
|
||||||
label={fmt}
|
label={fmt}
|
||||||
color={ring.color}
|
color={ring.color}
|
||||||
angle={ghostAngle}
|
angle={trail2Angle}
|
||||||
radius={ring.radius}
|
radius={ring.radius}
|
||||||
/>
|
trailOpacity={0.1}
|
||||||
);
|
/>,
|
||||||
|
<TrailBadge
|
||||||
|
key={`trail1-${ring.radius}-${fmt}`}
|
||||||
|
label={fmt}
|
||||||
|
color={ring.color}
|
||||||
|
angle={trail1Angle}
|
||||||
|
radius={ring.radius}
|
||||||
|
trailOpacity={0.3}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Main badges at current frame */}
|
||||||
{RINGS.map((ring) =>
|
{RINGS.map((ring) =>
|
||||||
ring.formats.map((fmt, fi) => {
|
ring.formats.map((fmt, fi) => {
|
||||||
const angleOffset = (fi / ring.formats.length) * Math.PI * 2;
|
const angleOffset = (fi / ring.formats.length) * Math.PI * 2;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion";
|
import { AbsoluteFill, useCurrentFrame } from "remotion";
|
||||||
import { ClipReveal } from "@/components/ClipReveal";
|
import { ClipReveal } from "@/components/ClipReveal";
|
||||||
import { COLOR } from "@/lib/colors";
|
import { COLOR } from "@/lib/colors";
|
||||||
import { TEXT } from "@/lib/fonts";
|
import { TEXT } from "@/lib/fonts";
|
||||||
|
|||||||
Reference in New Issue
Block a user