mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-11 19:09:20 +00:00
The generated catalog pages rendered the "Source" accordion title as a
JSX expression, `<Accordion title={`name.html`}>`. Mintlify's search
indexer stringifies expression attribute nodes rather than evaluating
them, so every one of the 399 catalog pages surfaced its Source section
in search results as "[object Object]" (visible on the Scroll Feed page,
among others).
The template literal carried no escaping benefit: every title is a
single-segment `name.html` path, and a double-quoted JSX string handles
it directly. Introduce `mdxStringAttribute`, which emits a plain
`name="value"` attribute and escapes `& " < { }` as HTML character
references (MDX decodes references inside quoted attribute values, and
unlike JSON.stringify it does not rely on backslash escapes, which JSX
strings do not honour). Use it for both the Source accordion title and
the preview iframe title, and regenerate the catalog pages.
Regenerated with `npx tsx scripts/generate-catalog-pages.ts`; the only
change across the 399 pages is the one Accordion title line each.
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
423 lines
18 KiB
Plaintext
423 lines
18 KiB
Plaintext
---
|
|
title: "Card Resize"
|
|
description: "A transitions.dev-inspired card resize primitive that expands a compact card into a detail panel"
|
|
---
|
|
|
|
import { InstallCommand } from "/snippets/install-command.jsx";
|
|
import { VariablesExplorer } from "/snippets/variables-explorer.jsx";
|
|
|
|
## Install
|
|
|
|
<InstallCommand command="npx hyperframes add card-resize" item="card-resize" />
|
|
|
|
That writes one file: `compositions/components/card-resize.html`.
|
|
|
|
<VariablesExplorer
|
|
previewSrc="/public/catalog/components/card-resize.json"
|
|
compositionId="card-resize"
|
|
compositionSrc="compositions/components/card-resize.html"
|
|
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"}]}]}
|
|
>
|
|
|
|
```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);
|
|
-->
|
|
```
|
|
|
|
</VariablesExplorer>
|
|
|
|
## 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. |
|
|
|
|
Set them with `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:
|
|
|
|
```html wrap
|
|
<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
|
|
|
|
<Accordion title="card-resize.html">
|
|
|
|
```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);
|
|
-->
|
|
```
|
|
|
|
</Accordion>
|
|
|
|
## Usage
|
|
|
|
Open `compositions/components/card-resize.html` and paste its contents into your composition. See the comment header in the file for detailed instructions.
|
|
|
|
{/* hf:generated-footer */}
|
|
|
|
Tagged `video-primitive` `transition-primitive` `transitions-dev-port` `card` `layout`.
|
|
|
|
## Related topics
|
|
|
|
- [Browse the complete Catalog](/catalog)
|
|
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
|
|
- [Build a richer composition](/go-further)
|