/* ============================================================
   The Carnation — shared components
   ============================================================ */
const { useState, useEffect, useRef } = React;
const C = () => window.CARN;
const S = () => window.STORE;

function Clock() {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(t);
  }, []);
  const time = now.toLocaleTimeString("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" });
  return (
    <span className="mast-flank__clock" title="Alliance, Ohio · Eastern Time">
      <span className="mast-flank__clock-dot" aria-hidden="true" />{time} ET
    </span>
  );
}

function wxInfo(code) {
  if (code === 0) return { label: "Clear", group: "clear" };
  if (code === 1) return { label: "Fair", group: "clear" };
  if (code === 2) return { label: "Partly cloudy", group: "partly" };
  if (code === 3) return { label: "Overcast", group: "cloud" };
  if (code === 45 || code === 48) return { label: "Fog", group: "fog" };
  if (code >= 51 && code <= 57) return { label: "Drizzle", group: "rain" };
  if (code >= 61 && code <= 67) return { label: "Rain", group: "rain" };
  if (code >= 71 && code <= 77) return { label: "Snow", group: "snow" };
  if (code >= 80 && code <= 82) return { label: "Showers", group: "rain" };
  if (code === 85 || code === 86) return { label: "Snow showers", group: "snow" };
  if (code >= 95) return { label: "Thunderstorms", group: "storm" };
  return { label: "Cloudy", group: "cloud" };
}

