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

--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
βœ— DON'T
  • βœ—Use #000000 as 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
🎨
8 variables
fixed roles
β˜…
1 accent
dominant amber
⌨
cyan = code
technical only
πŸŒ™
fixed background
full consistency
2

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

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 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: breathing amber radial glow, giant drifting ghost text, hairline grid, and grain (SVG noise). Together, they create depth without distraction.

⚠ Caution: 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 will shift 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. 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.

πŸ“Š 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 (no flash)
  • βœ“Commit assets/fonts/ in the repository
  • βœ“Check the Latin subset with unicode-range
βœ— DON'T
  • βœ—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
.woff2
ideal format
πŸ‡§πŸ‡·
subset latin
covers Brazilian Portuguese
⚑
font-display:block
without flashing
πŸ“
assets/fonts/
always local
5

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

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

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% wide, 0.6s ease
z:1 Subhead: Inter 400, 28–36px, muted, stagger 0.12s after headline
z:2 Caption at the bottom: 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 start
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 drop-shadow glow, and 🌐 inema.club in JetBrains Mono. It’s the channel’s signature β€” every video ends the same way.

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

βœ“ DO
  • βœ“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
βœ— DON'T
  • βœ—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
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 only dominant accent (#FFC300)
  • βœ“Three sources: Sora / Inter / JetBrains Mono, each with a distinct role
  • βœ“Why .woff2 local 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 + 🌐
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.