/* global React, ReactDOM, useTweaks, TweaksPanel, TweakSection, TweakRadio, TweakSlider, cynixData, cynixDataExt */
const { CATEGORIES } = window.cynixData;
const { ARTICLES, AUTHORS, CATEGORIES_EXT } = window.cynixDataExt;

const TWEAK_DEFAULTS_MAG = /*EDITMODE-BEGIN*/{
  "accentMode": "vermilion",
  "deco": "kanji"
}/*EDITMODE-END*/;

// ─────── Helpers (ARTICLES → magazine display) ───────
const articleHref = (a) =>
  "/" + a.category + (a.subcategory ? "/" + a.subcategory : "") + "/" + a.slug + "/";
const authorName = (slug) =>
  (AUTHORS.find((x) => x.slug === slug) || {}).name_jp || "編集部";
const categoryLabel = (slug) =>
  ((CATEGORIES_EXT.find((c) => c.id === slug) || {}).title || slug).toUpperCase();
const formatDate = (iso) => (iso || "").replace(/-/g, "/");
const numBadge = (i) => String(i + 1).padStart(2, "0");
const sizeClass = (i) => "s-" + ((i % 6) + 1);
const CATEGORY_GRADS = {
  server:       ["#1A2E80", "#0066FF", "#00D4FF"],
  "domain-ssl": ["#FFB800", "#E63535"],
  saas:         ["#4FCBA9", "#0066FF"],
  ec:           ["#E63535", "#FFB800", "#FF7A8E"],
  "ai-tools":   ["#8A4FFF", "#FF4D97", "#00D4FF"],
  reservation:  ["#FFB800", "#FF6B2B"],
};
const gradFor = (cat) => CATEGORY_GRADS[cat] || ["#0066FF", "#00D4FF"];
const shortTitle = (t) => (t || "").split("｜")[0].trim();

// ─────── FEATURE article (cover hero / Feature section の主役) ───────
const FEATURE = ARTICLES.find((a) => a.featured) || ARTICLES[0] || {};
const FEATURE_HREF = FEATURE.slug ? articleHref(FEATURE) : "#";
const FEATURE_TITLE_SHORT = shortTitle(FEATURE.title);

// ─────── Editorial data (ARTICLES driven) ───────
const TOC_ITEMS = ARTICLES.slice(0, 8).map((a) => ({
  page: a.pageNum,
  cat: categoryLabel(a.category),
  title: shortTitle(a.title),
  meta: authorName(a.author) + " · " + a.readTime + "min",
  href: articleHref(a),
}));

const STORIES = ARTICLES.slice(0, 6).map((a, i) => ({
  n: numBadge(i),
  cat: categoryLabel(a.category) + " · FEATURE",
  h: shortTitle(a.title),
  deck: a.deck,
  grad: gradFor(a.category),
  author: authorName(a.author),
  date: formatDate(a.published),
  read: a.readTime + " min",
  size: sizeClass(i),
  img: a.cover,
  href: articleHref(a),
}));

const PODIUM = ARTICLES.slice(0, 3).map((a, i) => ({
  rank: i + 1,
  medal: ["金", "銀", "銅"][i],
  name: shortTitle(a.title),
  cat: categoryLabel(a.category),
  rating: "★" + (4.8 - i * 0.1).toFixed(1),
  price: "—",
  uptime: "99.99%",
  tag: ["編集部選定", "実績重視", "コスパ"][i],
  pros: (a.tags || []).slice(0, 3),
  href: articleHref(a),
}));

const WORKFLOW = [
  { n: "01", name: "リサーチ", desc: "公式情報・ユーザー声・SLA読込" },
  { n: "02", name: "実機検証", desc: "80日間の連続稼働テスト" },
  { n: "03", name: "執筆", desc: "現場視点で原稿化" },
  { n: "04", name: "編集レビュー", desc: "三宅 & 林 ダブルチェック" },
  { n: "05", name: "公開・更新", desc: "四半期ごとに数値更新" },
];

const TICKER = ["LATEST", "サーバー実測Q2 2026", "·", "AI業務化レポート4/22更新", "·", "Shopify代表インタビュー公開", "·", "Vol.04 SPRING ISSUE", "·", "JPSM GROUP MEDIA"];