function WxIcon({ group, day }) {
  const p = { width: 15, height: 15, fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round" };
  if (group === "clear" && !day) return <svg viewBox="0 0 24 24" {...p}><path d="M20 14.5A7 7 0 1 1 11 4a6 6 0 0 0 9 10.5z"/></svg>;
  if (group === "clear") return <svg viewBox="0 0 24 24" {...p}><circle cx="12" cy="12" r="4.2"/><path d="M12 2v2M12 20v2M4 12H2M22 12h-2M5 5l1.4 1.4M17.6 17.6 19 19M19 5l-1.4 1.4M6.4 17.6 5 19"/></svg>;
  if (group === "partly") return <svg viewBox="0 0 24 24" {...p}><circle cx="8" cy="7.5" r="3"/><path d="M8 1.5v1M2.5 7.5h1M4.2 3.7l.7.7"/><path d="M7 18h9a3.5 3.5 0 0 0 .3-7A5 5 0 0 0 7 12.5 3 3 0 0 0 7 18z"/></svg>;
  if (group === "fog") return <svg viewBox="0 0 24 24" {...p}><path d="M5 13h11a3.5 3.5 0 0 0 0-7 5 5 0 0 0-9.6 1.5A3 3 0 0 0 5 13z"/><path d="M4 17h12M7 20h10"/></svg>;
  if (group === "rain") return <svg viewBox="0 0 24 24" {...p}><path d="M6 13h11a3.5 3.5 0 0 0 0-7 5 5 0 0 0-9.6 1.5A3 3 0 0 0 6 13z"/><path d="M8 16.5l-1 2.5M12 16.5l-1 2.5M16 16.5l-1 2.5"/></svg>;
  if (group === "snow") return <svg viewBox="0 0 24 24" {...p}><path d="M6 12h11a3.5 3.5 0 0 0 0-7 5 5 0 0 0-9.6 1.5A3 3 0 0 0 6 12z"/><path d="M8 16h.01M12 18h.01M16 16h.01"/></svg>;
  if (group === "storm") return <svg viewBox="0 0 24 24" {...p}><path d="M6 12h11a3.5 3.5 0 0 0 0-7 5 5 0 0 0-9.6 1.5A3 3 0 0 0 6 12z"/><path d="M13 13l-3 4h3l-2 4"/></svg>;
  return <svg viewBox="0 0 24 24" {...p}><path d="M6 17h11a3.8 3.8 0 0 0 0-8 5.2 5.2 0 0 0-10-1.5A3.5 3.5 0 0 0 6 17z"/></svg>;
}

const WX_URL = "https://api.open-meteo.com/v1/forecast?latitude=40.9153&longitude=-81.1059&current=temperature_2m,weather_code,is_day,wind_speed_10m&daily=sunrise,sunset&temperature_unit=fahrenheit&wind_speed_unit=mph&timezone=America/New_York&forecast_days=2&timeformat=unixtime";
let _wxPromise = null;
function fetchWx() {
  if (!_wxPromise) _wxPromise = fetch(WX_URL).then((r) => r.json()).then((j) => {
    if (!j.current) return null;
    const out = { temp: Math.round(j.current.temperature_2m), code: j.current.weather_code, day: j.current.is_day === 1, wind: Math.round(j.current.wind_speed_10m) };
    if (j.daily && j.daily.sunrise && j.daily.sunset) {
      const now = Date.now() / 1000;
      const ev = [];
      j.daily.sunrise.forEach((t) => ev.push({ type: "sunrise", t }));
      j.daily.sunset.forEach((t) => ev.push({ type: "sunset", t }));
      const next = ev.filter((e) => e.t > now).sort((a, b) => a.t - b.t)[0];
      if (next) out.sun = next;
    }
    return out;
  });
  return _wxPromise;
}
function useWeather() {
  const [w, setW] = useState(() => {
    try { const c = JSON.parse(localStorage.getItem("carn_wx") || "null"); if (c && Date.now() - c.t < 1800000) return c.d; } catch (e) {}
    return null;
  });
  useEffect(() => {
    let alive = true;
    fetchWx().then((d) => {
      if (!alive || !d) return;
      setW(d);
      try { localStorage.setItem("carn_wx", JSON.stringify({ t: Date.now(), d })); } catch (e) {}
    }).catch(() => {});
    return () => { alive = false; };
  }, []);
  return w;
}
function Weather() {
  const w = useWeather();
  if (!w) return null;
  const { label, group } = wxInfo(w.code);
  return (
    <span className="mast-flank__wx" title={"Alliance, Ohio · " + label + " · " + w.temp + "°F"}>
      <span className="mast-flank__sep" aria-hidden="true" /><WxIcon group={group} day={w.day} />{w.temp}°
    </span>
  );
}

function WindIcon() {
  return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8h10a2.6 2.6 0 1 0-2.6-2.6"/><path d="M3 12h14a2.6 2.6 0 1 1-2.6 2.6"/><path d="M3 16h7"/></svg>;
}
function SunEventIcon({ type }) {
  const p = { width: 15, height: 15, fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round" };
  return (
    <svg viewBox="0 0 24 24" {...p}>
      <path d="M2.5 20h19" />
      <path d="M7.5 20a4.5 4.5 0 0 1 9 0" />
      {type === "sunrise"
        ? <path d="M12 2.5v7M8.6 5.9 12 2.5l3.4 3.4" />
        : <path d="M12 2.5v7M8.6 6.1 12 9.5l3.4-3.4" />}
    </svg>
  );
}
function WeatherDetail() {
  const w = useWeather();
  if (!w || (w.wind == null && !w.sun)) return null;
  const sun = w.sun;
  const sunTime = sun ? new Date(sun.t * 1000).toLocaleTimeString("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" }) : null;
  return (
    <span className="mast-flank__detail" style={{display:"flex", flexWrap:"wrap", alignItems:"center", gap:"4px 7px", marginTop:"4px"}}>
      {w.wind != null && <span className="mast-flank__detail-item"><WindIcon />{w.wind} mph</span>}
      {sun && <span className="mast-flank__sep" aria-hidden="true" />}
      {sun && <span className="mast-flank__detail-item" title={sun.type === "sunrise" ? "Sunrise" : "Sunset"}><SunEventIcon type={sun.type} />{sun.type === "sunrise" ? "Rise" : "Set"} {sunTime}</span>}
    </span>
  );
}
function IssueWeather() {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => { const t = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(t); }, []);
  const w = useWeather();
  const time = now.toLocaleTimeString("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" });
  const info = w ? wxInfo(w.code) : null;
  const sun = w && w.sun;
  const sunTime = sun ? new Date(sun.t * 1000).toLocaleTimeString("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" }) : null;
  return (
    <span className="issuebar__wx hide-sm">
      <span className="issuebar__div" aria-hidden="true" />
      <span className="issuebar__wx-item">{time} ET</span>
      {w && <span className="issuebar__div" aria-hidden="true" />}
      {w && <span className="issuebar__wx-item"><WxIcon group={info.group} day={w.day} />{w.temp}°</span>}
      {w && w.wind != null && <span className="issuebar__div" aria-hidden="true" />}
      {w && w.wind != null && <span className="issuebar__wx-item"><WindIcon />{w.wind} mph</span>}
      {sun && <span className="issuebar__div" aria-hidden="true" />}
      {sun && <span className="issuebar__wx-item" title={sun.type === "sunrise" ? "Sunrise" : "Sunset"}><SunEventIcon type={sun.type} />{sunTime}</span>}
    </span>
  );
}
function LiveConditions() {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => { const t = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(t); }, []);
  const w = useWeather();
  const time = now.toLocaleTimeString("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" });
  const parts = [time];
  if (w) {
    parts.push(w.temp + "°");
    if (w.wind != null) parts.push("Wind " + w.wind + " mph");
    if (w.sun) {
      const st = new Date(w.sun.t * 1000).toLocaleTimeString("en-US", { timeZone: "America/New_York", hour: "numeric", minute: "2-digit" });
      parts.push((w.sun.type === "sunrise" ? "Sunrise " : "Sunset ") + st);
    }
  }
  return <span className="mast-flank__cond">{parts.join(" · ")}</span>;
}
function fxKind(w) {
  const { group } = wxInfo(w.code);
  if (group === "rain" || group === "storm") return "rain";
  if (group === "snow") return "snow";
  if (group === "fog") return "fog";
  if (group === "clear") return w.day ? "sun" : "stars";
  return "cloud"; // partly, overcast, cloudy
}
function WeatherFX() {
  const w = useWeather();
  if (!w) return null;
  const fx = fxKind(w);
  const make = (n, fn) => Array.from({ length: n }, (_, i) => fn(i));
  return (
    <div className={"wxfx wxfx--" + fx} aria-hidden="true">
      {fx === "rain" && make(24, (i) => <span className="wxfx__drop" key={i} style={{ left: (i * 4.3 + (i % 3)) + "%", animationDelay: ((i * 0.17) % 1.3).toFixed(2) + "s", animationDuration: (0.62 + (i % 4) * 0.1).toFixed(2) + "s" }} />)}
      {fx === "snow" && make(22, (i) => <span className="wxfx__flake" key={i} style={{ left: (i * 4.6 + (i % 4)) + "%", animationDelay: ((i * 0.4) % 5).toFixed(2) + "s", animationDuration: (5 + (i % 5)) + "s" }} />)}
      {fx === "sun" && (<div className="wxfx__sun"><span className="wxfx__glow" />{make(12, (i) => <span className="wxfx__ray" key={i} style={{ transform: "rotate(" + (i * 30) + "deg)" }} />)}</div>)}
      {fx === "stars" && make(18, (i) => <span className="wxfx__star" key={i} style={{ left: (i * 5.4 + (i % 5)) + "%", top: ((i * 41) % 78 + 6) + "%", animationDelay: ((i * 0.37) % 3).toFixed(2) + "s" }} />)}
      {(fx === "cloud" || fx === "fog") && make(fx === "fog" ? 4 : 5, (i) => <span className={"wxfx__cloud" + (fx === "fog" ? " wxfx__cloud--fog" : "")} key={i} style={{ top: (8 + i * 17) + "%", animationDelay: (i * -8) + "s", animationDuration: (40 + i * 9) + "s" }} />)}
    </div>
  );
}
const AR_URL = "https://alliance-renaissance.org"; /* deployed main site (placeholder) */

