Ir al contenido
Evaluar mi proyecto
Componentes · Mundos de estiloDisponible

Mundo skeuomórfico: libreta de notas

Encuadernación de cuero cosido, papel amarillo rayado con margen rojo, tipografía manuscrita y casillas que se tachan.

Pendientes de hoy

Código completo

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

mundo-skeuo-notas.tsx88 líneas
Descargar
"use client";

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

/** Mundo skeuomórfico: libreta de notas con encuadernación de cuero cosido, papel amarillo rayado con margen rojo, tipografía manuscrita y casillas que se tachan. Se pueden añadir y marcar tareas. React + TypeScript, sin dependencias. */
export type MundoSkeuoNotasProps = {
  className?: string;
  style?: CSSProperties;
};

const GRAIN = "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' seed='4'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 .5 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\")";

const CSS = `
.gcmsn-root { box-sizing: border-box; container-type: inline-size; width: 100%; padding: clamp(18px, 4cqi, 40px); background: radial-gradient(120% 120% at 50% 0%, #b79b78, #8a6d4a); font: 500 15px/1.3 ui-sans-serif, system-ui, sans-serif; }
.gcmsn-root *, .gcmsn-root *::before, .gcmsn-root *::after { box-sizing: border-box; }
.gcmsn-root button, .gcmsn-root input { font: inherit; }
.gcmsn-root :focus-visible { outline: 2px solid #1b4fd8; outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .gcmsn-root * { transition-duration: .01ms !important; animation: none !important; } }
.gcmsn-pad { position: relative; max-width: 420px; margin: 0 auto; border-radius: 6px 6px 12px 12px; background: ${GRAIN}, #fff3a6; background-blend-mode: multiply, normal; box-shadow: 0 2px 0 #cdbb5a, 0 22px 36px -12px rgba(0, 0, 0, .5); transform: rotate(-.8deg); }
.gcmsn-bind { position: relative; height: 44px; border-radius: 10px 10px 0 0; background: ${GRAIN}, linear-gradient(180deg, #6b3a1c, #4c2610); background-blend-mode: multiply, normal; box-shadow: inset 0 2px 0 rgba(255, 210, 160, .3), inset 0 -3px 5px rgba(0, 0, 0, .5); }
.gcmsn-bind::after { content: ""; position: absolute; left: 14px; right: 14px; bottom: 9px; border-bottom: 2px dashed rgba(240, 200, 150, .8); filter: drop-shadow(0 1px 0 rgba(0, 0, 0, .6)); }
.gcmsn-holes { position: absolute; left: 0; right: 0; top: 44px; display: flex; justify-content: space-around; padding: 0 30px; transform: translateY(-50%); }
.gcmsn-holes i { width: 14px; height: 14px; border-radius: 50%; background: #3a2a1c; box-shadow: inset 0 2px 3px #000, 0 1px 0 rgba(255, 255, 255, .3); }
.gcmsn-sheet { position: relative; padding: 22px 22px 24px 62px; background: repeating-linear-gradient(180deg, transparent 0 33px, rgba(70, 120, 200, .35) 33px 34px); background-position: 0 14px; }
.gcmsn-sheet::before { content: ""; position: absolute; left: 50px; top: 0; bottom: 0; width: 2px; background: rgba(220, 70, 70, .55); }
.gcmsn-title { margin: 0 0 6px; font: 700 28px/34px "Caveat", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive; color: #1b3a8a; transform: rotate(-1deg); }
.gcmsn-list { margin: 0; padding: 0; list-style: none; }
.gcmsn-item { display: flex; align-items: center; gap: 10px; min-height: 34px; font: 600 22px/34px "Caveat", "Bradley Hand", "Segoe Script", "Comic Sans MS", cursive; color: #1b2a5a; }
.gcmsn-item label { display: flex; align-items: center; gap: 10px; cursor: pointer; flex: 1; position: relative; }
.gcmsn-item input { position: absolute; opacity: 0; width: 24px; height: 24px; margin: 0; }
.gcmsn-box { flex: none; width: 22px; height: 22px; border: 2px solid #1b2a5a; border-radius: 3px 5px 3px 6px; background: rgba(255, 255, 255, .45); position: relative; }
.gcmsn-box::after { content: ""; position: absolute; left: 3px; top: 0; width: 8px; height: 14px; border: solid #c62828; border-width: 0 3px 3px 0; transform: rotate(40deg) scale(0); transition: transform .25s cubic-bezier(.34, 1.56, .64, 1); }
.gcmsn-item input:checked + .gcmsn-box::after { transform: rotate(40deg) scale(1); }
.gcmsn-item input:focus-visible + .gcmsn-box { outline: 2px solid #1b4fd8; outline-offset: 2px; }
.gcmsn-txt { position: relative; }
.gcmsn-txt::after { content: ""; position: absolute; left: -2px; right: -2px; top: 54%; height: 2px; background: #1b2a5a; transform: scaleX(0); transform-origin: left; transition: transform .3s; }
.gcmsn-item input:checked ~ .gcmsn-txt { opacity: .6; }
.gcmsn-item input:checked ~ .gcmsn-txt::after { transform: scaleX(1); }
.gcmsn-add { display: flex; gap: 8px; margin-top: 4px; }
.gcmsn-in { flex: 1; min-width: 0; height: 34px; border: 0; background: transparent; color: #1b2a5a; font: 600 22px/34px "Caveat", "Bradley Hand", "Segoe Script", cursive; outline: 0; border-bottom: 2px dotted rgba(27, 42, 90, .5); }
.gcmsn-in::placeholder { color: rgba(27, 42, 90, .45); }
.gcmsn-go { width: 34px; height: 34px; border: 0; border-radius: 50%; background: #1b3a8a; color: #fff; cursor: pointer; box-shadow: 0 2px 0 #0d1f52; }
.gcmsn-go:active { transform: translateY(2px); box-shadow: none; }`;