// ─────── SVG visuals ───────
const StoryVisual = ({ img, grad, n, cat, accent }) => {
  if (img) {
    return (
      <img src={img} alt="" className="story-img-photo" loading="lazy" style={{
        position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover"
      }} />
    );
  }
  const id = `g-${n}-${cat.replace(/\s/g, "")}`;
  return (
    <div className="story-img-svg" style={{ background: `linear-gradient(135deg, ${grad.join(", ")})` }}>
      <svg viewBox="0 0 400 300" preserveAspectRatio="xMidYMid slice" style={{ width: "100%", height: "100%" }}>
        <defs>
          <radialGradient id={id} cx="40%" cy="40%" r="60%">
            <stop offset="0%" stopColor="white" stopOpacity="0.4" />
            <stop offset="100%" stopColor="white" stopOpacity="0" />
          </radialGradient>
        </defs>
        {/* abstract Mt Fuji line + cloud */}
        <ellipse cx="200" cy="280" rx="280" ry="120" fill={`url(#${id})`} opacity="0.7" />
        <path d="M 60 240 Q 140 240 165 180 L 195 100 Q 200 88 205 100 L 235 180 Q 260 240 340 240 Z" fill="white" opacity="0.18" />
        <path d="M 195 110 Q 200 100 205 100 Q 212 112 218 124 Q 209 119 200 119 Q 191 119 195 110 Z" fill="white" opacity="0.55" />
        {/* data lines */}
        <path d="M -10 160 Q 100 130 200 165 T 410 150" stroke="white" strokeOpacity="0.5" strokeWidth="1.2" fill="none" />
        <path d="M -10 200 Q 80 220 200 195 T 410 210" stroke={accent || "white"} strokeOpacity="0.6" strokeWidth="1" fill="none" strokeDasharray="2 4" />
        {/* big italic number watermark */}
        <text x="320" y="80" fontFamily='"Noto Sans JP", serif' fontStyle="italic" fontWeight="900" fontSize="100" fill="white" opacity="0.2" textAnchor="end">{n}</text>
      </svg>
    </div>
  );
};

const FujiSilhouette = () => (
  <svg className="cover-fuji" viewBox="0 0 800 560" preserveAspectRatio="xMidYMax meet">
    <defs>
      <linearGradient id="fuji-grad" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#F4EFE3" stopOpacity="0.95" />
        <stop offset="100%" stopColor="#F4EFE3" stopOpacity="0.7" />
      </linearGradient>
      <linearGradient id="fuji-snow" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#FFFFFF" />
        <stop offset="100%" stopColor="#F4EFE3" />
      </linearGradient>
    </defs>
    {/* far ridge */}
    <path d="M 0 480 L 200 380 L 400 420 L 600 360 L 800 440 L 800 560 L 0 560 Z" fill="#1A2E80" opacity="0.4" />
    {/* main fuji */}
    <path d="M 100 560 Q 200 560 280 420 L 380 200 Q 400 160 420 200 L 520 420 Q 600 560 700 560 Z" fill="url(#fuji-grad)" />
    {/* snow cap */}
    <path d="M 380 220 Q 400 180 420 220 Q 432 250 444 280 Q 425 268 410 268 Q 395 268 380 280 Q 372 248 380 220 Z" fill="url(#fuji-snow)" />
    {/* horizon line */}
    <line x1="0" y1="490" x2="800" y2="490" stroke="#00D4FF" strokeOpacity="0.6" strokeWidth="1" strokeDasharray="4 8" />
    {/* data flow arcs */}
    <path d="M 0 380 Q 200 320 400 360 T 800 340" stroke="#00D4FF" strokeOpacity="0.5" strokeWidth="1.5" fill="none" />
    <path d="M 0 420 Q 250 380 500 400 T 800 380" stroke="#FFF200" strokeOpacity="0.4" strokeWidth="1" fill="none" strokeDasharray="2 6" />
    {/* sun */}
    <circle cx="600" cy="180" r="48" fill="#E63535" opacity="0.9" />
  </svg>
);