/* ---------- store subscription: re-render on any persisted change ---------- */
function useStore() {
  const [, force] = useState(0);
  useEffect(() => S().subscribe(() => force((n) => n + 1)), []);
}
/* ---------- a single media slot (real photo or null) ---------- */
function usePhoto(slot) {
  const [url, setUrl] = useState(() => (slot ? S().media.get(slot) : null));
  useEffect(() => {
    setUrl(slot ? S().media.get(slot) : null);
    return S().subscribe(() => setUrl(slot ? S().media.get(slot) : null));
  }, [slot]);
  return url;
}

/* ---------- in-view reveal hook ---------- */
function useInView(opts) {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    if (!("IntersectionObserver" in window)) { setSeen(true); return; }
    const r = el.getBoundingClientRect();
    if (r.top < (window.innerHeight || 800) + 80) { setSeen(true); return; }
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } });
    }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px", ...(opts || {}) });
    io.observe(el);
    const t = setTimeout(() => setSeen(true), 600);
    return () => { io.disconnect(); clearTimeout(t); };
  }, []);
  return [ref, seen];
}
function Reveal({ children, className }) {
  const [ref, seen] = useInView();
  return <div ref={ref} className={(seen ? "in " : "") + (className || "")}>{children}</div>;
}

/* ---------- count-up number (animates when scrolled into view) ---------- */
function CountUp({ to, dur = 1100 }) {
  const ref = useRef(null);
  const [val, setVal] = useState(to);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setVal(to); return; }
    if (!("IntersectionObserver" in window)) { setVal(to); return; }
    let raf = 0, done = false;
    const run = () => {
      if (done) return; done = true;
      setVal(0);
      const t0 = performance.now();
      const tick = (t) => {
        const p = Math.min(1, (t - t0) / dur);
        const e = 1 - Math.pow(1 - p, 3);
        setVal(Math.round(to * e));
        if (p < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
    };
    const io = new IntersectionObserver((es) => {
      es.forEach((e) => { if (e.isIntersecting) { run(); io.disconnect(); } });
    }, { threshold: 0.6 });
    io.observe(el);
    const safety = setTimeout(() => { if (!done) { done = true; setVal(to); io.disconnect(); } }, 700);
    return () => { io.disconnect(); cancelAnimationFrame(raf); clearTimeout(safety); };
  }, [to]);
  return <span ref={ref}>{val}</span>;
}

/* refined carnation bloom, composed for a 400x300 cover */
const COVER_BLOOM = (function () {
  const CX = 188, CY = 120;
  const rings = [{ n: 11, d: 60, r: 27, off: 0 }, { n: 8, d: 37, r: 27, off: 0.4 }, { n: 5, d: 18, r: 24, off: 0.2 }];
  const arr = [];
  rings.forEach((rg) => { for (let i = 0; i < rg.n; i++) { const a = rg.off + 2 * Math.PI * i / rg.n; arr.push([(CX + rg.d * Math.cos(a)).toFixed(1), (CY + rg.d * Math.sin(a)).toFixed(1), rg.r]); } });
  arr.push([CX, CY, 23]);
  return arr;
})();

/* ---------- COVER ART (procedural plate, overridable by a real photo) ----------
   Pass `slot` to make the cover swappable from the Admin panel: if a photo
   exists at that media key it renders the photo; otherwise the procedural
   plate shows as a designed fallback. */
