/* global React, ReactDOM */
/* lp-blog.jsx — dual mode: listing (no slug) / article (slug present) */
const { useEffect: useEff_bl, useState: useSt_bl } = React;
const SANITY_PROJECT = 'gyrzd1m5';
const SANITY_DATASET = 'production';
const SANITY_API = `https://${SANITY_PROJECT}.api.sanity.io/v2024-01-01/data/query/${SANITY_DATASET}`;
const SANITY_IMG = `https://cdn.sanity.io/images/gyrzd1m5/${SANITY_DATASET}/`;
const slugify = (text) =>
text.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]/g, '');
const formatDate = (iso) => {
if (!iso) return '';
return new Date(iso).toLocaleDateString('es-MX', { year: 'numeric', month: 'long', day: 'numeric' });
};
/* ══════════════════════════════════════════
BLOG LISTING
═══════════════════════════════════════════ */
function BlogCard({ post, featured }) {
const href = `/blog?slug=${post.slug || ''}`;
if (featured) {
return (
{post.category}{post.readTime ? ` · ${post.readTime} min` : ''}
{post.title}
{post.excerpt &&
{post.excerpt}
}
{formatDate(post.publishedAt)}
Leer artículo
);
}
return (
{post.imageUrl && }
{post.category}{post.readTime ? ` · ${post.readTime} min` : ''}
{post.title}
{post.excerpt &&
{post.excerpt}
}
{formatDate(post.publishedAt)}
);
}
function BlogListing() {
const [posts, setPosts] = useSt_bl([]);
const [loading, setLoading] = useSt_bl(true);
useEff_bl(() => {
const q = encodeURIComponent(
`*[_type=="post"] | order(publishedAt desc) { title, "slug": slug.current, category, readTime, publishedAt, excerpt, "imageUrl": mainImage.asset->url }`
);
fetch(`${SANITY_API}?query=${q}`)
.then(r => r.json())
.then(d => { setPosts(d.result || []); setLoading(false); })
.catch(() => setLoading(false));
}, []);
return (
<>
{/* ── Hero ── */}
{/* ── Listing ── */}
{loading && (
)}
{!loading && posts.length === 0 && (
Próximamente
Pronto publicaremos nuestras primeras historias.
)}
{!loading && posts.length > 0 && (
<>
{posts.length > 1 && (
<>
Más artículos
{posts.slice(1).map((p, i) => (
))}
>
)}
>
)}
>
);
}
/* ══════════════════════════════════════════
BLOG ARTICLE (detail)
═══════════════════════════════════════════ */
function renderBlock(block, i) {
if (block._type === 'image') {
const ref = block.asset?._ref || '';
const file = ref.replace('image-', '').replace(/-([a-z]+)$/, '.$1');
return
;
}
if (block._type !== 'block') return null;
const children = (block.children || []).map((span, si) => {
let node = span.text;
if (span.marks?.includes('strong')) node = {node};
if (span.marks?.includes('em')) node = {node};
return node;
});
const style = block.style || 'normal';
const rawText = (block.children || []).map(s => s.text).join('');
if (style === 'h2') return {children}
;
if (style === 'h3') return {children}
;
if (style === 'blockquote') return {children}
;
return {children}
;
}
function TableOfContents({ body }) {
const headings = (body || [])
.filter(b => b._type === 'block' && (b.style === 'h2' || b.style === 'h3'))
.map(b => ({ text: (b.children || []).map(s => s.text).join(''), level: b.style }));
if (headings.length === 0) return null;
return (
);
}
function ReadingProgress() {
const [pct, setPct] = useSt_bl(0);
useEff_bl(() => {
const onScroll = () => {
const el = document.documentElement;
const max = el.scrollHeight - el.clientHeight;
setPct(max > 0 ? (window.scrollY / max) * 100 : 0);
};
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
return ;
}
function BlogArticle() {
const [post, setPost] = useSt_bl(null);
const [loading, setLoading] = useSt_bl(true);
const [error, setError] = useSt_bl(false);
useEff_bl(() => {
const slug = new URLSearchParams(window.location.search).get('slug');
if (!slug) { setError(true); setLoading(false); return; }
const q = encodeURIComponent(
`*[_type=="post" && slug.current==$slug][0]{ title, category, readTime, publishedAt, excerpt, "imageUrl": mainImage.asset->url, body }`
);
const slugParam = `%24slug=${encodeURIComponent(JSON.stringify(slug))}`;
fetch(`${SANITY_API}?query=${q}&${slugParam}`)
.then(r => r.json())
.then(d => {
if (!d.result) { setError(true); } else { setPost(d.result); }
setLoading(false);
})
.catch(() => { setError(true); setLoading(false); });
}, []);
return (
<>
{loading && (
)}
{error && !loading && (
)}
{post && !loading && (
{post.excerpt && {post.excerpt}
}
{(post.body || []).map((block, i) => renderBlock(block, i))}
)}
>
);
}
/* ══════════════════════════════════════════
ROUTER — slug present → article, else → listing
═══════════════════════════════════════════ */
const hasSlug = new URLSearchParams(window.location.search).has('slug');
ReactDOM.createRoot(document.getElementById('root')).render(
hasSlug ? :
);