fix: make layout fully responsive for any screen size

Root cause: viewport was hardcoded to width=3840 and body to
3840x2160px. If TV browser has different viewport, content
overflows and can't be zoomed to fit.

Changes:
- viewport meta: width=device-width instead of width=3840
- body: 100vw/100vh instead of fixed pixels
- App container: w-screen h-screen
- font-size: clamp(14px, 1.15vw, 24px) scales with viewport
- Topology: horizontal chain (Modem → FW → Switch) saves
  vertical space, VM pills in 3-col grid, all sizes relative
- Kanban: 3-col grid, compact project cards
- All padding/gaps use rem (scale with base font)
- Removed all hardcoded pixel max-widths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-16 01:55:34 +00:00
parent e3e210a9dc
commit c4065f2cce
7 changed files with 142 additions and 201 deletions

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=3840, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TV Dashboard</title> <title>TV Dashboard</title>
</head> </head>
<body> <body>

View File

@@ -53,7 +53,7 @@ function App() {
const connected = !topology.error && !tasks.error && !calendar.error; const connected = !topology.error && !tasks.error && !calendar.error;
return ( return (
<div className="flex flex-col w-[3840px] h-[2160px]"> <div className="flex flex-col w-screen h-screen">
<Header viewName={VIEW_NAMES[activeView]} connected={connected} /> <Header viewName={VIEW_NAMES[activeView]} connected={connected} />
<ViewRotator activeView={activeView}> <ViewRotator activeView={activeView}>
<div className="h-full"> <div className="h-full">

View File

@@ -28,73 +28,57 @@ export function CalendarView({ events }: CalendarViewProps) {
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
{/* Summary bar */} <div className="flex items-center gap-6 px-8 py-2 bg-bg-secondary border-b border-border shrink-0">
<div className="flex items-center gap-8 px-16 py-4 bg-bg-secondary border-b border-border"> <span className="text-base text-text-secondary">
<span className="text-lg text-text-secondary"> <span className="font-bold text-text-primary">{events.length}</span> eventos esta semana
<span className="font-bold text-text-primary">{events.length}</span> eventos esta
semana
</span> </span>
</div> </div>
<div className="flex-1 overflow-y-auto px-16 py-8"> <div className="flex-1 overflow-y-auto px-8 py-6">
{!hasEvents ? ( {!hasEvents ? (
<div className="flex flex-col items-center justify-center h-full gap-6 -mt-16"> <div className="flex flex-col items-center justify-center h-full gap-4">
<span className="text-8xl opacity-30">📅</span> <span className="text-6xl opacity-30">📅</span>
<p className="text-3xl font-semibold text-text-secondary"> <p className="text-2xl font-semibold text-text-secondary">
Sin eventos programados Sin eventos programados
</p> </p>
<p className="text-xl text-text-muted"> <p className="text-lg text-text-muted">
Los próximos eventos del calendario de Odoo aparecerán aquí Los próximos eventos del calendario de Odoo aparecerán aquí
</p> </p>
</div> </div>
) : ( ) : (
<div className="grid grid-cols-3 gap-10 h-full"> <div className="grid grid-cols-3 gap-8 h-full">
{/* Today */} <section>
<section className="flex flex-col"> <h2 className="text-xl font-bold text-text-primary mb-1">Hoy</h2>
<h2 className="text-2xl font-bold text-text-primary mb-2">Hoy</h2> <p className="text-sm text-text-muted capitalize mb-4">{formatDate(today)}</p>
<p className="text-base text-text-muted capitalize mb-6">{formatDate(today)}</p>
{todayEvents.length === 0 ? ( {todayEvents.length === 0 ? (
<p className="text-lg text-text-secondary">Sin eventos</p> <p className="text-base text-text-secondary">Sin eventos</p>
) : (
<div className="space-y-4">
{todayEvents.map((e) => (
<EventCard key={e.id} event={e} />
))}
</div>
)}
</section>
{/* Tomorrow */}
<section className="flex flex-col">
<h2 className="text-2xl font-bold text-text-primary mb-2">Mañana</h2>
<p className="text-base text-text-muted capitalize mb-6">
{formatDate(tomorrow)}
</p>
{tomorrowEvents.length === 0 ? (
<p className="text-lg text-text-secondary">Sin eventos</p>
) : (
<div className="space-y-4">
{tomorrowEvents.map((e) => (
<EventCard key={e.id} event={e} />
))}
</div>
)}
</section>
{/* This week */}
<section className="flex flex-col">
<h2 className="text-2xl font-bold text-text-primary mb-2">Esta semana</h2>
<p className="text-base text-text-muted mb-6">Próximos días</p>
{laterEvents.length === 0 ? (
<p className="text-lg text-text-secondary">Sin eventos</p>
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{todayEvents.map((e) => <EventCard key={e.id} event={e} />)}
</div>
)}
</section>
<section>
<h2 className="text-xl font-bold text-text-primary mb-1">Mañana</h2>
<p className="text-sm text-text-muted capitalize mb-4">{formatDate(tomorrow)}</p>
{tomorrowEvents.length === 0 ? (
<p className="text-base text-text-secondary">Sin eventos</p>
) : (
<div className="space-y-3">
{tomorrowEvents.map((e) => <EventCard key={e.id} event={e} />)}
</div>
)}
</section>
<section>
<h2 className="text-xl font-bold text-text-primary mb-1">Esta semana</h2>
<p className="text-sm text-text-muted mb-4">Próximos días</p>
{laterEvents.length === 0 ? (
<p className="text-base text-text-secondary">Sin eventos</p>
) : (
<div className="space-y-2">
{laterEvents.map((e) => ( {laterEvents.map((e) => (
<div <div key={e.id} className="flex gap-3 text-sm bg-bg-card rounded-lg px-3 py-2 border border-border">
key={e.id} <span className="font-mono text-text-muted min-w-[10rem] capitalize">
className="flex gap-4 text-base bg-bg-card rounded-lg px-4 py-3 border border-border"
>
<span className="font-mono text-text-muted min-w-[160px] capitalize">
{formatDate(e.start)} {formatDate(e.start)}
</span> </span>
<span className="text-text-primary truncate">{e.name}</span> <span className="text-text-primary truncate">{e.name}</span>

View File

@@ -27,22 +27,22 @@ export function Header({ viewName, connected }: HeaderProps) {
}); });
return ( return (
<header className="flex items-center justify-between px-16 py-5 bg-bg-secondary border-b border-border"> <header className="flex items-center justify-between px-8 py-3 bg-bg-secondary border-b border-border shrink-0">
<div className="flex items-center gap-6"> <div className="flex items-center gap-4">
<h1 className="text-3xl font-bold tracking-tight text-text-primary"> <h1 className="text-2xl font-bold tracking-tight text-text-primary">
Consultoria AS Consultoria AS
</h1> </h1>
<span className="text-xl text-text-muted">|</span> <span className="text-text-muted">|</span>
<span className="text-xl font-medium text-accent">{viewName}</span> <span className="text-lg font-medium text-accent">{viewName}</span>
</div> </div>
<div className="flex items-center gap-10"> <div className="flex items-center gap-6">
<span className="text-lg text-text-secondary capitalize">{dateStr}</span> <span className="text-base text-text-secondary capitalize">{dateStr}</span>
<span className="text-3xl font-mono font-bold tabular-nums">{timeStr}</span> <span className="text-2xl font-mono font-bold tabular-nums">{timeStr}</span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
className={`w-3 h-3 rounded-full ${connected ? "bg-success animate-pulse" : "bg-danger"}`} className={`w-3 h-3 rounded-full ${connected ? "bg-success animate-pulse" : "bg-danger"}`}
/> />
<span className="text-sm text-text-secondary"> <span className="text-xs text-text-secondary">
{connected ? "Conectado" : "Sin conexión"} {connected ? "Conectado" : "Sin conexión"}
</span> </span>
</div> </div>

View File

@@ -22,45 +22,41 @@ function ProjectRow({ project }: { project: Project }) {
if (totalTasks === 0) return null; if (totalTasks === 0) return null;
return ( return (
<div className="bg-bg-card border border-border rounded-2xl p-7 flex flex-col gap-4"> <div className="bg-bg-card border border-border rounded-xl p-4 flex flex-col gap-2.5">
{/* Project header */} <div className="flex items-center justify-between gap-3">
<div className="flex items-center justify-between gap-4"> <h3 className="text-lg font-bold text-text-primary leading-snug">
<h3 className="text-2xl font-bold text-text-primary leading-snug">
{project.name} {project.name}
</h3> </h3>
<span className="text-lg font-mono text-text-secondary shrink-0"> <span className="text-sm font-mono text-text-secondary shrink-0">
{totalTasks} tareas {totalTasks}
</span> </span>
</div> </div>
{/* Progress bar */} <div className="flex h-3 rounded-full overflow-hidden bg-bg-primary">
<div className="flex h-5 rounded-full overflow-hidden bg-bg-primary">
{stages.map(([stageName, tasks], i) => { {stages.map(([stageName, tasks], i) => {
const pct = (tasks.length / totalTasks) * 100; const pct = (tasks.length / totalTasks) * 100;
if (pct < 1) return null; if (pct < 1) return null;
return ( return (
<div <div
key={stageName} key={stageName}
className={`${STAGE_COLORS[i % STAGE_COLORS.length]} transition-all`} className={`${STAGE_COLORS[i % STAGE_COLORS.length]}`}
style={{ width: `${pct}%` }} style={{ width: `${pct}%` }}
title={`${stageName}: ${tasks.length}`}
/> />
); );
})} })}
</div> </div>
{/* Stage chips */} <div className="flex flex-wrap gap-1.5">
<div className="flex flex-wrap gap-3">
{stages.map(([stageName, tasks], i) => ( {stages.map(([stageName, tasks], i) => (
<span <span
key={stageName} key={stageName}
className="inline-flex items-center gap-2 text-base text-text-secondary bg-bg-secondary rounded-lg px-4 py-2" className="inline-flex items-center gap-1.5 text-sm text-text-secondary bg-bg-secondary rounded-md px-2.5 py-1"
> >
<span <span
className={`w-3 h-3 rounded-full shrink-0 ${STAGE_COLORS[i % STAGE_COLORS.length]}`} className={`w-2 h-2 rounded-full shrink-0 ${STAGE_COLORS[i % STAGE_COLORS.length]}`}
/> />
<span className="leading-tight">{stageName}</span> <span className="truncate max-w-[12rem]">{stageName}</span>
<span className="font-mono font-bold text-text-primary text-lg">{tasks.length}</span> <span className="font-mono font-bold text-text-primary">{tasks.length}</span>
</span> </span>
))} ))}
</div> </div>
@@ -81,21 +77,18 @@ export function KanbanBoard({ projects }: KanbanBoardProps) {
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
{/* Summary bar */} <div className="flex items-center gap-6 px-8 py-2 bg-bg-secondary border-b border-border shrink-0">
<div className="flex items-center gap-10 px-16 py-5 bg-bg-secondary border-b border-border"> <span className="text-base text-text-secondary">
<span className="text-xl text-text-secondary">
<span className="font-bold text-text-primary">{activeProjects.length}</span> proyectos <span className="font-bold text-text-primary">{activeProjects.length}</span> proyectos
activos
</span> </span>
<span className="text-border-light text-2xl">|</span> <span className="text-border-light">|</span>
<span className="text-xl text-text-secondary"> <span className="text-base text-text-secondary">
<span className="font-bold text-text-primary">{totalTasks}</span> tareas totales <span className="font-bold text-text-primary">{totalTasks}</span> tareas
</span> </span>
</div> </div>
{/* Projects grid */} <div className="flex-1 overflow-y-auto px-8 py-4">
<div className="flex-1 overflow-y-auto px-16 py-8"> <div className="grid grid-cols-3 gap-4">
<div className="grid grid-cols-2 gap-6">
{activeProjects.map((project) => ( {activeProjects.map((project) => (
<ProjectRow key={project.id} project={project} /> <ProjectRow key={project.id} project={project} />
))} ))}

View File

@@ -17,16 +17,15 @@ function VmPill({ node }: { node: NetworkNode }) {
const dotColor = isUp ? "bg-success" : "bg-danger"; const dotColor = isUp ? "bg-success" : "bg-danger";
return ( return (
<div className="flex items-center gap-2 bg-bg-card border border-border rounded-lg px-3 py-2"> <div className="flex items-center gap-1.5 bg-bg-card border border-border rounded-md px-2 py-1">
<span className={`w-2.5 h-2.5 rounded-full ${dotColor} shrink-0`} /> <span className={`w-2 h-2 rounded-full ${dotColor} shrink-0`} />
<span className="text-base font-medium text-text-primary truncate">{node.name}</span> <span className="text-sm font-medium text-text-primary truncate">{node.name}</span>
<span className="text-sm font-mono text-text-muted">{node.ip.split(".").pop()}</span>
</div> </div>
); );
} }
/* ── Full node card (infrastructure + proxmox) ───────────── */ /* ── Infrastructure card ─────────────────────────────────── */
function InfraCard({ node, isCenter }: { node: NetworkNode; isCenter?: boolean }) { function InfraCard({ node }: { node: NetworkNode }) {
const [showPass, setShowPass] = useState(false); const [showPass, setShowPass] = useState(false);
const isUp = node.status === "up"; const isUp = node.status === "up";
const borderColor = isUp ? "border-success/40" : "border-danger/40"; const borderColor = isUp ? "border-success/40" : "border-danger/40";
@@ -35,56 +34,42 @@ function InfraCard({ node, isCenter }: { node: NetworkNode; isCenter?: boolean }
return ( return (
<div <div
className={`${bgColor} border-2 ${borderColor} rounded-2xl px-6 py-5 cursor-pointer transition-all hover:brightness-110 ${isCenter ? "min-w-[280px]" : ""}`} className={`${bgColor} border ${borderColor} rounded-xl px-4 py-3 cursor-pointer transition-all hover:brightness-110`}
onClick={() => setShowPass((p) => !p)} onClick={() => setShowPass((p) => !p)}
> >
<div className="flex items-center gap-3 mb-1"> <div className="flex items-center gap-2">
<span className="text-3xl">{ICON_MAP[node.icon] || "📦"}</span> <span className="text-2xl">{ICON_MAP[node.icon] || "📦"}</span>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-xl font-bold text-text-primary leading-tight">{node.name}</p> <p className="text-lg font-bold text-text-primary leading-tight">{node.name}</p>
<p className="text-base font-mono text-text-secondary">{node.ip}</p> <p className="text-sm font-mono text-text-secondary">{node.ip}</p>
</div> </div>
<span className={`w-4 h-4 rounded-full ${dotColor} shrink-0`} /> <span className={`w-3 h-3 rounded-full ${dotColor} shrink-0`} />
</div> </div>
{node.username && ( {showPass && node.username && (
<p className="text-sm text-text-secondary mt-1"> <p className="text-xs text-text-secondary mt-1">
{node.username} {node.username}
{node.password && ( {node.password && <span className="font-mono text-warning"> / {node.password}</span>}
<span className="ml-1 font-mono text-warning">
{showPass ? ` / ${node.password}` : " / ••••••"}
</span>
)}
</p> </p>
)} )}
{node.public_url && ( {showPass && node.public_url && (
<p className="text-sm text-accent mt-0.5">{node.public_url}</p> <p className="text-xs text-accent mt-0.5">{node.public_url}</p>
)} )}
</div> </div>
); );
} }
/* ── Proxmox server with its VMs ─────────────────────────── */ /* ── Proxmox server column ───────────────────────────────── */
function ProxmoxColumn({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) { function ProxmoxColumn({ server, vms }: { server: NetworkNode; vms: NetworkNode[] }) {
const vmCount = vms.length;
const upCount = vms.filter((v) => v.status === "up").length; const upCount = vms.filter((v) => v.status === "up").length;
return ( return (
<div className="flex flex-col items-center gap-0"> <div className="flex flex-col items-center">
{/* Server card */}
<InfraCard node={server} /> <InfraCard node={server} />
<div className="w-px h-4 bg-border-light" />
{/* Vertical connector */} <span className="text-xs text-text-muted mb-2">
<div className="w-0.5 h-6 bg-border-light" /> {upCount}/{vms.length} activos
</span>
{/* VM count badge */} <div className="grid grid-cols-3 gap-1.5 w-full">
<div className="flex items-center gap-2 bg-bg-secondary border border-border rounded-full px-4 py-1.5 mb-2">
<span className="text-sm text-text-muted">
{upCount}/{vmCount} VMs
</span>
</div>
{/* VM grid */}
<div className="grid grid-cols-2 gap-2 max-w-[520px]">
{vms.map((vm) => ( {vms.map((vm) => (
<VmPill key={vm.ip + vm.name} node={vm} /> <VmPill key={vm.ip + vm.name} node={vm} />
))} ))}
@@ -93,14 +78,13 @@ function ProxmoxColumn({ server, vms }: { server: NetworkNode; vms: NetworkNode[
); );
} }
/* ── Vertical connector line ─────────────────────────────── */ /* ── Vertical line ───────────────────────────────────────── */
function VLine() { function VLine() {
return <div className="w-0.5 h-8 bg-border-light mx-auto" />; return <div className="w-px h-5 bg-border-light mx-auto" />;
} }
/* ── Main topology graph ─────────────────────────────────── */ /* ── Main topology ───────────────────────────────────────── */
export function NetworkGraph({ nodes }: NetworkGraphProps) { export function NetworkGraph({ nodes }: NetworkGraphProps) {
// Find nodes by role
const findByName = (name: string) => nodes.find((n) => n.name === name); const findByName = (name: string) => nodes.find((n) => n.name === name);
const modem = findByName("Router Telmex"); const modem = findByName("Router Telmex");
const firewall = findByName("Firewall OPNsense"); const firewall = findByName("Firewall OPNsense");
@@ -115,7 +99,6 @@ export function NetworkGraph({ nodes }: NetworkGraphProps) {
n.name !== "Switch Cisco" n.name !== "Switch Cisco"
); );
// Group VMs by parent
const vmsByParent = new Map<string, NetworkNode[]>(); const vmsByParent = new Map<string, NetworkNode[]>();
for (const node of nodes) { for (const node of nodes) {
if ((node.type === "vm" || node.type === "ct") && node.parent) { if ((node.type === "vm" || node.type === "ct") && node.parent) {
@@ -131,23 +114,22 @@ export function NetworkGraph({ nodes }: NetworkGraphProps) {
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
{/* Summary bar */} {/* Summary bar */}
<div className="flex items-center gap-10 px-16 py-4 bg-bg-secondary border-b border-border"> <div className="flex items-center gap-6 px-8 py-2 bg-bg-secondary border-b border-border">
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
<span className="w-4 h-4 rounded-full bg-success" /> <span className="w-3 h-3 rounded-full bg-success" />
<span className="text-xl text-text-secondary"> <span className="text-base text-text-secondary">
<span className="font-bold text-text-primary">{onlineCount}</span> online <span className="font-bold text-text-primary">{onlineCount}</span> online
</span> </span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-2">
<span className="w-4 h-4 rounded-full bg-danger" /> <span className="w-3 h-3 rounded-full bg-danger" />
<span className="text-xl text-text-secondary"> <span className="text-base text-text-secondary">
<span className="font-bold text-text-primary">{total - onlineCount}</span> offline <span className="font-bold text-text-primary">{total - onlineCount}</span> offline
</span> </span>
</div> </div>
<span className="text-border-light text-2xl">|</span> <span className="text-border-light">|</span>
<span className="text-xl text-text-secondary"> <span className="text-base text-text-secondary">
<span className="font-bold text-text-primary">{proxmoxServers.length}</span> Proxmox <span className="font-bold text-text-primary">{proxmoxServers.length}</span> Proxmox ·{" "}
<span className="mx-2">·</span>
<span className="font-bold text-text-primary"> <span className="font-bold text-text-primary">
{nodes.filter((n) => n.type === "vm" || n.type === "ct").length} {nodes.filter((n) => n.type === "vm" || n.type === "ct").length}
</span>{" "} </span>{" "}
@@ -155,65 +137,48 @@ export function NetworkGraph({ nodes }: NetworkGraphProps) {
</span> </span>
</div> </div>
{/* Diagram area */} {/* Diagram */}
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto flex flex-col items-center px-6 py-4">
<div className="flex flex-col items-center py-8 px-8"> {/* Top chain: Modem → Firewall → Switch */}
<div className="flex items-center gap-3">
{modem && <InfraCard node={modem} />}
<span className="text-border-light text-xl"></span>
{firewall && <InfraCard node={firewall} />}
<span className="text-border-light text-xl"></span>
{switchNode && <InfraCard node={switchNode} />}
</div>
{/* ── Level 1: Modem ── */} <VLine />
{modem && <InfraCard node={modem} isCenter />}
<VLine />
{/* ── Level 2: Firewall ── */} {/* Horizontal branch line */}
{firewall && <InfraCard node={firewall} isCenter />} <div className="w-3/4 h-px bg-border-light" />
<VLine />
{/* ── Level 3: Switch ── */} {/* Proxmox columns */}
{switchNode && <InfraCard node={switchNode} isCenter />} <div className="flex gap-6 w-full px-4 mt-0">
<VLine /> {proxmoxServers.map((server) => {
const vms = vmsByParent.get(server.name) || [];
{/* ── Branch lines to Proxmox servers ── */} return (
<div className="flex items-start justify-center w-full"> <div key={server.ip} className="flex-1 flex flex-col items-center">
{/* Horizontal bar spanning all columns */} <div className="w-px h-4 bg-border-light" />
<div className="relative flex justify-center" style={{ width: "100%", maxWidth: "3600px" }}> <ProxmoxColumn server={server} vms={vms} />
{/* Horizontal line */}
<div
className="absolute top-0 h-0.5 bg-border-light"
style={{
left: `${100 / (proxmoxServers.length * 2)}%`,
right: `${100 / (proxmoxServers.length * 2)}%`,
}}
/>
{/* Proxmox columns */}
<div className="flex justify-center gap-8 w-full pt-0">
{proxmoxServers.map((server) => {
const vms = vmsByParent.get(server.name) || [];
return (
<div key={server.ip} className="flex-1 flex flex-col items-center max-w-[600px]">
{/* Vertical connector from horizontal bar */}
<div className="w-0.5 h-8 bg-border-light" />
<ProxmoxColumn server={server} vms={vms} />
</div>
);
})}
</div> </div>
);
})}
</div>
{/* Other devices */}
{otherDevices.length > 0 && (
<div className="mt-4 w-full">
<p className="text-xs font-bold text-text-muted uppercase tracking-widest mb-2 text-center">
Otros dispositivos
</p>
<div className="flex justify-center gap-3 flex-wrap">
{otherDevices.map((node) => (
<InfraCard key={node.ip} node={node} />
))}
</div> </div>
</div> </div>
)}
{/* ── Other devices ── */}
{otherDevices.length > 0 && (
<div className="mt-8 w-full max-w-[3600px]">
<h3 className="text-base font-bold text-text-muted uppercase tracking-widest mb-4 text-center">
Otros dispositivos conectados al switch
</h3>
<div className="flex justify-center gap-4 flex-wrap">
{otherDevices.map((node) => (
<InfraCard key={node.ip} node={node} />
))}
</div>
</div>
)}
</div>
</div> </div>
</div> </div>
); );

View File

@@ -21,7 +21,7 @@
} }
html { html {
font-size: 24px; font-size: clamp(14px, 1.15vw, 24px);
} }
body { body {
@@ -30,11 +30,10 @@ body {
font-family: "Inter", system-ui, -apple-system, sans-serif; font-family: "Inter", system-ui, -apple-system, sans-serif;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
width: 3840px; width: 100vw;
height: 2160px; height: 100vh;
} }
/* Smooth scrollbar for overflow areas */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
} }