function Cover({ variant, tag, slot, className, style, alt }) {
  const photo = usePhoto(slot);
  const C0 = "#15110c", RED = "#b21e36", REDINK = "#8c1428", CREAM = "#ffffff", PAPER2 = "#efede8", BLUE = "#1d2f6b", GREEN = "#2f6b41", GREEN_DK = "#234d30";
  const plates = {
    bloom: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={CREAM} />
        <rect x="15" y="15" width="370" height="270" fill="none" stroke={C0} strokeWidth="1.5" />
        <path d="M188 214 C 192 240 206 262 236 286" stroke={GREEN} strokeWidth="9" fill="none" strokeLinecap="round" />
        <path d="M196 240 C 162 228 138 236 122 260 C 156 268 184 258 200 242 Z" fill={GREEN} stroke={GREEN_DK} strokeWidth="2" />
        <path d="M216 264 C 250 254 276 262 292 286 C 260 292 234 284 218 268 Z" fill={GREEN} stroke={GREEN_DK} strokeWidth="2" />
        <path d="M176 198 Q188 190 200 198 L196 222 Q188 228 180 222 Z" fill={GREEN} stroke={GREEN_DK} strokeWidth="2" />
        <g stroke={REDINK} strokeWidth="3">
          {COVER_BLOOM.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r={p[2]} fill={RED} />)}
        </g>
      </svg>
    ),
    ledger: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={C0} />
        {[...Array(8)].map((_,i)=>(<rect key={i} x={30+i*46} y={70+(i%3)*30} width="30" height={180-(i%3)*30} fill={i%4===0?RED:CREAM} opacity={i%4===0?1:0.92}/>))}
        <rect x="0" y="250" width="400" height="6" fill={RED}/>
      </svg>
    ),
    window: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={PAPER2}/>
        <rect x="60" y="40" width="280" height="220" fill={C0}/>
        <rect x="74" y="54" width="118" height="93" fill={CREAM}/>
        <rect x="208" y="54" width="118" height="93" fill={RED}/>
        <rect x="74" y="160" width="118" height="93" fill={RED}/>
        <rect x="208" y="160" width="118" height="93" fill={CREAM}/>
      </svg>
    ),
    bench: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={CREAM}/>
        <rect x="0" y="180" width="400" height="120" fill={C0}/>
        <circle cx="120" cy="120" r="58" fill={RED}/>
        <circle cx="270" cy="140" r="40" fill={C0}/>
        <rect x="40" y="210" width="320" height="14" fill={CREAM}/>
        <rect x="60" y="224" width="10" height="50" fill={CREAM}/><rect x="330" y="224" width="10" height="50" fill={CREAM}/>
      </svg>
    ),
    press: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={C0}/>
        {[...Array(5)].map((_,i)=>(<rect key={i} x="50" y={50+i*42} width={280-i*30} height="20" fill={i===0?RED:CREAM} opacity={i===0?1:0.9-i*0.12}/>))}
        <circle cx="320" cy="220" r="46" fill={RED}/>
      </svg>
    ),
    marquee: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={RED}/>
        <rect x="50" y="60" width="300" height="180" fill={C0}/>
        <rect x="50" y="60" width="300" height="40" fill={CREAM}/>
        {[...Array(9)].map((_,i)=>(<circle key={i} cx={70+i*32} cy="80" r="6" fill={RED}/>))}
        <rect x="80" y="130" width="240" height="14" fill={CREAM}/>
        <rect x="80" y="160" width="180" height="14" fill={CREAM} opacity="0.6"/>
      </svg>
    ),
    campus: (
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice">
        <rect width="400" height="300" fill={BLUE} opacity="0.18"/><rect width="400" height="300" fill={CREAM} opacity="0.5"/>
        <polygon points="200,40 320,130 80,130" fill={RED}/>
        <rect x="100" y="130" width="200" height="120" fill={C0}/>
        {[...Array(4)].map((_,i)=>(<rect key={i} x={120+i*45} y="155" width="22" height="70" fill={CREAM}/>))}
        <rect x="190" y="200" width="20" height="50" fill={CREAM}/>
      </svg>
    ),
  };
  return (
    <div className={"cover " + (photo ? "cover--photo " : "") + (className || "")} style={style}>
      {photo
        ? <img className="cover__img" src={photo} alt={alt || tag || ""} />
        : <>{plates[variant] || plates.bloom}<div className="cover__halftone" /></>}
      {tag && <span className="cover__tag">{tag}</span>}
    </div>
  );
}

/* ---------- AUTHOR / STAFF AVATAR (real photo or monogram) ---------- */
function Avatar({ slot, initials, size = 44, className }) {
  const photo = usePhoto(slot);
  return (
    <span className={"avatar " + (className || "")} style={{ width: size, height: size }}>
      {photo ? <img src={photo} alt={initials || ""} /> : <span className="avatar__mono">{initials}</span>}
    </span>
  );
}

/* ---------- LIVE TIMESTAMP ---------- */
function TimeAgo({ ts }) {
  useStore();
  const [, tick] = useState(0);
  useEffect(() => { const id = setInterval(() => tick((n) => n + 1), 60000); return () => clearInterval(id); }, []);
  return <span>{C().timeAgo(ts)}</span>;
}

/* ---------- TAG ROW ---------- */
function TagRow({ tags, className }) {
  if (!tags || !tags.length) return null;
  return (
    <div className={"tagrow " + (className || "")}>
      {tags.map((t) => (
        <a key={t} className="tagchip" href={"#/tag/" + t} onClick={(e) => e.stopPropagation()}>{C().tagLabel(t)}</a>
      ))}
    </div>
  );
}

/* ---------- ISSUE BAR ---------- */
function IssueBar() {
  const today = C().fmtDate(Date.now());
  return (
    <div className="issuebar">
      <div className="wrap issuebar__in">
        <span className="dot" /> Independent · Free to read
        <span className="hide-sm" style={{opacity:.62, marginLeft:14}}>{today}</span>
        <span className="sp" />
        <span className="hide-sm" style={{opacity:.7}}>A publication of The Alliance Renaissance</span>
        <a href={AR_URL} target="_blank" rel="noopener" style={{marginLeft:18}}>Main Site →</a>
      </div>
    </div>
  );
}

/* ---------- CARNATION MARK ---------- */
const CARN_PETALS = (function () {
  const CX = 58, CY = 44;
  const rings = [{ n: 11, d: 23, r: 10, off: 0 }, { n: 8, d: 14, r: 10, off: 0.4 }, { n: 5, d: 7, r: 9, off: 0.2 }];
  const arr = [];
  rings.forEach((rg) => { for (let i = 0; i < rg.n; i++) { const a = rg.off + 2 * Math.PI * i / rg.n; arr.push([CX + rg.d * Math.cos(a), CY + rg.d * Math.sin(a), rg.r]); } });
  arr.push([CX, CY, 9]);
  return arr;
})();
function CarnationMark({ className, style, compact, sprig }) {
  const RED = "#b21e36", RED_INK = "#8c1428", GREEN = "#2f6b41", GREEN_DK = "#234d30";
  const vb = sprig ? "18 6 90 140" : compact ? "18 4 82 88" : "0 0 120 150";
  return (
    <svg className={className} style={style} viewBox={vb} xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
      <path d="M58 78 C 60 98 66 116 80 140" stroke={GREEN} strokeWidth="4.5" fill="none" strokeLinecap="round" />
      <path d="M61 100 C 45 93 33 97 26 110 C 43 115 56 110 62 101 Z" fill={GREEN} stroke={GREEN_DK} strokeWidth="1.2" />
      <path d="M66 118 C 82 111 95 116 101 129 C 84 133 71 128 65 119 Z" fill={GREEN} stroke={GREEN_DK} strokeWidth="1.2" />
      <path d="M50 66 Q58 60 66 66 L63 82 Q58 86 53 82 Z" fill={GREEN} stroke={GREEN_DK} strokeWidth="1.2" />
      <path d="M51 68 L48 56 L57 66 Z" fill={GREEN} />
      <path d="M65 68 L68 56 L59 66 Z" fill={GREEN} />
      <g stroke={RED_INK} strokeWidth="1.5">
        {CARN_PETALS.map((p, i) => <circle key={i} cx={p[0].toFixed(1)} cy={p[1].toFixed(1)} r={p[2]} fill={RED} />)}
      </g>
    </svg>
  );
}

