// blog-ui.jsx — Reusable UI primitives for the Stories of Dawoodi Bohra Women blog

const { useState, useEffect, useRef, useMemo } = React;

// ── The Gul-Lala brand mark: a stylised tulip.
//    `tone` maps to a CSS variable — kept for backwards compatibility with the
//    old palette hooks:
//      "gold"   → --gold       (blush petals in the gul-lala palette)
//      "accent" → --accent     (rose accent)
//      "deep"   → --gold-deep  (deeper rose)
function Mark({ size = 48, tone = "gold", className = "", style = {}, title }) {
  const fillVar =
    tone === "accent" ? "var(--accent)" :
    tone === "deep"   ? "var(--gold-deep)" :
                        "var(--gold)";
  return (
    <svg
      width={size} height={size}
      viewBox="0 0 24 24"
      xmlns="http://www.w3.org/2000/svg"
      className={`mark ${className}`}
      style={style}
      aria-hidden={title ? undefined : true}
      role={title ? "img" : undefined}
    >
      {title && <title>{title}</title>}
      <ellipse fill={fillVar} cx="12" cy="8.2" rx="3" ry="5"/>
      <ellipse fill={fillVar} cx="8.4" cy="9.6" rx="2.7" ry="4.3" transform="rotate(-14 8.4 9.6)"/>
      <ellipse fill={fillVar} cx="15.6" cy="9.6" rx="2.7" ry="4.3" transform="rotate(14 15.6 9.6)"/>
      <path fill={fillVar} d="M6 10.4h12v1.3c0 3.2-2.7 5.3-6 5.3s-6-2.1-6-5.3z"/>
      <rect fill="#8FA382" x="11.35" y="16.6" width="1.3" height="5.2" rx="0.65"/>
      <path fill="#8FA382" d="M11.6 21.2c-2.8-.5-4.7-2.2-5.1-4.5 2.7.2 4.6 1.8 5.1 4.5z"/>
      <path fill="#8FA382" d="M12.4 21.2c2.8-.5 4.7-2.2 5.1-4.5-2.7.2-4.6 1.8-5.1 4.5z"/>
    </svg>
  );
}

// ── A large background motif — sits behind a section, low opacity, rotates subtly
function Motif({ size = 320, opacity = 0.06, rotate = 0, top, left, right, bottom, tone = "gold", className = "" }) {
  return (
    <span
      className={`motif ${className}`}
      style={{
        position: "absolute",
        top, left, right, bottom,
        opacity: `calc(${opacity} * var(--motif-intensity, 1))`,
        transform: `rotate(${rotate}deg)`,
        pointerEvents: "none",
        lineHeight: 0
      }}
      aria-hidden="true"
    >
      <Mark size={size} tone={tone} />
    </span>
  );
}

function Icon({ name, size = 18, className = "" }) {
  const common = {
    width: size,
    height: size,
    viewBox: "0 0 24 24",
    fill: "none",
    stroke: "currentColor",
    strokeWidth: "1.8",
    strokeLinecap: "round",
    strokeLinejoin: "round",
    className: `icon ${className}`,
    "aria-hidden": "true"
  };
  const paths = {
    home: (
      <>
        <path d="M3.5 11.2 12 4l8.5 7.2" />
        <path d="M5.8 10.4v8.2h4.1v-5h4.2v5h4.1v-8.2" />
      </>
    ),
    search: (
      <>
        <circle cx="11" cy="11" r="6.5" />
        <path d="m16 16 4 4" />
      </>
    ),
    close: (
      <>
        <path d="M6 6l12 12" />
        <path d="M18 6 6 18" />
      </>
    ),
    heart: (
      <path d="M20.4 5.8c-1.9-1.8-4.9-1.5-6.6.5L12 8.4l-1.8-2.1c-1.7-2-4.7-2.3-6.6-.5-2.2 2.1-2.1 5.7.2 7.8L12 21l8.2-7.4c2.3-2.1 2.4-5.7.2-7.8Z" />
    ),
    comment: (
      <>
        <path d="M5 5.8h14v9.4H9.2L5 18.7Z" />
        <path d="M8.5 9.2h7" />
        <path d="M8.5 12h4.8" />
      </>
    ),
    mail: (
      <>
        <path d="M4 6h16v12H4Z" />
        <path d="m4 7 8 6 8-6" />
      </>
    ),
    link: (
      <>
        <path d="M9.5 14.5 14.5 9.5" />
        <path d="M8.6 10.6 7.2 12a4 4 0 0 0 5.7 5.7l1.3-1.3" />
        <path d="m9.8 7.6 1.3-1.3a4 4 0 0 1 5.7 5.7l-1.4 1.4" />
      </>
    ),
    whatsapp: (
      <>
        <path d="M12 3a9 9 0 0 0-7.5 13.9L3 21l4.3-1.4A9 9 0 1 0 12 3Z" />
        <path d="M9.4 8.5c-.4 0-.7.1-1 .5-.4.5-1.4 1.4-1.4 3.3s1.4 3.7 1.6 4c.2.3 2.7 4.2 6.7 5.7 3.4 1.3 4.1 1 4.8.9.7-.1 2.3-.9 2.6-1.9.3-.9.3-1.7.2-1.9-.1-.2-.4-.3-.9-.5-.5-.3-2.3-1.1-2.7-1.3-.4-.1-.6-.2-.9.2-.3.4-1 1.3-1.2 1.5-.2.3-.4.3-.9.1-.5-.3-1.8-.7-3.5-2.1-1.3-1.2-2.1-2.6-2.4-3-.2-.5 0-.7.2-.9.2-.2.5-.5.7-.8.2-.3.3-.5.4-.8.1-.3.1-.6 0-.9-.1-.3-.9-2.2-1.2-3-.3-.8-.6-.7-.9-.7Z" fill="currentColor" stroke="none" />
      </>
    ),
    upload: (
      <>
        <path d="M12 16V5" />
        <path d="m8 9 4-4 4 4" />
        <path d="M5 16v3h14v-3" />
      </>
    ),
    send: (
      <>
        <path d="M4 12 20 4l-5 16-3-7Z" />
        <path d="m12 13 8-9" />
      </>
    )
  };
  return <svg {...common}>{paths[name]}</svg>;
}

// ── Printed plate: wraps real images with the existing editorial frame.
function Plate({ caption, tone = "warm", ratio = "4 / 3", className = "", style = {}, src, alt = "" }) {
  // Three tonal flavours that share the palette feel
  const tones = {
    warm: { a: "var(--paper-warm)", b: "var(--paper)", stripe: "rgba(192,148,99,.10)" },
    deep: { a: "color-mix(in oklab, var(--accent) 14%, var(--paper-warm))", b: "var(--paper-warm)", stripe: "rgba(192,148,99,.18)" },
    soft: { a: "var(--paper)", b: "var(--paper-warm)", stripe: "rgba(192,148,99,.08)" }
  }[tone] || { a: "var(--paper-warm)", b: "var(--paper)", stripe: "rgba(192,148,99,.10)" };

  return (
    <figure className={`plate ${className}`} style={{ "--plate-ratio": ratio, ...style }}>
      <div
        className={`plate-art ${src ? "has-image" : ""}`}
        style={{
          background:
            `repeating-linear-gradient(135deg, ${tones.stripe} 0 1px, transparent 1px 14px),` +
            `linear-gradient(135deg, ${tones.a}, ${tones.b})`
        }}
      >
        {src && <img className="plate-img" src={src} alt={alt} loading="lazy" decoding="async" />}
        <span className="bracket tl"></span>
        <span className="bracket tr"></span>
        <span className="bracket bl"></span>
        <span className="bracket br"></span>
        {!src && <span className="plate-glyph">photograph</span>}
      </div>
      {caption && <figcaption className="plate-cap">{caption}</figcaption>}
    </figure>
  );
}

// ── Decorative gold rule with a small brand mark in the middle
function Rule({ wide = false }) {
  return (
    <div className={`rule ${wide ? "rule-wide" : ""}`}>
      <span className="rule-line"></span>
      <Mark size={14} className="rule-mark" />
      <span className="rule-line"></span>
    </div>
  );
}

// ── Small asterism (three tiny marks) — section break
function Asterism() {
  return (
    <div className="asterism" aria-hidden="true">
      <Mark size={9} />
      <Mark size={11} />
      <Mark size={9} />
    </div>
  );
}

// ── Section heading with serif title + small caps eyebrow
function SectionHead({ eyebrow, title, kicker }) {
  return (
    <header className="sec-head">
      {eyebrow && <div className="eyebrow">{eyebrow}</div>}
      <h2 className="sec-title">{title}</h2>
      {kicker && <p className="sec-kicker">{kicker}</p>}
    </header>
  );
}

function HeaderBrand({ onHome }) {
  return (
    <a href="#" className="brand-lockup" aria-label="Dawoodi Bohra Women — home" onClick={(e) => { e.preventDefault(); onHome && onHome(); }}>
      <span className="brand-topline">
        <span>Our Culture</span>
        <Mark size={46} className="brand-mark" />
        <span>Our Tradition</span>
      </span>
      <span className="brand-eyebrow">Dawoodi Bohra Women</span>
    </a>
  );
}

function SearchBar({ query, onSearch, onClear }) {
  return (
    <label className="nav-search" role="search">
      <Icon name="search" size={15} className="nav-search-icon" />
      <input
        type="text"
        placeholder="Search stories, authors, themes…"
        value={query}
        onChange={(e) => onSearch(e.target.value)}
        aria-label="Search"
      />
      {query && (
        <button className="search-clear" type="button" onClick={onClear} aria-label="Clear search">
          <Icon name="close" size={12} />
        </button>
      )}
    </label>
  );
}

// ── The site logo lockup: mark + wordmark with 3 layout variants
// style:
//   "stacked"   → mark on top, "Stories of" eyebrow, then "Dawoodi Bohra Women" — fully centered (editorial masthead)
//   "inline"    → mark left of stacked wordmark (compact, horizontal)
//   "wordmark"  → typography only, no mark
function Logo({ size = 56, style = "stacked" }) {
  if (style === "wordmark") {
    return (
      <a href="#" className="logo logo-wordmark" aria-label="Stories of Dawoodi Bohra Women — home">
        <span className="logo-text">
          <span className="logo-eyebrow">Stories of</span>
          <span className="logo-name">Dawoodi Bohra Women</span>
        </span>
      </a>
    );
  }
  if (style === "inline") {
    return (
      <a href="#" className="logo logo-inline" aria-label="Stories of Dawoodi Bohra Women — home">
        <Mark size={Math.round(size * 0.85)} className="logo-mark" />
        <span className="logo-text">
          <span className="logo-eyebrow">Stories of</span>
          <span className="logo-name">Dawoodi Bohra Women</span>
        </span>
      </a>
    );
  }
  // stacked (default)
  return (
    <a href="#" className="logo logo-stacked" aria-label="Stories of Dawoodi Bohra Women — home">
      <Mark size={size} className="logo-mark" />
      <span className="logo-text">
        <span className="logo-eyebrow">Stories of</span>
        <span className="logo-name">Dawoodi Bohra Women</span>
      </span>
    </a>
  );
}