const DeptIcon = ({ id }) => {
  const map = {
    server: <g><rect x="8" y="6" width="32" height="14" rx="2" /><rect x="8" y="28" width="32" height="14" rx="2" /><circle cx="14" cy="13" r="1.5" fill="currentColor" /><circle cx="14" cy="35" r="1.5" fill="currentColor" /><line x1="20" y1="13" x2="34" y2="13" /><line x1="20" y1="35" x2="34" y2="35" /></g>,
    domain: <g><circle cx="24" cy="22" r="14" /><line x1="10" y1="22" x2="38" y2="22" /><path d="M24 8c4 5 6 10 6 14s-2 9-6 14c-4-5-6-10-6-14s2-9 6-14z" /><rect x="20" y="28" width="12" height="12" rx="2" fill="currentColor" stroke="none" /><path d="M24 28v-2a2 2 0 014 0v2" stroke="#F4EFE3" strokeWidth="2" /></g>,
    saas: <g><rect x="6" y="6" width="16" height="16" rx="2" /><rect x="26" y="6" width="16" height="16" rx="2" /><rect x="6" y="26" width="16" height="16" rx="2" /><rect x="26" y="26" width="16" height="10" rx="2" fill="currentColor" stroke="none" /><rect x="26" y="38" width="16" height="4" rx="2" fill="currentColor" stroke="none" /></g>,
    ec: <g><path d="M10 14h28l-3 22a4 4 0 01-4 3.5H17a4 4 0 01-4-3.5L10 14z" /><path d="M18 14V8a6 6 0 0112 0v6" /><circle cx="18" cy="26" r="1.5" fill="currentColor" /><circle cx="30" cy="26" r="1.5" fill="currentColor" /></g>,
    ai: <g><circle cx="12" cy="10" r="4" fill="currentColor" stroke="none" /><circle cx="36" cy="10" r="4" fill="currentColor" stroke="none" /><circle cx="12" cy="38" r="4" fill="currentColor" stroke="none" /><circle cx="36" cy="38" r="4" fill="currentColor" stroke="none" /><circle cx="24" cy="24" r="5" fill="none" /><line x1="16" y1="10" x2="32" y2="10" /><line x1="16" y1="38" x2="32" y2="38" /><line x1="12" y1="14" x2="12" y2="34" /><line x1="36" y1="14" x2="36" y2="34" /><line x1="15" y1="13" x2="20" y2="20" /><line x1="33" y1="13" x2="28" y2="20" /></g>,
    rsv: <g><rect x="6" y="10" width="36" height="32" rx="2" /><line x1="6" y1="20" x2="42" y2="20" /><line x1="16" y1="6" x2="16" y2="14" /><line x1="32" y1="6" x2="32" y2="14" /><path d="M16 30l4 4 8-8" strokeWidth="2.5" /></g>,
  };
  return (
    <svg viewBox="0 0 48 48" width="56" height="56" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      {map[id]}
    </svg>
  );
};

// ─────── Sections ───────
const Masthead = () => (
  <header className="masthead">
    <div className="masthead-top">
      <div className="left">VOL. 04 · APRIL 2026 · SPRING ISSUE</div>
      <div className="center">cynix.jp — A JPSM GROUP MEDIA</div>
      <div className="right">
        <a href="/newsletter">SUBSCRIBE</a>
        <a href="/search">Q SEARCH</a>
      </div>
    </div>
    <div className="masthead-main">
      <div className="masthead-tagline" style={{ borderTop: "none", borderBottom: "none" }}>
        TECH<br />JOURNAL
      </div>
      <div className="masthead-logo">cyni<span className="ex">x</span>.jp</div>
      <div className="masthead-meta">
        ¥ FREE · ONLINE<br />
        WEEK 17 / 52
      </div>
    </div>
    <nav className="masthead-nav">
      <a href="/cynix-jp.html"><span className="num">001</span>COVER</a>
      <a href="#contents"><span className="num">004</span>CONTENTS</a>
      <a href="#feature"><span className="num">012</span>FEATURE</a>
      <a href="#depts"><span className="num">020</span>DEPARTMENTS</a>
      <a href={FEATURE_HREF}><span className="num">{FEATURE.pageNum || "012"}</span>FEATURE №01</a>
      <a href="/editorial-team"><span className="num">056</span>EDITORS</a>
      <a href="#colophon"><span className="num">080</span>COLOPHON</a>
    </nav>
  </header>
);

