diff --git a/.atl/.skill-registry.cache.json b/.atl/.skill-registry.cache.json index abda2d6..03315d5 100644 --- a/.atl/.skill-registry.cache.json +++ b/.atl/.skill-registry.cache.json @@ -1,3 +1,3 @@ { - "fingerprint": "ee41cd16aee142978eedd00dcf1854a07a534d9e" + "fingerprint": "c6be2155b5d458c41adc0623b75fe4fad880b5f6" } diff --git a/.atl/skill-registry.md b/.atl/skill-registry.md index 4f78250..0c39af9 100644 --- a/.atl/skill-registry.md +++ b/.atl/skill-registry.md @@ -1,4 +1,4 @@ -# Skill Registry — worst_scan_web +# Skill Registry — worst_web @@ -7,7 +7,9 @@ Last updated: 2026-07-23 ## Sources scanned - /home/ren/.config/opencode/skills +- /home/ren/.claude/skills - /home/ren/.codex/skills +- /home/ren/.qwen/skills ## Contract @@ -19,16 +21,60 @@ Last updated: 2026-07-23 | Skill | Trigger / description | Scope | Path | | --- | --- | --- | --- | +| `api-design` | REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs. | user | `/home/ren/.claude/skills/api-design/SKILL.md` | +| `backend-patterns` | Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes. | user | `/home/ren/.claude/skills/backend-patterns/SKILL.md` | | `branch-pr` | Create Gentle AI pull requests with issue-first checks. Trigger: creating, opening, or preparing PRs for review. | user | `/home/ren/.config/opencode/skills/branch-pr/SKILL.md` | | `chained-pr` | Trigger: PRs over 400 lines, stacked PRs, review slices. Split oversized changes into chained PRs that protect review focus. | user | `/home/ren/.config/opencode/skills/chained-pr/SKILL.md` | +| `clickhouse-io` | ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads. | user | `/home/ren/.claude/skills/clickhouse-io/SKILL.md` | +| `coding-standards` | Universal coding standards, best practices, and patterns for TypeScript, JavaScript, React, and Node.js development. | user | `/home/ren/.claude/skills/coding-standards/SKILL.md` | | `cognitive-doc-design` | Design docs that reduce cognitive load. Trigger: writing guides, READMEs, RFCs, onboarding, architecture, or review-facing docs. | user | `/home/ren/.config/opencode/skills/cognitive-doc-design/SKILL.md` | | `comment-writer` | Write warm, direct collaboration comments. Trigger: PR feedback, issue replies, reviews, Slack messages, or GitHub comments. | user | `/home/ren/.config/opencode/skills/comment-writer/SKILL.md` | +| `configure-ecc` | Interactive installer for Everything Claude Code — guides users through selecting and installing skills and rules to user-level or project-level directories, verifies paths, and optionally optimizes installed files. | user | `/home/ren/.claude/skills/configure-ecc/SKILL.md` | +| `constructor` | Build, rebuild, flatten, clean, and verify structures in the Minecraft world managed by Crafty Controller on this VPS. Use when requests involve placing/removing blocks, terraforming areas, teleporting players, recreating murals/flags from images, validating coordinates or block states, and keeping ren.md updated after world changes. | user | `/home/ren/.codex/skills/constructor/SKILL.md` | +| `continuous-learning` | Automatically extract reusable patterns from Claude Code sessions and save them as learned skills for future use. | user | `/home/ren/.claude/skills/continuous-learning/SKILL.md` | +| `continuous-learning-v2` | Instinct-based learning system that observes sessions via hooks, creates atomic instincts with confidence scoring, and evolves them into skills/commands/agents. | user | `/home/ren/.claude/skills/continuous-learning-v2/SKILL.md` | +| `cpp-testing` | Use only when writing/updating/fixing C++ tests, configuring GoogleTest/CTest, diagnosing failing or flaky tests, or adding coverage/sanitizers. | user | `/home/ren/.claude/skills/cpp-testing/SKILL.md` | +| `database-migrations` | Database migration best practices for schema changes, data migrations, rollbacks, and zero-downtime deployments across PostgreSQL, MySQL, and common ORMs (Prisma, Drizzle, Django, TypeORM, golang-migrate). | user | `/home/ren/.claude/skills/database-migrations/SKILL.md` | +| `deployment-patterns` | Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications. | user | `/home/ren/.claude/skills/deployment-patterns/SKILL.md` | +| `django-patterns` | Django architecture patterns, REST API design with DRF, ORM best practices, caching, signals, middleware, and production-grade Django apps. | user | `/home/ren/.claude/skills/django-patterns/SKILL.md` | +| `django-security` | Django security best practices, authentication, authorization, CSRF protection, SQL injection prevention, XSS prevention, and secure deployment configurations. | user | `/home/ren/.claude/skills/django-security/SKILL.md` | +| `django-tdd` | Django testing strategies with pytest-django, TDD methodology, factory_boy, mocking, coverage, and testing Django REST Framework APIs. | user | `/home/ren/.claude/skills/django-tdd/SKILL.md` | +| `django-verification` | Verification loop for Django projects: migrations, linting, tests with coverage, security scans, and deployment readiness checks before release or PR. | user | `/home/ren/.claude/skills/django-verification/SKILL.md` | +| `docker-patterns` | Docker and Docker Compose patterns for local development, container security, networking, volume strategies, and multi-service orchestration. | user | `/home/ren/.claude/skills/docker-patterns/SKILL.md` | +| `e2e-testing` | Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies. | user | `/home/ren/.claude/skills/e2e-testing/SKILL.md` | +| `eval-harness` | Formal evaluation framework for Claude Code sessions implementing eval-driven development (EDD) principles | user | `/home/ren/.claude/skills/eval-harness/SKILL.md` | +| `frontend-patterns` | Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. | user | `/home/ren/.claude/skills/frontend-patterns/SKILL.md` | +| `gentle-ai-chained-pr` | Split large changes into chained or stacked pull requests that protect reviewer focus and stay within Gentle AI's 400-line cognitive review budget. Trigger: when a PR would exceed 400 changed lines, when planning chained PRs, stacked PRs, or reviewable slices. | user | `/home/ren/.claude/skills/chained-pr/SKILL.md` | | `go-testing` | Trigger: Go tests, go test coverage, Bubbletea teatest, golden files. Apply focused Go testing patterns. | user | `/home/ren/.config/opencode/skills/go-testing/SKILL.md` | +| `golang-patterns` | Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications. | user | `/home/ren/.claude/skills/golang-patterns/SKILL.md` | +| `golang-testing` | Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices. | user | `/home/ren/.claude/skills/golang-testing/SKILL.md` | | `issue-creation` | Create Gentle AI issues with issue-first checks. Trigger: creating GitHub issues, bug reports, or feature requests. | user | `/home/ren/.config/opencode/skills/issue-creation/SKILL.md` | +| `iterative-retrieval` | Pattern for progressively refining context retrieval to solve the subagent context problem | user | `/home/ren/.claude/skills/iterative-retrieval/SKILL.md` | +| `java-coding-standards` | Java coding standards for Spring Boot services: naming, immutability, Optional usage, streams, exceptions, generics, and project layout. | user | `/home/ren/.claude/skills/java-coding-standards/SKILL.md` | +| `jpa-patterns` | JPA/Hibernate patterns for entity design, relationships, query optimization, transactions, auditing, indexing, pagination, and pooling in Spring Boot. | user | `/home/ren/.claude/skills/jpa-patterns/SKILL.md` | | `judgment-day` | Trigger: judgment day, dual review, adversarial review, juzgar. Run blind dual review, fix confirmed issues, then re-judge. | user | `/home/ren/.config/opencode/skills/judgment-day/SKILL.md` | +| `kilo-force-multiplier` | Use Kilo (OpenCode's AI agent) as a force multiplier for development work. Covers subagent architecture, custom commands, skills system, MCP integration, multi-model routing, context management, and the SDD workflow. Trigger: when setting up Kilo, creating custom commands, building skills, configuring MCP servers, or optimizing AI-assisted development workflows. | user | `/home/ren/.claude/skills/kilo-force-multiplier/SKILL.md` | +| `minebuilder` | — | user | `/home/ren/.claude/skills/minebuilder/SKILL.md` | +| `minecraft-construction` | Minecraft large-scale construction via Crafty API. Parallel section processing, proper terrain flattening, chunk management, and multi-agent coordination for efficient building. | user | `/home/ren/.claude/skills/minecraft-construction/SKILL.md` | +| `nutrient-document-processing` | Process, convert, OCR, extract, redact, sign, and fill documents using the Nutrient DWS API. Works with PDFs, DOCX, XLSX, PPTX, HTML, and images. | user | `/home/ren/.claude/skills/nutrient-document-processing/SKILL.md` | +| `postgres-patterns` | PostgreSQL database patterns for query optimization, schema design, indexing, and security. Based on Supabase best practices. | user | `/home/ren/.claude/skills/postgres-patterns/SKILL.md` | +| `project-guidelines-example` | Example project-specific skill template based on a real production application. | user | `/home/ren/.claude/skills/project-guidelines-example/SKILL.md` | +| `python-patterns` | Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications. | user | `/home/ren/.claude/skills/python-patterns/SKILL.md` | +| `python-testing` | Python testing strategies using pytest, TDD methodology, fixtures, mocking, parametrization, and coverage requirements. | user | `/home/ren/.claude/skills/python-testing/SKILL.md` | +| `security-review` | Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns. | user | `/home/ren/.claude/skills/security-review/SKILL.md` | +| `security-scan` | Scan your Claude Code configuration (.claude/ directory) for security vulnerabilities, misconfigurations, and injection risks using AgentShield. Checks CLAUDE.md, settings.json, MCP servers, hooks, and agent definitions. | user | `/home/ren/.claude/skills/security-scan/SKILL.md` | | `skill-creator` | Trigger: new skills, agent instructions, documenting AI usage patterns. Create LLM-first skills with valid frontmatter. | user | `/home/ren/.config/opencode/skills/skill-creator/SKILL.md` | | `skill-improver` | Trigger: improve skills, audit skills, refactor skills, skill quality. Audit and upgrade existing LLM-first skills. | user | `/home/ren/.config/opencode/skills/skill-improver/SKILL.md` | +| `skill-installer` | Install Codex skills into $CODEX_HOME/skills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos). | user | `/home/ren/.codex/skills/.system/skill-installer/SKILL.md` | +| `springboot-patterns` | Spring Boot architecture patterns, REST API design, layered services, data access, caching, async processing, and logging. Use for Java Spring Boot backend work. | user | `/home/ren/.claude/skills/springboot-patterns/SKILL.md` | +| `springboot-security` | Spring Security best practices for authn/authz, validation, CSRF, secrets, headers, rate limiting, and dependency security in Java Spring Boot services. | user | `/home/ren/.claude/skills/springboot-security/SKILL.md` | +| `springboot-tdd` | Test-driven development for Spring Boot using JUnit 5, Mockito, MockMvc, Testcontainers, and JaCoCo. Use when adding features, fixing bugs, or refactoring. | user | `/home/ren/.claude/skills/springboot-tdd/SKILL.md` | +| `springboot-verification` | Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR. | user | `/home/ren/.claude/skills/springboot-verification/SKILL.md` | +| `strategic-compact` | Suggests manual context compaction at logical intervals to preserve context through task phases rather than arbitrary auto-compaction. | user | `/home/ren/.claude/skills/strategic-compact/SKILL.md` | +| `tdd-workflow` | Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests. | user | `/home/ren/.claude/skills/tdd-workflow/SKILL.md` | +| `verification-loop` | A comprehensive verification system for Claude Code sessions. | user | `/home/ren/.claude/skills/verification-loop/SKILL.md` | | `work-unit-commits` | Plan commits as reviewable work units. Trigger: implementation, commit splitting, chained PRs, or keeping tests and docs with code. | user | `/home/ren/.config/opencode/skills/work-unit-commits/SKILL.md` | +| `zai-vision` | Analyze images via Z.AI GLM-4.6V vision model. Use when the user shares an image file path, base64 image data, image URL, or asks to describe/analyze/OCR/read visual content. Gives vision capabilities to text-only models. | user | `/home/ren/.claude/skills/zai-vision/SKILL.md` | ## Loading protocol diff --git a/.env.example b/.env.example index a2bf4a3..1165bd6 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,9 @@ API_KEY= # Auth de la web (vacío = sin auth en dev) WEB_PASSWORD= +# Webhook Secret para sincronización automática desde el pipeline +WEBHOOK_SECRET= + # Puerto PORT=3000 diff --git a/.gitignore b/.gitignore index c6c38ba..1e57613 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ next-env.d.ts .env .env.local data/ +.atl/ diff --git a/docker-compose.yml b/docker-compose.yml index 755073c..677b2d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,15 +2,25 @@ services: web: build: . container_name: worst-scan-web - ports: - - "3000:3000" + expose: + - "3000" environment: - API_BASE_URL=${API_BASE_URL:-http://host.docker.internal:8080/api/v1} - API_KEY=${API_KEY:-} - WEB_PASSWORD=${WEB_PASSWORD:-} + - WEBHOOK_SECRET=${WEBHOOK_SECRET:-} + extra_hosts: + - "host.docker.internal:host-gateway" volumes: - data:/app/data + networks: + - caddy + - default restart: unless-stopped +networks: + caddy: + external: true + volumes: data: diff --git a/dominios-disponibles.md b/dominios-disponibles.md new file mode 100644 index 0000000..3887e14 --- /dev/null +++ b/dominios-disponibles.md @@ -0,0 +1,91 @@ +# Dominios Disponibles + +> Generado: 2026-07-06 — Después de limpieza de contenedores + +## Resumen de recuperación + +| Recurso | Antes | Después | Liberado | +|---|---|---|---| +| Contenedores | 30 | **14** | 16 | +| Imágenes | 38 | **12** | 26 | +| Disco (imágenes) | 34.86 GB | **8.7 GB** | ~26 GB | +| Build cache | 15.34 GB | **4.1 GB** | ~11 GB | +| Redes Docker | 16 | **10** | 6 | + +--- + +## Dominios liberados (sin uso actual) + +| Dominio | Apuntaba a | Posible uso futuro | +|---|---|---| +| `mate.cbcren.online` | math-frontend + math-backend | App web, API REST, dashboard | +| `manga.cbcren.online` | Kavita (lector manga) | Static site, app web | +| `eco2.cbcren.online` | eco2-app (economía) | App Node.js / web | +| `simulacro.cbcren.online` | simulacro-app (flask) | Python app, web app | +| `am.cbcren.online` | Open WebUI (LLM) | Chat UI, interfaz AI | +| `demo2.cbcren.online` | pymesbot_backend | API demo, bot backend | + +### Cómo reutilizar un dominio + +```bash +# 1. Crear el nuevo contenedor en la red caddy +docker run -d --name mi-app --network caddy mi-imagen + +# 2. Editar Caddyfile con el dominio y el nuevo servicio +# /home/ren/immich/Caddyfile +# mimarcacbcren.online { +# reverse_proxy mi-app:3000 +# } + +# 3. Recargar Caddy sin downtime +docker exec caddy-ingress caddy reload --config /etc/caddy/Caddyfile +``` + +Estos dominios ya están definidos en `/home/ren/immich/Caddyfile`. Solo hay que cambiar el `reverse_proxy` al nuevo contenedor. Ejemplo de bloque en Caddyfile: + +```caddy +am.cbcren.online { + reverse_proxy nuevo-contenedor:8080 +} +``` + +--- + +## Dominios activos (no tocar) + +| Dominio | Servicio | Puerto | +|---|---|---| +| `eco.cbcren.online` | ASF (ArchiSteamFarm) — con basic auth | 1242 | +| `gitea.cbcren.online` | Gitea (repos git) | 3000 | +| `nextcloud.cbcren.online` | Nextcloud (files) | 80 | +| `bunismocraft.cbcren.online` | Crafty (Minecraft) | 8443 | +| `n8n.cbcren.online` | n8n (workflows) — apunta a host:5000 | 5000 | + +--- + +## Puertos locales liberados + +| Puerto | Antes | Ahora | +|---|---|---| +| `127.0.0.1:3010` → 3000 | math-frontend | **Libre** | +| `127.0.0.1:3011` → 3001 | math-backend | **Libre** | +| `127.0.0.1:5433` → 5432 | math-postgres | **Libre** | +| `127.0.0.1:6379` → 6379 | math-redis | **Libre** | +| `0.0.0.0:8089` → 80 | omni-tools | **Libre** | +| `0.0.0.0:8201` → 8000 | pymesbot_backend | **Libre** | +| `0.0.0.0:8202` → 8080 | pymesbot_picoclaw | **Libre** | +| TCP 3002 | math-pdf-worker | **Libre** | +| TCP 3003 | math-exercise-worker | **Libre** | +| TCP 3004 | math-notification-worker | **Libre** | +| `0.0.0.0:18080` → 8080 | finanzas_fireflyiii | **Libre** | + +## Redes Docker eliminadas + +| Red | Contenía | +|---|---| +| `economia-study-app_default` | eco2-app | +| `finanzas_default` | finanzas_fireflyiii | +| `pygmesbot_demo_net` | pymesbot_picoclaw + pymesbot_backend | +| `omni-tools_default` | omni-tools | +| `openclaw-config_pymesbot_net` | pymesbot_backend | +| `math_math-network` | Stack math completo | diff --git a/next.config.ts b/next.config.ts index a886a9c..f7ccfa2 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,9 @@ import type { NextConfig } from "next" const nextConfig: NextConfig = { output: "standalone", serverExternalPackages: ["better-sqlite3"], + turbopack: { + root: __dirname, + }, } export default nextConfig diff --git a/package-lock.json b/package-lock.json index a23c806..ca79f9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -618,9 +618,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -637,9 +634,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -656,9 +650,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -675,9 +666,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -694,9 +682,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -713,9 +698,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -732,9 +714,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -751,9 +730,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -770,9 +746,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -795,9 +768,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -820,9 +790,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -845,9 +812,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -870,9 +834,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -895,9 +856,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -920,9 +878,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -945,9 +900,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1163,9 +1115,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1182,9 +1131,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1201,9 +1147,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1220,9 +1163,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1461,9 +1401,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1481,9 +1418,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1501,9 +1435,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1521,9 +1452,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2084,9 +2012,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2101,9 +2026,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2118,9 +2040,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2135,9 +2054,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2152,9 +2068,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2169,9 +2082,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2186,9 +2096,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2203,9 +2110,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2220,9 +2124,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2237,9 +2138,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4994,9 +4892,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5018,9 +4913,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5042,9 +4934,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5066,9 +4955,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/src/app/(app)/gallery/[gid]/read/page.tsx b/src/app/(app)/gallery/[gid]/read/page.tsx index 9898841..da267a9 100644 --- a/src/app/(app)/gallery/[gid]/read/page.tsx +++ b/src/app/(app)/gallery/[gid]/read/page.tsx @@ -1,39 +1,68 @@ "use client" import { useEffect, useRef, useState, useCallback } from "react" -import { useParams } from "next/navigation" -import { ChevronLeft, ChevronRight, ZoomIn, ZoomOut, Loader2 } from "lucide-react" +import { useParams, useRouter } from "next/navigation" +import { + ArrowLeft, + ChevronLeft, + ChevronRight, + ZoomIn, + ZoomOut, + Loader2, + Rows, + BookOpen, + Maximize2, + Minimize2, + RotateCcw, +} from "lucide-react" export default function ReaderPage() { const { gid } = useParams<{ gid: string }>() + const router = useRouter() + const [page, setPage] = useState(0) const [pageUrls, setPageUrls] = useState([]) const [loading, setLoading] = useState(true) const [zoom, setZoom] = useState(1) const [error, setError] = useState("") const [title, setTitle] = useState("") - const containerRef = useRef(null) + const [mode, setMode] = useState<"paged" | "webtoon">("paged") const [showControls, setShowControls] = useState(true) - const hideTimer = useRef | undefined>(undefined) + const [isFullscreen, setIsFullscreen] = useState(false) + const hideTimer = useRef | undefined>(undefined) + const touchStartX = useRef(null) + const touchStartY = useRef(null) + const containerRef = useRef(null) + + // Fetch page list useEffect(() => { async function load() { setLoading(true) setError("") + try { + const pagesRes = await fetch(`/api/proxy/galleries/${gid}/pages`) + if (pagesRes.ok) { + const pagesData = await pagesRes.json() + const total = pagesData?.data?.total_pages || 0 + if (total > 0) { + const urls = Array.from({ length: total }, (_, i) => `/api/proxy/galleries/${gid}/page/${i + 1}`) + setPageUrls(urls) + setLoading(false) + return + } + } + } catch {} + try { const res = await fetch(`/api/proxy/galleries/${gid}/summary`) const data = await res.json() const summary = data?.data - setTitle(summary?.title || `g/${gid}`) + if (summary?.title) setTitle(summary.title) - if (!summary) { setError("Gallery no encontrada"); setLoading(false); return } - - if (summary.num_pages && summary.num_pages > 0) { - const urls: string[] = [] - for (let i = 0; i < summary.num_pages; i++) { - urls.push(`/api/proxy/galleries/${gid}/cover`) - } + if (summary?.num_pages && summary.num_pages > 0) { + const urls = Array.from({ length: summary.num_pages }, (_, i) => `/api/proxy/galleries/${gid}/page/${i + 1}`) setPageUrls(urls) setLoading(false) return @@ -45,7 +74,7 @@ export default function ReaderPage() { const artData = await artRes.json() const rendered = artData?.data?.files?.rendered if (rendered?.length) { - const urls = rendered.map(() => `/api/proxy/galleries/${gid}/cover`) + const urls = Array.from({ length: rendered.length }, (_, i) => `/api/proxy/galleries/${gid}/page/${i + 1}`) setPageUrls(urls) setLoading(false) return @@ -61,7 +90,7 @@ export default function ReaderPage() { } } catch {} - setError("No hay imágenes disponibles. La API del pipeline solo expone la portada.") + setError("No hay imágenes disponibles para esta galería.") setLoading(false) } @@ -69,47 +98,119 @@ export default function ReaderPage() { }, [gid]) const totalPages = pageUrls.length - const goTo = useCallback((n: number) => setPage(Math.max(0, Math.min(n, totalPages - 1))), [totalPages]) + const goTo = useCallback( + (n: number) => { + setPage((prev) => { + const next = Math.max(0, Math.min(n, totalPages - 1)) + return next + }) + }, + [totalPages] + ) + + // Preload next 2 images + useEffect(() => { + if (mode === "paged" && pageUrls.length > 0) { + const next1 = pageUrls[page + 1] + const next2 = pageUrls[page + 2] + if (next1) { const img1 = new Image(); img1.src = next1 } + if (next2) { const img2 = new Image(); img2.src = next2 } + } + }, [page, pageUrls, mode]) + + // Keyboard navigation useEffect(() => { function onKey(e: KeyboardEvent) { if (e.key === "ArrowLeft") goTo(page - 1) if (e.key === "ArrowRight") goTo(page + 1) if (e.key === "+" || e.key === "=") setZoom((z) => Math.min(z + 0.25, 3)) if (e.key === "-") setZoom((z) => Math.max(z - 0.25, 0.25)) + if (e.key === "0") setZoom(1) + if (e.key === "f" || e.key === "F") toggleFullscreen() } window.addEventListener("keydown", onKey) return () => window.removeEventListener("keydown", onKey) }, [page, goTo]) - const handleMouseMove = useCallback(() => { + const handleUserActivity = useCallback(() => { setShowControls(true) if (hideTimer.current) clearTimeout(hideTimer.current) - hideTimer.current = setTimeout(() => setShowControls(false), 2000) + hideTimer.current = setTimeout(() => setShowControls(false), 3500) }, []) + function toggleFullscreen() { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen().catch(() => {}) + setIsFullscreen(true) + } else { + document.exitFullscreen().catch(() => {}) + setIsFullscreen(false) + } + } + + // Touch gesture handling + function handleTouchStart(e: React.TouchEvent) { + if (e.touches.length === 1) { + touchStartX.current = e.touches[0].clientX + touchStartY.current = e.touches[0].clientY + } + } + + function handleTouchEnd(e: React.TouchEvent) { + if (touchStartX.current === null || touchStartY.current === null) return + + const diffX = touchStartX.current - e.changedTouches[0].clientX + const diffY = touchStartY.current - e.changedTouches[0].clientY + + // Swipe horizontal if X diff > Y diff and X diff > 40px + if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 40) { + if (mode === "paged") { + if (diffX > 0) goTo(page + 1) // Swipe left -> Next page + else goTo(page - 1) // Swipe right -> Prev page + } + } + + touchStartX.current = null + touchStartY.current = null + } + + function handleScreenClick(e: React.MouseEvent) { + const width = window.innerWidth + const clickX = e.clientX + + // Center 40% tap -> toggle controls + if (clickX >= width * 0.3 && clickX <= width * 0.7) { + setShowControls((s) => !s) + return + } + + if (mode === "paged") { + if (clickX < width * 0.3) { + goTo(page - 1) + } else if (clickX > width * 0.7) { + goTo(page + 1) + } + } + } + if (loading) { return ( -
- +
+ +

