This space introduces an initial idea without defining its outcome. Forms exist in a state of balance, shaped by intention rather than urgency.
Form and expression intersect without explanation. The subject exists between motion and stillness, marked by attitude rather than intent.
This section explores difference through placement and proportion. Repetition is avoided in favor of subtle change.
A clearer position begins to take shape. Elements feel grounded, deliberate, and visually assured.
The layout favors stability over motion, offering a moment of visual certainty.
This space holds without interruption. Nothing shifts unnecessarily, allowing form and color to remain uninterrupted.
The composition loosens its grip and allows space to dominate. Elements soften, contrast fades, and structure becomes secondary.
Add these scripts to your <head> or before the closing </body> tag:
<!-- GSAP -->
<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>
<!-- Lenis Smooth Scroll -->
<script src="https://unpkg.com/lenis@1.1.18/dist/lenis.min.js"></script>
/* Section base styles */
[data-followart-section] {
position: relative;
width: 100%;
height: 100svh;
min-height: 100svh;
overflow: hidden;
}
/* Container that rotates */
[data-followart-container] {
position: relative;
width: 100%;
height: 100%;
padding: 2rem;
display: flex;
transform: rotate(var(--followart-rotation, 30deg));
transform-origin: var(--followart-origin, bottom left);
will-change: transform;
}
/* Column layout helper */
[data-followart-col] {
flex: 1;
display: flex;
}
/* Hide scrollbar (optional) */
::-webkit-scrollbar {
display: none;
}
gsap.registerPlugin(ScrollTrigger);
document.addEventListener("DOMContentLoaded", () => {
// Initialize Lenis smooth scroll
const lenis = new Lenis();
lenis.on("scroll", ScrollTrigger.update);
gsap.ticker.add((time) => lenis.raf(time * 1000));
gsap.ticker.lagSmoothing(0);
// Get all sections with the data attribute
const sections = document.querySelectorAll("[data-followart-section]");
sections.forEach((section, index) => {
const container = section.querySelector("[data-followart-container]");
if (!container) return;
// Read custom rotation from data attribute or CSS variable
const rotation = section.dataset.followartRotation ||
getComputedStyle(container).getPropertyValue("--followart-rotation") || "30deg";
// Set initial rotation via CSS variable
container.style.setProperty("--followart-rotation", rotation);
// Animate rotation to 0 on scroll
gsap.to(container, {
rotation: 0,
ease: "none",
scrollTrigger: {
trigger: section,
start: "top bottom",
end: "top 20%",
scrub: true,
},
});
// Pin section unless it's marked with data-followart-no-pin
const shouldPin = !section.hasAttribute("data-followart-no-pin");
if (shouldPin) {
ScrollTrigger.create({
trigger: section,
start: "bottom bottom",
end: "bottom top",
pin: true,
pinSpacing: false,
});
}
});
});
<!-- Basic Section -->
<section data-followart-section>
<div data-followart-container>
<div data-followart-col>
<h1>Your Heading</h1>
</div>
<div data-followart-col>
<p>Your content here.</p>
</div>
</div>
</section>
<!-- Section with custom rotation -->
<section data-followart-section data-followart-rotation="45deg">
<div data-followart-container>
<!-- content -->
</div>
</section>
<!-- Last section (disable pinning) -->
<section data-followart-section data-followart-no-pin>
<div data-followart-container>
<!-- content -->
</div>
</section>
| Attribute | Element | Description |
|---|---|---|
data-followart-section |
section | Marks element as an animated section (required) |
data-followart-container |
div | The container that rotates (required) |
data-followart-col |
div | Flex column helper (optional) |
data-followart-rotation |
section | Custom initial rotation (default: 30deg) |
data-followart-no-pin |
section | Disable pinning for this section |
/* Override rotation per section */
.my-section [data-followart-container] {
--followart-rotation: 45deg;
--followart-origin: bottom right;
}
/* Custom colors via inline styles or classes */
.my-section [data-followart-container] {
background-color: #8e9487;
color: #000;
}
For Webstudio: Add the CSS to your project's custom CSS and the JavaScript to your custom code section. Then use the data attributes on your elements:
1. Go to Project Settings → Custom Code
2. Add GSAP & Lenis scripts to <head>
3. Add the JavaScript before </body>
4. Add required CSS to your stylesheet
5. Apply data attributes to your elements:
- Add "data-followart-section" to section elements
- Add "data-followart-container" to the inner container
- Add "data-followart-col" to column divs
- Add "data-followart-no-pin" to the last section
| Tip | Description |
|---|---|
| Taller sections |
Set height: 200svh on section for longer scroll
duration
|
| Different origins |
Use --followart-origin: bottom right for reverse
rotation
|
| No smooth scroll | Remove Lenis initialization to use native scrolling |
| Mobile |
Add flex-direction: column to container on small
screens
|