export function MundoSkeuoNotas({ className = "", style }: MundoSkeuoNotasProps) {
  const uid = useId();
  const [items, setItems] = useState([{ id: 1, t: "Llamar al proveedor", done: true }, { id: 2, t: "Revisar la propuesta", done: false }, { id: 3, t: "Enviar los accesos", done: false }]);
  const [text, setText] = useState("");
  function add() {
    const t = text.trim();
    if (!t) return;
    setItems((list) => [...list, { id: Date.now(), t, done: false }]);
    setText("");
  }
  return (
    <div className={`gcmsn-root ${className}`} style={style}>
      <style>{CSS}</style>
      <div className="gcmsn-pad">
        <div className="gcmsn-bind" aria-hidden="true" />
        <div className="gcmsn-holes" aria-hidden="true">
          {Array.from({ length: 6 }, (_, i) => (
            <i key={i} />
          ))}
        </div>
        <div className="gcmsn-sheet">
          <h3 className="gcmsn-title">Pendientes de hoy</h3>
          <ul className="gcmsn-list">
            {items.map((item) => (
              <li key={item.id} className="gcmsn-item">
                <label>
                  <input type="checkbox" checked={item.done} onChange={() => setItems((list) => list.map((i) => (i.id === item.id ? { ...i, done: !i.done } : i)))} />
                  <span className="gcmsn-box" />
                  <span className="gcmsn-txt">{item.t}</span>
                </label>
              </li>
            ))}
          </ul>
          <form className="gcmsn-add" onSubmit={(e) => { e.preventDefault(); add(); }}>
            <label htmlFor={`${uid}-n`} style={{ position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)" }}>Nueva tarea</label>
            <input id={`${uid}-n`} className="gcmsn-in" placeholder="Anota algo…" value={text} onChange={(e) => setText(e.target.value)} />
            <button className="gcmsn-go" type="submit" aria-label="Añadir tarea">+</button>
          </form>
        </div>
      </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-skeuo-notas

Ejemplo de uso de Mundo skeuomórfico: libreta de notas

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

ejemplo.tsx5 líneas
import { MundoSkeuoNotas } from "./mundo-skeuo-notas";

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

Propiedades de MundoSkeuoNotas

MundoSkeuoNotas 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 88 líneas (7 KB) y no necesita @keyframes. Esto es lo que hace Mundo skeuomórfico: libreta de notas 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".
Licencia
MIT
Dependencias
React >=18
Compatibilidad
React 18 y 19 · Next.js (componente de cliente)