// Aula — Editorial knowledge feed

// ── Design tokens ─────────────────────────────────────────────────────────────
const AL = {
  page:   '#FAFAF7',
  card:   '#FFFFFF',
  navy:   '#0F1A2E',
  sub:    '#5C6477',
  muted:  '#6B7280',
  border: 'rgba(15,26,46,0.07)',
  bord2:  'rgba(15,26,46,0.11)',
};

const ALcat = {
  hipertrofia:  { label:'Hipertrofia',  c:'#059669', bg:'rgba(5,150,105,0.09)',   dot:'#10B981', grad:'135deg,#082e1d 0%,#0d5c3a 100%' },
  fuerza:       { label:'Fuerza',       c:'#1E40AF', bg:'rgba(30,64,175,0.08)',   dot:'#3B82F6', grad:'135deg,#08102e 0%,#1a2e70 100%' },
  nutricion:    { label:'Nutrición',    c:'#B45309', bg:'rgba(180,83,9,0.09)',    dot:'#F59E0B', grad:'135deg,#2e1a05 0%,#704215 100%' },
  recuperacion: { label:'Recuperación', c:'#0369A1', bg:'rgba(3,105,161,0.08)',   dot:'#38BDF8', grad:'135deg,#041824 0%,#093a5c 100%' },
  cognitivo:    { label:'Cognitivo',    c:'#6D28D9', bg:'rgba(109,40,217,0.08)',  dot:'#A78BFA', grad:'135deg,#120a2e 0%,#3d1a8a 100%' },
  sueno:        { label:'Sueño',        c:'#0F766E', bg:'rgba(15,118,110,0.09)',  dot:'#2DD4BF', grad:'135deg,#04201e 0%,#0a4a45 100%' },
  biomecánica:  { label:'Biomecánica',  c:'#be123c', bg:'rgba(190,18,60,0.08)',   dot:'#FB7185', grad:'135deg,#240510 0%,#700a1e 100%' },
  suplementos:  { label:'Suplementos',  c:'#7C3AED', bg:'rgba(124,58,237,0.08)',  dot:'#C4B5FD', grad:'135deg,#130a2e 0%,#4a1a8c 100%' },
};

const EVIDENCE_LABEL = {
  'meta-analysis':'Meta-análisis', rct:'ECA', cohort:'Cohorte',
  review:'Revisión', 'expert-opinion':'Experto',
  'umbrella-review':'Revisión paraguas', 'systematic-review':'Revisión sistemática',
};

// Per-reference evidence-type tag. Each reference block may carry `ev` with the
// display label directly (e.g. 'Metaanálisis'); shown as a discreet pill in the
// bibliography so the reader can weigh each source at a glance.
function AlRefEvTag({ label }) {
  if (!label) return null;
  return (
    <span style={{
      display:'inline-block', marginLeft:8, verticalAlign:'1px',
      padding:'1px 7px', borderRadius:5, whiteSpace:'nowrap',
      background:'rgba(15,26,46,0.05)', color:AL.muted,
      fontFamily:'ui-monospace,Menlo,monospace', fontSize:9.5, fontWeight:700, letterSpacing:0.3,
    }}>{label}</span>
  );
}

// ── Portadas del Aula (Unsplash CDN) — UNA por categoría ──────────────────────
// Fuente de verdad única: Inicio también lee de aquí (window.AULA_PHOTOS), así la
// misma categoría usa siempre la misma imagen. `null` → portada con el nombre de
// la categoría (sin foto). Reorganización del inventario: cada foto en un solo
// sitio. Las marcadas con TODO son provisionales, a sustituir por foto propia.
const AULA_PHOTOS = {
  nutricion:    'https://images.unsplash.com/photo-1547592180-85f173990554?w=900&q=80&fit=crop',     // #9
  recuperacion: 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=900&q=80&fit=crop',  // #7
  sueno:        'https://images.unsplash.com/photo-1484480974693-6ca0a78fb36b?w=900&q=80&fit=crop',  // #8
  cognitivo:    'https://images.unsplash.com/photo-1542596081-6a9405c1827d?w=900&q=80&fit=crop',     // #13
  hipertrofia:  'https://images.unsplash.com/photo-1589880768855-b106592ac541?w=900&q=80&fit=crop',  // Monis Yousafzai
  fuerza:       'https://images.unsplash.com/photo-1782849206337-f826f32f91ce?w=900&q=80&fit=crop',  // Anastase Maragos
  'biomecánica':'https://images.unsplash.com/photo-1624716472114-a7c5f18ac832?w=900&q=80&fit=crop',  // CHUTTERSNAP
  suplementos:  'https://images.unsplash.com/photo-1693996046865-19217d179161?w=900&q=80&fit=crop',  // Alex Saks
};
if (typeof window !== 'undefined') window.AULA_PHOTOS = AULA_PHOTOS;

// ── Static content ─────────────────────────────────────────────────────────────
const MICRO_TIPS = [
  { type:'fact',  label:'¿Sabías que?',     icon:'🔬', text:'Entrenar 2× por músculo/semana produce hasta un 22% más de hipertrofia que 1× con el mismo volumen total.' },
  { type:'myth',  label:'Mito derribado',   icon:'✗',  text:'El cardio no quema músculo si mantienes 1.6 g/kg de proteína. El problema es el déficit calórico excesivo.' },
  { type:'tip',   label:'Tip rápido',       icon:'⚡', text:'RPE 8 = puedes hacer 2 reps más. Autorregula la carga diaria sin necesitar tests de 1RM.' },
  { type:'error', label:'Error común',      icon:'⚠',  text:'Más series ≠ más músculo. Pasadas las 20–22 series/sesión, la calidad del estímulo cae y la fatiga sube.' },
  { type:'fact',  label:'Ciencia aplicada', icon:'📊', text:'La ventana anabólica post-entreno es real pero cómoda: tienes 2–4 horas para comer proteína, no 30 minutos.' },
  { type:'tip',   label:'Tip rápido',       icon:'💤', text:'La GH se libera principalmente en sueño N3. Menos de 6h de sueño puede reducir la síntesis proteica nocturna en un 30%.' },
];

const SCIENCE_NEWS = [
  { date:'May 2026', finding:'Entrenar de noche no perjudica el sueño en personas sanas y activas', application:'El horario importa menos que la consistencia. Entrena cuando puedas cumplir con más frecuencia.', source:'Sports Medicine (2026)' },
  { date:'Abr 2026', finding:'La creatina mejora la recuperación muscular incluso en atletas con dieta alta en proteína', application:'3–5 g/día es la dosis efectiva sin carga. El efecto es acumulativo a las 2–4 semanas.', source:'JISSN (2026)' },
  { date:'Mar 2026', finding:'El entrenamiento excéntrico lento produce hasta un 15% más de hipertrofia que el concéntrico puro', application:'Baja el peso en 3–4 segundos. La fase excéntrica es tan importante como la concéntrica.', source:'Eur J Applied Physiol (2026)' },
  { date:'Feb 2026', finding:'Deloads de solo 5–7 días son suficientes para restaurar la capacidad de entrenamiento en atletas intermedios', application:'Una semana ligera cada 4–6 semanas es suficiente. No necesitas semanas enteras de descanso total.', source:'J Strength Cond Res (2026)' },
];

const EXPLORE_TOPICS = [
  { id:'hipertrofia',  emoji:'↑',  count:4 },
  { id:'fuerza',       emoji:'⊕',  count:3 },
  { id:'nutricion',    emoji:'◉',  count:2 },
  { id:'recuperacion', emoji:'○',  count:3 },
  { id:'cognitivo',    emoji:'◎',  count:1 },
  { id:'sueno',        emoji:'⌁',  count:2 },
];

// ── Cover visual ──────────────────────────────────────────────────────────────
function AlCover({ category, cover, height, radius, style, imgIdx }) {
  const [imgOk, setImgOk] = React.useState(true);
  const m = ALcat[category] || ALcat.fuerza;
  const h = height || 180;
  // Portada propia del artículo (cover) si existe; si no, la portada de la
  // categoría; si tampoco, portada con el nombre de la categoría.
  const photo    = cover || AULA_PHOTOS[category] || null;
  const showPhoto = !!photo && imgOk;

  return (
    <div style={{
      width: '100%', height: h,
      background: `linear-gradient(${m.grad})`,
      borderRadius: radius !== undefined ? radius : 0,
      position: 'relative', overflow: 'hidden',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexShrink: 0,
      ...style,
    }}>
      {/* Foto a color — sin filtro de escala de grises */}
      {showPhoto && (
        <img
          src={photo}
          alt=""
          loading="lazy"
          onError={() => setImgOk(false)}
          style={{
            position: 'absolute', inset: 0,
            width: '100%', height: '100%',
            objectFit: 'cover', objectPosition: 'center 30%',
          }}
        />
      )}

      {/* Contraste SOLO en la franja superior (zona de las etiquetas), sin
          oscurecer toda la foto. Garantiza texto blanco legible (WCAG AA)
          sobre cualquier imagen. */}
      {showPhoto && (
        <div style={{ position:'absolute', top:0, left:0, right:0, height:'46%',
          background:'linear-gradient(to bottom, rgba(0,0,0,0.52) 0%, rgba(0,0,0,0.12) 45%, transparent 100%)',
          pointerEvents:'none' }} />
      )}

      {/* Portada sin foto: textura + nombre de la categoría */}
      {!showPhoto && (
        <div style={{
          position: 'absolute', inset: 0,
          background: 'linear-gradient(rgba(255,255,255,0.028) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.028) 1px,transparent 1px)',
          backgroundSize: '30px 30px',
        }} />
      )}
      {!showPhoto && (
        <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to top, rgba(0,0,0,0.32) 0%, transparent 55%)' }} />
      )}

      {/* Gradient fallback decorations */}
      {!showPhoto && (
        <>
          <div style={{ position:'absolute', width:220, height:220, borderRadius:'50%', border:'1px solid rgba(255,255,255,0.05)', right:-70, bottom:-70 }} />
          <div style={{ position:'absolute', width:100, height:100, borderRadius:'50%', border:'1px solid rgba(255,255,255,0.04)', left:-20, top:-20 }} />
          <span style={{
            fontFamily:'"Space Grotesk",system-ui', fontStyle:'normal', fontWeight:700,
            fontSize: Math.min(h * 0.45, 64), color:'rgba(255,255,255,0.08)',
            letterSpacing:-2, userSelect:'none', position:'relative', zIndex:1,
          }}>
            {m.label}
          </span>
        </>
      )}
    </div>
  );
}

// ── Evidence badge ────────────────────────────────────────────────────────────
function AlEvBadge({ level }) {
  if (!level || !EVIDENCE_LABEL[level]) return null;
  return (
    <span style={{
      padding:'2px 8px', borderRadius:5,
      background:'rgba(15,26,46,0.05)', color:AL.muted,
      fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, fontWeight:700, letterSpacing:0.3,
    }}>
      {EVIDENCE_LABEL[level]}
    </span>
  );
}

// ── "Nuevo" badge — shown for an article's first 2 weeks ──────────────────────
function AlNewBadge({ article }) {
  const isNew = (typeof ArticlesService !== 'undefined' && ArticlesService.isNew)
    ? ArticlesService.isNew(article) : false;
  if (!isNew) return null;
  return (
    <span style={{
      display:'inline-flex', alignItems:'center', gap:4,
      padding:'2px 9px', borderRadius:5,
      background:'rgba(5,150,105,0.10)', color:'#059669',
      fontFamily:'"Inter",system-ui', fontSize:10, fontWeight:800, letterSpacing:0.4, textTransform:'uppercase',
    }}>
      <span style={{ width:5, height:5, borderRadius:'50%', background:'#059669', display:'block' }} />
      Nuevo
    </span>
  );
}

// Format an ISO publish date as e.g. "10 ene 2026"
function alFmtPubDate(iso) {
  if (!iso) return '';
  const t = Date.parse(iso);
  if (isNaN(t)) return '';
  try { return new Date(t).toLocaleDateString('es-ES', { day:'numeric', month:'short', year:'numeric' }); }
  catch { return ''; }
}