// ── Masthead: top nav. Editorial-style.
function Masthead({ onSearch, query, onClearSearch, onOpenCategory, activeCat }) {
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 8);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  const onPick = (id) => {
    onOpenCategory(id);
  };

  return (
    <header className={`masthead ${scrolled ? "scrolled" : ""}`}>
      <div className="masthead-bar">
        <div className="masthead-center">
          <HeaderBrand onHome={() => onPick("home")} />
        </div>
      </div>

      <nav className="masthead-nav" aria-label="Primary">
        <div className="masthead-nav-links">
          {window.CATEGORIES.map((c) => (
            <button
              key={c.id}
              className={`nav-link ${c.id === "home" ? "nav-link-home" : ""} ${activeCat === c.id ? "active" : ""}`}
              onClick={() => onPick(c.id)}
              aria-label={c.id === "home" ? "Home" : undefined}
              title={c.id === "home" ? "Home" : undefined}
            >
              {c.id === "home" ? <Icon name="home" size={17} /> : c.label}
            </button>
          ))}
        </div>
        <SearchBar
          query={query}
          onSearch={onSearch}
          onClear={onClearSearch}
        />
      </nav>
    </header>
  );
}

// ── Hero featured article
function Hero({ article, onOpen }) {
  if (!article) return null;
  return (
    <section className="hero" aria-label="Featured story">
      <Motif size={520} opacity={0.05} rotate={12} top="-60px" right="-180px" />
      <Motif size={140} opacity={0.10} rotate={-8} bottom="40px" left="-60px" tone="deep" />
      <div className="hero-grid">
        <div className="hero-text">
          <div className="eyebrow">
            <span className="dot">◆</span> Featured · {labelFor(article.category)}
          </div>
          <h1 className="hero-title" onClick={() => onOpen(article)}>
            {article.title}
          </h1>
          <p className="hero-sub">{article.subtitle}</p>
          <p className="hero-dek">{article.excerpt}</p>
          <div className="byline">
            <span>By <em>{article.author}</em></span>
            <span className="byline-dot">·</span>
            <span>{article.date}</span>
            <span className="byline-dot">·</span>
            <span>{article.readTime}</span>
          </div>
          <button className="btn-read" onClick={() => onOpen(article)}>
            Read the essay
            <span className="arr">→</span>
          </button>
        </div>
        <div className="hero-art">
          <Plate
            caption={article.imageCaption}
            tone="deep"
            ratio="5 / 6"
            src={article.image}
            alt={article.imageAlt || article.title}
          />
        </div>
      </div>
    </section>
  );
}

function labelFor(catId) {
  const cat = window.CATEGORIES.find((c) => c.id === catId);
  return cat ? cat.label : catId;
}

// ── ArticleCard — three sizes: large (column-spanning), medium, small (listing)
function ArticleCard({ article, size = "medium", onOpen }) {
  const sizeClass = `card card-${size}`;
  return (
    <article className={sizeClass} onClick={() => onOpen(article)}>
      {size !== "small" && (
        <Plate
          caption={article.imageCaption}
          tone={article.id.length % 2 === 0 ? "warm" : "soft"}
          ratio={size === "large" ? "16 / 10" : "4 / 3"}
          src={article.image}
          alt={article.imageAlt || article.title}
        />
      )}
      <div className="card-body">
        <div className="eyebrow eyebrow-sm">{labelFor(article.category)}</div>
        <h3 className="card-title">{article.title}</h3>
        {size !== "small" && <p className="card-sub">{article.subtitle}</p>}
        <div className="card-meta">
          <span>{article.author}</span>
          <span className="byline-dot">·</span>
          <span>{article.date}</span>
          <span className="byline-dot">·</span>
          <span>{article.readTime}</span>
        </div>
      </div>
    </article>
  );
}

// ── Pull quote — large serif italic. The decorative tulip on top is
// drawn by .pullquote::before in CSS (SVG data-URI, no JSX needed).
function PullQuote({ text, attribution }) {
  return (
    <aside className="pullquote">
      <blockquote>
        <p>{text}</p>
        {attribution && <footer>— {attribution}</footer>}
      </blockquote>
    </aside>
  );
}

// ── Category hero: shown above the archive on category pages.
function CategoryHero({ category, count }) {
  if (!category) return null;
  return (
    <section className="cat-hero" data-screen-label={`Category — ${category.label}`}>
      <Motif size={420} opacity={0.06} rotate={14} top="-80px" right="-120px" />
      <Motif size={180} opacity={0.10} rotate={-18} bottom="-40px" left="-30px" tone="deep" />
      <div className="cat-hero-inner">
        <div className="eyebrow"><span className="dot">◆</span> Column</div>
        <h1 className="cat-hero-title">{category.label}</h1>
        <Rule />
        {category.description && (
          <p className="cat-hero-desc">{category.description}</p>
        )}
        <div className="cat-hero-meta">
          {count} {count === 1 ? "piece" : "pieces"} in this column
        </div>
      </div>
    </section>
  );
}

// ── Home-page teaser CTA that opens the submit modal.
function SubmitStoryTeaser({ onOpen }) {
  return (
    <section className="submit-story" id="submit-story" data-screen-label="Submit story">
      <div className="submit-story-copy">
        <div className="eyebrow"><span className="dot">◆</span> Submit a Story</div>
        <h2 className="submit-story-title">Tell us what should be remembered.</h2>
        <p>
          Send an essay, oral history, household memory, photograph note, or recipe.
          The editors read every submission before it appears on the site.
        </p>
        <button
          type="button"
          className="submit-story-cta"
          onClick={onOpen}
        >
          <Icon name="send" size={16} />
          Submit a story
        </button>
      </div>
    </section>
  );
}

// ── Submit story modal (matches the editorial design language).
function SubmitStoryModal({ open, onClose }) {
  const [submitted, setSubmitted] = useState(false);
  const dialogRef = useRef(null);
  const firstFieldRef = useRef(null);

  useEffect(() => {
    if (!open) return;
    setSubmitted(false);
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    setTimeout(() => { if (firstFieldRef.current) firstFieldRef.current.focus(); }, 60);
    return () => {
      document.body.style.overflow = prevOverflow;
      document.removeEventListener("keydown", onKey);
    };
  }, [open, onClose]);

  if (!open) return null;

  return (
    <div
      className="modal-backdrop"
      role="dialog"
      aria-modal="true"
      aria-labelledby="submit-modal-title"
      onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
    >
      <div className="modal-card" ref={dialogRef} role="document">
        <Motif size={260} opacity={0.05} rotate={18} top="-80px" right="-80px" tone="gold" />

        <header className="modal-head">
          <div className="modal-head-text">
            <div className="eyebrow"><span className="dot">◆</span> Submit a Story</div>
            <h2 id="submit-modal-title" className="modal-title">Tell us what should be remembered.</h2>
          </div>
          <button
            type="button"
            className="modal-close"
            onClick={onClose}
            aria-label="Close"
          >
            <Icon name="close" size={16} />
          </button>
        </header>

        <Rule />

        <p className="modal-lede">
          Send an essay, oral history, photograph note, or recipe. The editors
          read every submission before it appears on the site.
        </p>

        {submitted ? (
          <div className="modal-thanks">
            <Mark size={36} tone="deep" />
            <h3>Thank you.</h3>
            <p>Your story is queued for editorial review. We’ll be in touch by email.</p>
            <button type="button" className="modal-thanks-btn" onClick={onClose}>Close</button>
          </div>
        ) : (
          <form
            className="story-form"
            onSubmit={(e) => { e.preventDefault(); setSubmitted(true); }}
          >
            <label>
              <span>Name</span>
              <input ref={firstFieldRef} type="text" name="name" autoComplete="name" required />
            </label>
            <label>
              <span>Phone number</span>
              <input type="tel" name="phone" autoComplete="tel" required />
            </label>
            <label className="full">
              <span>Email</span>
              <input type="email" name="email" autoComplete="email" required />
            </label>
            <label className="full">
              <span>A note for the editors <em className="opt">(optional)</em></span>
              <textarea name="note" rows="3" placeholder="A line or two about the piece, the people in it, or how it came to you." />
            </label>
            <label className="full file-field">
              <span>Attach your story</span>
              <input type="file" name="story" accept=".pdf,.doc,.docx,.txt" required />
            </label>
            <div className="story-form-foot">
              <span className="form-hint">PDF, DOC, or TXT. Up to 10 MB.</span>
              <div className="story-form-actions">
                <button type="button" className="btn-ghost" onClick={onClose}>Cancel</button>
                <button type="submit" className="submit-story-btn">
                  <Icon name="send" size={15} />
                  Submit
                </button>
              </div>
            </div>
          </form>
        )}
      </div>
    </div>
  );
}

function articleShareUrl(article) {
  const base = window.location.href.split("#")[0];
  return `${base}#article/${article.id}`;
}

// Format a relative timestamp from a JS Date or epoch ms.
function timeAgo(ts) {
  const now = Date.now();
  const diff = Math.max(0, now - (typeof ts === "number" ? ts : ts.getTime()));
  const m = Math.floor(diff / 60000);
  if (m < 1) return "just now";
  if (m < 60) return `${m} min ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h} hr${h === 1 ? "" : "s"} ago`;
  const d = Math.floor(h / 24);
  if (d < 7) return `${d} day${d === 1 ? "" : "s"} ago`;
  const date = new Date(ts);
  return date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
}

const COMMENT_MAX = 600;
const COMMENT_PAGE = 5;

