Framed in tungsten and shadows, every shot holds its own deliberate tension.

This is cinematography in its raw form with practical lamps, soft falloff, and the presence of grain that fills each corner of the frame.

Still frames with bold contrast and lighting choices that embrace imperfection.

Every frame forms a clear visual language built through restraint.

1. CDN Scripts

Add these to your <head> or before closing </body> tag:

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/SplitText.min.js"></script>

2. Required CSS

.block-line-wrapper {
position: relative;
width: max-content;
display: block;
}

.block-line {
position: relative;
display: block;
}

.block-revealer {
position: absolute;
top: 0;
left: 0;
width: 101%;
height: 101%;
pointer-events: none;
will-change: transform;
z-index: 1;
}

[data-block-reveal] {
visibility: hidden;
}

[data-block-reveal].is-ready {
visibility: visible;
}

3. JavaScript

gsap.registerPlugin(ScrollTrigger, SplitText);

document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll("[data-block-reveal]").forEach((element) => {
const blockColor = element.dataset.blockColor || "#fe0100";
const stagger = parseFloat(element.dataset.stagger) || 0.15;
const duration = parseFloat(element.dataset.duration) || 0.75;

const lines = [];
const blocks = [];

const split = new SplitText(element, {
type: "lines",
linesClass: "block-line",
});

split.lines.forEach((line) => {
const wrapper = document.createElement("div");
wrapper.className = "block-line-wrapper";
line.parentNode.insertBefore(wrapper, line);
wrapper.appendChild(line);

const block = document.createElement("div");
block.className = "block-revealer";
block.style.backgroundColor = blockColor;
wrapper.appendChild(block);

lines.push(line);
blocks.push(block);
});

gsap.set(lines, { opacity: 0 });
gsap.set(blocks, { scaleX: 0, transformOrigin: "left center" });

element.classList.add("is-ready");

blocks.forEach((block, index) => {
const line = lines[index];

const tl = gsap.timeline({
paused: true,
delay: index * stagger,
});

tl.to(block, { scaleX: 1, duration: duration, ease: "power4.inOut" });
tl.set(line, { opacity: 1 });
tl.set(block, { transformOrigin: "right center" });
tl.to(block, { scaleX: 0, duration: duration, ease: "power4.inOut" });

ScrollTrigger.create({
trigger: element,
start: "top 90%",
once: true,
onEnter: () => tl.play(),
});
});
});
});

4. HTML Usage

<!-- Red block (default) -->
<h1 data-block-reveal>
Your heading text here.
</h1>

<!-- Custom black block -->
<p data-block-reveal data-block-color="#000000">
Your paragraph text here.
</p>

<!-- Custom color + slower animation -->
<h2 data-block-reveal data-block-color="#3b82f6" data-duration="1" data-stagger="0.2">
Custom styled text.
</h2>

5. Data Attributes Reference

AttributeDefaultDescription
data-block-revealrequiredEnables the animation
data-block-color#fe0100Color of the reveal block
data-duration0.75Animation speed in seconds
data-stagger0.15Delay between each line

6. Center Aligned Lines (Optional CSS)

/* Add this if you want centered text */
section h1 .block-line-wrapper,
section p .block-line-wrapper {
margin: 0 auto;
}