// ── Category pill ─────────────────────────────────────────────────────────────
function AlCatPill({ category }) {
  const m = ALcat[category] || ALcat.fuerza;
  return (
    <span style={{
      padding:'2px 9px', borderRadius:5,
      background:m.bg, color:m.c,
      fontFamily:'"Inter",system-ui', fontSize:11, fontWeight:700,
    }}>
      {m.label}
    </span>
  );
}

// ── Hero ──────────────────────────────────────────────────────────────────────
function AulaHero({ total, completed, query, onQuery }) {
  const isMobile = useIsMobile();
  return (
    <div style={{ paddingBottom: isMobile ? 24 : 40, marginBottom: isMobile ? 24 : 40, borderBottom: `1px solid ${AL.border}` }}>
      <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:16 }}>
        <div style={{ width:7, height:7, borderRadius:'50%', background:'#10B981' }} />
        <span style={{ fontFamily:'"Inter",system-ui', fontSize:11, fontWeight:700, letterSpacing:2, textTransform:'uppercase', color:AL.muted }}>Aula Atlas</span>
      </div>

      <div style={{ display:'flex', alignItems: isMobile ? 'stretch' : 'flex-end', flexDirection: isMobile ? 'column' : 'row', justifyContent:'space-between', gap: isMobile ? 16 : 24, flexWrap:'wrap' }}>
        <div style={{ maxWidth:600 }}>
          <h1 style={{
            fontFamily:'"Space Grotesk",system-ui', fontWeight:700,
            fontSize:'clamp(30px, 8vw, 54px)', color:AL.navy, letterSpacing: isMobile ? -1.2 : -2.5,
            lineHeight:1.0, margin:'0 0 14px',
          }}>
            Entrena con evidencia.
          </h1>
          <p style={{ fontFamily:'"Inter",system-ui', fontSize:16, color:AL.sub, lineHeight:1.55, margin:0, letterSpacing:-0.2, maxWidth:520 }}>
            Ciencia aplicada al entrenamiento. Sin dogma, sin ruido.
          </p>
        </div>

        <div style={{ display:'flex', flexDirection:'column', gap:6, alignItems: isMobile ? 'stretch' : 'flex-end' }}>
          {/* Search */}
          <div style={{ position:'relative' }}>
            <span style={{ position:'absolute', left:12, top:'50%', transform:'translateY(-50%)', color:AL.muted, fontSize:14, pointerEvents:'none' }}>⌕</span>
            <input
              value={query} onChange={e => onQuery(e.target.value)}
              aria-label="Buscar artículos" placeholder="Buscar artículos…"
              style={{
                padding: isMobile ? '13px 14px 13px 34px' : '10px 14px 10px 34px', borderRadius:12,
                border:`1px solid ${AL.bord2}`, background:'#FFFFFF',
                color:AL.navy, fontFamily:'"Inter",system-ui', fontSize: isMobile ? 16 : 13,
                outline:'none', width: isMobile ? '100%' : 220, boxSizing:'border-box',
              }}
            />
          </div>
          {/* Stats */}
          {completed > 0 && (
            <span style={{
              padding:'5px 12px', borderRadius:999,
              background:'rgba(5,150,105,0.07)', border:'1px solid rgba(5,150,105,0.14)',
              fontFamily:'"Inter",system-ui', fontSize:11, color:'#059669', fontWeight:700,
            }}>
              ✓ {completed} leído{completed !== 1 ? 's' : ''} · +{completed * 15} 💎
            </span>
          )}
        </div>
      </div>
    </div>
  );
}

// ── Category filter strip ─────────────────────────────────────────────────────
// Ordered category keys — single source of truth for the strip and the swipe nav.
const AULA_CATS = ['all','hipertrofia','fuerza','nutricion','recuperacion','cognitivo','sueno'];

function AlCatStrip({ active, onChange }) {
  const cats = [['all','Todos'], ...Object.entries(ALcat).filter(([k]) => AULA_CATS.includes(k)).map(([k,v]) => [k,v.label])];
  return (
    <div role="tablist" aria-label="Categorías del Aula" style={{ display:'flex', gap:6, marginBottom:40, flexWrap:'wrap' }}>
      {cats.map(([key, label]) => {
        const m = ALcat[key];
        const act = active === key;
        return (
          <button key={key} role="tab" aria-selected={act} onClick={() => onChange(key)} style={{
            padding:'7px 15px', borderRadius:999, border:'none', cursor:'pointer',
            background: act ? AL.navy : 'rgba(15,26,46,0.05)',
            color: act ? '#FAFAF7' : AL.sub,
            fontFamily:'"Inter",system-ui', fontSize:13, fontWeight: act ? 700 : 500,
            transition:'all .15s',
          }}>
            {m && !act && (
              <span style={{ display:'inline-block', width:6, height:6, borderRadius:'50%', background:m.dot, marginRight:6, verticalAlign:'middle', marginTop:-1 }} />
            )}
            {label}
          </button>
        );
      })}
    </div>
  );
}

// ── Featured article (horizontal hero card) ───────────────────────────────────
function AulaFeatured({ article, isRead, onOpen }) {
  const [hov, setHov] = React.useState(false);
  const isMobile = useIsMobile();
  const m = ALcat[article.category] || ALcat.fuerza;

  return (
    <div
      onClick={() => onOpen(article.id)}
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      style={{
        display:'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
        borderRadius:24, overflow:'hidden',
        background:AL.card, border:`1px solid ${AL.border}`,
        cursor:'pointer',
        boxShadow: hov ? '0 24px 64px rgba(15,26,46,0.15)' : '0 4px 20px rgba(15,26,46,0.07)',
        transform: hov ? 'translateY(-4px)' : 'none',
        transition:'all .3s cubic-bezier(.2,.8,.2,1)',
        marginBottom: isMobile ? 28 : 56,
      }}
    >
      {/* Left: cover */}
      <div style={{ position:'relative', overflow:'hidden' }}>
        <AlCover category={article.category} cover={article.coverImage} height={isMobile ? 200 : 340} />
        <div style={{
          position:'absolute', top:16, left:16,
          padding:'4px 10px', borderRadius:6,
          background:'rgba(0,0,0,0.40)', backdropFilter:'blur(10px)',
          fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700,
          color:'rgba(255,255,255,0.92)', letterSpacing:1.2,
        }}>DESTACADO DEL DÍA</div>
        {isRead && (
          <div style={{
            position:'absolute', top:16, right:16,
            padding:'4px 10px', borderRadius:6,
            background:'rgba(5,150,105,0.75)', backdropFilter:'blur(8px)',
            fontFamily:'"Inter",system-ui', fontSize:10, fontWeight:700, color:'#fff',
          }}>✓ Leído</div>
        )}
      </div>

      {/* Right: content */}
      <div style={{ padding:'36px 40px', display:'flex', flexDirection:'column', justifyContent:'center' }}>
        <div style={{ display:'flex', gap:8, marginBottom:20, flexWrap:'wrap', alignItems:'center' }}>
          <AlNewBadge article={article} />
          <AlCatPill category={article.category} />
          <AlEvBadge level={article.evidenceLevel} />
          {alFmtPubDate(article.publishedAt) && (
            <span style={{ fontFamily:'"Inter",system-ui', fontSize:11, color:AL.muted, fontWeight:600 }}>{alFmtPubDate(article.publishedAt)}</span>
          )}
        </div>

        <h2 style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:30, fontWeight:700, color:AL.navy, letterSpacing:-1.2, lineHeight:1.08, margin:'0 0 12px' }}>
          {article.title}
        </h2>
        <p style={{ fontFamily:'"Inter",system-ui', fontSize:14, color:AL.sub, margin:'0 0 16px', lineHeight:1.45, fontWeight:500, letterSpacing:0.1 }}>
          {article.subtitle}
        </p>
        <p style={{
          fontFamily:'"Inter",system-ui', fontSize:14, color:AL.sub, lineHeight:1.65,
          margin:'0 0 28px',
          display:'-webkit-box', WebkitLineClamp:3, WebkitBoxOrient:'vertical', overflow:'hidden',
        }}>
          {article.summary}
        </p>

        <div style={{ display:'flex', alignItems:'center', gap:14 }}>
          <span style={{
            padding:'11px 22px', borderRadius:12,
            background:AL.navy, color:'#FAFAF7',
            fontFamily:'"Inter",system-ui', fontSize:13, fontWeight:700,
            transition:'opacity .15s',
          }}>
            Ver análisis →
          </span>
          <div style={{ display:'flex', alignItems:'center', gap:10 }}>
            <span style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:AL.muted, fontWeight:500 }}>{article.readTime} min</span>
            {!isRead && <span style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:'#059669', fontWeight:700 }}>+{article.gems} 💎</span>}
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Feed card ─────────────────────────────────────────────────────────────────
function AulaCard({ article, isRead, onOpen, imgIdx }) {
  const [hov, setHov] = React.useState(false);
  return (
    <div
      onClick={() => onOpen(article.id)}
      onMouseEnter={() => setHov(true)}
      onMouseLeave={() => setHov(false)}
      style={{
        borderRadius:20, overflow:'hidden',
        background:AL.card, border:`1px solid ${AL.border}`,
        cursor:'pointer',
        boxShadow: hov ? '0 16px 48px rgba(15,26,46,0.12)' : '0 2px 8px rgba(15,26,46,0.05)',
        transform: hov ? 'translateY(-5px)' : 'none',
        transition:'all .25s cubic-bezier(.2,.8,.2,1)',
      }}
    >
      <div style={{ position:'relative', overflow:'hidden' }}>
        <AlCover category={article.category} cover={article.coverImage} height={168} />
        {isRead && (
          <div style={{
            position:'absolute', top:12, right:12,
            width:22, height:22, borderRadius:'50%',
            background:'rgba(5,150,105,0.8)', backdropFilter:'blur(6px)',
            display:'flex', alignItems:'center', justifyContent:'center',
            fontFamily:'"Inter",system-ui', fontSize:11, color:'#fff', fontWeight:700,
          }}>✓</div>
        )}
      </div>

      <div style={{ padding:'18px 22px 22px' }}>
        <div style={{ display:'flex', gap:7, marginBottom:12, alignItems:'center', flexWrap:'wrap' }}>
          <AlNewBadge article={article} />
          <AlCatPill category={article.category} />
          <AlEvBadge level={article.evidenceLevel} />
        </div>
        <h3 style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:17, fontWeight:700, color:AL.navy, letterSpacing:-0.5, lineHeight:1.2, margin:'0 0 6px' }}>
          {article.title}
        </h3>
        <p style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:AL.sub, margin:'0 0 16px', lineHeight:1.5 }}>
          {article.subtitle}
        </p>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span style={{ fontFamily:'"Inter",system-ui', fontSize:11, color:AL.muted, fontWeight:600 }}>
            {alFmtPubDate(article.publishedAt) ? `${alFmtPubDate(article.publishedAt)} · ` : ''}{article.readTime} min
          </span>
          {!isRead
            ? <span style={{ fontFamily:'"Inter",system-ui', fontSize:11, color:'#059669', fontWeight:700 }}>+{article.gems} 💎</span>
            : <span style={{ fontFamily:'"Inter",system-ui', fontSize:11, color:'#059669', fontWeight:600 }}>Completado</span>
          }
        </div>
      </div>
    </div>
  );
}

// ── Section heading ───────────────────────────────────────────────────────────
function AlSectionHead({ label, sublabel }) {
  return (
    <div style={{ display:'flex', alignItems:'baseline', gap:12, marginBottom:24 }}>
      <h2 style={{ fontFamily:'"Inter",system-ui', fontSize:22, fontWeight:800, color:AL.navy, letterSpacing:-0.8, margin:0 }}>{label}</h2>
      {sublabel && <span style={{ fontFamily:'"Inter",system-ui', fontSize:13, color:AL.muted }}>{sublabel}</span>}
    </div>
  );
}

