PTENES
MODULE 3.2

🎨 Premium dark house style

Palette, typography, background layer, and video scale β€” the visual system that makes each scene unmistakable.

6
Topics
22
Minutes
Advanced
Level
Reference
Type
DARK --bg #0D1321 --bg2 #1D2D44 --bg3 #3E5C76 --fg #F0EBD8 --muted #748CAB --accent ONLY #FFC300 --accent2 #FCA311 --code #2EC4B6 ONE accent. Always. BACKGROUND TEXT BORDAS
1

🎨 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.

--bg
#0D1321
--bg2
#1D2D44
--bg3 / border
#3E5C76
--fg
#F0EBD8
--muted
#748CAB
--accent β˜…
#FFC300
--accent2
#FCA311
--code
#2EC4B6
:root of composition-template.mjs
: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 */
}
βœ“ DO
  • βœ“Use #0D1321 as 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
βœ— DO NOT
  • βœ—Use #000000 as 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
🎨
8 variables
fixed roles
β˜…
1 accent
dominant amber
⌨
cyan = code
technical only
πŸŒ™
fixed background
full consistency
2

πŸ”€ 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.

Sora 800 Β· headline 64–172px
House style
premium dark.
Inter 400 Β· body 28–42px
The body uses Inter in regular or medium weight.
Readable on a large screen, without distraction.
JetBrains Mono Β· code and URLs
node build-index.mjs --vertical
🌐 inema.club
Main concept β€” Why local fonts?

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 generated by fetch-fonts.mjs
@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: Γ‘ Γ  Γ’ Γ£ Γ© Γͺ Γ­ Γ³ Γ΄ Γ΅ ΓΊ Γ§ */
Workflow: local sources in the pipeline
1
node fetch-fonts.mjs β€” downloads .woff2, generates fonts.css
2
build-index.mjs β€” injects readFileSync("fonts.css") in the HTML
3
Headless Chrome loads HTML β€” fonts already inline, zero CDN
βœ“
Frame rendered with perfect Sora β€” no fallback
Sora
700–800 Β· titles
Inter
400–700 Β· body
Mono
JetBrains Β· code
πŸ“¦
.woff2 local
never use a CDN
3

🌌 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.

⚠ Attention: data-layout-ignore

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.

HTML β€” persistent layer (composition-template.mjs)
<!-- 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>
CSS β€” layer and ambient animations
.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); }
}
πŸ’‘ Tip β€” Decorative element opacity

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).

πŸŒ…
amber glow
breathing radial
DARK
ghost text
3.5% Β· drifting
⊞
hairline grid
depth
β–‘
grain
premium texture
4

πŸ’Ύ 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.

πŸ“Š Files generated by fetch-fonts.mjs
Sora
Sora-700.woff2
Sora-800.woff2
~18KB each
Inter
Inter-400.woff2
Inter-500.woff2
Inter-600.woff2
Inter-700.woff2
~14KB each
JetBrains Mono
JetBrainsMono-400.woff2
JetBrainsMono-600.woff2
JetBrainsMono-700.woff2
~22KB each
βœ“ DO
  • βœ“Run node fetch-fonts.mjs once per project
  • βœ“Use font-display:block (without a flash)
  • βœ“Commit assets/fonts/ in the repository
  • βœ“Check the Latin subset with unicode-range
βœ— DO NOT
  • βœ—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
.woff2
ideal format
πŸ‡§πŸ‡·
subset latin
covers PT-BR
⚑
font-display:block
without flash
πŸ“
assets/fonts/
always local
5

🎬 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.

Typography scale by context
16:9 Β· 1920Γ—1080
headline: 72–172px
body: 28–40px
caption: 32–36px
kicker: 20–28px
9:16 Β· 1080Γ—1920
headline: 104–118px
body: 36–42px
caption: 38–44px
kicker: 24–32px
Decorative
opacity: 12–25%
borders: 2–4px
ghost text: ~3.5%
glow: 4–8%
πŸ’‘ Tip β€” Ambient motion

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.

Anatomy of a scene (8–10 elements)
z:-1 Background layer: glow + ghost + grid + grain (data-layout-ignore)
z:1 Kicker/label: 20–28px font, amber or muted, 0.8s entrance
z:1 Headline: Sora 800, 72–172px, cream white, 0.4s ease-out entrance
z:1 Amber rule: 2–3px tall, 0% β†’ 100% width, 0.6s ease
z:1 Subhead: Inter 400, 28–36px, muted, stagger 0.12s after headline
z:2 Footer caption: Inter 600, translucent background #0D1321CC
z:2 Amber progress bar: 2px tall at the bottom, scaleX(progress)
8–10
elements/scene
maximum
172px
max headline
16:9
0.4s
quick entry
ease-out
25%
decorative max
opacity
6

🏁 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.

CONTINUES IN
INEMA.CLUB
🌐 inema.club
scene9() β€” composition-template.mjs (do not remove)
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 */
πŸ’‘ Tip β€” CTA narration

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.

βœ“ DO
  • βœ“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
βœ— DO NOT
  • βœ—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
INEMA
--fg cream
.CLUB
--accent + glow
🌐 inema.club
JetBrains Mono
🏁
scene9
always the last one

πŸ“‹ Module 3.2 summary

What you learned
  • βœ“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 .woff2 local 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 + 🌐
Module references
  • πŸ“„ references/house-style.md
  • πŸ“„ scripts/composition-template.mjs
  • πŸ“„ scripts/fetch-fonts.mjs
  • πŸ”‘ Amber rule: 1 accent. Identical background. NEVER use a CDN.
Next module:
βš™οΈ
3.3 β€” The build-index.mjs generator
How the generator turns HTML into frames and frames into MP4.