function ArticleEngagement({ article }) {
  const [liked, setLiked] = useState(false);
  const [likes, setLikes] = useState(article.likes || 0);
  const seedTimestamps = useMemo(() => {
    // Deterministic timestamps for seeded comments — within the last few weeks.
    const base = Date.now() - 1000 * 60 * 60 * 24 * 7; // 1 week ago
    return (article.comments || []).map((_, i) => base - i * 1000 * 60 * 60 * 26);
  }, [article.id]);
  const [comments, setComments] = useState(() =>
    (article.comments || []).map((c, i) => ({ ...c, ts: seedTimestamps[i] }))
  );
  const [commentName, setCommentName] = useState("");
  const [commentText, setCommentText] = useState("");
  const [sortOrder, setSortOrder] = useState("newest");
  const [visibleComments, setVisibleComments] = useState(COMMENT_PAGE);
  const [shareOpen, setShareOpen] = useState(false);
  const [shareStatus, setShareStatus] = useState("");
  const shareRef = useRef(null);
  const commentInputRef = useRef(null);

  const url = articleShareUrl(article);
  const encodedTitle = encodeURIComponent(article.title);
  const emailHref = `mailto:?subject=${encodedTitle}&body=${encodeURIComponent(`${article.title}\n\n${url}`)}`;
  const whatsappHref = `https://wa.me/?text=${encodeURIComponent(`${article.title} ${url}`)}`;

  useEffect(() => {
    if (!shareOpen) return;
    const onDocClick = (e) => {
      if (shareRef.current && !shareRef.current.contains(e.target)) setShareOpen(false);
    };
    const onEsc = (e) => { if (e.key === "Escape") setShareOpen(false); };
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onEsc);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onEsc);
    };
  }, [shareOpen]);

  useEffect(() => {
    if (!shareStatus) return;
    const t = setTimeout(() => setShareStatus(""), 2200);
    return () => clearTimeout(t);
  }, [shareStatus]);

  const toggleLike = () => {
    setLiked((wasLiked) => {
      setLikes((count) => count + (wasLiked ? -1 : 1));
      return !wasLiked;
    });
  };

  const copyLink = () => {
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(url).then(() => setShareStatus("Link copied"));
    } else {
      setShareStatus("Copy from the address bar");
    }
  };

  const addComment = (e) => {
    e.preventDefault();
    const name = commentName.trim();
    const text = commentText.trim();
    if (!name || !text || text.length > COMMENT_MAX) return;
    setComments((items) => [{ author: name, text, ts: Date.now() }, ...items]);
    setCommentName("");
    setCommentText("");
  };

  const focusComment = () => {
    if (commentInputRef.current) {
      commentInputRef.current.focus();
      commentInputRef.current.scrollIntoView({ behavior: "smooth", block: "center" });
    }
  };

  const sortedComments = useMemo(() => {
    const arr = [...comments];
    arr.sort((a, b) => sortOrder === "newest" ? b.ts - a.ts : a.ts - b.ts);
    return arr;
  }, [comments, sortOrder]);

  const charCount = commentText.length;
  const overLimit = charCount > COMMENT_MAX;
  const canPost = commentName.trim() && commentText.trim() && !overLimit;

  return (
    <section className="article-engagement" aria-label="Article engagement">
      <div className="engage-row">
        <button
          className={`engage-action ${liked ? "active" : ""}`}
          type="button"
          onClick={toggleLike}
          aria-pressed={liked}
        >
          <Icon name="heart" size={16} />
          <span className="engage-count-n">{likes}</span>
          <span className="engage-label">{likes === 1 ? "like" : "likes"}</span>
        </button>

        <button className="engage-action" type="button" onClick={focusComment}>
          <Icon name="comment" size={16} />
          <span className="engage-count-n">{comments.length}</span>
          <span className="engage-label">{comments.length === 1 ? "comment" : "comments"}</span>
        </button>

        <div className="engage-share" ref={shareRef}>
          <button
            className={`engage-action ${shareOpen ? "active" : ""}`}
            type="button"
            onClick={() => setShareOpen((v) => !v)}
            aria-expanded={shareOpen}
            aria-haspopup="true"
          >
            <Icon name="send" size={16} />
            <span className="engage-label">Share</span>
          </button>
          {shareOpen && (
            <div className="share-pop" role="menu">
              <a className="share-pop-item" href={emailHref} role="menuitem">
                <Icon name="mail" size={15} /> Email
              </a>
              <a className="share-pop-item" href={whatsappHref} target="_blank" rel="noreferrer" role="menuitem">
                <Icon name="whatsapp" size={15} /> WhatsApp
              </a>
              <button className="share-pop-item" type="button" onClick={copyLink} role="menuitem">
                <Icon name="link" size={15} /> Copy link
              </button>
            </div>
          )}
        </div>

        {shareStatus && <span className="engage-status" role="status">{shareStatus}</span>}
      </div>

      <div className="comments" id={`comments-${article.id}`}>
        <header className="comments-head">
          <div>
            <h3 className="comments-title">Responses</h3>
            <p className="comments-sub">
              {comments.length === 0
                ? "Be the first to respond."
                : `${comments.length} ${comments.length === 1 ? "response" : "responses"} from readers.`}
            </p>
          </div>
          {comments.length > 1 && (
            <label className="comments-sort">
              <span className="sr-only">Sort</span>
              <select value={sortOrder} onChange={(e) => setSortOrder(e.target.value)}>
                <option value="newest">Newest first</option>
                <option value="oldest">Oldest first</option>
              </select>
            </label>
          )}
        </header>

        {comments.length > 0 && (
          <ul className="comment-list">
            {sortedComments.slice(0, visibleComments).map((comment, index) => {
              const initial = (comment.author || "•").trim().charAt(0).toUpperCase();
              return (
                <li className="comment-item" key={`${comment.author}-${comment.ts}-${index}`}>
                  <span className="comment-avatar" aria-hidden="true">{initial}</span>
                  <div className="comment-body">
                    <div className="comment-meta">
                      <span className="comment-author">{comment.author}</span>
                      <span className="comment-dot" aria-hidden="true">·</span>
                      <time className="comment-time">{timeAgo(comment.ts)}</time>
                    </div>
                    <p>{comment.text}</p>
                  </div>
                </li>
              );
            })}
          </ul>
        )}

        {sortedComments.length > visibleComments && (
          <div className="comment-more">
            <button
              type="button"
              className="comment-more-btn"
              onClick={() => setVisibleComments(v => v + COMMENT_PAGE)}
            >
              See more responses <span className="comment-more-count">
                ({sortedComments.length - visibleComments} more)
              </span>
            </button>
          </div>
        )}

        <form className="comment-form" onSubmit={addComment} aria-label="Leave a response">
          <div className="comment-form-title">
            {comments.length === 0 ? "Leave a response" : "Join the conversation"}
          </div>
          <div className="comment-form-grid">
            <span className="comment-avatar comment-avatar-form" aria-hidden="true">
              {(commentName.trim().charAt(0) || "•").toUpperCase()}
            </span>
            <div className="comment-form-fields">
              <input
                className="comment-name"
                type="text"
                placeholder="Your name"
                value={commentName}
                onChange={(e) => setCommentName(e.target.value)}
                aria-label="Your name"
                maxLength={60}
              />
              <textarea
                ref={commentInputRef}
                className={`comment-text ${overLimit ? "over-limit" : ""}`}
                placeholder="Share a thought, a memory, or a gentle correction…"
                value={commentText}
                onChange={(e) => setCommentText(e.target.value)}
                rows="3"
                aria-label="Your response"
                aria-invalid={overLimit}
                aria-describedby={`comment-counter-${article.id}`}
              />
              <div className="comment-form-foot">
                <span className="comment-form-hint">
                  Comments are read by the editors before they appear.
                </span>
                <div className="comment-form-actions">
                  <span
                    id={`comment-counter-${article.id}`}
                    className={`comment-counter ${overLimit ? "over" : charCount > COMMENT_MAX * 0.85 ? "warn" : ""}`}
                    aria-live="polite"
                  >
                    {charCount} / {COMMENT_MAX}
                  </span>
                  <button type="submit" className="comment-submit" disabled={!canPost}>
                    Post response
                  </button>
                </div>
              </div>
            </div>
          </div>
        </form>
      </div>
    </section>
  );
}

// ── ArticlePage — full-page article view (own route via hash)
function ArticlePage({ article, onBack, onOpen, onTag }) {
  useEffect(() => {
    window.scrollTo({ top: 0, behavior: "instant" });
  }, [article && article.id]);

  if (!article) return null;

  const articleTags = article.tags && article.tags.length
    ? article.tags
    : ["heritage", "oral-history"];

  // Related: same category, different article
  const related = window.ARTICLES
    .filter(a => a.category === article.category && a.id !== article.id)
    .slice(0, 3);

  return (
    <div className="article-page" data-screen-label={`Article — ${article.title}`}>
      <div className="article-topbar">
        <button className="back-link" onClick={onBack}>
          <span className="arr-left">←</span>
          <span>Back to the archive</span>
        </button>
        <div className="article-crumbs">
          <span>{labelFor(article.category)}</span>
        </div>
      </div>

      <article className="article">
        <header className="article-head">
          <div className="eyebrow"><span className="dot">◆</span> {labelFor(article.category)}</div>
          <h1 className="article-title">{article.title}</h1>
          <p className="article-sub">{article.subtitle}</p>
          <div className="byline byline-center">
            <span>By <em>{article.author}</em></span>
            <span className="byline-dot">·</span>
            <span>{article.date}</span>
            <span className="byline-dot">·</span>
            <span>{article.readTime}</span>
          </div>
          <Rule />
        </header>

        <Plate
          caption={article.imageCaption || `Illustrative plate — "${article.title}"`}
          tone="deep"
          ratio="16 / 9"
          className="article-plate"
          src={(typeof glResolveImage === "function" && glResolveImage(article)) || article.image}
          alt={article.imageAlt || article.title}
        />

        <div
          className="article-body"
          dangerouslySetInnerHTML={{ __html: article.body }}
        />
        {article.pull && <PullQuote text={article.pull} />}
        <div className="article-body-footer">
          <Asterism />
        </div>

        <footer className="article-foot">
          <div className="article-tags-row">
            <span className="article-tags-lbl">Themes</span>
            <div className="article-tags">
              {articleTags.map((tag) => (
                <button
                  key={tag}
                  type="button"
                  className="tag tag-btn"
                  onClick={() => onTag && onTag(tag)}
                  aria-label={`Filter by ${tag}`}
                >
                  {tag}
                </button>
              ))}
            </div>
          </div>
          <ArticleEngagement article={article} />
        </footer>
      </article>

      {related.length > 0 && (
        <section className="related">
          <SectionHead
            eyebrow="Continue reading"
            title={`More from ${labelFor(article.category)}`}
          />
          <div className="related-grid">
            {related.map(a => (
              <ArticleCard key={a.id} article={a} size="medium" onOpen={onOpen} />
            ))}
          </div>
        </section>
      )}
    </div>
  );
}

// ── Gallery — six-up media grid
function Gallery() {
  return (
    <section className="gallery">
      <SectionHead
        eyebrow="The Plates"
        title="Visual Gallery"
        kicker="Photographs, embroidery details, manuscript pages — assembled slowly, the way collections are."
      />
      <div className="gallery-grid">
        {window.MEDIA_GALLERY.map((m, i) => (
          <Plate
            key={m.id}
            caption={m.caption}
            tone={m.tone}
            ratio="1 / 1"
            className={`gal-item gal-${i}`}
            src={m.image}
            alt={m.imageAlt || m.caption}
          />
        ))}
      </div>
    </section>
  );
}