// ── Micro-learning strip ──────────────────────────────────────────────────────
function AulaMicroLearning() {
  const isMobile = useIsMobile();
  const typeColors = {
    fact:  { c:'#1E40AF', bg:'rgba(30,64,175,0.07)'  },
    myth:  { c:'#B45309', bg:'rgba(180,83,9,0.07)'   },
    tip:   { c:'#059669', bg:'rgba(5,150,105,0.07)'  },
    error: { c:'#be123c', bg:'rgba(190,18,60,0.07)'  },
  };

  return (
    <div style={{ marginBottom:64 }}>
      <AlSectionHead label="30 segundos de ciencia" sublabel="Datos rápidos, basados en evidencia" />
      <div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3,1fr)', gap:14 }}>
        {MICRO_TIPS.map((tip, i) => {
          const tc = typeColors[tip.type] || typeColors.fact;
          return (
            <div key={i} style={{
              padding:'20px 22px', borderRadius:18,
              background:AL.card, border:`1px solid ${AL.border}`,
              boxShadow:'0 2px 8px rgba(15,26,46,0.04)',
            }}>
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:12 }}>
                <span style={{
                  padding:'3px 9px', borderRadius:5,
                  background:tc.bg, color:tc.c,
                  fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, letterSpacing:0.6,
                }}>
                  {tip.label.toUpperCase()}
                </span>
              </div>
              <p style={{ fontFamily:'"Inter",system-ui', fontSize:14, color:AL.navy, lineHeight:1.6, margin:0, fontWeight:500 }}>
                {tip.text}
              </p>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Science feed ──────────────────────────────────────────────────────────────
function AulaScienceFeed() {
  const isMobile = useIsMobile();
  return (
    <div style={{ marginBottom:64 }}>
      <AlSectionHead label="Avances recientes" sublabel="Últimos hallazgos con aplicación práctica" />
      <div style={{ display:'flex', flexDirection:'column', gap:2 }}>
        {SCIENCE_NEWS.map((item, i) => (
          <div key={i} style={{
            display:'grid', gridTemplateColumns: isMobile ? '1fr' : '80px 1fr 1fr',
            gap: isMobile ? 12 : 24, padding:'22px 24px', borderRadius:16,
            background:AL.card, border:`1px solid ${AL.border}`,
            marginBottom:2,
            alignItems:'center',
          }}>
            <div>
              <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, color:AL.muted, fontWeight:700, letterSpacing:0.5 }}>{item.date}</div>
            </div>
            <div>
              <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8, fontWeight:700, color:AL.muted, letterSpacing:1, marginBottom:6 }}>HALLAZGO</div>
              <p style={{ fontFamily:'"Inter",system-ui', fontSize:14, fontWeight:600, color:AL.navy, lineHeight:1.45, margin:0 }}>{item.finding}</p>
              <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, color:AL.muted, marginTop:6 }}>{item.source}</div>
            </div>
            <div style={{ padding:'14px 18px', borderRadius:12, background:'rgba(5,150,105,0.05)', border:'1px solid rgba(5,150,105,0.12)' }}>
              <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8, fontWeight:700, color:'#059669', letterSpacing:1, marginBottom:6 }}>APLICACIÓN PRÁCTICA</div>
              <p style={{ fontFamily:'"Inter",system-ui', fontSize:13, color:'#047857', lineHeight:1.5, margin:0 }}>{item.application}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── Glossary term card ─────────────────────────────────────────────────────────
// A readable definition card. `onPickTerm` lets a related-term chip jump to that
// entry (also the hook a future article→glossary deep-link would use).
function AulaGlossaryCard({ term, onOpenArticle, flash, publishedIds }) {
  const cat = (typeof GlossaryService !== 'undefined') ? GlossaryService.catFor(term.category) : { label: term.category, c: AL.sub, bg: 'rgba(15,26,46,0.06)' };
  const related = (term.related || [])
    .map(id => (typeof GlossaryService !== 'undefined' ? GlossaryService.getById(id) : null))
    .filter(Boolean);
  // "Leer más en el Aula" solo si el artículo existe y está PUBLICADO.
  const hasArticle = !!term.articleId && (!publishedIds || publishedIds.has(term.articleId));
  return (
    <div id={`glosario-${term.id}`} style={{
      borderRadius:16, background: flash ? 'rgba(42,111,219,0.07)' : AL.card,
      border:`1px solid ${flash ? 'rgba(42,111,219,0.55)' : AL.border}`,
      boxShadow: flash ? '0 0 0 3px rgba(42,111,219,0.14)' : '0 2px 8px rgba(15,26,46,0.04)',
      transition:'background .3s ease, border-color .3s ease, box-shadow .3s ease',
      padding:'18px 20px', display:'flex', flexDirection:'column', gap:10, scrollMarginTop:80,
    }}>
      <div style={{ display:'flex', alignItems:'center', gap:9, flexWrap:'wrap' }}>
        <h3 style={{ fontFamily:'"Inter",system-ui', fontSize:16, fontWeight:800, color:AL.navy, letterSpacing:-0.3, margin:0 }}>{term.term}</h3>
        <span style={{ padding:'2px 9px', borderRadius:999, background:cat.bg, color:cat.c, fontFamily:'"Inter",system-ui', fontSize:10, fontWeight:700 }}>{cat.label}</span>
        {/* Shareable per-term anchor (/aula/glosario#<id>) — copiable y enlazable. */}
        <a href={`#/aula/glosario#${term.id}`} aria-label={`Enlace a «${term.term}»`} title="Copiar enlace a este término"
          style={{ marginLeft:'auto', textDecoration:'none', color:AL.muted, fontFamily:'ui-monospace,Menlo,monospace', fontSize:14, fontWeight:700, lineHeight:1, padding:'2px 4px', borderRadius:6 }}>
          #
        </a>
      </div>
      <p style={{ fontFamily:'"Inter",system-ui', fontSize:14, color:AL.navy, lineHeight:1.6, margin:0, fontWeight:450 }}>
        {term.definition}
      </p>
      {(related.length > 0 || hasArticle) && (
        <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap', marginTop:2 }}>
          {related.length > 0 && (
            <>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, color:AL.muted, letterSpacing:0.6 }}>RELACIONADO</span>
              {related.map(r => (
                <a key={r.id} href={`#/aula/glosario#${r.id}`}
                  style={{ padding:'3px 10px', borderRadius:999, border:`1px solid ${AL.bord2}`, background:'transparent', color:AL.sub, fontFamily:'"Inter",system-ui', fontSize:11, fontWeight:600, cursor:'pointer', textDecoration:'none' }}>
                  {r.term}
                </a>
              ))}
            </>
          )}
          {hasArticle && (
            <button onClick={() => onOpenArticle && onOpenArticle(term.articleId)}
              style={{ marginLeft:'auto', padding:'3px 10px', borderRadius:999, border:`1px solid rgba(30,64,175,0.25)`, background:'rgba(30,64,175,0.06)', color:'#1E40AF', fontFamily:'"Inter",system-ui', fontSize:11, fontWeight:700, cursor:'pointer' }}>
              Leer más en el Aula →
            </button>
          )}
        </div>
      )}
    </div>
  );
}

// ── Glossary section ───────────────────────────────────────────────────────────
// Searchable (accent/typo-tolerant) + category-filterable list of training terms.
// Content is placeholder; the trainer supplies the real terms/definitions.
function AulaGlossary({ onOpenArticle, jumpTo, publishedIds }) {
  const isMobile = useIsMobile();
  const [q, setQ]     = React.useState('');
  const [cat, setCat] = React.useState('all');
  const [flashId, setFlashId] = React.useState(null);
  const svc = (typeof GlossaryService !== 'undefined') ? GlossaryService : null;
  const cats = svc ? svc.categories : [];
  const results = React.useMemo(
    () => (svc ? svc.search(q, cat) : []),
    [q, cat, svc]
  );
  const total = svc ? svc.getAll().length : 0;

  function scrollToTerm(id, flash) {
    setTimeout(() => {
      const el = document.getElementById(`glosario-${id}`);
      if (el) el.scrollIntoView({ behavior:'smooth', block:'center' });
      if (flash) { setFlashId(id); setTimeout(() => setFlashId(f => f === id ? null : f), 1600); }
    }, 80);
  }
  function pickTerm(t) { setQ(''); setCat('all'); scrollToTerm(t.id, false); }

  // Deep-link from an article term ("Ver en el glosario"): clear filters, scroll
  // to the entry and flash it so it's easy to spot.
  React.useEffect(() => {
    if (!jumpTo || !jumpTo.id) return;
    setQ(''); setCat('all');
    scrollToTerm(jumpTo.id, true);
  }, [jumpTo]); // eslint-disable-line react-hooks/exhaustive-deps

  const chip = (active, label, onClick, c) => (
    <button onClick={onClick} style={{
      padding:'6px 13px', borderRadius:999, cursor:'pointer',
      border:`1px solid ${active ? 'transparent' : AL.bord2}`,
      background: active ? (c || AL.navy) : 'transparent',
      color: active ? '#fff' : AL.sub,
      fontFamily:'"Inter",system-ui', fontSize:12, fontWeight:700, whiteSpace:'nowrap',
    }}>{label}</button>
  );

  return (
    <div id="glosario" style={{ marginBottom:64, scrollMarginTop:80 }}>
      <AlSectionHead label="Glosario" sublabel="Términos de entrenamiento, en lenguaje claro" />

      {/* Search */}
      <div style={{ position:'relative', marginBottom:14 }}>
        <span style={{ position:'absolute', left:16, top:'50%', transform:'translateY(-50%)', color:AL.muted, fontSize:15 }}>⌕</span>
        <input
          value={q} onChange={e => setQ(e.target.value)}
          aria-label="Buscar un término del glosario" placeholder="Buscar un término…"
          style={{ width:'100%', boxSizing:'border-box', padding: isMobile ? '13px 16px 13px 40px' : '12px 16px 12px 40px',
            borderRadius:12, border:`1px solid ${AL.bord2}`, background:AL.card, color:AL.navy,
            fontFamily:'"Inter",system-ui', fontSize: isMobile ? 16 : 14, outline:'none' }}
        />
      </div>

      {/* Category filter */}
      <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginBottom:20 }}>
        {chip(cat === 'all', 'Todas', () => setCat('all'))}
        {cats.map(c => chip(cat === c.id, c.label, () => setCat(c.id), c.c))}
      </div>

      {/* Results */}
      {results.length > 0 ? (
        <div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(2,1fr)', gap:16 }}>
          {results.map(t => (
            <AulaGlossaryCard key={t.id} term={t} onOpenArticle={onOpenArticle} flash={flashId === t.id} publishedIds={publishedIds} />
          ))}
        </div>
      ) : (
        <div style={{ textAlign:'center', padding:'40px 20px', color:AL.muted, fontFamily:'"Inter",system-ui', fontSize:14 }}>
          {total === 0
            ? 'El glosario aún no tiene términos.'
            : <>Sin resultados para “{q}”. <button onClick={() => { setQ(''); setCat('all'); }} style={{ background:'none', border:'none', color:'#1E40AF', fontWeight:700, cursor:'pointer', fontSize:14, fontFamily:'"Inter",system-ui' }}>Ver todos</button></>}
        </div>
      )}
    </div>
  );
}

