Files
NightSpire/web-next/app/globals.css
T
Inna e500bd7a84 store: precios en euros en el carrito y mínimo real de las pasarelas
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>
2026-07-15 09:55:27 +00:00

629 lines
18 KiB
CSS

@import 'tailwindcss';
/* Tokens de diseño NovaWoW (paleta oscura fantasía WotLK). */
@theme {
--color-nw-bg: #140d08;
--color-nw-panel: #241812;
--color-nw-panel-2: #2c1e14;
--color-nw-border: #4a3320;
--color-nw-gold: #d79602;
--color-nw-gold-light: #f0b93f;
--color-nw-text: #e8dccb;
--color-nw-muted: #b1997f;
}
:root {
color-scheme: dark;
}
/* El fondo, la tipografía (FuturaEF-Book) y los estilos base del <body>
los controla el tema real nw-ryu (novawow-style.css), cargado en el <head>
del layout. No los redefinimos aquí para que el tema Django quede idéntico. */
/* El tema nw-ryu se diseñó para los defaults del navegador (igual que la web
Django, que NO usa Tailwind). El preflight de Tailwind rompe dos cosas:
1) box-sizing:border-box hace que la height fija de los inputs incluya el
padding → campos aplastados. Volvemos a content-box SOLO en los inputs de
texto que estiliza el tema (el navegador les da content-box por defecto,
como la web Django sin Tailwind; los de .admin-form conservan border-box
por su mayor especificidad). OJO: `select` NO va aquí — su default del
navegador es border-box (40px); forzarlo a content-box lo agranda a 60px.
2) border-collapse:collapse anula el border-spacing de las tablas del tema
(middle-center-table, etc.) → filas pegadas. Volvemos a separate.
Reglas SIN capa: ganan al preflight de Tailwind (que va en @layer base). */
input[type='text'],
input[type='email'],
input[type='password'],
input[type='number'] {
box-sizing: content-box;
}
table {
border-collapse: separate;
}
/* El tooltip de wowhead se inyecta en el <body>, así que le caen encima las
reglas globales del tema. La que lo rompe es `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, dejando huecos enormes entre líneas. El store original no lo sufre
porque su tooltip lo pinta otro script con otro markup. Se neutraliza solo
dentro del tooltip: alto natural y sin el fondo de cabecera del tema.
`font-weight` no hace falta tocarlo: wowhead ya lo pone normal. */
.wowhead-tooltip th {
height: auto;
background-color: transparent;
}
/* tooltips.js NO trae fondo para el tooltip (universal.css solo le da layout y
los colores de calidad): en wowhead.com lo pinta el CSS de su web, que aquí no
cargamos. Sin esto el tooltip es transparente y se lee la página a través de
él —se ve igual en una página desnuda con solo tooltips.js, no es cosa del
tema—. Se le da el fondo y el borde de las cajas del tema. */
.wowhead-tooltip {
background: #0b0705;
border: 2px solid #352e2b;
}
/* 3) Tailwind pone img{display:block}, lo que apila verticalmente los iconos
en línea (monedas de oro/plata/cobre, iconos inline del tema). El tema los
usa inline (como la web Django). Volvemos a inline; las imágenes que deban
ser bloque ya lo fijan por su propia clase (.img-med-icon, etc., mayor
especificidad). */
img {
display: inline;
}
/* 4) La barra de navegación se diseñó para content-box: sus enlaces/botones usan
`min-width: 126px` + `padding: 24px`, así que con el border-box del preflight
quedan ~48px más estrechos (contenido 78px) y el header sale apretado/pequeño
respecto al original. Volvemos a content-box en los elementos del nav para que
el ancho coincida con la web original (126 + 48 = 174px). */
.nav-bar a,
.nav-dropdown-btn,
.nav-dropdown-content a {
box-sizing: content-box;
}
/* Componentes reutilizables */
@layer components {
.nw-btn {
@apply inline-flex items-center justify-center rounded-md bg-nw-gold px-5 py-2.5 font-semibold text-[#1b120b] transition hover:bg-nw-gold-light;
}
.nw-btn-ghost {
@apply inline-flex items-center justify-center rounded-md border border-nw-border px-5 py-2.5 font-semibold text-nw-text transition hover:border-nw-gold hover:text-nw-gold-light;
}
.nw-card {
@apply rounded-xl border border-nw-border/70 bg-nw-panel/80 p-5 shadow-lg shadow-black/30 backdrop-blur;
}
.nw-heading {
@apply bg-gradient-to-b from-nw-gold-light to-nw-gold bg-clip-text font-extrabold tracking-wide text-transparent;
}
.nw-input {
@apply w-full rounded-md border border-nw-border bg-nw-panel-2 px-3 py-2 text-nw-text outline-none transition focus:border-nw-gold;
}
}
/* Contenido HTML de noticias/foro */
.prose :where(a) {
color: #7cc0f7;
}
/* Foro: textarea con el look del tema real (el tema nw-ryu no estiliza textarea base). */
textarea {
width: 100%;
background-color: #000;
border: 2px solid #352e2b;
color: #fff;
font-family: 'FuturaEF-Book';
font-size: 17px;
padding: 8px;
transition: 0.5s;
}
textarea:focus {
border-color: #948474;
}
/* Foro: barra de herramientas del editor y paginación (colores del tema). */
.nw-tool-btn {
border: 2px solid #352e2b;
background: hsla(0, 0%, 100%, 0.05);
color: #ebdec2;
padding: 4px 10px;
cursor: pointer;
transition: 0.3s;
}
.nw-tool-btn:hover {
background: hsla(0, 0%, 100%, 0.1);
color: #fff;
}
/* Paginación con el estilo de los botones del tema (borde + fondo tenue + hover).
Los selectores :link/:visited elevan la especificidad para ganar a las reglas
`a`/`a:visited` del tema (que si no dejan los enlaces visitados en azul). */
.nw-page-link {
display: inline-block;
min-width: 42px;
padding: 9px 14px;
margin: 0 3px;
border: 2px solid #352e2b;
background: hsla(0, 0%, 100%, 0.05);
text-align: center;
transition: 0.3s;
}
.nw-page-link:link,
.nw-page-link:visited {
color: #ebdec2;
}
.nw-page-link:hover {
background: hsla(0, 0%, 100%, 0.1);
color: #fff;
}
.nw-page-current,
.nw-page-current:link,
.nw-page-current:visited {
border-color: #b17c02;
color: #d79602;
background: hsla(40, 90%, 45%, 0.12);
}
/* Contenido de posts del foro */
.post-content {
line-height: 22px;
}
.post-content a {
color: #2471a9;
}
/* Panel de admin: campos a ancho completo dentro de la caja del formulario. */
.admin-form input,
.admin-form textarea,
.admin-form select {
width: 100%;
box-sizing: border-box;
margin-bottom: 10px;
}
/* Listas dentro de las cajas de contenido (el tema no las estiliza y el
preflight de Tailwind quita los bullets; los devolvemos como en Django). */
.body-box-content ul {
list-style: disc;
padding-left: 22px;
margin: 8px 0;
}
.body-box-content li {
line-height: 22px;
}
/* Declaración de cookies: tablas y botones de consentimiento. */
.uw-cookie-table {
width: 100%;
border-collapse: collapse;
margin: 12px 0 16px;
font-size: 15px;
}
.uw-cookie-table th,
.uw-cookie-table td {
border: 1px solid #352e2b;
padding: 8px 10px;
text-align: left;
vertical-align: top;
}
.uw-cookie-table th {
background: hsla(0, 0%, 100%, 0.05);
color: #ebdec2;
text-transform: uppercase;
font-size: 13px;
}
.uw-cookie-table td {
color: #b1997f;
}
.uw-cookie-table a {
color: #2471a9;
}
.uw-consent-buttons {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 12px 0;
}
.btn-uw {
border: 2px solid #352e2b;
background: hsla(0, 0%, 100%, 0.05);
color: #ebdec2;
padding: 10px 18px;
cursor: pointer;
text-transform: uppercase;
transition: 0.3s;
}
.btn-uw:hover {
background: hsla(0, 0%, 100%, 0.1);
color: #fff;
}
.btn-uw-secondary {
border-color: #5c2b2b;
color: #d98c8c;
}
.btn-uw-secondary:hover {
background: hsla(0, 60%, 30%, 0.15);
color: #fff;
}
/* Banner de consentimiento de cookies (fijo abajo). */
.uw-cookie-content {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: 16px 24px;
background: #140d08;
border-top: 2px solid #4a3320;
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.55);
}
.uw-cookie-text {
flex: 1 1 420px;
}
.uw-cookie-text h3 {
color: #d79602;
margin-bottom: 6px;
}
.uw-cookie-text p {
color: #b1997f;
font-size: 14px;
line-height: 20px;
}
.uw-cookie-link {
display: inline-block;
margin-top: 6px;
color: #2471a9;
}
.uw-cookie-actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.uw-cookie-btn {
border: 2px solid #352e2b;
background: hsla(0, 0%, 100%, 0.05);
color: #ebdec2;
padding: 9px 16px;
cursor: pointer;
text-transform: uppercase;
font-size: 14px;
transition: 0.3s;
}
.uw-cookie-btn:hover {
background: hsla(0, 0%, 100%, 0.1);
color: #fff;
}
.uw-cookie-btn-accept {
border-color: #b17c02;
color: #f0b93f;
}
.uw-cookie-btn-accept:hover {
background: hsla(40, 90%, 45%, 0.15);
color: #fff;
}
.uw-cookie-btn-reject {
border-color: #5c2b2b;
color: #d98c8c;
}
.uw-cookie-btn-save {
border-color: #b17c02;
color: #f0b93f;
}
.uw-cookie-btn-save:hover {
background: hsla(40, 90%, 45%, 0.15);
color: #fff;
}
/* Panel «Personalizar» del banner (checkboxes por categoría). */
.uw-cookie-details {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid #352e2b;
}
.uw-cookie-category {
margin-bottom: 10px;
}
.uw-cookie-category label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.uw-cookie-category input[type='checkbox'] {
width: 16px;
height: 16px;
accent-color: #d79602;
cursor: pointer;
}
.uw-cookie-category input[type='checkbox']:disabled {
cursor: default;
}
.uw-cookie-cat-name {
color: #ebdec2;
font-size: 15px;
}
.uw-cookie-category p {
color: #b1997f;
font-size: 13px;
margin: 2px 0 0 24px;
line-height: 18px;
}
.uw-cookie-actions-detail {
margin-top: 8px;
}
/* Panel «Tu estado de consentimiento» en /cookies (datos de la API de Zaraz). */
.uw-consent-status {
border: 1px solid #352e2b;
padding: 12px 14px;
margin: 12px 0;
}
.uw-consent-status .uw-consent-source {
font-size: 13px;
color: #7e8d09;
margin-bottom: 8px;
}
.uw-consent-status ul {
list-style: none;
padding: 0;
margin: 0;
}
.uw-consent-status li {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 4px 0;
border-bottom: 1px solid #241812;
}
.uw-consent-status li:last-child {
border-bottom: 0;
}
.uw-consent-on {
color: #6aa84f;
}
.uw-consent-off {
color: #cc5b5b;
}
.uw-consent-always {
color: #b1997f;
}
/* Selector de cuenta inline (dentro de «Nombre de la cuenta») + botón «+». */
.account-select {
width: auto;
min-width: 92px;
height: auto;
padding: 2px 8px;
margin: 0 0 0 4px;
font-size: 17px;
vertical-align: middle;
}
.add-account-btn {
height: auto;
padding: 1px 9px;
margin: 0 0 0 6px;
font-size: 17px;
line-height: 1.4;
vertical-align: middle;
border: 2px solid #352e2b;
background: hsla(0, 0%, 100%, 0.05);
color: #ebdec2;
cursor: pointer;
transition: 0.3s;
}
.add-account-btn:hover {
background: hsla(0, 0%, 100%, 0.1);
color: #fff;
}
/* El formulario de añadir cuenta sale en bloque, debajo del nombre. */
.add-account-box {
display: block;
margin-top: 6px;
}
.add-account-input {
min-width: 150px;
}
.add-account-msg {
display: block;
margin-top: 4px;
}
/* Colores de calidad de objeto (enlaces de recompensas RAF, etc.). */
.q0 { color: #9d9d9d; }
.q1 { color: #ffffff; }
.q2 { color: #1eff00; }
.q3 { color: #0070dd; }
.q4 { color: #a335ee; }
.q5 { color: #ff8000; }
/* Colores de clase de WoW (nombre de personaje) */
.class-warrior { color: #c79c6e; }
.class-paladin { color: #f58cba; }
.class-hunter { color: #abd473; }
.class-rogue { color: #fff569; }
.class-priest { color: #ffffff; }
.class-death-knight { color: #c41f3b; }
.class-shaman { color: #0070de; }
.class-mage { color: #69ccf0; }
.class-warlock { color: #9482c9; }
.class-druid { color: #ff7d0a; }
.class-unknown { color: #f0b100; }
/* Logos en línea de las pasarelas de pago (lista de métodos en /d-points) */
.pay-inline-logo {
height: 18px;
width: auto;
vertical-align: middle;
margin: 0 2px;
}
/* Pasarelas de pago en /d-points: layout e iconos de los botones de método
(portado del tema original; los SVG no tienen tamaño intrínseco, así que se
dimensionan aquí para que el icono quede en línea con el texto como PayPal). */
.dnt-sidebar.dnt-methods {
margin-top: 5px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 5px;
}
.dnt-sidebar.dnt-methods .dnt-button-2 {
flex: 1 1 auto;
padding: 0 18px;
}
.dnt-sidebar.dnt-methods .dnt-button-2 img {
height: 22px;
width: auto;
vertical-align: middle;
margin-right: 6px;
}
/* ==== Verificación en 2 pasos (/security-2falogin) ==== */
.twofa-login-info { display: block; }
.twofa-login-preview {
float: right;
max-width: 370px;
margin-left: 24px;
margin-bottom: 8px;
height: auto;
border: 2px solid #352e2b;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}
.google-play-logo { height: 40px; width: 140px; vertical-align: middle; }
.apple-store-logo { height: 40px; width: 120px; vertical-align: middle; }
.toggle-password, .toggle-token { cursor: pointer; margin-left: -28px; color: #b1997f; transition: .3s; }
.toggle-password:hover, .toggle-token:hover { color: #fff; }
.copy-secret-container { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 6px; }
#secret-key { width: 240px; text-align: center; }
.copy-btn { cursor: pointer; }
#qr-code { display: block; margin: 0 auto; max-width: 220px; height: auto; background: #fff; padding: 8px; border-radius: 6px; }
@media screen and (max-width: 1024px) {
.twofa-login-info { display: flex; flex-direction: column-reverse; align-items: center; }
.twofa-login-preview { float: none; display: block; clear: both; margin: 10px auto 8px auto; max-width: 90vw; }
}
/* ==== Selector de forma de pago (PD / Stripe / SumUp) ==== */
.pay-method-select { margin: 10px auto 4px auto; max-width: 560px; }
.pay-method-options {
display: flex;
gap: 10px;
justify-content: center;
flex-wrap: wrap;
margin: 8px 0;
}
.pay-method-option {
flex: 1 1 150px;
min-width: 140px;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 12px 10px;
cursor: pointer;
border: 2px solid #352e2b;
border-radius: 8px;
background: rgba(0, 0, 0, 0.25);
transition: border-color .25s, background .25s;
}
.pay-method-option:hover { border-color: #6b5a4e; }
.pay-method-option.selected {
border-color: #d79602;
background: rgba(215, 150, 2, 0.10);
}
.pay-method-option.disabled { cursor: not-allowed; opacity: .55; }
.pay-method-option input[type='radio'] { accent-color: #d79602; }
.pay-method-label { color: #ebdec2; font-weight: bold; }
.pay-method-sub { font-size: 13px; }
.pay-method-balance { margin-top: 2px; font-size: 13px; }
/* ==== Tienda (/store) ==== */
#store-list, #store-list ul { list-style: none; margin: 0; padding-left: 14px; }
#store-list ul { display: block !important; }
#store-list > li { margin: 4px 0; }
/* Ojo: como en el markup original, el <ul> anidado comparte clase con el <span>
que lo abre (store-subcat/store-sub-subcat), así que estas reglas van atadas a
`span` para no convertir la lista en inline-block ni teñirla al pasar el ratón. */
#store-list span.store-cat,
#store-list span.store-subcat,
#store-list span.store-sub-subcat {
display: inline-block !important;
cursor: pointer;
padding: 3px 0;
user-select: none;
}
#store-list span.store-cat:hover,
#store-list span.store-subcat:hover,
#store-list span.store-sub-subcat:hover { color: #fff; }
#store-list span.store-cat i,
#store-list span.store-subcat i,
#store-list span.store-sub-subcat i { transition: transform .2s; margin-right: 4px; }
/* Rejilla de objetos dentro de una hoja. El tema YA define .item-box, .item-name,
.item-img-box y .item-box hr igual que el original: no se redefinen aquí (hacerlo
con `#store-list ...` gana la cascada y descuadra el cuadro del ítem). Lo único
que hace falta es deshacer el `display:none` con el que el tema oculta .item-list
(el original la revelaba con jQuery; aquí la monta React) y reponer el hueco entre
cajas: el PHP dejaba espacios en blanco entre los inline-block y JSX no los emite,
así que se imita con column-gap (el vertical ya lo da el margin del tema). */
#store-list .item-list {
display: flex !important;
flex-wrap: wrap;
align-items: flex-start;
gap: 0 5px;
margin: 8px 0 8px 10px;
}
/* Mismo problema que con los inputs de arriba: el tema se diseñó para el
content-box del navegador, y el preflight de Tailwind mete border-box. Los
200px de `.item-box` son los del original, que con su padding y su borde
miden 214; con border-box se quedaban en 200. Se restaura SOLO en la tienda:
en send-gift el .item-box va con width:auto dentro de una rejilla de columnas
de 160px, y ahí el content-box sumaría el padding y la desbordaría. */
#store-list .item-box { box-sizing: content-box; }
/* El tema esconde #store-div y los <ul> anidados (`display:none`) porque el store
original los revelaba con jQuery (.show()/slideToggle()). Aquí la visibilidad la
decide React montándolos, así que hay que deshacer ese display:none del tema
(que se carga después y gana la cascada). */
#store-div { margin-top: 14px; display: block !important; }
/* Carrito: el tema ya trae .max-left-table2 (width 95% + border-spacing 0 8px, que
es lo que separa las filas), .icon-td, .width-50-td y .justified del original.
No se pisan: el `border-collapse` que había aquí anulaba ese border-spacing. */
/* Precios del carrito: se enseñan SIEMPRE las dos monedas (saldo PD/PV y euros)
y se apaga la que no se va a cobrar con el método elegido. Así el carrito no
presupone nada antes de elegir ni se contradice al pagar con tarjeta. */
.store-price { display: block; transition: opacity .2s, color .2s; }
.store-price-eur { color: #ebdec2; }
.store-price-total { font-weight: bold; }
.store-price-off { opacity: .4; }
.store-price-off .dp-color,
.store-price-off .vp-color { color: inherit; }
/* Modal de resultado */
.modal-div {
position: fixed; inset: 0; z-index: 1000;
background: rgba(0, 0, 0, 0.6);
display: flex; align-items: center; justify-content: center;
}
.modal-content {
position: relative;
background: #17110e;
border: 2px solid #d79602;
border-radius: 10px;
padding: 26px 30px;
max-width: 90vw; min-width: 260px;
text-align: center;
}
.modal-close {
position: absolute; top: 6px; right: 12px;
cursor: pointer; font-size: 22px; color: #b1997f;
}
.modal-close:hover { color: #fff; }
/* Novedades de la tienda (cabecera plegable) */
#store-news { cursor: pointer; user-select: none; }
#store-news-content { margin-top: 6px; }
#store-news-content ul { padding-left: 16px; }
#store-news-content li { margin: 4px 0; }