/* ---------- MASTHEAD ---------- */
function Masthead() {
  return (
    <header className="masthead">
      <WeatherFX />
      <div className="wrap masthead__in">
        <div className="mast-flank mast-flank--l hide-sm">
          Local Record<b>Alliance, Ohio</b><span className="mast-flank__est" style={{whiteSpace:"nowrap", display:"block"}}><span style={{color:"var(--blue)"}}>The Carnation City</span></span><span className="mast-flank__cond-label" style={{display:"block", marginTop:"13px", color:"var(--red)", fontSize:".56rem", letterSpacing:".16em"}}>Local Conditions</span><span className="mast-flank__live" style={{display:"flex", flexWrap:"wrap", alignItems:"center", marginTop:"4px"}}><Clock /><Weather /></span><WeatherDetail />
        </div>
        <a href="#/" className="masthead__brand">
          <h1 className="masthead__title">The Carnation</h1>
          <CarnationMark className="masthead__mark" sprig />
        </a>
        <p className="masthead__sub">People, places, and possibility<br />from Alliance, Ohio.</p>
        <div className="mast-flank mast-flank--r hide-sm">
          Local Signals<b>Stories · Spaces · Ideas</b><span className="mast-flank__est" style={{whiteSpace:"nowrap", display:"block"}}><span style={{color:"var(--red)"}}>Future-Facing</span></span>
        </div>
      </div>
    </header>
  );
}

/* ---------- PRIMARY NAV ---------- */
function CNav({ active }) {
  const [open, setOpen] = useState(false);
  const [grp, setGrp] = useState(null);
  useEffect(() => { setOpen(false); setGrp(null); }, [active]);
  useEffect(() => {
    const onDoc = (e) => { if (!e.target.closest(".cnav__group")) setGrp(null); };
    document.addEventListener("click", onDoc);
    return () => document.removeEventListener("click", onDoc);
  }, []);
  const isMobile = () => window.matchMedia("(max-width: 760px)").matches;
  return (
    <nav className={"cnav" + (open ? " open" : "")}>
      <div className="wrap cnav__in">
        <button className="cnav__bars" aria-label="Menu" onClick={() => setOpen(o => !o)}>
          <span /><span /><span />
        </button>
        <div className="cnav__links">
          {C().NAV.map((n) => n.children ? (
            <div className={"cnav__group" + (grp === n.key ? " open" : "")} key={n.key}
              onMouseEnter={() => { if (!isMobile()) setGrp(n.key); }}
              onMouseLeave={() => { if (!isMobile()) setGrp(null); }}>
              <a href={n.path || "#/stories"} aria-expanded={grp === n.key} aria-haspopup="true"
                className={"cnav__top" + (n.children.some(c => c.key === active) || active===n.key ? " on" : "")}
                onClick={(e) => { if (isMobile()) { e.preventDefault(); setGrp(g => g === n.key ? null : n.key); } }}>{n.label}<span className="cnav__caret" aria-hidden="true">▾</span></a>
              <div className="cnav__menu" role="menu">
                {n.children.map((c) => (
                  <a key={c.key} href={c.path} role="menuitem" className={active === c.key ? "on" : ""} onClick={() => setGrp(null)}>{c.label}</a>
                ))}
              </div>
            </div>
          ) : (
            <a key={n.key} href={n.path} className={active === n.key ? "on" : ""}>{n.label}</a>
          ))}
        </div>
        <a className="cnav__search" href="#/search" aria-label="Search">
          <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
          <span className="cnav__search-l">Search</span>
        </a>
      </div>
    </nav>
  );
}

/* ---------- SPONSOR WELL ---------- */
function SponsorWell() {
  return (
    <section className="section section--tight">
      <div className="wrap">
        <a className="well" href="#/advertise">
          <span className="well__tag">Partner with The Carnation</span>
          <span className="well__h">Support local culture. Reach the people building Alliance.</span>
          <span className="well__p">Section sponsorships, Spotlight features, and premium profiles in The Local Index. Backing for independent civic media, beside the stories the city reads on purpose.</span>
          <span className="well__cta">Become a partner <span className="arw">→</span></span>
        </a>
      </div>
    </section>
  );
}

/* ---------- TICKER ---------- */
function Ticker() {
  const items = C().TICKER;
  const run = [...items, ...items];
  return (
    <div className="ticker">
      <div className="ticker__track">
        {run.map((it, i) => (
          <a className="ticker__item" key={i} href={it.href}
             aria-hidden={i >= items.length ? "true" : undefined}
             tabIndex={i >= items.length ? -1 : undefined}>{it.t}</a>
        ))}
      </div>
    </div>
  );
}

