Communitygithub.com

OthmanAdi/bookshelf

Agrega una estantería 3D a un proyecto web: una fila arrastrable de lomos de libros para un gestor de marcadores, una lista de lectura o una lista de

¿Qué es bookshelf?

Este skill implementa una estantería 3D de libros completamente funcional para navegadores, construida con TypeScript puro y sin dependencias de tiempo de ejecución. Está diseñada para integrarse en proyectos web como un gestor de marcadores, una lista de lectura o un carrusel de portadas. Se compone de cuatro módulos independientes que pueden usarse por separado o juntos: `shelf` (renderizado CSS 3D de libros), `motion` (motor de física para arrastre y resorte), `store` (almacén local versionado con importación/exportación) y `reader` (visor de texto seguro que recuerda la posición). La habilidad guía al agente para ubicar el código fuente (ya sea junto al skill, mediante una ruta proporcionada por el usuario, o clonando el repositorio público previo consentimiento) y luego copiar solo los módulos necesarios según lo que el usuario solicite. Incluye reglas precisas para evitar problemas comunes como la pérdida del contexto 3D por propiedades CSS incorrectas, manejo táctil adecuado, y protección contra contenido remoto o scripts. Es ideal para desarrolladores que buscan una experiencia de biblioteca inmersiva sin frameworks pesados ni WebGL.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OthmanAdi/bookmark-bookshelf/tree/HEAD/.agents/skills/bookshelf

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Estantería de Marcadores (Bookmark Bookshelf)

Cuatro pequeños módulos TypeScript sin dependencias de ejecución. Cada uno funciona por sí solo. Juntos forman una estantería de enlaces guardados que puedes arrastrar, abrir y leer. Este skill describe la versión 0.2.0.

MóduloCarpetaFunciónNo sabe nada de
shelfsrc/shelfDibuja volúmenes para una pose, reporta intenciones (foco, abrir, paso, salto)tiempo, gestos, almacenamiento
motionsrc/motionResortes, arrastre, impulso, ajuste, progreso de apertura y cierrelibros, estructura DOM
storesrc/storeBiblioteca versionada en almacenamiento local: validación, migración, importación y exportación, búsqueda, ordenación, una estantería como archivo o enlaceDOM, renderizado
readersrc/readerMuestra texto guardado como nodos de texto, rastrea y restaura la posición, tamaño de texto, notas privadas, "Marcar como leído"estanterías, almacenamiento

Los módulos no están dentro de esta carpeta de skill. Viven en un checkout del repositorio Bookmark Bookshelf, y la primera tarea es encontrarlo. Copia las carpetas de los módulos desde allí y no las vuelvas a escribir.

0. Encontrar la raíz del código fuente

Prueba las rutas en este orden y detente en la primera que funcione.

  1. Junto al skill. Si este archivo está en <raíz>/.agents/skills/bookshelf/SKILL.md y <raíz>/src/shelf/index.ts existe, <raíz> es la raíz del código fuente. Este es el caso cuando el skill se usa en su lugar, y cuando se instaló como un plugin que conserva todo el repositorio.

  2. Una ruta que proporcione la persona. Si el skill se copió en otro lugar (la carpeta de skills de un agente), la persona puede tener ya un checkout. Si la solicitud contiene una ruta, úsala como raíz del código fuente. Si no, pide una, y ofrece la ruta 3 en la misma pregunta.

  3. Un clon superficial, solo después de que la persona acepte. Pregunta primero, y di claramente lo que sucederá: el repositorio público https://github.com/OthmanAdi/bookmark-bookshelf se clona en la etiqueta de versión v0.2.0 en una nueva carpeta temporal, y no se descarga nada más. No obtengas nada antes de que la persona diga que sí. Después de un sí, ejecuta:

    git clone --depth 1 --branch v0.2.0 https://github.com/OthmanAdi/bookmark-bookshelf <carpeta temporal>
    

    La carpeta temporal es un directorio nuevo y vacío fuera del proyecto (el directorio temporal del sistema está bien). Indica a la persona dónde está. Es la raíz del código fuente. Copia desde allí, nunca edites dentro de ella, y di al final que se puede eliminar. Si el clon falla (sin red, sin git, etiqueta faltante), dilo y vuelve a la ruta 2.

  4. Verifica antes de copiar. En la raíz del código fuente:

    • package.json tiene "name": "bookmark-bookshelf". Anota su version.
    • Estos marcadores existen: src/shelf/index.ts, src/motion/index.ts, src/store/index.ts, src/reader/index.ts, src/app/bookshelf.ts, src/app/bookshelf.css, src/app/flight.ts, src/styles/tokens.css.
    • Si es un checkout de git, anota git -C <raíz> rev-parse --short HEAD.

    Si falta un marcador, detente y di cuál. Este skill describe la versión 0.2.x. Para otra versión, lee el types.ts de cada módulo antes de confiar en los archivos de referencia aquí: 0.1.0 no tiene flight.ts, y sus contratos de estantería, almacén y lector difieren (ver el final de references/modules.md).

  5. Informa la ruta de origen, versión y commit desde los que copiaste, y si fue un clon que hiciste, para que una actualización posterior pueda compararse.

