Motion
ContainerScroll
A hero block where a device-like frame starts tilted back in 3D and straightens as the page scrolls, with the title drifting up.
Guidelines#
Use it for one product showcase per landing page: a screenshot, video or live mock of the workspace under a headline. Crew OS uses it for the scrolling product video.
What you provide: titleComponent (your heading and subtitle) and children (what shows on the screen; it fills the frame). Nothing else is configurable.
Anatomy: the outer block is 960px tall (h-[60rem]), 8px padding (80px from md), with perspective: 1000px. The frame is max-w-5xl, 480px tall (640px from md), rounded-[30px], a 4px #6C6C6C border, bg-[#222222], 8px padding (24px from md) and a six-layer drop shadow; it overlaps the title by 48px. The screen inside is rounded-2xl, bg-gray-100 (dark:bg-zinc-900), 16px padding from md.
Behaviour: progress is the block's position in the window scroll (Framer Motion useScroll). Across it the frame rotates on X from 20° to 0°, scales from 1.05 to 1 (0.7 to 0.9 at 768px wide and below) and the title moves up 100px. Only window scroll drives it, so it cannot live inside a scrolling panel; this preview makes the card itself scrollable. It does not check reduced motion.
Don't use it twice on a page, put interactive forms on the tilted screen, or rely on text inside the frame being readable before it flattens.
Facts#
| Group | Motion |
|---|---|
| Product | Crew OS |
| Global | window.DigitalCrew.ContainerScroll |
| Source | digitalcrew-orchestrator: components/ui/container-scroll-animation.tsx |
| Import in the app | import { ContainerScroll } from "@/components/ui/container-scroll-animation"; |
API#
TypeScript declarations emitted from components/ui/container-scroll-animation.tsx.
export declare const ContainerScroll: ({ titleComponent, children, }: {
titleComponent: string | React.ReactNode;
children: React.ReactNode;
}) => any;
export declare const Header: ({ translate, titleComponent }: {
translate: MotionValue<number>;
titleComponent: React.ReactNode;
}) => any;
export declare const Card: ({ rotate, scale, children, }: {
rotate: MotionValue<number>;
scale: MotionValue<number>;
translate: MotionValue<number>;
children: React.ReactNode;
}) => any;Example#
The code of the preview above: plain React.createElement against window.DigitalCrew, with realistic data.
<div id="root" class="p-6"></div>
<script>
(function () {
var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
var rows = [
["Claire Dubois", "CFO, Northwind Traders", "Meeting booked", "Tue 14:30"],
["Omar Haddad", "VP Finance, Brightline", "Replied", "18 min ago"],
["Lena Fischer", "Head of Sales, Kestrel Labs", "Opened 3×", "1 h ago"],
["Marc Lefèvre", "Founder, Atlas Freight", "Connected", "3 h ago"],
["Aiko Tanaka", "COO, Vantage Health", "In sequence", "Step 2 of 4"]
];
function Screen() {
return h("div", { className: "grid h-full content-start gap-4 bg-background p-4 text-foreground" },
h("div", { className: "flex items-center justify-between" },
h("div", null, h("p", { className: "text-lg font-semibold" }, "Good morning, Julie"), h("p", { className: "text-muted-foreground text-sm" }, "Max worked through 214 prospects overnight.")),
h(DC.Button, null, h(I.Sparkles), "Ask Max")),
h("div", { className: "grid grid-cols-3 gap-3" },
[["Meetings booked", "11"], ["Reply rate", "8.9%"], ["Pipeline added", "€184k"]].map(function (k) {
return h("div", { key: k[0], className: "rounded-xl border bg-card p-4" }, h("p", { className: "text-muted-foreground text-xs" }, k[0]), h("p", { className: "text-2xl font-semibold tabular-nums" }, k[1]));
})),
h("div", { className: "divide-y rounded-xl border bg-card" }, rows.map(function (r) {
return h("div", { key: r[0], className: "flex items-center gap-3 px-4 py-3 text-sm" },
h("span", { className: "bg-muted flex size-8 items-center justify-center rounded-full text-xs font-medium" }, r[0].split(" ").map(function (w) { return w[0]; }).join("")),
h("span", { className: "flex-1" }, h("span", { className: "font-medium" }, r[0]), h("span", { className: "text-muted-foreground" }, " · " + r[1])),
h(DC.Badge, { variant: "secondary" }, r[2]),
h("span", { className: "text-muted-foreground w-24 text-right text-xs" }, r[3]));
})));
}
function App() {
return h("div", null,
h(DC.ContainerScroll, { titleComponent: h("div", { className: "mb-8 text-center" },
h("h2", { className: "mb-3 text-4xl font-bold" }, "Your sales floor, on autopilot"),
h("p", { className: "text-muted-foreground mx-auto max-w-lg" }, "Digital Workers prospect, follow up and book meetings while you sleep.")) },
h(Screen)),
h("div", { className: "text-muted-foreground flex h-[560px] items-start justify-center text-sm" }, "Scroll back up to tilt the screen again."));
}
ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>