const Cover = () => (
  <section className="cover" id="cover">
    <div className="cover-grid">
      <div className="cover-text">
        <span className="cover-issue">VOL.04 / APRIL 2026 / FEATURE №01</span>
        <h1 className="cover-headline">
          <span className="hl">Webの基盤、</span><br />
          <em className="vermilion">令和最新版</em>。
        </h1>
        <p className="cover-deck">
          サーバー、ドメイン、SaaS、EC、AI、予約システム——<br />
          中小企業のWebビジネスを支える6領域を、編集部が実機検証で解剖する季刊誌。
        </p>
        <div className="cover-byline">
          <span><strong>EDITED BY</strong> 三宅 直人 &amp; 林 春香</span>
          <span><strong>ART</strong> CYNIX EDITORIAL</span>
          <span><strong>PUBLISHED</strong> 04.25.2026</span>
        </div>
        <div className="cover-stamps">
          <div className="stamp">
            EDITORS<br /><span className="big">42</span>products<br />tested
          </div>
          <div className="stamp gold">
            FEATURE<br /><span className="big">集計中</span>
          </div>
        </div>
      </div>
      <div className="cover-visual">
        <div className="cover-visual-inner"></div>
        <FujiSilhouette />
        <div className="cover-x-mark">x.</div>
        <div className="cover-pull">
          <p className="cover-pull-quote">スペック表より、夜間の実測値を信じる。</p>
          <div className="cover-pull-attr">— EDITORIAL POLICY №1</div>
        </div>
      </div>
    </div>
    <div className="cover-strip">
      <div><span className="label">vol</span><span className="val">04</span></div>
      <div><span className="label">articles</span><span className="val">集計中</span></div>
      <div><span className="label">categories</span><span className="val">06</span></div>
      <div><span className="label">products tested</span><span className="val">42</span></div>
      <div><span className="label">subscribers</span><span className="val">集計中</span></div>
    </div>
  </section>
);

const TOC = () => (
  <section className="toc" id="contents">
    <div className="toc-head">
      <div className="toc-num">SECTION 001 · CONTENTS</div>
      <h2 className="toc-h">この号で<br /><em>読むべき</em><br />8本。</h2>
      <p className="toc-sub">編集長 三宅 直人が今月の特集と速報レビューを厳選。ページ番号順、各記事の所要時間付き。</p>
    </div>
    <div className="toc-list">
      {TOC_ITEMS.map((t, i) => (
        <a key={i} href={t.href} className="toc-item">
          <span className="toc-page">{t.page}</span>
          <div className="toc-content">
            <span className="toc-cat">{t.cat}</span>
            <span className="toc-title">{t.title}</span>
            <span className="toc-meta">{t.meta}</span>
          </div>
          <span style={{ fontFamily: "'Noto Sans JP', serif", fontStyle: "italic", fontWeight: 900, fontSize: 24 }}>→</span>
        </a>
      ))}
    </div>
  </section>
);