Cada ruta a continuación que comienza con src/, examples/ o public/ es relativa a esa raíz del código fuente.

1. Decidir el alcance primero

Pregunta qué necesita la persona, luego toma solo eso.

Ellos quierenTomaTambién toma
Una fila 3D de lomos que se pueda arrastrarshelf, motionsrc/styles/tokens.css
Una estantería con su propio manejo de clics, sin arrastreshelftokens.css
Una vista de lectura con reanudaciónreadertokens.css, las reglas .bb-button, .bb-label y .bb-visually-hidden de src/styles/base.css
Una biblioteca de marcadores sin interfaz de usuario: importar, exportar, buscar, compartir una estanteríastorenada
Arrastre, impulso y ajuste para alguna otra filamotionnada
Una estantería que se abre en un lector, con sus propios datosshelf, motion, readertokens.css, las mismas tres reglas de base.css
Toda la composiciónlos cuatro, src/app/bookshelf.ts, src/app/bookshelf.css, src/app/flight.tssrc/styles/*, src/data/* si se desean los enlaces de ejemplo

Los módulos nunca se importan entre sí. Si aparece una importación entre dos carpetas de módulos, la integración es incorrecta: el punto de encuentro pertenece al propio archivo de composición del anfitrión (ver src/app/bookshelf.ts).

Toda la composición son siete cosas, y es fácil que falte una: las cuatro carpetas de módulos, src/app/bookshelf.ts, su hoja de estilo src/app/bookshelf.css, y src/app/flight.ts, que transporta la portada entre la estantería y la página. El script importa los otros dos, por lo que una compilación sin ellos falla con un error de archivo faltante. Copia los tres archivos juntos, en una carpeta que esté junto a las carpetas de módulos.

La aplicación que rodea la composición (la barra y sus menús, los diálogos para agregar enlaces, importar muchos y compartir una estantería, la vista de lista) es el resto de src/app/ junto con app.html. Esos archivos dependen unos de otros y de los ids de elementos en app.html. Tómalos en su totalidad y adáptalos, o construye tu propia interfaz sobre las funciones del almacén, como se muestra en references/composition.md.

2. Incorporar los archivos

  1. Copia cada carpeta elegida de src/ sin cambios, incluidas las pruebas (*.test.ts), en el árbol fuente del anfitrión.
  2. Copia src/styles/tokens.css y cárgalo una vez. Los módulos leen las propiedades personalizadas --bb-* para color, tipo, espaciado y temporización. Para cambiar el estilo, anula los tokens y no edites el CSS de los módulos. Los tokens solo definen valores: el fondo de página, la fuente del cuerpo y los resets viven en src/styles/base.css. En un proyecto vacío, copia también ese archivo (importa tokens.css y fonts.css, así que elimina la importación de fuentes si omites el paso 3), o establece background: var(--bb-paper); color: var(--bb-ink); font-family: var(--bb-font-serif) en body tú mismo.
  3. Las fuentes son opcionales. src/styles/fonts.css incluye Newsreader e IBM Plex Mono (SIL OFL 1.1) y necesita dos dependencias de desarrollo: @fontsource-variable/newsreader y @fontsource/ibm-plex-mono. Si las incluyes, incluye también los textos de licencia en public/licenses/. Sin ellas, elimina la línea @import './fonts.css' de base.css y las pilas de tokens recurren a las fuentes del sistema.
  4. shelf.ts y reader.ts importan su CSS (import './shelf.css'). Esto necesita un empaquetador que maneje importaciones de CSS (Vite, webpack, Parcel, esbuild con un cargador CSS). Sin uno, elimina esas líneas de importación y enlaza los archivos CSS desde la página: shelf.css, cover.css y variants.css para la estantería, reader.css y reader-text.css para el lector.
  5. TypeScript: el código está escrito para strict, noUncheckedIndexedAccess y verbatimModuleSyntax, con moduleResolution: "bundler". También compila con configuraciones más flexibles.
  6. Herramientas para un proyecto vacío: npm i -D vite typescript vitest happy-dom @playwright/test, Node ^22.12.0 || ^24.0.0 || >=26.0.0. references/checks.md tiene la configuración mínima de Vitest y Playwright.

Anfitriones de frameworks (React, Vue, Svelte): cada módulo es mount(element, options) que devuelve un manejador con destroy(). Monta en el hook "después del primer renderizado" del framework y destruye en su limpieza. references/composition.md tiene una función simple para envolver.

3. Cablear

Lee references/modules.md para los contratos (entradas, salidas, eventos) y references/composition.md para el cableado listo para copiar de cada combinación. La más corta útil, estantería más movimiento:

import { createShelfMotion, prefersReducedMotion } from './motion';
import { mountShelf } from './shelf';

const motion = createShelfMotion({ count: items.length, reducedMotion: prefersReducedMotion });
const shelf = mountShelf(host, {
  items,
  label: 'Mi estantería',
  onLoopChange: (bucle) => motion.setLoop(bucle), // la estantería decide si la fila se repite
});
motion.setLoop(shelf.loop);
motion.setTiming(shelf.variant.motion);

motion.subscribe((state) => {
  shelf.setPose({ position: state.position, open: 0, reduced: state.reduced });
  shelf.element.dataset.settled = state.settled ? '1' : '0'; // permite que las pruebas esperen al reposo
});
motion.attach(shelf.surface, {
  pxPerItem: () => shelf.pitch(),
  extent: () => shelf.surface.clientWidth,
  enabled: () => !shelf.fixed, // una fila corta permanece quieta, por lo que un arrastre no debe moverla
});

shelf.on((intent) => {
  if (intent.type === 'focus') motion.goTo(intent.slot);
  else if (intent.type === 'step') motion.step(intent.direction);
  else if (intent.type === 'jump') motion.goTo(intent.to === 'first' ? 0 : items.length - 1);
  else openSomething(intent.id);
});

items es un array de { id, title, source?, author?, cloth?, pattern?, seed?, progress?, thickness?, stature?, outline?, note?, done? }. Las versiones ejecutables de las combinaciones están en examples/.

El bucle lo decide la estantería. Una fila se repite solo cuando sus volúmenes llenan al menos cuatro quintos de la escena. Una fila más corta permanece quieta en el medio entre dos guardalibros (shelf.fixed): las flechas, las teclas de flecha y un clic en un lomo eligen el volumen presentado, y un arrastre no hace nada. Indica a la persona cuál de los dos verá con su número de elementos, porque ocho títulos en una pantalla de escritorio permanecen quietos.

4. Reglas que lo mantienen funcionando

Estas son las cosas que se rompen cuando se cambian. Cada una tiene una razón.

  • Nada entre .bb-shelf__row y una cara de libro puede establecer overflow, filter, clip-path, mask, contain: paint u opacity por debajo de 1. Cualquiera de ellos aplana el contexto 3D y los libros se colapsan en un plano. Atenúa la escena (el elemento de perspectiva) en su lugar.
  • La estantería dibuja la pose que se le da. El movimiento a lo largo de la fila y la apertura provienen de setPose en un bucle de fotogramas propiedad del módulo de movimiento. Agregar transiciones CSS a .bb-book lucha contra ese bucle. Los efectos de hover van en .bb-book__body.
  • La estantería no vuela la portada. Mientras pose.open está por encima de 0, oculta el volumen centrado y separa a sus vecinos. Una portada viajera es trabajo del anfitrión (src/app/flight.ts): una copia en una capa fija, movida solo por transform, desde shelf.coverPlacement() hasta reader.flightTarget(). El volumen de la estantería, la copia y la propia portada del lector nunca son visibles juntos.
  • Un botón nativo por elemento, no por libro renderizado. Una fila en bucle renderiza copias. Solo la copia real lleva button.bb-book__control, y las copias son aria-hidden. Las caras son simples spans y el botón es un elemento separado e invisible delante de ellas. No hagas que las caras sean enfocables y no envuelvas las caras en un <button>.
  • Mantén touch-action: pan-y en la superficie de arrastre. Con none, un dedo que aterriza en la estantería ya no puede desplazar la página.
  • Durante un arrastre táctil, ignora lostpointercapture de los descendientes. El elemento bajo el dedo captura el puntero implícitamente, y cuando la superficie toma la captura, el evento de pérdida de ese descendiente burbujea hacia arriba. gesture.ts verifica event.target === element. Mantén la verificación.
  • Los toques táctiles en los volúmenes se reconocen a partir de eventos de puntero, no de click. Después de un deslizamiento soltado a velocidad, los navegadores pueden tragarse el clic del siguiente toque. shelf.ts por lo tanto activa un volumen en pointerup cuando el toque nunca salió de un radio de 10 px y duró menos de 700 ms, y omite el clic que el navegador envía para ese mismo toque (los clics de teclado, que tienen detail 0, siempre cuentan). gesture.ts también consume touchmove una vez que un arrastre está bloqueado para que no comience ningún desplazamiento del navegador.
  • El lector construye DOM solo con createElement y textContent. No hay innerHTML en ninguna parte de src/reader, y una prueba a nivel de código fuente lo exige. Los enlaces se mantienen solo para http: y https:.
  • El almacén acepta solo direcciones http: y https: y nunca obtiene una. La aplicación muestra un enlace y cualquier texto que la persona haya pegado. No agregues obtención remota para hacer el lector "más inteligente": los navegadores lo bloquean para la mayoría de los sitios y un proxy cambia el modelo de seguridad.
  • Una importación nunca sobrescribe, y nada que llegue desde fuera se aplica sin una vista previa. Un archivo de estantería o enlace de estantería proviene de un extraño: léelo con readShelfFile o decodeShelfLink, muestra summariseShelfFile, y llama a importShelf solo después de que la persona acepte. Las posiciones de lectura, las marcas de leído y las notas privadas nunca van a un archivo de estantería.
  • El movimiento reducido es su propio camino, sin nada que viaje. reducedMotion en el módulo de movimiento hace que los movimientos sean instantáneos y convierte la apertura y el cierre en un desvanecimiento de 150 ms. Pasa la preferencia en vivo (prefersReducedMotion), no un valor leído una vez.
  • Indica siempre el límite del lector donde la gente pueda verlo. Un enlace sin texto debe mostrar "Abrir original" y una forma de un solo paso para agregar texto.

5. Verificar el resultado

Ejecuta lo que corresponda. references/checks.md tiene los comandos, los hooks DOM para afirmar y fragmentos de Playwright para arrastres reales con ratón y táctil.

TomóMínima prueba
cualquier módulonpx tsc --noEmit limpio, las pruebas *.test.ts del módulo pasan con Vitest y happy-dom
estanteríalos libros se renderizan, data-focus cambia en setPose, exactamente un .bb-book__control por elemento, las teclas de flecha emiten step
estantería + movimientoen una fila lo suficientemente larga para bucle, un arrastre real con ratón cambia data-focus-id y termina con data-settled="1". En una fila quieta, las teclas de flecha lo cambian y un arrastre no. Un arrastre táctil vertical desplaza la página
lectorEscape llama a onClose, una recarga restaura scrollTop, un <script> en el texto se muestra como texto
almacénuna dirección javascript: es rechazada, exportar luego importar da la vuelta completa, un segundo createStore en el mismo almacenamiento ve los datos, una estantería sobrevive a encodeShelfLink luego decodeShelfLink
los cuatrola prueba de humo de configuración en references/checks.md pasa. Abre un volumen, desplázate, recarga: el mismo volumen está abierto en el mismo lugar. Cierra: el foco vuelve a ese volumen

Informa lo que ejecutaste y lo que no. Una compilación que pasa la verificación de tipos no ha demostrado poder arrastrarse.

Apariencia

paper (defau...

Skills relacionados