mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-15 22:08:03 +00:00
One block per loop variant (loop-<family>[-<n>], 1080x1080), one hand-written engine per family, driven by a single paused GSAP timeline that recomputes every card transform from t. Every engine parameter is exposed through data-composition-variables, grouped like a template editor's panel (Media / Background / Appearance / Animation / Template options), plus image1..N content slots that default to monochrome SVG placeholders. No third-party media ships. Validation: scripts/lint-registry-items.mjs 249/249, oxfmt clean, hyperframes check on one variant per family (95/95), registry.json + docs/catalog pages regenerated by the repo scripts. Previews are rendered locally (720^2) and need the docs-images upload. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
482 lines
54 KiB
Plaintext
482 lines
54 KiB
Plaintext
---
|
||
title: "Loop Book 3"
|
||
description: "A book-page spread whose pages turn one after another to reveal each image. 8 image slots, 8s seamless loop, 1:1."
|
||
---
|
||
|
||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||
|
||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/loop-book-3.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/loop-book-3.png" autoPlay muted loop playsInline />
|
||
|
||
## Install
|
||
|
||
<InstallCommand command="npx hyperframes add loop-book-3" item="loop-book-3" />
|
||
|
||
That writes one file: `compositions/loop-book-3.html`.
|
||
|
||
## Add it to your video
|
||
|
||
It runs for 8 seconds at 1080×1080. Paste this into your composition:
|
||
|
||
```html index.html
|
||
<div
|
||
data-composition-id="loop-book-3"
|
||
data-composition-src="compositions/loop-book-3.html"
|
||
data-start="0"
|
||
data-duration="8"
|
||
data-track-index="1"
|
||
data-width="1080"
|
||
data-height="1080"
|
||
></div>
|
||
```
|
||
|
||
Move it in time with `data-start`. Put it on a different timeline row with
|
||
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
|
||
|
||
## 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 |
|
||
| --- | --- | --- | --- |
|
||
| `mediaFit` | `crop` | `natural`, `crop` | Natural keeps each image's own ratio; Crop fills the card box at the media aspect below. |
|
||
| `cropAspectRatio` | `1:1` | `16:9`, `9:16`, `1:1`, `4:5`, `3:2`, `3:4`, `2:3` | Card aspect used when Fit is Crop. |
|
||
| `backgroundType` | `gradient` | `gradient`, `color`, `image` | Gradient (start → end colours), flat Colour, or your own Image. |
|
||
| `backgroundGradientStart` | `#111111` | color | Top colour of the gradient. |
|
||
| `backgroundGradientEnd` | `#444444` | color | Bottom colour of the gradient. |
|
||
| `backgroundColor` | `#0b0b0f` | color | Flat colour when Type is Color; also the ground under an Image. |
|
||
| `backgroundImage` | `` | image | Cover-fitted background when Type is Image. |
|
||
| `blurAmount` | `0` | 0px to 60px, step 1px | Blur on the background image, px. |
|
||
| `backgroundGradientNoise` | `0` | 0% to 100%, step 1% | Film-grain overlay strength. |
|
||
| `backgroundOpacity` | `1` | 0 to 1, step 0.05 | Opacity of the whole background layer (0 = transparent ground). |
|
||
| `borderRadius` | `24` | 0px to 200px, step 1px | Card corner radius in px at 1080. |
|
||
| `cycles` | `1` | 0 to 10, step 0.1 | Number of motion cycles per loop. |
|
||
| `cycleDeg` | `360` | -180° to 180°, step 1° | Degrees turned per cycle. |
|
||
| `direction` | `forward` | `forward`, `reverse` | Direction of travel. |
|
||
| `foredge` | `lines` | `lines`, `image` | Foredge (engine parameter; see the block header comment). |
|
||
| `finish` | `matte` | `flat`, `matte`, `glossy` | Finish (engine parameter; see the block header comment). |
|
||
| `planeSize` | `1450` | 0 to 2900, step 10 | Card size in scene units. |
|
||
| `thickness` | `5` | 0 to 10, step 0.1 | Thickness (engine parameter; see the block header comment). |
|
||
| `perspective` | `100` | 0 to 200, step 1 | Camera focal length: smaller = stronger perspective. |
|
||
| `distance` | `2200` | 0 to 4400, step 10 | Camera distance from the formation. |
|
||
| `rotationX` | `0` | -180° to 180°, step 1° | Formation tilt about the horizontal axis, degrees. |
|
||
| `rotationY` | `0` | -180° to 180°, step 1° | Formation turn about the vertical axis, degrees. |
|
||
| `rotationZ` | `0` | -180° to 180°, step 1° | Formation roll about the view axis, degrees. |
|
||
| `offsetX` | `0` | -100% to 100%, step 1% | Horizontal offset of the formation, % of width. |
|
||
| `offsetY` | `0` | -100% to 100%, step 1% | Vertical offset of the formation, % of height. |
|
||
| `lightIntensity` | `100` | 0 to 250, step 10 | Light Intensity (engine parameter; see the block header comment). |
|
||
| `lightAngle` | `30` | 0 to 452, step 10 | Light Angle (engine parameter; see the block header comment). |
|
||
| `lightHeight` | `40` | 0 to 80, step 1 | Light Height (engine parameter; see the block header comment). |
|
||
| `image1` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 1 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image2` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 2 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image3` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 3 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image4` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 4 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image5` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 5 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image6` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 6 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image7` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 7 of 8. Any aspect; fitted per mediaFit. |
|
||
| `image8` | `data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E` | image | Image on card 8 of 8. Any aspect; fitted per mediaFit. |
|
||
|
||
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="loop-book-3"
|
||
data-composition-src="compositions/loop-book-3.html"
|
||
data-variable-values='{"mediaFit":"crop","cropAspectRatio":"1:1","backgroundType":"gradient","backgroundGradientStart":"#111111","backgroundGradientEnd":"#444444","backgroundColor":"#0b0b0f","backgroundImage":"","blurAmount":0,"backgroundGradientNoise":0,"backgroundOpacity":1,"borderRadius":24,"cycles":1,"cycleDeg":360,"direction":"forward","foredge":"lines","finish":"matte","planeSize":1450,"thickness":5,"perspective":100,"distance":2200,"rotationX":0,"rotationY":0,"rotationZ":0,"offsetX":0,"offsetY":0,"lightIntensity":100,"lightAngle":30,"lightHeight":40,"image1":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image2":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image3":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image4":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image5":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image6":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image7":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E","image8":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"}'
|
||
></div>
|
||
```
|
||
|
||
## Source
|
||
|
||
<Accordion title={`loop-book-3.html`}>
|
||
|
||
```html
|
||
<!doctype html>
|
||
<html
|
||
lang="en"
|
||
data-composition-variables='[{"id":"mediaFit","type":"enum","role":"style","label":"Media · Fit","description":"Natural keeps each image's own ratio; Crop fills the card box at the media aspect below.","default":"crop","options":[{"value":"natural","label":"Natural (original ratio)"},{"value":"crop","label":"Crop"}]},{"id":"cropAspectRatio","type":"enum","role":"style","label":"Media · Aspect","description":"Card aspect used when Fit is Crop.","default":"1:1","options":[{"value":"16:9","label":"16:9"},{"value":"9:16","label":"9:16"},{"value":"1:1","label":"1:1"},{"value":"4:5","label":"4:5"},{"value":"3:2","label":"3:2"},{"value":"3:4","label":"3:4"},{"value":"2:3","label":"2:3"}]},{"id":"backgroundType","type":"enum","role":"style","label":"Background · Type","description":"Gradient (start → end colours), flat Colour, or your own Image.","default":"gradient","options":[{"value":"gradient","label":"Gradient"},{"value":"color","label":"Color"},{"value":"image","label":"Image"}]},{"id":"backgroundGradientStart","type":"color","role":"style","label":"Background · Gradient start","description":"Top colour of the gradient.","default":"#111111"},{"id":"backgroundGradientEnd","type":"color","role":"style","label":"Background · Gradient end","description":"Bottom colour of the gradient.","default":"#444444"},{"id":"backgroundColor","type":"color","role":"style","label":"Background · Color","description":"Flat colour when Type is Color; also the ground under an Image.","default":"#0b0b0f"},{"id":"backgroundImage","type":"image","role":"style","label":"Background · Image","description":"Cover-fitted background when Type is Image.","portrays":["background_image"],"default":""},{"id":"blurAmount","type":"number","role":"style","label":"Background · Blur","description":"Blur on the background image, px.","default":0,"min":0,"max":60,"step":1,"unit":"px"},{"id":"backgroundGradientNoise","type":"number","role":"style","label":"Background · Grain","description":"Film-grain overlay strength.","default":0,"min":0,"max":100,"step":1,"unit":"%"},{"id":"backgroundOpacity","type":"number","role":"style","label":"Background · Opacity","description":"Opacity of the whole background layer (0 = transparent ground).","default":1,"min":0,"max":1,"step":0.05},{"id":"borderRadius","type":"number","role":"style","label":"Appearance · Border radius","description":"Card corner radius in px at 1080.","default":24,"min":0,"max":200,"step":1,"unit":"px"},{"id":"cycles","type":"number","role":"style","label":"Animation · Cycles","description":"Number of motion cycles per loop.","default":1,"min":0,"max":10,"step":0.1},{"id":"cycleDeg","type":"number","role":"style","label":"Animation · Cycle Deg","description":"Degrees turned per cycle.","default":360,"min":-180,"max":180,"step":1,"unit":"°"},{"id":"direction","type":"enum","role":"style","label":"Animation · Direction","description":"Direction of travel.","default":"forward","options":[{"value":"forward","label":"Forward"},{"value":"reverse","label":"Reverse"}]},{"id":"foredge","type":"enum","role":"style","label":"Template options · Foredge","description":"Foredge (engine parameter; see the block header comment).","default":"lines","options":[{"value":"lines","label":"Lines"},{"value":"image","label":"Image"}]},{"id":"finish","type":"enum","role":"style","label":"Template options · Finish","description":"Finish (engine parameter; see the block header comment).","default":"matte","options":[{"value":"flat","label":"Flat"},{"value":"matte","label":"Matte"},{"value":"glossy","label":"Glossy"}]},{"id":"planeSize","type":"number","role":"style","label":"Template options · Media size","description":"Card size in scene units.","default":1450,"min":0,"max":2900,"step":10},{"id":"thickness","type":"number","role":"style","label":"Template options · Thickness","description":"Thickness (engine parameter; see the block header comment).","default":5,"min":0,"max":10,"step":0.1},{"id":"perspective","type":"number","role":"style","label":"Template options · Perspective","description":"Camera focal length: smaller = stronger perspective.","default":100,"min":0,"max":200,"step":1},{"id":"distance","type":"number","role":"style","label":"Template options · Distance","description":"Camera distance from the formation.","default":2200,"min":0,"max":4400,"step":10},{"id":"rotationX","type":"number","role":"style","label":"Template options · Rotation X","description":"Formation tilt about the horizontal axis, degrees.","default":0,"min":-180,"max":180,"step":1,"unit":"°"},{"id":"rotationY","type":"number","role":"style","label":"Template options · Rotation Y","description":"Formation turn about the vertical axis, degrees.","default":0,"min":-180,"max":180,"step":1,"unit":"°"},{"id":"rotationZ","type":"number","role":"style","label":"Template options · Rotation Z","description":"Formation roll about the view axis, degrees.","default":0,"min":-180,"max":180,"step":1,"unit":"°"},{"id":"offsetX","type":"number","role":"style","label":"Template options · Offset X","description":"Horizontal offset of the formation, % of width.","default":0,"min":-100,"max":100,"step":1,"unit":"%"},{"id":"offsetY","type":"number","role":"style","label":"Template options · Offset Y","description":"Vertical offset of the formation, % of height.","default":0,"min":-100,"max":100,"step":1,"unit":"%"},{"id":"lightIntensity","type":"number","role":"style","label":"Template options · Light Intensity","description":"Light Intensity (engine parameter; see the block header comment).","default":100,"min":0,"max":250,"step":10},{"id":"lightAngle","type":"number","role":"style","label":"Template options · Light Angle","description":"Light Angle (engine parameter; see the block header comment).","default":30,"min":0,"max":452,"step":10},{"id":"lightHeight","type":"number","role":"style","label":"Template options · Light Height","description":"Light Height (engine parameter; see the block header comment).","default":40,"min":0,"max":80,"step":1},{"id":"image1","type":"image","role":"content","label":"Media · Image 1","description":"Image on card 1 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image2","type":"image","role":"content","label":"Media · Image 2","description":"Image on card 2 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image3","type":"image","role":"content","label":"Media · Image 3","description":"Image on card 3 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image4","type":"image","role":"content","label":"Media · Image 4","description":"Image on card 4 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image5","type":"image","role":"content","label":"Media · Image 5","description":"Image on card 5 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image6","type":"image","role":"content","label":"Media · Image 6","description":"Image on card 6 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image7","type":"image","role":"content","label":"Media · Image 7","description":"Image on card 7 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"},{"id":"image8","type":"image","role":"content","label":"Media · Image 8","description":"Image on card 8 of 8. Any aspect; fitted per mediaFit.","portrays":["subject_image"],"default":"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E"}]'
|
||
>
|
||
<head>
|
||
<title>loop-book-3</title>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=1080, height=1080" />
|
||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
html,
|
||
body {
|
||
width: 1080px;
|
||
height: 1080px;
|
||
overflow: hidden;
|
||
background: #0b0b0f;
|
||
}
|
||
#loop-book-3-bg {
|
||
position: absolute;
|
||
inset: 0;
|
||
}
|
||
#loop-book-3-stage {
|
||
position: absolute;
|
||
inset: 0;
|
||
perspective-origin: 50% 50%;
|
||
}
|
||
#loop-book-3-book,
|
||
#loop-book-3-spin {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: 0;
|
||
height: 0;
|
||
transform-style: preserve-3d;
|
||
}
|
||
.face {
|
||
position: absolute;
|
||
backface-visibility: hidden;
|
||
overflow: hidden;
|
||
}
|
||
.face img {
|
||
display: block;
|
||
}
|
||
.shade,
|
||
.gloss {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
}
|
||
.shade {
|
||
background: #000;
|
||
}
|
||
.gloss {
|
||
background: radial-gradient(ellipse at 30% 30%, #fff 0%, rgba(255, 255, 255, 0) 60%);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div
|
||
id="loop-book-3-root"
|
||
data-composition-id="loop-book-3"
|
||
data-start="0"
|
||
data-width="1080"
|
||
data-height="1080"
|
||
data-duration="8"
|
||
>
|
||
<div
|
||
id="loop-book-3-loop"
|
||
class="clip"
|
||
data-start="0"
|
||
data-duration="8"
|
||
data-track-index="1"
|
||
data-layout-allow-overflow
|
||
>
|
||
<div id="loop-book-3-bg"></div>
|
||
<div id="loop-book-3-stage">
|
||
<div id="loop-book-3-book"><div id="loop-book-3-spin"></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
// Shared runtime for the the reference tool loop ports. Loaded by every family engine.
|
||
// Everything here is a pure function of the HyperFrames variables + t.
|
||
//
|
||
// Reference constants (measured on the 512x512 preview library, 2026-09-08):
|
||
// Marquee planeSize -> 0.3% of frame height per unit (7 variants, all 1.54px/unit @512)
|
||
// Marquee gap -> 0.2% of frame height per unit
|
||
// 3D perspective -> CSS focal length f = 1000px * (100/perspective) at H=1080
|
||
// (3D01/02/08/11: card widths & far-card spacing match to <3%)
|
||
// Both are calibrated on a SQUARE canvas; for 16:9 we scale by H. ponytail: if the
|
||
// dashboard 16:9 reference shows width-relative scaling, flip REF below to W.
|
||
window.RF = (() => {
|
||
// Variables are injected by the inline script via RF.use(): HyperFrames wraps INLINE scripts in a
|
||
// per-composition scope where getVariables() resolves; this external file only sees the host window.
|
||
const V = {},
|
||
MEDIA = [];
|
||
const use = (vars, media) => {
|
||
Object.assign(V, vars || {});
|
||
MEDIA.splice(0, MEDIA.length, ...(media || []));
|
||
};
|
||
const root = document.querySelector("[data-composition-id]");
|
||
const W = +root.dataset.width,
|
||
H = +root.dataset.height;
|
||
const REF = H;
|
||
const num = (k, d) => (V[k] !== undefined && V[k] !== "" && !isNaN(+V[k]) ? +V[k] : d);
|
||
const str = (k, d) => (V[k] !== undefined && V[k] !== "" ? String(V[k]) : d);
|
||
const on = (k, d) => /^(true|on|1)$/i.test(str(k, d ? "on" : "off")); // spec booleans arrive as "true"/"false", enums as on/off
|
||
|
||
function pick(n) {
|
||
n = Math.max(1, Math.min(MEDIA.length, n | 0));
|
||
return MEDIA.slice(0, n);
|
||
}
|
||
// aspect (w/h) a card takes for a media item under mediaFit + cropAspectRatio
|
||
function aspect(m, fit, cropAR) {
|
||
if (fit === "natural") return m.w / m.h;
|
||
const [a, b] = String(cropAR || "1:1")
|
||
.split(":")
|
||
.map(Number);
|
||
return a && b ? a / b : 1;
|
||
}
|
||
function card(m, w, h, radius) {
|
||
const el = document.createElement("div");
|
||
el.className = "card";
|
||
el.style.cssText = `position:absolute;width:${w}px;height:${h}px;overflow:hidden;border-radius:${radius}px;`;
|
||
const img = document.createElement("img");
|
||
img.src = m.src;
|
||
img.width = Math.round(w);
|
||
img.height = Math.round(h);
|
||
img.style.cssText = `display:block;width:${w}px;height:${h}px;object-fit:cover;`;
|
||
el.appendChild(img);
|
||
return el;
|
||
}
|
||
function background(el) {
|
||
// the editor's Background panel: type gradient | color | image, gradient start/end, grain (noise %), opacity, image blur
|
||
const type = str("backgroundType", "gradient");
|
||
const c = str("backgroundColor", "#0b0b0f");
|
||
el.style.background = c;
|
||
if (type === "gradient") {
|
||
const g1 = str("backgroundGradientStart", "#111111"),
|
||
g2 = str("backgroundGradientEnd", "#444444");
|
||
el.style.background = `linear-gradient(180deg, ${g1}, ${g2})`;
|
||
} else if (type === "image") {
|
||
const im = V.backgroundImage,
|
||
src = im && typeof im === "object" ? im.url || im.src || "" : im || "";
|
||
if (src) {
|
||
el.style.background = `${c} url("${src}") center / cover no-repeat`;
|
||
const b = num("blurAmount", 0);
|
||
if (b > 0) el.style.filter = `blur(${b}px)`;
|
||
}
|
||
}
|
||
el.style.opacity = String(Math.max(0, Math.min(1, num("backgroundOpacity", 1))));
|
||
const noise = num("backgroundGradientNoise", 0);
|
||
if (noise > 0) {
|
||
const g = document.createElement("div");
|
||
g.style.cssText = `position:absolute;inset:0;pointer-events:none;mix-blend-mode:overlay;opacity:${((noise / 100) * 0.7).toFixed(3)};background:url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'256'%20height%3D'256'%3E%3Cfilter%20id%3D'n'%3E%3CfeTurbulence%20type%3D'fractalNoise'%20baseFrequency%3D'0.9'%20numOctaves%3D'3'%20stitchTiles%3D'stitch'%20seed%3D'7'%2F%3E%3CfeColorMatrix%20values%3D'0%200%200%200%200.5%200%200%200%200%200.5%200%200%200%200%200.5%200%200%200%201%200'%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D'256'%20height%3D'256'%20filter%3D'url(%23n)'%2F%3E%3C%2Fsvg%3E") 0 0 / 256px 256px repeat`;
|
||
el.appendChild(g);
|
||
}
|
||
}
|
||
const expoInOut = (x) =>
|
||
x <= 0
|
||
? 0
|
||
: x >= 1
|
||
? 1
|
||
: x < 0.5
|
||
? Math.pow(2, 20 * x - 10) / 2
|
||
: (2 - Math.pow(2, -20 * x + 10)) / 2;
|
||
const frac = (x) => x - Math.floor(x);
|
||
return {
|
||
V,
|
||
use,
|
||
W,
|
||
H,
|
||
REF,
|
||
num,
|
||
str,
|
||
on,
|
||
MEDIA,
|
||
pick,
|
||
aspect,
|
||
card,
|
||
background,
|
||
expoInOut,
|
||
frac,
|
||
};
|
||
})();
|
||
</script>
|
||
<script>
|
||
(async () => {
|
||
// Book loop family. Spec says "a book opens and its pages turn", but all five
|
||
// previews show a CLOSED book (a slab: front cover = image 1, back cover = image 2, page-edge sides) spinning
|
||
// about its vertical axis at constant speed, cycles*cycleDeg per loop (= 360 on every variant), with a static
|
||
// rotationX/Y/Z tilt (Book04/05) and diffuse shading (+ a glossy highlight on Book04). Measured (2026-09-08):
|
||
// 3D-family world units: f = 1000px*(100/perspective)*H/1080, s = f/distance; planeSize 1450 -> 312px @512
|
||
// (fit W=312 from the slab silhouette, 16 frames); thickness 5 -> 16px = 5% of planeSize
|
||
// angle per 5 frames f0..75: 0.5/6.5/12/18.5/26/31.5/37/42/48.5/54.5/60/66/72/78/84/90 = uniform 1.2deg/f
|
||
// (180deg per 150 frames, 360 per 10s loop), the left edge comes toward the viewer first (rotateY +)
|
||
// face luma 78 frontal -> 67 at 60deg (both covers, ratio .86) => brightness = .72 + .28*cos
|
||
// Guesses: light direction from lightAngle (azimuth, 0 = from the camera) and lightHeight (elevation deg);
|
||
// lightIntensity scales the diffuse term (.28 at 125); glossy = a soft radial highlight faded by cos^8;
|
||
// foredge "lines" = thin repeating light/dark stripes, "image" = the cover image on the edges; spine =
|
||
// dark cover colour; spineWidth/delay unused; natural fit: planeSize = height, width from the aspect.
|
||
// media list [{src,w,h}] is inlined by build.mjs; HyperFrames scopes inline scripts, so hand both to the shared lib explicitly
|
||
const __V = Object.assign(
|
||
{},
|
||
(window.__hyperframes && window.__hyperframes.getVariables
|
||
? window.__hyperframes.getVariables()
|
||
: null) || {},
|
||
{ loopDuration: 8, loopRepeat: 2, imageCount: 8 },
|
||
);
|
||
const __PH = [
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1050' viewBox%3D'0 0 1400 1050'%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1050' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1046' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 778.05H621.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M286.65 778.05L379.05 662.55L471.45 778.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'552.3' cy%3D'674.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'788' viewBox%3D'0 0 1400 788'%3E%3Crect width%3D'1400' height%3D'788' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'788' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'784' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 583.908H529.376' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'12.382857142857144' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M278.004 583.908L347.348 497.228L416.692 583.908Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'477.36800000000005' cy%3D'505.896' r%3D'26.004' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1050' height%3D'1400' viewBox%3D'0 0 1050 1400'%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1050' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1046' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M189 995.05H558.6' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'16.5' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M223.65 995.05L316.05 879.55L408.45 995.05Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'489.3' cy%3D'891.1' r%3D'34.65' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
"data:image/svg+xml,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'1400' height%3D'1400' viewBox%3D'0 0 1400 1400'%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'%230b0b0f'%2F%3E%3Crect width%3D'1400' height%3D'1400' fill%3D'rgba(255%2C255%2C255%2C0.18)'%2F%3E%3Crect x%3D'2' y%3D'2' width%3D'1396' height%3D'1396' fill%3D'none' stroke%3D'rgba(255%2C255%2C255%2C0.14)' stroke-width%3D'4'%2F%3E%3Cpath d%3D'M252 1037.4H744.8' stroke%3D'rgba(255%2C255%2C255%2C0.45)' stroke-width%3D'22' stroke-linecap%3D'round'%2F%3E%3Cpath d%3D'M298.2 1037.4L421.4 883.4L544.5999999999999 1037.4Z' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3Ccircle cx%3D'652.4000000000001' cy%3D'898.8' r%3D'46.199999999999996' fill%3D'rgba(255%2C255%2C255%2C0.45)'%2F%3E%3C%2Fsvg%3E",
|
||
];
|
||
const __MEDIA = await Promise.all(
|
||
["image1", "image2", "image3", "image4", "image5", "image6", "image7", "image8"].map(
|
||
async (id, i) => {
|
||
const v = __V[id];
|
||
const src = (v && typeof v === "object" ? v.url || v.src : v) || __PH[i];
|
||
const img = new Image();
|
||
img.src = src;
|
||
try {
|
||
await img.decode();
|
||
} catch (e) {}
|
||
return { src, w: img.naturalWidth || 1400, h: img.naturalHeight || 1050 };
|
||
},
|
||
),
|
||
);
|
||
RF.use(__V, __MEDIA);
|
||
const { W, H, REF, num, str, pick, aspect, background } = RF;
|
||
const DUR = num("loopDuration", 10);
|
||
const FIT = str("mediaFit", "crop");
|
||
const CROP_AR = str("cropAspectRatio", "1:1");
|
||
const FOREDGE = str("foredge", "lines");
|
||
const FINISH = str("finish", "matte");
|
||
const PLANE = num("planeSize", 1450);
|
||
const THICK = num("thickness", 5) / 100;
|
||
const PERSP = Math.max(1, num("perspective", 100));
|
||
const D = Math.max(1, num("distance", 2200));
|
||
const RX = num("rotationX", 0),
|
||
RY = num("rotationY", 0),
|
||
RZ = num("rotationZ", 0);
|
||
const OX = num("offsetX", 0),
|
||
OY = num("offsetY", 0);
|
||
const LI = num("lightIntensity", 125) / 125;
|
||
const LAZ = (num("lightAngle", 0) * Math.PI) / 180,
|
||
LEL = (num("lightHeight", 40) * Math.PI) / 180;
|
||
const TOTAL = num("cycles", 2) * num("cycleDeg", 180);
|
||
const FWD = str("direction", "forward") === "forward" ? 1 : -1;
|
||
const RADIUS = (num("borderRadius", 24) * REF) / 1080;
|
||
const IMAGES = pick(num("imageCount", 16));
|
||
|
||
document.getElementById("loop-book-3-loop").dataset.duration = DUR;
|
||
background(document.getElementById("loop-book-3-bg"));
|
||
const f = 1000 * (100 / PERSP) * (REF / 1080),
|
||
s = f / D;
|
||
document.getElementById("loop-book-3-stage").style.perspective = f + "px";
|
||
const book = document.getElementById("loop-book-3-book"),
|
||
spin = document.getElementById("loop-book-3-spin");
|
||
book.style.transform = `translate3d(${(OX / 100) * REF}px,${(OY / 100) * REF}px,0) rotateX(${RX}deg) rotateY(${RY}deg) rotateZ(${RZ}deg)`;
|
||
|
||
const a = aspect(IMAGES[0], FIT, CROP_AR);
|
||
const h = PLANE * s,
|
||
w = FIT === "natural" ? h * a : h * a,
|
||
t = THICK * h; // book size in px
|
||
// light direction (unit) in the book's spinning frame is recomputed per frame; here in view space
|
||
const L = [Math.sin(LAZ) * Math.cos(LEL), Math.sin(LEL), Math.cos(LAZ) * Math.cos(LEL)];
|
||
|
||
const faces = [];
|
||
function face(width, height, transform, fill) {
|
||
const el = document.createElement("div");
|
||
el.className = "face";
|
||
el.style.cssText = `width:${width}px;height:${height}px;left:${-width / 2}px;top:${-height / 2}px;transform:${transform};`;
|
||
if (fill.img) {
|
||
const img = document.createElement("img");
|
||
img.src = fill.img.src;
|
||
img.width = Math.round(width);
|
||
img.height = Math.round(height);
|
||
img.style.cssText = `display:block;width:${width}px;height:${height}px;object-fit:cover;`;
|
||
el.appendChild(img);
|
||
} else el.style.background = fill.css;
|
||
const sh = document.createElement("div");
|
||
sh.className = "shade";
|
||
el.appendChild(sh);
|
||
let gl = null;
|
||
if (FINISH === "glossy") {
|
||
gl = document.createElement("div");
|
||
gl.className = "gloss";
|
||
el.appendChild(gl);
|
||
}
|
||
if (fill.radius) el.style.borderRadius = fill.radius + "px";
|
||
spin.appendChild(el);
|
||
faces.push({ el, sh, gl, n: fill.n });
|
||
return el;
|
||
}
|
||
const lines = `repeating-linear-gradient(90deg, #d9d6cf 0 1px, #a9a49b 1px 2px)`;
|
||
const edge = (img) => (FOREDGE === "image" && img ? { img } : { css: lines });
|
||
const front = IMAGES[0],
|
||
back = IMAGES[1 % IMAGES.length];
|
||
face(w, h, `translateZ(${t / 2}px)`, { img: front, radius: RADIUS, n: [0, 0, 1] });
|
||
face(w, h, `rotateY(180deg) translateZ(${t / 2}px)`, {
|
||
img: back,
|
||
radius: RADIUS,
|
||
n: [0, 0, -1],
|
||
});
|
||
face(t, h, `rotateY(90deg) translateZ(${w / 2}px)`, { ...edge(front), n: [1, 0, 0] }); // foredge (right)
|
||
face(t, h, `rotateY(-90deg) translateZ(${w / 2}px)`, { css: "#2a2a2e", n: [-1, 0, 0] }); // spine (left)
|
||
Object.assign(
|
||
face(w, t, `rotateX(90deg) translateZ(${h / 2}px)`, { ...edge(front), n: [0, -1, 0] })
|
||
.style,
|
||
{ backgroundSize: "2px 100%" },
|
||
);
|
||
face(w, t, `rotateX(-90deg) translateZ(${h / 2}px)`, { ...edge(front), n: [0, 1, 0] });
|
||
for (const F of faces)
|
||
if (F.el.style.background.includes("repeating"))
|
||
F.el.style.backgroundImage = F.n[1] ? lines.replace("90deg", "90deg") : lines;
|
||
|
||
const rotY = (v, deg) => {
|
||
const r = (deg * Math.PI) / 180,
|
||
c = Math.cos(r),
|
||
si = Math.sin(r);
|
||
return [v[0] * c + v[2] * si, v[1], -v[0] * si + v[2] * c];
|
||
};
|
||
const rotX = (v, deg) => {
|
||
const r = (deg * Math.PI) / 180,
|
||
c = Math.cos(r),
|
||
si = Math.sin(r);
|
||
return [v[0], v[1] * c - v[2] * si, v[1] * si + v[2] * c];
|
||
};
|
||
const rotZ = (v, deg) => {
|
||
const r = (deg * Math.PI) / 180,
|
||
c = Math.cos(r),
|
||
si = Math.sin(r);
|
||
return [v[0] * c - v[1] * si, v[0] * si + v[1] * c, v[2]];
|
||
};
|
||
function renderAt(t_) {
|
||
const ang = FWD * TOTAL * t_;
|
||
spin.style.transform = `rotateY(${ang}deg)`;
|
||
for (const F of faces) {
|
||
// face normal in view space: static tilt (X, Y, Z) applied after the spin; CSS y points down -> flip y for lighting
|
||
let n = rotY(F.n, ang);
|
||
n = rotX(n, RX);
|
||
n = rotY(n, RY);
|
||
n = rotZ(n, RZ);
|
||
const nl = Math.max(0, n[0] * L[0] + -n[1] * L[1] + n[2] * L[2]);
|
||
const bright = FINISH === "flat" ? 1 : 0.72 + 0.28 * LI * nl;
|
||
F.sh.style.opacity = Math.max(0, Math.min(0.8, 1 - bright));
|
||
if (F.gl) F.gl.style.opacity = 0.55 * Math.pow(nl, 8);
|
||
}
|
||
}
|
||
// one paused linear tween drives t; every frame is recomputed from t alone (seek-safe)
|
||
const state = { t: 0 };
|
||
const tl = gsap.timeline({ paused: true });
|
||
tl.to(state, { t: 1, duration: DUR, ease: "none", onUpdate: () => renderAt(state.t) }, 0);
|
||
renderAt(0);
|
||
window.__timelines = window.__timelines || {};
|
||
window.__timelines["loop-book-3"] = tl;
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
</Accordion>
|
||
|
||
{/* hf:generated-footer */}
|
||
|
||
Tagged `loop` `images` `showcase` `gallery` `book` `3d`.
|
||
|
||
Created by Miao Yang.
|
||
|
||
## Related topics
|
||
|
||
- [Browse the complete Catalog](/catalog)
|
||
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
|
||
- [Build a richer composition](/go-further)
|