// ── Footer
function SiteFoot({ onSubmitStory }) {
  const navCats = window.CATEGORIES.filter((c) => c.id !== "home" && c.id !== "podcast");
  const goCat = (id) => { window.location.hash = `#category/${id}`; };
  const goHome = () => { window.location.hash = ""; };
  const goPodcast = () => { window.location.hash = "#category/podcast"; };

  return (
    <footer className="site-foot">
      <Motif size={320} opacity={0.06} rotate={-14} bottom="-60px" right="-80px" tone="deep" />

      <div className="foot-inner">
        <div className="foot-brand">
          <a href="#" className="foot-mark" onClick={(e) => { e.preventDefault(); goHome(); }} aria-label="Home">
            <Mark size={52} />
          </a>
          <h4 className="foot-brand-name">Stories of Dawoodi Bohra Women</h4>
          <p className="foot-brand-tag">
            An editorial archive — written by women, for everyone.
          </p>
        </div>

        <Rule />

        <div className="foot-grid">
          <nav className="foot-col" aria-label="Read sections">
            <h4>Read</h4>
            <ul className="foot-nav">
              {navCats.map((c) => (
                <li key={c.id}>
                  <a href={`#category/${c.id}`} onClick={(e) => { e.preventDefault(); goCat(c.id); }}>
                    {c.label}
                  </a>
                </li>
              ))}
            </ul>
          </nav>

          <nav className="foot-col" aria-label="Listen and submit">
            <h4>Listen &amp; Contribute</h4>
            <ul className="foot-nav">
              <li>
                <a href="#category/podcast" onClick={(e) => { e.preventDefault(); goPodcast(); }}>
                  The Threshold — podcast
                </a>
              </li>
              <li>
                <a
                  href="#submit-story"
                  onClick={(e) => {
                    if (onSubmitStory) { e.preventDefault(); onSubmitStory(); }
                  }}
                >
                  Submit your story
                </a>
              </li>
              <li>
                <a href="#" onClick={(e) => { e.preventDefault(); goHome(); }}>
                  About the project
                </a>
              </li>
            </ul>
          </nav>

          <div className="foot-col foot-col-letters">
            <h4>The Letters</h4>
            <p>A monthly dispatch — one essay, two plates, no advertising.</p>
            <form className="subscribe" onSubmit={(e) => e.preventDefault()}>
              <input type="email" placeholder="you@example.com" aria-label="Email" />
              <button type="submit">Subscribe</button>
            </form>
          </div>
        </div>

        <div className="foot-base">
          <span>© 2026 Stories of Dawoodi Bohra Women</span>
          <span className="foot-base-dot" aria-hidden="true">·</span>
          <span>An independent editorial project</span>
        </div>
      </div>
    </footer>
  );
}

// ── Podcast: cover art (square, with brand mark + episode #)
function PodcastCover({ size = "medium", number, tone = "deep", image }) {
  const px = size === "large" ? 240 : size === "small" ? 96 : 160;
  const coverImage = image || null;
  return (
    <div
      className={`pod-cover pod-cover-${size}`}
      style={{
        width: "100%",
        aspectRatio: "1 / 1",
        background:
          tone === "deep"
            ? `linear-gradient(140deg, color-mix(in oklab, var(--accent) 88%, black), var(--accent))`
            : `linear-gradient(140deg, var(--paper-warm), color-mix(in oklab, var(--gold) 22%, var(--paper-warm)))`,
        position: "relative",
        overflow: "hidden",
        border: "0.5px solid var(--rule)"
      }}
    >
      {coverImage && (
        <img
          src={coverImage}
          alt=""
          aria-hidden="true"
          loading="lazy"
          decoding="async"
          style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover" }}
        />
      )}
      <span style={{
        position: "absolute", inset: 0,
        background: "linear-gradient(140deg, rgba(31,23,20,.36), rgba(122,59,63,.18)), repeating-linear-gradient(135deg, rgba(192,148,99,.10) 0 1px, transparent 1px 18px)",
        opacity: coverImage ? 0.72 : 0.6
      }}></span>
      <Mark size={Math.round(px * 0.5)} tone="gold"
        style={{
          position: "absolute", left: "50%", top: "48%",
          transform: "translate(-50%, -50%)",
          opacity: coverImage ? 0.58 : 0.85
        }} />
      {number !== undefined && (
        <span style={{
          position: "absolute", left: 12, top: 10,
          fontFamily: "var(--font-mono)",
          fontSize: 10, letterSpacing: "0.16em",
          color: tone === "deep" ? "color-mix(in oklab, var(--gold) 80%, white)" : "var(--gold-deep)",
          textTransform: "uppercase"
        }}>Ep. {String(number).padStart(2, "0")}</span>
      )}
      <span style={{
        position: "absolute", right: 12, bottom: 10,
        fontFamily: "var(--font-display)",
        fontStyle: "italic",
        fontSize: 13,
        color: tone === "deep" ? "color-mix(in oklab, var(--gold) 70%, white)" : "var(--gold-deep)"
      }}>The Threshold</span>
    </div>
  );
}

// ── Round play button used on episode cards
function PlayButton({ size = 44, onClick, label = "Play episode" }) {
  return (
    <button className="play-btn" onClick={onClick} aria-label={label}
      style={{ width: size, height: size }}>
      <svg width={size * 0.36} height={size * 0.36} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
        <path d="M8 5v14l11-7z"/>
      </svg>
    </button>
  );
}

// ── A single podcast-episode row (large = featured)
function EpisodeRow({ ep, size = "medium", onPlay }) {
  return (
    <article className={`ep-row ep-row-${size}`}>
      <div className="ep-cover">
        <PodcastCover
          number={ep.number}
          size={size === "large" ? "large" : "medium"}
          tone={size === "large" ? "deep" : "soft"}
          image={ep.coverImage}
        />
      </div>
      <div className="ep-body">
        <div className="eyebrow eyebrow-sm">
          Episode {String(ep.number).padStart(2, "0")} · {ep.durationStr}
        </div>
        <h3 className="ep-title">{ep.title}</h3>
        <p className="ep-sub">{ep.subtitle}</p>
        <p className="ep-desc">{ep.description}</p>
        <div className="ep-meta">
          <span>With <em>{ep.guest}</em></span>
          <span className="byline-dot">·</span>
          <span>{ep.location}</span>
          <span className="byline-dot">·</span>
          <span>{ep.date}</span>
        </div>
        <div className="ep-actions">
          <PlayButton onClick={() => onPlay && onPlay(ep)} />
          <span className="ep-listen-on">Available on this site only</span>
        </div>
      </div>
    </article>
  );
}

// ── Compact podcast strip for the home page (sits between gallery and newsletter)
function PodcastStrip({ onGoto, onPlay }) {
  const latest = window.PODCAST_EPISODES[0];
  const more = window.PODCAST_EPISODES.slice(1, 4);
  if (!latest) return null;
  return (
    <section className="pod-strip" data-screen-label="Podcast strip">
      <Motif size={360} opacity={0.07} rotate={-14} top="-80px" right="-60px" tone="accent" />
      <div className="pod-strip-inner">
        <div className="pod-strip-head">
          <div className="eyebrow"><span className="dot">◆</span> Listen</div>
          <h2 className="pod-strip-title">
            <em>The Threshold</em> — a podcast
          </h2>
          <p className="pod-strip-kicker">{window.PODCAST_META.tagline}</p>
        </div>

        <div className="pod-strip-grid">
          <div className="pod-strip-feature">
            <PodcastCover number={latest.number} size="large" tone="deep" image={latest.coverImage} />
            <div className="pod-strip-meta">
              <div className="eyebrow eyebrow-sm">Latest · Ep. {String(latest.number).padStart(2, "0")} · {latest.durationStr}</div>
              <h3 className="pod-strip-feat-title">{latest.title}</h3>
              <p className="pod-strip-feat-sub">{latest.subtitle}</p>
              <div className="pod-strip-feat-actions">
                <PlayButton onClick={() => onPlay && onPlay(latest)} />
                <button className="btn-read" onClick={onGoto}>
                  All episodes <span className="arr">→</span>
                </button>
              </div>
            </div>
          </div>

          <ul className="pod-strip-list">
            {more.map((ep) => (
              <li key={ep.id} className="pod-strip-item" onClick={() => onPlay && onPlay(ep)}>
                <span className="pod-mini-num">{String(ep.number).padStart(2, "0")}</span>
                <span className="pod-mini-text">
                  <span className="pod-mini-title">{ep.title}</span>
                  <span className="pod-mini-sub">{ep.guest} · {ep.durationStr}</span>
                </span>
                <PlayButton size={36} onClick={() => onPlay && onPlay(ep)} />
              </li>
            ))}
          </ul>
        </div>
      </div>
    </section>
  );
}