Cargando manga...

) } if (error) { return ( -
-
+
+

{error}

-

- La API del pipeline solo sirve la página 1 vía /galleries/{"{gid}"}/cover. -

- - Volver al detalle - +
) @@ -117,88 +218,196 @@ export default function ReaderPage() { return (
-
- {title} -
- - {Math.round(zoom * 100)}% - - {totalPages > 1 && ( - {page + 1} / {totalPages} - )} + + {title || `g/${gid}`} +
-
-
-
- {totalPages > 1 && ( +
+ {/* Mode Switcher */} + + + {/* Zoom controls (paged mode) */} + {mode === "paged" && ( +
+ + + {Math.round(zoom * 100)}% + + + {zoom !== 1 && ( + + )} +
+ )} + + +
+ + + {/* Main Image Viewer Container */} +
+ {mode === "paged" ? ( + /* Paged Mode Layout */ +
+ {`Página +
+ ) : ( + /* Webtoon / Vertical Continuous Scroll Mode */ +
+ {pageUrls.map((url, i) => ( + {`Página + ))} +
+ )} + + {/* Desktop Side Arrow Buttons */} + {mode === "paged" && totalPages > 1 && ( + <> - )} - - {totalPages 1 ? `Página ${page + 1}` : "Portada"} - style={{ transform: `scale(${zoom})` }} - className="max-h-[calc(100vh-8rem)] max-w-full origin-center object-contain transition-transform" - draggable={false} - /> - - {totalPages > 1 && ( - )} -
+ + )}
- {totalPages > 1 && ( -
- {Array.from({ length: Math.min(totalPages, 100) }).map((_, i) => ( + {/* Floating Bottom Navigation Bar */} + {mode === "paged" && totalPages > 1 && ( +
+ {/* Page Scrubber Slider */} +
- )} + onClick={() => goTo(page - 1)} + disabled={page === 0} + className="p-1 text-white/70 hover:text-white disabled:opacity-30" + > + + - {totalPages === 1 && ( -
- Solo portada disponible. El pipeline solo expone la página 1 vía /cover. -
+ goTo(Number(e.target.value))} + className="flex-1 h-1.5 accent-[var(--accent)] bg-white/20 rounded-lg cursor-pointer appearance-none" + /> + + +
+ + {/* Page Counter Indicator */} +
+ {page + 1} + / + {totalPages} +
+ )}
) diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 238bec0..46dc17b 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -2,9 +2,11 @@ import { Sidebar } from "@/components/layout/sidebar" export default function AppLayout({ children }: { children: React.ReactNode }) { return ( -
+
-
{children}
+
+ {children} +
) } diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx index 96f67c9..eb87a65 100644 --- a/src/app/(public)/layout.tsx +++ b/src/app/(public)/layout.tsx @@ -1,26 +1,39 @@ import Link from "next/link" -import { Image } from "lucide-react" +import { Image, Search, Shield } from "lucide-react" export default function PublicLayout({ children }: { children: React.ReactNode }) { return ( <> -
-
+
+
worst-scan -
-
{children}
-