Install
That writes one file:compositions/components/card-resize.html.
Variables
Every one of these has a default, so the piece works untouched. Set the ones you want to change on the element:| Variable | Default | Accepts | What it does |
|---|---|---|---|
surface | paper | paper, ink | Card colour scheme: paper is the original white card, ink is a dark card for dark frames. |
accent | ocean | ocean, forest, ember | Treatment of the media thumbnail: a plain plate, a hatched surface, or an image plate with a horizon-and-disc glyph. |
elevation | raised | flat, raised, floating | How far the card lifts off the frame, from a contact shadow to a deep drop. |
data-variable-values on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:
<div
data-composition-id="card-resize"
data-composition-src="compositions/components/card-resize.html"
data-variable-values='{"surface":"paper","accent":"ocean","elevation":"raised"}'
></div>
Source
card-resize.html
card-resize.html
<!--
Card Resize - transitions.dev-inspired primitive for HyperFrames.
Paste the markup, CSS and script into a composition. Use the timeline
integration note at the bottom as the starting point for deterministic GSAP
animation.
Variables. The script reads each one, falls back to the declared default on
anything missing or unrecognised, and writes the result as a custom property
the CSS above consumes. The timeline recipe is untouched by them: it keeps
driving the same width, height and fade, and these decide how the card that
expands is dressed.
- surface (paper | ink, default paper): the card colour scheme. paper is
the original white card with near-black type, ink is a dark card with
light type for dark frames.
- accent (ocean | forest | ember, default ocean): the treatment of the media
thumbnail. ocean is a plain plate, forest adds a fine achromatic hatch,
ember adds a horizon-and-disc glyph so the plate reads as a photo. All
three are built from the surface's own ink at fixed alphas, so the
thumbnail carries no hue on either surface. The three value names are
inherited from the hue families they used to paint and no longer describe
what they render; renaming them is a separate, breaking change.
- elevation (flat | raised | floating, default raised): how far the card
lifts off the frame, from a tight contact shadow to a deep drop.
On every default the result is a white card with a quiet ink-tinted thumbnail
plate under a soft wide drop shadow. Layout, sizing and motion are unchanged.
-->
<div
class="hf-transition-card-resize"
data-composition-variables='[
{ "id": "surface", "type": "enum", "role": "style", "label": "Surface", "description": "Card colour scheme: paper is the original white card, ink is a dark card for dark frames.", "default": "paper", "options": [{ "value": "paper", "label": "Paper" }, { "value": "ink", "label": "Ink" }] },
{ "id": "accent", "type": "enum", "role": "style", "label": "Accent", "description": "Treatment of the media thumbnail: a plain plate, a hatched surface, or an image plate with a horizon-and-disc glyph.", "default": "ocean", "options": [{ "value": "ocean", "label": "Ocean" }, { "value": "forest", "label": "Forest" }, { "value": "ember", "label": "Ember" }] },
{ "id": "elevation", "type": "enum", "role": "style", "label": "Elevation", "description": "How far the card lifts off the frame, from a contact shadow to a deep drop.", "default": "raised", "options": [{ "value": "flat", "label": "Flat" }, { "value": "raised", "label": "Raised" }, { "value": "floating", "label": "Floating" }] }
]'
>
<div class="hf-transition-card-media"></div>
<div class="hf-transition-card-copy">
<span>Project status</span>
<strong>Ready to render</strong>
<p>All scenes validated and queued for export.</p>
</div>
</div>
<style>
.hf-transition-card-resize {
--hf-card-w: 330px;
--hf-card-h: 132px;
/* Placeholder ramp: the surface's own ink at fixed alphas, so the media
plate inverts with the surface instead of needing a second palette. */
--hf-card-l2: rgba(24, 24, 27, 0.45);
--hf-card-l3: rgba(24, 24, 27, 0.18);
--hf-card-hair: rgba(24, 24, 27, 0.14);
display: grid;
grid-template-columns: 92px 1fr;
gap: 18px;
width: var(--hf-card-w);
height: var(--hf-card-h);
padding: 18px;
overflow: hidden;
border: 1px solid var(--hf-card-border, #e4e4e7);
border-radius: 26px;
background: var(--hf-card-bg, #ffffff);
color: var(--hf-card-ink, #18181b);
font-family: Inter, system-ui, sans-serif;
box-shadow: var(--hf-card-shadow, 0 26px 80px rgba(0, 0, 0, 0.12));
will-change: width, height, transform;
}
.hf-transition-card-media {
width: 92px;
height: 92px;
border-radius: 20px;
background: var(--hf-card-media, var(--hf-card-l3));
box-shadow: inset 0 0 0 1px var(--hf-card-hair);
}
.hf-transition-card-copy {
display: grid;
align-content: center;
gap: 7px;
min-width: 0;
}
.hf-transition-card-copy span {
color: var(--hf-card-muted, #71717a);
font-size: 13px;
font-weight: 850;
text-transform: uppercase;
}
.hf-transition-card-copy strong {
white-space: nowrap;
font-size: 22px;
letter-spacing: -0.01em;
}
.hf-transition-card-copy p {
margin: 0;
color: var(--hf-card-muted, #71717a);
font-size: 15px;
line-height: 1.45;
}
</style>
<script>
(function () {
"use strict";
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
// Unrecognised overrides return to their declared defaults.
function pick(table, value, fallback) {
return Object.prototype.hasOwnProperty.call(table, value) ? value : fallback;
}
var surfaces = {
paper: {
bg: "#ffffff",
ink: "#18181b",
muted: "#71717a",
border: "#e4e4e7",
l2: "rgba(24, 24, 27, 0.45)",
l3: "rgba(24, 24, 27, 0.18)",
hair: "rgba(24, 24, 27, 0.14)",
},
ink: {
bg: "#16161a",
ink: "#fafafa",
muted: "#a1a1aa",
border: "#2f2f37",
l2: "rgba(250, 250, 250, 0.45)",
l3: "rgba(250, 250, 250, 0.18)",
hair: "rgba(250, 250, 250, 0.14)",
},
};
// Media treatments, not hue families: the plate itself, the plate hatched,
// and the plate with a horizon-and-disc glyph that reads as a photo. Each
// one is written in the surface's own ink ramp, so both surfaces work.
var medias = {
ocean: "var(--hf-card-l3)",
forest:
"repeating-linear-gradient(135deg, var(--hf-card-hair) 0 2px, transparent 2px 4px), var(--hf-card-l3)",
ember:
"radial-gradient(circle 11px at 64px 28px, var(--hf-card-l2) 0 100%, transparent 100%), linear-gradient(to top, var(--hf-card-l2) 0 24px, transparent 24px), var(--hf-card-l3)",
};
var shadows = {
flat: "0 2px 10px rgba(0, 0, 0, 0.1)",
raised: "0 26px 80px rgba(0, 0, 0, 0.12)",
floating: "0 46px 120px rgba(0, 0, 0, 0.3)",
};
var surface = surfaces[pick(surfaces, vars.surface, "paper")];
var media = medias[pick(medias, vars.accent, "ocean")];
var shadow = shadows[pick(shadows, vars.elevation, "raised")];
var roots = document.querySelectorAll(".hf-transition-card-resize");
for (var i = 0; i < roots.length; i += 1) {
roots[i].style.setProperty("--hf-card-bg", surface.bg);
roots[i].style.setProperty("--hf-card-ink", surface.ink);
roots[i].style.setProperty("--hf-card-muted", surface.muted);
roots[i].style.setProperty("--hf-card-border", surface.border);
roots[i].style.setProperty("--hf-card-l2", surface.l2);
roots[i].style.setProperty("--hf-card-l3", surface.l3);
roots[i].style.setProperty("--hf-card-hair", surface.hair);
roots[i].style.setProperty("--hf-card-media", media);
roots[i].style.setProperty("--hf-card-shadow", shadow);
}
})();
</script>
<!--
Timeline integration:
tl.fromTo('.hf-transition-card-resize', { '--hf-card-w': '330px', '--hf-card-h': '132px', opacity: 0, y: 24 }, { '--hf-card-w': '560px', '--hf-card-h': '210px', opacity: 1, y: 0, duration: 0.68, ease: 'power3.inOut' }, 0.35);
tl.fromTo('.hf-transition-card-copy p', { opacity: 0, y: 12 }, { opacity: 1, y: 0, duration: 0.28, ease: 'power2.out' }, 0.82);
-->
Usage
Opencompositions/components/card-resize.html and paste its contents into your composition. See the comment header in the file for detailed instructions.
Tagged video-primitive transition-primitive transitions-dev-port card layout.