// Componentes compartilhados — Landing v2 · Estúdio 9J
// Logo, ON AIR, Wave, Kicker, Display, CTA, Container, Section
// + SVG renderings (câmera, mesa de som, mesa de iluminação, workstation, microfone, control room)
// + Modal de contato rápido
// ─────────────────────────────────────────────────────────────
// Tipografia
// ─────────────────────────────────────────────────────────────
const Kicker = ({ children, color, style }) =>
{children}
;
const Display = ({ children, size = 80, color = T.cream, weight = 300, style }) =>
{children}
;
// ─────────────────────────────────────────────────────────────
// Logo — wordmark com ponto magenta no canto superior do "J"
// ─────────────────────────────────────────────────────────────
function Logo({ color = T.cream, accent = T.magenta, size = 22, short = false, live = false }) {
const dot = size * 0.20;
return (
{!short &&
ESTUDIO
}
9J
);
}
// ON AIR
function OnAir({ scale = 1, label = 'ON AIR', accent = T.magenta }) {
return (
{label}
);
}
// Waveform
function Wave({ bars = 56, height = 60, width = 480, color = T.cream, seed = 1, weight = 1, animate = false }) {
const arr = Array.from({ length: bars }, (_, i) => {
const v = Math.abs(Math.sin((i + seed) * 1.7) * 0.55 + Math.cos((i + seed) * 0.93) * 0.45);
return 0.14 + v * 0.86;
});
return (
{arr.map((h, i) => {
const gap = width / bars;
const x = i * gap + gap / 2;
const bh = h * height * 0.94;
return (
);
})}
);
}
// ─────────────────────────────────────────────────────────────
// CTA primary — botão
// ─────────────────────────────────────────────────────────────
function CTA({ children, href = '#', accent = T.magenta, fg = T.ink, kind = 'primary', sub, style, onClick }) {
const isPrimary = kind === 'primary';
return (
{e.currentTarget.style.transform = 'translateY(-1px)';if (!isPrimary) e.currentTarget.style.borderColor = T.cream;}}
onMouseLeave={(e) => {e.currentTarget.style.transform = 'translateY(0)';if (!isPrimary) e.currentTarget.style.borderColor = T.hairline;}}>
{isPrimary &&
}
{children}
{sub &&
{sub}
}
);
}
// ─────────────────────────────────────────────────────────────
// Containers
// ─────────────────────────────────────────────────────────────
function Container({ children, style, narrow }) {
return (
{children}
);
}
function Section({ children, id, bg = T.night, fg = T.cream, py = 120, style }) {
return (
);
}
// Header da seção: Kicker à esquerda + ref técnica à direita + Display + caption
function SectionHead({ kicker, eyebrow, title1, title1Hi, title1Punct, caption, accent, displaySize = 56, captionRight = true }) {
return (
<>
{kicker}
{eyebrow && {eyebrow} }
{title1}{title1Hi} {title1Punct}
{caption &&
{caption}
}
>);
}
// ─────────────────────────────────────────────────────────────
// SVG ILLUSTRATIONS — broadcast set
// All in stroke-only line-art matching brand
// ─────────────────────────────────────────────────────────────
// Camera on tripod
function IllusCamera({ size = 280, color = T.cream, accent = T.magenta }) {
return (
{/* tripod */}
{/* head plate */}
{/* body */}
{/* viewfinder */}
{/* lens */}
{/* top accessories */}
CAM·01
{/* REC indicator */}
);
}
// Audio mixer console
function IllusMixer({ size = 280, color = T.cream, accent = T.magenta }) {
return (
{/* console body */}
{/* divider */}
{/* faders 8 channels */}
{[0, 1, 2, 3, 4, 5, 6, 7].map((i) => {
const x = 38 + i * 28;
const fy = 140 + i * 37 % 26;
return (
{/* knobs above */}
);
})}
{/* level meter */}
{[0, 1, 2, 3, 4, 5, 6].map((i) =>
4 ? accent : color} strokeWidth="2" opacity={0.4 + i * 0.1} />
)}
MIXER · 8CH
);
}
// DMX lighting console
function IllusLights({ size = 280, color = T.cream, accent = T.magenta }) {
return (
{/* truss top */}
{/* fixtures */}
{[
{ x: 70, color: accent },
{ x: 120, color: '#8C5BFF' },
{ x: 170, color: '#36E8E1' },
{ x: 220, color: '#FF8C42' }].
map((f, i) =>
{/* cone */}
)}
{/* DMX console at bottom */}
{[0, 1, 2, 3, 4, 5, 6, 7].map((i) =>
)}
DMX·512
);
}
// Workstation (dual monitors)
function IllusWorkstation({ size = 280, color = T.cream, accent = T.magenta }) {
return (
{/* left monitor — timeline */}
{/* tracks */}
{/* playhead */}
{/* right monitor — preview with waveform */}
{/* preview face */}
{/* mini wave at bottom */}
{Array.from({ length: 18 }).map((_, i) => {
const h = 4 + Math.abs(Math.sin(i * 1.3)) * 8;
return ;
})}
{/* keyboard / desk */}
WORKSTATION · EDIT · AI POST
);
}
// Microphone (suspended)
function IllusMic({ size = 200, color = T.cream, accent = T.magenta }) {
return (
{/* arm */}
{/* shock mount */}
{/* mic body */}
{/* grille */}
{/* foam pop filter circle */}
{/* xlr cable */}
{/* on indicator */}
);
}
// Control room — schematic top-down view
function IllusControlRoom({ width = 600, height = 320, color = T.cream, accent = T.magenta }) {
return (
{/* room outline */}
{/* curved bench */}
{/* mics */}
{[0, 1, 2, 3].map((i) => {
const x = 156 + i * 96;
const y = 200 - Math.sin(i / 3 * Math.PI) * 22;
return (
M·{i + 1}
);
})}
{/* 3 cameras */}
{[
{ x: 110, y: 100, label: 'CAM·01' },
{ x: 300, y: 80, label: 'CAM·02' },
{ x: 490, y: 100, label: 'CAM·03' }].
map((c, i) =>
{c.label}
{i === 1 && }
)}
{/* 55" reference screen */}
REF · 55"
{/* lighting truss above */}
{[0, 1, 2, 3, 4].map((i) =>
)}
{/* labels */}
DMX TRUSS
CONTROL ROOM · TOP VIEW
);
}
// 55" reference screen — used in personalization
function IllusScreen55({ width = 600, height = 360, color = T.cream, accent = T.magenta, screenContent = null, lightColor = '#FF3E8E', bgVariant = 'wood', shelfVariant = 'books' }) {
// wall background variants
const wallFill = {
wood: 'rgba(180, 140, 90, 0.10)',
concrete: 'rgba(120, 120, 120, 0.10)',
black: 'rgba(20, 8, 32, 0.85)',
purple: 'rgba(60, 40, 90, 0.20)'
}[bgVariant] || 'rgba(180, 140, 90, 0.10)';
const wallStrokeOpacity = bgVariant === 'black' ? 0.4 : 0.6;
const isDark = bgVariant === 'black';
const fg = isDark ? T.night : color;
return (
{/* wall behind */}
{/* light wash from above */}
{/* shelf above (left) */}
{/* shelf objects */}
{shelfVariant === 'books' &&
}
{shelfVariant === 'plants' &&
}
{shelfVariant === 'vinyl' &&
}
{shelfVariant === 'catalog' &&
}
{/* 55" reference screen (right side) */}
{/* screen content */}
{screenContent === 'logo' &&
9J
SEU PROGRAMA
}
{screenContent === 'video' &&
VÍDEO DE CAPA
}
{screenContent === 'neutral' &&
WALLPAPER NEUTRO
}
{/* TV bezel detail */}
{/* bench / desk */}
{/* mics on bench */}
{[150, 300, 450].map((x, i) =>
)}
{/* floor label */}
PREVIEW · STAGE A
);
}
// ─────────────────────────────────────────────────────────────
// Modal de contato rápido
// ─────────────────────────────────────────────────────────────
function ContactModal({ open, onClose, copy, accent, waLink }) {
const [name, setName] = React.useState('');
const [wa, setWa] = React.useState('');
const [type, setType] = React.useState(copy.modal.types[0]);
const m = copy.modal;
if (!open) return null;
const handleSubmit = (e) => {
e.preventDefault();
const msg = `Oi! Sou ${name || '[sem nome]'}. Projeto: ${type}. WhatsApp: ${wa || '[não informado]'}. Vim do site.`;
const url = `https://wa.me/5511936213170?text=${encodeURIComponent(msg)}`;
window.open(url, '_blank');
onClose();
};
return (
);
}
const inputStyle = {
width: '100%',
padding: '14px 16px',
background: T.night2,
border: `1px solid ${T.hairline}`,
borderRadius: 4,
fontFamily: T.sans, fontSize: 15,
color: T.cream,
outline: 'none',
transition: 'border-color .15s ease'
};
function Field({ label, children }) {
return (
{label}
{children}
);
}
// ─────────────────────────────────────────────────────────────
// CTA flutuante WhatsApp (compartilhado entre home e segmentos)
// ─────────────────────────────────────────────────────────────
function FloatingCTA({ waLink, accent }) {
return (
{
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = `0 12px 40px ${accent}88, 0 0 0 1px ${T.ink}22`;
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = `0 8px 32px ${accent}55, 0 0 0 1px ${T.ink}22`;
}}>
WhatsApp
);
}
// ─────────────────────────────────────────────────────────────
// Media Slot — placeholders pra vídeo e imagem (genéricos por ora)
// ─────────────────────────────────────────────────────────────
function MediaSlot({
kind = 'image', // 'image' | 'video'
src = '', // se preenchido, mostra a foto de verdade sob o chrome
label = '', // e.g. "B-ROLL · MAIN ROOM"
caption = '', // legenda discreta abaixo
aspect = '16/9', // CSS aspect-ratio
accent = T.magenta,
duration = '02:18', // só pra video
timecode = '00:00:24',
showChrome = true, // mostrar barra de status acima
fill = true, // se false, ratio definido pelo container
style
}) {
const isVideo = kind === 'video';
const hasPhoto = !!src;
// Background pattern: very subtle scan lines + crosshair center
return (
{/* Foto real, se houver */}
{hasPhoto &&
}
{/* Scan-line pattern background — só quando placeholder */}
{!hasPhoto &&
}
{/* Vignette (mais leve quando há foto) */}
{/* Top chrome */}
{showChrome &&
{label || (isVideo ? 'B-ROLL · LIVE' : 'IMAGE · PLACEHOLDER')}
{isVideo && {timecode} }
{!isVideo && 4:3 · 12MP }
}
{/* Center icon (escondido quando tem foto real) */}
{!hasPhoto &&
{isVideo ?
// Play button
{
e.currentTarget.style.transform = 'scale(1.08)';
e.currentTarget.style.borderColor = accent;
e.currentTarget.style.background = 'rgba(11, 5, 21, 0.55)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1)';
e.currentTarget.style.borderColor = 'rgba(245, 239, 224, 0.6)';
e.currentTarget.style.background = 'rgba(11, 5, 21, 0.35)';
}}>
:
// Crosshair / framing marks
{/* corners */}
{/* crosshair */}
}
}
{/* Bottom chrome — scrubber for video */}
{isVideo && showChrome &&
4K · 25fps · -12dB
{timecode} · {duration}
}
{/* Caption tag — small marker outside on the right */}
{caption &&
{caption}
}
);
}
Object.assign(window, {
Kicker, Display, Logo, OnAir, Wave, CTA, Container, Section, SectionHead,
IllusCamera, IllusMixer, IllusLights, IllusWorkstation, IllusMic, IllusControlRoom, IllusScreen55,
ContactModal, Field, FloatingCTA, MediaSlot, PillarIcon, Icon
});
// ─────────────────────────────────────────────────────────────
// PillarIcon — pequenos ícones stroke pra Áudio / Vídeo / Iluminação
// ─────────────────────────────────────────────────────────────
function PillarIcon({ kind, size = 36, color, accent = '#FF3E8E' }) {
const c = color || T.cream;
const k = kind === 'LIT' ? 'LUZ' : kind;
if (k === 'AUD') {
return (
);
}
if (k === 'VID') {
return (
);
}
// LUZ
if (k === 'LUZ') {
return (
);
}
// PRC — workstation
if (k === 'PRC') {
return (
);
}
// INF — building / room
return (
);
}
// ─────────────────────────────────────────────────────────────
// Icon — biblioteca unificada de ícones stroke (servicos, segmentos, passos)
// ─────────────────────────────────────────────────────────────
function Icon({ kind, size = 32, color, accent = '#FF3E8E', strokeWidth = 1.5 }) {
const c = color || T.cream;
const common = {
width: size, height: size, viewBox: '0 0 32 32',
fill: 'none', stroke: c, strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round'
};
switch (kind) {
// ─── SERVIÇOS ───
case 'podcast': // mic on stand
return (
);
case 'live': // signal tower with bars
return (
);
case 'hybrid': // split screen / two participants
return (
);
case 'corporate': // building / institutional
return (
);
case 'branded': // mic + sparkle (brand)
return (
);
case 'reels': // vertical phone with play
return (
);
// ─── SEGMENTOS ───
case 'criadores': // person with play badge
return (
);
case 'corporativo': // briefcase
return (
);
case 'midia': // newspaper / article
return (
);
case 'eventos': // calendar with star / event
return (
);
// ─── COMO FUNCIONA / PASSOS ───
case 'pick': // checklist
return (
);
case 'configure': // sliders
return (
);
case 'record': // microphone with rec dot
return (
);
case 'receive': // download / package
return (
);
// fallback
default:
return (
);
}
}