Scroll-driven reveals, pointer-tilt, and pinned sticky grids — all
wired via data-sp-anim attributes. The HTML is
identical to every other section above; only the attributes change.
Disable JS or turn on Reduce Motion to confirm content stays visible.
This card starts transparent and 24px below its final position. When it enters the viewport, it animates into place once.
The same recipe, delayed 150ms. Stack several of these in a row and you get a natural cascade without writing a timeline.
Each card is an independent ScrollTrigger — the bootstrap registers one per element and disposes them on teardown.
Move your cursor over this card — it tracks the pointer via
gsap.quickTo. Coarse pointers (touch) and
reduced-motion users skip it.
Same recipe, max: 14 degrees instead of the
default 8. Options flow through the data-sp-anim-opts JSON blob.
Multiple recipes on one element — space-separated. This card reveals on scroll and tilts on hover.
The recipe splits on whitespace, wraps each word in a clipping box, animates the inner span from below, then restores the original markup on dispose. Screen readers see the intact sentence via aria-label.
A clip-path tween scrubbed to scroll progress. Put data-sp-anim="scrollMask" on any element (or a
composition with a [data-sp-anim-role="mask-target"]
child, like Feature) and it reveals as the block
enters the viewport.
Three decorative frames moving at different scroll rates across a
full viewport. Positive speed means slower than
the page; negative values move against it.
Full-viewport panels stack and pin as you scroll. Tall panels
fake-scroll their inner content before the panel itself scales down
and fades out, revealing the next one. Ported from the GreenSock
"Slides Pinning – Overscroll" CodePen as a single data-sp-anim="stackPinFade" recipe; the final
panel is the landing destination and is not animated.
Short content — fits the viewport, pins, scales down and fades.
This panel is taller than the viewport. While it is pinned, the recipe fake-scrolls the inner content upward, simulating an internal scroll without any nested scroll container.
The computed marginBottom on the panel
extends the scroll budget by exactly the overshoot height, so
the user's scroll distance maps 1:1 to the translate.
Only once the inner content has fully scrolled past does the scale-and-fade take over — at which point the panel unpins and reveals the next one below.
Everything is driven by a single scrubbed GSAP timeline per
panel, shared across short and tall variants. The recipe reads [data-sp-anim-role="slide"] children of its
wrapper and infers which branch to use per panel based on the
inner height vs. viewport height.
Resize the window while scrolled in — the end offset and
translate distance recompute automatically via invalidateOnRefresh.
Another short panel — short panels skip the fake-scroll phase and go straight to scale + fade.
The last animated panel — scales down to reveal the landing destination below.
Not animated — this is where the stack settles. Keep scrolling for the rest of the page.
Per-character and per-word reveals scrubbed to scroll progress.
Distilled from the Codrops OnScrollTypographyAnimations demo into a family
of individual recipes — each one tree-shakeable, each addressable
directly via data-sp-anim="...". Available:
charTumble, charZoom, charFlipTop, charFlipBottomRandom, charFanOut, charScatter, charScaleSlide, charElasticDrop, charScaleYPinned, charScaleYAlternatingPinned, wordFlyoutPinned, charBlurArc, charScaleCorner, wordFadeTilt.
A pinned image column on one side, tall info blocks on the other. Each
info block occupies a full viewport, so scrolling naturally steps
through them while the pinned image stack collapses its top layer with
a clip-path wipe — revealing the next image beneath. One data-sp-anim="scrollStory" on the wrapper plus [data-sp-anim-role="pinned"] on the image column
and [data-sp-anim-role="frame"] on each image.
Vibrant streets with vertical gardens and solar buildings. An oasis that thrives on renewable energy, smart transport, and green spaces for biodiversity.
Avenues with azure facades and eco-structures — clean energy, smart transit, and urban parks sheltering native wildlife.
Desert refuge with fluid architecture and glowing interiors — a sanctuary drawing on solar power, sustainable design, and natural harmony.
Ethereal structures arc over tranquil waters, bathed in the glow of a setting Martian sun — stark, desolate beauty on the red planet.
A grid that reacts to scroll velocity — tiles stretch and skew as you
fling the page, then elastically snap back to rest the moment you
stop. Each tile has a slightly longer return duration than the last,
so they settle in a staggered wave. One data-sp-anim="elasticGrid" on the wrapper; every
direct child becomes a tile.
Throw the mouse wheel at it — notice how the top row snaps back
first and the bottom row lingers. Tune maxSkew,
maxStretch, stagger, and
ease via data-sp-anim-opts.
A CSS Grid of tiles overlays each surface. Mousemove lights the tile
under the pointer with a short TTL fade; adjacent lit tiles merge
through an SVG feGaussianBlur + feColorMatrix gooey filter. Click for a radial
stagger ripple from the click point. Colors, blend mode, and tile
shape are driven by inline --sp-cursor-* custom
properties — one data-sp-anim="cursorField"
drives every variant. Scroll through the full-viewport showcases
below.
Square tiles, exclusion blend, 24 columns — the default recipe.
32 columns, longer TTL, round tiles — the blobs merge like lava.
Radial-gradient tile fill — click fires a glow-out ripple staggered across the grid from the click point.
Twelve tiles converge, the grid zooms into place, the title
locks center and the call to action fades in — all from one data-sp-anim attribute on a single wrapper.
Keep scrolling down — the page captures the scroll, pins this section, and translates the track sideways through five panels. Release happens at the end, footer follows cleanly.
This is a sample page rendered by SeoPages. Every component on this page comes from the @seopages/ui library — pure CSS, zero JavaScript, fully accessible HTML.
Server-side rendered HTML delivered from the edge. No client-side JavaScript required.
Semantic HTML with proper heading hierarchy, accessible landmarks, and crawlable content.
Override CSS custom properties to match any brand. No rebuild needed — just set your tokens.
The display uses auto-fill with a minimum column width. Items flow naturally and wrap responsively — no breakpoints needed.
The card uses the outlined variant with large padding, making it suitable for primary content areas.
Cards sit side by side when there's room, and stack when there isn't.
Default card with a subtle border. Clean and minimal.
Surface-filled card. Draws attention to featured content.
No border, transparent. Blends into the background.
Just a quick note.
This card has more content to demonstrate how masonry fills vertical gaps. Items pack tightly instead of aligning to rigid row heights.
The layout adapts naturally to varying content lengths — perfect for galleries, testimonials, or blog post grids.
Somewhere in between. The masonry layout places this card optimally to minimize whitespace.
Compact and tidy.
This card is the tallest of the bunch. In a standard grid, all items in the same row would stretch to match this height, leaving awkward gaps.
With masonry, each item takes only the vertical space it needs. The next item slots into the shortest column automatically.
No JavaScript required — pure CSS via display: grid-lanes.
Wraps up the masonry demo. Resize the browser to see columns reflow.
A vibrant gradient wraps this card. The "border" is actually padding on a wrapper div with a CSS background.
Larger width and radius make the gradient border more prominent. Works with any CSS background value.
A rainbow conic gradient border — impossible with native CSS borders.
Repeating stripes as a border — any CSS background pattern works here.
A looping video plays behind the padding area, creating an animated border effect. The inner content sits above the video via z-index.
Horizontal scroll-snap with native CSS pagination dots via
::scroll-marker — zero JavaScript.
The mask clips content to its bounds — no fade, just a hard edge.
Scroll this block to see how overflow is clipped naturally.
No JavaScript, no overlays — pure CSS overflow on a wrapper div.
Useful for scrollable lists or constraining overflowing children.
Works in every browser — just overflow: hidden.
Pairs with any primitive that might overflow its container.
SwiftUI-style HStack / VStack / ZStack via the direction prop. Pure flexbox for horizontal/vertical, single-cell grid for depth.
Merged buttons with zero gap — only the first and last keep their outer corners.
Left
Middle
Minimal, sp-* prefixed atomic classes —
spacing scale 0–5 driven by --sp-space,
flex/grid centering, z-index, text align, and size. Apply via
className on any primitive.
One class on the wrapper does the whole job.
place-items: center
sp-p-1
sp-p-2
sp-p-3
sp-p-4
sp-p-5
Baseline text.
Follows with consistent rhythm.
z-0
z-10
z-20
sp-text-left
sp-text-center
sp-text-right
Leading
Inline variant flows with prose. Block variant
renders a scrollable <pre><code> surface
with an optional language label. Uses --sp-font-mono
and global radius, border, and space tokens.
import { Modal, Button } from "@seopages/ui";
export function ConfirmDelete() {
return (
<Modal id="confirm" size="sm">
<Title as="h3">Delete this item?</Title>
<Text>This cannot be undone.</Text>
<ButtonGroup>
<Button href="#!" variant="outline">Cancel</Button>
<Button href="#!">Delete</Button>
</ButtonGroup>
</Modal>
);
} .sp-card {
background-color: var(--sp-color-surface);
border-radius: var(--sp-radius);
padding: var(--sp-space);
font-family: var(--sp-font-body);
} make render-dev # local Astro dev with .env.development
make render-deploy # build + push secrets + wrangler deploy A plain block without a language label, with wrap enabled — long lines break instead of scrolling horizontally. Useful for prose-adjacent snippets where horizontal overflow would be disruptive to the reading flow.
Zero-JS <dialog> driven by :target.
Open via a link to the modal's id, close via any link to
a non-matching hash (default #!). Click the backdrop or
the ✕ to dismiss.
Native <details> / <summary> —
zero JavaScript, keyboard accessible, theme tokens cascade (try the
dark-mode toggle).
SeoPages renders tenant websites from a shared component library at the edge. Every page is static HTML, cached aggressively, and invalidated on demand.
This disclosure block is a single Collapse
primitive wrapping plain text — click the summary to collapse it.
No. The Collapse primitive uses the native <details> element. Sharing a `name` across siblings makes opening one close the others — the HTML spec handles it.
Every surface, spacing, and radius value pulls from --sp-* custom properties. Flip the dark-mode toggle in the header with a trigger open — the colors update without remounting.
The summary is keyboard-focusable; Space and Enter toggle it. Screen readers announce expanded/collapsed state via the native element, and a visible focus ring uses --sp-color-primary.
Collapse holds any primitive — Cards, Grids, Stacks, even another Collapse.
The flat variant drops its border so the parent Card owns the outline.
Native <details> disclosure with compound
subcomponents — Dropdown.Item, Dropdown.Action, Dropdown.Divider, Dropdown.Header. Honest disclosure semantics (no
fake role="menu"), zero JavaScript. Sharing name across siblings makes opening one close the
others.
A two-column block built from the Image, Title, Text, and Button primitives. Swap imagePosition between "left"
and "right"; columns collapse to a single stack
below the mobile breakpoint.
Every primitive in the library is a pure function from props to HTML. Compose them into sections, drop them in an Astro page, and let the edge cache do the rest.
Override any --sp-* token on a wrapper element and the whole composition retunes — no rebuild, no remount, no surprises in dark mode.
Use imagePosition="top" or "bottom" for hero-style layouts. Content stacks above or below the image and aligns to the center.
The image prop accepts a single object or an array. Two images render as a 1×2 grid, three as a 1×3, four as a 2×2 — the layout adapts to the count.
Image grid sits below the centered content block, capped at the same stacked max width as the single-image variant.
Drop data-sp-anim="scrollMask" on the Feature root and its image reveals through an expanding clip-path scrubbed to scroll progress. Tune shape, from/to, radius, and start/end via data-sp-anim-opts — no component changes, no extra markup.
Same recipe, shape:"circle" — the image opens from a small centered disc outward to cover the frame. Swap from / to percentages to tune how early the reveal starts and how much overshoot you want at the corners.