/* ---------- ARTICLE CARD ---------- */
function ArticleCard({ a, size = "md", showCover = true }) {
  return (
    <a className={"card card--" + size} href={"#/article/" + a.slug}>
      {showCover && <Cover variant={a.cover} tag={a.category} slot={"cover:art:" + a.slug} className="card__cover" />}
      <div className="card__cat">{a.category}</div>
      <h3 className="card__title">{a.title}</h3>
      {size !== "sm" && <p className="card__dek">{a.dek}</p>}
      <div className="card__meta">{a.author} · <TimeAgo ts={a.ts} /> · {a.read} read</div>
    </a>
  );
}

/* ---------- ROW CARD (text list) ---------- */
function RowCard({ a, rank }) {
  return (
    <a className="rowcard" href={"#/article/" + a.slug}>
      {rank != null && <span className="rowcard__rank">{String(rank).padStart(2,"0")}</span>}
      <div>
        <div className="rowcard__t">{a.title}</div>
        <div className="rowcard__d">{a.dek}</div>
        <div className="rowcard__stamp">{a.category} · <TimeAgo ts={a.ts} /></div>
      </div>
      <div className="rowcard__meta">{a.read} read</div>
    </a>
  );
}

/* ---------- SECTION RULE HEADING ---------- */
function SecRule({ title, meta, href }) {
  return (
    <div className="sec-rule">
      <h2>{title}</h2>
      {meta && (href ? <a className="meta sec-rule__link" href={href}>{meta} →</a> : <span className="meta">{meta}</span>)}
    </div>
  );
}

/* ---------- NEWSLETTER (reusable inline form) ---------- */
function Newsletter({ dark }) {
  useStore();
  const [sent, setSent] = useState(false);
  const [dupe, setDupe] = useState(false);
  const [val, setVal] = useState("");
  const [err, setErr] = useState("");
  const submit = async (e) => {
    e.preventDefault();
    const email = val.trim();
    if (!email) return;
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { setErr("Please enter a valid email address."); return; }
    setErr("");
    if (window.CLOUD && window.CLOUD.enabled) {
      const ok = await window.captureOrAlert("newsletter", { email });
      if (!ok) return;
      setSent(true);
      return;
    }
    if (window.STORE && S().newsletter.has(email)) { setDupe(true); setSent(true); return; }
    const ok = await window.captureOrAlert("newsletter", { email });
    if (!ok) return;
    setSent(true);
  };
  return (
    <form className="nl" onSubmit={submit}>
      {sent
        ? <p className="nl__ok">{dupe ? "You're already on the list — thanks for the enthusiasm." : "Thank you — you're on the list for the Dispatch."}</p>
        : <>
            <input type="email" required placeholder="Your email for the Dispatch" aria-label="Email" value={val} onChange={(e)=>{setVal(e.target.value); if(err) setErr("");}} />
            <button className="btn btn--red btn-sm" type="submit">Subscribe</button>
            {err && <span className="nl__err" style={{flexBasis:"100%", color:"var(--red)", fontSize:".82rem", marginTop:"6px"}}>{err}</span>}
          </>}
    </form>
  );
}

