Components / Preloader
Slate Intro
On first load a clapperboard fills the screen, its striped arm snaps shut, and the page hard-cuts in. Every field is your text.
Props
#FFFFFF
#0E0E0D
Press Replay to clap it again.
In Framer, open Assets, add a new code file, paste this over everything in it and save. Slate Intro shows up in the Insert menu, and its controls appear in the right panel.
// Slate Intro · Layr Components
//
// When the page first loads a clapperboard fills the screen, its striped arm
// snaps shut, and the page hard-cuts in. Every field on the slate is your text.
// Pure CSS: it starts before any JavaScript runs, never blocks clicks, and is
// skipped for visitors who prefer reduced motion.
// Paste into a new code file in Framer. Place one on the page (or in a shared
// layout); it covers the window by itself. On the canvas it shows as a small
// label so it never covers your design. Preview the page to see it.
import { addPropertyControls, ControlType, RenderTarget } from "framer"
import { useEffect, useState, type CSSProperties } from "react"
interface SlateIntroProps {
production: string
scene: string
take: string
roll: string
director: string
duration: number
background: string
color: string
fontFamily: string
once: boolean
style?: CSSProperties
}
/** The slate plays once per page load; moving between pages inside the site won't replay it. */
let played = false
const SHEET = `
@keyframes lyr-si-cut{to{visibility:hidden}}
@keyframes lyr-si-clap{0%,50%{transform:rotate(-22deg)}64%,100%{transform:rotate(0deg)}}
@media (prefers-reduced-motion:reduce){.lyr-si{display:none!important}}
`
/**
* @framerIntrinsicWidth 200
* @framerIntrinsicHeight 28
*
* @framerSupportedLayoutWidth auto
* @framerSupportedLayoutHeight auto
*/
export default function SlateIntro(props: SlateIntroProps) {
const {
production = "Showreel",
scene = "01",
take = "01",
roll = "A",
director = "Mara Ellis",
duration = 1.3,
background = "#0A0A0A",
color = "#F2EFE9",
fontFamily = "Inter",
once = true,
} = props
const target = RenderTarget.current()
const onCanvas = target === RenderTarget.canvas || target === RenderTarget.thumbnail || target === RenderTarget.export
const [show] = useState(() => !once || !played)
useEffect(() => {
played = true
}, [])
const font = `"${fontFamily}", "Inter", system-ui, sans-serif`
if (onCanvas) {
return (
<div
style={{
...props.style,
padding: "7px 10px",
borderRadius: 4,
background,
color,
border: `1px dashed ${color}`,
fontFamily: font,
fontSize: 12,
lineHeight: 1,
whiteSpace: "nowrap",
opacity: 0.8,
}}
>
Slate intro · plays on load
</div>
)
}
if (!show) return null
const seconds = Math.min(3, Math.max(0.6, Number(duration) || 1.3))
const stripes = `repeating-linear-gradient(-45deg, ${color} 0 18px, ${background} 18px 36px)`
const field = (label: string, value: string, wide = false) => (
<div style={{ display: "flex", flexDirection: "column", gap: 8, padding: "14px 16px", gridColumn: wide ? "1 / -1" : undefined, borderTop: `1px solid ${color}` }}>
<span style={{ fontSize: 12, lineHeight: 1, letterSpacing: "-0.005em", opacity: 0.6 }}>{label}</span>
<span style={{ fontSize: "clamp(22px, 3vw, 32px)", lineHeight: 1, letterSpacing: "-0.02em" }}>{value}</span>
</div>
)
return (
<div
className="lyr-si"
aria-hidden="true"
style={{
position: "fixed",
inset: 0,
zIndex: 2147482000,
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: 24,
background,
color,
fontFamily: font,
pointerEvents: "none",
animation: `lyr-si-cut 0s linear ${seconds}s forwards`,
}}
>
<style>{SHEET}</style>
<div style={{ width: "min(460px, 86vw)", border: `1.5px solid ${color}`, borderRadius: 6, overflow: "visible" }}>
<div
style={{
height: 40,
background: stripes,
borderBottom: `1.5px solid ${color}`,
transformOrigin: "0% 100%",
animation: `lyr-si-clap ${seconds * 0.8}s cubic-bezier(0.7, 0, 0.3, 1) forwards`,
borderRadius: "5px 5px 0 0",
}}
/>
<div style={{ height: 40, background: stripes, opacity: 0.9 }} />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}>
{field("Production", production, true)}
{field("Scene", scene)}
{field("Take", take)}
{field("Roll", roll)}
{field("Director", director, true)}
</div>
</div>
</div>
)
}
addPropertyControls(SlateIntro, {
production: { type: ControlType.String, title: "Production", defaultValue: "Showreel" },
scene: { type: ControlType.String, title: "Scene", defaultValue: "01" },
take: { type: ControlType.String, title: "Take", defaultValue: "01" },
roll: { type: ControlType.String, title: "Roll", defaultValue: "A" },
director: { type: ControlType.String, title: "Director", defaultValue: "Mara Ellis" },
duration: {
type: ControlType.Number,
title: "Duration",
defaultValue: 1.3,
min: 0.6,
max: 3,
step: 0.1,
unit: "s",
description: "How long the slate shows before it cuts to the page.",
},
background: { type: ControlType.Color, title: "Background", defaultValue: "#0A0A0A" },
color: { type: ControlType.Color, title: "Color", defaultValue: "#F2EFE9" },
fontFamily: { type: ControlType.String, title: "Font family", defaultValue: "Inter", description: "Use the family name of your text styles." },
once: {
type: ControlType.Boolean,
title: "Once per load",
defaultValue: true,
description: "On: plays when the site is opened or reloaded, not again on page changes inside the site. Off: plays every time it appears.",
},
})
In this preview
The photos are demo pictures shown on this site only; they are not in the files you copy. Set your own through the image props.