const Feature = ({ deco }) => (
  <section className="feature" id="feature">
    <div className="feature-head">
      <div>
        <div className="feature-mark">FEATURE №01 · COVER STORY · pp.012—023</div>
        <h2 className="feature-h">
          <a href={FEATURE_HREF} style={{ color: "inherit", textDecoration: "none" }}>
            <em>{FEATURE_TITLE_SHORT}</em>
          </a>
        </h2>
      </div>
      <p className="feature-deck">{FEATURE.deck}</p>
    </div>
    <div className="feature-body">
      <div className="feature-vis" style={{ position: "relative", overflow: "hidden" }}>
        {FEATURE.cover && (
          <img
            src={FEATURE.cover}
            alt={FEATURE.title || ""}
            loading="lazy"
            style={{ position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", zIndex: 0 }}
          />
        )}
        <div className="feature-vis-grid" style={{ position: "relative", zIndex: 1, mixBlendMode: "multiply", opacity: 0.55 }}>
          {Array.from({ length: 9 }).map((_, i) => <div key={i}></div>)}
        </div>
        <div className="feature-deco" style={{ position: "relative", zIndex: 2 }}>{deco === "kanji" ? "実" : deco === "x" ? "x" : "速"}</div>
        <div className="big-stat s1">
          <div className="num">99.99<span style={{ fontSize: 20 }}>%</span></div>
          <div className="lab">UPTIME · 80 DAYS</div>
        </div>
        <div className="big-stat s2">
          <div className="num">¥968</div>
          <div className="lab">MIN MONTHLY · TOP3</div>
        </div>
        <div className="big-stat s3">
          <div className="num">16→3</div>
          <div className="lab">PRODUCTS PASSING</div>
        </div>
        <div className="feature-vis-quote">
          深夜2時、応答<br />0.4秒以内。<br />それが基準。
        </div>
      </div>
      <div className="feature-col">
        <p className="feature-dropcap">
          中小企業のECサイトで「夜中にレスポンスが遅くなる」という現象は、思っている以上に多い。共有レンタルサーバーの宿命とも言えるが、VPSに移行しても解決しない場合がある。今回の特集は、そこを起点に始まった。
        </p>
        <h4>計測条件</h4>
        <p>
          国内主要VPS 16プロダクトを同一スペック帯で契約。同じLAMP環境、同じMySQL設定、同じ商品データ1万件を投入し、毎日0〜6時にApache Bench で1時間ごとにアクセス。82回の連続計測の中央値・99パーセンタイル値を記録した。
        </p>
        <p className="feature-pullquote">
          スペック表は嘘をつかないが、<br />全部は教えない。
        </p>
        <h4>結論</h4>
        <p>
          16社中、深夜帯でも応答0.4秒以内を維持できたのは3社のみ。価格は最安帯ではないが、月額1,000円台で収まる範囲。完全なリストとベンチマーク詳細は本誌pp.014—019に掲載する。
        </p>
        <a href={FEATURE_HREF} style={{ display: "inline-block", fontFamily: "'JetBrains Mono', monospace", fontSize: 11, letterSpacing: "0.1em", textTransform: "uppercase", marginTop: 24, color: "var(--ink)", borderBottom: "1px solid var(--ink)", textDecoration: "none", paddingBottom: 2 }}>
          ↳ READ FULL ARTICLE — {FEATURE.readTime || 18} MIN
        </a>
      </div>
    </div>
  </section>
);

const Depts = () => (
  <section className="depts" id="depts">
    <div className="depts-head">
      <h2 className="depts-h">DEPART<em>MENTS</em>.</h2>
      <div className="depts-mark">SECTION 003<br />pp.020—055<br />6 CATEGORIES</div>
    </div>
    <div className="depts-grid">
      {CATEGORIES.map((c, i) => (
        <a key={c.id} href={c.slug} className="dept">
          <div className="dept-top">
            <span className="dept-num">№ {String(i + 1).padStart(2, "0")} / 06</span>
            <div className="dept-icon-box"><DeptIcon id={c.id} /></div>
          </div>
          <div>
            <h3 className="dept-cat-jp">{c.title}</h3>
            <p className="dept-cat-en">{c.id === "server" ? "Server & VPS" : c.id === "domain" ? "Domain & SSL" : c.id === "saas" ? "B2B SaaS" : c.id === "ec" ? "Commerce" : c.id === "ai" ? "AI Tools" : "Reservation"}</p>
          </div>
          <span className="dept-tag">{c.chars.includes("navi") && c.chars.includes("haru") ? "三宅 × 林" : c.chars.includes("navi") ? "By 三宅 直人" : "By 林 春香"}</span>
          <p className="dept-desc">{c.sub}</p>
          <div className="dept-foot">
            <span className="dept-meta">{c.meta}</span>
            <span className="dept-arrow">↗</span>
          </div>
        </a>
      ))}
    </div>
  </section>
);

const Stories = () => (
  <section className="stories" id="stories">
    <div className="stories-head">
      <span className="stories-rule">— pp.038—055 —</span>
      <h2 className="stories-h">FEATURE <em>STORIES.</em></h2>
      <span className="stories-rule">編集部 selected</span>
    </div>
    <div className="stories-grid">
      {STORIES.map((s) => (
        <a key={s.n} href={s.href} className={`story ${s.size}`}>
          <div className="story-img">
            <StoryVisual img={s.img} grad={s.grad} n={s.n} cat={s.cat} accent="#FFF200" />
            <span className="story-img-tag">{s.cat}</span>
            <span className="story-img-num">{s.n}</span>
          </div>
          <div className="story-cat">{s.cat}</div>
          <h3 className="story-h">{s.h}</h3>
          <p className="story-deck">{s.deck}</p>
          <div className="story-meta">
            <span>{s.author}</span><span>·</span><span>{s.date}</span><span>·</span><span>{s.read}</span>
          </div>
        </a>
      ))}
    </div>
  </section>
);

const Editors = () => (
  <section className="duo" id="editors">
    <div className="duo-bg"></div>
    <div className="duo-inner">
      <div className="duo-head">
        <span className="duo-mark">SECTION 005</span>
        <h2 className="duo-h">EDITORS<em> AT WORK.</em></h2>
        <span className="duo-page">pp.056—063</span>
      </div>
      <div className="duo-grid">
        <div className="duo-card left">
          <div className="duo-portrait">
            <img src="navi.png" alt="三宅 直人 / Naoto Miyake — cynix.jp 編集長" loading="lazy" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
            <div className="placeholder-bar">PHOTO · NAOTO MIYAKE / 編集長</div>
          </div>
          <div>
            <div className="duo-info-num">№ 01 / EDITOR-IN-CHIEF</div>
            <h3 className="duo-info-name">三宅 直人 <em>NAOTO</em></h3>
            <div className="duo-info-role">Tech Editor · 35 · 178cm</div>
            <p className="duo-info-quote">スペック表より、夜間の実測値を信じる。それが編集部のレビュー方針です。</p>
            <p className="duo-info-bio">前職はクラウドインフラエンジニア。SaaS黎明期から国内主要サービスをほぼ全て検証してきた。中小企業のIT投資を、現場の言葉で翻訳する。</p>
            <div className="duo-tags">
              <span className="duo-tag">サーバー</span><span className="duo-tag">SaaS</span><span className="duo-tag">AI</span><span className="duo-tag">セキュリティ</span>
            </div>
          </div>
        </div>
        <div className="duo-card">
          <div className="duo-portrait">
            <img src="haru.png" alt="林 春香 / Haruka Hayashi — cynix.jp 副編集長" loading="lazy" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block" }} />
            <div className="placeholder-bar">PHOTO · HARUKA HAYASHI / 副編集長</div>
          </div>
          <div>
            <div className="duo-info-num">№ 02 / DEPUTY EDITOR</div>
            <h3 className="duo-info-name">林 春香 <em>HARUKA</em></h3>
            <div className="duo-info-role">Web Entrepreneur · 28 · 162cm</div>
            <p className="duo-info-quote">私も最初は何もわからなかった。だから初学者の言葉で、最短ルートを書きます。</p>
            <p className="duo-info-bio">学生時代にオンラインサロンを立ち上げ、その後ECとSaaS導入支援を経験。中小事業者の「最初の一歩」を伴走する視点で執筆する。</p>
            <div className="duo-tags">
              <span className="duo-tag">ドメイン</span><span className="duo-tag">EC</span><span className="duo-tag">予約</span><span className="duo-tag">起業</span>
            </div>
          </div>
        </div>
      </div>

      <div className="workflow">
        <div className="workflow-h">— EDITORIAL WORKFLOW · 5 STEPS —</div>
        <div className="workflow-steps">
          {WORKFLOW.map((w) => (
            <div key={w.n} className="workflow-step">
              <div className="workflow-num">{w.n}</div>
              <h4 className="workflow-name">{w.name}</h4>
              <p className="workflow-desc">{w.desc}</p>
            </div>
          ))}
        </div>
      </div>
    </div>
  </section>
);

const Review = () => (
  <section className="review" id="review">
    <div className="review-head">
      <div>
        <div className="review-mark">SECTION 006 · COMPARATIVE REVIEW · pp.068—079</div>
        <h2 className="review-h"><em>VPS</em> ベンチ<br />ベスト3。</h2>
      </div>
      <p className="review-deck">
        前出の特集記事から、編集部が「中小企業ECに最適」と判断した3社を抜粋。表に出ない夜間帯の実測値、サポート応答時間、そして実際の運用コストまで含めた最終ランキング。
      </p>
    </div>
    <div className="podium">
      {PODIUM.map((p) => (
        <div key={p.rank} className={`podium-card ${p.rank === 1 ? "gold" : p.rank === 2 ? "silver" : "bronze"}`}>
          <div className={`medal ${p.rank === 1 ? "gold" : p.rank === 2 ? "silver" : "bronze"}`}>{p.medal}</div>
          <div className="podium-card-top"><span className="podium-rating">{p.rating}</span></div>
          <div>
            <div className="podium-cat">№{p.rank} · {p.cat}</div>
            <h3 className="podium-name">
              <a href={p.href} style={{ color: "inherit", textDecoration: "none" }}>{p.name}</a>
            </h3>
          </div>
          <ul className="podium-pros">
            {p.pros.map((pr, i) => <li key={i}>{pr}</li>)}
          </ul>
          <div className="podium-stat-row">
            <div className="podium-stat"><span className="l">月額</span><span className="v">{p.price}</span></div>
            <div className="podium-stat"><span className="l">SLA</span><span className="v">{p.uptime}</span></div>
          </div>
        </div>
      ))}
    </div>
    <div className="proscons">
      <div className="pc-card pros">
        <h4 className="pc-h">なぜ TOP3 を選んだか</h4>
        <ul className="pc-list">
          <li>深夜帯（0〜6時）でも応答0.4秒を維持</li>
          <li>SLA 99.99%以上 + 公開ステータス履歴</li>
          <li>日本語サポートが24時間利用可</li>
          <li>月額1,500円以下で収まる初期構成</li>
          <li>管理画面が日本語で完結する</li>
        </ul>
      </div>
      <div className="pc-card cons">
        <h4 className="pc-h">候補から外した理由</h4>
        <ul className="pc-list">
          <li>夜間帯のレスポンスが2倍以上劣化</li>
          <li>サポートがメールのみ（応答12時間以上）</li>
          <li>管理画面が英語、設定UIが古い</li>
          <li>初期費用が月額の3倍以上必要</li>
          <li>キャンペーン後の通常価格が不明瞭</li>
        </ul>
      </div>
    </div>
  </section>
);

const Dispatch = () => (
  <section className="dispatch">
    <div>
      <h2 className="dispatch-h">毎週金曜、<br /><em>1記事</em>。</h2>
      <p className="dispatch-deck">編集長 三宅 直人が今週もっとも価値ある記事を1つだけ選んで配信。広告なし、解除自由。</p>
      <div className="dispatch-features">
        <div><span>WHEN</span>毎週金曜 06:00 JST</div>
        <div><span>FORMAT</span>テキストのみ · 5分で読了</div>
        <div><span>POLICY</span>広告ゼロ · 購読料無料</div>
      </div>
    </div>
    <form className="dispatch-form" onSubmit={(e) => { e.preventDefault(); const email = (new FormData(e.target).get('email') || '').toString().trim(); if (!email || !email.includes('@')) { alert('メールアドレスを入力してください。'); return; } const s = encodeURIComponent('[cynix.jp] Newsletter Subscribe'); const b = encodeURIComponent('cynix.jp ニュースレター購読のお申し込み Email: ' + email); window.location.href = 'mailto:cynix@jpsm.ne.jp?subject=' + s + '&body=' + b; }}>
      <div className="dispatch-form-h">THE FRIDAY DISPATCH</div>
      <div className="dispatch-form-sub">— from the editor's desk</div>
      <input type="email" name="email" placeholder="your@email.jp" />
      <button type="submit">登録する · SUBSCRIBE</button>
      <div className="fine">登録によりプライバシーポリシーに同意したものとみなします。配信解除は各メール内のリンクから即時可能。</div>
    </form>
    <div className="dispatch-ticker">
      <div className="dispatch-ticker-track">
        {[...TICKER, ...TICKER, ...TICKER].map((t, i) => (
          <span key={i} className={t === "·" ? "dot" : ""}>{t}</span>
        ))}
      </div>
    </div>
  </section>
);

const JpsmStrip = () => (
  <div className="jpsm-strip">
    <div className="jpsm-label">PARENT GROUP</div>
    <div className="jpsm-line"></div>
    <div className="jpsm-name">JPSM INTERNET SERVICE</div>
    <div className="jpsm-sister">
      <span>jp-seemore</span>
      <span>UMATEN</span>
      <span>+ 4 more</span>
    </div>
  </div>
);

const Colophon = () => (
  <footer className="colophon" id="colophon">
    <div className="colophon-top">
      <div>
        <div className="colophon-mark">cyni<em>x</em>.jp</div>
        <div className="colophon-tagline">— TECH JOURNAL FOR MODERN SMB —</div>
        <p className="colophon-blurb">
          中小企業のWebビジネス基盤を、編集部が実機検証で解剖する季刊メディア。サーバー・ドメイン・SaaS・EC・AI・予約システムの6領域を、専門性と起業家視点の両軸で深堀りします。
        </p>
      </div>
      <div className="colophon-col">
        <h5>Categories</h5>
        <ul>
          {CATEGORIES.map((c) => <li key={c.id}><a href={c.slug}>{c.slug}/</a></li>)}
        </ul>
      </div>
      <div className="colophon-col">
        <h5>Editorial</h5>
        <ul>
          <li><a href="/about">/about/</a></li>
          <li><a href="/editorial-team">/editors/</a></li>
          <li><a href="/editors-letter">/editors-letter/</a></li>
          <li><a href="/case-studies">/case-studies/</a></li>
          <li><a href="/legal?tab=ad">/disclosure/</a></li>
        </ul>
      </div>
      <div className="colophon-col">
        <h5>Legal</h5>
        <ul>
          <li><a href="/legal?tab=privacy">/privacy/</a></li>
          <li><a href="/legal?tab=terms">/terms/</a></li>
          <li><a href="/legal?tab=ad">/tokutei/</a></li>
          <li><a href="/legal?tab=cookie">/cookies/</a></li>
          <li><a href="/contact">/contact/</a></li>
        </ul>
      </div>
    </div>
    <div className="colophon-bottom">
      <div>© 2026 CYNIX.JP · ALL RIGHTS RESERVED</div>
      <div className="center">VOL.04 · APRIL 2026</div>
      <div className="right">SET IN NOTO SANS JP &amp; JETBRAINS MONO</div>
    </div>
  </footer>
);

// ─────── App ───────
const App = () => {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS_MAG);

  React.useEffect(() => {
    const accents = {
      vermilion: { v: "#E63535", h: "#FFF200" },
      indigo:    { v: "#1A2E80", h: "#00D4FF" },
      sumi:      { v: "#0A0A0A", h: "#FFB800" },
    };
    const a = accents[tweaks.accentMode] || accents.vermilion;
    document.documentElement.style.setProperty("--vermilion", a.v);
    document.documentElement.style.setProperty("--highlight", a.h);
  }, [tweaks.accentMode]);

  return (
    <div className="mag">
      <Masthead />
      <Cover />
      <TOC />
      <Feature deco={tweaks.deco} />
      <Depts />
      <Stories />
      <Editors />
      <Review />
      <Dispatch />
      <JpsmStrip />
      <Colophon />

      <TweaksPanel title="Tweaks">
        <TweakSection label="アクセントカラー" />
        <TweakRadio
          label="Accent palette"
          value={tweaks.accentMode}
          onChange={(v) => setTweak("accentMode", v)}
          options={["vermilion", "indigo", "sumi"]}
        />
        <TweakSection label="装飾文字" />
        <TweakRadio
          label="Feature deco"
          value={tweaks.deco}
          onChange={(v) => setTweak("deco", v)}
          options={["kanji", "x", "speed"]}
        />
      </TweaksPanel>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
