π¨ The Palette
Eight CSS variables. One per role. No improvisation.
The default 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, a subtle border, warm cream text, muted for hierarchy, and amber as the only dominant accent. 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 a background (looks like a noob YouTuber's video) - βMixing two accents in the same scene (green + amber)
- βUse cyan for decorative text
- βChange the palette from scene to scene
π€ Typography
Three sources. Three roles. No ambiguity.
The system uses Sora for headlines (weight 700β800, video scale 72β172px in 16:9), Inter for body text and UI (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 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: breathing amber radial glow, giant drifting ghost text, 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 will shift 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. Zero 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: Γ‘ Γ Γ’ Γ£ Γ© Γͺ Γ Γ³ Γ΄ Γ΅ ΓΊ Γ§), download the .woff2 and generates assets/fonts/fonts.css.
- βRun
node fetch-fonts.mjsonce per project - βUse
font-display:block(no 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 without any 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 throughout.
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 elements at 12β25% opacity to create layers without clutter. 8β10 elements per scene is the limitβmore than that creates visual chaos in compression.
Every decoration has ambient motion: 0.4β0.6s entrances, varied eases, combining transform + opacity, stagger of 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 drop-shadow glow, 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 narration for scene9 is: "This is INEMA dot CLUB content. Go to: 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 of 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 from the track
- βUse a different font for the CTA
- βOmit the π from the URL
π Module 3.2 Summary
- βThe palette of 8 CSS variables with fixed roles
- βamber as the only dominant accent (
#FFC300) - βThree sources: Sora / Inter / JetBrains Mono, each with a distinct role
- βWhy
.woff2local and never CDN-based during rendering - βPersistent background layer with
data-layout-ignore - βVideo scale: 64β172px headlines, 8β10 elements per scene
- βINEMA.CLUB CTA: INEMA cream + .CLUB amber + glow + π
-
π
references/house-style.md -
π
scripts/composition-template.mjs -
π
scripts/fetch-fonts.mjs - π Amber rule: 1 accent. Identical background. NEVER use a CDN.