// ── Explore by topic ──────────────────────────────────────────────────────────
function AulaExplore({ onCategory }) {
  const isMobile = useIsMobile();
  return (
    <div style={{ marginBottom:64 }}>
      <AlSectionHead label="Explorar por tema" sublabel="Elige lo que quieres aprender hoy" />
      <div style={{ display:'grid', gridTemplateColumns: isMobile ? 'repeat(2,1fr)' : 'repeat(3,1fr)', gap:10 }}>
        {EXPLORE_TOPICS.map(topic => {
          const m = ALcat[topic.id] || ALcat.fuerza;
          return (
            <button
              key={topic.id}
              onClick={() => onCategory(topic.id)}
              style={{
                padding:'20px 22px', borderRadius:16, border:`1px solid ${AL.border}`,
                background:AL.card, cursor:'pointer', textAlign:'left',
                transition:'all .2s',
              }}
              onMouseEnter={e => { e.currentTarget.style.background = m.bg; e.currentTarget.style.borderColor = m.c.replace(')',',0.25)').replace('rgb','rgba'); }}
              onMouseLeave={e => { e.currentTarget.style.background = AL.card; e.currentTarget.style.borderColor = AL.border; }}
            >
              <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:6 }}>
                <div style={{ width:8, height:8, borderRadius:'50%', background:m.dot }} />
                <span style={{ fontFamily:'"Inter",system-ui', fontSize:15, fontWeight:700, color:AL.navy }}>{m.label}</span>
              </div>
              <div style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:AL.muted }}>{topic.count} artículo{topic.count !== 1 ? 's' : ''}</div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ── Article detail (kept from v1, style polish) ───────────────────────────────
// ── Original SVG diagrams (license-free, drawn here) ──────────────────────────
// Referenced by an image block via { type:'image', diagram:'<id>' }. Add more by
// dropping another entry here — no reader changes needed.
const ARTICLE_DIAGRAMS = {
  // Same weekly volume (12 sets) split across 1 session vs 3 sessions.
  'volumen-1-vs-3': () => (
    <svg viewBox="0 0 320 180" width="100%" role="img" aria-label="Mismo volumen semanal repartido en 1 sesión o en 3 sesiones" style={{ display:'block' }}>
      <line x1="20" y1="150" x2="300" y2="150" stroke="rgba(15,26,46,0.25)" strokeWidth="1.2" />
      {/* 1 session: one tall bar = 12 sets */}
      <rect x="46" y="34" width="52" height="116" rx="4" fill="#059669" opacity="0.85" />
      <text x="72" y="28" textAnchor="middle" fill="#0F1A2E" fontSize="11" fontWeight="700" fontFamily="ui-monospace,Menlo,monospace">12</text>
      <text x="72" y="167" textAnchor="middle" fill="rgba(15,26,46,0.6)" fontSize="11" fontFamily="Inter,system-ui">1 sesión</text>
      {/* 3 sessions: three bars of 4 sets each */}
      {[0,1,2].map(i=>(
        <g key={i}>
          <rect x={196+i*34} y="111" width="24" height="39" rx="3" fill="#1E40AF" opacity="0.85" />
          <text x={196+i*34+12} y="105" textAnchor="middle" fill="#0F1A2E" fontSize="10" fontWeight="700" fontFamily="ui-monospace,Menlo,monospace">4</text>
        </g>
      ))}
      <text x="234" y="167" textAnchor="middle" fill="rgba(15,26,46,0.6)" fontSize="11" fontFamily="Inter,system-ui">3 sesiones</text>
      <text x="160" y="14" textAnchor="middle" fill="rgba(15,26,46,0.55)" fontSize="10.5" fontStyle="italic" fontFamily="Inter,system-ui">mismo volumen semanal</text>
    </svg>
  ),
  'progresion-lineal': () => (
    <svg viewBox="0 0 320 180" width="100%" role="img" aria-label="Progresión lineal de la carga" style={{ display:'block' }}>
      <line x1="34" y1="150" x2="306" y2="150" stroke="rgba(15,26,46,0.25)" strokeWidth="1.2" />
      <line x1="34" y1="150" x2="34" y2="20" stroke="rgba(15,26,46,0.25)" strokeWidth="1.2" />
      <line x1="34" y1="132" x2="300" y2="60" stroke="rgba(15,26,46,0.20)" strokeWidth="1" strokeDasharray="3 4" />
      <polyline points="40,138 82,138 82,120 124,120 124,102 166,102 166,84 208,84 208,66 250,66 250,48 292,48"
        fill="none" stroke="#1E40AF" strokeWidth="2.6" strokeLinejoin="round" strokeLinecap="round" />
      {[138,120,102,84,66,48].map((y,i)=>(<circle key={i} cx={82+i*42} cy={y} r="3.4" fill="#1E40AF" />))}
      <text x="34" y="168" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui">sesiones →</text>
      <text x="14" y="30" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui" transform="rotate(-90 14 30)">carga →</text>
    </svg>
  ),
  'mev-mav-mrv': () => (
    <svg viewBox="0 0 320 190" width="100%" role="img" aria-label="Umbrales de volumen MEV, MAV y MRV" style={{ display:'block' }}>
      <rect x="34" y="52" width="230" height="76" fill="rgba(5,150,105,0.08)" />
      <line x1="34" y1="160" x2="264" y2="160" stroke="rgba(15,26,46,0.25)" strokeWidth="1.2" />
      <line x1="34" y1="160" x2="34" y2="20" stroke="rgba(15,26,46,0.25)" strokeWidth="1.2" />
      {[["MRV",52,"#be123c"],["MAV",90,"#059669"],["MEV",128,"#B45309"]].map(([lab,y,c],i)=>(
        <g key={i}>
          <line x1="34" y1={y} x2="264" y2={y} stroke={c} strokeWidth="1.3" strokeDasharray="4 3" />
          <text x="270" y={y+3.5} fill={c} fontSize="11" fontWeight="700" fontFamily="ui-monospace,Menlo,monospace">{lab}</text>
        </g>
      ))}
      <polyline points="40,140 74,128 108,112 142,96 176,76 210,58 236,52 240,120 262,108"
        fill="none" stroke="#0F1A2E" strokeWidth="2.6" strokeLinejoin="round" strokeLinecap="round" />
      <text x="228" y="176" fill="rgba(15,26,46,0.5)" fontSize="9.5" fontFamily="Inter,system-ui">deload</text>
      <text x="34" y="184" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui">semanas →</text>
      <text x="14" y="34" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui" transform="rotate(-90 14 34)">series/sem →</text>
    </svg>
  ),

  // ── Flujo: tensión mecánica → hipertrofia (HTML flex: horizontal en escritorio,
  //    vertical en móvil, siempre legible). ────────────────────────────────────
  'tension-cascada': () => {
    const isMobile = useIsMobile();
    const steps = ['Tensión mecánica', 'La fibra lo detecta', 'Señales (vía mTOR)', 'Más síntesis de proteína, ribosomas y núcleos'];
    const box = (txt, j, result) => (
      <div key={j} style={{ flex: isMobile ? 'none' : '1 1 0', minWidth: 0, textAlign:'center',
        padding:'12px 10px', borderRadius:12,
        background: result ? '#2563EB' : 'rgba(37,99,235,0.07)',
        border: `1px solid ${result ? '#2563EB' : 'rgba(37,99,235,0.22)'}`,
        color: result ? '#fff' : AL.navy, fontFamily:'"Inter",system-ui', fontSize:13, fontWeight:700, lineHeight:1.3 }}>{txt}</div>
    );
    const arrow = (k) => (
      <div key={'a'+k} aria-hidden="true" style={{ flexShrink:0, color:'#3B82F6', fontWeight:800, fontSize:16, display:'flex', alignItems:'center', justifyContent:'center', padding: isMobile ? '2px 0' : '0 2px' }}>{isMobile ? '↓' : '→'}</div>
    );
    const items = [];
    steps.forEach((s, j) => { items.push(box(s, j, false)); items.push(arrow(j)); });
    items.push(box('Hipertrofia', 'r', true));
    return (
      <div role="img" aria-label="La tensión mecánica se detecta en la fibra, activa señales por la vía mTOR y aumenta la síntesis de proteína, lo que produce hipertrofia."
        style={{ display:'flex', flexDirection: isMobile ? 'column' : 'row', alignItems:'stretch', gap: isMobile ? 4 : 6 }}>
        {items}
      </div>
    );
  },

  // ── Ciclo de doble progresión. ────────────────────────────────────────────────
  'doble-progresion': () => {
    const isMobile = useIsMobile();
    const steps = ['3 × 8', '3 × 10', '3 × 12 (tope del rango)', 'Sube el peso lo mínimo'];
    const box = (txt, j, hi) => (
      <div key={j} style={{ flex: isMobile ? 'none' : '1 1 0', minWidth:0, textAlign:'center', padding:'12px 10px', borderRadius:12,
        background: hi ? 'rgba(5,150,105,0.10)' : 'rgba(37,99,235,0.07)',
        border:`1px solid ${hi ? 'rgba(5,150,105,0.30)' : 'rgba(37,99,235,0.22)'}`,
        color:AL.navy, fontFamily:'"Inter",system-ui', fontSize:13, fontWeight:700, lineHeight:1.3 }}>{txt}</div>
    );
    const arrow = (k) => (<div key={'a'+k} aria-hidden="true" style={{ flexShrink:0, color:'#3B82F6', fontWeight:800, fontSize:16, display:'flex', alignItems:'center', justifyContent:'center', padding: isMobile ? '2px 0' : '0 2px' }}>{isMobile ? '↓' : '→'}</div>);
    const items = [];
    steps.forEach((s, j) => { items.push(box(s, j, j === 3)); if (j < steps.length - 1) items.push(arrow(j)); });
    return (
      <div role="img" aria-label="Ciclo de doble progresión: 3×8, 3×10, 3×12 en el tope del rango y, entonces, subir el peso lo mínimo para volver a 3×8.">
        <div style={{ display:'flex', flexDirection: isMobile ? 'column' : 'row', alignItems:'stretch', gap: isMobile ? 4 : 6 }}>{items}</div>
        <div aria-hidden="true" style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:7, marginTop:10, color:'#059669', fontFamily:'"Inter",system-ui', fontSize:12, fontWeight:700 }}>
          <span style={{ fontSize:15 }}>↺</span><span>vuelve a 3 × 8</span>
        </div>
        <div style={{ textAlign:'center', marginTop:8, fontFamily:'"Inter",system-ui', fontSize:12.5, color:AL.sub, lineHeight:1.5 }}>Mismo peso hasta llegar arriba del rango; entonces, más peso.</div>
      </div>
    );
  },

  // ── Escala de proximidad al fallo (RIR 5 → 0). ────────────────────────────────
  'proximidad-fallo': () => (
    <svg viewBox="0 0 320 200" width="100%" role="img" aria-label="Escala de repeticiones en reserva de 5 (lejos del fallo) a 0 (fallo). Dejar entre 1 y 3 repeticiones en reserva es el punto de partida para la mayoría. Para el músculo conviene algo más cuanto más cerca del fallo; para la fuerza máxima pesa más la carga." style={{ display:'block' }}>
      {/* zona sombreada RIR 1–3 */}
      <rect x="170" y="54" width="106" height="34" rx="4" fill="rgba(37,99,235,0.12)" stroke="rgba(37,99,235,0.35)" strokeWidth="1" />
      <text x="223" y="46" textAnchor="middle" fill="#1E40AF" fontSize="10.5" fontWeight="700" fontFamily="Inter,system-ui">Punto de partida</text>
      <text x="223" y="34" textAnchor="middle" fill="#1E40AF" fontSize="10.5" fontWeight="700" fontFamily="Inter,system-ui">para la mayoría</text>
      {/* eje */}
      <line x1="30" y1="71" x2="300" y2="71" stroke="rgba(15,26,46,0.3)" strokeWidth="1.4" />
      {[['5',48],['4',90],['3',132],['2',174],['1',216],['0',258]].map(([lab,x],i)=>(
        <g key={i}>
          <line x1={x} y1="66" x2={x} y2="76" stroke="rgba(15,26,46,0.35)" strokeWidth="1.2" />
          <text x={x} y="90" textAnchor="middle" fill="#0F1A2E" fontSize="12" fontWeight="700" fontFamily="ui-monospace,Menlo,monospace">{lab}</text>
        </g>
      ))}
      <text x="48" y="108" textAnchor="middle" fill="rgba(15,26,46,0.55)" fontSize="9.5" fontFamily="Inter,system-ui">lejos del fallo</text>
      <text x="258" y="108" textAnchor="middle" fill="#be123c" fontSize="9.5" fontFamily="Inter,system-ui">fallo</text>
      <text x="165" y="16" textAnchor="middle" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui">RIR (repeticiones en reserva)</text>
      {/* rótulos */}
      <g transform="translate(0,130)">
        <rect x="30" y="0" width="260" height="26" rx="6" fill="rgba(5,150,105,0.08)" />
        <text x="42" y="17" fill="#047857" fontSize="11.5" fontWeight="600" fontFamily="Inter,system-ui">Músculo: algo más cuanto más cerca</text>
        <rect x="30" y="32" width="260" height="26" rx="6" fill="rgba(30,64,175,0.07)" />
        <text x="42" y="49" fill="#1E40AF" fontSize="11.5" fontWeight="600" fontFamily="Inter,system-ui">Fuerza máxima: depende más de la carga</text>
      </g>
      <text x="290" y="196" textAnchor="end" fill="rgba(15,26,46,0.4)" fontSize="9" fontStyle="italic" fontFamily="Inter,system-ui">Esquema conceptual</text>
    </svg>
  ),

  // ── Descarga entre bloques (timeline de 3 tramos, sin semanas numeradas). ──────
  'descarga-bloques': () => (
    <svg viewBox="0 0 320 180" width="100%" role="img" aria-label="Línea de tiempo: un bloque exigente de trabajo alto, después una descarga con menos series y más repeticiones en reserva (trabajo bajo), y luego el siguiente bloque de trabajo alto." style={{ display:'block' }}>
      <line x1="16" y1="140" x2="304" y2="140" stroke="rgba(15,26,46,0.25)" strokeWidth="1.2" />
      {/* bloque exigente */}
      <rect x="24" y="40" width="80" height="100" rx="4" fill="#1E40AF" opacity="0.85" />
      {/* descarga */}
      <rect x="120" y="100" width="80" height="40" rx="4" fill="#B45309" opacity="0.8" />
      {/* siguiente bloque */}
      <rect x="216" y="40" width="80" height="100" rx="4" fill="#1E40AF" opacity="0.85" />
      <text x="64" y="158" textAnchor="middle" fill="#0F1A2E" fontSize="10.5" fontWeight="700" fontFamily="Inter,system-ui">Bloque exigente</text>
      <text x="160" y="158" textAnchor="middle" fill="#92400E" fontSize="10" fontWeight="700" fontFamily="Inter,system-ui">Descarga</text>
      <text x="160" y="170" textAnchor="middle" fill="rgba(15,26,46,0.55)" fontSize="9" fontFamily="Inter,system-ui">menos series, más RIR</text>
      <text x="256" y="158" textAnchor="middle" fill="#0F1A2E" fontSize="10.5" fontWeight="700" fontFamily="Inter,system-ui">Siguiente bloque</text>
      <text x="160" y="22" textAnchor="middle" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui">carga de entrenamiento →</text>
    </svg>
  ),

  // ── HRV: dato diario irregular + media de 7 días + banda "rango habitual". ─────
  'hrv-tendencia': () => (
    <svg viewBox="0 0 320 190" width="100%" role="img" aria-label="Gráfico conceptual: los puntos diarios de HRV varían mucho de un día a otro; la media de siete días es una línea suave que se mantiene dentro de una banda que representa tu rango habitual y, al final, baja por debajo de la banda durante varios días." style={{ display:'block' }}>
      {/* banda rango habitual */}
      <rect x="20" y="66" width="280" height="44" fill="rgba(37,99,235,0.08)" />
      <line x1="20" y1="66" x2="300" y2="66" stroke="rgba(37,99,235,0.3)" strokeWidth="1" strokeDasharray="4 3" />
      <line x1="20" y1="110" x2="300" y2="110" stroke="rgba(37,99,235,0.3)" strokeWidth="1" strokeDasharray="4 3" />
      <text x="24" y="60" fill="#1E40AF" fontSize="10" fontWeight="700" fontFamily="Inter,system-ui">Tu rango habitual</text>
      {/* puntos diarios irregulares */}
      {[[32,96],[52,74],[72,104],[92,82],[112,100],[132,72],[152,98],[172,84],[192,106],[212,88],[232,120],[252,132],[272,128],[292,140]].map(([x,y],i)=>(
        <circle key={i} cx={x} cy={y} r="2.6" fill="rgba(15,26,46,0.35)" />
      ))}
      {/* media de 7 días (línea suave) */}
      <polyline points="32,92 72,90 112,88 152,88 192,90 232,100 252,118 272,126 292,132"
        fill="none" stroke="#2563EB" strokeWidth="2.8" strokeLinejoin="round" strokeLinecap="round" />
      <text x="20" y="178" fill="rgba(15,26,46,0.5)" fontSize="10" fontFamily="Inter,system-ui">días →</text>
      <text x="300" y="178" textAnchor="end" fill="rgba(15,26,46,0.4)" fontSize="9" fontStyle="italic" fontFamily="Inter,system-ui">Esquema ilustrativo, no son datos reales</text>
      <text x="300" y="20" textAnchor="end" fill="#2563EB" fontSize="9.5" fontWeight="700" fontFamily="Inter,system-ui">— media 7 días</text>
    </svg>
  ),
};