/* ---------- THE CARNATION'S OWN SOCIALS ---------- */
const CARN_HANDLE = "thecarnationpost";
function SocialRow({ label }) {
  const items = [
    { k: "ig", href: "https://instagram.com/" + CARN_HANDLE, t: "Instagram",
      icon: <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.8"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1.1" fill="currentColor" stroke="none"/></svg> },
    { k: "fb", href: "https://facebook.com/" + CARN_HANDLE, t: "Facebook",
      icon: <svg viewBox="0 0 24 24" width="17" height="17" fill="currentColor"><path d="M14 8.5V6.8c0-.8.2-1.3 1.4-1.3H17V2.6c-.3 0-1.3-.1-2.4-.1-2.4 0-4.1 1.5-4.1 4.2v1.8H8v3h2.5V22H14v-7.5h2.5l.4-3H14Z"/></svg> },
    { k: "x", href: "https://x.com/" + CARN_HANDLE, t: "X",
      icon: <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.66l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z"/></svg> },
    { k: "rss", href: "feed.xml", t: "RSS",
      icon: <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><circle cx="6.2" cy="17.8" r="2.2"/><path d="M4 4.4v3.1c6.9 0 12.5 5.6 12.5 12.5h3.1C19.6 11.2 12.8 4.4 4 4.4Z"/><path d="M4 10.6v3.1c3.5 0 6.3 2.8 6.3 6.3h3.1C13.4 14.9 9.1 10.6 4 10.6Z"/></svg> },
  ];
  return (
    <div className="socialrow">
      {label && <span className="socialrow__l">{label}</span>}
      <div className="socialrow__icons">
        {items.map((it) => (
          <a key={it.k} className="socialbtn" href={it.href} target={it.k === "rss" ? "_blank" : "_blank"} rel="noopener" aria-label={it.t} title={it.t}>{it.icon}</a>
        ))}
      </div>
    </div>
  );
}

/* ---------- AUTHOR CONTACT (socials + email + civility note) ---------- */
function AuthorContact({ a, fineprint }) {
  if (!a || !a.contact) return null;
  const c = a.contact;
  const first = a.key === "the-editors" ? "the editors" : ((a.name.split(" ").length <= 2 && a.name.length <= 16) ? a.name : a.name.split(" ")[0]);
  const I = {
    mail: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.9"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7.5 9 6 9-6"/></svg>,
    x: <svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.66l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z"/></svg>,
    in: <svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1 4.98 2.12 4.98 3.5zM0 8h5v16H0V8zm7.5 0H12v2.2h.07c.62-1.17 2.14-2.4 4.4-2.4C21.1 7.8 24 10 24 14.6V24h-5v-8.3c0-2-.04-4.56-2.78-4.56-2.78 0-3.2 2.17-3.2 4.42V24h-5V8z"/></svg>,
    ig: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1.1" fill="currentColor" stroke="none"/></svg>,
  };
  const socs = [];
  if (c.x) socs.push({ k: "x", href: "https://x.com/" + c.x, t: "X", icon: I.x });
  if (c.linkedin) socs.push({ k: "in", href: "https://www.linkedin.com/in/" + c.linkedin, t: "LinkedIn", icon: I.in });
  if (c.ig) socs.push({ k: "ig", href: "https://instagram.com/" + c.ig, t: "Instagram", icon: I.ig });
  return (
    <div className="authorc">
      <div className="authorc__row">
        {socs.map((s) => <a key={s.k} className="authorc__soc" href={s.href} target="_blank" rel="noopener" aria-label={s.t} title={s.t}>{s.icon}</a>)}
        {c.email && <a className="authorc__email" href={"mailto:" + c.email}>{I.mail}<span>Email {first}</span></a>}
      </div>
      {fineprint && <p className="authorc__note">Tips, leads, and honest disagreement are always welcome. Please keep it respectful — harassment, threats, and abuse aren’t, and won’t get a reply.</p>}
    </div>
  );
}

/* ---------- FOOTER ---------- */
function CFooter() {
  return (
    <footer className="cfoot">
      <div className="wrap">
        <div className="cfoot__preamble">
          <span className="cfoot__preamble-rule" aria-hidden="true" />
          <p className="cfoot__declar">In the conviction that a city which records itself, plainly and ambitiously, may yet author its own future, we publish this journal for Alliance, Ohio, and for every overlooked American place resolved to build what comes next.</p>
          <span className="cfoot__preamble-rule" aria-hidden="true" />
        </div>
        <div className="cfoot__top">
          <div className="cfoot__brand">
            <a href="#/" className="cfoot__lockup">
              <span className="cfoot__mast">The Carnation</span>
              <CarnationMark sprig className="cfoot__mark" />
            </a>
            <p className="cfoot__sub">People, places, and possibility<br />from Alliance, Ohio.</p>
            <p className="cfoot__creed">Published by <a href={AR_URL} target="_blank" rel="noopener" style={{color:"var(--red)"}}>The Alliance Renaissance</a>.<br />Independent. No paywall, ever.</p>
            <a className="cfoot__cta" href="#/membership">Become a Founding Supporter <span className="arw">→</span></a>
            <SocialRow label="Follow @thecarnationpost" />
          </div>

          <div className="cfoot__cols">
          <div className="cfoot__col">
            <h5>Read</h5>
            <a href="#/stories">Stories</a>
            <a href="#/ideas">Ideas</a>
            <a href="#/business">Business</a>
            <a href="#/spaces">Spaces</a>
            <a href="#/culture">Culture</a>
            <a href="#/local-index">The Local Index</a>
          </div>
          <div className="cfoot__col">
            <h5>Take Part</h5>
            <a href="#/add-business">Nominate a Business</a>
            <a href="#/list-space">Suggest a Space</a>
            <a href="#/contribute/write">Submit a Story</a>
            <a href="#/ideas">Open Questions</a>
            <a href="#/join">Write for Us</a>
          </div>
          <div className="cfoot__col">
            <h5>Partner</h5>
            <a href="#/advertise">Partner With The Carnation</a>
            <a href="#/advertise?at=founding">Founding Partners</a>
            <a href="#/spotlight">Spotlights &amp; Profiles</a>
            <a href="#/advertise?at=coverage">Sponsor a Section</a>
            <a href="#/press">Partner Kit</a>
          </div>
          <div className="cfoot__col">
            <h5>About</h5>
            <a href="#/about">About</a>
            <a href="#/staff">Masthead</a>
            <a href="#/ethics">Ethics &amp; Standards</a>
            <a href="#/funding">How We&rsquo;re Funded</a>
            <a href="#/contact">Contact</a>
            <a href={AR_URL} target="_blank" rel="noopener">The Alliance Renaissance</a>
          </div>
        </div>
        </div>

        <div className="cfoot__base">
          <span>© {new Date().getFullYear()} The Carnation · A publication of <a href={AR_URL} target="_blank" rel="noopener">The Alliance Renaissance</a></span>
          <span className="cfoot__base-links"><a href="#/privacy">Privacy</a><a href="#/terms">Terms</a><a href="feed.xml" target="_blank" rel="noopener">RSS</a></span>
          <span>Alliance, Ohio · Founded 1854 · <span style={{color:"var(--red)"}}>The Carnation City</span></span>
        </div>
      </div>
    </footer>
  );
}

/* ---------- READING PROGRESS BAR ---------- */
function ReadingProgress() {
  const [p, setP] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const max = (h.scrollHeight - h.clientHeight) || 1;
      setP(Math.min(1, Math.max(0, h.scrollTop / max)));
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, []);
  return <div className="readprog" aria-hidden="true"><span style={{ transform: "scaleX(" + p + ")" }} /></div>;
}

/* ---------- SAVE-FOR-LATER ---------- */
function SaveButton({ slug, label }) {
  useStore();
  const saved = S().saved.has(slug);
  return (
    <button className={"savebtn" + (saved ? " on" : "")} onClick={() => S().saved.toggle(slug)} aria-pressed={saved}>
      <svg viewBox="0 0 24 24" width="15" height="15" fill={saved ? "currentColor" : "none"} stroke="currentColor" strokeWidth="2"><path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z"/></svg>
      {label !== false && <span>{saved ? "Saved" : "Save"}</span>}
    </button>
  );
}

