diff --git a/web-next/app/[locale]/layout.tsx b/web-next/app/[locale]/layout.tsx
index 28105bd..5c9455f 100644
--- a/web-next/app/[locale]/layout.tsx
+++ b/web-next/app/[locale]/layout.tsx
@@ -54,8 +54,9 @@ export default async function LocaleLayout({
return (
- {/* Verificación de la cuenta de Google AdSense: solo si hay ADSENSE_CLIENT_ID
- y el visitante no es admin. Ver components/AdSense.tsx. */}
+ {/* Google AdSense (meta de verificación + adsbygoogle.js): solo si hay
+ ADSENSE_CLIENT_ID, el visitante no es admin ni ha pagado, y —para el
+ script— ha aceptado «Marketing». Ver components/AdSense.tsx. */}
{/* Iconos del sitio (idénticos a la web Django) */}
diff --git a/web-next/app/forum.css b/web-next/app/forum.css
index 852e0ef..5120585 100644
--- a/web-next/app/forum.css
+++ b/web-next/app/forum.css
@@ -295,3 +295,6 @@ a.nice_button:focus {
.topic_post .left_side { flex-basis: 100%; }
.topic_post .left_side .user_avatar { width: 56px; height: 56px; }
}
+
+/* Nombre de hermandad junto al personaje en el foro */
+.username_guild { color: #c8a24c; font-weight: 400; font-size: .8em; }
diff --git a/web-next/components/AdSense.tsx b/web-next/components/AdSense.tsx
index 7bddbaf..4e36512 100644
--- a/web-next/components/AdSense.tsx
+++ b/web-next/components/AdSense.tsx
@@ -1,7 +1,11 @@
import { shouldServeAds } from '@/lib/ads'
+import { AdSenseScript } from './AdSenseScript'
/**
- * Etiqueta de verificación de Google AdSense, con DOS interruptores:
+ * Google AdSense: etiqueta de verificación de la cuenta + script de anuncios
+ * («Auto ads»: se pega en todas las páginas y Google decide dónde colocarlos).
+ *
+ * TRES interruptores:
*
* 1. `ADSENSE_CLIENT_ID` en el .env. Vacío o sin poner = no se emite nada. Es el
* interruptor del administrador. No lleva prefijo NEXT_PUBLIC_ a propósito:
@@ -10,10 +14,11 @@ import { shouldServeAds } from '@/lib/ads'
* rebuildear para activarlo o desactivarlo.
* 2. El filtro común de publicidad (`shouldServeAds`, ver lib/ads.ts): fuera para
* admins y para quien sea rango 2+ o haya pagado alguna vez.
- *
- * Ojo: esto es solo la etiqueta de verificación de la cuenta, no carga adsbygoogle.js
- * ni escribe cookies, por eso no pasa por el banner de consentimiento (el script de
- * anuncios sí lo hace, ver components/Monetag.tsx).
+ * 3. El consentimiento «Marketing» del visitante, pero SOLO para el script, en
+ * (cliente). La se queda fuera del banner a propósito:
+ * es un identificador estático de la cuenta, no carga nada ni escribe cookies,
+ * y Google la quiere presente para poder verificar el sitio aunque el visitante
+ * rechace los anuncios.
*/
export async function AdSense() {
const client = process.env.ADSENSE_CLIENT_ID
@@ -21,5 +26,10 @@ export async function AdSense() {
if (!(await shouldServeAds())) return null
- return
+ return (
+ <>
+
+
+ >
+ )
}
diff --git a/web-next/components/AdSenseScript.tsx b/web-next/components/AdSenseScript.tsx
new file mode 100644
index 0000000..23d222c
--- /dev/null
+++ b/web-next/components/AdSenseScript.tsx
@@ -0,0 +1,32 @@
+'use client'
+
+import Script from 'next/script'
+import { useConsent } from './CookieConsent'
+
+/**
+ * Parte de cliente de AdSense: el script que de verdad pinta los anuncios
+ * (adsbygoogle.js, «Auto ads»). Existe separado de solo para mirar el
+ * consentimiento, que vive en el navegador (cookie `ns_cookie_consent` + evento
+ * `ns:consentchange`).
+ *
+ * A diferencia de la etiqueta de verificación, esto SÍ pasa por el banner:
+ * adsbygoogle.js escribe cookies de personalización y perfila al visitante, así que
+ * cargarlo sin mirar la categoría «Marketing» convertiría el banner en mentira y, con
+ * visitantes en la UE, en un problema legal. Mismo criterio que .
+ *
+ * `useConsent` reacciona en caliente: entra en cuanto se acepta y deja de renderizarse
+ * si se revoca, sin recargar. Arranca en `false`, así que hasta confirmar el
+ * consentimiento no se carga nada.
+ */
+export function AdSenseScript({ client }: { client: string }) {
+ const consent = useConsent('marketing')
+ if (!consent) return null
+
+ return (
+
+ )
+}
diff --git a/web-next/lib/forum.ts b/web-next/lib/forum.ts
index 54e77fc..3ef454c 100644
--- a/web-next/lib/forum.ts
+++ b/web-next/lib/forum.ts
@@ -87,6 +87,23 @@ export interface PosterInfo {
joindate: string | null
postCount: number
avatar: string
+ charName: string | null
+ race: number
+ gender: number
+ classId: number
+ level: number
+ guildName: string | null
+ achPoints: number
+}
+
+// Imagen de raza (CDN wowhead) según raza+género del personaje elegido.
+const FORUM_RACE_SLUG: Record = {
+ 1: 'human', 2: 'orc', 3: 'dwarf', 4: 'nightelf', 5: 'scourge', 6: 'tauren',
+ 7: 'gnome', 8: 'troll', 9: 'goblin', 10: 'bloodelf', 11: 'draenei', 22: 'worgen',
+}
+function raceAvatar(race: number, gender: number): string {
+ const slug = FORUM_RACE_SLUG[race] || 'human'
+ return `https://wow.zamimg.com/images/wow/icons/large/race_${slug}_${gender === 1 ? 'female' : 'male'}.jpg`
}
// Avatar por clase (FusionCMS) del personaje de más nivel de la cuenta. Solo clases
@@ -383,20 +400,74 @@ export async function resolvePosters(ids: number[]): Promise