// Inline citation superscript(s) → link to the numbered bibliography.
function AlCite({ cite, refCount }) {
  if (cite == null) return null;
  const nums = (Array.isArray(cite) ? cite : [cite]).filter(n => n >= 1 && n <= refCount);
  if (!nums.length) return null;
  return (
    <sup style={{ fontSize:'0.7em', lineHeight:0, marginLeft:2 }}>
      {nums.map((n, i) => (
        <React.Fragment key={n}>
          {i > 0 && <span style={{ color:AL.muted }}>,</span>}
          <a href={'#ref-' + n}
            onClick={e => { e.preventDefault(); const el = document.getElementById('ref-' + n); if (el) el.scrollIntoView({ behavior:'smooth', block:'center' }); }}
            style={{ color:'#1a4fa0', textDecoration:'none', fontWeight:700 }}>[{n}]</a>
        </React.Fragment>
      ))}
    </sup>
  );
}

// Renders a scientific reference as a bibliography line.
function alFormatRef(r) {
  if (r.raw && !r.title) return r.raw;
  const authors = Array.isArray(r.authors) ? r.authors.join(', ') : (r.authors || '');
  const bits = [];
  if (authors) bits.push(authors + (r.year ? ' (' + r.year + ')' : ''));
  else if (r.year) bits.push('(' + r.year + ')');
  if (r.title) bits.push(r.title);
  if (r.journal) bits.push(r.journal);
  return bits.join('. ') + '.';
}

// ── Article → glossary linking ────────────────────────────────────────────────
// Wires the glossary: the first mention of a known term in the article body
// becomes a tappable link to its definition. Only ever operates on plain-string
// block text (paragraphs/lead), so article formatting is never touched.
let _glossIdx = null;
function alGlossIndex() {
  if (_glossIdx) return _glossIdx;
  if (typeof GlossaryService === 'undefined') return { re: null, map: new Map() };
  const map = new Map(); const surfaces = [];
  GlossaryService.getAll().forEach(t => {
    const base = t.term.replace(/\s*\([^)]*\)/g, '').trim();   // "RIR (…)" → "RIR"
    [base, ...(t.aliases || [])].filter(Boolean).forEach(f => {
      const k = f.toLowerCase();
      if (!map.has(k)) { map.set(k, t.id); surfaces.push(f); }
    });
  });
  if (!surfaces.length) { _glossIdx = { re: null, map }; return _glossIdx; }
  surfaces.sort((a, b) => b.length - a.length);  // longest first (greedy)
  const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  let re;
  try { re = new RegExp('(?<![\\p{L}\\d])(' + surfaces.map(esc).join('|') + ')(?![\\p{L}\\d])', 'giu'); }
  catch { re = new RegExp('\\b(' + surfaces.map(esc).join('|') + ')\\b', 'gi'); }
  _glossIdx = { re, map };
  return _glossIdx;
}
function AlTermLink({ surface, termId, onOpen }) {
  return (
    <span role="button" tabIndex={0}
      onClick={e => { e.stopPropagation(); onOpen(termId); }}
      onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(termId); } }}
      style={{ borderBottom:'1px dotted rgba(30,64,175,0.55)', color:'#1a4fa0', cursor:'pointer', fontWeight:600 }}
      title="Ver en el glosario">{surface}</span>
  );
}
// Returns the text as-is (string) or an array of [text …, <AlTermLink>, …] nodes.
// `seen` dedupes across the whole article so each term links only once.
function alLinkTerms(text, seen, onOpen, keyPrefix) {
  const { re, map } = alGlossIndex();
  if (!re || typeof text !== 'string' || !text) return text;
  re.lastIndex = 0;
  const out = []; let last = 0, m, n = 0;
  while ((m = re.exec(text))) {
    const id = map.get(m[0].toLowerCase());
    if (!id || seen.has(id)) continue;
    seen.add(id);
    if (m.index > last) out.push(text.slice(last, m.index));
    out.push(<AlTermLink key={`${keyPrefix}-${n++}`} surface={m[0]} termId={id} onOpen={onOpen} />);
    last = m.index + m[0].length;
  }
  if (!out.length) return text;
  if (last < text.length) out.push(text.slice(last));
  return out;
}
// Definition popover shown when a linked term is tapped.
function AlTermPopover({ termId, isMobile, onClose, onOpenGlossary }) {
  if (typeof useEscClose === 'function') useEscClose(onClose);
  const t = (typeof GlossaryService !== 'undefined') ? GlossaryService.getById(termId) : null;
  if (!t || typeof ReactDOM === 'undefined') return null;
  const cat = GlossaryService.catFor(t.category);
  return ReactDOM.createPortal(
    <div onClick={onClose} style={{ position:'fixed', inset:0, zIndex:5000, background:'rgba(15,26,46,0.35)', display:'flex', alignItems: isMobile ? 'flex-end' : 'center', justifyContent:'center', animation:'fadeIn .15s ease' }}>
      <div role="dialog" aria-modal="true" aria-label={`Glosario: ${t.term}`} onClick={e => e.stopPropagation()} style={{ width: isMobile ? '100%' : 440, maxWidth:'100%', background:'#fff', borderRadius: isMobile ? '18px 18px 0 0' : 16, border:`1px solid ${AL.border}`, boxShadow:'0 -8px 40px rgba(15,26,46,0.18)', padding:'20px 22px', paddingBottom: isMobile ? 'calc(20px + env(safe-area-inset-bottom))' : 20 }}>
        <div style={{ display:'flex', alignItems:'center', gap:9, marginBottom:10 }}>
          <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, letterSpacing:1, color:AL.muted }}>GLOSARIO</span>
          <span style={{ padding:'2px 9px', borderRadius:999, background:cat.bg, color:cat.c, fontFamily:'"Inter",system-ui', fontSize:10, fontWeight:700 }}>{cat.label}</span>
          <button onClick={onClose} aria-label="Cerrar" style={{ marginLeft:'auto', width:30, height:30, borderRadius:8, border:`1px solid ${AL.border}`, background:'transparent', color:AL.sub, cursor:'pointer', fontSize:15 }}>✕</button>
        </div>
        <div style={{ fontFamily:'"Inter",system-ui', fontSize:18, fontWeight:800, color:AL.navy, letterSpacing:-0.3, marginBottom:8 }}>{t.term}</div>
        <p style={{ fontFamily:'"Inter",system-ui', fontSize:14.5, color:'#2C3542', lineHeight:1.6, margin:'0 0 16px' }}>{t.definition}</p>
        <button onClick={() => onOpenGlossary(t.id)} style={{ width:'100%', padding:'11px', borderRadius:11, border:'none', cursor:'pointer', background:'#2563EB', color:'#fff', fontFamily:'"Inter",system-ui', fontSize:13, fontWeight:700 }}>Ver en el glosario →</button>
      </div>
    </div>,
    document.body
  );
}

