Mundo retro: escritorio Windows 95
Escritorio verde azulado con ventana de archivos, biseles, barra de tareas con reloj real y ventana que se minimiza y se cierra.
Mis documentos
NombreTamañoTipo
4 objeto(s)42 KB
12:00
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import { useEffect, useState, type CSSProperties } from "react";
/** Mundo retro Windows 95: escritorio verde azulado con ventana de archivos (selección y vista de detalles), botones con biseles, barra de tareas con reloj real y la ventana que se minimiza, restaura y cierra. React + TypeScript, sin dependencias. */
export type MundoWin95Props = {
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcmw9-root { box-sizing: border-box; container-type: inline-size; position: relative; width: 100%; min-height: 440px; padding: 22px 22px 52px; background: #008080; color: #000; font: 400 13px/1.2 "MS Sans Serif", Tahoma, Geneva, "Segoe UI", sans-serif; overflow: hidden; user-select: none; }
.gcmw9-root *, .gcmw9-root *::before, .gcmw9-root *::after { box-sizing: border-box; }
.gcmw9-root button { font: inherit; }
.gcmw9-root :focus-visible { outline: 1px dotted #000; outline-offset: -4px; }
.gcmw9-bevel { background: #c0c0c0; box-shadow: inset -1px -1px 0 #0a0a0a, inset 1px 1px 0 #fff, inset -2px -2px 0 #808080, inset 2px 2px 0 #dfdfdf; }
.gcmw9-btn { min-width: 75px; min-height: 23px; padding: 0 12px; border: 0; background: #c0c0c0; color: #000; cursor: default; box-shadow: inset -1px -1px 0 #0a0a0a, inset 1px 1px 0 #fff, inset -2px -2px 0 #808080, inset 2px 2px 0 #dfdfdf; }
.gcmw9-btn:active { box-shadow: inset 1px 1px 0 #0a0a0a, inset -1px -1px 0 #fff, inset 2px 2px 0 #808080, inset -2px -2px 0 #dfdfdf; padding: 1px 11px 0 13px; }
.gcmw9-icons { position: absolute; left: 22px; top: 22px; display: grid; gap: 16px; }
.gcmw9-ico { display: grid; justify-items: center; gap: 4px; width: 76px; padding: 0; border: 0; background: none; color: #fff; text-align: center; cursor: default; }
.gcmw9-ico span:first-child { display: grid; place-items: center; width: 32px; height: 32px; }
.gcmw9-ico:focus-visible span:last-child, .gcmw9-ico[aria-pressed="true"] span:last-child { background: #000080; outline: 1px dotted #fff; }
.gcmw9-win { position: relative; margin: 0 0 0 96px; max-width: 460px; padding: 3px; }
.gcmw9-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; height: 20px; padding: 2px 3px 2px 4px; background: linear-gradient(90deg, #000080, #1084d0); color: #fff; font-weight: 700; }
.gcmw9-title span { display: flex; align-items: center; gap: 6px; }
.gcmw9-ctl { display: flex; gap: 2px; }
.gcmw9-ctl button { display: grid; place-items: center; width: 16px; height: 14px; padding: 0; border: 0; background: #c0c0c0; color: #000; font: 700 9px/1 sans-serif; box-shadow: inset -1px -1px 0 #0a0a0a, inset 1px 1px 0 #fff, inset -2px -2px 0 #808080, inset 2px 2px 0 #dfdfdf; cursor: default; }
.gcmw9-menu { display: flex; gap: 14px; padding: 3px 6px; }
.gcmw9-pane { margin: 0 2px 2px; min-height: 190px; background: #fff; box-shadow: inset 1px 1px 0 #0a0a0a, inset -1px -1px 0 #fff, inset 2px 2px 0 #808080, inset -2px -2px 0 #dfdfdf; }
.gcmw9-th, .gcmw9-tr { display: grid; grid-template-columns: 1fr 70px 100px; align-items: center; padding: 3px 8px; }
.gcmw9-th { background: #c0c0c0; box-shadow: inset -1px -1px 0 #0a0a0a, inset 1px 1px 0 #fff; font-weight: 400; }
.gcmw9-tr { width: 100%; border: 0; background: #fff; color: #000; text-align: left; cursor: default; }
.gcmw9-tr[aria-selected="true"] { background: #000080; color: #fff; }
.gcmw9-status { display: flex; justify-content: space-between; padding: 3px 6px; }
.gcmw9-task { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 4px; height: 38px; padding: 4px; background: #c0c0c0; box-shadow: inset 0 1px 0 #dfdfdf, 0 -1px 0 #fff; }
.gcmw9-start { display: flex; align-items: center; gap: 6px; min-height: 28px; padding: 0 8px; font-weight: 700; }
.gcmw9-tbtn { min-width: 150px; min-height: 28px; text-align: left; font-weight: 700; }
.gcmw9-tbtn[aria-pressed="true"] { box-shadow: inset 1px 1px 0 #0a0a0a, inset -1px -1px 0 #fff, inset 2px 2px 0 #808080; background: repeating-conic-gradient(#c0c0c0 0 25%, #fff 0 50%) 0 0 / 2px 2px; }
.gcmw9-clock { margin-left: auto; padding: 4px 10px; box-shadow: inset 1px 1px 0 #808080, inset -1px -1px 0 #fff; }`;
const FILES = [
{ n: "Propuesta.doc", s: "42 KB", t: "Documento" },
{ n: "Presupuesto.xls", s: "18 KB", t: "Hoja de cálculo" },
{ n: "Notas.txt", s: "2 KB", t: "Texto" },
{ n: "Logo.bmp", s: "310 KB", t: "Imagen" },
];
const FolderIcon = (
<svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><path d="M2 8h10l3 3h15v16H2z" fill="#ffe14d" stroke="#000" strokeWidth="1" /></svg>
);
export function MundoWin95({ className = "", style }: MundoWin95Props) {
const [open, setOpen] = useState(true);
const [min, setMin] = useState(false);
const [sel, setSel] = useState(0);
const [now, setNow] = useState("12:00");
useEffect(() => {
const tick = () => setNow(new Date().toLocaleTimeString("es-MX", { hour: "2-digit", minute: "2-digit" }));
tick();
const id = window.setInterval(tick, 20000);
return () => window.clearInterval(id);
}, []);
const visible = open && !min;
return (
<div className={`gcmw9-root ${className}`} style={style}>
<style>{CSS}</style>
<div className="gcmw9-icons">
<button className="gcmw9-ico" type="button" onDoubleClick={() => { setOpen(true); setMin(false); }} onClick={() => { setOpen(true); setMin(false); }} aria-label="Abrir Mis documentos">
<span>{FolderIcon}</span>
<span>Mis documentos</span>
</button>
<button className="gcmw9-ico" type="button" aria-label="Papelera de reciclaje">
<span>
<svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><path d="M8 8h16l-2 20H10z" fill="#c0c0c0" stroke="#000" /><path d="M6 8h20" stroke="#000" strokeWidth="2" /></svg>
</span>
<span>Papelera</span>
</button>
</div>
{visible ? (
<div className="gcmw9-win gcmw9-bevel" role="dialog" aria-label="Mis documentos">
<div className="gcmw9-title">
<span>{FolderIcon && <svg width="14" height="14" viewBox="0 0 32 32" aria-hidden="true"><path d="M2 8h10l3 3h15v16H2z" fill="#ffe14d" /></svg>}Mis documentos</span>
<div className="gcmw9-ctl">
<button type="button" aria-label="Minimizar" onClick={() => setMin(true)}>_</button>
<button type="button" aria-label="Maximizar">□</button>
<button type="button" aria-label="Cerrar" onClick={() => setOpen(false)}>✕</button>
</div>
</div>
<div className="gcmw9-menu"><span>Archivo</span><span>Edición</span><span>Ver</span><span>Ayuda</span></div>
<div className="gcmw9-pane" role="listbox" aria-label="Archivos">
<div className="gcmw9-th"><span>Nombre</span><span>Tamaño</span><span>Tipo</span></div>
{FILES.map((f, i) => (
<button key={f.n} className="gcmw9-tr" type="button" role="option" aria-selected={sel === i} onClick={() => setSel(i)}>
<span>{f.n}</span>
<span>{f.s}</span>
<span>{f.t}</span>
</button>
))}
</div>
<div className="gcmw9-status"><span>{FILES.length} objeto(s)</span><span>{FILES[sel].s}</span></div>
</div>
) : null}
<div className="gcmw9-task">
<button className="gcmw9-btn gcmw9-start" type="button">
<svg width="16" height="14" viewBox="0 0 16 14" aria-hidden="true"><path d="M1 2l6-1v6H1zM8 1l7-1v7H8zM1 8h6v5l-6-1zM8 8h7v6l-7-1z" fill="#c00" /></svg>
Inicio
</button>
{open ? (
<button className="gcmw9-btn gcmw9-tbtn" type="button" aria-pressed={visible} onClick={() => setMin((v) => !v)}>Mis documentos</button>
) : null}
<span className="gcmw9-clock">{now}</span>
</div>
</div>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/mundo-win95Ejemplo de uso de Mundo retro: escritorio Windows 95
Después de copiar mundo-win95.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { MundoWin95 } from "./mundo-win95";
export default function Ejemplo() {
return <MundoWin95 />;
}Propiedades de MundoWin95
MundoWin95 acepta 2 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Accesibilidad y rendimiento
El archivo tiene 116 líneas (8 KB) y no necesita @keyframes. Esto es lo que hace Mundo retro: escritorio Windows 95 por quien lo usa:
- Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
- Declara roles ARIA para lectores de pantalla: dialog, listbox, option.
- Es un componente de cliente de Next.js: incluye la directiva "use client".