Ir al contenido
Evaluar mi proyecto
Componentes · Progreso y cargaDisponible

Barras de vida y experiencia 8 bits

Bloques de píxeles que se llenan, marco blanco y parpadeo en estado crítico, como un videojuego retro.

HéroeNv 7
HP
72
XP
40

Código completo

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

progreso-pixel.tsx71 líneas
Descargar
"use client";

import { useEffect, useState, type CSSProperties } from "react";

/** Barras de vida y experiencia estilo videojuego de 8 bits, con bloques que se llenan, marco de píxeles y parpadeo en estado crítico. React + TypeScript, sin dependencias. */
export type ProgresoPixelProps = {
  hp?: number;
  xp?: number;
  name?: string;
  level?: number;
  simulate?: boolean;
  className?: string;
  style?: CSSProperties;
};

const CSS = `
.gcprx-root { box-sizing: border-box; display: grid; gap: 14px; width: 100%; max-width: 380px; padding: 18px; background: #1b1034; color: #fff; font: 700 13px/1 "Press Start 2P", ui-monospace, "SF Mono", Menlo, Consolas, monospace; letter-spacing: .04em; text-transform: uppercase; image-rendering: pixelated; box-shadow: 0 -4px 0 0 #fff, 0 4px 0 0 #fff, -4px 0 0 0 #fff, 4px 0 0 0 #fff, 0 -8px 0 -2px #14081f, 0 8px 0 -2px #14081f, -8px 0 0 -2px #14081f, 8px 0 0 -2px #14081f; }
.gcprx-root *, .gcprx-root *::before, .gcprx-root *::after { box-sizing: border-box; }
@media (prefers-reduced-motion: reduce) { .gcprx-root * { animation: none !important; } }
.gcprx-top { display: flex; justify-content: space-between; align-items: center; }
.gcprx-row { display: grid; grid-template-columns: 34px 1fr 48px; align-items: center; gap: 10px; }
.gcprx-lab { color: #ffd23f; }
.gcprx-bar { display: grid; grid-template-columns: repeat(20, 1fr); gap: 2px; height: 20px; padding: 3px; background: #0b0517; box-shadow: 0 -2px 0 0 #fff, 0 2px 0 0 #fff, -2px 0 0 0 #fff, 2px 0 0 0 #fff; }
.gcprx-bar i { background: #2a1950; }
.gcprx-bar i[data-on="true"] { background: var(--c); box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .3), inset 0 3px 0 rgba(255, 255, 255, .3); }
.gcprx-row[data-low="true"] .gcprx-bar i[data-on="true"] { animation: gcprx-blink .5s steps(2, jump-none) infinite; }
.gcprx-num { text-align: right; font-variant-numeric: tabular-nums; }
@keyframes gcprx-blink { 50% { opacity: .35; } }`;

export function ProgresoPixel({ hp, xp, name = "Héroe", level = 7, simulate = true, className = "", style }: ProgresoPixelProps) {
  const [aHp, setHp] = useState(72);
  const [aXp, setXp] = useState(40);
  useEffect(() => {
    if ((hp !== undefined && xp !== undefined) || !simulate || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    const id = window.setInterval(() => {
      if (document.hidden) return;
      setHp((v) => (v <= 8 ? 100 : Math.max(5, v - 6 - Math.random() * 10)));
      setXp((v) => (v >= 100 ? 0 : Math.min(100, v + 5 + Math.random() * 8)));
    }, 900);
    return () => window.clearInterval(id);
  }, [hp, xp, simulate]);
  const h = Math.round(Math.max(0, Math.min(100, hp ?? aHp)));
  const x = Math.round(Math.max(0, Math.min(100, xp ?? aXp)));
  const bar = (v: number, c: string, label: string) => (
    <div className="gcprx-bar" role="progressbar" aria-valuemin={0} aria-valuemax={100} aria-valuenow={v} aria-label={label} style={{ "--c": c } as CSSProperties}>
      {Array.from({ length: 20 }, (_, i) => (
        <i key={i} data-on={i < Math.round(v / 5)} />
      ))}
    </div>
  );
  return (
    <div className={`gcprx-root ${className}`} style={style}>
      <style>{CSS}</style>
      <div className="gcprx-top">
        <span>{name}</span>
        <span className="gcprx-lab">Nv {level}</span>
      </div>
      <div className="gcprx-row" data-low={h < 25}>
        <span className="gcprx-lab">HP</span>
        {bar(h, h < 25 ? "#ff4d4d" : "#4ddc6a", "Vida")}
        <span className="gcprx-num">{h}</span>
      </div>
      <div className="gcprx-row">
        <span className="gcprx-lab">XP</span>
        {bar(x, "#5aa8ff", "Experiencia")}
        <span className="gcprx-num">{x}</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/progreso-pixel

Ejemplo de uso de Barras de vida y experiencia 8 bits

Después de copiar progreso-pixel.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
import { ProgresoPixel } from "./progreso-pixel";

export default function Ejemplo() {
  return <ProgresoPixel simulate />;
}

Propiedades de ProgresoPixel

ProgresoPixel acepta 7 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.

PropiedadTipoDescripción
hp?numberOpcional.
xp?numberOpcional.
name?stringOpcional.
level?numberOpcional.
simulate?booleanOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

Accesibilidad y rendimiento

El archivo tiene 71 líneas (4 KB) y define 1 animación con @keyframes. Esto es lo que hace Barras de vida y experiencia 8 bits por quien lo usa:

  • Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
  • Declara roles ARIA para lectores de pantalla: progressbar.
  • 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)