/* ---------- canonical shareable URL for a post (static stub page) ---------- */
function postUrl(kind, slug) {
  try {
    const base = location.href.split("#")[0].replace(/[^/]*$/, "");
    return base + "share/" + kind + "/" + slug + ".html";
  } catch (e) { return location.href; }
}

/* ---------- place / business link helpers + icon row ---------- */
function mapsSearchUrl(q) { return "https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(q); }
function mapsDirUrl(addr) { return "https://www.google.com/maps/dir/?api=1&destination=" + encodeURIComponent(addr); }
function telHref(phone) { const d = (phone || "").replace(/[^\d+]/g, ""); return d ? "tel:" + d : null; }

function PlaceLinks({ l, label, kind }) {
  const I = {
    pin: <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11Z"/><circle cx="12" cy="10" r="2.5"/></svg>,
    phone: <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8.1 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.5c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2Z"/></svg>,
    globe: <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.8"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.7 2.5 15.3 0 18M12 3c-2.5 2.7-2.5 15.3 0 18"/></svg>,
    ig: <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.8"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.5" cy="6.5" r="1.1" fill="currentColor" stroke="none"/></svg>,
    fb: <svg viewBox="0 0 24 24" width="17" height="17" fill="currentColor"><path d="M14 8.5V6.8c0-.8.2-1.3 1.4-1.3H17V2.6c-.3 0-1.3-.1-2.4-.1-2.4 0-4.1 1.5-4.1 4.2v1.8H8v3h2.5V22H14v-7.5h2.5l.4-3H14Z"/></svg>,
    star: <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinejoin="round"><path d="m12 3.5 2.6 5.3 5.9.9-4.3 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8L3.5 9.7l5.9-.9L12 3.5Z"/></svg>,
  };
  const items = [];
  const k = kind || (l && l.kind) || "business";
  const showReviews = !!(C().KIND_SHOWS_REVIEWS && C().KIND_SHOWS_REVIEWS[k]);
  if (l.address) items.push({ k: "dir", href: mapsDirUrl(l.address), t: "Directions", icon: I.pin });
  const tel = telHref(l.phone);
  if (tel) items.push({ k: "call", href: tel, t: "Call", icon: I.phone, sameTab: true });
  if (l.web) items.push({ k: "web", href: (l.web.startsWith("http") ? l.web : "https://" + l.web), t: "Website", icon: I.globe });
  if (l.ig) items.push({ k: "ig", href: "https://instagram.com/" + l.ig, t: "Instagram", icon: I.ig });
  if (l.fb) items.push({ k: "fb", href: "https://facebook.com/" + l.fb, t: "Facebook", icon: I.fb });
  if (showReviews && l.address) items.push({ k: "rev", href: mapsSearchUrl(l.name + " " + l.address), t: "Reviews on Google", icon: I.star });
  if (!items.length) return null;
  return (
    <div className="placelinks-wrap">
      {label !== false && <span className="placelinks__label">Find &amp; follow</span>}
      <div className="placelinks">
        {items.map((it) => (
          <a key={it.k} className="placelink" href={it.href} title={it.t} aria-label={it.t}
             target={it.sameTab ? undefined : "_blank"} rel={it.sameTab ? undefined : "noopener"}>
            {it.icon}
          </a>
        ))}
      </div>
    </div>
  );
}

/* ---------- SHARE ROW ---------- */
function ShareRow({ title, url, dark }) {
  const [copied, setCopied] = useState(false);
  const link = url || location.href;
  const enc = encodeURIComponent;
  const copy = async () => {
    try { await navigator.clipboard.writeText(link); } catch (e) {}
    setCopied(true); setTimeout(() => setCopied(false), 1800);
  };
  const ICON = {
    x: <svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24h-6.66l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231 5.45-6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77Z"/></svg>,
    fb: <svg viewBox="0 0 24 24" width="15" height="15" fill="currentColor" aria-hidden="true"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073Z"/></svg>,
    mail: <svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7.5 9 6 9-6"/></svg>,
    link: <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.07 0l2.83-2.83a5 5 0 0 0-7.07-7.07l-1.4 1.4"/><path d="M14 11a5 5 0 0 0-7.07 0l-2.83 2.83a5 5 0 0 0 7.07 7.07l1.4-1.4"/></svg>,
  };
  return (
    <div className={"sharerow" + (dark ? " sharerow--dark" : "")}>
      <span className="sharerow__l">Share</span>
      <a className="sharebtn" href={"https://twitter.com/intent/tweet?url=" + enc(link) + "&text=" + enc(title)} target="_blank" rel="noopener" aria-label="Share on X" title="Share on X">{ICON.x}</a>
      <a className="sharebtn" href={"https://www.facebook.com/sharer/sharer.php?u=" + enc(link)} target="_blank" rel="noopener" aria-label="Share on Facebook" title="Share on Facebook">{ICON.fb}</a>
      <a className="sharebtn" href={"mailto:?subject=" + enc(title) + "&body=" + enc(link)} aria-label="Share by email" title="Email this story">{ICON.mail}</a>
      <button className="sharebtn sharebtn--copy" onClick={copy} aria-label="Copy link">{ICON.link}<span>{copied ? "Link copied" : "Copy link"}</span></button>
    </div>
  );
}

Object.assign(window, {
  useStore, usePhoto, useInView, Reveal, CountUp, Cover, Avatar, TimeAgo, TagRow,
  CarnationMark, IssueBar, Masthead, CNav, Ticker, SponsorWell,
  ArticleCard, RowCard, SecRule, Newsletter, CFooter, AR_URL,
  ReadingProgress, SaveButton, ShareRow, postUrl, PlaceLinks, mapsSearchUrl, mapsDirUrl, telHref, SocialRow, AuthorContact,
});
