Footer de terminal con árbol del sitio
Mapa del sitio como árbol de directorios con enlaces reales, prompt con cursor parpadeante y estado del sistema.
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
"use client";
import type { CSSProperties } from "react";
/** Footer de terminal: el mapa del sitio como un árbol de directorios con enlaces reales, prompt con cursor y estado del sistema. React + TypeScript, sin dependencias. */
export type FooterTerminalGrupo = { dir: string; links: { label: string; href?: string }[] };
export type FooterTerminalProps = {
user?: string;
groups?: FooterTerminalGrupo[];
status?: string;
legal?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcfte-root { box-sizing: border-box; container-type: inline-size; width: 100%; padding: clamp(22px, 4cqi, 48px); background: #07090b; color: #8be8a6; font: 500 14px/1.7 ui-monospace, "SF Mono", Menlo, Consolas, monospace; }
.gcfte-root *, .gcfte-root *::before, .gcfte-root *::after { box-sizing: border-box; }
.gcfte-root :focus-visible { outline: 1px dashed #8be8a6; outline-offset: 3px; }
.gcfte-root ::selection { background: #8be8a6; color: #07090b; }
@media (prefers-reduced-motion: reduce) { .gcfte-root *, .gcfte-root *::before, .gcfte-root *::after { animation: none !important; } }
.gcfte-prompt { margin: 0 0 14px; color: #c9f7d6; }
.gcfte-prompt b { color: #62b4ff; font-weight: 500; }
.gcfte-caret { display: inline-block; width: .6em; height: 1.1em; margin-left: 2px; vertical-align: text-bottom; background: #8be8a6; animation: gcfte-blink 1s steps(1) infinite; }
.gcfte-tree { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 18px 32px; margin-bottom: 22px; }
.gcfte-dir { margin: 0; color: #62b4ff; }
.gcfte-list { margin: 0; padding: 0; list-style: none; }
.gcfte-list li { position: relative; padding-left: 2.2ch; color: #4f8a63; }
.gcfte-list li::before { content: "├─"; position: absolute; left: 0; }
.gcfte-list li:last-child::before { content: "└─"; }
.gcfte-list a { color: #8be8a6; text-decoration: none; }
.gcfte-list a:hover { background: #8be8a6; color: #07090b; }
.gcfte-foot { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 24px; padding-top: 14px; border-top: 1px dashed #1e3a2a; font-size: 13px; color: #4f8a63; }
.gcfte-dot { display: inline-block; width: 8px; height: 8px; margin-right: 8px; border-radius: 50%; background: #8be8a6; box-shadow: 0 0 8px #8be8a6; animation: gcfte-pulse 2s ease-in-out infinite; }
@keyframes gcfte-blink { 50% { opacity: 0; } }
@keyframes gcfte-pulse { 50% { opacity: .35; } }`;
const DEFAULT_GROUPS: FooterTerminalGrupo[] = [
{ dir: "producto/", links: [{ label: "funciones.md" }, { label: "precios.md" }, { label: "cambios.log" }] },
{ dir: "empresa/", links: [{ label: "nosotros.md" }, { label: "contacto.md" }, { label: "trabajo.md" }] },
{ dir: "legal/", links: [{ label: "privacidad.txt" }, { label: "terminos.txt" }] },
];
export function FooterTerminal({ user = "visitante@sitio", groups = DEFAULT_GROUPS, status = "todos los sistemas operativos", legal = "© 2026 · hecho con cariño y café", className = "", style }: FooterTerminalProps) {
return (
<footer className={`gcfte-root ${className}`} style={style}>
<style>{CSS}</style>
<p className="gcfte-prompt">
<b>{user}</b>:~$ tree --mapa-del-sitio
</p>
<div className="gcfte-tree">
{groups.map((group) => (
<nav key={group.dir} aria-label={group.dir}>
<p className="gcfte-dir">{group.dir}</p>
<ul className="gcfte-list">
{group.links.map((link) => (
<li key={link.label}>
<a href={link.href ?? "#"} onClick={(event) => !link.href && event.preventDefault()}>
{link.label}
</a>
</li>
))}
</ul>
</nav>
))}
</div>
<p className="gcfte-prompt">
<b>{user}</b>:~$ <span className="gcfte-caret" aria-hidden="true" />
</p>
<div className="gcfte-foot">
<span>
<i className="gcfte-dot" aria-hidden="true" />
{status}
</span>
<span>{legal}</span>
</div>
</footer>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/footer-terminalEjemplo de uso de Footer de terminal con árbol del sitio
Después de copiar footer-terminal.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { FooterTerminal } from "./footer-terminal";
export default function Ejemplo() {
return <FooterTerminal user="Ejemplo" />;
}Propiedades de FooterTerminal
FooterTerminal acepta 6 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
user? | string | Opcional. |
groups? | FooterTerminalGrupo[] | Opcional. |
status? | string | Opcional. |
legal? | string | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Accesibilidad y rendimiento
El archivo tiene 81 líneas (4 KB) y define 2 animaciones con @keyframes. Esto es lo que hace Footer de terminal con árbol del sitio por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Muestra un anillo de foco visible al navegar con teclado (:focus-visible).
- Expone su estado a tecnologías de apoyo con atributos aria-*.
- Es un componente de cliente de Next.js: incluye la directiva "use client".