MEGAPROMPT: EPUB 3 IDE — Fixed-Layout Editor
1. IDENTIDAD Y PROPÓSITO
Eres un desarrollador frontend senior especializado en herramientas de edición de publicaciones digitales. Debes crear una **aplicación web monolítica** (un solo archivo HTML) que funcione como **IDE profesional para editar archivos EPUB 3 con diseño fijo (fixed-layout)**.
La aplicación debe permitir:
- Cargar archivos `.epub` vía drag & drop o selector de archivos
- Explorar la estructura interna del EPUB (árbol de archivos)
- Editar archivos XHTML y CSS en tiempo real con resaltado de sintaxis
- Visualizar previsualizaciones en vivo del contenido
- **Aplicar animaciones CSS de Animate.css a elementos seleccionados con botones funcionales e iconos descriptivos**
- **Copiar código CSS completo de animaciones al portapapeles con confirmación visual**
- Exportar el EPUB modificado respetando el estándar OCF (Open Container Format)
---
## 2. ARQUITECTURA VISUAL (LAYOUT)
La interfaz usa una **cuadrícula CSS de 3 columnas × 3 filas**:
```
┌─────────────────────────────────────────────────────────┐
│ TOPBAR (brand + controles: Deshacer/Rehacer/Abrir/Preview/Exportar) │
├──────────┬──────────────────────────────┬─────────────────┤
│ │ │ │
│ SIDEBAR │ EDITOR PRINCIPAL │ PROPS PANEL │
│ (árbol │ ┌────────────────────────┐ │ (viewport + │
│ de │ │ Tabs: HTML+CSS / │ │ coordenadas + │
│ archivos│ │ Preview / │ │ preview en │
│ EPUB) │ │ ✨ Animaciones │ │ vivo) │
│ │ ├────────────────────────┤ │ │
│ │ │ Panel XHTML (arriba) │ │ │
│ │ │ ─── resizer ─── │ │ │
│ │ │ Panel CSS (abajo) │ │ │
│ │ └────────────────────────┘ │ │
│ │ │ │
├──────────┴──────────────────────────────┴─────────────────┤
│ FOOTER (info del curso/docente) │
└─────────────────────────────────────────────────────────┘
```
**Variables CSS obligatorias:**
```css
:root {
--bg: #1e1e1e; --bg-sidebar: #252526; --bg-panel: #2d2d30;
--bg-input: #3c3c3c; --border: #3e3e42; --text: #cccccc;
--text-muted: #858585; --text-bright: #ffffff; --accent: #007acc;
--danger: #f44747; --success: #4ec9b0; --warning: #dcdcaa;
--syn-tag: #569cd6; --syn-attr: #9cdcfe; --syn-string: #ce9178;
--syn-comment: #6a9955; --syn-keyword: #c586c0; --syn-number: #b5cea8;
--sidebar-w: 220px; --props-w: 260px;
}
```
---
## 3. ESTRUCTURA DEL HTML
El archivo debe ser **autocontenido** (un solo `.html`) con:
### 3.1 Dependencias externas (CDN)
```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<!-- Monaco Editor opcional: https://cdn.jsdelivr.net/npm/monaco-editor@0.48.0/min/vs/loader.js -->
```
### 3.2 Secciones DOM obligatorias
| ID | Descripción |
|----|-------------|
| `dropOverlay` | Overlay visual para drag & drop de archivos `.epub` |
| `fileInput` | Input file oculto para abrir EPUB (`accept=".epub"`) |
| `sidebarTree` | Contenedor del árbol de archivos del EPUB |
| `treeText` / `treeStyles` / `treeImages` | Subcontenedores para XHTML, CSS e imágenes |
| `htmlEditor` / `cssEditor` | Textareas para edición (fallback si Monaco no carga) |
| `monacoHtml` / `monacoCss` | Contenedores para Monaco Editor |
| `lineNumbersHtml` / `lineNumbersCss` | Números de línea sincronizados |
| `syntaxOverlayHtml` / `syntaxOverlayCss` | Resaltado de sintaxis HTML/CSS |
| `previewFrame` / `livePreviewFrame` | Iframes para preview (panel central y panel derecho) |
| `vpWidth` / `vpHeight` | Inputs para editar viewport (ICB) |
| `objLeft` / `objTop` / `objWidth` / `objHeight` / `objZIndex` / `objId` / `objSelector` | Inputs de coordenadas del elemento seleccionado |
| **`coordsHint`** | **Mensaje instructivo: "💡 Haz clic en un elemento del Preview en vivo para seleccionarlo y editar sus coordenadas."** |
| `animGrid` | Grid de tarjetas de animaciones Animate.css |
| **`animInstructions`** | **Panel de instrucciones con 3 tarjetas guía (ver sección 4.8)** |
| `animCodeModal` | Modal para ver código CSS de animaciones |
| `treeContextMenu` | Menú contextual (click derecho) en el árbol de archivos |
| `toast` | Sistema de notificaciones toast |
---
## 4. MOTOR JAVASCRIPT: REQUISITOS FUNCIONALES
### 4.1 Estado Global (`state`)
```javascript
const state = {
zip: null, // Instancia JSZip del EPUB cargado
opfPath: "", // Ruta al content.opf
opfDir: "", // Directorio del OPF
manifest: new Map(), // Items del manifest (id → {href, mediaType, properties})
spine: [], // Orden de lectura (itemrefs)
metadata: {}, // Metadatos DC (title, creator, language, etc.)
chapterIndex: -1, // Índice del capítulo activo
chapterState: new Map(), // Cache de capítulos cargados (href → {doc, history, ...})
currentHtmlPath: "", // Ruta del XHTML activo en el editor
currentCssPath: "", // Ruta del CSS activo en el editor
currentCssContent: "", // Contenido CSS original
selectedElementId: null, // data-euid del elemento seleccionado en preview
viewport: {width: 1920, height: 1080},
blobCache: new Map(), // Cache de Blob URLs para recursos locales
};
```
### 4.2 Pipeline de carga de EPUB
```
1. Validar MIME type o extensión .epub
2. Descomprimir con JSZip
3. Verificar mimetype === "application/epub+zip"
4. Leer META-INF/container.xml → extraer full-path del rootfile
5. Parsear content.opf:
- Extraer metadatos DC (namespace http://purl.org/dc/elements/1.1/)
- Construir manifest (resolver hrefs a rutas absolutas)
- Construir spine (orden de itemrefs)
- Extraer viewport de rendition:viewport si existe
6. Renderizar árbol de archivos en sidebar
7. Cargar primer capítulo del spine (si existe)
```
### 4.3 Sistema de rutas
Implementar funciones **obligatorias**:
- `dirOf(path)` → directorio de una ruta
- `resolvePath(base, href)` → resolución de ruta relativa (como navegador)
- `zipFile(path)` → buscar archivo en JSZip (con fallback decode/encode URI)
- `zipKey(path)` → clave real del archivo en el ZIP (evita duplicados por encoding)
### 4.4 Edición XHTML con DOM en memoria
**Flujo crítico:**
1. Al cargar un XHTML, parsearlo con `DOMParser` (application/xhtml+xml)
2. Asignar `data-euid` único a cada elemento del `<body>`
3. Guardar el documento DOM en `chapterState`
4. El editor muestra la serialización XML del DOM
5. Al editar (debounce 500ms):
- Parsear el texto del editor
- Si es válido XML: reemplazar el DOM del capítulo, preservar data-euids existentes
- Si es inválido: mostrar indicador visual (⚠) pero NO descartar el texto
- Guardar en JSZip y sincronizar preview
### 4.5 Sincronización de Preview
**Dos previews:**
- **Preview principal** (`previewFrame`): Recarga completa con `srcdoc`
- **Preview en vivo** (`livePreviewFrame`): Inyección diferencial (preserva scroll, solo actualiza body y CSS)
**Resolución de recursos locales:**
- Convertir `src`, `href` relativos a **Blob URLs** usando `URL.createObjectURL()`
- Guardar el valor original en `data-original-src` / `data-original-href`
- Al exportar, restaurar los valores originales
### 4.6 Interacción con Preview (selección de elementos)
1. El usuario hace clic en un elemento del **Preview en vivo**
2. Se resalta con outline azul (`2px solid #007acc`)
3. Se extraen coordenadas del `getComputedStyle`
4. Los inputs del panel COORDENADAS se habilitan y se llenan
5. Al editar coordenadas: se actualiza el DOM del capítulo Y se genera/actualiza la regla CSS correspondiente
### 4.7 Panel de Coordenadas
**Instrucción visual obligatoria:**
```html
<div class="viewport-info" id="coordsHint" style="color: var(--warning); font-size: 11px; padding: 6px 12px;">
<strong>💡 Haz clic en un elemento del Preview en vivo para seleccionarlo y editar sus coordenadas.</strong>
</div>
```
**Comportamiento:**
- Inputs deshabilitados hasta que se seleccione un elemento
- Edición con debounce (300ms)
- Actualiza: `position: absolute`, `left`, `top`, `width`, `height`, `z-index`, `margin: 0`
- Genera/actualiza regla CSS en el editor CSS
---
### 4.8 Catálogo de Animaciones (Animate.css) — **SECCIÓN CRÍTICA**
**Requisitos:**
- Embeber el CSS completo de Animate.css v4.1.1 en un `<style id="animateCssLib">`
- Extraer nombres de animaciones con regex: `/\.animate__([A-Za-z]+)/g`
- Agrupar por categorías: "Entradas (In)", "Salidas (Out)", "Llamar la atención / Especiales"
- Cada tarjeta muestra: demo visual, nombre, y **5 botones funcionales con iconos descriptivos**
#### 4.8.1 Panel de Instrucciones (obligatorio)
Debajo del toolbar de animaciones, agregar un panel instructivo con **3 tarjetas guía**:
```html
<div class="anim-instructions" style="background: var(--bg-panel); border-bottom: 1px solid var(--border); padding: 10px 14px;">
<strong style="color: var(--warning);">📖 Cómo usar el catálogo de animaciones:</strong>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px;">
<!-- Tarjeta 1: Copiar CSS -->
<div style="background: var(--bg-input); border-radius: 4px; padding: 8px 10px; border-left: 3px solid var(--success);">
<strong style="color: var(--success);">1️⃣ Copiar CSS</strong>
<p>Haz clic en <strong>📋 CSS</strong> para guardar el código completo (clase + @keyframes) en tu portapapeles, listo para pegar.</p>
</div>
<!-- Tarjeta 2: Ver antes de copiar -->
<div style="background: var(--bg-input); border-radius: 4px; padding: 8px 10px; border-left: 3px solid var(--accent);">
<strong style="color: var(--accent);">2️⃣ Ver antes de copiar</strong>
<p>Haz clic en <strong>👁 Ver</strong> para abrir una ventana con el código CSS completo formateado y revisarlo antes de copiar.</p>
</div>
<!-- Tarjeta 3: Aplicar a elemento -->
<div style="background: var(--bg-input); border-radius: 4px; padding: 8px 10px; border-left: 3px solid var(--syn-tag);">
<strong style="color: var(--syn-tag);">3️⃣ Aplicar a elemento</strong>
<p>Selecciona un elemento en el <strong>Preview en vivo</strong> (panel derecho), luego haz clic en <strong>+ Aplicar</strong> para añadir la animación.</p>
</div>
</div>
</div>
```
#### 4.8.2 Botones de cada Tarjeta de Animación (5 botones obligatorios)
Cada tarjeta debe renderizar estos botones en un **grid de 2 columnas**:
```javascript
card.innerHTML =
'<div class="anim-demo-wrap"><div class="anim-demo">Aa</div></div>' +
'<div class="anim-name">' + nombreAnimacion + '</div>' +
'<div class="anim-actions">' +
'<button data-act="play" title="Previsualizar animacion">▶ Play</button>' +
'<button data-act="copycss" title="Copiar CSS completo al portapapeles">📋 CSS</button>' +
'<button data-act="view" title="Ver codigo CSS en ventana">👁 Ver</button>' +
'<button data-act="apply" title="Aplicar al elemento seleccionado">+ Aplicar</button>' +
'<button data-act="copy" title="Copiar solo las clases CSS">⧉ Clases</button>' +
'</div>';
```
**CSS obligatorio para las tarjetas y botones:**
```css
.anim-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
.anim-card {
background: var(--bg-panel);
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
cursor: default;
}
.anim-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
width: 100%;
}
.anim-actions button {
background: var(--bg-input);
border: 1px solid var(--border);
color: var(--text-muted);
border-radius: 3px;
font-size: 10px;
line-height: 1.3;
padding: 5px 4px;
cursor: pointer;
text-align: center;
white-space: nowrap;
}
```
#### 4.8.3 Handlers de Eventos para los Botones
```javascript
// Delegación de eventos en el grid
grid.addEventListener("click", (e) => {
const card = e.target.closest(".anim-card");
if (!card) return;
const name = card.dataset.anim;
const btn = e.target.closest("button");
const act = btn ? btn.dataset.act : "play";
if (act === "play") {
// Previsualizar animación en el demo
const demo = card.querySelector(".anim-demo");
demo.className = "anim-demo";
void demo.offsetWidth; // fuerza reflow
demo.classList.add("animate__animated", "animate__" + name);
demo.addEventListener("animationend", () => {
demo.className = "anim-demo";
}, { once: true });
}
else if (act === "copycss") {
// COPIAR CSS COMPLETO AL PORTAPAPELES + TOAST DE CONFIRMACIÓN
const css = getAnimationCss(name);
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(css).then(
() => showToast(
'✅ CSS de "' + name + '" copiado al portapapeles. ' +
'Pégalo en tu hoja de estilos y añade la clase "' + name + '" al elemento en el XHTML.',
"success"
),
() => showToast(
"❌ Error al copiar al portapapeles. Intenta usar '👁 Ver' y copiar manualmente.",
"error"
)
);
} else {
showToast(
"⚠️ Clipboard no disponible en este navegador. Usa '👁 Ver' para ver y copiar el código manualmente.",
"error"
);
}
}
else if (act === "view") {
// Abrir modal con código CSS formateado
showAnimCode(name);
}
else if (act === "apply") {
// Aplicar animación al elemento seleccionado en preview
applyAnimToSelection(name);
}
else if (act === "copy") {
// Copiar solo las clases CSS
const cls = "animate__animated animate__" + name;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(cls).then(
() => showToast("Clases copiadas: " + cls, "success"),
() => showToast(cls, "info")
);
} else {
showToast(cls, "info");
}
}
});
```
#### 4.8.4 Función `getAnimationCss(name)` — EXTRACCIÓN DE CSS
```javascript
function getAnimationCss(name) {
// 1. Buscar la clase .animate__{nombre} en el CSS embebido
const clsBlock = ruleBlock(".animate__" + name);
// 2. Extraer animation-name y propiedades adicionales
let animName = name;
let extra = "";
if (clsBlock) {
const body = clsBlock.slice(clsBlock.indexOf("{") + 1, clsBlock.lastIndexOf("}"));
const an = body.match(/animation-name\s*:\s*([A-Za-z0-9_-]+)/);
if (an) animName = an[1];
// Extraer propiedades adicionales (excluir animation-name y -webkit-)
extra = body.split(";")
.map(s => s.trim())
.filter(s => s && !/^animation-name/i.test(s) && !/^-webkit-/i.test(s))
.map(s => " " + s + ";")
.join("\n");
}
// 3. Buscar @keyframes
let kf = ruleBlock("@keyframes " + animName);
if (!kf) kf = ruleBlock("@-webkit-keyframes " + animName);
// 4. Construir snippet autosuficiente
const cls =
'/* Animacion "' + name + '" — Animate.css (https://animate.style) */\n' +
"." + name + " {\n" +
" animation-duration: 1s;\n" +
" animation-fill-mode: both;\n" +
" animation-name: " + animName + ";\n" +
(extra ? extra + "\n" : "") +
"}";
return cls + "\n\n" + (kf ? pretty(kf) : "/* keyframes no encontrado */") +
'\n\n/* Uso en el XHTML: <div class="' + name + '"> … </div> */';
}
// Función auxiliar: extraer bloque CSS con balanceo de llaves
function ruleBlock(header) {
const hre = new RegExp(header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?![A-Za-z0-9_-])");
const mm = hre.exec(css);
if (!mm) return "";
const open = css.indexOf("{", mm.index);
if (open < 0) return "";
let depth = 0;
for (let i = open; i < css.length; i++) {
if (css[i] === "{") depth++;
else if (css[i] === "}") {
depth--;
if (depth === 0) return css.slice(mm.index, i + 1);
}
}
return css.slice(mm.index);
}
// Función auxiliar: formatear CSS minificado
function pretty(block) {
let indent = 0, out = "";
block = block.replace(/\s+/g, " ").trim();
for (let i = 0; i < block.length; i++) {
const ch = block[i];
if (ch === "{") {
out = out.replace(/\s+$/, "") + " {\n" + " ".repeat(++indent);
}
else if (ch === "}") {
indent = Math.max(0, indent - 1);
out = out.replace(/\s+$/, "") + "\n" + " ".repeat(indent) + "}\n" + " ".repeat(indent);
}
else if (ch === ";") {
out += ";\n" + " ".repeat(indent);
}
else {
out += ch;
}
}
return out.replace(/[ \t]+\n/g, "\n").replace(/\n{2,}/g, "\n").trim();
}
```
#### 4.8.5 Modal de Visualización de Código CSS
```html
<div id="animCodeModal" class="anim-modal hidden">
<div class="anim-modal-box">
<div class="anim-modal-head">
<span>Código CSS — <code id="animCodeTitle">animación</code></span>
<button id="animCodeClose" title="Cerrar">×</button>
</div>
<textarea id="animCodeText" readonly spellcheck="false"></textarea>
<div class="anim-modal-foot">
<span class="anim-hint">Copia este CSS y pégalo en tu hoja de estilos; luego añade la clase al elemento en el XHTML.</span>
<button id="animCodeCopy" class="ide-btn primary">⧉ Copiar CSS</button>
</div>
</div>
</div>
```
```javascript
function showAnimCode(name) {
const modal = document.getElementById("animCodeModal");
const codeTitle = document.getElementById("animCodeTitle");
const codeText = document.getElementById("animCodeText");
codeTitle.textContent = "." + name;
codeText.value = getAnimationCss(name);
modal.classList.remove("hidden");
codeText.focus();
codeText.select();
}
// Cerrar modal
document.getElementById("animCodeClose").addEventListener("click", () => {
document.getElementById("animCodeModal").classList.add("hidden");
});
document.getElementById("animCodeModal").addEventListener("click", (e) => {
if (e.target === document.getElementById("animCodeModal")) {
document.getElementById("animCodeModal").classList.add("hidden");
}
});
```
#### 4.8.6 Instalación de Animate.css en el EPUB
```javascript
async function installAnimateCss() {
const lib = document.getElementById("animateCssLib");
const css = lib ? lib.textContent : "";
if (!css || css.indexOf("animate__") === -1) {
showToast("Animate.css no está embebido en esta página.", "error");
return null;
}
if (!state.zip) {
showToast("Carga un EPUB primero", "error");
return null;
}
const cssDir = state.currentCssPath ? dirOf(state.currentCssPath) :
(state.opfDir ? state.opfDir + "/Styles" : "Styles");
const target = cssDir + "/animate.css";
const already = !!manifestEntryByPath(target) || !!zipFile(target);
state.zip.file(target, css);
if (!manifestEntryByPath(target)) {
let id = "animate-css";
let k = 1;
while (state.manifest.has(id)) id = "animate-css-" + (++k);
await withOpf((doc) => {
const man = doc.querySelector("manifest");
if (!man) return;
const it = doc.createElement("item");
it.setAttribute("id", id);
it.setAttribute("href", relToOpf(target));
it.setAttribute("media-type", "text/css");
man.appendChild(it);
});
state.manifest.set(id, { href: target, mediaType: "text/css", properties: "" });
}
linkCssInCurrentChapter(target);
renderSidebarTree();
wireStaticNodes();
setDirty(true);
showToast(
already ? "Animate.css actualizado en el EPUB" : "Animate.css instalado en el EPUB",
"success"
);
return target;
}
```
---
### 4.9 Menú Contextual del Árbol (Click Derecho)
**Opciones por archivo:**
- 📝 Abrir en el Editor de Código
- 👁 Ver en Preview
- ✏️ Renombrar…
- ⧉ Duplicar
- ⬇️ Exportar este archivo
- 🗑️ Eliminar del EPUB
**Operaciones de archivo:**
- **Renombrar**: Mueve bytes en ZIP, reescribe referencias en TODOS los archivos de texto, actualiza manifest
- **Duplicar**: Copia bytes, genera nuevo ID en manifest
- **Eliminar**: Elimina del ZIP, manifest y spine; actualiza estado en memoria
- **Exportar**: Descarga archivo individual
---
### 4.10 Buscador en Editores
Implementar búsqueda en XHTML y CSS con:
- Input de búsqueda en toolbar de cada editor
- Contador de coincidencias (ej: "2/5")
- Navegación con ▲ / ▼ o Enter / Shift+Enter
- Soporte para Monaco (usar `model.findMatches()`) y textarea fallback
---
### 4.11 Historial Undo/Redo
- Por editor separado (HTML y CSS)
- Límite de 60 snapshots
- Teclas: Ctrl+Z / Ctrl+Y / Ctrl+Shift+Z
- Si Monaco está activo: delegar a su sistema nativo
---
### 4.12 Exportación OCF Compliant
```
1. Restaurar data-original-src y data-original-href en todos los capítulos
2. Re-serializar capítulos modificados al ZIP
3. Crear nuevo JSZip:
- mimetype: SIN compresión (STORE)
- Resto: compresión DEFLATE nivel 9
4. Generar blob y descargar como .epub
```
---
## 5. RESALTADO DE SINTAXIS (Fallback sin Monaco)
Si Monaco no carga, implementar resaltado con **overlay sincronizado**:
```javascript
// Estructura:
// <div class="code-wrap">
// <div class="line-numbers">1\n2\n3...</div>
// <div class="syntax-overlay">HTML resaltado</div>
// <textarea class="code-input">Código editable</textarea>
// </div>
// Sincronizar scroll entre textarea y overlay
// Actualizar números de línea y overlay en cada input
```
**Reglas de resaltado HTML:**
- Tags: `<span class="syn-tag">`
- Atributos: `<span class="syn-attr">`
- Strings: `<span class="syn-string">`
- Comentarios: `<span class="syn-comment">`
- Keywords (DOCTYPE): `<span class="syn-keyword">`
- Números: `<span class="syn-number">`
**Reglas de resaltado CSS:**
- Selectores: `<span class="syn-selector">`
- Propiedades: `<span class="syn-prop">`
- Valores keyword: `<span class="syn-keyword">`
- Comentarios: `<span class="syn-comment">`
- Números/unidades: `<span class="syn-number">`
---
## 6. INTEGRACIÓN CON MONACO EDITOR (Opcional pero recomendado)
```javascript
// Si carga Monaco:
// 1. Ocultar textareas, overlays y números de línea
// 2. Crear editores en #monacoHtml y #monacoCss
// 3. Crear adapters con misma interfaz (.value, addEventListener, focus)
// 4. Delegar undo/redo a Monaco cuando esté activo
```
---
## 7. MANEJO DE ERRORES Y VALIDACIONES
- **XHTML mal formado**: Indicador visual en label del archivo (color rojo, ⚠), NO bloquear edición
- **EPUB corrupto**: Toast de error detallado, resetear estado
- **Recurso no encontrado**: Warning en consola, continuar sin crash
- **Clipboard no disponible**: Fallback a `document.execCommand('copy')` o mensaje informativo
---
## 8. ESTILOS CSS ADICIONALES OBLIGATORIOS
```css
/* Menú contextual */
.context-menu { position: absolute; z-index: 9999; min-width: 210px; ... }
.context-menu-item:hover { background: var(--accent); color: #fff; }
.context-menu-item.danger { color: var(--danger); }
.context-menu-item.danger:hover { background: var(--danger); color: #fff; }
/* Toast notifications */
.toast { position: fixed; right: 16px; bottom: 16px; ... }
.toast.error { border-left: 3px solid var(--danger); }
.toast.success { border-left: 3px solid var(--success); }
.toast.info { border-left: 3px solid var(--accent); }
/* Animaciones panel - INSTRUCCIONES */
.anim-instructions { background: var(--bg-panel); border-bottom: 1px solid var(--border); }
.anim-instructions strong { color: var(--warning); }
/* Modal */
.anim-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 300; ... }
.anim-modal.hidden { display: none; }
```
---
## 9. RESTRICCIONES TÉCNICAS
- **UN SOLO ARCHIVO HTML**: Todo el CSS y JavaScript debe estar embebido (inline)
- **Sin frameworks**: Vanilla JS, CSS puro, sin React/Vue/Angular
- **Sin build step**: Debe funcionar abriendo directamente el archivo en navegador
- **Compatible con Chrome/Firefox/Edge modernos**
- **CORS**: No depender de recursos externos críticos (excepto JSZip y Monaco opcional)
---
## 10. EJEMPLO DE USO ESPERADO
1. Usuario arrastra `mi-libro.epub` al área de drop
2. Se descomprime y muestra el árbol de archivos
3. Se carga automáticamente el primer capítulo del spine
4. Usuario edita XHTML en el panel superior, CSS en el inferior
5. El preview en vivo se actualiza en tiempo real
6. Usuario hace clic en un elemento del preview → se activan coordenadas
7. Usuario ajusta posición X/Y desde el panel derecho
8. Usuario va a pestaña **"✨ Animaciones CSS"**
9. Lee las instrucciones del panel superior (📖 Cómo usar...)
10. Busca "bounce", hace clic en **`📋 CSS`** → **Toast: "✅ CSS de 'bounce' copiado al portapapeles..."**
11. Pega el CSS en el editor CSS y aplica clases al XHTML
12. Usuario clic en **"Exportar"** → descarga `mi-libro.epub` modificado
---
## 11. ENTREGABLE
Genera **un único archivo HTML** (`EPUB_IDE7.html`) que contenga:
- `<style>` con TODO el CSS (incluyendo Animate.css embebido)
- `<body>` con TODA la estructura DOM
- `<script>` con TODO el JavaScript (motor completo)
El archivo debe ser funcional al abrirlo directamente en un navegador moderno.
Comentarios
Publicar un comentario