// ── Full podcast page (when activeCat === "podcast")
function PodcastPage({ onPlay }) {
  const [featured, ...rest] = window.PODCAST_EPISODES;
  if (!featured) return <div className="empty">No episodes yet.</div>;
  return (
    <div className="pod-page" data-screen-label="Podcast">
      <header className="pod-page-head">
        <Motif size={420} opacity={0.06} rotate={16} top="-100px" right="-120px" />
        <Motif size={180} opacity={0.10} rotate={-22} bottom="-40px" left="-30px" tone="deep" />
        <div className="pod-page-head-inner">
          <div className="eyebrow"><span className="dot">◆</span> A monthly conversation</div>
          <h1 className="pod-page-title"><em>The Threshold</em></h1>
          <p className="pod-page-tag">{window.PODCAST_META.tagline}</p>
          <p className="pod-page-intro">{window.PODCAST_META.intro}</p>
          <div className="pod-page-meta">
            <span>Hosted by <em>{window.PODCAST_META.hostedBy}</em></span>
            <span className="byline-dot">·</span>
            <span>{window.PODCAST_EPISODES.length} episodes</span>
          </div>
          <div className="pod-page-platforms">
            <span className="ep-listen-on">Listen here on the site</span>
          </div>
        </div>
      </header>

      <Rule wide />

      <section className="pod-list">
        <SectionHead eyebrow="Latest" title="The most recent episode" />
        <div className="pod-list-inner">
          <EpisodeRow ep={featured} size="large" onPlay={onPlay} />
        </div>
        <Asterism />
        <SectionHead eyebrow="The Archive" title="All episodes" />
        <div className="pod-list-inner">
          {rest.map((ep) => (
            <EpisodeRow key={ep.id} ep={ep} size="medium" onPlay={onPlay} />
          ))}
        </div>
      </section>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
//   GUL-LALA LANDING  (Women's blog tulip redesign)
// ─────────────────────────────────────────────────────────────────────────────

const GL_SECTION_COLORS = {
  culture:    "#E4A9A9",
  marriage:   "#DE9E93",
  motherhood: "#E5C79A",
  household:  "#A9BC98",
  faith:      "#9FB3C4",
  travel:     "#C0A3BE",
  work:       "#C98D82",
};

function TulipIcon({ color = "#A8555E", size = 17, className = "" }) {
  return (
    <svg viewBox="0 0 24 24" width={size} height={size} aria-hidden="true"
         className={className} style={{ flexShrink: 0 }}>
      <ellipse fill={color} cx="12" cy="8.2" rx="3" ry="5"/>
      <ellipse fill={color} cx="8.4" cy="9.6" rx="2.7" ry="4.3" transform="rotate(-14 8.4 9.6)"/>
      <ellipse fill={color} cx="15.6" cy="9.6" rx="2.7" ry="4.3" transform="rotate(14 15.6 9.6)"/>
      <path fill={color} d="M6 10.4h12v1.3c0 3.2-2.7 5.3-6 5.3s-6-2.1-6-5.3z"/>
      <rect fill="#8FA382" x="11.35" y="16.6" width="1.3" height="5.2" rx="0.65"/>
      <path fill="#8FA382" d="M11.6 21.2c-2.8-.5-4.7-2.2-5.1-4.5 2.7.2 4.6 1.8 5.1 4.5z"/>
      <path fill="#8FA382" d="M12.4 21.2c2.8-.5 4.7-2.2 5.1-4.5-2.7.2-4.6 1.8-5.1 4.5z"/>
    </svg>
  );
}

// ── Gul-Lala Nav — mobile-first: compact bar with brand, search-toggle,
//    hamburger. Search opens as a bar; menu opens as a drawer with categories.
function GLNav({ query, onSearch, onClearSearch, onOpenCategory, activeCat, onSubmit }) {
  const [scrolled, setScrolled] = useState(false);
  const [menuOpen, setMenuOpen] = useState(false);
  const [drawerOpen, setDrawerOpen] = useState(false);
  const [searchOpen, setSearchOpen] = useState(!!query);
  const searchInputRef = useRef(null);
  const closeTimer = useRef(null);

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 8);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  // Focus the search input when it becomes visible
  useEffect(() => {
    if (searchOpen && searchInputRef.current) {
      const el = searchInputRef.current;
      setTimeout(() => { el.focus(); el.select && el.select(); }, 20);
    }
  }, [searchOpen]);

  // Body scroll-lock when the mobile drawer is open
  useEffect(() => {
    if (!drawerOpen) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    const onKey = (e) => { if (e.key === "Escape") setDrawerOpen(false); };
    document.addEventListener("keydown", onKey);
    return () => {
      document.body.style.overflow = prev;
      document.removeEventListener("keydown", onKey);
    };
  }, [drawerOpen]);

  const openDropdown = () => { clearTimeout(closeTimer.current); setMenuOpen(true); };
  const closeDropdown = () => {
    clearTimeout(closeTimer.current);
    closeTimer.current = setTimeout(() => setMenuOpen(false), 250);
  };

  const cats = window.CATEGORIES.filter(c => c.id !== "home" && c.id !== "podcast");
  const goHome     = () => { setDrawerOpen(false); setSearchOpen(false); onOpenCategory("home"); };
  const goCategory = (id) => { setDrawerOpen(false); setMenuOpen(false); setSearchOpen(false); onOpenCategory(id); };
  const openSubmit = () => { setDrawerOpen(false); setSearchOpen(false); onSubmit && onSubmit(); };
  // Toggling the search bar preserves the current query — user might
  // just want to hide the bar without losing their filter.
  const openSearch  = () => { setDrawerOpen(false); setSearchOpen(true); };
  const closeSearch = () => setSearchOpen(false);
  const toggleSearch = () => searchOpen ? closeSearch() : openSearch();
  const openDrawer  = () => { setSearchOpen(false); setDrawerOpen(true); };
  const closeDrawer = () => setDrawerOpen(false);
  const toggleDrawer = () => drawerOpen ? closeDrawer() : openDrawer();

  return (
    <header className={`gl-nav ${scrolled ? "scrolled" : ""} ${searchOpen ? "search-active" : ""}`}>
      <nav className="gl-nav-inner" aria-label="Primary" data-search={searchOpen ? "open" : "closed"}>
        <a className="gl-brand" href="#" onClick={(e) => { e.preventDefault(); goHome(); }}>
          <span className="gl-brand-urdu">گل لالہ</span>
          <span className="gl-brand-latin">GUL-LALA</span>
        </a>

        {/* Desktop links */}
        <div className="gl-nav-links">
          <div className="gl-sections"
               onMouseEnter={openDropdown} onMouseLeave={closeDropdown}>
            <button className={`gl-link ${activeCat && !["home","about","podcast"].includes(activeCat) ? "active" : ""}`}
                    onClick={() => setMenuOpen(o => !o)}
                    aria-expanded={menuOpen}>
              Sections <span className="gl-caret">▾</span>
            </button>
            {menuOpen && (
              <div className="gl-menu">
                {cats.map(c => (
                  <a key={c.id} href={`#category/${c.id}`}
                     onClick={(e) => { e.preventDefault(); goCategory(c.id); }}>
                    <TulipIcon color={GL_SECTION_COLORS[c.id] || "#C9A0A6"} size={17} />
                    {c.label}
                  </a>
                ))}
              </div>
            )}
          </div>
          <a className={`gl-link ${activeCat === "podcast" ? "active" : ""}`}
             href="#category/podcast"
             onClick={(e) => { e.preventDefault(); goCategory("podcast"); }}>
            Podcast
          </a>
          <a className={`gl-link ${activeCat === "about" ? "active" : ""}`}
             href="#about"
             onClick={(e) => { e.preventDefault(); goCategory("about"); }}>
            About
          </a>
        </div>

        {/* Desktop search */}
        <label className="gl-search gl-search-desktop" role="search">
          <Icon name="search" size={14} className="gl-search-icon" />
          <input
            type="text"
            placeholder="Search stories, sections, episodes…"
            value={query}
            onChange={(e) => onSearch(e.target.value)}
            aria-label="Search the site"
          />
          {query && (
            <button className="gl-search-clear" type="button" onClick={onClearSearch} aria-label="Clear">
              <Icon name="close" size={12} />
            </button>
          )}
        </label>

        {/* Desktop CTA */}
        <button className="gl-cta gl-cta-desktop" type="button" onClick={openSubmit}>
          Submit a Story
        </button>

        {/* Mobile: search toggle (icon that will animate outward into the bar) */}
        <button
          className="gl-nav-icon gl-nav-icon-search"
          type="button"
          aria-label="Search"
          onClick={openSearch}
        >
          <Icon name="search" size={18} />
        </button>

        {/* Mobile: hamburger */}
        <button
          className={`gl-nav-icon gl-nav-toggle ${drawerOpen ? "open" : ""}`}
          type="button"
          aria-label={drawerOpen ? "Close menu" : "Menu"}
          aria-expanded={drawerOpen}
          onClick={toggleDrawer}
        >
          <span></span><span></span><span></span>
        </button>

        {/* Mobile: search bar — always in the DOM so it can animate. It sits
            on top of the top row and expands from the search-icon position.
            Single X button: clears the query if there's text, otherwise
            closes the whole bar. */}
        <label
          className="gl-search gl-search-mobile"
          role="search"
          aria-hidden={!searchOpen}
        >
          <Icon name="search" size={16} className="gl-search-icon" />
          <input
            ref={searchInputRef}
            type="text"
            inputMode="search"
            enterKeyHint="search"
            placeholder="Search stories…"
            value={query}
            onChange={(e) => onSearch(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") e.currentTarget.blur(); }}
            aria-label="Search the site"
            autoComplete="off"
            autoCorrect="off"
            spellCheck="false"
            tabIndex={searchOpen ? 0 : -1}
          />
          <button
            className="gl-search-cancel"
            type="button"
            onClick={query ? () => onSearch("") : closeSearch}
            aria-label={query ? "Clear search" : "Close search"}
            tabIndex={searchOpen ? 0 : -1}
          >
            <Icon name="close" size={16} />
          </button>
        </label>
      </nav>

      {/* Mobile drawer */}
      {drawerOpen && (
        <>
          <div className="gl-drawer-backdrop" onClick={closeDrawer} aria-hidden="true"></div>
          <aside className="gl-drawer" role="dialog" aria-label="Site navigation" aria-modal="true">
            <div className="gl-drawer-head">
              <span className="gl-drawer-eyebrow">Sections</span>
              <button className="gl-drawer-close" type="button" onClick={closeDrawer} aria-label="Close menu">
                <Icon name="close" size={18} />
              </button>
            </div>
            <ul className="gl-drawer-list">
              {cats.map(c => (
                <li key={c.id}>
                  <a href={`#category/${c.id}`}
                     className={activeCat === c.id ? "active" : ""}
                     onClick={(e) => { e.preventDefault(); goCategory(c.id); }}>
                    <TulipIcon color={GL_SECTION_COLORS[c.id] || "#C9A0A6"} size={22} />
                    <span>{c.label}</span>
                  </a>
                </li>
              ))}
            </ul>
            <div className="gl-drawer-sep"></div>
            <ul className="gl-drawer-list">
              <li>
                <a href="#category/podcast"
                   className={activeCat === "podcast" ? "active" : ""}
                   onClick={(e) => { e.preventDefault(); goCategory("podcast"); }}>
                  <span className="gl-drawer-dot" style={{ background: "#A8555E" }}></span>
                  <span>Podcast</span>
                </a>
              </li>
              <li>
                <a href="#about"
                   className={activeCat === "about" ? "active" : ""}
                   onClick={(e) => { e.preventDefault(); goCategory("about"); }}>
                  <span className="gl-drawer-dot" style={{ background: "#C9A0A6" }}></span>
                  <span>About Gul-Lala</span>
                </a>
              </li>
            </ul>
            <button className="gl-cta gl-drawer-cta" type="button" onClick={openSubmit}>
              Submit a Story
            </button>
          </aside>
        </>
      )}
    </header>
  );
}

// ── Gul-Lala Hero — rotating slides, tulip image, dot navigation
// Local image overrides — always win over the CMS image for a matching slug.
// Use for stories where we ship a curated photograph with the site itself.
const GL_IMAGE_OVERRIDES = {
  "threads-of-heritage": "assets/threads-of-heritage.jpg",
};
function glResolveImage(article) {
  if (!article) return null;
  const key = article.slug || article.id;
  if (key && GL_IMAGE_OVERRIDES[key]) return GL_IMAGE_OVERRIDES[key];
  return article.image || null;
}

function GLHero({ slides, onOpenStories }) {
  const [slide, setSlide] = useState(0);
  const rotate = useRef(null);
  const touchStartX = useRef(null);
  const touchStartY = useRef(null);

  const restart = () => {
    if (rotate.current) clearInterval(rotate.current);
    rotate.current = setInterval(() => setSlide(s => (s + 1) % slides.length), 7000);
  };

  useEffect(() => {
    restart();
    return () => rotate.current && clearInterval(rotate.current);
  }, [slides.length]);

  const pick = (i) => { restart(); setSlide(i); };
  const next = () => pick((slide + 1) % slides.length);
  const prev = () => pick((slide - 1 + slides.length) % slides.length);

  const onTouchStart = (e) => {
    const t = e.touches[0];
    touchStartX.current = t.clientX;
    touchStartY.current = t.clientY;
  };
  const onTouchEnd = (e) => {
    if (touchStartX.current == null) return;
    const t = e.changedTouches[0];
    const dx = t.clientX - touchStartX.current;
    const dy = t.clientY - touchStartY.current;
    touchStartX.current = null;
    touchStartY.current = null;
    if (Math.abs(dx) > 44 && Math.abs(dx) > Math.abs(dy)) {
      if (dx < 0) next(); else prev();
    }
  };

  const cur = slides[slide] || slides[0];
  if (!cur) return null;

  return (
    <header
      className="gl-hero"
      id="top"
      onTouchStart={onTouchStart}
      onTouchEnd={onTouchEnd}
    >
      {/* Full-width watercolor tulip strip anchored to the bottom of the hero
          as a very soft brand backdrop. Fades to transparent at the top via
          a CSS mask so the type still reads cleanly. */}
      <img
        className="gl-hero-strip"
        src="assets/tulip/hero-tulip-strip.png"
        alt=""
        aria-hidden="true"
        loading="lazy"
      />
      <img
        className="gl-hero-flower"
        src="assets/tulip/hero-tulip.png"
        alt=""
        aria-hidden="true"
      />
      <div className="gl-hero-copy">
        <span className="gl-hero-kicker">{cur.kicker}</span>
        <h1 className="gl-hero-title">{cur.title}</h1>
        <p className="gl-hero-body">{cur.body}</p>
        <div className="gl-hero-actions">
          <a className="gl-btn-primary"
             href={cur.href}
             onClick={cur.onClick ? (e) => { e.preventDefault(); cur.onClick(); } : undefined}>
            Read this story
          </a>
        </div>
        <div className="gl-hero-dots" role="tablist" aria-label="Featured story">
          {slides.map((s, i) => (
            <button key={i}
                    className={`gl-dot ${i === slide ? "active" : ""}`}
                    onClick={() => pick(i)}
                    role="tab"
                    aria-selected={i === slide}
                    aria-label={s.dotLabel || `Slide ${i + 1}`}></button>
          ))}
        </div>
      </div>
      <div className="gl-hero-art">
        <div className="gl-hero-halo"></div>
        <a className="gl-hero-frame"
           href={cur.href}
           onClick={cur.onClick ? (e) => { e.preventDefault(); cur.onClick(); } : undefined}
           aria-label={cur.title}>
          {slides.map((s, i) => (
            <img key={s.imgSrc + i}
                 className="gl-hero-img"
                 src={s.imgSrc}
                 alt={s.imgAlt || ""}
                 loading={i === 0 ? "eager" : "lazy"}
                 style={{ opacity: i === slide ? 1 : 0 }} />
          ))}
        </a>
      </div>
    </header>
  );
}

// ── Gul-Lala About band — bilingual gradient
function GLAbout() {
  return (
    <section className="gl-about" id="about">
      <div className="gl-about-inner">
        <span className="gl-about-urdu">گل لالہ</span>
        <h2 className="gl-about-title">gul · flower — lala · tulip</h2>
        <p className="gl-about-body">
          Gul-lala is the tulip — the flower Persian and Urdu poets kept coming back
          to, because nothing else said it quite as well. Colour that stops you where
          you stand. A bloom that needs no ornament to be remarkable.
        </p>
        <p className="gl-about-body">
          That&rsquo;s the idea here. Bohra women have been building, teaching,
          healing, trading, leading and holding things together for generations —
          usually without anyone writing it down. Not because the work was small,
          but because it was so familiar we forgot to look.
        </p>
      </div>
    </section>
  );
}

// ── Gul-Lala Stories — filter chips + search + card grid
// Fallback tulip images cycled by article slug — keeps every card visual
// even when the CMS entry has no featured_image.
const GL_TULIP_FALLBACKS = [
  "assets/tulip/tulip-coral.png",
  "assets/tulip/tulip-pink-soft.png",
  "assets/tulip/tulip-magenta.png",
  "assets/tulip/tulip-red-pointed.png",
  "assets/tulip/tulip-purple-stripe.png",
  "assets/tulip/tulip-pale-pink.png",
  "assets/tulip/tulip-red-short.png",
  "assets/tulip/tulip-bud.png",
];
function glFallbackImage(slug) {
  const s = String(slug || "");
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
  return GL_TULIP_FALLBACKS[h % GL_TULIP_FALLBACKS.length];
}

const GL_STORIES_PAGE = 5;

function GLStories({ activeCat, tagFilter, query, onOpenCategory, onOpen, onSearch, filterOnly }) {
  const cats = window.CATEGORIES.filter(c => c.id !== "home" && c.id !== "podcast");
  const chipDot = (id) => (id === "all" ? "#8FA382" : (GL_SECTION_COLORS[id] || "#C9A0A6"));
  const activeChip = tagFilter ? null : activeCat;

  const q = (query || "").trim().toLowerCase();
  const list = window.ARTICLES.filter(a => {
    const matchCat = activeCat === "home" || a.category === activeCat;
    const matchTag = !tagFilter || (a.tags || []).includes(tagFilter);
    if (!q) return matchCat && matchTag;
    const bodyStr = Array.isArray(a.body)
      ? a.body.join(" ")
      : (a.body || "").replace(/<[^>]*>/g, " ");
    const hay = [
      a.title, a.subtitle, a.excerpt, bodyStr,
      a.author || "",
      labelFor(a.category), a.category, a.pull || "",
      ...(a.tags || [])
    ].join(" ").toLowerCase();
    return matchCat && matchTag && hay.includes(q);
  });

  // Show-more: keep track of how many cards are visible on the home view;
  // filtered views (search / tag / category) show everything.
  const [visibleCount, setVisibleCount] = useState(GL_STORIES_PAGE);
  useEffect(() => { setVisibleCount(GL_STORIES_PAGE); }, [activeCat, tagFilter, q]);

  const count = list.length;
  const paginate = !filterOnly;
  const displayList = paginate ? list.slice(0, visibleCount) : list;
  const hasMore = paginate && count > displayList.length;
  const remaining = count - displayList.length;

  const activeCatDef = cats.find(c => c.id === activeCat);
  let heading = "All Stories";
  let subline = null;
  if (q) {
    heading = `Search: “${query}”`;
    subline = null;
  } else if (tagFilter) {
    heading = `Theme: #${tagFilter}`;
    subline = null;
  } else if (activeCatDef) {
    heading = activeCatDef.label;
    subline = activeCatDef.intro || activeCatDef.description || "";
  }

  return (
    <section className={`gl-stories ${filterOnly ? "filter-only" : ""}`} id="stories">
      <div className="gl-stories-head">
        <div className="gl-stories-head-title">
          <h2>{heading}</h2>
          {subline && <p className="gl-stories-subline">{subline}</p>}
        </div>
        <span className="gl-stories-count">
          {count} {count === 1 ? "story" : "stories"}
        </span>
      </div>

      {/* Filter chrome (in-section search + section chips) only on the
          landing home view. When the user is already searching / filtering,
          the whole page is the results — no need for a second search box
          or chips row. */}
      {!filterOnly && (
        <div className="gl-stories-filters">
          <div className="gl-chip-row">
            <button
              className={`gl-chip ${activeChip === "home" ? "active" : ""}`}
              type="button"
              onClick={() => onOpenCategory("home")}
            >
              <TulipIcon color={chipDot("all")} />
              All
            </button>
            {cats.map(c => (
              <button
                key={c.id}
                className={`gl-chip ${activeChip === c.id ? "active" : ""}`}
                type="button"
                onClick={() => onOpenCategory(c.id)}
              >
                <TulipIcon color={chipDot(c.id)} />
                {c.label}
              </button>
            ))}
          </div>
        </div>
      )}

      {count === 0 ? (
        <p className="gl-stories-empty">No stories match — try another word, or clear the filter.</p>
      ) : (
        <>
          <div className="gl-story-grid">
            {displayList.map(a => {
              const bandColor = GL_SECTION_COLORS[a.category] || "#C9A0A6";
              const img = glResolveImage(a) || glFallbackImage(a.slug || a.id);
              const commentCount = Array.isArray(a.comments) ? a.comments.length : 0;
              return (
                <article
                  key={a.id}
                  className="gl-story-card"
                  onClick={() => onOpen(a)}
                >
                  <div className="gl-story-media" style={{
                    background: `linear-gradient(145deg, ${bandColor}33 0%, #FDFBF6 100%)`
                  }}>
                    <img src={img} alt={a.imageAlt || a.title} loading="lazy" />
                  </div>
                  <div className="gl-story-body">
                    <span className="gl-story-eyebrow">{labelFor(a.category)}</span>
                    <h3>{a.title}</h3>
                    <p>{a.excerpt || a.subtitle || ""}</p>
                    <div className="gl-story-foot">
                      <span className="gl-story-more">Read story →</span>
                      <div className="gl-story-stats">
                        <span>♥ {a.likes || 0}</span>
                        <span>✎ {commentCount}</span>
                      </div>
                    </div>
                  </div>
                </article>
              );
            })}
          </div>
          {hasMore && (
            <div className="gl-stories-more">
              <button
                type="button"
                className="gl-btn-ghost gl-stories-more-btn"
                onClick={() => setVisibleCount(v => v + GL_STORIES_PAGE)}
              >
                Show more <span className="gl-stories-more-count">({remaining} more)</span>
              </button>
            </div>
          )}
        </>
      )}
    </section>
  );
}

// ── Gul-Lala Podcast band — audio players with progress
function GLPodcast({ onGoto }) {
  const episodes = (window.PODCAST_EPISODES || []).slice(0, 3);
  const [playingId, setPlayingId] = useState(null);
  const [elapsed, setElapsed] = useState(0);
  const [likes, setLikes] = useState({});
  const [shared, setShared] = useState(null);
  const ticker = useRef(null);

  useEffect(() => {
    if (playingId != null) {
      ticker.current = setInterval(() => setElapsed(s => s + 1), 1000);
      return () => clearInterval(ticker.current);
    }
  }, [playingId]);

  useEffect(() => () => clearInterval(ticker.current), []);

  const fmt = (sec) => {
    const m = Math.floor(sec / 60);
    const s = Math.max(0, Math.floor(sec % 60));
    return m + ":" + String(s).padStart(2, "0");
  };

  const toggle = (ep) => {
    if (playingId === ep.number) {
      setPlayingId(null);
    } else {
      setPlayingId(ep.number);
      setElapsed(0);
    }
  };

  const share = (ep) => {
    const url = window.location.href.split("#")[0] + "#category/podcast";
    if (navigator.clipboard) {
      navigator.clipboard.writeText(`${ep.title} — ${url}`).catch(() => {});
    }
    setShared(ep.number);
    setTimeout(() => setShared(null), 1800);
  };

  if (episodes.length === 0) {
    return (
      <section className="gl-podcast">
        <div className="gl-podcast-inner">
          <div className="gl-podcast-copy">
            <span className="gl-podcast-eyebrow">The Gul-Lala Podcast</span>
            <h2>Voices from the majlis, the kitchen, and the road.</h2>
            <p>New episodes coming soon.</p>
          </div>
        </div>
      </section>
    );
  }

  const latest = episodes[0];

  return (
    <section className="gl-podcast" id="podcast">
      <div className="gl-podcast-inner">
        <div className="gl-podcast-copy">
          <span className="gl-podcast-eyebrow">The Gul-Lala Podcast</span>
          <h2>Voices from the majlis, the kitchen, and the road.</h2>
          <p>
            Conversations with Dawoodi Bohra women — on faith, marriage,
            motherhood, and everything carried in between.
            {window.PODCAST_META && window.PODCAST_META.tagline
              ? ` ${window.PODCAST_META.tagline}` : " A new episode every other Friday."}
          </p>
          <div className="gl-podcast-actions">
            <button className="gl-btn-primary" type="button"
                    onClick={() => toggle(latest)}>
              {playingId === latest.number ? "Now Playing…" : "Listen Now"}
            </button>
            <button className="gl-btn-ghost" type="button" onClick={onGoto}>
              All Episodes
            </button>
          </div>
        </div>

        <div className="gl-podcast-list">
          {episodes.map(ep => {
            const active = playingId === ep.number;
            const totalSec = 34 * 60; // rough default; could parse ep.durationStr
            const pct = active ? Math.min(100, (elapsed / totalSec) * 100) : 0;
            const liked = !!likes[ep.number];
            const color = ep.number % 3 === 0 ? "#A9BC98" : (ep.number % 2 === 0 ? "#A8555E" : "#E4A9A9");
            return (
              <div key={ep.number} className="gl-ep">
                <div className="gl-ep-head">
                  <button className="gl-ep-play"
                          style={{ background: color }}
                          onClick={() => toggle(ep)}
                          aria-label={(active ? "Pause " : "Play ") + ep.title}>
                    {active ? "❚❚" : "▶"}
                  </button>
                  <div className="gl-ep-text">
                    <span className="gl-ep-title">{ep.title}</span>
                    <span className="gl-ep-meta">{ep.guest || ep.date} · {ep.durationStr || ""}</span>
                  </div>
                  <div className="gl-ep-actions">
                    <button className={`gl-ep-like ${liked ? "on" : ""}`}
                            type="button"
                            onClick={() => setLikes(x => ({ ...x, [ep.number]: !x[ep.number] }))}
                            aria-label="Like episode">
                      {liked ? "♥" : "♡"}
                    </button>
                    <button className="gl-ep-share" type="button"
                            onClick={() => share(ep)}
                            aria-label="Share episode">
                      {shared === ep.number ? "Copied ✓" : "↗ Share"}
                    </button>
                  </div>
                </div>
                {active && (
                  <div className="gl-ep-progress">
                    <button className="gl-ep-skip" onClick={() => setElapsed(s => Math.max(0, s - 15))}>↺15</button>
                    <button className="gl-ep-skip" onClick={() => setElapsed(s => Math.min(totalSec, s + 30))}>30↻</button>
                    <span className="gl-ep-time">{fmt(elapsed)}</span>
                    <div className="gl-ep-bar">
                      <span style={{ width: pct.toFixed(1) + "%", background: color }}></span>
                    </div>
                    <span className="gl-ep-time">{fmt(totalSec)}</span>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

// ── Gul-Lala Submit — inline form section
function GLSubmit() {
  const [name, setName] = useState("");
  const [phone, setPhone] = useState("");
  const [email, setEmail] = useState("");
  const [note, setNote] = useState("");
  const [file, setFile] = useState("");
  const [err, setErr] = useState("");
  const [done, setDone] = useState(false);

  const reset = () => {
    setName(""); setPhone(""); setEmail(""); setNote(""); setFile(""); setErr(""); setDone(false);
  };
  const submit = (e) => {
    e.preventDefault();
    if (!name.trim() || !email.trim() || !note.trim()) {
      return setErr("Please fill in your name, email, and a note for the editors.");
    }
    if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email.trim())) {
      return setErr("That email doesn’t look right — mind checking it?");
    }
    setErr(""); setDone(true);
  };

  return (
    <section className="gl-submit" id="submit">
      <div className="gl-submit-head">
        <span className="gl-submit-eyebrow">Submit a Story</span>
        <h2>Your story belongs in this garden.</h2>
        <p>A memory, a recipe, a journey, a lesson — send it to our editors and we will help you tell it.</p>
      </div>
      {done ? (
        <div className="gl-submit-thanks">
          <span className="gl-submit-thanks-urdu">گل لالہ</span>
          <p>Thank you, {name || "friend"} — our editors will be in touch.</p>
          <button type="button" className="gl-btn-link" onClick={reset}>Send another</button>
        </div>
      ) : (
        <form className="gl-form" onSubmit={submit}>
          <div className="gl-form-row">
            <input placeholder="Your name *" value={name} onChange={(e) => setName(e.target.value)} />
            <input type="tel" placeholder="Phone" value={phone} onChange={(e) => setPhone(e.target.value)} />
          </div>
          <input type="email" placeholder="Email *" value={email} onChange={(e) => setEmail(e.target.value)} />
          <textarea rows="4"
                    placeholder="A note for the editors — what is your story about? *"
                    value={note}
                    onChange={(e) => setNote(e.target.value)}></textarea>
          <label className="gl-form-file">
            <span className="gl-form-file-plus">✚</span>
            <span>{file ? `Attached: ${file}` : "Attach your draft or photos (optional)"}</span>
            <input type="file" onChange={(e) => setFile(e.target.files && e.target.files[0] ? e.target.files[0].name : "")} />
          </label>
          {err && <p className="gl-form-err">{err}</p>}
          <button type="submit" className="gl-btn-primary">Send to the Editors</button>
        </form>
      )}
    </section>
  );
}

// ── Gul-Lala Footer — three columns: brand (left), copyright (centre),
//    sitemap (right).
function GLFooter({ onSubmit, onAbout, onOpenCategory, onGotoPodcast }) {
  const cats = window.CATEGORIES.filter(c => c.id !== "home" && c.id !== "podcast");
  return (
    <footer className="gl-footer">
      <div className="gl-footer-inner">
        <div className="gl-footer-brand">
          <span className="gl-footer-urdu">گل لالہ</span>
          <span className="gl-footer-domain">gul-lala.com</span>
        </div>

        <div className="gl-footer-center">
          <span>Our Tradition, Our Culture</span>
          <span className="gl-footer-dot" aria-hidden="true">·</span>
          <span>© 2026</span>
        </div>

        <div className="gl-footer-sitemap">
          <span className="gl-footer-eyebrow">Sitemap</span>
          <ul>
            <li>
              <a href="#" onClick={(e) => { e.preventDefault(); onOpenCategory && onOpenCategory("home"); }}>
                Home
              </a>
            </li>
            <li>
              <a href="#stories" onClick={(e) => {
                e.preventDefault();
                onOpenCategory && onOpenCategory("home");
                setTimeout(() => {
                  const el = document.getElementById("stories");
                  if (el) el.scrollIntoView({ behavior: "smooth" });
                }, 50);
              }}>All Stories</a>
            </li>
            <li>
              <a href="#category/podcast" onClick={(e) => { e.preventDefault(); onGotoPodcast ? onGotoPodcast() : onOpenCategory("podcast"); }}>
                Podcast
              </a>
            </li>
            <li>
              <a href="#about" onClick={(e) => { e.preventDefault(); onAbout && onAbout(); }}>
                About
              </a>
            </li>
            <li>
              <button type="button" onClick={onSubmit}>Submit a Story</button>
            </li>
            <li className="gl-footer-sitemap-group">
              <span className="gl-footer-eyebrow-sub">Sections</span>
              <ul>
                {cats.map(c => (
                  <li key={c.id}>
                    <a href={`#category/${c.id}`} onClick={(e) => { e.preventDefault(); onOpenCategory && onOpenCategory(c.id); }}>
                      {c.label}
                    </a>
                  </li>
                ))}
              </ul>
            </li>
          </ul>
        </div>
      </div>
    </footer>
  );
}

// ── Gul-Lala About page — long-form essay page with tulip field footer band
function GLAboutPage({ onGotoStories }) {
  useEffect(() => { window.scrollTo({ top: 0, behavior: "instant" }); }, []);
  return (
    <main className="gl-about-page">
      <header className="gl-about-title">
        <span className="gl-about-title-urdu">گل لالہ</span>
        <div className="gl-about-title-gloss">
          <span><em>gul</em> · flower</span>
          <span className="gl-about-title-sep">·</span>
          <span><em>lala</em> · tulip</span>
        </div>
        <h1>Why Gul-Lala</h1>
      </header>

      <article className="gl-about-essay">
        <p>
          <span className="gl-about-dropcap">G</span>ul-lala is the tulip — the flower Persian and
          Urdu poets kept returning to, generation after generation, because nothing else quite
          carried what they wanted to say. Colour that stops you where you stand. A bloom that
          needs no ornament to be remarkable, because it already is.
        </p>

        <figure className="gl-about-figure">
          <img src="assets/tulip/tulips-pastel.png" alt="Tulips in soft pastel light" />
          <figcaption>The flower the poets kept returning to.</figcaption>
        </figure>

        <blockquote className="gl-about-quote">
          Colour that stops you where you stand. A bloom that needs no ornament to be remarkable,
          because it already is.
        </blockquote>

        <p>
          That felt exactly right for this blog. Bohra women have been building, teaching,
          healing, trading, hosting, leading, and holding families and communities together for
          generations — often without anyone thinking to write it down. Not because the work was
          small, but because it was so familiar to us that we forgot to look. And when you do
          look, it&rsquo;s everywhere, and it&rsquo;s extraordinary.
        </p>

        <p className="gl-about-close">Gul-Lala is where we look, and where we tell those stories.</p>
      </article>

      <section className="gl-about-cta">
        <img className="gl-about-cta-tulip" src="assets/tulip/tulip-watercolor.png" alt="Watercolor tulip" />
        <button className="gl-btn-primary" type="button" onClick={onGotoStories}>
          Read the Stories
        </button>
      </section>
    </main>
  );
}

// ── Gul-Lala Podcast page — big now-playing player + full episode list
function GLPodcastPage() {
  const rawEps = window.PODCAST_EPISODES || [];
  const episodes = rawEps.map(ep => {
    const [mmStr] = String(ep.durationStr || "").match(/\d+/) || ["34"];
    const mins = parseInt(mmStr, 10) || 34;
    const number = typeof ep.number === "number" ? ep.number : 1;
    const palette = ["#E5C79A", "#A9BC98", "#C98D82", "#E4A9A9", "#DE9E93", "#9FB3C4", "#C0A3BE"];
    return {
      id: number,
      title: ep.title || `Ep ${number}`,
      section: ep.guest || ep.date || "",
      mins,
      color: palette[number % palette.length],
      baseLikes: 30 + (number * 3) % 60,
    };
  });

  const [currentId, setCurrentId] = useState(episodes[0] ? episodes[0].id : null);
  const [playing, setPlaying] = useState(false);
  const [elapsed, setElapsed] = useState(0);
  const [likes, setLikes] = useState({});
  const [shared, setShared] = useState(null);
  const tick = useRef(null);

  useEffect(() => { window.scrollTo({ top: 0, behavior: "instant" }); }, []);

  const cur = episodes.find(e => e.id === currentId) || episodes[0];
  const totalSec = cur ? cur.mins * 60 : 0;

  useEffect(() => {
    if (playing) {
      tick.current = setInterval(() => {
        setElapsed(v => {
          if (v + 1 >= totalSec) { setPlaying(false); return totalSec; }
          return v + 1;
        });
      }, 1000);
      return () => clearInterval(tick.current);
    }
  }, [playing, totalSec]);
  useEffect(() => () => clearInterval(tick.current), []);

  if (!cur) {
    return (
      <main className="gl-podcast-page">
        <header className="gl-pod-header">
          <div className="gl-pod-header-inner">
            <span className="gl-podcast-eyebrow">The Gul-Lala Podcast</span>
            <h1>Voices from the majlis, the kitchen, and the road.</h1>
          </div>
        </header>
        <section className="gl-pod-empty">
          <p>New episodes coming soon.</p>
        </section>
      </main>
    );
  }

  const fmt = (sec) => {
    const m = Math.floor(sec / 60);
    return m + ":" + String(Math.max(0, Math.floor(sec % 60))).padStart(2, "0");
  };
  const shareEp = (ep) => {
    const url = window.location.href.split("#")[0] + "#category/podcast";
    if (navigator.clipboard) navigator.clipboard.writeText(`${ep.title} — ${url}`).catch(() => {});
    setShared(ep.id);
    setTimeout(() => setShared(null), 1800);
  };
  const curLiked = !!likes[cur.id];

  return (
    <main className="gl-podcast-page">
      <header className="gl-pod-header">
        <div className="gl-pod-header-inner">
          <span className="gl-podcast-eyebrow">The Gul-Lala Podcast</span>
          <h1>Voices from the majlis, the kitchen, and the road.</h1>
          <span className="gl-pod-header-meta">
            {episodes.length} {episodes.length === 1 ? "episode" : "episodes"} · a new one every other Friday
          </span>
        </div>
      </header>

      <section className="gl-pod-player-wrap">
        <div className="gl-pod-player">
          <div className="gl-pod-np">
            <span className="gl-pod-np-label">Now Playing</span>
            <span className="gl-pod-np-title">{cur.title}</span>
            <span className="gl-pod-np-meta">{cur.section} · {cur.mins} min</span>
          </div>
          <div className="gl-pod-seek">
            <span className="gl-pod-time">{fmt(elapsed)}</span>
            <input
              className="gl-pod-range"
              type="range"
              min="0"
              max={totalSec}
              value={elapsed}
              onChange={(e) => setElapsed(parseInt(e.target.value, 10) || 0)}
              aria-label="Seek"
            />
            <span className="gl-pod-time">{fmt(totalSec)}</span>
          </div>
          <div className="gl-pod-controls">
            <button className="gl-pod-skip"
                    onClick={() => setElapsed(v => Math.max(0, v - 15))}
                    aria-label="Back 15 seconds">↺15</button>
            <button className="gl-pod-play"
                    onClick={() => setPlaying(p => !p)}
                    aria-label={playing ? "Pause" : "Play"}>
              {playing ? "❚❚" : "▶"}
            </button>
            <button className="gl-pod-skip"
                    onClick={() => setElapsed(v => Math.min(totalSec, v + 30))}
                    aria-label="Forward 30 seconds">30↻</button>
            <div className="gl-pod-side-actions">
              <button className={`gl-ep-like ${curLiked ? "on" : ""}`}
                      onClick={() => setLikes(x => ({ ...x, [cur.id]: !x[cur.id] }))}
                      aria-label="Like episode">
                {curLiked ? "♥" : "♡"} {cur.baseLikes + (curLiked ? 1 : 0)}
              </button>
              <button className="gl-ep-share"
                      onClick={() => shareEp(cur)}
                      aria-label="Share episode">
                {shared === cur.id ? "Copied ✓" : "↗ Share"}
              </button>
            </div>
          </div>
        </div>
      </section>

      <section className="gl-pod-list">
        <h2>All Episodes</h2>
        {episodes.map(ep => {
          const isCur = ep.id === cur.id;
          const liked = !!likes[ep.id];
          return (
            <div key={ep.id} className={`gl-pod-row ${isCur ? "current" : ""}`}>
              <button
                className="gl-pod-row-play"
                style={{ background: ep.color }}
                onClick={() => {
                  if (isCur) { setPlaying(p => !p); }
                  else { setCurrentId(ep.id); setElapsed(0); setPlaying(true); }
                }}
                aria-label={(isCur && playing ? "Pause " : "Play ") + ep.title}
              >
                {isCur && playing ? "❚❚" : "▶"}
              </button>
              <div className="gl-pod-row-text">
                <span className="gl-pod-row-title">{ep.title}</span>
                <span className="gl-pod-row-meta">
                  {ep.section ? `${ep.section} · ` : ""}{ep.mins} min
                </span>
              </div>
              <div className="gl-pod-row-actions">
                <button className={`gl-ep-like ${liked ? "on" : ""}`}
                        onClick={() => setLikes(x => ({ ...x, [ep.id]: !x[ep.id] }))}
                        aria-label="Like">
                  {liked ? "♥" : "♡"} {ep.baseLikes + (liked ? 1 : 0)}
                </button>
                <button className="gl-ep-share"
                        onClick={() => shareEp(ep)}
                        aria-label="Share">
                  {shared === ep.id ? "Copied ✓" : "↗"}
                </button>
              </div>
            </div>
          );
        })}
      </section>
    </main>
  );
}

// ── Gul-Lala Home — the whole landing page composition
function GLLandingHome({ query, onSearch, activeCat, tagFilter, onOpenCategory, onOpen, onSubmit, onGotoPodcast, featured }) {
  // Hero rotates through the top featured articles (or the most recent if
  // none are featured). Every slide points at a real story and shows its
  // real image — no "garden of stories" placeholder any more.
  const buildSlides = () => {
    const articles = window.ARTICLES || [];
    // An article "has an image" for hero purposes if we can resolve one —
    // either the CMS gave us featured_image, or we override the slug locally.
    const withImg = articles.filter(a => !!glResolveImage(a));
    const featuredFirst = [
      ...withImg.filter(a => a.featured),
      ...withImg.filter(a => !a.featured),
    ];
    const pick = featuredFirst.slice(0, 3);
    // Fallback to the base featured object even if the array is empty.
    const src = pick.length ? pick : (featured ? [featured] : []);
    return src.map((a) => ({
      kicker: `Featured · ${labelFor(a.category)}`,
      title: a.title,
      body: a.excerpt || a.subtitle || "",
      cta: "Read This Story",
      href: `#article/${a.id}`,
      onClick: () => onOpen(a),
      imgSrc: glResolveImage(a),
      imgAlt: a.imageAlt || a.title,
      dotLabel: a.title,
    }));
  };

  const slides = useMemo(buildSlides, [featured && featured.id, activeCat, (window.ARTICLES || []).length]);

  const isSearching  = !!(query && query.trim());
  const isTagFilter  = !!tagFilter;
  const isCategory   = activeCat !== "home";
  // Filter-only view: skip hero / about / podcast / submit; show stories only.
  const filterOnly = isSearching || isTagFilter || isCategory;

  return (
    <main className="gl-main">
      {!filterOnly && (
        <>
          <GLHero
            slides={slides}
            onOpenStories={() => {
              const el = document.getElementById("stories");
              if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
            }}
            onSubmit={onSubmit}
          />
          <GLAbout />
        </>
      )}
      <GLStories
        activeCat={activeCat}
        tagFilter={tagFilter}
        query={query}
        onOpenCategory={onOpenCategory}
        onOpen={onOpen}
        onSearch={onSearch}
        filterOnly={filterOnly}
      />
      {!filterOnly && (
        <>
          <GLPodcast onGoto={onGotoPodcast} />
          <GLSubmit />
        </>
      )}
    </main>
  );
}

Object.assign(window, {
  Icon, Plate, Rule, Asterism, SectionHead, Logo, Mark, Motif,
  HeaderBrand, SearchBar, Masthead, Hero, ArticleCard, PullQuote,
  CategoryHero, SubmitStoryTeaser, SubmitStoryModal,
  ArticleEngagement, ArticlePage, Gallery, SiteFoot,
  PodcastCover, PlayButton, EpisodeRow, PodcastStrip, PodcastPage,
  labelFor,
  // Gul-Lala
  TulipIcon, GLNav, GLHero, GLAbout, GLStories, GLPodcast, GLSubmit, GLFooter, GLLandingHome,
  GLAboutPage, GLPodcastPage,
});
