Setting the file. One moment.
Caption Skin · Hyperframes Creative · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page 22.10
Narration
(opens in a new tab)
frame-presets/coral/ caption-skin.html
HTML · 226 lines · 8 KB
15
--cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
16 --font-body · --cap-band-top · --cap-band-height
17 Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
18 root data-composition-id="captions", timeline at window.__timelines["captions"].
19 State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
20 never tl.call() callbacks (seek does not fire them → wrong state on render).
21
22 Visual: a flat warm-cream block on a single ink hairline border — coral's hard-edge
23 geometry, square corners, ZERO radius, ZERO shadow (coral's depth is hard color edges,
24 never cast). Bebas Neue, uppercase + tracked, ink glyphs. Upcoming words sit in faint
25 ink; the current word gets a solid coral-fire flat block (the brand's signature
26 hard-edge fill — a square block, no blur, no radius) with ink reading on top; spoken
27 words settle to solid ink, fill cleared. Brand/key terms carry a coral hairline
28 underline (the counter inflection).
29 -->
30 < script src = "https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js" ></ script >
31
32 <!-- captions.mjs injects the project's :root tokens here -->
33 < style data-brand-tokens ></ style >
34
35 < style >
36 #captions-root {
37 position : absolute ;
38 inset : 0 ;
39 pointer-events : none ;
40 }
41 .caption-layer {
42 position : absolute ;
43 inset : 0 ;
44 z-index : 20 ;
45 pointer-events : none ;
46 }
47 /* the keep-out band, from captionBand(H) — frame content stays above it */
48 .caption-stage {
49 position : absolute ;
50 left : 0 ;
51 right : 0 ;
52 top : var ( --cap-band-top , 900 px );
53 height : var ( --cap-band-height , 180 px );
54 display : flex ;
55 align-items : center ;
56 justify-content : center ;
57 }
58 .caption-group {
59 /* every group is absolutely stacked over the SAME band — exactly one shown at a
60 time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
61 position : absolute ;
62 inset : 0 ;
63 display : flex ;
64 align-items : center ;
65 justify-content : center ;
66 opacity : 0 ;
67 }
68
69 /* THE flat cream block — single ink hairline border, square corners (coral's hard-edge
70 geometry), ZERO radius, ZERO shadow */
71 .caption-pill {
72 max-width : 78 % ;
73 padding : 18 px 44 px 20 px ;
74 background : var ( --cap-canvas , #f5f0e8 );
75 border : 1 px solid var ( --cap-ink , #1a1a1a );
76 border-radius : 0 ;
77 }
78 .caption-line {
79 display : flex ;
80 flex-wrap : wrap ;
81 justify-content : center ;
82 gap : 0.1 em 0.3 em ;
83 font-family : var ( --font-display , "Bebas Neue" ), sans-serif ;
84 font-weight : 400 ;
85 text-transform : uppercase ;
86 font-size : clamp ( 44 px , 4.8 vw , 64 px );
87 line-height : 1.04 ;
88 letter-spacing : 0.06 em ;
89 }
90
91 /* upcoming — faint ink, no fill */
92 .caption-word {
93 display : inline-block ;
94 padding : 0 0.12 em ;
95 color : color-mix ( in srgb , var ( --cap-ink , #1a1a1a ) 40 % , var ( --cap-canvas , #f5f0e8 ));
96 }
97 /* current word — THE coral-fire flat block (box-shadow spread = a hard square block
98 w/o layout shift; no blur, no radius), ink reading on top */
99 .caption-word.is-active {
100 color : var ( --cap-ink , #1a1a1a );
101 background : var ( --cap-accent , #e85d5d );
102 box-shadow : 0 0 0 0.1 em var ( --cap-accent , #e85d5d );
103 }
104 /* already spoken — solid ink, fill cleared */
105 .caption-word.is-spoken {
106 color : var ( --cap-ink , #1a1a1a );
107 background : transparent ;
108 box-shadow : none ;
109 }
110
111 /* optional per-word accents (dormant until a word-classer tags them) */
112 /* brand / key term — a coral hairline underline (coral's only display inflection) */
113 .caption-word.cap-brand {
114 text-decoration : underline ;
115 text-decoration-thickness : 0.06 em ;
116 text-decoration-color : var ( --cap-accent , #e85d5d );
117 text-underline-offset : 0.12 em ;
118 }
119 /* numerals / data — Inter body voice, slightly recessed */
120 .caption-word.cap-num {
121 font-family : var ( --font-body , "Inter" ), ui-sans-serif , sans-serif ;
122 color : color-mix ( in srgb , var ( --cap-ink , #1a1a1a ) 72 % , var ( --cap-canvas , #f5f0e8 ));
123 }
124 /* emphasis — a coral-dark hairline underline (the counter accent) */
125 .caption-word.cap-cta {
126 text-decoration : underline ;
127 text-decoration-thickness : 2 px ;
128 text-decoration-color : var ( --cap-accent-2 , #d44a4a );
129 text-underline-offset : 0.1 em ;
130 }
131
132 @media ( max-aspect-ratio : 9 / 16 ) {
133 .caption-pill {
134 max-width : 92 % ;
135 }
136 .caption-line {
137 font-size : clamp ( 38 px , 5.8 vw , 54 px );
138 }
139 }
140 </ style >
141
142 < div
143 id = "captions-root"
144 data-composition-id = "captions"
145 data-timeline-locked
146 data-start = "0"
147 data-duration = "0"
148 data-fps = "30"
149 data-width = "0"
150 data-height = "0"
151 >
152 < div class = "caption-layer" aria-hidden = "true" >
153 < div id = "caption-stage" class = "caption-stage" ></ div >
154 </ div >
155 </ div >
156
157 < script >
158 // captions.mjs injects the engine's scene-aware groups + real duration here.
159 var GROUPS = [];
160 var DURATION = 0 ;
161
162 ( function () {
163 var stage = document. getElementById ( "caption-stage" );
164
165 // build DOM: one .caption-group per group, .caption-word per word
166 GROUPS . forEach ( function ( group , g ) {
167 var groupEl = document. createElement ( "div" );
168 groupEl.className = "caption-group" ;
169 groupEl.id = "caption-group-" + g;
170 var pill = document. createElement ( "div" );
171 pill.className = "caption-pill" ;
172 var line = document. createElement ( "div" );
173 line.className = "caption-line" ;
174 (group.words || []). forEach ( function ( w , i ) {
175 var span = document. createElement ( "span" );
176 span.className = "caption-word" ;
177 span.id = "caption-word-" + g + "-" + i;
178 span.textContent = String (w.text);
179 line. appendChild (span);
180 });
181 pill. appendChild (line);
182 groupEl. appendChild (pill);
183 stage. appendChild (groupEl);
184 });
185
186 window.__timelines = window.__timelines || {};
187 var tl = gsap. timeline ({ paused: true });
188
189 GROUPS . forEach ( function ( group , g ) {
190 var groupEl = document. getElementById ( "caption-group-" + g);
191 var words = group.words || [];
192 var next = GROUPS [g + 1 ];
193 var isLast = g === GROUPS . length - 1 ;
194 var start = Math. max ( 0 , Number (group.start));
195 var end = isLast ? DURATION : Math. min ( Number (next.start), Number (group.end) + 0.3 );
196 if (end <= start) end = start + 0.01 ;
197
198 // group on/off — exactly one group visible at a time
199 tl. set (groupEl, { opacity: 1 }, start);
200 tl. set (groupEl, { opacity: 0 }, end);
201
202 // 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
203 // engine's frame-by-frame seek; .call callbacks would NOT fire).
204 words. forEach ( function ( w , i ) {
205 var el = document. getElementById ( "caption-word-" + g + "-" + i);
206 var at = Math. max (start, Number (w.start));
207 tl. set (el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
208 tl. set (el, { className: "caption-word is-active" }, at); // current
209 tl. fromTo (el, { scale: 0.97 }, { scale: 1 , duration: 0.14 , ease: "power2.out" }, at);
210 if (i + 1 < words. length ) {
211 var nextAt = Math. max (start, Number (words[i + 1 ].start));
212 tl. set (el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
213 }
214 });
215 if (words. length ) {
216 var lastEl = document. getElementById ( "caption-word-" + g + "-" + (words. length - 1 ));
217 var lastSpoken = Math. min (end, Number (words[words. length - 1 ].end) + 0.1 );
218 tl. set (lastEl, { className: "caption-word is-spoken" }, lastSpoken);
219 }
220 });
221
222 // full-span anchor so the sub-comp timeline spans the whole video
223 tl. to ({}, { duration: DURATION }, 0 );
224 window.__timelines[ "captions" ] = tl;
225 })();
226 </ script >