---
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";

<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>

## Install

<InstallCommand command="npx hyperframes add card-resize" item="card-resize" />

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. |

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)
