Setting the file. One moment.
Tokens · Faceless Explainer · heygen-com/hyperframes · Skills Docs
ContentsBack to the top of the page const q
— line 185
This file
Number 15.21
Position 21 of 26
Type JavaScript
Size 10 KB
Lines 211 scripts/lib/ tokens.mjs
JavaScript · 211 lines · 10 KB
of
md.
split
(
/
\r
?
\n
/
)) {
11 if ( / ^ colors: \s *$ / . test (line)) {
12 inBlock = true ;
13 continue ;
14 }
15 if ( ! inBlock) continue ;
16 if ( / ^ \S / . test (line)) break ; // dedent to a top-level key → end of block
17 const m = line. match (
18 / ^ \s + ( [\w-] + ): \s * (?:"( [ ^ "] + )" | '( [ ^ '] + )' | (# [0-9a-fA-F] {3,8}| rgba ? \( [ ^ )] * \) | [ ^ #\s][ ^ #\n] *? )) \s * (?:# . * ) ?$ / ,
19 );
20 if (m) out. push ([m[ 1 ], (m[ 2 ] ?? m[ 3 ] ?? m[ 4 ]). trim ()]);
21 }
22 return out;
23 }
24
25 // relative luminance of a #rrggbb (null for non-hex like rgba()).
26 export function lum ( v ) {
27 const m = / ^ # ? ( [0-9a-fA-F] {6} ) $ / . exec ( String (v). trim ());
28 if ( ! m) return null ;
29 const n = parseInt (m[ 1 ], 16 );
30 return 0.2126 * ((n >> 16 ) & 255 ) + 0.7152 * ((n >> 8 ) & 255 ) + 0.0722 * (n & 255 );
31 }
32
33 // chroma (max−min channel) of a #rrggbb — a cheap "how colorful" proxy; −1 for non-hex.
34 export function chroma ( v ) {
35 const m = / ^ # ? ( [0-9a-fA-F] {6} ) $ / . exec ( String (v). trim ());
36 if ( ! m) return - 1 ;
37 const n = parseInt (m[ 1 ], 16 );
38 const r = (n >> 16 ) & 255 ,
39 g = (n >> 8 ) & 255 ,
40 b = n & 255 ;
41 return Math. max (r, g, b) - Math. min (r, g, b);
42 }
43
44 // Browser user-agent default colors for links / visited links. These leak into a
45 // capture from any UNSTYLED <a> and are NOT brand colors — but being pure & saturated
46 // they beat a real accent on chroma alone. Never let one become the accent.
47 export const UA_DEFAULT_COLORS = new Set (
48 [ "#0000EE" , "#0000FF" , "#0000CC" , "#1A0DAB" , "#551A8B" , "#EE0000" ]. map (( c ) => c. toUpperCase ()),
49 );
50
51 // Semantic STATUS roles (green "positive", red "negative"/"error", amber "warning" …). Their HUE
52 // carries the meaning, so they are never a brand ACCENT — a status red is frequently the most
53 // chromatic color in a palette (e.g. #dc2626 chroma 182 beats a deep-blue accent #1E40AF chroma
54 // 145) and would otherwise win a pure chroma ranking, painting captions/highlights the error red.
55 // build-frame.mjs uses this same key set to protect status colors during the preset→brand remix.
56 export const STATUS_ROLE_KEY =
57 /(?: ^| [-_] )(?:positive | negative | success | error | warning | danger | good | bad | up | down | info | neutral | alert | caution | critical)(?: [-_] |$ )/ i ;
58
59 // Pick the brand ACCENT — never by raw chroma alone, never a UA-default link color.
60 // Priority:
61 // 1) with capture colorStats → the colorful color that RECURS across the UI. The brand
62 // accent shows up in MANY roles (link text + icon + button + badge), whereas a one-off
63 // CTA fill appears in just one. So rank chromatic (chroma>40) candidates by role
64 // diversity first, then total prevalence, then interactive use, then chroma. This keeps
65 // a pervasive brand color (e.g. an indigo used everywhere) ahead of a single bright
66 // button fill (e.g. a lime used once) — the old "top interactiveBg" rule picked the
67 // latter. Requiring interactiveBg>0 is dropped so a text/icon-only accent can still win.
68 // 2) no stats → most chromatic color AFTER removing UA defaults + `exclude`.
69 // A stray default link color (e.g. #0000EE) can win under neither path.
70 export function pickAccent ( stats , colors , exclude = []) {
71 const ban = new Set ([ ... exclude, ... UA_DEFAULT_COLORS ]. map (( c ) => String (c). toUpperCase ()));
72 const ok = ( h ) => / ^ # [0-9a-fA-F] {6}$ / . test ( String (h)) && ! ban. has ( String (h). toUpperCase ());
73 // Prominence rank from the (frequency-ordered) `colors` palette: index 0 = most used.
74 // A saturated color sitting at the TAIL is almost always a one-off (a single CTA fill),
75 // not the brand accent — capture colorStats counts are too sparse to tell these apart
76 // (e.g. Linear's indigo and a lime CTA both register count≈1), but palette ORDER does.
77 const rank = new Map ((colors ?? []). map (( h , i ) => [ String (h). toUpperCase (), i]));
78 const prom = ( h ) => (rank. has ( String (h). toUpperCase ()) ? rank. get ( String (h). toUpperCase ()) : 1e9 );
79 if (Array. isArray (stats) && stats. length ) {
80 const roles = ( s ) =>
81 ((s.interactiveBg || 0 ) > 0 ? 1 : 0 ) +
82 ((s.textCount || 0 ) > 0 ? 1 : 0 ) +
83 ((s.bgCount || 0 ) > 0 ? 1 : 0 );
84 const a = stats
85 . filter (( s ) => ok (s?.hex) && chroma (s.hex) > 40 )
86 . sort (
87 ( x , y ) =>
88 roles (y) - roles (x) || // used in MORE roles (link+icon+button) = the brand accent
89 prom (x.hex) - prom (y.hex) || // earlier in the palette = more prominent
90 (y.count || 0 ) - (x.count || 0 ) ||
91 (y.interactiveBg || 0 ) - (x.interactiveBg || 0 ) ||
92 chroma (y.hex) - chroma (x.hex),
93 );
94 if (a. length ) return a[ 0 ].hex;
95 }
96 const c = (colors ?? [])
97 . map (String)
98 . filter (ok)
99 . sort (( x , y ) => chroma (y) - chroma (x));
100 return c[ 0 ];
101 }
102
103 // Derive brand roles from rich capture colorStats (areaBg / interactiveBg / textCount /
104 // maxArea) — by semantic FUNCTION, not luminance/chroma proxies. Returns null when stats
105 // are unusable, so the caller can fall back. canvas = the color painting the most real
106 // background area (the page ground, dark or light); ink = the dominant text color that
107 // actually contrasts with the canvas; accent via pickAccent.
108 export function brandRolesFromStats ( stats , colorsInOrder ) {
109 if ( ! Array. isArray (stats) || ! stats. length ) return null ;
110 const v = stats. filter (( s ) => / ^ # [0-9a-fA-F] {6}$ / . test (s?.hex || "" ));
111 if ( ! v. length ) return null ;
112 const canvas = [ ... v]. sort (
113 ( a , b ) =>
114 (b.areaBg || 0 ) - (a.areaBg || 0 ) ||
115 (b.maxArea || 0 ) - (a.maxArea || 0 ) ||
116 (b.bgCount || 0 ) - (a.bgCount || 0 ),
117 )[ 0 ]?.hex;
118 // pass the frequency-ordered palette (tokens.colors) so pickAccent can use palette
119 // PROMINENCE — colorStats counts alone are too sparse to rank rare accents.
120 const accent = pickAccent (v, colorsInOrder ?? v. map (( s ) => s.hex), [canvas]);
121 if ( ! canvas || ! accent) return null ;
122 const cl = lum (canvas) ?? 0 ;
123 const ink =
124 [ ... v]
125 . filter (( s ) => s.hex !== canvas && s.hex !== accent)
126 . sort (( a , b ) => (b.textCount || 0 ) - (a.textCount || 0 ))
127 . find (( s ) => Math. abs (( lum (s.hex) ?? 0 ) - cl) > 64 )?.hex ??
128 (cl > 128 ? "#000000" : "#FFFFFF" );
129 const accent2 =
130 pickAccent (v, colorsInOrder ?? v. map (( s ) => s.hex), [canvas, ink, accent]) ?? accent;
131 return { ink, canvas, accent, accent2 };
132 }
133
134 // Map a list of [key, value] colors to semantic roles. ink = a dark/ink-named
135 // color (else darkest); canvas = a paper/cream/white-named color (else lightest);
136 // accents = whatever's left, ranked by chroma (the loudest color is almost always
137 // the brand accent) — UA-default link colors AND semantic status colors (positive/
138 // negative/error…) excluded so neither a stray <a> color nor a status red ever wins.
139 // For an unkeyed brand list, pass synthetic keys — name matching simply no-ops and it
140 // falls back to luminance/chroma, which is what we want. NOTE: when capture colorStats
141 // exist, prefer brandRolesFromStats() — it picks by function, not these proxies.
142 export function semanticColors ( colors ) {
143 if ( ! colors. length ) return {};
144 const named = ( re ) => colors. find (([ k ]) => re. test (k));
145 const hexes = colors. filter (([, v ]) => lum (v) != null );
146 const byLum = [ ... hexes]. sort (( a , b ) => ( lum (a[ 1 ]) ?? 1e9 ) - ( lum (b[ 1 ]) ?? 1e9 ));
147 const pick = ( m , fallback ) => (m ? m[ 1 ] : fallback ? fallback[ 1 ] : undefined );
148 // "ink" must be a whole word-segment so "soft-pink"/"pink" don't match it.
149 const ink = pick (
150 named ( /(?: ^| [-_] )ink(?: [-_] |$ ) | black | charcoal |^ text(?:-dark) ?$| outline | noir/ i ),
151 byLum[ 0 ] ?? colors[ 0 ],
152 );
153 const canvas = pick (
154 named ( /cream | paper | canvas | white | bg | ground | surface | base | sand | parchment | off- ? white | bone/ i ),
155 byLum[byLum. length - 1 ] ?? colors[colors. length - 1 ],
156 );
157 const accents = colors
158 . filter (
159 ([ k , v ]) =>
160 v !== ink &&
161 v !== canvas &&
162 ! UA_DEFAULT_COLORS . has ( String (v). toUpperCase ()) &&
163 ! STATUS_ROLE_KEY . test (k), // a status red/green carries meaning by hue — never an accent
164 )
165 . sort (( a , b ) => chroma (b[ 1 ]) - chroma (a[ 1 ]))
166 . map (([, v ]) => v);
167 return { ink, canvas, accent: accents[ 0 ] ?? ink, accent2: accents[ 1 ] ?? accents[ 0 ] ?? ink };
168 }
169
170 // Collect role→fontFamily under the top-level `typography:` block; pick a display
171 // + body family from the usual role names. Returns quoted families (or null).
172 export function parseFonts ( md ) {
173 const roles = {};
174 let inBlock = false ;
175 for ( const line of md. split ( / \r ? \n / )) {
176 if ( / ^ typography: \s *$ / . test (line)) {
177 inBlock = true ;
178 continue ;
179 }
180 if ( ! inBlock) continue ;
181 if ( / ^ \S / . test (line)) break ;
182 const m = line. match ( / ^ \s + ( [\w-] + ): \s * \{ [ ^ }] * fontFamily: \s * "( [ ^ "] + )"/ );
183 if (m) roles[m[ 1 ]] = m[ 2 ];
184 }
185 const q = ( s ) => (s ? `"${ s }"` : null );
186 const body = roles.body ?? roles.subtitle ?? Object. values (roles)[ 0 ];
187 const display =
188 roles.display ??
189 roles.headline ??
190 roles[ "card-headline" ] ??
191 roles[ "section-headline" ] ??
192 roles[ "quote-display" ] ??
193 roles.h1 ??
194 roles.h2 ??
195 roles.title ??
196 roles.hero ??
197 body;
198 // the monospace / chrome family (code, tags, ticks, page numbers) — so the remix can
199 // route a captured brand mono (Berkeley Mono, JetBrains Mono…) onto this role instead
200 // of the reading body. null when the preset has no distinct mono role.
201 const mono =
202 roles.mono ??
203 roles[ "mono-tag" ] ??
204 roles[ "mono-chrome" ] ??
205 roles[ "mono-tick" ] ??
206 roles.code ??
207 roles.data ??
208 roles.pagenum ??
209 null ;
210 return { display: q (display), body: q (body), mono: q (mono) };
211 }