π¨ The Palette
Eight CSS variables. One per role. No improvisation.
The house style isnβt a list of pretty colorsβitβs a system with a defined role for each tone. Night background (#0D1321), slightly lighter panels, subtle borders, warm cream text, muted for hierarchy, and amber as the sole dominant accent. The cyan (#2EC4B6) is reserved exclusively for code and technical values.
:root {
--bg: #0D1321; /* azul-noite β NUNCA preto puro */
--bg2: #1D2D44; /* painΓ©is e cards */
--bg3: #3E5C76; /* bordas e divisores */
--fg: #F0EBD8; /* texto creme quente */
--muted: #748CAB; /* secundΓ‘rio / hierarquia */
--accent: #FFC300; /* Γ’mbar β destaque ΓNICO */
--accent2: #FCA311; /* Γ’mbar quente p/ gradientes */
--code: #2EC4B6; /* verde-Γ‘gua β sΓ³ cΓ³digo/valores */
}
- βUse
#0D1321as the background in every scene - βAmber only for the dominant accent (CTAs, highlights)
- βCyan only for code, URLs, and technical values
- βIdentical background in every video scene
- βUse
#000000as the background (looks like a noob YouTuber's video) - βMix two accents in the same scene (green + amber)
- βUse cyan for decorative text
- βChange the palette from scene to scene
π€ Typography
Three fonts. Three roles. No ambiguity.
The system uses Sora for headlines (weight 700β800, video scale 72β172px at 16:9), Inter for body text and interface (28β42px), and JetBrains Mono for code, URLs, and technical labels. The three fonts have .woff2 local β the generator never makes requests to a CDN at runtime.
premium dark.
Readable on a large screen, without distraction.
During rendering with headless Chrome, the page can't make external requests. If the font comes from a CDN, Chrome renders with a fallbackβand the video disappears in the render. The script fetch-fonts.mjs downloads the .woff2 (Latin subset) and generates assets/fonts/fonts.css with @font-face local before any rendering.
@font-face{font-family:'Sora';font-style:normal;font-weight:700;
font-display:block;src:url('./fonts/Sora-700.woff2') format('woff2');}
@font-face{font-family:'Sora';font-style:normal;font-weight:800;
font-display:block;src:url('./fonts/Sora-800.woff2') format('woff2');}
@font-face{font-family:'Inter';font-style:normal;font-weight:400;
font-display:block;src:url('./fonts/Inter-400.woff2') format('woff2');}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;
font-display:block;src:url('./fonts/JetBrainsMono-400.woff2') format('woff2');}
/* subset: latin β cobre PT-BR: Γ‘ Γ Γ’ Γ£ Γ© Γͺ Γ Γ³ Γ΄ Γ΅ ΓΊ Γ§ */
node fetch-fonts.mjs β downloads .woff2, generates fonts.css
build-index.mjs β injects readFileSync("fonts.css") in the HTML
π Persistent Background Layer
The breathing backgroundβalways present, never competing.
Each scene has a persistent background layer marked with data-layout-ignore. It is never repositioned by the layout engine. It contains four elements: a breathing amber radial glow, giant drifting ghost text, a hairline grid, and grain (SVG noise). Together, they create depth without distraction.
The attribute data-layout-ignore signals to the generator that this element stays in position:absolute; z-index:-1 and doesn't participate in the layout flow. Without it, the ghost text or glow shifts the scene's actual content.
<!-- FUNDO PERSISTENTE β data-layout-ignore -->
<div data-layout-ignore class="bg-layer">
<!-- glow radial Γ’mbar respirando -->
<div class="glow-amber"></div>
<!-- ghost text gigante driftando -->
<div class="ghost-text">DARK</div>
<!-- grid hairline -->
<svg class="grid-lines" ...></svg>
<!-- grΓ£o (SVG feTurbulence) -->
<canvas class="grain"></canvas>
</div>
.bg-layer {
position: absolute; inset: 0; z-index: -1; overflow: hidden;
}
.glow-amber {
position: absolute; top: 40%; left: 50%;
transform: translate(-50%, -50%);
width: 60%; height: 40%;
background: radial-gradient(ellipse, #FFC300 0%, transparent 70%);
opacity: 0.06;
animation: breathe 4s ease-in-out infinite;
}
.ghost-text {
position: absolute; font-family: 'Sora', sans-serif;
font-size: 172px; font-weight: 800;
color: #F0EBD8; opacity: 0.035;
animation: drift 8s ease-in-out infinite;
}
@keyframes breathe {
0%,100% { opacity:.04; transform: translate(-50%,-50%) scale(1); }
50% { opacity:.08; transform: translate(-50%,-50%) scale(1.08); }
}
@keyframes drift {
0% { transform: translateX(0px) translateY(0px); }
33% { transform: translateX(12px) translateY(-6px); }
66% { transform: translateX(-8px) translateY(4px); }
100% { transform: translateX(0px) translateY(0px); }
}
Decorative elements should stay between 12β25% opacity. Below 12%, it disappears in H.264 compression. Above 25%, it competes with the content and looks amateurish. The ghost text is around 3.5% (almost invisible, just a sense of depth).
πΎ Local .woff2 fonts
Latin subset. Local @font-face. No CDN in render.
The script fetch-fonts.mjs uses Chrome 131's User-Agent to simulate a browser, makes a request to the Google Fonts API, filters only the latin subset (unicode-range U+0000-00FF covers all Brazilian Portuguese characters: Γ‘ Γ Γ’ Γ£ Γ© Γͺ Γ Γ³ Γ΄ Γ΅ ΓΊ Γ§), downloads the .woff2 and generates assets/fonts/fonts.css.
- βRun
node fetch-fonts.mjsonce per project - βUse
font-display:block(without a flash) - βCommit
assets/fonts/in the repository - βCheck the Latin subset with
unicode-range
- βLink to Google Fonts CDN in the scene HTML
- βUse
font-display:swap(causes a flash in the render) - βDownload all subsets (increases size with no PT-BR benefit)
- βIgnore .gitignore β fonts must be committed
π¬ Video Scale, Not Web Scale
8β10 elements per scene. Headlines at 64β172px. Ambient motion in everything.
The biggest mistake when creating videos with HTML is using web scale. At 1920Γ1080 with 1:1 zoom, 16px text disappears. The house style defines minimum 64px headline, body text 28β42px, and decorative text at 12β25% opacity to create layers without clutter. 8β10 elements per scene is the limit β more than that creates visual clutter after compression.
Every decoration has ambient motion: 0.4β0.6s entrances, varied eases, combining transform + opacity, stagger by 0.08β0.14s between elements. The rule is: if itβs decorative, let it breathe. If itβs content, show it once and stop.
#0D1321CC
π The INEMA.CLUB CTA
The last scene. The standard sign-off. Never remove it.
Scene9 (the last scene in every HyperFrames video) is the INEMA.CLUB CTA: "CONTINUES IN" text in muted, INEMA in cream (--fg), .CLUB in amber with a glow drop shadow, and π inema.club in JetBrains Mono. It's the channel's signature β every video ends the same way.
function scene9() {
return `
<div class="cta-wrap">
<div class="cta-pre">CONTINUA EM</div>
<div class="cta-brand">
<span class="cta-inema">INEMA</span>
<span class="cta-club">.CLUB</span>
</div>
<div class="cta-url">π inema.club</div>
</div>`;
}
/* CSS correspondente */
.cta-pre { font-family:'Inter'; color:var(--muted); letter-spacing:.2em; }
.cta-brand{ font-family:'Sora'; font-weight:800; font-size:clamp(72px,8vw,120px); }
.cta-inema{ color:var(--fg); } /* INEMA β creme */
.cta-club { color:var(--accent); /* .CLUB β Γ’mbar */
filter:drop-shadow(0 0 16px #FFC300aa); }
.cta-url { font-family:'JetBrains Mono'; color:var(--code); } /* π inema.club */
The default scene9 narration is: "This is INEMA dot CLUB content. Visit: inema dot club." β generated with pf_dora --speed 0.98. Typical duration: ~3.84s. LEAD: 0.5s, TAIL: 0.9s.
- βKeep scene9 as the last scene in every video
- βINEMA in
--fg(cream), .CLUB in--accent(amber) - βDrop-shadow glow on .CLUB
- βURL in JetBrains Mono + cyan
- βRemove or skip scene9
- βChange .CLUB's color to another color in the track
- βUse a different font in the CTA
- βOmit the π from the URL
π Module 3.2 summary
- βThe palette of 8 CSS variables with fixed roles
- βamber as the sole dominant accent (
#FFC300) - βThree fonts: Sora / Inter / JetBrains Mono, each with a distinct role
- βWhy
.woff2local and never from a CDN during rendering - βPersistent background layer with
data-layout-ignore - βVideo scale: 64β172px headlines, 8β10 elements/scene
- βINEMA.CLUB CTA: INEMA in cream + .CLUB in amber + glow + π
-
π
references/house-style.md -
π
scripts/composition-template.mjs -
π
scripts/fetch-fonts.mjs - π Amber rule: 1 accent. Identical background. NEVER use a CDN.