El botón «Mostrar regalos» parecía no hacer nada: setFormError se llamaba
bien, pero el tema deja `.alert-message` con display:none esperando que la
rellene y la revele el JS antiguo (jQuery), que ya no existe. La caja se
pintaba y no se veía. Se fuerza el display desde React, como el resto de
formularios.
De paso, `.modal-content` estaba definida en globals.css Y en theme.css.
Como theme.css se importa el último (a propósito, para ganarle al preflight
de Tailwind), ganaba él a igual especificidad y el bloque de globals.css era
código muerto: el modal de la tienda salía como hoja pegada al fondo con
borde gris en vez de tarjeta centrada dorada. Se acota a
`.modal-div .modal-content` para ganar por especificidad y no por orden.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Antes: <link href="/ns-themes/.../nightspire-style.css?v=2"> en el <head>. Una
peticion aparte, sin minificar y con cache invalidada a mano con ?v=2.
Ahora: app/theme.css, importado al FINAL de globals.css. Next lo compila,
minifica y le pone hash de contenido; se sirve con cache immutable y hay una
hoja de estilo menos (4 -> 3).
⚠ El @import va el ULTIMO y no es cosmético: el tema se diseñó contra los
defaults del navegador y el preflight de Tailwind los pisa (box-sizing,
alturas). Antes ganaba la cascada por cargarse el último en el <head>; ahora
gana por ser el último import. Si sube de sitio, el diseño se rompe.
Las url() pasan a ABSOLUTAS (/ns-themes/ns-ryu/...). Los assets siguen en
public/, y así el compilador no intenta resolverlos: el @font-face declara
.eot/.otf/.ttf que NO existen (solo hay .woff) y con rutas relativas el build
fallaría.
Verificado: los 6 páginas dan exactamente el mismo alto que antes (8159, 1356,
2645, 1356, 2221, 2349 px) con la misma fuente y colores, y el input del login
sigue en box-sizing:content-box — que es justo donde el preflight rompía. Ojo:
el md5 de las capturas NO sirve para comparar, porque la cabecera lleva un vídeo
y cada captura pilla un fotograma distinto.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Renombrado completo de los prefijos heredados: 19 carpetas, 11 ficheros y 318
referencias en 35 ficheros de código, más las clases y las rutas url() de dentro
del CSS del tema. Se usa git mv para conservar el historial.
También fuera del código, que un sed no ve:
- BD: votesite.image_url (4 filas) apuntaba a /nw-themes/...
- Ficheros con la marca vieja en el NOMBRE: novawow-maintenance.webp ->
nightspire-maintenance.webp (lo usa la página de mantenimiento) y
store_novawow_response.js.
⚠ Alias en Caddy /nw-themes/* -> /ns-themes/*: los correos ENVIADOS antes del
rebranding llevan esas rutas escritas y están en las bandejas de los usuarios.
Sin el alias, sus imágenes se romperían. Verificado que las rutas viejas siguen
sirviendo 200.
Nota: ns-js/ y ns-js-handlers/ son CÓDIGO MUERTO (manejadores jQuery del portal
Django, que ya se borró). Se midió en el navegador: la web no pide ni un solo JS
del tema. Se renombran igualmente por consistencia, pero son candidatos a
borrarse.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sitios de voto: los 4 apuntaban a las fichas de UltimoWoW en los rankings con
SUS ids (Gtop100 94649, pingUsername 91402, etc.). No era cosmética: cada voto
de nuestros jugadores subía a UltimoWoW en el ranking. Se cambian nombre e ids
por un 236588 de relleno, así el enlace deja de acreditar a otro servidor. Las
fichas reales de NightSpire están por crear; entonces habrá que poner sus ids.
Nota legal: decía "Error 404 es la empresa que representa el servicio", heredado
de ultimowow. Al renombrar la marca pasaba a afirmar que una empresa ajena
representa a NightSpire, lo cual es falso. Se quita la empresa en ES y EN.
Prefijo uw- (de UltimoWoW) -> ns-: 100 identificadores en 11 ficheros (clases
CSS, ids de formulario, eventos y la cookie de consentimiento, que pasa a
ns_cookie_consent; a los usuarios les reaparecerá el aviso una vez). Se comprobó
antes que el CSS del tema no define ninguna clase uw-, así que no rompe estilos.
btn-ns-form y ns-cookie-btn-customize se usan sin estar definidas, pero ya era
así antes del cambio: eran clases muertas.
Repo de Gitea renombrado Inna/NovaWoW -> Inna/NightSpire (era la marca vieja más
visible de /changelogs, que forma la URL de cada commit). Gitea redirige la URL
antigua con 301. Se actualizan el remoto y lib/changelog.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Marca, dominios y correos: UltimoWoW/Nova WoW -> NightSpire, cualquier URL de
esos dominios -> https://www.nightspire.gg y los correos -> consultas@nightspire.gg.
98 sustituciones en 16 ficheros de textos de usuario (messages/, app/, components/).
Assets: logo largo de cabecera y vídeo sustituidos por los de NightSpire (el logo
nuevo es 214x28, igual que el viejo, así que no hace falta tocar el CSS).
Redes: apuntan a las cuentas de NightSpire, tanto en el pie de la web como en el
de los correos (que seguían enlazando a las de NovaWoW desde el porte del diseño).
Cookie de sesión novawow_session -> nightspire_session. Cierra la sesión de todos
los conectados una vez; se asume ahora, recién hecho el cutover.
Ficheros del tema renombrados: novawow-style.css -> nightspire-style.css y
novawow-main-logo-transparent.webp -> nightspire-main-logo-transparent.webp, para
que la marca vieja no quede ni en las rutas.
El foro externo (foro.ultimowow.com) pasa a ser el nuestro: <Link> a /forum, que
respeta el idioma y no abre pestaña nueva.
NO se tocan y es a propósito:
- Los comentarios que nombran AzerothCore: describen el comportamiento REAL del
core (límite de 12 ítems por correo, convención de baneos, SOAP...). Cambiarlos
a NightSpire los volvería falsos. Además AzerothCore no aparece en la web: las
14 menciones son todas comentarios de código.
- El regex de brandify() sigue buscando los nombres VIEJOS: son los que hay
escritos en las noticias de la BD.
- lib/changelog.ts REPO='Inna/NovaWoW': es la ruta real del repo en Gitea.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dos cosas, y la segunda dejaba la página sin contenido:
- El botón "Solicitar nuevo cambio de correo" salía de 250x100 pegado a la
derecha. `.back-to-account` es el botón de la CABECERA de una caja (250px de
ancho, line-height 50, float right); suelto en el CUERPO el float lo pega a un
lado, y el texto no cabe en 250px, se parte en dos líneas y cada una se lleva
sus 50px. Se le deja el aspecto del tema pero ajustado a su texto: pasa a
336x50 y centrado.
- El mensaje rojo ("el enlace ha expirado") NO SE VEÍA: el tema deja
`.alert-message` con display:none porque es el hueco que rellena y revela el
JS, y aquí el aviso es estático. Se pone display:block a mano, igual que hacen
todos los formularios del proyecto. Comprobado que al original le pasa lo
mismo: su página de enlace caducado enseña el título y el botón, y la
explicación queda invisible.
Medido en el navegador: botón 336x50, centrado, sin desbordar, y el mensaje
visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SiteHeader enlazaba /content-creators y esa página no existía en Next: el menú
VIDEOS llevaba a un 404. Réplica del partial Django `partials/videos.html`, con
sus clases del tema (cc-box, cc-avatar, cc-name...). Lee el primer registro de
home_contentcreator, igual que la vista de Django.
La descripción es HTML de CKEditor (lo edita un admin), así que se sanea con
`cleanPostHtml`, la misma allowlist que los mensajes del foro. Comprobado
metiendo un <script>alert(1)</script> a propósito: no aparece en el HTML servido.
Dos arreglos sobre el diseño de Django, que copiaba tal cual:
- El título va SIEMPRE. Django lo metía dentro del `if content_creator`, así que
sin datos la página quedaba con un <p> suelto, sin cabecera ni caja. Ahora sin
creador sale el título de la sección y el aviso centrado en su box-content.
- Los enlaces de YouTube/Facebook se salían por abajo de la caja. El tema le da a
`.cc-box` una altura FIJA de 172px contando con el content-box del navegador, y
el border-box de Tailwind le restaba el padding y el borde: 24px menos de alto
útil. Es el mismo caso que .item-box en la tienda. Medido: la caja pasa de 172
a 196px de alto y los enlaces dejan de desbordar.
Verificado con un creador de prueba (borrado después): /es/ y /en/ dan 200, el
título, el avatar, el iframe y los enlaces caen dentro de la caja, y con la tabla
vacía sale el mismo texto que Django.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El carrito era un conjunto: añadir dos veces el mismo ítem no hacía nada y el
botón se quedaba en "Añadido". Ahora cada línea lleva sus copias, editables con
un <input number> en la columna Cant, y volver a pulsar Añadir suma una (mismo
patrón que el carrito de send-gift, que ya lo hacía así).
Ojo con los dos "cantidad" que conviven, que no son lo mismo:
`home_store_item.quantity` es el LOTE que entrega cada copia (Paño de lino = 20)
y `copies` es cuántas veces se compra la línea. Por eso la fila enseña "3 × 20"
y el total de la columna Cant sigue siendo unidades (60), como en el original.
Cada copia se manda al SOAP como una entrada propia (`2589:20 2589:20`), que es
justo lo que ya sabía trocear sendStoreItems (12 por correo) y parsear
fulfillStoreOrder: el formato del pedido de tarjeta no cambia.
El servidor no se fía del cliente: priceStoreCart recibe {id, copies}, valida las
copias (1..MAX_COPIES, como el MAX_QTY de send-gift), rechaza el carrito si algún
id no existe y recalcula los totales como precio*copias contra la BD.
Verificado contra la API con saldo de prueba, no solo en la interfaz: con 500 PD
y un ítem de 200, 2 copias (400) pasan el cobro y 3 (600) dan insufficientPd. Si
el servidor ignorase las copias ambas darían lo mismo, así que el corte exacto
entre 2 y 3 prueba que multiplica. Comprobado también que el reembolso del
deliveryFailed devuelve los 400 PD, y que copias=0 o 101 se rechazan.
El CSS del input va con `#cart-list` por especificidad, no por gusto: el tema
define `input[type=number] { width: 290px }` y se carga el último para ganar la
cascada, así que un `.store-copies` a secas perdía y el input salía de 290px
reventando la tabla (send-gift se libra porque usa estilo en línea).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El carrito enseñaba PD/PV aunque eligieras tarjeta, y por defecto ya presuponía
saldo. Ahora se muestran SIEMPRE las dos monedas y se apaga la que no se va a
cobrar con el método elegido, así que no presupone nada ni se contradice.
Los euros por línea van con hasta 3 decimales a propósito: un ítem en PV con
precio impar cuesta medio céntimo (75 PV = 0,375 €), y redondear cada línea a 2
haría que las líneas no sumaran el total (0,38 + 0,38 = 0,76 contra un total de
0,75). El total sí va a céntimos: es el importe que se cobra de verdad. Y con
mínimo 2 decimales, que es dinero: "1,50 €", no "1,5 €". El formateo va por
Intl con el locale de la web, así que en español sale con coma; antes el
selector interpolaba el número crudo y decía "0.75 €" con punto.
Mínimos de las pasarelas: SumUp ya estaba en 1 € y Stripe estaba en 0,50 €,
ahora también 1 €. Estaban sueltos en la ruta; se centralizan en
`lib/store-pricing`, un módulo PURO que importan tanto la ruta como el
componente, para que el carrito enseñe exactamente lo que se valida y se cobra.
`storeEuroTotal` pasa a vivir ahí (una sola implementación) y lib/store lo
envuelve con un guard: si alguien cambia PD_PER_UNIT o VP_PRICE_FACTOR sin tocar
store-pricing, revienta al primer uso en vez de cobrar mal en silencio (esos
módulos tocan BD y no pueden llegar al bundle del cliente, de ahí la
duplicación, igual que en PaymentMethodSelect).
Por debajo del mínimo, la opción de tarjeta se desactiva y dice "Mínimo 1,00 €"
en vez de dejarte pulsar Enviar para que la API la rechace. El método efectivo
se deriva en el render (si el carrito baja del mínimo con tarjeta ya elegida,
cae al saldo) en vez de sincronizarlo con un setState en un efecto, que
provocaría renders en cascada.
Verificado en el navegador: con 0,375 € las dos tarjetas salen desactivadas y
elegido el saldo; con 1,50 € se habilitan. Las dos líneas de 0,375 € suman el
total de 0,75 €.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El tooltip se inyecta en el <body>, así que le caen encima las reglas globales
del tema. La culpable era `th { height: 40px }` (global, viene tal cual del tema
original): el tooltip usa <th> para la columna de la derecha (fase, tipo de
arma, velocidad...), y cada una de esas filas se estiraba a 40px en vez de ~17.
Cuatro filas de esas = los ~100px que sobraban.
El store original no lo sufre porque su tooltip lo pinta otro script, con otro
markup: es un problema que aparece al usar el tooltips.js de wowhead de verdad.
Afectaba a TODAS las páginas con tooltips, no solo a la tienda.
Medido contra una página desnuda con solo tooltips.js (misma versión, mismo
ítem), que es la referencia de cómo debe verse: el tooltip pasa de 390px de alto
a 289, exactamente el de la referencia. Las tablas del tema no se tocan: la
regla va acotada a .wowhead-tooltip th, y el <th> del carrito sigue midiendo
40px.
De paso, el tooltip no tenía fondo y se leía la página a través de él. No era
cosa nuestra —pasa igual en la página desnuda—: universal.css solo le da layout
y colores de calidad, y el fondo lo pone el CSS de wowhead.com, que aquí no se
carga. Se le da el fondo y el borde de las cajas del tema.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dos arreglos pendientes que quedaron señalados:
1. "Sacedorte" era una errata de "Sacerdote" en tres categorías (3-10, 4-10 y
19-9; la 20-9 estaba bien). Se veía así en la web en español. Corregido en la
BD y, sobre todo, en el seed `store_catalog.sql`, que la traía 3 veces: sin
eso, reconstruir la BD la resucitaba. Regenerado store_category_en.sql (217
nombres distintos en vez de 218: las dos grafías ya son una).
Un escaneo por parecido contra los términos oficiales de los DB2 confirma que
era la única errata; el resto de casi-coincidencias son plurales legítimos
("Espadas" vs el "Espada" del juego).
2. El cuadro del ítem medía 160px y el del store original 200. Igualado. Solo
afecta a /store: send-gift fija las columnas de su rejilla a 160px y hace
width:auto, y se ha comprobado que sigue midiendo 160.
Con solo cambiar la anchura se quedaba en 200 en vez de 214: el tema se
diseñó para el content-box del navegador y el preflight de Tailwind mete
border-box, así que el padding y el borde dejaban de sumar (el mismo problema
que ya estaba arreglado para los inputs). Se restaura content-box solo en la
tienda, porque en la rejilla de send-gift desbordaría las columnas.
Medido en el navegador: el cuadro pasa a 214px de ancho, que es lo que mide el
del original (213 en la captura). send-gift sigue en 160.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comparado contra el store original (ultimowow /es/store-bennu) con una sesión
real: markup, CSS y clases que aplica su power.js. Cinco causas distintas:
- Los nombres de ítem salían azules: el layout declaraba `const whTooltips`,
que queda en el ámbito léxico del script y NO crea propiedad en `window`,
que es de donde tooltips.js lee la config -> se ignoraba entera. Ahora
`window.whTooltips`, con colorLinks+iconizeLinks (el original no define
whTooltips y usa esos defaults; sus enlaces acaban con clases `icontinyl q4`).
renameLinks sigue en false: el texto sale de nuestra BD, en español.
- tooltips.js solo recorre el DOM al cargar, así que los enlaces que monta
React (hoja del árbol, carrito) no se teñían. Se pasan por
$WowheadPower.refreshLinks(), igual que hace el original tras cada AJAX.
- globals.css redefinía .item-box/.item-name/.item-img-box/.max-left-table2,
que el tema nw-ryu ya trae del original, y con más especificidad ganaba la
cascada. El peor: un `border-collapse` que anulaba el `border-spacing: 0 8px`
del tema, que es lo que separa las filas del carrito.
- Faltaban la caja "Carrito - <REINO>" y "Personaje seleccionado: <nombre>"
(coloreado por clase), que el original sí tiene.
- El total de la columna "Cant" mostraba el número de líneas en vez de la suma
de cantidades: un ítem que entrega un mazo de 20 debe contar 20, no 1.
Verificado con navegador contra un build de producción: el nombre del ítem sale
con clases `icontinyl q4` y color rgb(163,53,238), el mismo valor exacto que
devuelve el original.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El selector funcionaba: el fetch del catálogo respondía y React montaba el
árbol entero, pero el tema lo tenía oculto y no se veía nada en pantalla.
Son reglas del store original (BENNU), que ocultaba estos elementos y los
revelaba con jQuery (.show()/slideToggle()). En el port a React la visibilidad
la decide el montaje del componente, y el tema nw-ryu se carga el último para
ganar la cascada, así que hay que deshacerlas con !important:
- #store-div: ocultaba el árbol completo (el síntoma reportado).
- #store-list li ul: habría ocultado las subcategorías al desplegarlas.
Además, el <ul> anidado comparte clase con el <span> que lo abre (igual que en
el markup original), así que el display:inline-block de los spans deformaba la
lista. Esas reglas pasan a ir atadas a `span`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replica el bloque Novedades del diseño original: fieldset plegable con el
changelog (Noviembre 2024) y sus objetos enlazados a wowhead (icono zamimg tiny
+ color por calidad). components/StoreNews.tsx (client, toggle con flecha
rotate/rotate2 del tema), incluido en la caja de info de /store. i18n
Store.newsTitle/newsIntro/newsDate (es/en).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Porta la tienda de ítems/transfiguración del sistema antiguo: elegir personaje →
árbol de categorías (3 niveles) → carrito → enviar por correo. Se paga con el
saldo PD o PV de la cuenta (cada ítem lleva su precio y moneda).
- BD: home_store_category (árbol por code "1-1-1") + home_store_item
(mismos item_id, nombres, iconos, precios y moneda que el original;
2947 en PD, 121 en PV). Seed en sql/store_catalog.sql, extraído del HTML real.
- lib/store.ts: getStoreCatalog (árbol anidado), getStoreBalances, priceStoreCart
(precios REALES de BD), purchaseStoreCart (cobro atómico PD+PV con FOR UPDATE y
reembolso si el envío SOAP falla; `.send items` troceado a 12/correo).
- API: GET /api/store/catalog (tras elegir personaje) y POST /api/store/send.
- components/StoreBrowser.tsx: selector de personaje, árbol colapsable (ítems
montados solo al abrir), carrito con totales PD/PV, enviar, modal de resultado.
- página /store con la info y el título "Tienda - REINO". Enlaces de ítems e
iconos por wowhead/zamimg (con tooltip y locale). El enlace ya existía en
AccountTools. i18n namespace Store (es/en). CSS del árbol/carrito/modal.
Verificado: catálogo E2E (750 cats/3068 ítems), cobro atómico + reembolso y
detección de saldo insuficiente (cuenta 15). SOAP real sin probar (worldserver
caído en este entorno).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Los 9 servicios con precio en euros (rename, customize, change-race,
change-faction, level-up, gold, transfer, restore-item, send-gift) ahora
ofrecen un selector de forma de pago con las 3 opciones: saldo PD, tarjeta
(Stripe) o tarjeta (SumUp).
- lib/dpoints: spendDPoints() descuenta PD de forma atómica (FOR UPDATE).
- lib/pay-with-dpoints: paga con saldo PD, ejecuta la acción al momento y
reembolsa si la ejecución falla; 100 PD = 1 €.
- rutas checkout (character/[service] y gift): rama provider='pd' que valida
saldo, descuenta, ejecuta fulfill y devuelve la URL de éxito (sin pasarela).
- service-success: rama provider='pd' (la entrega ya se hizo en el checkout).
- PaymentMethodSelect: componente compartido con coste por método y saldo;
desactiva PD si no hay saldo suficiente.
- Formularios (PaidServiceForm, Gold, Transfer, RestoreItems, SendGift) y sus
páginas pasan el saldo PD y envían el método elegido + locale.
- i18n: namespace Pay (es/en); añadidas claves Paid.restore-item que faltaban.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El tema nw-ryu se diseñó para content-box; los items del nav usan min-width:126px
+ padding:24px (ancho total 174px). El preflight de Tailwind fuerza border-box, que
mete el padding dentro del min-width -> items ~48px más estrechos y header apretado
respecto a ultimowow.com. Se revierte a content-box en .nav-bar a / .nav-dropdown-btn
/ .nav-dropdown-content a (mismo patrón que ya se hizo con inputs/tablas/img).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Activación de 2FA (TOTP) para el login del juego, guardando el secreto en
acore_auth.account.totp_secret tal como lo lee el bnetserver 3.4.3.
- lib/two-factor.ts: TOTP (HMAC-SHA1, 30s, 6 dígitos, ±1) idéntico al core
(verificado contra los vectores RFC 6238), Base32, otpauth, y codificado
del secreto: crudo por defecto o AES-128-GCM (ciphertext‖IV12‖tag12) si se
define TOTP_MASTER_SECRET (mismo hex que TOTPMasterSecret del servidor).
- Flujo seguro sin bloqueos: el secreto se genera y queda PENDIENTE en la
sesión; sólo se escribe en la cuenta tras verificar un código válido.
- /api/account/2fa: activar (password + token de seguridad), verificar y
desactivar (con código actual). QR generado en el servidor (el secreto no
sale a terceros).
- Página + componente fieles al markup (preview, pasos, copiar clave, ojos).
Verificado E2E: activar -> QR/secreto -> verificar -> totp_secret = 20 bytes
(igual al secreto escaneado) -> desactivar -> NULL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Los SVG no tenían tamaño intrínseco y se renderizaban enormes encima del
texto. Se porta el layout flex de .dnt-methods del tema original y se
dimensionan los iconos (22px, en línea con el texto) igual que se veía
PayPal. Los SVG llevan ahora width/height explícitos.
Verificado además el flujo Stripe de extremo a extremo (tarjeta de prueba
4242): pago -> página de éxito -> 100 PD acreditados por €1 con reclamo
atómico idempotente. Datos de prueba restaurados tras la verificación.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Elimina las pestañas de PayPal/dLocal/Crypto/Skrill y por país; deja
Información + Stripe + SumUp (las dos únicas pasarelas que se usarán).
- Stripe: totalmente funcional reutilizando createCheckoutSession + webhook
+ página de éxito. Nueva ruta /api/dpoints/checkout crea la sesión con
importe elegido por el usuario (1 unidad = 100 PD).
- SumUp: integración real (lib/sumup.ts) mediante checkout hospedado,
habilitada al definir SUMUP_API_KEY/SUMUP_MERCHANT_CODE; si no, avisa.
- Nuevo servicio de entrega `dpoints` (lib/paid-services.ts) que acredita
PD en home_api_points; entrega idempotente vía reclamo atómico.
- Página /d-points-success entrega ambos proveedores (session_id/ref).
- Iconos SVG generados para cada pasarela (stripe/sumup, marca + wordmark).
- Moneda configurable con DP_CURRENCY (por defecto EUR).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Reescrita la página: box «Información» con la guía completa de Recluta a un
amigo (marca → nombre del reino) + box «Panel de Recluta a un amigo».
- Nuevo RecruitPanel: estadísticas (cuenta reclutada, amigos reclutados, amigos
al nivel 80, recompensas reclamadas X/6) + tabla de recompensas (objetivo,
objeto con icono y color de calidad, estado) con botón Reclamar para las
disponibles + sección «Recompensas disponibles». Sustituye a RecruitClaim.
- lib/recruit-claim: getRecruitStats (isRecruited + recruitedCount).
- Colores de calidad de objeto .q0-.q5 en globals.css.
- Sembradas las 6 recompensas en home_recruitreward (seed en sql/).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El <form> dentro del <p> «Nombre de la cuenta» era HTML inválido y el mensaje
salía inline (en inglés «Wrong password» partía en «Add Wrong / Password»).
Se quita el form: input+botón (onClick, Enter) en un span-bloque .add-account-box
debajo del nombre, y el mensaje en .add-account-msg (display:block) en su propia
línea. Igual en ES y EN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Junto al «Nombre de la cuenta» hay un botón «+» que pide la contraseña y crea
una nueva cuenta de juego bajo la Battle.net. Ruta /api/account/add-game-account:
verifica la contraseña (authenticate contra battlenet_accounts), calcula el
siguiente battlenet_index (máx 8) y crea la fila `account` con SRP6 Grunt
derivado de la contraseña (gameMakeRegistration) — igual que la activación, así
la cuenta sirve para entrar al juego. Recarga completa al crear/cambiar.
GameAccountSwitcher siempre presente (select si >1, texto si 1) + «+».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- El desplegable WOW1/WOW2… ya no es un box aparte: va dentro del campo «Nombre
de la cuenta» (select compacto .account-select). Con una sola cuenta, texto plano.
- FIX iconos: el cambio de cuenta hacía router.refresh() (navegación suave) que en
algunos navegadores dejaba caer las hojas de estilo/FA y no se recuperaban. Ahora
hace window.location.reload() (recarga completa) → todo se recarga bien.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El tema tiene `a:visited{color:#2471a9}` (espec. 0,1,1) que ganaba a
`.nw-page-link` (0,1,0) → Anterior/Siguiente y los números visitados salían
azules. Se añaden selectores `.nw-page-link:link/:visited/:hover` y
`.nw-page-current:link/:visited` (espec. 0,2,0) para ganar y mantener el
estilo del tema (crema + actual en dorado).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- /changelogs pasa a 5 commits por página.
- Botones de paginación (.nw-page-link) con el estilo de los botones del tema:
fondo tenue hsla, borde, padding y hover como el resto de la web; la página
actual en dorado. También afecta a la paginación del foro (consistencia).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El override box-sizing:content-box incluía `select`, agrandándolo a 60px de
alto y 320px de ancho. El default del navegador para <select> es border-box
(40px), igual que la web original sin Tailwind. Se quita `select` del override
(los inputs de texto siguen en content-box, que sí es su default). Corrige el
campo de /download-client y todos los selects del sitio (recover, admin,
servicios de personaje).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El botón Personalizar del banner ahora despliega un panel con checkboxes por
categoría (Necesarias fija+desactivada, Analíticas, Marketing) y «Guardar
preferencias», tal como el markup del usuario. Al guardar, escribe la cookie
uw_cookie_consent con la selección granular y sincroniza con Zaraz. Categorías
alineadas a las del panel (Necesarias/Analíticas/Marketing); ConsentStatus y
acceptAll/rejectAll usan las mismas. Estilos del panel en globals.css.
Verificado: Personalizar abre el panel, marcar Analíticas + Guardar deja la
cookie {necessary,analytics:true,marketing:false} y /cookies lo refleja.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Banner real `uw-cookie-content` (Rechazar/Personalizar/Aceptar todas) fijo
abajo y site-wide (montado en el layout, componente CookieBanner). Aparece
en la primera visita, guarda la decisión en la cookie uw_cookie_consent (1
año) y se sincroniza con Cloudflare Zaraz si está presente
(zaraz.consent.setAll / .modal). Estilos en globals.css.
- «Personalizar» abre el modal granular de Zaraz (o /cookies si no hay Zaraz).
- window.UWCookies {showBanner, revokeConsent, acceptAll, rejectAll}.
- Página /cookies: panel «Tu estado de consentimiento» (ConsentStatus) que
muestra los datos EN VIVO de la API de Zaraz (zaraz.consent.getAll()) por
propósito; si Zaraz no está, cae a la preferencia guardada por categoría.
Verificado con navegador: el banner aparece, «Aceptar todas» lo oculta y
escribe la cookie, y el panel de /cookies refleja el estado.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Crea la ruta de Declaración de Cookies con el markup del tema y las 5 tablas
de cookies (uw-cookie-table, estilizadas en globals.css) + estilos de listas
y botones (btn-uw). Los enlaces internos .php se cablean a las rutas Next
(/privacy-policy, /contact-us).
Sistema de consentimiento = Cloudflare Zaraz (elección del usuario):
components/CookieConsent.tsx expone window.UWCookies.showBanner()
(zaraz.consent.modal=true) y revokeConsent() (zaraz.consent.setAll(false)),
que Cloudflare activa cuando el dominio pasa por su proxy con Zaraz Consent;
degrada con aviso si Zaraz no está presente. El footer ya enlazaba aquí.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La página de cuenta no se parecía a la de Django (oro descuadrado, info como
tabla, herramientas en grupos abiertos). Reescrita para replicar el
partials/my-account.html + isla AccountDashboard de Django:
- Información en DOS fieldsets (.account-fieldset): «Datos básicos» (cuentas,
correos, fecha de registro, IPs) y «Estado de la cuenta» (insignia de rango
nw-rank + PD/PV/créditos/token + baneo + reclutamiento). lib/account.ts
ampliado para traer reg_mail/email/joindate/last_ip/last_attempt_ip, estado
de baneo (account_banned) y nº de reclutados.
- Personajes en char-box con el markup exacto de Django: retrato flotante +
oro EN LÍNEA (monedas width=10). Antes las monedas salían a tamaño nativo
(descuadre) y apiladas.
- Herramientas en paneles plegables (acordeón) «OPCIONES DE CUENTA / OPCIONES
DE PERSONAJE» como Django (componente cliente AccountTools), con los
servicios ya implementados en Next y las etiquetas/descripciones exactas.
- globals.css: revertir otro reset del preflight de Tailwind — img{display:
block} apilaba verticalmente los iconos en línea (monedas); el tema los usa
inline como la web Django. Volvemos a img{display:inline}.
Verificado con Playwright (login real con cuenta de prueba + captura): info,
personajes con oro en línea y paneles que expanden con sus iconos; home sin
regresión por el cambio de img.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
El tema nw-ryu se diseño para los defaults del navegador (la web Django no
usa Tailwind). El preflight de Tailwind rompia el layout de los formularios:
- box-sizing:border-box hacia que la height fija de los inputs (24px)
incluyera el padding → campos finos/aplastados. Se vuelve a content-box
en los inputs/select del tema (.admin-form conserva border-box).
- table{border-collapse:collapse} anulaba el border-spacing de las tablas
del tema (middle-center-table, max-center-table...) → filas pegadas sin
separacion. Se vuelve a border-collapse:separate.
Reglas sin @layer → ganan al preflight (que va en @layer base). Verificado
por captura: los campos de /login ahora coinciden pixel a pixel con Django.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reescritura completa del frontend Next.js del sistema visual Tailwind
"simulado" al tema Django original (nw-ryu), para paridad pixel con la
web actual antes del cutover.
- Tema real: copia de static/nw-themes/nw-ryu + favicons a public/, el
layout carga el novavow-style.css real de ultimo (gana la cascada sobre
Tailwind) + Font Awesome.
- Shell replicando los partials Django: SiteHeader, Video, Social, Footer,
ServerClock; home con estructura real (main-page/middle-content/...).
- Helpers reutilizables: PageShell (main-page > middle-content > body-content
> title-content) y ServiceBox (title-box-content + back-to-account).
- Paginas migradas a clases reales del tema (fieldset/tool-button/char-box/
item-box/info-box-light/max-center-table/alert-message/botones reales),
eliminando el markup Tailwind (.nw-btn/.nw-card/.nw-input):
auth (login/register/recover/reset/select-account/activate),
cuenta + servicios de personaje (revive/unstuck/rename/customize/
change-race/change-faction/level-up/gold/transfer + pago Stripe),
ajustes (change-password/change-email/security-token),
comunidad (vote-points/recruit/battlepay), foro completo, y
admin (indice + 7 secciones + los Admin*Manager).
- Se conserva el bilingue (next-intl); claves nuevas en messages/es|en.json.
Verificado: typecheck + build OK; rutas protegidas 307->login; sin
MISSING_MESSAGE; cero Tailwind residual (solo .nw-tool-btn/.nw-page,
clases propias en globals.css).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- lib/data/zone-names.json: ZONE_NAMES (9156 zonas) exportado de zone_definitions.py.
- lib/character-info.ts: getZoneName, getClassCss/getClassName, getRaceName y
getCharacterImage (raza/clase/género -> /races/big-*.webp, con validación de
compatibilidad clase-raza y fallback unknown), portado de account.py.
- lib/account.ts: Character enriquecido (race, class, zone, imageUrl, class/raceName).
- account page: tarjetas con imagen de raza, color de clase WoW, nivel, raza/clase,
📍 zona y oro. globals.css: colores de clase (.class-*).
- public/races: 20 imágenes de raza del tema + unknown.webp.
Verificado: build OK, imágenes 200, /account 307→login. El JSON de zonas se usa
solo en servidor (no infla el bundle cliente).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reemplazo masivo de los estilos sueltos repetidos por las clases del sistema visual
en ~20 componentes/páginas (auth, cuenta, servicios de personaje, foro, admin, voto):
- inputs -> .nw-input, botones primarios -> .nw-btn, tarjetas -> .nw-card.
Cohesión visual completa con la home y la cabecera.
Verificado: build OK, páginas clave 200/redirect correctos, clases aplicadas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- globals.css: tokens @theme (nw-bg/panel/border/gold/text/muted), fondo con
degradado + brillo radial, y componentes reutilizables (.nw-btn, .nw-btn-ghost,
.nw-card, .nw-heading con oro degradado).
- Header: sticky con backdrop-blur y marca en oro degradado.
- Home: sección hero (nombre del server + tagline + CTAs Crear cuenta / Foros) y
estado del servidor + noticias en tarjetas (nw-card). Textos hero en catálogos.
Verificado: build OK, home renderiza el hero/tarjetas, los tokens nw compilan en el
CSS. Sistema visual base listo para aplicar al resto de páginas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Inicia la reescritura del frontend a una app Next.js con SSR (Django pasará a ser
API JSON). No toca producción: Django sigue sirviendo www; Next corre aparte en :3001.
- web-next/: Next.js 16 App Router, TypeScript, Tailwind CSS v4, alias @/*.
- lib/api.ts: cliente de la API Django (SSR -> 127.0.0.1:8001; navegador -> /api
relativo vía Caddy).
- app/page.tsx: home con SSR por petición (force-dynamic) que hace fetch de
/api/home/news/ y /api/home/status/ y renderiza noticias + estado con Tailwind.
- app/layout.tsx: lang=es + SEO (title/description/keywords/OpenGraph) por el
Metadata API de Next (renderizado en servidor).
- Servicio systemd novawow-next (next start -p 3001, DJANGO_API_BASE).
Nota Next 16 (breaking): params/searchParams son Promise (await); fetch no se
cachea por defecto. Verificado: SSR sirve HTML con datos reales de Django y SEO
en el <head>; producción Django intacta.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>