// ── Block-based article body ─────────────────────────────────────────────────
function AlArticleBody({ article, isMobile, onOpenGlossary }) {
  const blocks = ArticlesService.getBlocks(article);
  const refs = blocks.filter(b => b.type === 'reference');
  const refCount = refs.length;
  const [popTerm, setPopTerm] = React.useState(null);
  const openTerm = React.useCallback((id) => setPopTerm(id), []);

  const P = { fontFamily:'"Inter",system-ui', fontSize: isMobile ? 17 : 16.5, color:'#2C3542', lineHeight:1.72, margin:'0 0 18px' };
  const nodes = [];
  // Fresh per render so first-occurrence linking is consistent across re-renders.
  const seen = new Set();

  blocks.forEach((b, i) => {
    if (b.type === 'reference') return;
    if (b.type === 'lead') {
      nodes.push(<p key={i} style={{ fontFamily:'"Inter",system-ui', fontSize: isMobile ? 19 : 20, color:AL.sub, fontWeight:500, lineHeight:1.55, letterSpacing:-0.2, margin:'0 0 28px' }}>{alLinkTerms(b.text, seen, openTerm, `l${i}`)}</p>);
    } else if (b.type === 'heading') {
      nodes.push(<h2 key={i} style={{ fontFamily:'"Space Grotesk",system-ui', fontSize: isMobile ? 21 : 24, fontWeight:700, color:AL.navy, letterSpacing:-0.6, lineHeight:1.2, margin:'36px 0 14px' }}>{b.text}</h2>);
    } else if (b.type === 'paragraph') {
      nodes.push(<p key={i} style={P}>{alLinkTerms(b.text, seen, openTerm, `p${i}`)}<AlCite cite={b.cite} refCount={refCount} /></p>);
    } else if (b.type === 'list') {
      const Tag = b.ordered ? 'ol' : 'ul';
      nodes.push(
        <Tag key={i} style={{ margin:'0 0 20px', paddingLeft: b.ordered ? 22 : 0, listStyle: b.ordered ? 'decimal' : 'none', display:'flex', flexDirection:'column', gap:9 }}>
          {(b.items || []).map((it, j) => (
            <li key={j} style={{ fontFamily:'"Inter",system-ui', fontSize: isMobile ? 16.5 : 16, color:'#2C3542', lineHeight:1.6, display:'flex', gap:11, alignItems:'baseline' }}>
              {!b.ordered && <span style={{ flexShrink:0, width:6, height:6, borderRadius:2, background: (ALcat[article.category]||ALcat.fuerza).c, transform:'translateY(-2px)' }} />}
              <span>{it}</span>
            </li>
          ))}
        </Tag>
      );
    } else if (b.type === 'quote') {
      nodes.push(
        <blockquote key={i} style={{ margin:'26px 0', padding:'6px 0 6px 20px', borderLeft:`3px solid ${(ALcat[article.category]||ALcat.fuerza).c}` }}>
          <p style={{ fontFamily:'"Space Grotesk",system-ui', fontSize: isMobile ? 19 : 21, fontWeight:500, color:AL.navy, lineHeight:1.4, letterSpacing:-0.4, margin:0 }}>{b.text}</p>
          {b.attribution && <cite style={{ display:'block', marginTop:8, fontFamily:'"Inter",system-ui', fontStyle:'normal', fontSize:13, color:AL.muted }}>— {b.attribution}</cite>}
        </blockquote>
      );
    } else if (b.type === 'callout') {
      const cc = { key:{ c:'#1E40AF', bg:'rgba(30,64,175,0.06)', bd:'rgba(30,64,175,0.18)', ic:'★' },
                   tip:{ c:'#059669', bg:'rgba(5,150,105,0.06)', bd:'rgba(5,150,105,0.18)', ic:'✓' },
                   warning:{ c:'#B45309', bg:'rgba(180,83,9,0.07)', bd:'rgba(180,83,9,0.2)', ic:'!' } }[b.variant] || null;
      const s = cc || { c:AL.navy, bg:'rgba(15,26,46,0.04)', bd:AL.border, ic:'i' };
      nodes.push(
        <div key={i} style={{ margin:'24px 0', padding:'16px 18px', borderRadius:14, background:s.bg, border:`1px solid ${s.bd}` }}>
          <div style={{ display:'flex', alignItems:'center', gap:9, marginBottom: b.text ? 6 : 0 }}>
            <span style={{ flexShrink:0, width:20, height:20, borderRadius:6, background:s.c, color:'#FAFAF7', display:'inline-flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:800 }}>{s.ic}</span>
            <span style={{ fontFamily:'"Inter",system-ui', fontSize:13.5, fontWeight:800, color:s.c, letterSpacing:0.2 }}>{b.title || 'Nota'}</span>
          </div>
          {b.text && <p style={{ fontFamily:'"Inter",system-ui', fontSize: isMobile ? 16 : 15, color:'#2C3542', lineHeight:1.6, margin:0 }}>{b.text}</p>}
        </div>
      );
    } else if (b.type === 'image') {
      const D = b.diagram ? ARTICLE_DIAGRAMS[b.diagram] : null;
      nodes.push(
        <figure key={i} style={{ margin:'28px 0' }}>
          <div style={{ borderRadius:14, overflow:'hidden', border:`1px solid ${AL.border}`, background:AL.card, padding: D ? (isMobile ? '12px 10px' : '16px 14px') : 0 }}>
            {D ? <D />
              : b.src ? <img src={b.src} alt={b.alt || ''} loading="lazy" style={{ display:'block', width:'100%', height:'auto' }} />
              : <div style={{ height:120, display:'flex', alignItems:'center', justifyContent:'center', color:AL.muted, fontFamily:'ui-monospace,Menlo,monospace', fontSize:11, letterSpacing:1 }}>FIGURA</div>}
          </div>
          {(b.caption || b.credit) && (
            <figcaption style={{ marginTop:9 }}>
              {b.caption && <span style={{ fontFamily:'"Inter",system-ui', fontSize:12.5, color:AL.sub, lineHeight:1.5 }}>{b.caption}</span>}
              {b.credit && <span style={{ display:'block', fontFamily:'"Inter",system-ui', fontSize:11, color:AL.muted, marginTop:2 }}>{b.credit}</span>}
            </figcaption>
          )}
        </figure>
      );
    } else if (b.type === 'keypoints') {
      // Caja "Puntos clave" — fondo azul muy suave, siempre arriba del todo.
      nodes.push(
        <div key={i} style={{ margin:'0 0 30px', padding: isMobile ? '16px 18px' : '18px 22px', borderRadius:16, background:'rgba(37,99,235,0.06)', border:'1px solid rgba(37,99,235,0.20)' }}>
          <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:12 }}>
            <span aria-hidden="true" style={{ flexShrink:0, width:20, height:20, borderRadius:6, background:'#2563EB', color:'#fff', display:'inline-flex', alignItems:'center', justifyContent:'center', fontSize:12, fontWeight:800 }}>⚡</span>
            <span style={{ fontFamily:'"Inter",system-ui', fontSize:13.5, fontWeight:800, color:'#1E40AF', letterSpacing:0.2 }}>En 30 segundos</span>
          </div>
          <ul style={{ margin:0, padding:0, listStyle:'none', display:'flex', flexDirection:'column', gap:10 }}>
            {(b.items || []).map((it, j) => (
              <li key={j} style={{ display:'flex', gap:10, alignItems:'baseline', fontFamily:'"Inter",system-ui', fontSize: isMobile ? 16 : 15.5, color:'#2C3542', lineHeight:1.55 }}>
                <span aria-hidden="true" style={{ flexShrink:0, width:15, height:15, borderRadius:5, background:'#2563EB', color:'#fff', display:'inline-flex', alignItems:'center', justifyContent:'center', fontSize:9, fontWeight:800, transform:'translateY(2px)' }}>✓</span>
                <span>{it}</span>
              </li>
            ))}
          </ul>
        </div>
      );
    } else if (b.type === 'table') {
      // Tabla accesible con scroll horizontal DENTRO de su contenedor (nunca de la
      // página). Cabecera navy + texto crema, filas alternas, caption y note.
      const cols = b.columns || [];
      const minW = cols.length >= 3 ? (isMobile ? 520 : 0) : (isMobile ? 360 : 0);
      nodes.push(
        <figure key={i} style={{ margin:'28px 0' }}>
          {b.caption && (
            <div aria-hidden="true" style={{ fontFamily:'"Inter",system-ui', fontSize:14.5, fontWeight:700, color:AL.navy, lineHeight:1.4, marginBottom:10 }}>{b.caption}</div>
          )}
          <div style={{ overflowX:'auto', WebkitOverflowScrolling:'touch', borderRadius:12, border:`1px solid ${AL.border}` }}>
            <table style={{ borderCollapse:'collapse', width:'100%', minWidth: minW || undefined, fontFamily:'"Inter",system-ui', fontSize: isMobile ? 14 : 14.5 }}>
              {b.caption && <caption style={{ position:'absolute', width:1, height:1, padding:0, margin:-1, overflow:'hidden', clip:'rect(0 0 0 0)', whiteSpace:'nowrap', border:0 }}>{b.caption}</caption>}
              <thead>
                <tr>
                  {cols.map((c, ci) => (
                    <th key={ci} scope="col" style={{ textAlign:'left', background:AL.navy, color:'#F5F0E6', fontWeight:700, padding:'10px 14px', lineHeight:1.35, verticalAlign:'top', borderRight: ci < cols.length - 1 ? '1px solid rgba(245,240,230,0.12)' : 'none' }}>{c}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {(b.rows || []).map((row, ri) => (
                  <tr key={ri} style={{ background: ri % 2 ? 'rgba(15,26,46,0.035)' : '#FFFFFF' }}>
                    {row.map((cell, cj) => (
                      <td key={cj} style={{ padding:'10px 14px', color:'#2C3542', lineHeight:1.45, verticalAlign:'top', borderTop:`1px solid ${AL.border}`, fontWeight: cj === 0 ? 600 : 400 }}>{cell}</td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          {b.note && <figcaption style={{ marginTop:8, fontFamily:'"Inter",system-ui', fontSize:12, color:AL.muted, lineHeight:1.5 }}>{b.note}</figcaption>}
        </figure>
      );
    }
  });

  return (
    <div>
      {nodes}
      {refCount > 0 && (
        <section style={{ marginTop:36, padding: isMobile ? '18px 18px' : '22px 24px', borderRadius:16, background:AL.page, border:`1px solid ${AL.border}` }}>
          <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, color:AL.muted, letterSpacing:1.4, marginBottom:14 }}>REFERENCIAS CIENTÍFICAS</div>
          <ol style={{ margin:0, padding:0, listStyle:'none', display:'flex', flexDirection:'column', gap:12 }}>
            {refs.map((r, i) => (
              <li key={i} id={'ref-' + (i + 1)} style={{ display:'flex', gap:10, scrollMarginTop:80 }}>
                <span style={{ flexShrink:0, fontFamily:'ui-monospace,Menlo,monospace', fontSize:12, fontWeight:700, color:(ALcat[article.category]||ALcat.fuerza).c }}>[{i + 1}]</span>
                <div style={{ fontFamily:'"Inter",system-ui', fontSize:12.5, color:AL.sub, lineHeight:1.55 }}>
                  {alFormatRef(r)}
                  {r.doi && <> <a href={'https://doi.org/' + r.doi} target="_blank" rel="noopener noreferrer" style={{ color:'#1a4fa0', textDecoration:'none', whiteSpace:'nowrap' }}>doi:{r.doi}</a></>}
                  {!r.doi && r.url && <> <a href={r.url} target="_blank" rel="noopener noreferrer" style={{ color:'#1a4fa0', textDecoration:'none' }}>enlace</a></>}
                  <AlRefEvTag label={r.ev} />
                </div>
              </li>
            ))}
          </ol>
        </section>
      )}
      {popTerm && (
        <AlTermPopover
          termId={popTerm}
          isMobile={isMobile}
          onClose={() => setPopTerm(null)}
          onOpenGlossary={(id) => { setPopTerm(null); onOpenGlossary && onOpenGlossary(id); }}
        />
      )}
    </div>
  );
}

function AulaDetail({ article, isRead, onBack, onMarkRead, allArticles, completedIds, onOpen, onAskCoach, onOpenGlossary }) {
  const isMobile = useIsMobile();
  const [showAi, setShowAi]     = React.useState(false);
  const m  = ALcat[article.category] || ALcat.fuerza;
  const ev = EVIDENCE_LABEL;

  return (
    <div style={{ maxWidth:740, margin:'0 auto' }}>
      <button onClick={onBack} style={{ display:'inline-flex', alignItems:'center', gap:8, marginBottom:36, background:'none', border:'none', cursor:'pointer', fontFamily:'"Inter",system-ui', fontSize:14, fontWeight:600, color:AL.muted, padding:0 }}>
        ← Volver al Aula
      </button>

      <div style={{ marginBottom:36, borderRadius:20, overflow:'hidden' }}>
        <AlCover category={article.category} cover={article.coverImage} height={280} radius={20} />
      </div>

      <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:14, flexWrap:'wrap' }}>
        <AlNewBadge article={article} />
        <AlCatPill category={article.category} />
        <AlEvBadge level={article.evidenceLevel} />
        {alFmtPubDate(article.publishedAt) && (
          <span style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:AL.muted }}>· {alFmtPubDate(article.publishedAt)}</span>
        )}
        <span style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:AL.muted }}>· {article.readTime} min lectura</span>
        {article.publishYear && article.journal && (
          <span style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:AL.muted }}>· {article.journal} ({article.publishYear})</span>
        )}
      </div>

      <h1 style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:'clamp(26px, 7vw, 40px)', fontWeight:700, color:AL.navy, letterSpacing: isMobile ? -0.8 : -1.8, lineHeight:1.06, margin:'0 0 10px' }}>
        {article.title}
      </h1>
      <p style={{ fontFamily:'"Inter",system-ui', fontSize:17, color:AL.sub, margin:'0 0 8px', fontWeight:500, letterSpacing:-0.2, lineHeight:1.5 }}>
        {article.subtitle}
      </p>
      {article.authors?.length > 0 && (
        <p style={{ fontFamily:'"Inter",system-ui', fontSize:13, color:AL.muted, margin:'0 0 32px' }}>
          {article.authors.join(', ')}
          {article.doi && <span> · <a href={'https://doi.org/' + article.doi} target="_blank" rel="noopener noreferrer" style={{ color:'#1a4fa0', textDecoration:'none' }}>doi:{article.doi}</a></span>}
        </p>
      )}

      {/* Abstract */}
      <div style={{ padding:'20px 24px', borderRadius:16, background:'rgba(15,26,46,0.03)', border:`1px solid ${AL.border}`, marginBottom:24 }}>
        <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, color:AL.muted, letterSpacing:1, marginBottom:10 }}>ABSTRACT</div>
        <p style={{ fontFamily:'"Inter",system-ui', fontSize: isMobile ? 16 : 15, color:'#3A4257', lineHeight:1.6, margin:0 }}>{article.summary}</p>
      </div>

      {/* AI summary */}
      {article.aiSummary && (
        <div style={{ marginBottom:24 }}>
          <button onClick={() => setShowAi(s=>!s)} style={{ display:'flex', alignItems:'center', gap:8, background:'none', border:'none', cursor:'pointer', padding:0, fontFamily:'"Inter",system-ui', fontSize:13, fontWeight:700, color:AL.navy }}>
            <span style={{ width:22, height:22, borderRadius:999, background:AL.navy, color:'#FAFAF7', display:'inline-flex', alignItems:'center', justifyContent:'center', fontSize:10, fontWeight:800 }}>A</span>
            Resumen IA {showAi ? '↑' : '↓'}
          </button>
          {showAi && (
            <div style={{ marginTop:10, padding:'16px 20px', borderRadius:14, background:'rgba(15,26,46,0.03)', border:`1px solid ${AL.border}` }}>
              <p style={{ fontFamily:'"Inter",system-ui', fontSize:14, color:AL.navy, lineHeight:1.55, margin:0 }}>"{article.aiSummary}"</p>
            </div>
          )}
        </div>
      )}

      {/* Article body — rich blocks (paragraphs, headings, images, lists,
          quotes, callouts) with an inline-cited scientific bibliography. */}
      <div style={{ marginBottom:32 }}>
        <AlArticleBody article={article} isMobile={isMobile} onOpenGlossary={onOpenGlossary} />
      </div>

      {/* Read CTA */}
      <div style={{ padding:'22px 26px', borderRadius:18, background: isRead ? 'rgba(5,150,105,0.07)' : AL.navy, border: isRead ? '1px solid rgba(5,150,105,0.16)' : 'none', display:'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'stretch' : 'center', justifyContent:'space-between', gap:16 }}>
        {isRead ? (
          <div style={{ fontFamily:'"Inter",system-ui', fontSize:14, fontWeight:700, color:'#059669' }}>✓ Artículo completado · gemas acreditadas</div>
        ) : (
          <>
            <div>
              <div style={{ fontFamily:'"Inter",system-ui', fontSize:14, fontWeight:700, color:'#FAFAF7' }}>¿Has terminado de leer?</div>
              <div style={{ fontFamily:'"Inter",system-ui', fontSize:12, color:'rgba(250,250,247,0.55)', marginTop:2 }}>Gana {article.gems} gemas al marcarlo como leído</div>
            </div>
            <button onClick={onMarkRead} style={{ minHeight: isMobile ? 46 : undefined, padding:'11px 22px', borderRadius:12, border:'none', cursor:'pointer', background:'#FAFAF7', color:AL.navy, fontFamily:'"Inter",system-ui', fontSize:14, fontWeight:700, whiteSpace:'nowrap' }}>
              Marcar como leído 💎
            </button>
          </>
        )}
      </div>

      {/* Related articles */}
      {allArticles && onOpen && (
        <AulaRelated allArticles={allArticles} currentId={article.id} completedIds={completedIds || []} onOpen={onOpen} />
      )}

      {/* Ask Coach */}
      {onAskCoach && (
        <div style={{ padding: '14px 18px', borderRadius: 14, background: 'rgba(15,26,46,0.03)', border: `1px solid ${AL.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, marginBottom: 16 }}>
          <span style={{ fontFamily: '"Inter",system-ui', fontSize: 13, color: AL.sub }}>¿Tienes dudas sobre este tema?</span>
          <button onClick={onAskCoach} style={{ padding: '7px 16px', borderRadius: 9, border: 'none', background: AL.navy, color: '#FAFAF7', fontFamily: '"Inter",system-ui', fontSize: 12, fontWeight: 700, cursor: 'pointer' }}>
            Pregunta al Coach →
          </button>
        </div>
      )}
    </div>
  );
}

// ── For You — personalized article strip ──────────────────────────────────────
function AulaForYou({ articles, profile, completedIds, onOpen }) {
  const isMobile = useIsMobile();
  if (typeof aulaGetRecommended === 'undefined' || !profile) return null;
  const recommended = aulaGetRecommended(articles, profile, completedIds, 4);
  if (!recommended.length) return null;

  const OBJ_LABELS = { muscle:'Hipertrofia', fat_loss:'Pérdida de grasa', recomp:'Recomposición', performance:'Rendimiento', health:'Salud y bienestar' };
  const objLabel = OBJ_LABELS[profile.objective] || 'tu objetivo';

  return (
    <div style={{ marginBottom: 48 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 6 }}>
        <h2 style={{ fontFamily: '"Inter",system-ui', fontSize: 22, fontWeight: 800, color: AL.navy, letterSpacing: -0.8, margin: 0 }}>Para ti</h2>
        <span style={{ fontFamily: '"Inter",system-ui', fontSize: 13, color: AL.muted }}>seleccionado según tu objetivo: <strong style={{ color: AL.sub }}>{objLabel}</strong></span>
      </div>
      <p style={{ fontFamily: '"Inter",system-ui', fontSize: 12, color: AL.muted, margin: '0 0 20px' }}>
        {profile.experience === 'beginner' ? 'Empezamos por los fundamentos.' : profile.experience === 'advanced' ? 'Contenido avanzado alineado con tu nivel.' : 'Contenido intermedio ordenado por relevancia.'}
      </p>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(4,1fr)', gap: 16 }}>
        {recommended.map((a, i) => (
          <AulaCard key={a.id} article={a} isRead={completedIds.includes(a.id)} onOpen={onOpen} imgIdx={i} />
        ))}
      </div>
    </div>
  );
}

// ── Learning Path Strip ───────────────────────────────────────────────────────
function AulaLearningPath({ articles, profile, completedIds, onOpen }) {
  if (typeof aulaGetLearningPath === 'undefined' || !profile) return null;
  const path = aulaGetLearningPath(profile);
  const available = (path.steps || []).filter(s => articles.some(a => a.id === s.id));
  if (available.length < 2) return null;

  const doneCount = available.filter(s => completedIds.includes(s.id)).length;
  const pct       = Math.round((doneCount / available.length) * 100);

  return (
    <div style={{ marginBottom: 40, padding: '20px 24px', borderRadius: 18, background: AL.card, border: `1px solid ${AL.border}` }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
        <div>
          <div style={{ fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 9, fontWeight: 700, color: AL.muted, letterSpacing: 1, marginBottom: 4 }}>RUTA DE APRENDIZAJE</div>
          <div style={{ fontFamily: '"Inter",system-ui', fontSize: 15, fontWeight: 700, color: AL.navy }}>{path.label}</div>
        </div>
        <div style={{ textAlign: 'right' }}>
          <div style={{ fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 13, fontWeight: 700, color: path.color || AL.navy }}>{doneCount} / {available.length}</div>
          <div style={{ fontFamily: '"Inter",system-ui', fontSize: 11, color: AL.muted }}>completados</div>
        </div>
      </div>
      <div style={{ height: 4, borderRadius: 999, background: 'rgba(15,26,46,0.08)', marginBottom: 16, overflow: 'hidden' }}>
        <div style={{ height: '100%', borderRadius: 999, background: path.color || AL.navy, width: `${pct}%`, transition: 'width .4s ease' }} />
      </div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        {available.map((step, i) => {
          const done    = completedIds.includes(step.id);
          const article = articles.find(a => a.id === step.id);
          return (
            <button
              key={step.id}
              onClick={() => article && onOpen(step.id)}
              style={{
                display: 'flex', alignItems: 'center', gap: 6,
                padding: '6px 13px', borderRadius: 999,
                border: done ? 'none' : `1px solid rgba(15,26,46,0.12)`,
                background: done ? (path.color || AL.navy) : 'transparent',
                color: done ? '#FFFFFF' : AL.sub,
                fontFamily: '"Inter",system-ui', fontSize: 12, fontWeight: 600,
                cursor: article ? 'pointer' : 'default',
                transition: 'all .15s',
              }}
            >
              <span style={{ fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 9, opacity: 0.6 }}>{i + 1}</span>
              {step.label}
              {done && <span>✓</span>}
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ── Related articles (inside detail view) ─────────────────────────────────────
function AulaRelated({ allArticles, currentId, completedIds, onOpen }) {
  if (typeof aulaGetRelatedArticles === 'undefined') return null;
  const related = aulaGetRelatedArticles(allArticles, currentId, 3);
  if (!related.length) return null;
  return (
    <div style={{ marginTop: 32, marginBottom: 24 }}>
      <div style={{ fontFamily: 'ui-monospace,Menlo,monospace', fontSize: 9, fontWeight: 700, color: AL.muted, letterSpacing: 1, marginBottom: 14 }}>ARTÍCULOS RELACIONADOS</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {related.map(a => {
          const done = completedIds.includes(a.id);
          const m    = ALcat[a.category] || ALcat.fuerza;
          return (
            <button
              key={a.id}
              onClick={() => onOpen(a.id)}
              style={{
                display: 'flex', alignItems: 'center', gap: 14,
                padding: '12px 16px', borderRadius: 12,
                border: `1px solid ${AL.border}`, background: AL.card,
                cursor: 'pointer', textAlign: 'left', transition: 'all .15s', width: '100%',
              }}
              onMouseEnter={e => { e.currentTarget.style.background = '#F5F5F0'; }}
              onMouseLeave={e => { e.currentTarget.style.background = AL.card; }}
            >
              <div style={{ width: 36, height: 36, borderRadius: 9, flexShrink: 0, background: m.bg, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <div style={{ width: 8, height: 8, borderRadius: '50%', background: m.dot }} />
              </div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontFamily: '"Inter",system-ui', fontSize: 13, fontWeight: 700, color: AL.navy, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{a.title}</div>
                <div style={{ fontFamily: '"Inter",system-ui', fontSize: 11, color: AL.muted, marginTop: 2 }}>{a.readTime} min · {(ALcat[a.category] || ALcat.fuerza).label}</div>
              </div>
              {done
                ? <span style={{ fontFamily: '"Inter",system-ui', fontSize: 11, color: '#059669', fontWeight: 700, flexShrink: 0 }}>✓ Leído</span>
                : <span style={{ fontFamily: '"Inter",system-ui', fontSize: 11, color: '#059669', fontWeight: 700, flexShrink: 0 }}>+{a.gems} 💎</span>
              }
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ── Main section ──────────────────────────────────────────────────────────────
function AulaSection() {
  const { state, actions } = useStore();
  const { navigate }       = useRoute();
  const isMobile = useIsMobile();
  const [articles, setArticles] = React.useState([]);
  const [loading, setLoading]   = React.useState(true);
  const [query, setQuery]       = React.useState('');
  const [category, setCategory] = React.useState('all');
  const [openId, setOpenId]     = React.useState(null);
  const [gemFlash, setGemFlash] = React.useState(null);
  // Glossary jump: set when a reader term's "Ver en el glosario" is tapped —
  // closes the article and tells the glossary to scroll to that entry.
  const [glossJump, setGlossJump] = React.useState(null);
  const openGlossary = React.useCallback((termId) => { setOpenId(null); setGlossJump({ id: termId, ts: Date.now() }); }, []);

  // Shareable deep links: #/aula/glosario (→ scroll to the glossary) and
  // #/aula/glosario#<term> (→ scroll to and flash that term).
  const handleGlossaryDeepLink = React.useCallback(() => {
    const raw = (window.location.hash || '').replace(/^#/, '');
    const i = raw.indexOf('#');
    const path = i >= 0 ? raw.slice(0, i) : raw;
    const frag = i >= 0 ? raw.slice(i + 1) : '';
    if (path !== '/aula/glosario') return;
    if (frag) { openGlossary(frag); }
    else { setOpenId(null); setTimeout(() => { const el = document.getElementById('glosario'); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 140); }
  }, [openGlossary]);

  React.useEffect(() => {
    window.addEventListener('hashchange', handleGlossaryDeepLink);
    return () => window.removeEventListener('hashchange', handleGlossaryDeepLink);
  }, [handleGlossaryDeepLink]);

  // First-mount deep link (#/aula/glosario[#term]): run independently of the
  // article fetch so the glossary jump/flash works even if the feed is slow.
  React.useEffect(() => {
    const t = setTimeout(handleGlossaryDeepLink, 180);
    return () => clearTimeout(t);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const atlasProfile = React.useMemo(() => {
    try { return JSON.parse(localStorage.getItem('atlas.profile.v1') || 'null'); } catch { return null; }
  }, []);

  React.useEffect(() => {
    setLoading(true);
    ArticlesService.getAll({ status:'published' }).then(data => {
      setArticles(data);
      setLoading(false);
      // Open article deep-linked from Coach
      const pending = localStorage.getItem('atlas.aula.pending.v1');
      if (pending) {
        localStorage.removeItem('atlas.aula.pending.v1');
        setOpenId(pending);
      }
      // Jump to a glossary term deep-linked from Coach
      const pendingGloss = localStorage.getItem('atlas.aula.glossary.pending.v1');
      if (pendingGloss) {
        localStorage.removeItem('atlas.aula.glossary.pending.v1');
        openGlossary(pendingGloss);
      }
      // URL deep link (#/aula/glosario[#term]) — run once the glossary can mount.
      if (!pending && !pendingGloss) handleGlossaryDeepLink();
    });
  }, []);

  const completed = state.reading?.completed || [];
  // Published article ids — the glossary only shows "Leer más" for these.
  const publishedIds = React.useMemo(() => new Set((articles || []).map(a => a.id)), [articles]);

  const filtered = React.useMemo(() => {
    const q = query.toLowerCase().trim();
    return articles.filter(a =>
      (category === 'all' || a.category === category) &&
      (!q || a.title.toLowerCase().includes(q) || a.subtitle.toLowerCase().includes(q) ||
       a.summary.toLowerCase().includes(q) ||
       (a.aiKeywords || []).some(k => k.toLowerCase().includes(q)) ||
       a.tags.some(t => t.toLowerCase().includes(q)))
    );
  }, [articles, query, category]);

  const featured = filtered[0] || null;
  const feedItems = filtered.slice(1);
  const openArticle = openId ? articles.find(a => a.id === openId) : null;

  function handleMarkRead() {
    if (!openId) return;
    const art = articles.find(a => a.id === openId);
    if (!art || completed.includes(openId)) return;
    actions.markArticleRead(openId);
    setGemFlash(`+${art.gems} gemas · ${art.title}`);
    setTimeout(() => setGemFlash(null), 2500);
  }

  // Category change with a direction hint for the swipe/slide transition.
  const [swipeDir, setSwipeDir] = React.useState(1);
  const changeCategory = React.useCallback((cat) => {
    setCategory(prev => {
      const from = AULA_CATS.indexOf(prev), to = AULA_CATS.indexOf(cat);
      setSwipeDir(to >= from ? 1 : -1);
      return cat;
    });
  }, []);
  const catIndex = Math.max(0, AULA_CATS.indexOf(category));
  const catSwipe = useSwipeNav({ index: catIndex, count: AULA_CATS.length, onChange: (next) => changeCategory(AULA_CATS[next]) });

  // Tapping the active "Aula" tab while at the top returns to the section root:
  // close an open article.
  React.useEffect(() => {
    const onReset = (e) => { if (e.detail && e.detail.route === '/aula') setOpenId(null); };
    window.addEventListener('atlas:navreset', onReset);
    return () => window.removeEventListener('atlas:navreset', onReset);
  }, []);

  function handleCategoryFromExplore(cat) {
    changeCategory(cat);
    setOpenId(null);
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }

  return (
    <section style={{ padding: isMobile ? '24px 20px 32px' : '100px 32px 100px', background:AL.page, minHeight:'100vh' }}>
      {gemFlash && (
        <div style={{ position:'fixed', top:80, right:32, zIndex:300, background:AL.navy, color:'#FAFAF7', padding:'10px 20px', borderRadius:999, fontFamily:'"Inter",system-ui', fontSize:14, fontWeight:700, animation:'fadeIn .3s ease', boxShadow:'0 8px 32px rgba(15,26,46,0.25)' }}>
          💎 {gemFlash}
        </div>
      )}

      <div style={{ maxWidth:1100, margin:'0 auto' }}>
        {openArticle ? (
          <AulaDetail
            article={openArticle}
            isRead={completed.includes(openId)}
            onBack={() => setOpenId(null)}
            onMarkRead={handleMarkRead}
            allArticles={articles}
            completedIds={completed}
            onOpen={setOpenId}
            onAskCoach={() => navigate('/coach')}
            onOpenGlossary={openGlossary}
          />
        ) : (
          <>
            {/* ── Hero ─────────────────────────────────────────────── */}
            <AulaHero
              total={articles.length}
              completed={completed.length}
              query={query}
              onQuery={setQuery}
            />

            {/* ── Personalized: For You ─────────────────────────────── */}
            {!loading && atlasProfile && (
              <AulaForYou articles={articles} profile={atlasProfile} completedIds={completed} onOpen={setOpenId} />
            )}

            {/* ── Learning path ─────────────────────────────────────── */}
            {!loading && atlasProfile && (
              <AulaLearningPath articles={articles} profile={atlasProfile} completedIds={completed} onOpen={setOpenId} />
            )}

            {/* ── Category strip ────────────────────────────────────── */}
            <AlCatStrip active={category} onChange={changeCategory} />

            {/* ── Swipeable, animated category feed (prev/next on horizontal swipe) ── */}
            <div {...catSwipe}>
              <div key={category} className={swipeDir === 1 ? 'atlas-tab-in-next' : 'atlas-tab-in-prev'}>
                {/* Loading skeleton */}
                {loading && (
                  <div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap:20 }}>
                    {[1,2,3,4].map(i => <div key={i} style={{ borderRadius:20, background:'rgba(15,26,46,0.05)', height:280 }} />)}
                  </div>
                )}

                {/* Empty state */}
                {!loading && filtered.length === 0 && (
                  <div style={{ textAlign:'center', padding:'80px 0' }}>
                    <div style={{ fontFamily:'"Space Grotesk",system-ui', fontWeight:600, fontSize:22, color:AL.sub, marginBottom:12 }}>Sin resultados</div>
                    <p style={{ fontFamily:'"Inter",system-ui', fontSize:14, color:AL.muted, marginBottom:20 }}>
                      No hay artículos que coincidan con "{query}".
                    </p>
                    <button onClick={() => { setQuery(''); changeCategory('all'); }} style={{ padding:'9px 20px', borderRadius:999, border:`1px solid ${AL.bord2}`, background:'transparent', cursor:'pointer', fontFamily:'"Inter",system-ui', fontSize:13, fontWeight:600, color:AL.navy }}>
                      Limpiar filtros
                    </button>
                  </div>
                )}

                {/* Featured article */}
                {!loading && featured && (
                  <AulaFeatured article={featured} isRead={completed.includes(featured.id)} onOpen={setOpenId} />
                )}

                {/* Feed grid */}
                {!loading && feedItems.length > 0 && (
                  <div style={{ marginBottom:64 }}>
                    <AlSectionHead
                      label={category === 'all' ? 'Lo más reciente' : (ALcat[category]?.label || 'Artículos')}
                      sublabel={`${feedItems.length} artículo${feedItems.length !== 1 ? 's' : ''}`}
                    />
                    <div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(2,1fr)', gap:20 }}>
                      {feedItems.map((a, i) => (
                        <AulaCard key={a.id} article={a} isRead={completed.includes(a.id)} onOpen={setOpenId} imgIdx={i} />
                      ))}
                    </div>
                  </div>
                )}
              </div>
            </div>

            {/* ── Micro-learning ────────────────────────────────────── */}
            {/* Oculto tras bandera (FEATURE_AULA_SNIPPETS): el contenido contradice
                los artículos validados y sus citas no son verificables. */}
            {!loading && (typeof ATLAS_FEATURE === 'function' && ATLAS_FEATURE('FEATURE_AULA_SNIPPETS')) && <AulaMicroLearning />}

            {/* ── Science feed ──────────────────────────────────────── */}
            {!loading && (typeof ATLAS_FEATURE === 'function' && ATLAS_FEATURE('FEATURE_AULA_SNIPPETS')) && <AulaScienceFeed />}

            {/* ── Glosario ─────────────────────────────────────────── */}
            {/* El glosario es contenido estático (GlossaryService), no depende de
                la carga de artículos: se muestra siempre para que sus entradas
                (p. ej. «Descarga») aparezcan aunque el feed aún esté cargando. */}
            <AulaGlossary onOpenArticle={setOpenId} jumpTo={glossJump} publishedIds={publishedIds} />

            {/* ── Explore by topic ──────────────────────────────────── */}
            <AulaExplore onCategory={handleCategoryFromExplore} />
          </>
        )}
      </div>
    </section>
  );
}

Object.assign(window, { AulaSection });
