Ir al contenido
Evaluar mi proyecto
Componentes · Mundos de estiloDisponible

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.

12:00

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

mundo-win95.tsx116 líneas
Descargar
"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.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/mundo-win95

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

ejemplo.tsx5 líneas
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.

PropiedadTipoDescripción
className?stringOpcional.
style?CSSPropertiesOpcional.

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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)