hyperframes 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +674 -249
- package/dist/hyperframe-runtime.js +5 -5
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +5 -5
- package/dist/skills/hyperframes/SKILL.md +76 -5
- package/dist/skills/hyperframes/house-style.md +1 -1
- package/dist/skills/hyperframes/references/captions.md +1 -1
- package/dist/skills/hyperframes/references/examples.md +1 -1
- package/dist/skills/hyperframes/references/fonts.md +134 -0
- package/dist/skills/hyperframes/references/motion-principles.md +69 -0
- package/dist/skills/hyperframes/references/transitions/catalog.md +2 -2
- package/package.json +1 -1
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
style="
|
|
50
50
|
position: absolute; inset: 0;
|
|
51
51
|
display: flex; align-items: center; justify-content: center;
|
|
52
|
-
font-family: '
|
|
52
|
+
font-family: 'Bricolage Grotesque', sans-serif; font-size: 72px; color: #fff;
|
|
53
53
|
background: #111;
|
|
54
54
|
"
|
|
55
55
|
>
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
The compiler embeds supported fonts — just write `font-family` in CSS.
|
|
4
|
+
|
|
5
|
+
## Banned
|
|
6
|
+
|
|
7
|
+
Inter, Roboto, Open Sans, Noto Sans, Arimo, Lato, Source Sans, PT Sans, Nunito, Poppins, Outfit, Sora, Playfair Display, Cormorant Garamond, Bodoni Moda, EB Garamond, Cinzel, Prata
|
|
8
|
+
|
|
9
|
+
## Guardrails
|
|
10
|
+
|
|
11
|
+
You know these rules but you violate them. Stop.
|
|
12
|
+
|
|
13
|
+
- **Don't pair two sans-serifs.** You do this constantly — one for headlines, one for body. Cross the boundary: serif + sans, or sans + mono.
|
|
14
|
+
- **One expressive font per scene.** You pick two interesting fonts trying to make it "better." One performs, one recedes.
|
|
15
|
+
- **Weight contrast must be extreme.** You default to 400 vs 700. Video needs 300 vs 900. The difference must be visible in motion at a glance.
|
|
16
|
+
- **Video sizes, not web sizes.** Body: 20px minimum. Headlines: 60px+. Data labels: 16px. You will try to use 14px. Don't.
|
|
17
|
+
|
|
18
|
+
## What You Don't Do Without Being Told
|
|
19
|
+
|
|
20
|
+
- **Tension should mean something.** Don't pattern-match pairings. Ask WHY these two fonts disagree. The pairing should embody the content's contradiction — mechanical vs human, public vs private, institutional vs personal. If you can't articulate the tension, it's arbitrary.
|
|
21
|
+
- **Register switching.** Assign different fonts to different communicative modes — one voice for statements, another for data, another for attribution. Not hierarchy on a page. Voices in a conversation.
|
|
22
|
+
- **Tension can live inside a single font.** A font that looks familiar but is secretly strange creates tension with the viewer's expectations, not with another font.
|
|
23
|
+
- **One variable changed = dramatic contrast.** Same letterforms, monospaced vs proportional. Same family at different optical sizes. Changing only rhythm while everything else stays constant.
|
|
24
|
+
- **Double personality works.** Two expressive fonts can coexist if they share an attitude (both irreverent, both precise) even when their forms are completely different.
|
|
25
|
+
- **Time is hierarchy.** The first element to appear is the most important. In video, sequence replaces position.
|
|
26
|
+
- **Motion is typography.** How a word enters carries as much meaning as the font. A 0.1s slam vs a 2s fade — same font, completely different message.
|
|
27
|
+
- **Fixed reading time.** 3 seconds on screen = must be readable in 2. Fewer words, larger type.
|
|
28
|
+
- **Tracking tighter than web.** -0.03em to -0.05em on display sizes. Video encoding compresses letter detail.
|
|
29
|
+
|
|
30
|
+
## Finding Fonts
|
|
31
|
+
|
|
32
|
+
Don't default to what you know. If the content is luxury, a grotesque sans might create more tension than the expected Didone serif. Decide the register first, then search.
|
|
33
|
+
|
|
34
|
+
Save this script to `/tmp/fontquery.py` and run with `curl -s 'https://fonts.google.com/metadata/fonts' > /tmp/gfonts.json && python3 /tmp/fontquery.py /tmp/gfonts.json`:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import json, sys
|
|
38
|
+
from collections import OrderedDict
|
|
39
|
+
|
|
40
|
+
with open(sys.argv[1]) as f:
|
|
41
|
+
data = json.load(f)
|
|
42
|
+
fonts = data.get("familyMetadataList", [])
|
|
43
|
+
|
|
44
|
+
ban = {"Inter","Roboto","Open Sans","Noto Sans","Lato","Poppins","Source Sans 3",
|
|
45
|
+
"PT Sans","Nunito","Outfit","Sora","Playfair Display","Cormorant Garamond",
|
|
46
|
+
"Bodoni Moda","EB Garamond","Cinzel","Prata","Arimo","Source Sans Pro"}
|
|
47
|
+
skip_pfx = ("Roboto","Noto ","Google Sans","Bpmf","Playwrite","Anek","BIZ ",
|
|
48
|
+
"Nanum","Shippori","Sawarabi","Zen ","Kaisei","Kiwi ","Yuji ","Radio ")
|
|
49
|
+
|
|
50
|
+
def ok(f):
|
|
51
|
+
if f["family"] in ban: return False
|
|
52
|
+
if any(f["family"].startswith(b) for b in skip_pfx): return False
|
|
53
|
+
if "latin" not in (f.get("subsets") or []): return False
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
seen = set()
|
|
57
|
+
R = OrderedDict()
|
|
58
|
+
|
|
59
|
+
# Trending Sans — recent (2022+), popular (<300)
|
|
60
|
+
R["Trending Sans"] = []
|
|
61
|
+
for f in fonts:
|
|
62
|
+
if not ok(f) or f["family"] in seen: continue
|
|
63
|
+
if f.get("category") in ("Sans Serif","Display") and f.get("dateAdded","") >= "2022-01-01" and f.get("popularity",9999) < 300:
|
|
64
|
+
R["Trending Sans"].append(f); seen.add(f["family"])
|
|
65
|
+
|
|
66
|
+
# Trending Serif — recent (2018+), popular (<600)
|
|
67
|
+
R["Trending Serif"] = []
|
|
68
|
+
for f in fonts:
|
|
69
|
+
if not ok(f) or f["family"] in seen: continue
|
|
70
|
+
if f.get("category") == "Serif" and f.get("dateAdded","") >= "2018-01-01" and f.get("popularity",9999) < 600:
|
|
71
|
+
R["Trending Serif"].append(f); seen.add(f["family"])
|
|
72
|
+
|
|
73
|
+
# Monospace — recent (2018+), popular (<600)
|
|
74
|
+
R["Monospace"] = []
|
|
75
|
+
for f in fonts:
|
|
76
|
+
if not ok(f) or f["family"] in seen: continue
|
|
77
|
+
if f.get("category") == "Monospace" and f.get("dateAdded","") >= "2018-01-01" and f.get("popularity",9999) < 600:
|
|
78
|
+
R["Monospace"].append(f); seen.add(f["family"])
|
|
79
|
+
|
|
80
|
+
# Impact & Condensed — curated names + heavy display fonts
|
|
81
|
+
R["Impact & Condensed"] = []
|
|
82
|
+
impact = {"Bebas Neue","Archivo Black","Big Shoulders Display","Teko","League Gothic",
|
|
83
|
+
"Barlow Condensed","Staatliches","Anton","Oswald","Saira","Syne",
|
|
84
|
+
"Titillium Web","Alumni Sans","Advent Pro"}
|
|
85
|
+
for f in fonts:
|
|
86
|
+
if not ok(f) or f["family"] in seen: continue
|
|
87
|
+
is_impact = f["family"] in impact
|
|
88
|
+
is_heavy_display = ("Display" in (f.get("classifications") or [])
|
|
89
|
+
and any(k in list(f.get("fonts",{}).keys()) for k in ("800","900"))
|
|
90
|
+
and f.get("popularity",9999) < 400
|
|
91
|
+
and f.get("category") in ("Sans Serif","Display"))
|
|
92
|
+
if is_impact or is_heavy_display:
|
|
93
|
+
R["Impact & Condensed"].append(f); seen.add(f["family"])
|
|
94
|
+
|
|
95
|
+
# Bold Geometric Display — curated
|
|
96
|
+
R["Bold Geometric Display"] = []
|
|
97
|
+
for f in fonts:
|
|
98
|
+
if not ok(f) or f["family"] in seen: continue
|
|
99
|
+
if f["family"] in {"DM Serif Display","Abril Fatface","Righteous","Orbitron","Black Ops One"}:
|
|
100
|
+
R["Bold Geometric Display"].append(f); seen.add(f["family"])
|
|
101
|
+
|
|
102
|
+
# Script & Handwriting — popular (<300)
|
|
103
|
+
R["Script & Handwriting"] = []
|
|
104
|
+
for f in fonts:
|
|
105
|
+
if not ok(f) or f["family"] in seen: continue
|
|
106
|
+
if f.get("category") == "Handwriting" and f.get("popularity",9999) < 300:
|
|
107
|
+
R["Script & Handwriting"].append(f); seen.add(f["family"])
|
|
108
|
+
|
|
109
|
+
# Established Classics — good older fonts
|
|
110
|
+
R["Established Classics"] = []
|
|
111
|
+
classics = {"Josefin Sans","Raleway","Montserrat","Abel","Exo","Red Hat Display",
|
|
112
|
+
"Rubik","Alegreya","Arvo","Besley","Crimson Text","Fraunces",
|
|
113
|
+
"Lora","Merriweather","Vollkorn"}
|
|
114
|
+
for f in fonts:
|
|
115
|
+
if f["family"] in classics and f["family"] not in seen:
|
|
116
|
+
R["Established Classics"].append(f); seen.add(f["family"])
|
|
117
|
+
|
|
118
|
+
# Print
|
|
119
|
+
for cat in R:
|
|
120
|
+
R[cat].sort(key=lambda x: x.get("popularity",9999))
|
|
121
|
+
limits = {"Trending Sans":15,"Trending Serif":12,"Monospace":8,
|
|
122
|
+
"Impact & Condensed":12,"Bold Geometric Display":8,
|
|
123
|
+
"Script & Handwriting":10,"Established Classics":20}
|
|
124
|
+
for cat in R:
|
|
125
|
+
items = R[cat][:limits.get(cat,10)]
|
|
126
|
+
if not items: continue
|
|
127
|
+
print(f"--- {cat} ({len(items)}) ---")
|
|
128
|
+
for ff in items:
|
|
129
|
+
var = "VAR" if ff.get("axes") else " "
|
|
130
|
+
print(f' {ff.get("popularity"):4d} | {var} | {ff["family"]}')
|
|
131
|
+
print()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Seven categories: trending sans, trending serif, monospace, impact/condensed, bold geometric, script/handwriting, and established classics. Cross classification boundaries when pairing.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Motion Principles
|
|
2
|
+
|
|
3
|
+
## Guardrails
|
|
4
|
+
|
|
5
|
+
You know these rules but you violate them. Stop.
|
|
6
|
+
|
|
7
|
+
- **Don't use the same ease on every tween.** You default to `power2.out` on everything. Vary eases like you vary font weights — no more than 2 independent tweens with the same ease in a scene.
|
|
8
|
+
- **Don't use the same speed on everything.** You default to 0.4-0.5s for everything. The slowest scene should be 3× slower than the fastest. Vary duration deliberately.
|
|
9
|
+
- **Don't enter everything from the same direction.** You default to `y: 30, opacity: 0` on every element. Vary: from left, from right, from scale, opacity-only, letter-spacing.
|
|
10
|
+
- **Don't use the same stagger on every scene.** Each scene needs its own rhythm.
|
|
11
|
+
- **Don't use ambient zoom on every scene.** Pick different ambient motion per scene: slow pan, subtle rotation, scale push, color shift, or nothing. Stillness after motion is powerful.
|
|
12
|
+
- **Don't start at t=0.** Offset the first animation 0.1-0.3s. Zero-delay feels like a jump cut.
|
|
13
|
+
|
|
14
|
+
## What You Don't Do Without Being Told
|
|
15
|
+
|
|
16
|
+
### Easing is emotion, not technique
|
|
17
|
+
|
|
18
|
+
The transition is the verb. The easing is the adverb. A slide-in with `expo.out` = confident. With `sine.inOut` = dreamy. With `elastic.out` = playful. Same motion, different meaning. Choose the adverb deliberately.
|
|
19
|
+
|
|
20
|
+
**Direction rules — these are not optional:**
|
|
21
|
+
|
|
22
|
+
- `.out` for elements entering. Starts fast, decelerates. Feels responsive. This is your default.
|
|
23
|
+
- `.in` for elements leaving. Starts slow, accelerates away. Throws them off.
|
|
24
|
+
- `.inOut` for elements moving between positions.
|
|
25
|
+
|
|
26
|
+
You get this backwards constantly. Ease-in for entrances feels sluggish. Ease-out for exits feels reluctant.
|
|
27
|
+
|
|
28
|
+
### Speed communicates weight
|
|
29
|
+
|
|
30
|
+
- Fast (0.15-0.3s) — energy, urgency, confidence
|
|
31
|
+
- Medium (0.3-0.5s) — professional, most content
|
|
32
|
+
- Slow (0.5-0.8s) — gravity, luxury, contemplation
|
|
33
|
+
- Very slow (0.8-2.0s) — cinematic, emotional, atmospheric
|
|
34
|
+
|
|
35
|
+
### Scene structure: build / breathe / resolve
|
|
36
|
+
|
|
37
|
+
Every scene has three phases. You dump everything in the build and leave nothing for breathe or resolve.
|
|
38
|
+
|
|
39
|
+
- **Build (0-30%)** — elements enter, staggered. Don't dump everything at once.
|
|
40
|
+
- **Breathe (30-70%)** — content visible, alive with ONE ambient motion.
|
|
41
|
+
- **Resolve (70-100%)** — exit or decisive end. Exits are faster than entrances.
|
|
42
|
+
|
|
43
|
+
### Transitions are meaning
|
|
44
|
+
|
|
45
|
+
- **Crossfade** = "this continues"
|
|
46
|
+
- **Hard cut** = "wake up" / disruption
|
|
47
|
+
- **Slow dissolve** = "drift with me"
|
|
48
|
+
|
|
49
|
+
You crossfade everything. Use hard cuts for disruption and register shifts.
|
|
50
|
+
|
|
51
|
+
### Choreography is hierarchy
|
|
52
|
+
|
|
53
|
+
The element that moves first is perceived as most important. Stagger in order of importance, not DOM order. Don't wait for completion — overlap entries. Total stagger sequence under 500ms regardless of item count.
|
|
54
|
+
|
|
55
|
+
### Asymmetry
|
|
56
|
+
|
|
57
|
+
Entrances need longer than exits. A card takes 0.4s to appear but 0.25s to disappear.
|
|
58
|
+
|
|
59
|
+
## Visual Composition
|
|
60
|
+
|
|
61
|
+
You build for the web. Video frames are not pages.
|
|
62
|
+
|
|
63
|
+
- **Two focal points minimum per scene.** The eye needs somewhere to travel. Never a single text block floating in empty space.
|
|
64
|
+
- **Fill the frame.** Hero text: 60-80% of width. You will try to use web-sized elements. Don't.
|
|
65
|
+
- **Three layers minimum per scene.** Background treatment (glow, oversized faded type, color panel). Foreground content. Accent elements (dividers, labels, data bars).
|
|
66
|
+
- **Background is not empty.** Radial glows, oversized faded type bleeding off-frame, subtle border panels, hairline rules. Pure solid #000 reads as "nothing loaded."
|
|
67
|
+
- **Anchor to edges.** Pin content to left/top or right/bottom. Centered-and-floating is a web pattern.
|
|
68
|
+
- **Split frames.** Data panel on the left, content on the right. Top bar with metadata, full-width below. Zone-based layouts, not centered stacks.
|
|
69
|
+
- **Use structural elements.** Rules, dividers, border panels. They create paths for the eye and animate well (scaleX from 0).
|
|
@@ -8,7 +8,7 @@ These cause real bugs if violated.
|
|
|
8
8
|
|
|
9
9
|
**Scene visibility:** Scene 1 visible by default (no `opacity: 0`). Scenes 2+ have `opacity: 0` on the CONTAINER div. GSAP reveals them. No visibility shim (`timedEls`).
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Fonts:** Just write the `font-family` you want — the compiler embeds supported fonts automatically via `@font-face` with inline data URIs. No need for `<link>` tags or `@import`. Works in all contexts including sandboxed iframes.
|
|
12
12
|
|
|
13
13
|
**Element structure:** No `class="clip"` on scene divs in standalone compositions. Only the root div gets `data-composition-id`/`data-start`/`data-duration`.
|
|
14
14
|
|
|
@@ -57,7 +57,7 @@ Read [shader-setup.md](./shader-setup.md) for the full setup code these rules ap
|
|
|
57
57
|
height: 1080px;
|
|
58
58
|
overflow: hidden;
|
|
59
59
|
background: #000;
|
|
60
|
-
font-family:
|
|
60
|
+
font-family: "YOUR FONT", sans-serif; /* compiler embeds supported fonts automatically */
|
|
61
61
|
}
|
|
62
62
|
.scene {
|
|
63
63
|
position: absolute;
|